]> diplodocus.org Git - flac-archive/blob - fa-mb
fix tests after shortening CD filenames
[flac-archive] / fa-mb
1 #!/usr/local/bin/perl
2
3 =head1 NAME
4
5 B<fa-mb> - Query MusicBrainz
6
7 =head1 SYNOPSIS
8
9 B<fa-mb> B<-Q> B<ARTIST> B<TITLE>
10 B<fa-mb> [B<-p>] B<RELEASE-ID>
11
12 =head1 DESCRIPTION
13
14 The first form queries for releases by B<ARTIST> and B<TITLE>, listing a
15 summary, one per line. The first column is the id, which is used as the
16 B<RELEASE-ID> argument to the second form.
17
18 The second form looks up a release by B<RELEASE-ID>, listing all the tags for
19 each disc in the release in the form expected by B<fa-rip>.
20
21 The second form accepts a B<-p> option which enables the PAREN HACK, which is
22 quite gross. I like to put information such as "(live)" in the VERSION tag
23 rather than TITLE tag. This information comes out of MusicBrainz parenthesized
24 at the end of the track title, so this just strips out any parenthesized parts.
25 Frequently it is incorrect: pay attention!
26
27 The tags listing also hacks various Unicode characters that work just fine with
28 their ASCII equivalents, e.g. quotation marks.
29
30 =cut
31
32 package epg::flac::archive::mb;
33
34 use v5.12;
35 use utf8;
36 use warnings;
37
38 use Exporter 'import';
39 use JSON::PP;
40
41 use URI::Escape;
42
43 our @EXPORT_OK = qw[
44 format_tags
45 list_releases
46 read_releases
47 read_release
48 ];
49
50 sub tracks {
51 local $_;
52 my $tracks = shift;
53 [
54 map {
55 $_ = $_->{title};
56 # TODO need to apply these everywhere! also seen in ARTIST and ALBUM
57 # unicode hacks
58 s/‐/-/g;
59 s/’/'/g;
60 $_
61 } @$tracks
62 ]
63 }
64
65 sub read_release {
66 local $_;
67 my $parsed = decode_json(shift);
68 my $release = release_metadata($parsed);
69 # TODO this is dumb. put it into release_metadata
70 # TODO only include "format": "CD" (i.e. exclude "format": "DVD-Video")
71 $release->{discs} = [map { tracks($_->{tracks}) } @{$parsed->{media}}];
72 $release
73 }
74
75 sub release_metadata {
76 local $_ = shift;
77 my $date = $_->{date} // 'UNKNOWN';
78 my $events = $_->{'release-events'};
79 if (defined($events)) {
80 if (@$events > 1) {
81 # TODO e.g. https://musicbrainz.org/release/d9bfd2d6-2bab-4fd8-9b5d-84d2a7d1457b
82 # Some Great Reward has two release events: 0. Europe; 1. Germany
83 use Data::Dumper;
84 warn('found example of multiple release events! ' . $_->{id} . ' ' . Dumper($events))
85 }
86 my $event_date = $events->[0]->{date};
87 if (defined($event_date)) {
88 if ($event_date ne $date) {
89 warn("release-events has date $event_date (vs. $date)");
90 }
91 }
92 }
93 my ($discs, $tracks) = (0, 0);
94 for my $m (@{$_->{media} // []}) {
95 $discs += $m->{'disc-count'} // 0;
96 $tracks += $m->{'track-count'} // 0;
97 }
98 if ($tracks == 0 && defined($_->{'disc-count'})) {
99 $tracks = $_->{'disc-count'};
100 }
101 if (defined($_->{'disc-count'})) {
102 warn('disc count defined at release root!');
103 }
104 my $artist;
105 my $artcred = $_->{'artist-credit'};
106 if (defined($artcred)) {
107 $artist = join('', map {
108 # warn('HEYEPG! ', join(', ', keys(%$_)));
109 # warn("HEYEPG ", $_->{name}, 'join=', ($_->{joinphrase} // ''));
110 $_->{name}, ($_->{joinphrase} // '') } @$artcred);
111 } else {
112 warn('OH!');
113 }
114 {
115 id => $_->{id},
116 artist => $artist // 'UNKNOWN',
117 title => $_->{title},
118 discs => $discs,
119 tracks => $tracks,
120 country => $_->{country} // '--',
121 date => $date,
122 labels => join(', ', map { $_->{label}->{name} // 'UNKNOWN' } @{$_->{'label-info'} // []}),
123 disambiguation => $_->{disambiguation} // '',
124 status => $_->{status} // '',
125 }
126 }
127
128 sub read_releases {
129 local $_;
130 my $parsed = decode_json(shift);
131 [map { release_metadata($_) } @{$parsed->{releases}}]
132 }
133
134 sub list_releases {
135 local $_;
136 my $releases = shift;
137 join("\n",
138 map {
139 sprintf('%s %-10s %3d tracks / %2d discs in %s %s by %s; %s, %s',
140 $_->{id}, $_->{date},
141 $_->{tracks}, $_->{discs},
142 $_->{country}, $_->{title}, $_->{labels},
143 $_->{disambiguation}, $_->{status},
144 )
145 } @$releases)
146 }
147
148 sub format_tags {
149 local $_;
150 my %parm = @_;
151 my $hack_parens = $parm{hack_parens} // 0;
152 my $release = $parm{release};
153 my $artist = 'ARTIST=' . $release->{artist};
154 my $album = 'ALBUM=' . $release->{title};
155 my $date = 'DATE=' . $release->{date};
156 my $discs = $release->{discs};
157 my $multi = @$discs > 1;
158 my $d = 0;
159 join("\n",
160 map {
161 my @discnum;
162 if ($multi) {
163 $d++;
164 @discnum = ("DISCNUMBER=$d");
165 }
166 my $t = 0;
167 (
168 $artist, $album, $date, @discnum,
169 map {
170 $t++;
171 my @tags;
172 if ($hack_parens) {
173 /([^(]+)(.*)/;
174 my $title = $1;
175 my $version = $2;
176 if (length($version)) {
177 $title =~ s/\s+$//;
178 $version =~ s/^\(//;
179 $version =~ s/\)$//;
180 @tags = ("TITLE[$t]=$title", "VERSION[$t]=$version");
181 } else {
182 @tags = ("TITLE[$t]=$title")
183 }
184 } else {
185 @tags = ("TITLE[$t]=$_");
186 }
187 @tags
188 } @$_
189 )
190 } @$discs
191 )
192 }
193
194 sub main {
195 my $arg = shift || die;
196 if ($arg eq '-Q') {
197 my $artist = shift || die;
198 my $title = shift || die;
199 my $url =
200 'https://musicbrainz.org/ws/2/release?query='
201 . uri_escape("artist:\"$artist\" AND \"$title\" AND format:CD");
202 open(my $fh, '-|', 'curl', '--silent', '-Haccept:application/json', $url)
203 || die;
204 my @body;
205 while (<$fh>) {
206 push(@body, $_);
207 }
208 say(list_releases(read_releases(join('', @body))));
209 } else {
210 my $hack_parens;
211 if ($arg eq '-p') {
212 $arg = shift || die;
213 $hack_parens = 1;
214 } else { $hack_parens = 0 }
215 my $url = "https://musicbrainz.org/ws/2/release/$arg?inc=artist-credits+recordings";
216 open(my $fh, '-|', 'curl', '--silent', '-Haccept:application/json', $url)
217 || die;
218 say(format_tags(
219 hack_parens => $hack_parens,
220 release => read_release(join('', <$fh>)),
221 ));
222 }
223 0
224 }
225
226 if (!caller) {
227 exit(main(@ARGV))
228 }
229
230 1;
231
232 # Local variables:
233 # perl-indent-level: 8
234 # indent-tabs-mode: t
235 # End: