#!/usr/local/bin/perl =head1 NAME B - Query MusicBrainz =head1 SYNOPSIS B B<-Q> B B B<fa-mb> [B<-p>] B<RELEASE-ID> =head1 DESCRIPTION The first form queries for releases by B<ARTIST> and B<TITLE>, listing a summary, one per line. The first column is the id, which is used as the B<RELEASE-ID> argument to the second form. The second form looks up a release by B<RELEASE-ID>, listing all the tags for each disc in the release in the form expected by B<fa-rip>. The second form accepts a B<-p> option which enables the PAREN HACK, which is quite gross. I like to put information such as "(live)" in the VERSION tag rather than TITLE tag. This information comes out of MusicBrainz parenthesized at the end of the track title, so this just strips out any parenthesized parts. Frequently it is incorrect: pay attention! The tags listing also hacks various Unicode characters that work just fine with their ASCII equivalents, e.g. quotation marks. =cut package epg::flac::archive::mb; use v5.12; use utf8; use warnings; use Exporter 'import'; use JSON::PP; use URI::Escape; our @EXPORT_OK = qw[ format_tags list_releases read_releases read_release ]; sub tracks { local $_; my $tracks = shift; [ map { $_ = $_->{title}; # unicode hacks s/‐/-/g; s/’/'/g; $_ } @$tracks ] } sub read_release { local $_; my $parsed = decode_json(shift); my $release = release_metadata($parsed); # TODO this is dumb. put it into release_metadata # TODO only include "format": "CD" (i.e. exclude "format": "DVD-Video") $release->{discs} = [map { tracks($_->{tracks}) } @{$parsed->{media}}]; $release } sub release_metadata { local $_ = shift; my $date = $_->{date} // 'UNKNOWN'; my $events = $_->{'release-events'}; if (defined($events)) { if (@$events > 1) { warn('found example of multiple release events!'); } my $event_date = $events->[0]->{date}; if (defined($event_date)) { if ($event_date ne $date) { warn("release-events has date $event_date (vs. $date)"); } } } my ($discs, $tracks) = (0, 0); for my $m (@{$_->{media} // []}) { $discs += $m->{'disc-count'} // 0; $tracks += $m->{'track-count'} // 0; } if ($tracks == 0 && defined($_->{'disc-count'})) { $tracks = $_->{'disc-count'}; } if (defined($_->{'disc-count'})) { warn('disc count defined at release root!'); } my $artist; my $artcred = $_->{'artist-credit'}; if (defined($artcred)) { $artist = join('', map { # warn('HEYEPG! ', join(', ', keys(%$_))); # warn("HEYEPG ", $_->{name}, 'join=', ($_->{joinphrase} // '')); $_->{name}, ($_->{joinphrase} // '') } @$artcred); } else { warn('OH!'); } { id => $_->{id}, artist => $artist // 'UNKNOWN', title => $_->{title}, discs => $discs, tracks => $tracks, country => $_->{country} // '--', date => $date, labels => join(', ', map { $_->{label}->{name} // 'UNKNOWN' } @{$_->{'label-info'} // []}), disambiguation => $_->{disambiguation} // '', status => $_->{status}, } } sub read_releases { local $_; my $parsed = decode_json(shift); [map { release_metadata($_) } @{$parsed->{releases}}] } sub list_releases { local $_; my $releases = shift; join("\n", map { sprintf('%s %-10s %3d tracks / %2d discs in %s %s by %s; %s, %s', $_->{id}, $_->{date}, $_->{tracks}, $_->{discs}, $_->{country}, $_->{title}, $_->{labels} , $_->{disambiguation}, $_->{status}, ) } @$releases) } sub format_tags { local $_; my %parm = @_; my $hack_parens = $parm{hack_parens} // 0; my $release = $parm{release}; my $artist = 'ARTIST=' . $release->{artist}; my $album = 'ALBUM=' . $release->{title}; my $date = 'DATE=' . $release->{date}; my $discs = $release->{discs}; my $multi = @$discs > 1; my $d = 0; join("\n", map { my @discnum; if ($multi) { $d++; @discnum = ("DISCNUMBER=$d"); } my $t = 0; ( $artist, $album, $date, @discnum, map { $t++; my @tags; if ($hack_parens) { /([^(]+)(.*)/; my $title = $1; my $version = $2; if (length($version)) { $title =~ s/\s+$//; $version =~ s/^\(//; $version =~ s/\)$//; @tags = ("TITLE[$t]=$title", "VERSION[$t]=$version"); } else { @tags = ("TITLE[$t]=$title") } } else { @tags = ("TITLE[$t]=$_"); } @tags } @$_ ) } @$discs ) } sub main { my $arg = shift || die; if ($arg eq '-Q') { my $artist = shift || die; my $title = shift || die; my $url = 'https://musicbrainz.org/ws/2/release?query=' . uri_escape("artist:\"$artist\" AND \"$title\" AND format:CD"); open(my $fh, '-|', 'curl', '--silent', '-Haccept:application/json', $url) || die; my @body; while (<$fh>) { push(@body, $_); } say(list_releases(read_releases(join('', @body)))); } else { my $hack_parens; if ($arg eq '-p') { $arg = shift || die; $hack_parens = 1; } else { $hack_parens = 0 } my $url = "https://musicbrainz.org/ws/2/release/$arg?inc=artist-credits+recordings"; open(my $fh, '-|', 'curl', '--silent', '-Haccept:application/json', $url) || die; say(format_tags( hack_parens => $hack_parens, release => read_release(join('', <$fh>)), )); } 0 } if (!caller) { exit(main(@ARGV)) } 1; # Local variables: # perl-indent-level: 8 # indent-tabs-mode: t # End: