mirror of
https://github.com/moex3/flac2mp3.pl
synced 2024-11-24 12:25:55 -05:00
Compare commits
3 Commits
ccc5ba6af9
...
edc18b4871
Author | SHA1 | Date | |
---|---|---|---|
|
edc18b4871 | ||
|
e3dccbcf78 | ||
|
7f36a00b66 |
196
flac2mp3.pl
196
flac2mp3.pl
@ -5,6 +5,7 @@ use Getopt::Long;
|
|||||||
use File::Find;
|
use File::Find;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
use File::Basename;
|
use File::Basename;
|
||||||
|
use File::Temp qw/ tempfile /;
|
||||||
|
|
||||||
my $opt_no_genre;
|
my $opt_no_genre;
|
||||||
my $opt_comment;
|
my $opt_comment;
|
||||||
@ -22,6 +23,16 @@ my %genreMap = (
|
|||||||
# https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html
|
# https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html
|
||||||
# a lot of this may not work
|
# a lot of this may not work
|
||||||
# TODO escape potential 's
|
# 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 = (
|
my %idLookup = (
|
||||||
album => 'TALB',
|
album => 'TALB',
|
||||||
albumsort => 'TSOA',
|
albumsort => 'TSOA',
|
||||||
@ -35,15 +46,15 @@ my %idLookup = (
|
|||||||
albumartistsort => 'TSO2', # Maybe?
|
albumartistsort => 'TSO2', # Maybe?
|
||||||
artist => 'TPE1',
|
artist => 'TPE1',
|
||||||
artistsort => 'TSOP',
|
artistsort => 'TSOP',
|
||||||
arranger => 'TIPL=arranger',
|
arranger => ['TIPL', 'arranger:'],
|
||||||
author => 'TEXT',
|
author => 'TEXT',
|
||||||
composer => 'TCOM',
|
composer => 'TCOM',
|
||||||
conductor => 'TPE3',
|
conductor => 'TPE3',
|
||||||
engineer => 'TIPL=engineer',
|
engineer => ['TIPL', 'engineer:'],
|
||||||
djmixer => 'TIPL=DJ-mix',
|
djmixer => ['TIPL', 'DJ-mix:'],
|
||||||
mixer => 'TIPL=mix',
|
mixer => ['TIPL', 'mix:'],
|
||||||
#performer => 'TMCL', # This produces some really weird tags
|
#performer => 'TMCL', # This produces some really weird tags
|
||||||
producer => 'TIPL=producer',
|
producer => ['TIPL', 'producer:'],
|
||||||
publisher => 'TPUB',
|
publisher => 'TPUB',
|
||||||
organization => 'TPUB',
|
organization => 'TPUB',
|
||||||
label => 'TPUB',
|
label => 'TPUB',
|
||||||
@ -51,51 +62,51 @@ my %idLookup = (
|
|||||||
discnumber => ['TPOS', sub {
|
discnumber => ['TPOS', sub {
|
||||||
my $t = shift;
|
my $t = shift;
|
||||||
my $totalkey = exists($t->{disctotal}) ? 'disctotal' : 'totaldiscs';
|
my $totalkey = exists($t->{disctotal}) ? 'disctotal' : 'totaldiscs';
|
||||||
return "$t->{discnumber}" if !exists($t->{$totalkey});
|
return "$t->{discnumber}[0]" if !exists($t->{$totalkey});
|
||||||
return "$t->{discnumber}/$t->{$totalkey}";
|
return "$t->{discnumber}[0]/$t->{$totalkey}[0]";
|
||||||
}],
|
}],
|
||||||
totaldiscs => undef,
|
totaldiscs => undef,
|
||||||
disctotal => undef,
|
disctotal => undef,
|
||||||
tracknumber => ['TRCK', sub {
|
tracknumber => ['TRCK', sub {
|
||||||
my $t = shift;
|
my $t = shift;
|
||||||
my $totalkey = exists($t->{tracktotal}) ? 'tracktotal' : 'totaltracks';
|
my $totalkey = exists($t->{tracktotal}) ? 'tracktotal' : 'totaltracks';
|
||||||
return "$t->{tracknumber}" if !exists($t->{$totalkey});
|
return "$t->{tracknumber}[0]" if !exists($t->{$totalkey});
|
||||||
return "$t->{tracknumber}/$t->{$totalkey}";
|
return "$t->{tracknumber}[0]/$t->{$totalkey}[0]";
|
||||||
}],
|
}],
|
||||||
totaltracks => undef,
|
totaltracks => undef,
|
||||||
tracktotal => undef,
|
tracktotal => undef,
|
||||||
#date => 'TDRC', # This is for id3v2.4
|
#date => 'TDRC', # This is for id3v2.4
|
||||||
#date => 'TYER',
|
#date => 'TYER',
|
||||||
date => [undef, sub {
|
date => sub {
|
||||||
my $t = shift;
|
my $t = shift;
|
||||||
my $date = $t->{date};
|
my $date = $t->{date}[0];
|
||||||
if (length($date) == 4) { # Only year
|
if (length($date) == 4) { # Only year
|
||||||
return "TYER=$date";
|
return ["TYER", "$date"];
|
||||||
}
|
}
|
||||||
if (!($date =~ m/^\d{4}\.\d{2}\.\d{2}$/)) {
|
if (!($date =~ m/^\d{4}\.\d{2}\.\d{2}$/)) {
|
||||||
print("Date format unknown: $date\n");
|
print("Date format unknown: $date\n");
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
$date =~ s/\./-/g;
|
$date =~ s/\./-/g;
|
||||||
return "TDRL=$date"; # Release date
|
return ["TDRL", "$date"]; # Release date
|
||||||
}],
|
},
|
||||||
originaldate => 'TDOR', # Also for 2.4 only
|
originaldate => 'TDOR', # Also for 2.4 only
|
||||||
'release date' => 'TDOR', # Also for 2.4 only
|
'release date' => 'TDOR', # Also for 2.4 only
|
||||||
isrc => 'TSRC',
|
isrc => 'TSRC',
|
||||||
barcode => 'TXXX=BARCODE',
|
barcode => ['TXXX', 'BARCODE:'],
|
||||||
catalog => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalog'); } ],
|
catalog => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalog'); } ],
|
||||||
catalognumber => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalognumber'); } ],
|
catalognumber => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalognumber'); } ],
|
||||||
catalogid => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'catalogid'); } ],
|
catalogid => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'catalogid'); } ],
|
||||||
labelno => ['TXXX=CATALOGNUMBER', sub { return tagmap_catalogid(shift, 'labelno'); } ],
|
labelno => ['TXXX', sub { return "CATALOGNUMBER:" . tagmap_catalogid(shift, 'labelno'); } ],
|
||||||
'encoded-by' => 'TENC',
|
#'encoded-by' => 'TENC',
|
||||||
encoder => 'TSSE',
|
#encoder => 'TSSE',
|
||||||
encoding => 'TSSE',
|
#encoding => 'TSSE',
|
||||||
'encoder settings' => 'TSSE',
|
#'encoder settings' => 'TSSE',
|
||||||
media => 'TMED',
|
media => 'TMED',
|
||||||
genre => ['TCON', sub {
|
genre => ['TCON', sub {
|
||||||
return undef if ($opt_no_genre);
|
return undef if ($opt_no_genre);
|
||||||
|
|
||||||
my $genreName = shift->{genre};
|
my $genreName = shift->{genre}[0];
|
||||||
if (!exists($genreMap{lc($genreName)})) {
|
if (!exists($genreMap{lc($genreName)})) {
|
||||||
# If no genre number exists, use the name
|
# If no genre number exists, use the name
|
||||||
return $genreName;
|
return $genreName;
|
||||||
@ -105,38 +116,41 @@ my %idLookup = (
|
|||||||
#mood => ['TMOO', sub {
|
#mood => ['TMOO', sub {
|
||||||
#}],
|
#}],
|
||||||
bpm => 'TBPM',
|
bpm => 'TBPM',
|
||||||
comment => ['COMM=Comment', sub {
|
comment => ['COMM', sub {
|
||||||
return undef if (defined($opt_comment) && $opt_comment eq "");
|
return undef if (defined($opt_comment) && $opt_comment eq "");
|
||||||
return shift->{comment};
|
return "Comment:" . shift->{comment}[0];
|
||||||
}],
|
}],
|
||||||
copyright => 'TCOP',
|
copyright => 'TCOP',
|
||||||
language => 'TLAN',
|
language => 'TLAN',
|
||||||
#replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK',
|
#replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK',
|
||||||
#replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN',
|
#replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN',
|
||||||
replaygain_track_gain => sub {
|
replaygain_track_gain => sub {
|
||||||
|
print("EEEEEEERRRRRRRRROOOOOOOOOORRRRRRRRRRRE FIXXXXXXXXXXXX THIIIIIIIIISSSSSSSS\n");
|
||||||
|
exit(1);
|
||||||
return undef if (!$opt_rg);
|
return undef if (!$opt_rg);
|
||||||
shift->{replaygain_track_gain} =~ /^(-?\d+\.\d+) dB$/;
|
shift->{replaygain_track_gain}[0] =~ /^(-?\d+\.\d+) dB$/;
|
||||||
my $gain_db = $1;
|
my $gain_db = $1;
|
||||||
exit(1) if ($gain_db eq "");
|
exit(1) if ($gain_db eq "");
|
||||||
return "--replaygain-accurate --gain $gain_db";
|
return "--replaygain-accurate --gain $gain_db";
|
||||||
|
# TODO this lulw
|
||||||
},
|
},
|
||||||
|
|
||||||
#replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN',
|
#replaygain_album_gain => 'TXXX=REPLAYGAIN_ALBUM_GAIN',
|
||||||
#replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK',
|
#replaygain_album_peak => 'TXXX=REPLAYGAIN_ALBUM_PEAK',
|
||||||
#replaygain_track_gain => 'TXXX=REPLAYGAIN_TRACK_GAIN',
|
#replaygain_track_gain => 'TXXX=REPLAYGAIN_TRACK_GAIN',
|
||||||
#replaygain_track_peak => 'TXXX=REPLAYGAIN_TRACK_PEAK',
|
#replaygain_track_peak => 'TXXX=REPLAYGAIN_TRACK_PEAK',
|
||||||
script => 'TXXX=SCRIPT',
|
script => ['TXXX', 'SCRIPT:'],
|
||||||
lyrics => 'USLT',
|
lyrics => 'USLT',
|
||||||
circle => 'TXXX=CIRCLE',
|
circle => ['TXXX', 'CIRCLE:'],
|
||||||
event => 'TXXX=EVENT',
|
event => ['TXXX', 'EVENT:'],
|
||||||
discid => 'TXXX=DISCID',
|
discid => ['TXXX', 'DISCID:'],
|
||||||
originaltitle => 'TXXX=ORIGINALTITLE',
|
originaltitle => ['TXXX', 'ORIGINALTITLE:'],
|
||||||
);
|
);
|
||||||
sub tagmap_catalogid {
|
sub tagmap_catalogid {
|
||||||
my $t = shift;
|
my $t = shift;
|
||||||
my $own_tag_name = shift;
|
my $own_tag_name = shift;
|
||||||
return undef if (defined($opt_catid) && $opt_catid eq "");
|
return undef if (defined($opt_catid) && $opt_catid eq "");
|
||||||
return $t->{$own_tag_name};
|
return $t->{$own_tag_name}[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
my $opt_genre;
|
my $opt_genre;
|
||||||
@ -205,15 +219,56 @@ sub iterFlac {
|
|||||||
shellsan(\$dest);
|
shellsan(\$dest);
|
||||||
my $cmd;
|
my $cmd;
|
||||||
if ($opt_cbr) {
|
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 {
|
} 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");
|
#print("Debug - CMD: [$cmd]\n");
|
||||||
qx($cmd);
|
qx($cmd);
|
||||||
if ($? != 0) {
|
if ($? != 0) {
|
||||||
exit(1);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub embedImageFromFlac {
|
||||||
|
my $flac = shift;
|
||||||
|
my $mp3 = shift;
|
||||||
|
|
||||||
|
# I can't get the automatic deletion working :c
|
||||||
|
my (undef, $fname) = tempfile();
|
||||||
|
# Export image from flac
|
||||||
|
qx(metaflac --export-picture-to='$fname' -- '$flac');
|
||||||
|
if ($? != 0) {
|
||||||
|
# Probably no image
|
||||||
|
unlink($fname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
# Extract mime type too
|
||||||
|
my $pinfo = qx(metaflac --list --block-type=PICTURE -- '$flac');
|
||||||
|
$pinfo =~ m/MIME type: (.*)/;
|
||||||
|
my $mimeType = $1;
|
||||||
|
|
||||||
|
# Add image to mp3
|
||||||
|
qx(mid3v2 -p '${fname}:cover:3:$mimeType' -- '$mp3');
|
||||||
|
unlink($fname);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub argsToTags {
|
sub argsToTags {
|
||||||
@ -244,52 +299,55 @@ sub tagsToOpts {
|
|||||||
my $tags = shift;
|
my $tags = shift;
|
||||||
my @tagopts;
|
my @tagopts;
|
||||||
|
|
||||||
# TODO escape ' and =?
|
# TODO escape stuff?
|
||||||
foreach my $currKey (keys (%$tags)) {
|
foreach my $currKey (keys (%$tags)) {
|
||||||
if (!exists($idLookup{$currKey})) {
|
if (!exists($idLookup{$currKey})) {
|
||||||
print("Tag: '$currKey' doesn't have a mapping, skipping\n");
|
print("Tag: '$currKey' doesn't have a mapping, skipping\n");
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
my $tagName = $idLookup{$currKey};
|
my $tagMapping = $idLookup{$currKey};
|
||||||
my $type = ref($tagName);
|
my $type = ref($tagMapping);
|
||||||
if ($type eq "" && defined($tagName)) {
|
if ($type eq "" && defined($tagMapping)) {
|
||||||
# If tag name is defined and tag contents exists
|
# If tag name is defined and tag contents exists (aka not silenced)
|
||||||
my $tagCont = $tags->{$currKey};
|
foreach my $tagCont (@{$tags->{$currKey}}) {
|
||||||
shellsan(\$tagCont);
|
shellsan(\$tagCont);
|
||||||
push(@tagopts, qq(--tv '$tagName=$tagCont'));
|
push(@tagopts, qq('--$tagMapping' '$tagCont'));
|
||||||
|
}
|
||||||
} elsif ($type eq "ARRAY") {
|
} elsif ($type eq "ARRAY") {
|
||||||
my $tagCont = $tagName->[1]->($tags);
|
my $mapKey = $tagMapping->[0];
|
||||||
my $tagKey = $tagName->[0];
|
my $mapCont = $tagMapping->[1];
|
||||||
if (defined($tagCont)) {
|
my $mapContType = ref($mapCont);
|
||||||
if (defined($tagKey)) {
|
if (not defined($mapCont)) {
|
||||||
shellsan(\$tagCont);
|
print("WHUT???\n");
|
||||||
push(@tagopts, qq(--tv '$tagName->[0]=$tagCont'));
|
exit(1);
|
||||||
} 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'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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') {
|
} elsif ($type eq 'CODE') {
|
||||||
# If we have just a code reference
|
# If we have just a code reference
|
||||||
# do not assume, that this is a tag, rather a general cmd opt
|
# do not assume, that this is a tag, rather a general cmd opt
|
||||||
my $opt = $tagName->($tags);
|
#my $opt = $tagName->($tags);
|
||||||
if (defined($opt)) {
|
#if (defined($opt)) {
|
||||||
shellsan(\$opt);
|
#shellsan(\$opt);
|
||||||
push(@tagopts, qq($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;
|
return \@tagopts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +365,11 @@ sub getFlacTags {
|
|||||||
print("Empty tag: $1\n");
|
print("Empty tag: $1\n");
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
$tags{lc($1)} = $2;
|
if (not exists($tags{lc($1)})) {
|
||||||
|
@{$tags{lc($1)}} = ($2);
|
||||||
|
} else {
|
||||||
|
push(@{$tags{lc($1)}}, $2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return \%tags;
|
return \%tags;
|
||||||
|
Loading…
Reference in New Issue
Block a user