Created
June 2, 2010 00:21
-
-
Save hinrik/421731 to your computer and use it in GitHub Desktop.
Encode FLAC files into properly tagged MP3 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use strict; | |
use warnings; | |
use File::Spec::Functions qw(catfile splitpath); | |
use File::Which; | |
use Getopt::Long qw(:config auto_help); | |
use List::Util qw(first); | |
use Pod::Usage; | |
use String::ShellQuote; | |
my $VERSION = '0.01'; | |
my @deps = qw(flac metaflac lame eyeD3 mp3gain); | |
for my $dep (@deps) { | |
die "Dependency $dep not found" if !defined which($dep); | |
} | |
my %quality = ( | |
'v2' => 'lame --preset fast standard --noreplaygain', | |
'v0' => 'lame --preset fast extreme --noreplaygain', | |
'cbr' => 'lame --preset insane --noreplaygain', | |
); | |
my %flac_eyed3 = ( | |
artist => sub { return '-a', shell_quote($_[0]) }, | |
album => sub { return '-A', shell_quote($_[0]) }, | |
title => sub { return '-t', shell_quote($_[0]) }, | |
tracknumber => sub { return '-n', shell_quote($_[0]) }, | |
genre => sub { return '-G', shell_quote($_[0]) }, | |
date => sub { return '-Y', shell_quote($_[0]) }, | |
discnumber => sub { return shell_quote("--set-text-frame=TPOS:$_[0]") }, | |
); | |
GetOptions( | |
'd|debug' => \my $debug, | |
'R|no-replaygain' => \my $no_replaygain, | |
#'P|no-playlist' => \my $no_playlist, | |
'q|quality=s@' => \(my $quality = [qw(v2 v0 cbr)]), | |
'v|version' => sub { print "flac2mp3 $VERSION\n"; exit }, | |
) or pod2usage(); | |
my @files = grep { /\.flac$/ } @ARGV; | |
die "No FLAC files specified\n" if !@files; | |
for my $key (keys %quality) { | |
delete $quality{$key} if !first { $_ eq $key } @$quality; | |
} | |
for my $file (@files) { | |
my $q_flac = shell_quote($file); | |
my $track = (splitpath($file))[2]; | |
$track =~ s/\.flac$//; | |
my $q_wav = shell_quote("$track.wav"); | |
my $q_mp3 = shell_quote("$track.mp3"); | |
print "Reading tags from $track.flac\n"; | |
my @tags = split /\n/, cmd_x("metaflac --export-tags-to=- $q_flac"); | |
print "Decoding $track.flac to WAV\n"; | |
cmd("flac --decode -o $q_wav $q_flac >/dev/null 2>&1") if !-e "$track.wav"; | |
while (my ($dir, $encode_cmd) = each %quality) { | |
mkdir $dir; | |
die "Couldn't create dir $dir\n" if !-d $dir; | |
my $mp3 = catfile($dir, "$track.mp3"); | |
print "Encoding $track.wav to $dir MP3\n"; | |
cmd("$encode_cmd $q_wav ".shell_quote($mp3)." >/dev/null 2>&1") if !-f $mp3; | |
} | |
print "Deleting $track.wav\n"; | |
unlink "$track.wav"; | |
my @tag_opts; | |
for my $tag_line (@tags) { | |
my ($tag, $value) = split /=/, $tag_line, 2; | |
$tag = lc $tag; | |
if (exists $flac_eyed3{$tag}) { | |
push @tag_opts, $flac_eyed3{$tag}->($value); | |
} | |
} | |
my @mp3s = map { shell_quote(catfile($_, "$track.mp3")) } keys %quality; | |
print "Tagging $track.mp3\n"; | |
cmd("eyeD3 --to-v2.3 @tag_opts @mp3s >/dev/null 2>&1"); | |
} | |
=pod | |
unless ($no_playlist) { | |
my $dir = (keys %quality)[0]; | |
my @mp3s = map { | |
$_ = (splitpath($_))[2]; | |
s/\.flac$//; | |
catfile($dir, "$_.mp3") | |
} @files; | |
# TODO: create playlists | |
} | |
=cut | |
unless ($no_replaygain) { | |
my $dir = (keys %quality)[0]; | |
my @mp3s = map { | |
$_ = (splitpath($_))[2]; | |
s/\.flac$//; | |
catfile($dir, "$_.mp3") | |
} @files; | |
print "Computing ReplayGain values for album\n"; | |
my @opts = mp3gain(@mp3s); | |
for my $opts (@opts) { | |
my ($file, $frames) = @$opts; | |
$file = (splitpath($file))[2]; | |
my $q_file = shell_quote($file); | |
my @track_mp3s = map { shell_quote(catfile($_, $file)) } keys %quality; | |
print "Adding ReplayGain tags to $file\n"; | |
cmd("eyeD3 --to-v2.3 ".join(' ', map({ "--set-user-text-frame='$_'" } @$frames))." @track_mp3s >/dev/null 2>&1"); | |
} | |
} | |
sub mp3gain { | |
my @files = @_; | |
my $files = shell_quote(@files); | |
my @tracks_raw = split /\n\n/, cmd_x("mp3gain -s s $files"); | |
my $album_raw = splice @tracks_raw, -1; | |
my %album; | |
$album{gain} = sprintf('%.2f dB', $album_raw =~ /^Recommended "Album" dB change for all files: (.+)$/m); | |
$album{tracks} = []; | |
for my $track_raw (@tracks_raw) { | |
my %track; | |
my ($file) = $track_raw =~ /\A(.+)\n/m; | |
next if $file !~ /\.mp3$/i; | |
($track{gain}) = $track_raw =~ /^Recommended "Track" dB change: (.+)$/m; | |
$track{gain} = sprintf('%.2f dB', $track{gain}); | |
($track{peak}) = $track_raw =~ /^Max PCM sample at current gain: (.+)$/m; | |
$album{peak} = $track{peak} if !defined $album{peak} || $track{peak} > $album{peak}; | |
$track{peak} = sprintf('%.6f', $track{peak} / 32768); | |
push @{ $album{tracks} }, [$file, \%track]; | |
} | |
$album{peak} = sprintf('%.6f', $album{peak} / 32768); | |
my @opts; | |
for my $track (@{ $album{tracks} }) { | |
my ($file, $track_values) = @$track; | |
my @frames = ( | |
"replaygain_track_gain:$track_values->{gain}", | |
"replaygain_track_peak:$track_values->{peak}", | |
(@{ $album{tracks} } > 1 | |
? ("replaygain_album_gain:$album{gain}", | |
"replaygain_album_peak:$album{peak}") | |
: () | |
) | |
); | |
push @opts, [$file, \@frames] | |
} | |
return @opts; | |
} | |
sub cmd { | |
my (@cmd) = @_; | |
print " @cmd\n" if $debug; | |
system @cmd; | |
if (($? >> 8) != 0) { | |
die "The following command failed:\n @cmd\n"; | |
} | |
return; | |
} | |
sub cmd_x { | |
my (@cmd) = @_; | |
print " @cmd\n" if $debug; | |
my $out = qx/@cmd/; | |
if (($? >> 8) != 0) { | |
die "The following command failed:\n @cmd\n"; | |
} | |
return $out; | |
} | |
=encoding utf8 | |
=head1 NAME | |
flac2mp3 - Encode FLAC files into properly tagged MP3 files | |
=head1 SYNOPSIS | |
B<flac2mp3> [options] <files> | |
Options: | |
-d, --debug Print the commands that will be executed | |
-q FOO, --quality FOO Either v2, v0, or cbr (default: all) | |
-R, --no-replaygain Don't compute and tag ReplayGain values | |
-h, --help Print this help message | |
-v, --version Print version | |
Note: Multiple -q options are allowed. | |
The resulting MP3 files will go into F<v2>, F<v0>, and F<cbr> directories | |
inside the current directory. | |
Examples: | |
# encode to v2, v0, and cbr | |
flac2mp3 My_artist-My_album/*.flac | |
# only encode to v0 | |
flac2mp3 -q v0 My_artist-My_album/*.flac | |
=head1 DESCRIPTION | |
This little script encodes FLAC files into MP3 files of various qualities. | |
It also copies the FLAC tags into ID3 tags and computes ReplayGain tags. | |
=head1 DEPENDENCIES | |
The following programs are required: | |
=over 4 | |
=item flac | |
=item metaflac | |
=item lame | |
=item eyeD3 | |
=item mp3gain | |
=back | |
=head1 AUTHOR | |
Hinrik E<Ouml>rn SigurE<eth>sson, hinrik.sig@gmail.com | |
=head1 LICENSE AND COPYRIGHT | |
Copyright 2010 Hinrik E<Ouml>rn SigurE<eth>sson | |
This program is free software, you can redistribute it and/or modify | |
it under the same terms as Perl itself. | |
=cut |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment