File Coverage

blib/lib/Music/Tag/MusicBrainz.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Music::Tag::MusicBrainz;
2 1     1   8185 use strict; use warnings; use utf8;
  1     1   3  
  1     1   109  
  1         7  
  1         1  
  1         34  
  1         5  
  1         14  
  1         6  
3             our $VERSION = '0.4101';
4              
5             # Copyright © 2006,2010 Edward Allen III. Some rights reserved.
6             #
7             # You may distribute under the terms of either the GNU General Public
8             # License or the Artistic License, as specified in the README file.
9              
10 1     1   486 use WebService::MusicBrainz::Artist;
  0            
  0            
11             use WebService::MusicBrainz::Release;
12             use WebService::MusicBrainz::Track;
13             use Cache::FileCache;
14             use utf8;
15             use base qw(Music::Tag::Generic);
16              
17             sub default_options {
18             { prefered_country => "US",
19             min_artist_score => 1,
20             min_album_score => 17,
21             min_track_score => 3,
22             ignore_mbid => 0,
23             trust_time => 0,
24             trust_track => 0,
25             trust_title => 0,
26             skip_seen => 0,
27             ignore_multidisc_warning => 1,
28             mb_host => "www.musicbrainz.org",
29             };
30             }
31              
32             sub required_values {
33             return qw( artist);
34             }
35              
36             sub set_values {
37             return
38             qw( album releasedate totaltracks title tracknum title track releasedate);
39             }
40              
41             sub get_tag {
42             my $self = shift;
43             if ( ( $self->options->{skip_seen} )
44             && ( length( $self->info->get_data('mb_trackid') ) == 36 ) ) {
45             $self->status( "Skipping previously looked up track with mb_trackid "
46             . $self->info->get_data('mb_trackid') );
47             }
48             else {
49             $self->artist_info() && $self->album_info() && $self->track_info();
50             }
51             return $self;
52             }
53              
54             sub artist_info {
55             my $self = shift;
56             $self->status( "Looking up artist from " . $self->options->{mb_host} );
57             unless ( exists $self->{mb_a} ) {
58             $self->{mb_a} = WebService::MusicBrainz::Artist->new(
59             HOST => $self->options->{mb_host},
60             CACHE => $self->mb_cache
61             );
62             }
63             my $params = {};
64             my $maxscore = 0;
65             my $artist = undef;
66             if ( ( $self->info->has_data('mb_artistid') )
67             && ( not $self->options->{ignore_mbid} ) ) {
68             $params->{MBID} = $self->info->get_data('mb_artistid');
69             $artist = $self->{mb_a}->search($params);
70             unless ( ref($artist) eq "WebService::MusicBrainz::Response::Artist" )
71             {
72             $artist = $artist->artist();
73             }
74             $maxscore = 8;
75             }
76             elsif ( ( $self->info->has_data('artist') ) ) {
77             $params->{NAME} = $self->info->get_data('artist');
78             my $response = $self->{mb_a}->search($params);
79             return unless $response;
80             return unless $response->artist_list();
81             foreach ( @{ $response->artist_list->artists() } ) {
82             my $s = 0;
83             if ( ( $self->info->has_data('artist') )
84             && ( $_->{name} )
85             && ( $self->info->get_data('artist') eq $_->{name} ) ) {
86             $s += 16;
87             }
88             elsif (( $self->info->has_data('artist') )
89             && ( $_->{sortname} )
90             && ( $self->info->get_data('artist') eq $_->{sortname} ) ) {
91             $s += 8;
92             }
93             elsif (( $self->info->has_data('mb_artistid') )
94             && ( $_->{id} )
95             && ( $self->info->get_data('mb_artistid') eq $_->{id} ) ) {
96             $s += 4;
97             }
98             elsif (
99             ( $self->info->has_data('artist') )
100             && ( $_->{name} )
101             && ($self->simple_compare(
102             $self->info->get_data('artist'),
103             $_->{name}, .90
104             )
105             )
106             ) {
107             $s += 2;
108             }
109             if ( $s > $maxscore ) {
110             $artist = $_;
111             $maxscore = $s;
112             }
113             }
114             if ( $maxscore > $self->options->{min_artist_score} ) {
115             $self->status( "Artist ", $artist->name, " won election with ",
116             $maxscore, "pts" );
117             }
118             elsif ($maxscore) {
119             $self->status( "Artist ", $artist->name, " won election with ",
120             $maxscore, "pts, but that is not good enough" );
121             return;
122             }
123             else {
124             $self->status("No Artist found");
125             return;
126             }
127             }
128             return unless ( defined $artist );
129             my %amap = (
130             name => 'artist',
131             id => 'mb_artistid',
132             sort_name => 'sortname',
133             type => 'artist_type',
134             life_span_begin => 'artist_start',
135             life_span_end => 'artist_end',
136              
137             );
138              
139             while ( my ( $k, $v ) = each %amap ) {
140             if ( $artist->$k ) {
141             unless (( $self->info->has_data($v) )
142             and ( ( $self->info->get_data($v) ) eq ( $artist->$k ) ) ) {
143             $self->info->set_data( $v => $artist->$k );
144             $self->tagchange("ARTIST");
145             }
146             }
147             }
148             return $self->info;
149             }
150              
151             sub album_info {
152             my $self = shift;
153             $self->status( "Looking up album from " . $self->options->{mb_host} );
154             unless ( exists $self->{mb_r} ) {
155             $self->{mb_r} = WebService::MusicBrainz::Release->new(
156             HOST => $self->options->{mb_host},
157             CACHE => $self->mb_cache
158             );
159             }
160             my $params = { LIMIT => 200 };
161             my $release = undef;
162             if ( ( $self->info->has_data('mb_albumid') )
163             && ( not $self->info->options->{ignore_mbid} )
164             && ( length( $self->info->get_data('mb_albumid') ) > 30 )
165              
166             ) {
167             $params->{MBID} = $self->info->get_data('mb_albumid');
168             my $response = $self->{mb_r}->search($params);
169             $release = $response->release();
170              
171             #print Dumper($release);
172             }
173             else {
174             if ( ( $self->info->has_data('mb_artistid') )
175             && ( $self->info->get_data('mb_artistid') ) ) {
176             $params->{artistid} = $self->info->get_data('mb_artistid');
177             }
178             elsif ( ( $self->info->has_data('artist') )
179             && ( $self->info->get_data('artist') ) ) {
180             $params->{artist} = $self->info->get_data('artist');
181             }
182             else {
183             $self->status("Artist required for album lookup...");
184             return ();
185             }
186              
187             my $response = $self->{mb_r}->search($params);
188             return unless $response;
189              
190             # albumid 256 pts
191             # title 64 pts
192             # asin 32 pts
193             # simple_title 32 pts
194             # discid 32 pts
195             # track_count 16 pts
196             # release_date 8 pts
197             # track name match 4 pts
198             # strack name match 2 pts
199             # track time match 1 pts
200              
201             my $releases = $response->release_list();
202             return unless $releases;
203              
204             my $maxscore = 0;
205             foreach ( @{ $releases->releases } ) {
206             my $s = 0;
207             my $title = $_->{title};
208             my $disc = 1;
209             if ( $title =~ /^(.+) \(disc (\d)(\: ([^)]*))?\)/i ) {
210             $title = $1;
211             $disc = $2;
212             }
213             if ( ( $self->info->has_data('mb_albumid') )
214             and ( $self->info->get_data('mb_albumid') eq $_->id )
215             and ( not $self->options->{ignore_mbid} ) ) {
216             $s += 256;
217             }
218             if ( $title eq $self->info->album ) {
219             $s += 64;
220             }
221             if ( ( $_->{asin} )
222             && ( $self->info->asin )
223             && ( length( $_->{asin} ) > 8 )
224             && ( $_->{asin} eq $self->info->get_data('asin') ) ) {
225             $s += 32;
226             }
227             if ($self->simple_compare(
228             $title, $self->info->get_data('album'), .80
229             )
230             ) {
231             $s += 32;
232             }
233             if (( $self->info->has_data('totaltracks') )
234             and ( ( $self->info->get_data('totaltracks') )
235             == ( $_->track_list->{count} ) )
236             ) {
237             $s += 16;
238             }
239             if ( ( $self->info->has_data('disc') )
240             and ( defined $disc )
241             and ( ( $self->info->get_data('disc') ) == ($disc) ) ) {
242             $s += 8;
243             }
244             if ( $s > $maxscore ) {
245             $release = $_;
246             $maxscore = $s;
247             }
248             }
249             if ( $maxscore > $self->options->{min_album_score} ) {
250             $self->status( "Awarding highest score of "
251             . $maxscore . " to "
252             . $release->title );
253             }
254             elsif ($release) {
255             $self->status( "Highest score of "
256             . $maxscore . " to "
257             . $release->title
258             . " is too low" );
259             return;
260             }
261             else {
262             $self->status("No good match found for album, sorry\n");
263             return;
264             }
265             }
266             if ( $release->type ) {
267             unless (
268             ( $self->info->has_data('album_type') )
269             and ( ( $self->info->get_data('album_type') ) eq
270             ( $release->{type} ) )
271             ) {
272             $self->info->set_data( 'album_type', $release->{type} );
273             $self->tagchange("ALBUM_TYPE");
274             }
275             }
276             if ( $release->id ) {
277             unless (
278             ( $self->info->has_data('mb_albumid') )
279             and ( ( $self->info->get_data('mb_albumid') ) eq
280             ( $release->{id} ) )
281             ) {
282             $self->info->set_data( 'mb_albumid', $release->id() );
283             $self->tagchange("MB_ALBUMID");
284             }
285             }
286             if ( $release->title ) {
287              
288             # Parse out additional disc information. I still don't know how to deal with multi-volume sets
289             # in MusicBrainz. Style says to use (disc X) or (disc X: Disc Title) or even (box X, disc X).
290             # for now, I will support in album_title /\(disc (\d):?[^)]*\)/.
291             unless ( ( $self->info->has_data('album') )
292             && ( $self->info->get_data('album') eq $release->title ) ) {
293             if ($release->title() =~ /^(.+) \(disc (\d)(\: ([^)]*))?\)/i )
294             {
295             my ( $alb, $disc, $disctitle ) = ( $1, $2, $4 );
296             unless ( $self->info->get_data('album') eq $alb ) {
297             $self->info->set_data( 'album', $1 );
298             $self->tagchange("ALBUM");
299             }
300             unless ( $self->info->get_data('disc') eq $disc ) {
301             $self->info->set_data( 'disc', $2 );
302             $self->tagchange("DISC");
303             }
304             if ($3) {
305             $self->status("Debug disctitle: $disctitle");
306             unless (
307             $self->info->get_data('disctitle') eq $disctitle )
308             {
309             $self->info->set_data( 'disctitle', $disctitle );
310             $self->tagchange("DISCTITLE");
311             }
312             }
313             }
314             else {
315             $self->info->set_data( 'album', $release->title() );
316             $self->tagchange("ALBUM");
317             }
318             }
319             }
320             if ( $release->track_list ) {
321             unless (
322             ( $self->info->has_data('totaltracks') )
323             and ( ( $self->info->get_data('totaltracks') )
324             == ( $release->track_list->{count} ) )
325             ) {
326             $self->info->get_data( 'totaltracks',
327             $release->track_list->{count} );
328             $self->tagchange("TOTALTRACKS");
329             }
330             }
331              
332             if ( exists $release->{asin} ) {
333             unless (( $self->info->has_data('asin') )
334             and ( $self->info->get_data('asin') eq $release->{asin} ) ) {
335             $self->info->set_data( 'asin', $release->{asin} );
336             $self->tagchange("ASIN");
337             }
338             }
339             return $self->info;
340             }
341              
342             sub track_info {
343             my $self = shift;
344             if (( ( $self->info->has_data('totaldiscs')
345             && $self->info->get_data('totaldiscs') > 1
346             )
347             or ( $self->info->has_data('disc')
348             && $self->info->get_data('disc') > 1 )
349             )
350             && ( not $self->options->{ignore_multidisc_warning} )
351             ) {
352             $self->status(
353             "Warning! Multi-Disc item. MusicBrainz is not reliable for this. Will not change track name or number."
354             );
355             }
356             $self->status( "Looking up track from " . $self->options->{mb_host} );
357             unless ( exists $self->{mb_r} ) {
358             $self->{mb_r} = WebService::MusicBrainz::Release->new(
359             HOST => $self->options->{mb_host},
360             CACHE => $self->mb_cache
361             );
362             }
363             return unless ( $self->info->has_data('mb_albumid') );
364             my $params = {
365             MBID => $self->info->get_data('mb_albumid'),
366             INC => "tracks discs release-events",
367             };
368             my $response = $self->{mb_r}->search($params);
369             unless ( $response->release->track_list ) {
370             return;
371             }
372             my $tracks = $response->release->track_list->tracks();
373             my $release = $response->release;
374             my $tracknum = 0;
375             my $maxscore = 0;
376             my $track = undef;
377             my $trackn = 0;
378              
379             # track ID (unless ignore_ids) 128 pts
380             # tracknum match 4 pts
381             # trust track set 64 pts
382             # title match 8 pts
383             # trust title set 64 pts
384             # close title match 4 pts
385             # trust title set 16 pts
386             # time match 2 pts
387             # trust time set 64 pts
388             # close time match 1 pts
389             # trust time set 16 pts
390              
391             foreach my $t ( @{$tracks} ) {
392             my $s = 0;
393             if ( ( $self->info->has_data('mb_trackid') )
394             && ( $self->info->get_data('mb_trackid') eq $t->{id} )
395             && ( not $self->info->{ignore_mbid} ) ) {
396             $s += 128;
397             }
398             if ( ( $self->info->has_data('track') )
399             && ( $self->info->get_data('track') - 1 == $tracknum ) ) {
400             if ( $self->options->{trust_track} ) {
401             $s += 64;
402             }
403             else {
404             $s += 4;
405             }
406             }
407             if ( ( $self->info->has_data('title') )
408             && ( $self->info->get_data('title') eq $t->{title} ) ) {
409             if ( $self->options->{trust_title} ) {
410             $s += 64;
411             }
412             else {
413             $s += 8;
414             }
415             }
416             elsif (
417             ( $self->info->has_data('title') )
418             && ($self->simple_compare(
419             $self->info->get_data('title'),
420             $t->{title}, .80
421             )
422             )
423             ) {
424             if ( $self->options->{trust_title} ) {
425             $s += 16;
426             }
427             else {
428             $s += 4;
429             }
430             }
431             if ( ( $self->info->has_data('duration') )
432             && ( exists $t->{duration} )
433             && ( defined $t->{duration} ) ) {
434             my $diff =
435             abs( $self->info->get_data('duration') - $t->{duration} );
436             if ( $diff < 3000 ) {
437             if ( $self->options->{trust_time} ) {
438             $s += 16;
439             }
440             else {
441             $s += 1;
442             }
443             }
444             elsif ( $diff < 100 ) {
445             $s += 2;
446             if ( $self->options->{trust_time} ) {
447             $s += 64;
448             }
449             else {
450             $s += 1;
451             }
452             }
453             }
454             if ( $s > $maxscore ) {
455             $maxscore = $s;
456             $track = $t;
457             $trackn = $tracknum + 1;
458             }
459             $tracknum++;
460             }
461             if ( ($maxscore)
462             && ( $maxscore > $self->options->{min_track_score} ) ) {
463             $self->status( "Awarding highest score of "
464             . $maxscore . " to "
465             . $track->title );
466             }
467             elsif ($maxscore) {
468             $self->status( "Highest score was "
469             . $maxscore . " for "
470             . $track->title
471             . ", but that is not good enough, skipping track info." );
472             return;
473             }
474             else {
475             $self->status("No match for track, skipping track info.");
476             return;
477             }
478             unless (
479             ( ( $self->info->has_data('totaldiscs')
480             && $self->info->get_data('totaldiscs') > 1
481             )
482             or ( $self->info->has_data('disc')
483             && $self->info->get_data('disc') > 1 )
484             )
485             && ( not $self->options->{ignore_multidisc_warning} )
486             ) {
487             if ( $track->title ) {
488             unless (
489             ( $self->info->has_data('title') )
490             and ( ( $self->info->get_data('title') ) eq
491             ( $track->title ) )
492             ) {
493             $self->info->set_data( 'title', $track->title );
494             $self->tagchange("TITLE");
495             }
496             }
497             unless (( $self->info->has_data('track') )
498             and ( $self->info->get_data('track') == $trackn ) ) {
499             $self->info->set_data( 'track', $trackn );
500             $self->tagchange("TRACK");
501             }
502             if ( $track->id ) {
503             unless (
504             ( $self->info->has_data('mb_trackid') )
505             and ( ( $self->info->get_data('mb_trackid') ) eq
506             ( $track->id ) )
507             ) {
508             $self->info->set_data( 'mb_trackid', $track->id );
509             $self->tagchange("MB_TRACKID");
510             }
511             }
512             }
513             my $releases = [];
514             if ( $release->release_event_list ) {
515             $releases = $release->release_event_list->events;
516             }
517             my $countrycode = undef;
518             my $releasedate = undef;
519             if ( scalar @{$releases} ) {
520             $maxscore = 0;
521             foreach ( @{$releases} ) {
522             my $score = 0;
523             if ( ( $_->date )
524             && ( $self->info->has_data('releasedate') )
525             && ( $_->date eq $self->info->get_data('releasedate') ) )
526             {
527             $score += 4;
528             }
529             elsif ( $_->country eq $self->options->{prefered_country} ) {
530             $score += 2;
531             }
532             elsif ( not defined $countrycode ) {
533             $score += 1;
534             }
535             if ( $score > $maxscore ) {
536             $countrycode = $_->country();
537             $releasedate = $_->date();
538             $maxscore = $score;
539             }
540             }
541             }
542             if (($countrycode)
543             && (not( ( $self->info->has_data('countrycode') )
544             && ($self->info->get_data('countrycode') eq $countrycode )
545             )
546             )
547             ) {
548             $self->info->set_data( 'countrycode', $countrycode );
549             $self->tagchange("countrycode");
550             }
551             if (($releasedate)
552             && (not( ( $self->info->has_data('releasedate') )
553             && ($self->info->get_data('releasedate') eq $releasedate )
554             )
555             )
556             ) {
557             $self->info->set_data( 'releasedate', $releasedate );
558             $self->tagchange("releasedate");
559             }
560             }
561              
562             sub mb_cache {
563             my $self = shift;
564             my $new = shift;
565             if ($new) {
566             $self->{mb_cache} = $new;
567             }
568             unless ( ( exists $self->{mb_cache} ) && ( $self->{mb_cache} ) ) {
569             if ( $self->options->{mb_cache} ) {
570             $self->{mb_cache} = $self->options->{mb_cache};
571             }
572             else {
573             $self->{mb_cache} = Cache::FileCache->new(
574             { namespace => "mb_cache",
575             default_expires_in => 60000,
576             }
577             );
578             }
579             }
580             return $self->{mb_cache};
581             }
582              
583             1;
584             __END__