| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package LyricFinder::Musixmatch; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 8 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 32 |  | 
| 4 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 26 |  | 
| 5 | 1 |  |  | 1 |  | 5 | use Carp; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 6 | 1 |  |  | 1 |  | 6 | use HTML::Strip; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 20 |  | 
| 7 | 1 |  |  | 1 |  | 5 | use URI::Escape; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 78 |  | 
| 8 | 1 |  |  | 1 |  | 7 | use parent 'LyricFinder::_Class'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | our $haveLyricsCache; | 
| 11 |  |  |  |  |  |  | BEGIN { | 
| 12 | 1 |  |  | 1 |  | 100 | $haveLyricsCache = 0; | 
| 13 | 1 |  |  | 1 |  | 70 | eval "use LyricFinder::Cache; \$haveLyricsCache = 1; 1"; | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 21 |  | 
| 14 |  |  |  |  |  |  | } | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | my $Source = 'Musixmatch'; | 
| 17 |  |  |  |  |  |  | my $Site   = 'https://www.musixmatch.com'; | 
| 18 |  |  |  |  |  |  | my $DEBUG  = 0; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub new | 
| 21 |  |  |  |  |  |  | { | 
| 22 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 0 |  |  |  |  |  | my $self = $class->SUPER::new($Source, @_); | 
| 25 | 0 |  |  |  |  |  | @{$self->{'_fetchers'}} = ($Source); | 
|  | 0 |  |  |  |  |  |  | 
| 26 | 0 |  |  |  |  |  | unshift(@{$self->{'_fetchers'}}, 'Cache')  if ($haveLyricsCache | 
| 27 | 0 | 0 | 0 |  |  |  | && $self->{'-cache'} && $self->{'-cache'} !~ /^\>/); | 
|  |  |  | 0 |  |  |  |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 | 0 |  |  |  |  |  | bless $self, $class;   #BLESS IT! | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 0 |  |  |  |  |  | return $self; | 
| 32 |  |  |  |  |  |  | } | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | sub fetch { | 
| 35 | 0 |  |  | 0 | 1 |  | my ($self, $artist_in, $song_in) = @_; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 0 |  |  |  |  |  | $self->_debug("Musixmatch::fetch($artist_in, $song_in)!"); | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 0 | 0 |  |  |  |  | return ''  unless ($self->_check_inputs($artist_in, $song_in)); | 
| 40 | 0 | 0 |  |  |  |  | return ''  if ($self->{'Error'} ne 'Ok'); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | # first, see if we've got it cached: | 
| 43 | 0 |  |  |  |  |  | $self->_debug("i:haveCache=$haveLyricsCache= -cachedir=".$self->{'-cache'}."="); | 
| 44 | 0 | 0 | 0 |  |  |  | if ($haveLyricsCache && $self->{'-cache'} && $self->{'-cache'} !~ /^\>/) { | 
|  |  |  | 0 |  |  |  |  | 
| 45 | 0 |  |  |  |  |  | my $cache = new LyricFinder::Cache(%{$self}); | 
|  | 0 |  |  |  |  |  |  | 
| 46 | 0 | 0 |  |  |  |  | if ($cache) { | 
| 47 | 0 |  |  |  |  |  | my $lyrics = $cache->fetch($artist_in, $song_in); | 
| 48 | 0 | 0 | 0 |  |  |  | if (defined($lyrics) && $lyrics =~ /\w/) { | 
| 49 | 0 |  |  |  |  |  | $self->_debug("..Got lyrics from cache."); | 
| 50 | 0 |  |  |  |  |  | $self->{'Source'} = 'Cache'; | 
| 51 | 0 |  |  |  |  |  | $self->{'Site'} = $cache->site(); | 
| 52 | 0 |  |  |  |  |  | $self->{'Url'} = $cache->url(); | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 0 |  |  |  |  |  | return $lyrics; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 0 |  |  |  |  |  | $self->{'Site'} = $Site; | 
| 60 |  |  |  |  |  |  |  | 
| 61 | 0 |  |  |  |  |  | (my $artist = $artist_in) =~ s#\s*\/.*$##;  #ONLY USE 1ST ARTIST, IF MORE THAN ONE! | 
| 62 | 0 |  |  |  |  |  | (my $song = $song_in) =~ s#\s*\/\s*#\-#;  #FIX SONGS WITH "/" IN THEM! | 
| 63 | 0 |  |  |  |  |  | $artist =~ s/\&/feat/;    #DON'T ASK ME WHY "&" => "feat"? BUT IT DOES! | 
| 64 | 0 |  |  |  |  |  | $song =~ s/\&/feat/; | 
| 65 | 0 |  |  |  |  |  | $artist =~ s/ +/\-/g; | 
| 66 | 0 |  |  |  |  |  | $song =~ s/ +/\-/g; | 
| 67 | 0 |  |  |  |  |  | $artist =~ tr/A-Z/a-z/; | 
| 68 | 0 |  |  |  |  |  | $song =~ tr/A-Z/a-z/; | 
| 69 | 0 |  |  |  |  |  | $artist = uri_escape_utf8($artist); | 
| 70 | 0 |  |  |  |  |  | $song = uri_escape_utf8($song); | 
| 71 | 0 |  |  |  |  |  | $artist =~ s/[^a-z0-9\-\%]//gi; | 
| 72 | 0 |  |  |  |  |  | $song   =~ s/[^a-z0-9\-\%]//gi; | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | # Their URLs look like e.g.: | 
| 75 |  |  |  |  |  |  | #https://www.musixmatch.com/lyrics/Artist-name/Title | 
| 76 | 0 |  |  |  |  |  | $self->{'Url'} = $Site . '/lyrics/' | 
| 77 |  |  |  |  |  |  | . join('/', $artist, $song); | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 0 |  |  |  |  |  | my $lyrics = $self->_web_fetch($artist_in, $song_in); | 
| 80 | 0 | 0 | 0 |  |  |  | if ($lyrics && $haveLyricsCache && $self->{'-cache'} && $self->{'-cache'} !~ /^\) { | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 81 | 0 |  |  |  |  |  | $self->_debug("=== WILL CACHE LYRICS! ==="); | 
| 82 |  |  |  |  |  |  | # cache the fetched lyrics, if we can: | 
| 83 | 0 |  |  |  |  |  | my $cache = new LyricFinder::Cache(%{$self}); | 
|  | 0 |  |  |  |  |  |  | 
| 84 | 0 | 0 |  |  |  |  | $cache->save($artist_in, $song_in, $lyrics)  if ($cache); | 
| 85 |  |  |  |  |  |  | } | 
| 86 | 0 |  |  |  |  |  | return $lyrics; | 
| 87 |  |  |  |  |  |  | } | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | sub _parse { | 
| 90 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 91 | 0 |  |  |  |  |  | my $html = shift; | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 0 |  |  |  |  |  | $self->_debug("Musixmatch::_parse()!"); | 
| 94 | 0 |  |  |  |  |  | my $goodbit = ''; | 
| 95 | 0 |  |  |  |  |  | my $text = ''; | 
| 96 | 0 |  |  |  |  |  | my $mm_status = ''; | 
| 97 | 0 |  |  |  |  |  | my $hs   = HTML::Strip->new(); | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | #METHOD 1:  MERGE LYRICS FROM BOTH (OR MORE?) LYRIC "SPANS" MM LIKES TO BREAK LYRICS UP INTO | 
| 100 |  |  |  |  |  |  | #2 SEPARATE SPAN TAGS:  A SHORT (ABBREVIATED) PART, FOLLOWED BY THE REST OF THE LYRICS: | 
| 101 | 0 |  |  |  |  |  | while ($html =~ s#\(.+?)\<\/span\>##s) { | 
| 102 | 0 |  |  |  |  |  | $mm_status = $1; | 
| 103 | 0 |  |  |  |  |  | $goodbit = $2; | 
| 104 | 0 |  |  |  |  |  | $text .= $hs->parse($goodbit) . "\r\n"; | 
| 105 |  |  |  |  |  |  | } | 
| 106 | 0 | 0 |  |  |  |  | unless ($text =~ /[a-z]/i) {  #IF METHOD 1 FAILS, THE FULL LYRICS ARE OFTEN FOUND IN THE FIRST "body": | 
| 107 | 0 |  |  |  |  |  | $html =~ s#\\\"#\x02#gs;  #PROTECT ESCAPED QUOTES | 
| 108 | 0 | 0 |  |  |  |  | if ($html =~ m#\"body\"\:\"([^\"]+)#s) { | 
| 109 | 0 |  |  |  |  |  | $mm_status = 'error!'; | 
| 110 | 0 |  |  |  |  |  | ($goodbit = $1) =~ s#\x02#\"#gs; | 
| 111 | 0 |  |  |  |  |  | $text .= $hs->parse($goodbit); | 
| 112 | 0 |  |  |  |  |  | $text =~ s#\\n#\n#gs;  #line-feeds are encoded literally in the JSON block. | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  | } | 
| 115 | 0 | 0 |  |  |  |  | if ($text =~ /[a-z]/i) { | 
| 116 |  |  |  |  |  |  | $text .= "\r\n(Musixmatch status: $mm_status)" | 
| 117 | 0 | 0 | 0 |  |  |  | unless ((defined($self->{'-noextra'}) && $self->{'-noextra'}) || $mm_status !~ /\S/); | 
|  |  |  | 0 |  |  |  |  | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | #WHILE WE'RE AT IT, SEE IF WE HAVE A COVER IMAGE?!: | 
| 120 | 0 | 0 |  |  |  |  | if ($html =~ m#\ (.+?)\<\/div\>#s) {  | 
| 121 | 0 |  |  |  |  |  | my $imgdiv = $1; | 
| 122 | 0 | 0 |  |  |  |  | if ($imgdiv =~ m#\ ![]()  | 
| 123 | 0 |  |  |  |  |  | my $imgurl = $1; | 
| 124 | 0 | 0 |  |  |  |  | $imgurl = 'https:' . $imgurl  if ($imgurl =~ m#^//#); | 
| 125 | 0 |  |  |  |  |  | $self->{'image_url'} = $imgurl; | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  |  | return $self->_normalize_lyric_text($self->_html2text($text)); | 
| 130 |  |  |  |  |  |  | } else { | 
| 131 | 0 |  |  |  |  |  | carp($self->{'Error'} = "e:$Source - Failed to identify lyrics on result page."); | 
| 132 | 0 |  |  |  |  |  | return ''; | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | 1 | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | __END__ |