File Coverage

blib/lib/LyricFinder.pm
Criterion Covered Total %
statement 33 164 20.1
branch 0 56 0.0
condition 0 48 0.0
subroutine 11 16 68.7
pod 4 4 100.0
total 48 288 16.6


line stmt bran cond sub pod time code
1             package LyricFinder;
2              
3             require 5.001;
4              
5 1     1   92482 use strict;
  1         2  
  1         28  
6 1     1   3 use warnings;
  1         2  
  1         42  
7 1     1   14 use Carp;
  1         1  
  1         50  
8 1     1   320 use parent 'LyricFinder::_Class';
  1         237  
  1         4  
9              
10             # LyricFinder - A Derived work, by (c) 2020-2026 Jim Turner of:
11             #
12             # Lyrics Fetcher
13             #
14             # Copyright (C) 2007-2020 David Precious (CPAN: BIGPRESH)
15             #
16             # Originally authored by and copyright (C) 2003 Sir Reflog
17             # who kindly passed maintainership on to David Precious in Feb 2007
18             #
19             # Original idea:
20             # Copyright (C) 2003 Zachary P. Landau
21             # All rights reserved.
22             #
23             # This program is free software; you can redistribute it and/or modify
24             # it under the terms of the GNU General Public License as published by
25             # the Free Software Foundation; either version 2 of the License, or
26             # (at your option) any later version.
27             #
28             # This program is distributed in the hope that it will be useful,
29             # but WITHOUT ANY WARRANTY; without even the implied warranty of
30             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31             # GNU General Public License for more details.
32             #
33             # You should have received a copy of the GNU General Public License
34             # along with this program; if not, write to the Free Software
35             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36              
37             our $VERSION = '1.60';
38             our $DEBUG = 0; # If you want debug messages, set debug to a true value
39              
40             my @supported_mods = (qw(Cache ApiLyricsOvh AZLyrics Genius Letras Lrclib Musixmatch));
41              
42             my %haveit;
43              
44             foreach my $module (@supported_mods)
45             {
46             $haveit{$module} = 0;
47 1     1   736 eval "use LyricFinder::$module; \$haveit{$module} = 1; 1";
  1     1   4  
  1     1   40  
  1     1   686  
  1     1   5  
  1     1   41  
  1     1   596  
  1         5  
  1         41  
  1         635  
  1         5  
  1         42  
  1         623  
  1         4  
  1         41  
  1         721  
  1         4  
  1         42  
  1         692  
  1         4  
  1         56  
48             }
49              
50             sub new
51             {
52 0     0 1   my $class = shift;
53              
54             #EXTRACT ANY MAIN-SPECIFIC ARGUMENTS (NOT TO BE PASSED TO SUBMODULES):
55 0           my @args = ();
56 0           while (@_) {
57 0           my $arg = shift(@_);
58 0 0         if ($arg =~ /^\-omit$/o) { #ALLOW USER TO OMIT SPECIFIC INSTALLED SUBMODULE(S):
59 0           my $omit = shift(@_);
60 0 0         my @omitModules = ref($omit) ? @{$omit} : split(/\,\s*/, $omit);
  0            
61 0           foreach my $omit (@omitModules)
62             {
63 0 0 0       $haveit{$omit} = 0 if (defined($haveit{$omit}) && $haveit{$omit});
64             }
65             } else {
66 0           push @args, $arg;
67             }
68             }
69              
70 0           my $self = $class->SUPER::new('', @args);
71             # @{$self->{'_fetchers'}} = @FETCHERS;
72 0           @{$self->{'_fetchers'}} = ();
  0            
73 0           @{$self->{'_FETCHERS'}} = ();
  0            
74             #NOTE: UPPER CASE _FETCHERS USED FOR "random" AND "all", & *NEVER* INCLUDES CACHE (1ST SUBMODULE TRIES CACHE)!:
75             #LOWER CASE _fetchers INCLUDES CACHE FIRST IF CACHE DIRECTORY AND IT'S NOT WRITEONLY, AS THIS IS FOR ORDER/TRIED LIST!
76 0           foreach my $module (@supported_mods)
77             {
78 0 0 0       next unless ($haveit{$module} && $module ne 'Cache');
79 0           push @{$self->{'_FETCHERS'}}, $module;
  0            
80 0           push @{$self->{'_fetchers'}}, $module;
  0            
81             }
82            
83 0           unshift(@{$self->{'_fetchers'}}, 'Cache') if ($haveit{'Cache'}
84 0 0 0       && $self->{'-cache'} && $self->{'-cache'} !~ /^\>/);
      0        
85              
86 0           bless $self, $class; #BLESS IT!
87              
88 0           return $self;
89             }
90              
91             sub order {
92 0     0 1   my $self = shift;
93 0 0         return wantarray ? split(/\,/, $self->{'Order'}) : $self->{'Order'};
94             }
95              
96             sub tried {
97 0     0 1   my $self = shift;
98 0 0         return wantarray ? split(/\,/, $self->{'Tried'}) : $self->{'Tried'};
99             }
100              
101             sub _fetch {
102 0     0     my ($self, $artist, $title, $fetchers) = @_;
103              
104 0           $self->_debug("LyricFinder::_fetch($artist, $title, $fetchers)!");
105 0 0 0       if (!$artist || !$title || ref $artist || ref $title) {
      0        
      0        
106 0           carp("e:_fetch() called without artist and title.");
107 0           return;
108             }
109              
110 0 0 0       if (!$fetchers || ref $fetchers ne 'ARRAY') {
111 0           carp("e:_fetch not given arrayref of fetchers to try");
112 0           return;
113             }
114              
115 0           for my $fetcher (@$fetchers) {
116 0           $self->{'Url'} = '';
117 0           $self->_debug("..Trying fetcher $fetcher for artist:$artist title:$title");
118              
119 0           my $fetcherpkg = __PACKAGE__ . "::$fetcher";
120 0           my $finderModule = 0;
121 0           eval "\$finderModule = new ${fetcherpkg}(\%{\$self});";
122 0 0 0       if ($@ || !$finderModule) {
123 0           carp("w:Failed to load sub-module $fetcherpkg ($@)");
124 0           next;
125             }
126            
127             # OK, we require()d this fetcher, try using it:
128 0           $self->{'Error'} = 'Ok';
129 0           $self->_debug("..Source module $fetcher loaded OK");
130 0           $self->{'Tried'} .= "$fetcher,";
131 0 0         if (!$finderModule->can('fetch')) {
132 0           $self->_debug("e:Source LyricFinder::$fetcher can't ->fetch($finderModule->{'Error'})");
133 0           next;
134             }
135            
136 0           $self->_debug("..Trying to fetch with $fetcher");
137 0           my $lyrics = $finderModule->fetch($artist, $title);
138 0           $self->{'Error'} = $finderModule->message();
139 0           $self->{'Url'} = $finderModule->url();
140 0 0         if ($self->{'Error'} eq 'Ok') {
141 0           $self->_debug("..Source: $fetcher returned lyrics");
142 0 0 0       if (defined($lyrics) && $lyrics =~ /\S/o) {
143 0           $self->{'Source'} = $finderModule->source();;
144 0           $self->{'Site'} = $finderModule->site();
145 0           $self->{'image_url'} = $finderModule->image_url();
146 0           @{$self->{'Credits'}} = $finderModule->credits();
  0            
147 0           $self->{'Tried'} =~ s/\,$//;
148 0           $self->_debug("i:Lyrics fetched from: ".$self->{'Source'});
149              
150 0           return $lyrics;
151             }
152             }
153             }
154              
155             # if we get here, we tried all sites we were asked to try, and none
156             # of them worked.
157 0 0         $self->{'Error'} = 'e:All sites failed to fetch lyrics!' if ($#{$fetchers} > 0);
  0            
158 0           $self->{'Tried'} =~ s/\,$//;
159            
160 0           return undef;
161             }
162              
163             sub fetch {
164 0     0 1   my ($self, $artist, $title, $fetcherspec, $limit) = @_;
165              
166 0           my @tryfetchers = ();
167              
168 0           $self->_debug("LyricFinder::fetch($artist, $title, $fetcherspec)!");
169 0           $self->{'Tried'} = '';
170              
171 0 0         $fetcherspec = 'random' unless (defined $fetcherspec);
172 0 0         unless (ref $fetcherspec)
173             {
174 0 0         $fetcherspec = 'random' unless ($fetcherspec =~ /\w/);
175 0           $fetcherspec = [split /\s*\,\s*/, $fetcherspec],
176             }
177 0 0         $fetcherspec = ['random'] unless ($#{$fetcherspec} >= 0);
  0            
178 0 0         if ($#{$fetcherspec} > 0) {
  0            
179 0           my $specstring = join('|',@{$fetcherspec});
  0            
180 0 0         if ($specstring =~ /\b(Cache|auto|all)\b/i) {
181 0           carp("s:'$1' can only be used by itself, not in a list!");
182 0           return;
183             }
184             }
185 0           $self->{'Source'} = 'none';
186              
187             # we've got an arrayref of fetchers to use:
188 0           my %usedSources = (); #avoid including any module name more than once in the list.
189 0           for my $fetcher (@{$fetcherspec}) {
  0            
190 0 0 0       if (grep /$fetcher/, @{$self->{'_FETCHERS'}}) { #VALID FETCHER(S) SPECIFIED:
  0 0          
    0          
    0          
191 0           push @tryfetchers, $fetcher;
192 0           $usedSources{$fetcher} = 1;
193             } elsif ($fetcher =~ m'^random$'i) { #"random" SPECIFIED (CONVERTS TO A RANDOM LIST OF FETCHERS:
194 0           my $random_fetcher;
195 0           my $usedcnt = 0;
196 0           foreach my $t (@tryfetchers) {
197 0           $usedSources{$t} = 1;
198 0           ++$usedcnt;
199             }
200 0           my $fetchersCnt = scalar(@{$self->{'_FETCHERS'}});
  0            
201 0           my $maxcnt = ($fetchersCnt - $#{$fetcherspec}) + $usedcnt;
  0            
202 0 0 0       $maxcnt -= ($fetchersCnt - $limit) if (defined($limit) && $limit > 0 && $fetchersCnt > $limit);
      0        
203            
204             #NOTE: usedcnt = # OF ITEMS (IF ANY) SPECIFIED LEFT OF "random"!
205 0           while (scalar(@tryfetchers) < $maxcnt) {
206 0           $random_fetcher = int(rand(scalar @{$self->{'_FETCHERS'}}));
  0            
207 0 0 0       unless ($usedSources{${$self->{'_FETCHERS'}}[$random_fetcher]}
  0            
208 0           || grep(/${$self->{'_FETCHERS'}}[$random_fetcher]/, @{$fetcherspec})) {
  0            
209 0           push @tryfetchers, ${$self->{'_FETCHERS'}}[$random_fetcher];
  0            
210 0           $usedSources{${$self->{'_FETCHERS'}}[$random_fetcher]} = 1;
  0            
211 0           $usedcnt++;
212             }
213             }
214             } elsif ($fetcher =~ m'^Cache$'i && $haveit{'Cache'}) { #The Cache MODULE SPECIFIED:
215 0           @tryfetchers = ('Cache');
216 0           last; #these values can only be used once (first) & by itself!
217             } elsif ($fetcher =~ m'^A(?:uto|ll)$'i) { #"auto" or "all" SPECIFIED (SYNONYMS):
218 0           @tryfetchers = @{$self->{'_FETCHERS'}};
  0            
219 0           last;
220             } else { #INVALID MODULE-NAME OR VALUE SPECIFIED, SKIP:
221 0           carp("e:$fetcher isn't a valid fetcher, ignoring");
222             }
223             }
224 0 0 0       $#tryfetchers = $limit - 1 if (defined($limit) && $limit > 0 && $limit <= $#tryfetchers);
      0        
225 0 0         unless ($#tryfetchers >= 0) { #NO VALID MODULE NAMES S SPECIFIED (PUNT)!:
226 0           carp("s:No valid fetchers specified!") ;
227 0           return;
228             }
229              
230 0           $self->{'Order'} = join(',', @tryfetchers);
231 0           return $self->_fetch($artist, $title, \@tryfetchers);
232             } # end of sub fetch.
233              
234             1
235              
236             __END__