diff --git a/flac2mp3.pl b/flac2mp3.pl index ebf46cf..8d976d8 100755 --- a/flac2mp3.pl +++ b/flac2mp3.pl @@ -23,6 +23,16 @@ my %genreMap = ( # https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html # a lot of this may not work # TODO escape potential 's +# +# Format is: +# Vorbis tag string => Mp3 tag value +# where mp3 tag value may be: +# undef -> Skip this tag +# A string -> Use this as the mp3 tag, and use the vorbis tag value as value +# code -> Execute this function. This should return an array, where [0] is the tag, [1] is the value. +# An array (str, str) -> [0] is the mp3 tag to use, [1] is the value prefix +# An array (str, code) -> [0] is the mp3 tag to use, [1] is a function that is executed, and the result is the tag value +# The code-s here will be called with the flac tags hashmap my %idLookup = ( album => 'TALB', albumsort => 'TSOA', @@ -36,15 +46,15 @@ my %idLookup = ( albumartistsort => 'TSO2', # Maybe? artist => 'TPE1', artistsort => 'TSOP', - arranger => 'TIPL=arranger', + arranger => ['TIPL', 'arranger:'], author => 'TEXT', composer => 'TCOM', conductor => 'TPE3', - engineer => 'TIPL=engineer', - djmixer => 'TIPL=DJ-mix', - mixer => 'TIPL=mix', + engineer => ['TIPL', 'engineer:'], + djmixer => ['TIPL', 'DJ-mix:'], + mixer => ['TIPL', 'mix:'], #performer => 'TMCL', # This produces some really weird tags - producer => 'TIPL=producer', + producer => ['TIPL', 'producer:'], publisher => 'TPUB', organization => 'TPUB', label => 'TPUB', @@ -52,42 +62,42 @@ my %idLookup = ( discnumber => ['TPOS', sub { my $t = shift; my $totalkey = exists($t->{disctotal}) ? 'disctotal' : 'totaldiscs'; - return "$t->{discnumber}" if !exists($t->{$totalkey}); - return "$t->{discnumber}/$t->{$totalkey}"; + return "$t->{discnumber}[0]" if !exists($t->{$totalkey}); + return "$t->{discnumber}[0]/$t->{$totalkey}[0]"; }], totaldiscs => undef, disctotal => undef, tracknumber => ['TRCK', sub { my $t = shift; my $totalkey = exists($t->{tracktotal}) ? 'tracktotal' : 'totaltracks'; - return "$t->{tracknumber}" if !exists($t->{$totalkey}); - return "$t->{tracknumber}/$t->{$totalkey}"; + return "$t->{tracknumber}[0]" if !exists($t->{$totalkey}); + return "$t->{tracknumber}[0]/$t->{$totalkey}[0]"; }], totaltracks => undef, tracktotal => undef, #date => 'TDRC', # This is for id3v2.4 #date => 'TYER', - date => [undef, sub { + date => sub { my $t = shift; - my $date = $t->{date}; + my $date = $t->{date}[0]; if (length($date) == 4) { # Only year - return "TYER=$date"; + return ["TYER", "$date"]; } if (!($date =~ m/^\d{4}\.\d{2}\.\d{2}$/)) { print("Date format unknown: $date\n"); exit 1; } $date =~ s/\./-/g; - return "TDRL=$date"; # Release date - }], + return ["TDRL", "$date"]; # Release date + }, originaldate => 'TDOR', # Also for 2.4 only 'release date' => 'TDOR', # Also for 2.4 only isrc => 'TSRC', - barcode => 'TXXX=BARCODE', - catalog => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalog'); } ], - catalognumber => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalognumber'); } ], - catalogid => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalogid'); } ], - labelno => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'labelno'); } ], + barcode => ['TXXX', 'BARCODE:'], + catalog => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalog'); } ], + catalognumber => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalognumber'); } ], + catalogid => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalogid'); } ], + labelno => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'labelno'); } ], 'encoded-by' => 'TENC', encoder => 'TSSE', encoding => 'TSSE', @@ -96,7 +106,7 @@ my %idLookup = ( genre => ['TCON', sub { return undef if ($opt_no_genre); - my $genreName = shift->{genre}; + my $genreName = shift->{genre}[0]; if (!exists($genreMap{lc($genreName)})) { # If no genre number exists, use the name return $genreName; @@ -106,38 +116,41 @@ my %idLookup = ( #mood => ['TMOO', sub { #}], bpm => 'TBPM', - comment => ['COMM=Comment', sub { + comment => ['COMM', sub { return undef if (defined($opt_comment) && $opt_comment eq ""); - return shift->{comment}; + return "Comment:" . shift->{comment}[0]; }], copyright => 'TCOP', language => 'TLAN', #replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK', #replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN', replaygain_track_gain => sub { + print("EEEEEEERRRRRRRRROOOOOOOOOORRRRRRRRRRRE FIXXXXXXXXXXXX THIIIIIIIIISSSSSSSS\n"); + exit(1); return undef if (!$opt_rg); - shift->{replaygain_track_gain} =~ /^(-?\d+\.\d+) dB$/; + shift->{replaygain_track_gain}[0] =~ /^(-?\d+\.\d+) dB$/; my $gain_db = $1; exit(1) if ($gain_db eq ""); return "--replaygain-accurate --gain $gain_db"; + # TODO this lulw }, #replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN', #replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK', #replaygain_track_gain => 'TXXX=REPLAYGAIN_TRACK_GAIN', #replaygain_track_peak => 'TXXX=REPLAYGAIN_TRACK_PEAK', - script => 'TXXX=SCRIPT', + script => ['TXXX', 'SCRIPT:'], lyrics => 'USLT', - circle => 'TXXX=CIRCLE', - event => 'TXXX=EVENT', - discid => 'TXXX=DISCID', - originaltitle => 'TXXX=ORIGINALTITLE', + circle => ['TXXX', 'CIRCLE:'], + event => ['TXXX', 'EVENT:'], + discid => ['TXXX', 'DISCID:'], + originaltitle => ['TXXX', 'ORIGINALTITLE:'], ); sub tagmap_catalogid { my $t = shift; my $own_tag_name = shift; return undef if (defined($opt_catid) && $opt_catid eq ""); - return $t->{$own_tag_name}; + return $t->{$own_tag_name}[0]; } my $opt_genre; @@ -206,9 +219,9 @@ sub iterFlac { shellsan(\$dest); my $cmd; if ($opt_cbr) { - $cmd = "flac -cd -- '$flac' | lame -S -b 320 -q 0 --add-id3v2 @$tagopts - '$dest'"; + $cmd = "flac -cd -- '$flac' | lame -S -b 320 -q 0 --add-id3v2 - '$dest'"; } else { - $cmd = "flac -cd -- '$flac' | lame -S -V0 --vbr-new -q 0 --add-id3v2 @$tagopts - '$dest'"; + $cmd = "flac -cd -- '$flac' | lame -S -V0 --vbr-new -q 0 --add-id3v2 - '$dest'"; } #print("Debug - CMD: [$cmd]\n"); qx($cmd); @@ -216,6 +229,22 @@ sub iterFlac { exit(1); } + my $mid3v2TagLine = ""; + #print(Dumper(\@$tagopts)); + # Add tags with mid3v2 instead of lame to better support multiple tag values + foreach my $tagItem (@$tagopts) { + $mid3v2TagLine = $mid3v2TagLine . $tagItem . " "; + } + #print(Dumper(\$mid3v2TagLine)); + + my $mid3v2TagCmd = "mid3v2 $mid3v2TagLine -- '$dest'"; + #print("Mid3V2 Debug - CMD: [$mid3v2TagCmd]\n"); + qx($mid3v2TagCmd); + if ($? != 0) { + print("ERROR: At mid3v2 tag set\n"); + exit(1); + } + embedImageFromFlac($flac, $dest); } @@ -270,52 +299,55 @@ sub tagsToOpts { my $tags = shift; my @tagopts; - # TODO escape ' and =? + # TODO escape stuff? foreach my $currKey (keys (%$tags)) { if (!exists($idLookup{$currKey})) { print("Tag: '$currKey' doesn't have a mapping, skipping\n"); next; } - my $tagName = $idLookup{$currKey}; - my $type = ref($tagName); - if ($type eq "" && defined($tagName)) { - # If tag name is defined and tag contents exists - my $tagCont = $tags->{$currKey}; - shellsan(\$tagCont); - push(@tagopts, qq(--tv '$tagName=$tagCont')); + my $tagMapping = $idLookup{$currKey}; + my $type = ref($tagMapping); + if ($type eq "" && defined($tagMapping)) { + # If tag name is defined and tag contents exists (aka not silenced) + foreach my $tagCont (@{$tags->{$currKey}}) { + shellsan(\$tagCont); + push(@tagopts, qq('--$tagMapping' '$tagCont')); + } } elsif ($type eq "ARRAY") { - my $tagCont = $tagName->[1]->($tags); - my $tagKey = $tagName->[0]; - if (defined($tagCont)) { - if (defined($tagKey)) { - shellsan(\$tagCont); - push(@tagopts, qq(--tv '$tagName->[0]=$tagCont')); - } else { - if (ref($tagCont) eq 'ARRAY') { - # If we have an array of tags - foreach my $tC (@$tagCont) { - shellsan(\$tC); - push(@tagopts, qq(--tv '$tC')); - } - } else { - # If we have only one - shellsan(\$tagCont); - push(@tagopts, qq(--tv '$tagCont')); - } + my $mapKey = $tagMapping->[0]; + my $mapCont = $tagMapping->[1]; + my $mapContType = ref($mapCont); + if (not defined($mapCont)) { + print("WHUT???\n"); + exit(1); + } + + if ($mapContType eq "") { + foreach my $tagValue (@{$tags->{$currKey}}) { + shellsan(\$tagValue); + push(@tagopts, qq('--$mapKey' '$mapCont$tagValue')); } + } elsif ($mapContType eq "CODE") { + my $tagValue = $mapCont->($tags); + shellsan(\$tagValue); + push(@tagopts, qq('--$mapKey' '$tagValue')); } } elsif ($type eq 'CODE') { # If we have just a code reference # do not assume, that this is a tag, rather a general cmd opt - my $opt = $tagName->($tags); - if (defined($opt)) { - shellsan(\$opt); - push(@tagopts, qq($opt)); - } - } + #my $opt = $tagName->($tags); + #if (defined($opt)) { + #shellsan(\$opt); + #push(@tagopts, qq($opt)); + #} + my $codeRet = $tagMapping->($tags); + my $mapKey = $codeRet->[0]; + my $mapCont = $codeRet->[1]; + shellsan(\$mapCont); + push(@tagopts, qq('--$mapKey' '$mapCont')); + } } - return \@tagopts; } @@ -333,7 +365,11 @@ sub getFlacTags { print("Empty tag: $1\n"); next; } - $tags{lc($1)} = $2; + if (not exists($tags{lc($1)})) { + @{$tags{lc($1)}} = ($2); + } else { + push(@{$tags{lc($1)}}, $2); + } } } return \%tags;