]>
diplodocus.org Git - flac-archive/blob - tags.pl
d5cbce03ababd2376f338b538635239ab28fbcd6
1 package epg
::flac
::archive
::tags
;
5 subroutines for tag processing
13 # POSIX.1-2017, Base Definitions, 3.282 Portable Filename Character Set says
15 # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282
17 # Flac tags (set with `flac -T fieldname=...`) are Vorbis comments.
18 # https://www.xiph.org/vorbis/doc/v-comment.html#fieldnames field names we care about:
19 # - TITLE - Track/Work name
20 # - VERSION - may be used to differentiate multiple versions of the same track title in a single collection. (e.g. remix info)
21 # - ALBUM - The collection name to which this track belongs
22 # - TRACKNUMBER - The track number of this piece
23 # - ARTIST - The artist generally considered responsible for the track
24 # - DATE - Date the track was recorded (XXX I use US release date)
25 # https://age.hobba.nl/audio/mirroredpages/ogg-tagging.html (supposedly mirrored from http://reactor-core.org/ogg-tagging.html )
27 # - DISCNUMBER - if part of a multi-disc album, put the disc number here
28 # - VERSION - e.g. "live", "radio edit"
29 # - PARTNUMBER - part number if a work is divided across tracks
30 # - PART - part name e.g. "Oh sole mio"
31 # https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html
32 # - ALBUMARTIST - maps to ID3v2 TPE2
34 # Input is a directory containing:
35 # - trackNN.cdda.wav - WAV format files ripped from CD-DA audio tracks where NN is track number 01 - 99
36 # - tags - described below
37 # - cover.front.jpeg - optional JPEG format file containing the album front cover
39 # The tags file is composed of two blocks:
46 # - DISCNUMBER (optional)
48 # Track tags may be any of the rest of the tags listed above, suffixed with
49 # [N] where N is the track number. ARTIST, and only ARTIST, may also appear
50 # in the track tags. In this case, it overrides the album artist. In order
51 # to add artists, the album artist must be listed again. For example, Reba
52 # McEntire's "The Heart Won't Lie" on the album "It's Your Call" features
53 # Vince Gill, and is specified as:
54 # ARTIST=Reba McEntire
55 # ALBUM=It's Your Call
56 # TITLE[5]=The Heart Won't Lie
57 # ARTIST[5]=Reba McEntire
58 # ARTIST[5]=Vince Gill
61 # - sub filename in (fa-encode, fa-mp3cd, and flac2mp3) TESTED
62 # - use PART, PARTNUMBER, VERSION in those filenames
63 # - replace tags.pl with fa-tags which the others still import but which is a tool
65 # - -bt=2 Strictest brace tightness
66 # - -pt=2 Strictest paren tightness
67 # - -nvc No vertical code alignment
68 # - -se errors to stderr... how is this not the default?
73 use Exporter
'import';
89 my $s = sprintf('%02d', $n);
90 if (length($s) != 2) {
91 die("$n won't fit into two digits")
104 if (!s/^([^=[\]]+)(\[(\d+)])?=//) {
105 if (defined($name) && $name eq 'DESCRIPTION') {
106 # Qobuz offers FLAC files like this.
107 warn("looks like DESCRIPTION had multiple lines; discarding and returning early: $_");
110 die("no field name in: $_");
113 # TODO validate $name
114 # TODO album tags should be illegal after track tags
116 if ($3 == $tracknum + 2) {
118 } elsif ($3 != $tracknum + 1) {
119 $tracknum++; # increment from 0 to 1 for error message
120 die("illegal track number jump from $tracknum to $3")
122 push(@{$tracks[$tracknum]->{$name}}, $_);
124 push(@{$album{$name}}, $_);
131 sub read_tags_metaflac
{
133 open(my $fh, '-|', 'metaflac', '--no-utf8-convert', '--export-tags-to=-', $fn) || die("metaflac: $!");
134 my @result = read_tags
($fh);
137 die("metaflac exited $?")
139 die("close(metaflac): $!")
147 my $tag = $tags->{$name};
148 if (defined($tag) && @$tag > 0) {
155 my $tag = one_or_undef
(@_);
156 if (!defined($tag)) {
158 die("exactly one $name tag required")
167 Interpret the tags hash for a disc, returning:
172 Artist tag (exactly one required in the hash).
175 Album tag (exactly one required).
178 Date tag (zero or one required).
181 Disc number (zero or one required) as a reference to an array of size 0 or 1.
188 if (!defined($tags{ALBUM
}) || @{$tags{ALBUM
}} != 1) {
189 die('exactly one ALBUM tag required')
191 my $album_tag = $tags{ALBUM
}->[0];
194 if (!defined($tags{ARTIST
}) || @{$tags{ARTIST
}} != 1) {
195 die('exactly one ARTIST tag required')
197 my $artist = $tags{ARTIST
}->[0];
200 if (defined($tags{DATE
})) {
201 if (@{$tags{DATE
}} != 1) {
202 die('one or zero DATE tags required')
204 $date = $tags{DATE
}->[0];
208 if (defined($tags{DISCNUMBER
})) {
209 if (@{$tags{DISCNUMBER
}} != 1) {
210 die('one or zero DISCNUMBER tags required')
212 my ($discnum) = @{$tags{DISCNUMBER
}};
213 @discnumber = ($discnum);
214 # But check it against DISCTOTAL if present.
215 my $total = $tags{DISCTOTAL
};
216 if (defined($total) && @$total > 0) {
217 my ($total) = @$total;
218 if ($total < $discnum) {
219 die("DISCNUMBER=$discnum > DISCTOTAL=$total")
222 # Qobuz offers FLAC files like this.
228 $artist, $album_tag, $date, \
@discnumber
231 # TODO dedup with flac2mp3
234 # TODO probably ought to return hash too. then track_tags would augment
235 my ($artist, $album, $date, $discnumber) = disc_tags
(%$tags);
237 # TODO resurrect whole-disc FLAC?
238 # Stupid hack: only a single-track file should have the
239 # TRACKNUMBER tag, so use it if set for the first pass through
240 # the loop. At the end of the loop, we'll set $track for the
241 # next run, so this continues to work for multi-track files.
247 my $tracknumber = epg
::flac
::archive
::tags
::one
(TRACKNUMBER
=> $tags);
248 my $title = epg
::flac
::archive
::tags
::one
(TITLE
=> $tags);
249 # TODO restore PARTNUMBER and VERSION next time i need them
255 discnumber
=> $discnumber,
256 tracknumber
=> $tracknumber,
261 # TODO confusing relationship with track_tags which is wrong
262 sub track_tags_from_disc
{
263 my ($album, $tracks, $tracknumber) = @_;
264 my $trackcount = @$tracks;
265 if ($trackcount < $tracknumber) {
266 die("requested track $tracknumber out of $trackcount tracks");
268 # Listing the track tags second makes them override the album tags.
269 %$album, %{$tracks->[$tracknumber - 1]}
272 sub mangle_for_file_name
{
274 $fn =~ s/[!,.?]//g; # discard these punctuation marks
275 $fn =~ s/\s+/_/g; # replace all whitespace with _
276 $fn =~ s/[^A-Za-z0-9_]/-/g; # everything else with -
277 $fn =~ s/_[_-]/_/g; # collapse repeated _ or - to _
278 $fn =~ s/-[_-]/_/g; # more
279 $fn =~ s/^[_-]+//; # discard any _ or - at beginning
280 $fn =~ s/[_-]+$//; # at end too