source: flacrip/trunk/MusicBrainz.pm @ 8

Last change on this file since 8 was 8, checked in by peter, 11 years ago
  • use the VorbisComment style tag names in the info hash returned by MusicBrainz::get_musicbrainz_info()
  • use the MusicBrainz name credit join phrase to build the string where there are multiple artists
  • include the artist sort names in the returned info, as TRACKnn.ARTISTSORT keys, and ALBUMARTIST
  • added an applymeta script that applies the metadata listed in a file to a set of MP3 files
  • when loooking up a release, MusicBrainz::get_musicbrainz_info() prints the number of releases and some other debugging info to STDERR
File size: 8.2 KB
Line 
1package MusicBrainz;
2
3use strict;
4use warnings;
5
6our @ISA    = qw{Exporter};
7our @EXPORT = qw{get_musicbrainz_info lookup_release};
8
9#use WebService::MusicBrainz;
10use LWP;
11use XML::XPath;
12use XML::XPath::XMLParser;
13
14sub lookup_release {
15    my ($discid) = @_;
16    my $ua = LWP::UserAgent->new;
17
18    #my $uri = URI->new('http://musicbrainz.org/ws/1/release/');
19    #$uri->query_form(type => 'xml', discid => $discid);
20    my $uri = URI->new("http://musicbrainz.org/ws/2/discid/$discid");
21    $uri->query_form(inc => 'artists+labels+recordings+release-groups+artist-credits');
22
23    my $res = $ua->get($uri);
24    # pause for a second, so we don't run afoul of the MusicBrainz API TOS
25    sleep 1;
26
27    warn $res->status_line, "\n" if $res->code != 200;
28    return if $res->code >= 400;
29    #TODO: if we get a 5xx error, retry?
30
31    return $res->decoded_content;
32}
33
34sub get_musicbrainz_info {
35    my ($discid) = @_;
36    my %info;
37
38    #TODO: deprecate the old MBZ tag name
39    $info{MBZ_DISCID} = $discid;
40    $info{MUSICBRAINZ_DISCID} = $discid;
41
42    my $xpath = XML::XPath->new();
43    my $xml = lookup_release($discid) || return;
44   
45    $xpath->set_xml($xml);
46
47    # get the release; if there is more than one, take the first one
48    my $release_count = $xpath->findvalue('count(//release)');
49    my @releases = $xpath->findnodes('//release');
50    my $base = 'http://musicbrainz.org/release/';
51
52    my $i = 1;
53    #TODO: use this as the basis for an interactive menu to pick the correct release ID
54    warn "$release_count release(s) found matching $discid\n";
55    for my $release (@releases) {
56        warn sprintf "%2d) $base%s %s %s (%s)\n", 
57            $i++,
58            $xpath->findvalue('@id', $release)->value,
59            $xpath->findvalue('.//label-info/label/name', $release)->value,
60            $xpath->findvalue('.//label-info/catalog-number', $release)->value,
61            $xpath->findvalue('barcode', $release)->value;
62    }
63
64    # use the VorbisComment names from here http://musicbrainz.org/doc/MusicBrainz_Picard/Tags/Mapping
65
66    # use the first release by default
67    # TODO: configurable release selection criteria
68    my $release = $releases[0];
69
70    $info{MUSICBRAINZ_ALBUMID} = $xpath->findvalue('@id', $release)->value;
71    $info{ALBUM}               = $xpath->findvalue('title', $release)->value;
72    $info{ALBUMARTIST}         = $xpath->findvalue('artist-credit/name-credit/artist/name', $release)->value;
73    $info{ALBUMARTISTSORT}     = $xpath->findvalue('artist-credit/name-credit/artist/sort-name', $release)->value;
74    $info{DATE}                = $xpath->findvalue('date', $release)->value;
75    $info{ORIGINALDATE}        = $xpath->findvalue('release-group/first-release-date', $release)->value;
76
77    # select the proper medium (important for multidisc releases)
78    my ($medium) = $xpath->findnodes("medium-list/medium[disc-list/disc/\@id='$discid']", $release);
79
80    my $ua = LWP::UserAgent->new;
81    my $tracknum = 1;
82    for my $track_node ($xpath->findnodes('track-list/track', $medium)) {
83        my $prefix = sprintf('TRACK%02d', $tracknum);
84        my $track_mbid = $info{"$prefix.MUSICBRAINZ_TRACKID"} = $xpath->findvalue('@id', $track_node)->value;
85        my $recording_mbid = $info{"$prefix.MUSICBRAINZ_RECORDINGID"} = $xpath->findvalue('recording/@id', $track_node)->value;
86
87        $info{"$prefix.TITLE"}          = $xpath->findvalue('recording/title', $track_node)->value;
88
89        # use the MusicBrainz join phrase to build up the multiple artist credits
90        my ($credit, $sort_credit) = ('', '');
91        for my $credit_node ($xpath->findnodes('recording/artist-credit/name-credit', $track_node)) {
92            $credit .= $xpath->findvalue('concat(artist/name, @joinphrase)', $credit_node)->value;
93            $sort_credit .= $xpath->findvalue('concat(artist/sort-name, @joinphrase)', $credit_node)->value;
94        }
95        $info{"$prefix.ARTIST"}     = $credit;
96        $info{"$prefix.ARTISTSORT"} = $sort_credit;
97
98        $info{TRACKS}[$tracknum]{TITLE} = $info{"$prefix.TITLE"};
99        $info{TRACKS}[$tracknum]{ARTIST} = $info{"$prefix.ARTIST"};
100        $info{TRACKS}[$tracknum]{ARTISTSORT} = $info{"$prefix.ARTISTSORT"};
101        #my $uri = URI->new("http://musicbrainz.org/ws/2/recording/$recording_mbid");
102        #$uri->query_form(inc => 'artists');
103        #my $res = $ua->get($uri);
104        #die $res->decoded_content;
105
106        #TODO: get track relations (Covers, etc.)
107
108        $tracknum++;
109    }
110
111    return \%info;
112}
113
114# module return
1151;
116
117=begin MBZ API version 1
118
119sub lookup_release {
120    my ($discid) = @_;
121    my $ua = LWP::UserAgent->new;
122
123    my $uri = URI->new('http://musicbrainz.org/ws/1/release/');
124    $uri->query_form(type => 'xml', discid => $discid);
125
126    my $res = $ua->get($uri);
127    return $res->decoded_content;
128}
129
130sub get_musicbrainz_info {
131    my ($discid) = @_;
132    my %info;
133
134    $info{MBZ_DISCID} = $discid;
135
136    my $xpath = XML::XPath->new();
137
138    $xpath->set_xml(lookup_release($discid));
139
140    # TODO: check for more than 1 release?
141
142    $info{MB_RELEASE_ID} = $xpath->findvalue('//release/@id');
143    $info{ALBUM}         = $xpath->findvalue('//release/title');
144    $info{ARTIST}        = $xpath->findvalue('//release/artist/name');
145    $info{TRACKS}        = [];
146
147    # TODO: get release date
148
149    my $tracknum = 1;
150    for my $track_node ($xpath->findnodes('//track-list/track')) {
151        $info{TRACKS}[$tracknum]{MB_TRACKID} = $xpath->findvalue('@id', $track_node);
152        $info{TRACKS}[$tracknum]{TITLE}      = $xpath->findvalue('title', $track_node);
153        $info{TRACKS}[$tracknum]{ARTIST}     = $xpath->findvalue('artist/name', $track_node) || $info{ARTIST};
154        $tracknum++;
155    }
156
157    return %info;
158}
159
160=cut
161
162=begin WebService::MusicBrainz code
163
164    my $ws_artists = WebService::MusicBrainz->new_artist;
165    my $ws_releases = WebService::MusicBrainz->new_release;
166    my $ws_tracks = WebService::MusicBrainz->new_track;
167
168    # search on the discid
169    my $response = $ws_releases->search({ DISCID => $discid });
170
171    # save this object, since WS::MBZ deletes it when you fetch it
172    # TODO: bug report to WS::MBZ?
173    my $release = $response->release;
174
175    # return undef if there is no matching release for this DiscID
176    return unless defined $release;
177
178    # search again, using the MBID of the first release found
179    # TODO: deal with multiple releases found?
180    # include tracks and artist info
181    $response = $ws_releases->search({
182        MBID => $release->id,
183        INC => 'discs tracks artist release-events counts',
184    });
185
186    # get the fully filled out Release object (that represents the disc)
187    $release = $response->release;
188
189    if (defined $release->artist) {
190        $info{ARTIST} = $release->artist->name;
191    }
192    if (defined $release->title) {
193        $info{ALBUM} = $release->title;
194    }
195
196    # this is ID3v2:TDRL = Release Date
197    # (for now we just take the first date)
198    my $release_date = eval { @{ $release->release_event_list->events }[0]->date };
199    $release_date = '' if $@;
200
201    $info{DATE} = $release_date;
202
203    # get full info on each of the tracks
204    my @tracks;
205    my $track_num = 1;
206    for my $track_id (map { $_->id } @{ $release->track_list->tracks }) {
207        my $response = $ws_tracks->search({
208            MBID => $track_id,
209            INC => 'artist track-rels',
210        });
211        my $track = $response->track;
212        my $prefix = sprintf('TRACK%02d', $track_num);
213        $info{"$prefix.TITLE"} = $track->title;
214        #if (defined $track->artist && $track->artist->name ne $release->artist->name) {
215            $info{"$prefix.ARTIST"} = $track->artist->name;
216            $info{"$prefix.DATE"} = $release_date;
217        #}
218        push @tracks, $track;
219
220
221        if (defined $track->relation_list) {
222            for my $relation (@{ $track->relation_list->relations }) {
223                #warn $relation->type, $relation->target;
224                my $response = $ws_tracks->search({
225                    MBID => $relation->target,
226                    INC => 'artist releases',
227                });
228                my $track = $response->track;
229                $info{"$prefix.ORIGINAL_ARTIST"} = $track->artist->name;
230                $info{"$prefix.ORIGINAL_ALBUM"} =
231                    ( (@{ $track->release_list->releases })[0]->title );
232            }
233        }
234
235        $track_num++;
236    }
237
238=cut
Note: See TracBrowser for help on using the repository browser.