| [3] | 1 | package MusicBrainz; | 
|---|
|  | 2 |  | 
|---|
|  | 3 | use strict; | 
|---|
|  | 4 | use warnings; | 
|---|
|  | 5 |  | 
|---|
|  | 6 | our @ISA    = qw{Exporter}; | 
|---|
| [8] | 7 | our @EXPORT = qw{get_musicbrainz_info lookup_release}; | 
|---|
| [3] | 8 |  | 
|---|
|  | 9 | #use WebService::MusicBrainz; | 
|---|
|  | 10 | use LWP; | 
|---|
|  | 11 | use XML::XPath; | 
|---|
|  | 12 | use XML::XPath::XMLParser; | 
|---|
|  | 13 |  | 
|---|
|  | 14 | sub 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 |  | 
|---|
| [12] | 34 | sub select_release { | 
|---|
|  | 35 | my ($xpath, $discid) = @_; | 
|---|
| [3] | 36 |  | 
|---|
|  | 37 | # get the release; if there is more than one, take the first one | 
|---|
|  | 38 | my $release_count = $xpath->findvalue('count(//release)'); | 
|---|
| [8] | 39 | my @releases = $xpath->findnodes('//release'); | 
|---|
|  | 40 | my $base = 'http://musicbrainz.org/release/'; | 
|---|
| [3] | 41 |  | 
|---|
| [8] | 42 | my $i = 1; | 
|---|
| [12] | 43 | # present the user with an interactive menu to pick/confirm the correct release ID | 
|---|
| [8] | 44 | warn "$release_count release(s) found matching $discid\n"; | 
|---|
|  | 45 | for my $release (@releases) { | 
|---|
|  | 46 | warn sprintf "%2d) $base%s %s %s (%s)\n", | 
|---|
|  | 47 | $i++, | 
|---|
|  | 48 | $xpath->findvalue('@id', $release)->value, | 
|---|
|  | 49 | $xpath->findvalue('.//label-info/label/name', $release)->value, | 
|---|
|  | 50 | $xpath->findvalue('.//label-info/catalog-number', $release)->value, | 
|---|
|  | 51 | $xpath->findvalue('barcode', $release)->value; | 
|---|
|  | 52 | } | 
|---|
| [3] | 53 |  | 
|---|
| [12] | 54 | my $selection = 0; | 
|---|
|  | 55 |  | 
|---|
|  | 56 | while ($selection < 1 || $selection > $release_count) { | 
|---|
|  | 57 | print STDERR "Select a release (1-$release_count): "; | 
|---|
|  | 58 | $selection = <STDIN>; | 
|---|
|  | 59 | chomp $selection; | 
|---|
|  | 60 | return if $selection =~ /^q/i; | 
|---|
|  | 61 | } | 
|---|
|  | 62 |  | 
|---|
|  | 63 | return $releases[$selection - 1]; | 
|---|
|  | 64 | } | 
|---|
|  | 65 |  | 
|---|
|  | 66 | sub get_musicbrainz_info { | 
|---|
|  | 67 | my ($discid) = @_; | 
|---|
|  | 68 | my %info; | 
|---|
|  | 69 |  | 
|---|
|  | 70 | $info{MUSICBRAINZ_DISCID} = $discid; | 
|---|
|  | 71 |  | 
|---|
|  | 72 | my $xpath = XML::XPath->new(); | 
|---|
|  | 73 | my $xml = lookup_release($discid) || return; | 
|---|
|  | 74 |  | 
|---|
|  | 75 | $xpath->set_xml($xml); | 
|---|
|  | 76 |  | 
|---|
| [8] | 77 | # use the VorbisComment names from here http://musicbrainz.org/doc/MusicBrainz_Picard/Tags/Mapping | 
|---|
|  | 78 |  | 
|---|
| [12] | 79 | #my $release = $releases[0]; | 
|---|
|  | 80 | my $release = select_release($xpath, $discid); | 
|---|
|  | 81 | return unless $release; | 
|---|
| [8] | 82 |  | 
|---|
|  | 83 | $info{MUSICBRAINZ_ALBUMID} = $xpath->findvalue('@id', $release)->value; | 
|---|
|  | 84 | $info{ALBUM}               = $xpath->findvalue('title', $release)->value; | 
|---|
| [9] | 85 | @info{qw{ALBUMARTIST ALBUMARTISTSORT}} = get_artist_credits($xpath, $release); | 
|---|
| [8] | 86 | $info{DATE}                = $xpath->findvalue('date', $release)->value; | 
|---|
|  | 87 | $info{ORIGINALDATE}        = $xpath->findvalue('release-group/first-release-date', $release)->value; | 
|---|
| [16] | 88 | $info{LANGUAGE}            = $xpath->findvalue('text-representation/language', $release)->value; | 
|---|
|  | 89 | $info{SCRIPT}              = $xpath->findvalue('text-representation/script', $release)->value; | 
|---|
| [8] | 90 |  | 
|---|
| [5] | 91 | # select the proper medium (important for multidisc releases) | 
|---|
|  | 92 | my ($medium) = $xpath->findnodes("medium-list/medium[disc-list/disc/\@id='$discid']", $release); | 
|---|
|  | 93 |  | 
|---|
| [10] | 94 | # disc position info | 
|---|
|  | 95 | $info{DISCNUMBER} = $xpath->findvalue('position', $medium)->value; | 
|---|
|  | 96 | $info{DISCTOTAL}  = $xpath->findvalue('../@count', $medium)->value; | 
|---|
|  | 97 |  | 
|---|
| [9] | 98 | #my $ua = LWP::UserAgent->new; | 
|---|
| [3] | 99 | my $tracknum = 1; | 
|---|
| [5] | 100 | for my $track_node ($xpath->findnodes('track-list/track', $medium)) { | 
|---|
| [3] | 101 | my $prefix = sprintf('TRACK%02d', $tracknum); | 
|---|
| [8] | 102 |  | 
|---|
| [9] | 103 | $info{"$prefix.MUSICBRAINZ_TRACKID"} = $xpath->findvalue('@id', $track_node)->value; | 
|---|
| [8] | 104 |  | 
|---|
| [9] | 105 | my ($recording) = $xpath->findnodes('recording', $track_node); | 
|---|
|  | 106 | $info{"$prefix.MUSICBRAINZ_RECORDINGID"} = $xpath->findvalue('@id', $recording)->value; | 
|---|
|  | 107 | $info{"$prefix.TITLE"} = $xpath->findvalue('title', $recording)->value; | 
|---|
|  | 108 | @info{"$prefix.ARTIST", "$prefix.ARTISTSORT"} = get_artist_credits($xpath, $recording); | 
|---|
| [8] | 109 |  | 
|---|
| [3] | 110 | #my $uri = URI->new("http://musicbrainz.org/ws/2/recording/$recording_mbid"); | 
|---|
|  | 111 | #$uri->query_form(inc => 'artists'); | 
|---|
|  | 112 | #my $res = $ua->get($uri); | 
|---|
|  | 113 | #die $res->decoded_content; | 
|---|
|  | 114 |  | 
|---|
|  | 115 | #TODO: get track relations (Covers, etc.) | 
|---|
|  | 116 |  | 
|---|
|  | 117 | $tracknum++; | 
|---|
|  | 118 | } | 
|---|
|  | 119 |  | 
|---|
|  | 120 | return \%info; | 
|---|
|  | 121 | } | 
|---|
|  | 122 |  | 
|---|
| [9] | 123 | sub get_artist_credits { | 
|---|
|  | 124 | my ($xpath, $context_node) = @_; | 
|---|
|  | 125 |  | 
|---|
|  | 126 | # use the MusicBrainz join phrase to build up the multiple artist credits | 
|---|
|  | 127 | my ($credit, $sort_credit) = ('', ''); | 
|---|
|  | 128 | for my $credit_node ($xpath->findnodes('artist-credit/name-credit', $context_node)) { | 
|---|
|  | 129 | $credit      .= $xpath->findvalue('concat(artist/name, @joinphrase)', $credit_node)->value; | 
|---|
|  | 130 | $sort_credit .= $xpath->findvalue('concat(artist/sort-name, @joinphrase)', $credit_node)->value; | 
|---|
|  | 131 | } | 
|---|
|  | 132 |  | 
|---|
|  | 133 | return ($credit, $sort_credit); | 
|---|
|  | 134 | } | 
|---|
|  | 135 |  | 
|---|
| [3] | 136 | # module return | 
|---|
|  | 137 | 1; | 
|---|