commit dfb21dfc2bb2d519b6dd504bccea0310e2256b47 Author: moex3 <46636583+moex3@users.noreply.github.com> Date: Wed Apr 14 22:47:44 2021 +0200 Up. diff --git a/README.md b/README.md new file mode 100644 index 0000000..925f4a4 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# flac2mp3.pl +Perl script to transcode flac into mp3, while keeping the tags. Because hundreds of these scripts aren't enough apparently. + +Dependencies: `flac` `metaflac` `lame` + +# Usage +```bash +flac2mp3.pl ~/flacs ~/mp3 +``` + +Tags can be overridden with options. Only the genre tag is supported rith now. +```bash +flac2mp3.pl --genre 145 ~/flacs ~/mp3 +``` +This would add the genre as 'Anime'. Run `lame --genre-list` for the whole list. Can be specified multiple ones, with comma separation (i think). diff --git a/flac2mp3.pl b/flac2mp3.pl new file mode 100755 index 0000000..dfb1f5c --- /dev/null +++ b/flac2mp3.pl @@ -0,0 +1,188 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Getopt::Long; +use File::Find; +use Data::Dumper; +use File::Basename; + +# this is a godsent page +# https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping +# a lot of this may not work +my %idLookup = ( + album => 'TALB', + albumsort => 'TSOA', + discsubtitle => 'TSST', + grouping => 'TIT1', + title => 'TIT2', + titlesort => 'TSOT', + subtitle => 'TIT3', + subtitle => 'TIT3', + albumartist => 'TPE2', + albumartistsort => 'TSO2', # Maybe? + artist => 'TPE1', + artistsort => 'TSOP', + arranger => 'TIPL=arranger', + author => 'TEXT', + composer => 'TCOM', + conductor => 'TPE3', + engineer => 'TIPL=engineer', + djmixer => 'TIPL=DJ-mix', + mixer => 'TIPL=mix', + #performer => 'TMCL', # This produces some really weird tags + producer => 'TIPL=producer', + publisher => 'TPUB', + label => 'TPUB', + remixer => 'TPE4', + discnumber => ['TPOS', sub { + my $t = shift; + return "$t->{discnumber}" if !exists($t->{disctotal}); + return "$t->{discnumber}/$t->{disctotal}"; + }], + disctotal => undef, + tracknumber => ['TRCK', sub { + my $t = shift; + return "$t->{tracknumber}" if !exists($t->{tracktotal}); + return "$t->{tracknumber}/$t->{tracktotal}"; + }], + tracktotal => undef, + date => 'TDRC', # This is for id3v2.4 + originaldate => 'TDOR', # Also for 2.4 only + isrc => 'TSRC', + barcode => 'TXXX=BARCODE', + catalognumber => 'TXXX=CATALOGNUMBER', + catalog => 'TXXX=CATALOGNUMBER', + catalogid => 'TXXX=CATALOGNUMBER', + '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 24; + }], + #mood => ['TMOO', sub { + #}], + bpm => 'TBPM', + comment => 'COMM', + copyright => 'TCOP', + language => 'TLAN', + script => 'TXXX=SCRIPT', + lyrics => 'USLT', +); + +my $opt_genre; +my $opt_help; +GetOptions( + "genre=s" => \$opt_genre, + "help" => \$opt_help +) or die("Error in command line option"); + +if ($opt_help) { + help(); +} + +if (scalar(@ARGV) != 2) { + print("Bad arguments\n"); + usage(); +} + +my ($IDIR, $ODIR) = @ARGV; + +find({ wanted => \&iterFlac, no_chdir => 1 }, $IDIR); + +sub iterFlac { + # Return if file is not a file, or if it's not a flac + return if (!-f || !/\.flac$/); + + my @required_tags = ("artist", "title", "album", "tracknumber"); + my $flac = $_; + my $dest = "$ODIR/" . basename($flac); + $dest =~ s/\.flac$/\.mp3/; + my $tags = getFlacTags($flac); + + my $has_req_tags = 1; + foreach (@required_tags) { + if (!exists($tags->{lc($_)})) { + $has_req_tags = 0; + last; + } + } + if (!$has_req_tags) { + print("WARNING: File: '$flac' does not have all the required tags. Skipping\n"); + return; + } + + argsToTags($tags); + my $tagopts = tagsToOpts($tags); + + qx(flac -cd "$flac" | lame -V0 -S --vbr-new --add-id3v2 @$tagopts - "$dest"); +} + +sub argsToTags { + my $argTags = shift; + if (defined($opt_genre)) { + $argTags->{genre} = $opt_genre; + } +} + +sub tagsToOpts { + my $tags = shift; + my @tagopts; + + # TODO escape ' and =? + 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)) { + push(@tagopts, qq(--tv '$tagName=$tags->{$currKey}')); + } elsif ($type eq "ARRAY") { + my $tagCont = $tagName->[1]->($tags); + push(@tagopts, qq(--tv '$tagName->[0]=$tagCont')); + } + + } + + #print(Dumper(\@tagopts)); + return \@tagopts; +} + +sub getFlacTags { + my $flac = shift; + + my %tags; + my @tagtxt = qx(metaflac --list --block-type=VORBIS_COMMENT -- "$flac"); + foreach my $tagline (@tagtxt) { + if ($tagline =~ /comment\[\d+\]:\s(.*?)=(.*)/) { + $tags{lc($1)} = $2; + } + } + return \%tags; +} + +sub usage { + print("Usage: flac2mp3.pl [-h | --help] [-g | --genre NUM] \n"); + exit 1; +} + +sub help { + my $h = < + + -h, --help print this help text + -g, --genre NUM force this genre as a tag (lame --genre-list) +EOF + print($h); + exit 0; +} + +# vim: ts=4 sw=4 et sta