|
|
@@ -9,6 +9,7 @@ use File::Basename; |
|
|
|
my $opt_no_genre; |
|
|
|
my $opt_comment; |
|
|
|
my $opt_catid; |
|
|
|
my $opt_rg; |
|
|
|
|
|
|
|
# TODO fill this out |
|
|
|
my %genreMap = ( |
|
|
@@ -18,6 +19,7 @@ my %genreMap = ( |
|
|
|
|
|
|
|
# this is a godsent page |
|
|
|
# https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping |
|
|
|
# https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html |
|
|
|
# a lot of this may not work |
|
|
|
# TODO escape potential 's |
|
|
|
my %idLookup = ( |
|
|
@@ -43,6 +45,7 @@ my %idLookup = ( |
|
|
|
#performer => 'TMCL', # This produces some really weird tags |
|
|
|
producer => 'TIPL=producer', |
|
|
|
publisher => 'TPUB', |
|
|
|
organization => 'TPUB', |
|
|
|
label => 'TPUB', |
|
|
|
remixer => 'TPE4', |
|
|
|
discnumber => ['TPOS', sub { |
|
|
@@ -62,22 +65,33 @@ my %idLookup = ( |
|
|
|
totaltracks => undef, |
|
|
|
tracktotal => undef, |
|
|
|
#date => 'TDRC', # This is for id3v2.4 |
|
|
|
date => 'TYER', |
|
|
|
#date => 'TYER', |
|
|
|
date => [undef, sub { |
|
|
|
my $t = shift; |
|
|
|
my $date = $t->{date}; |
|
|
|
if (length($date) == 4) { # Only year |
|
|
|
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 |
|
|
|
}], |
|
|
|
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'); } ], |
|
|
|
'encoded-by' => 'TENC', |
|
|
|
encoder => 'TSSE', |
|
|
|
encoding => 'TSSE', |
|
|
|
'encoder settings' => 'TSSE', |
|
|
|
media => 'TMED', |
|
|
|
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', |
|
|
|
genre => ['TCON', sub { |
|
|
|
return undef if ($opt_no_genre); |
|
|
|
|
|
|
@@ -97,9 +111,26 @@ my %idLookup = ( |
|
|
|
}], |
|
|
|
copyright => 'TCOP', |
|
|
|
language => 'TLAN', |
|
|
|
#replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK', |
|
|
|
#replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN', |
|
|
|
replaygain_track_gain => sub { |
|
|
|
return undef if (!$opt_rg); |
|
|
|
shift->{replaygain_track_gain} =~ /^(-?\d+\.\d+) dB$/; |
|
|
|
my $gain_db = $1; |
|
|
|
exit(1) if ($gain_db eq ""); |
|
|
|
return "--replaygain-accurate --gain $gain_db"; |
|
|
|
}, |
|
|
|
|
|
|
|
#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', |
|
|
|
lyrics => 'USLT', |
|
|
|
circle => 'TXXX=CIRCLE', |
|
|
|
event => 'TXXX=EVENT', |
|
|
|
discid => 'TXXX=DISCID', |
|
|
|
originaltitle => 'TXXX=ORIGINALTITLE', |
|
|
|
); |
|
|
|
sub tagmap_catalogid { |
|
|
|
my $t = shift; |
|
|
@@ -110,12 +141,15 @@ sub tagmap_catalogid { |
|
|
|
|
|
|
|
my $opt_genre; |
|
|
|
my $opt_help; |
|
|
|
my @opt_tagreplace; |
|
|
|
GetOptions( |
|
|
|
"genre|g=s" => \$opt_genre, |
|
|
|
"no-genre|G" => \$opt_no_genre, |
|
|
|
"replay-gain|r" => \$opt_rg, |
|
|
|
"help|h" => \$opt_help, |
|
|
|
"catid=s" => \$opt_catid, |
|
|
|
"comment=s" => \$opt_comment, |
|
|
|
"tagreplace|t=s" => \@opt_tagreplace, |
|
|
|
) or die("Error in command line option"); |
|
|
|
|
|
|
|
if ($opt_help) { |
|
|
@@ -140,9 +174,12 @@ sub iterFlac { |
|
|
|
return if (!-f || !/\.flac$/); |
|
|
|
|
|
|
|
my @required_tags = ("artist", "title", "album", "tracknumber"); |
|
|
|
my $flacDir = substr($File::Find::name, length($IDIR)); |
|
|
|
my $flac = $_; |
|
|
|
my $flac_o = $flac; |
|
|
|
shellsan(\$flac); |
|
|
|
my $dest = "$ODIR/" . basename($flac); |
|
|
|
my $dest = "$ODIR/" . $flacDir; |
|
|
|
#print("DEBUG: $dest\n"); |
|
|
|
$dest =~ s/\.flac$/\.mp3/; |
|
|
|
my $tags = getFlacTags($flac); |
|
|
|
|
|
|
@@ -155,24 +192,45 @@ sub iterFlac { |
|
|
|
} |
|
|
|
if (!$has_req_tags) { |
|
|
|
print("WARNING: File: '$flac' does not have all the required tags. Skipping\n"); |
|
|
|
exit(1); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
argsToTags($tags); |
|
|
|
argsToTags($tags, $flac_o); |
|
|
|
my $tagopts = tagsToOpts($tags); |
|
|
|
|
|
|
|
qx(flac -cd -- '$flac' | lame -V0 -S --vbr-new --add-id3v2 @$tagopts - '$dest'); |
|
|
|
#print("Debug: @$tagopts\n"); |
|
|
|
shellsan(\$dest); |
|
|
|
my $cmd = "flac -cd -- '$flac' | lame -V0 -S --vbr-new -q 0 --add-id3v2 @$tagopts - '$dest'"; |
|
|
|
#print("Debug - CMD: [$cmd]\n"); |
|
|
|
qx($cmd); |
|
|
|
if ($? != 0) { |
|
|
|
exit(1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sub argsToTags { |
|
|
|
my $argTags = shift; |
|
|
|
my $fname = shift; |
|
|
|
$fname =~ s!^.*/!!; |
|
|
|
if (defined($opt_genre)) { |
|
|
|
$argTags->{genre} = $opt_genre; |
|
|
|
} elsif (defined($opt_comment) && $opt_comment ne "") { |
|
|
|
} |
|
|
|
if (defined($opt_comment) && $opt_comment ne "") { |
|
|
|
$argTags->{comment} = $opt_comment; |
|
|
|
} elsif (defined($opt_catid) && $opt_catid ne "") { |
|
|
|
} |
|
|
|
if (defined($opt_catid) && $opt_catid ne "") { |
|
|
|
$argTags->{catalognumber} = $opt_catid; |
|
|
|
} |
|
|
|
if (scalar @opt_tagreplace > 0) { |
|
|
|
foreach my $trepl (@opt_tagreplace) { |
|
|
|
$trepl =~ m!(.*?)/(.*?)=(.*)!; |
|
|
|
my ($freg, $tag, $tagval) = ($1, $2, $3); |
|
|
|
if ($fname =~ m!$freg!) { |
|
|
|
$argTags->{lc($tag)} = $tagval; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sub tagsToOpts { |
|
|
@@ -188,14 +246,38 @@ sub tagsToOpts { |
|
|
|
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')); |
|
|
|
} elsif ($type eq "ARRAY") { |
|
|
|
my $tagCont = $tagName->[1]->($tags); |
|
|
|
my $tagKey = $tagName->[0]; |
|
|
|
if (defined($tagCont)) { |
|
|
|
shellsan(\$tagCont); |
|
|
|
push(@tagopts, qq(--tv '$tagName->[0]=$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')); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} 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)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -209,8 +291,15 @@ sub getFlacTags { |
|
|
|
|
|
|
|
my %tags; |
|
|
|
my @tagtxt = qx(metaflac --list --block-type=VORBIS_COMMENT -- '$flac'); |
|
|
|
if ($? != 0) { |
|
|
|
exit(1); |
|
|
|
} |
|
|
|
foreach my $tagline (@tagtxt) { |
|
|
|
if ($tagline =~ /comment\[\d+\]:\s(.*?)=(.*)/) { |
|
|
|
if ($2 eq '') { |
|
|
|
print("Empty tag: $1\n"); |
|
|
|
next; |
|
|
|
} |
|
|
|
$tags{lc($1)} = $2; |
|
|
|
} |
|
|
|
} |
|
|
@@ -222,7 +311,7 @@ sub shellsan { |
|
|
|
} |
|
|
|
|
|
|
|
sub usage { |
|
|
|
print("Usage: flac2mp3.pl [-h | --help] [-g | --genre NUM] <input_dir> <output_dir>\n"); |
|
|
|
print("Usage: flac2mp3.pl [-h | --help] [-r] [-g | --genre NUM] <input_dir> <output_dir>\n"); |
|
|
|
exit 1; |
|
|
|
} |
|
|
|
|
|
|
@@ -234,8 +323,11 @@ Usage: |
|
|
|
-h, --help print this help text |
|
|
|
-g, --genre NUM force this genre as a tag (lame --genre-list) |
|
|
|
-G, --no-genre ignore genre in flac file |
|
|
|
-r, --replay-gain use replay gain values |
|
|
|
--catid STRING the catalog id to set (or "") |
|
|
|
--comment STRING the comment to set (or "") |
|
|
|
-t --tagreplace STR Replace flac tags for a specific file only |
|
|
|
Like -t '02*flac/TITLE=Some other title' |
|
|
|
EOF |
|
|
|
print($h); |
|
|
|
exit 0; |
|
|
|