Skip to content

Instantly share code, notes, and snippets.

@hinrik
Created June 2, 2010 00:21
Show Gist options
  • Save hinrik/421731 to your computer and use it in GitHub Desktop.
Save hinrik/421731 to your computer and use it in GitHub Desktop.
Encode FLAC files into properly tagged MP3 files
#!/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