PDA

View Full Version : Script to convert flac files to mp3



Robin Bowes
2005-02-15, 17:48
Hi all,

My music collection is almost exclusively in flac format as I'm a bit of
an audiophile (external DAC, tweaked amps biamped, etc.).

However, I've just got an iPod (60GB photo) so I need to convert all my
files to a format it can play. I've chosen mp3.

I've tried using Foobar2000 to convert the files, but I couldn't get it
to replicate the directory structure correctly - basically I just want
to make a copy of my entire music library but with the files in mp3
format rather than flac).

Attached is my first draft of a script that does just this: recursively
traverses a directory tree and converts any flac files it finds into
mp3s, writing the output into an identical directory structure.

Requirements:

flac
lame
Dan Sully's perl module Audio::FLAC::Header

It's still very much in test and there are a few features I want to add,
mainly making stuff optional e.g. checking for the existence of the
target file and only running the conversion if the file doesn't exist,
replaygain, different lame settings, checking for invalid tags, etc.

I'd appreciate any feedback on how this code works for you.

Cheers,

R.
--
http://robinbowes.com

#!/usr/bin/perl -w
#
# flac2mp3.pl
#
# Version 0.1
#
# Converts a directory full of flac files into a corresponding directory of mp3 files
#
# Usage:
#
# flac2mp3.pl [options] srcdir destdir
#
# Robin Bowes <robin (AT) robinbowes (DOT) com>

use strict;
use Getopt::Long;
use File::Basename;
use Audio::FLAC::Header;


our $flaccmd = "/usr/bin/flac";
our $lamecmd = "/usr/bin/lame";
our @flacargs = qw ( --decode --stdout );
our @lameargs = qw ( --preset standard --replaygain-accurate );

# Flac: ALBUM ARTIST TITLE DATE GENRE TRACKNUMBER COMMENT
# ID3v2: ALBUM ARTIST TITLE YEAR GENRE TRACK COMMENT

# Tags used in conversion
our %flactags = ( 'ALBUM' => 'ALBUM',
'ARTIST' => 'ARTIST',
'TITLE' => 'TITLE',
'DATE' => 'DATE',
'GENRE' => 'GENRE',
'TRACKNUMBER' => 'TRACKNUMBER',
'COMMENT' => 'COMMENT', );

our %lametags = ( 'ALBUM' => '--tl',
'ARTIST' => '--ta',
'TITLE' => '--tt',
'DATE' => '--ty',
'GENRE' => '--tg',
'TRACKNUMBER' => '--tn',
'COMMENT' => '--tc', );

use vars qw ( $d_info
$d_debug
);

GetOptions (
"d_info!"=>\$d_info,
"d_debug!"=>\$d_debug
);

showusage() unless (scalar @ARGV == 2);

my ($srcdirroot, $destdirroot) = @ARGV;

mkdir($destdirroot, 0777) or die "Can't create directory $destdirroot\n"
unless -d $destdirroot;

die "Source directory not found: $srcdirroot\n"
unless -d $srcdirroot;

find_files($srcdirroot, $destdirroot, $srcdirroot);

1;
## End of main program

sub find_files {
my ($srcdirroot, $destdirroot, $srcdir) = @_;

$::d_info && msg("Processing directory: $srcdir\n");

# remove the src root directory from the beginning
# of the current directory to give the additional
# path information to be added to the destination root.
# Try to create the new destination directory.
(my $extra_path = $srcdir) =~ s/^$srcdirroot//;
my $destdir = $destdirroot . $extra_path;
mkdir($destdir, 0777) or die "Can't create directory $destdir\n"
unless -d $destdir;

# get all directory entries
opendir(SRCDIR, $srcdir) or die "Couldn't open directory $srcdir\n";
my @direntries = readdir(SRCDIR) or
die "Couldn't read directory entries for directory $srcdir\n";
closedir(SRCDIR);

# get all target files within the present directory
my @target_files = map { $_->[1] } # extract pathnames
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { /\.flac$/ } # just flac files
@direntries;

# get all subdirs of the present directory
my @subdirs = map { $_->[1] } # extract pathnames
grep { -d $_->[1] } # only directories
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { !/^\.\.?$/ } # not . or ..
@direntries;

# process all files found in this directory
foreach my $srcfile (@target_files) {
my ($fbase, $fdir, $fext) = fileparse( $srcfile, '\..*$' );
my $destfile = $destdir . "/" . $fbase . ".mp3";
convert_file($srcfile, $destfile);
}

# process any subdirs of present directory
foreach my $srcsubdir (@subdirs) {
&find_files($srcdirroot, $destdirroot, $srcsubdir);
}
}

sub showusage {
print "Usage goes here\n";
exit 1;
}

sub msg {
my $msg = shift;
print "$msg";
}

sub convert_file {
my ($srcfile, $destfile) = @_;

$::d_info && msg("Converting $srcfile to $destfile\n");

# To do:
# Check for existence of target file before converting
# Use some sort of logic to only upgrade if source is newer
# or the tags have changed
# Use command-line switches to override default behaviour

# Get all flac tags
my $flac = Audio::FLAC::Header->new($srcfile);
my $tags = $flac->tags();

# Start building command used to convert file
my $convert_command = "$flaccmd @flacargs $srcfile | $lamecmd @lameargs ";

# Look for all the tags in whcih we are interested
for my $tag (keys(%flactags)) {
# Check the tag exists
if ($tags->{$flactags{$tag}}) {
# Add a switch to lame to add the tag
$convert_command .= " $lametags{$tag} '$tags->{$flactags{$tag}}'";
}
}

# add the last few bits of the conversion command
$convert_command .= " - $destfile";

$::d_debug && msg("$convert_command\n");

# Convert the file
system ($convert_command);

# Should check the return value of the system command

# should optionally reset the destfile timestamp to the same as the srcfile
}
# vim:set tabstop=3:

Robin Bowes
2005-02-15, 18:36
Robin Bowes wrote:
> Attached is my first draft of a script that does just this: recursively
> traverses a directory tree and converts any flac files it finds into
> mp3s, writing the output into an identical directory structure.
>
> Requirements:
>
> flac
> lame
> Dan Sully's perl module Audio::FLAC::Header
>
> It's still very much in test and there are a few features I want to add,
> mainly making stuff optional e.g. checking for the existence of the
> target file and only running the conversion if the file doesn't exist,
> replaygain, different lame settings, checking for invalid tags, etc.
>
> I'd appreciate any feedback on how this code works for you.

OK, so the script breaks pretty easily if the direcotry/filenames have
"special" shell characters in them (e.g. brackets). Looks like I need to
tweak it a little.

R.
--
http://robinbowes.com

Robin Bowes
2005-02-15, 19:02
Robin Bowes wrote:
>
> OK, so the script breaks pretty easily if the direcotry/filenames have
> "special" shell characters in them (e.g. brackets). Looks like I need to
> tweak it a little.

Fixed version attached.

R.

--
http://robinbowes.com

#!/usr/bin/perl -w
#
# flac2mp3.pl
#
# Version 0.1.1
#
# Converts a directory full of flac files into a corresponding directory of mp3 files
#
# Usage:
#
# flac2mp3.pl [options] srcdir destdir
#
# Robin Bowes <robin (AT) robinbowes (DOT) com>

use strict;
use Getopt::Long;
use File::Basename;
use Audio::FLAC::Header;


our $flaccmd = "/usr/bin/flac";
our $lamecmd = "/usr/bin/lame";
our @flacargs = qw ( --decode --stdout );
our @lameargs = qw ( --preset standard --replaygain-accurate );

# Flac: ALBUM ARTIST TITLE DATE GENRE TRACKNUMBER COMMENT
# ID3v2: ALBUM ARTIST TITLE YEAR GENRE TRACK COMMENT

# Tags used in conversion
our %flactags = ( 'ALBUM' => 'ALBUM',
'ARTIST' => 'ARTIST',
'TITLE' => 'TITLE',
'DATE' => 'DATE',
'GENRE' => 'GENRE',
'TRACKNUMBER' => 'TRACKNUMBER',
'COMMENT' => 'COMMENT', );

our %lametags = ( 'ALBUM' => '--tl',
'ARTIST' => '--ta',
'TITLE' => '--tt',
'DATE' => '--ty',
'GENRE' => '--tg',
'TRACKNUMBER' => '--tn',
'COMMENT' => '--tc', );

use vars qw ( $d_info
$d_debug
);

GetOptions (
"d_info!"=>\$d_info,
"d_debug!"=>\$d_debug
);

showusage() unless (scalar @ARGV == 2);

my ($srcdirroot, $destdirroot) = @ARGV;

mkdir($destdirroot, 0777) or die "Can't create directory $destdirroot\n"
unless -d $destdirroot;

die "Source directory not found: $srcdirroot\n"
unless -d $srcdirroot;

find_files($srcdirroot, $destdirroot, $srcdirroot);

1;
## End of main program

sub find_files {
my ($srcdirroot, $destdirroot, $srcdir) = @_;

$::d_info && msg("Processing directory: $srcdir\n");

# remove the src root directory from the beginning
# of the current directory to give the additional
# path information to be added to the destination root.
# Try to create the new destination directory.
(my $extra_path = $srcdir) =~ s/^$srcdirroot//;
my $destdir = $destdirroot . $extra_path;
mkdir($destdir, 0777) or die "Can't create directory $destdir\n"
unless -d $destdir;

# get all directory entries
opendir(SRCDIR, $srcdir) or die "Couldn't open directory $srcdir\n";
my @direntries = readdir(SRCDIR) or
die "Couldn't read directory entries for directory $srcdir\n";
closedir(SRCDIR);

# get all target files within the present directory
my @target_files = map { $_->[1] } # extract pathnames
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { /\.flac$/ } # just flac files
@direntries;

# get all subdirs of the present directory
my @subdirs = map { $_->[1] } # extract pathnames
grep { -d $_->[1] } # only directories
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { !/^\.\.?$/ } # not . or ..
@direntries;

# process all files found in this directory
foreach my $srcfile (@target_files) {
my ($fbase, $fdir, $fext) = fileparse( $srcfile, '\..*$' );
my $destfile = $destdir . "/" . $fbase . ".mp3";
convert_file($srcfile, $destfile);
}

# process any subdirs of present directory
foreach my $srcsubdir (@subdirs) {
&find_files($srcdirroot, $destdirroot, $srcsubdir);
}
}

sub showusage {
print "Usage goes here\n";
exit 1;
}

sub msg {
my $msg = shift;
print "$msg";
}

sub convert_file {
my ($srcfile, $destfile) = @_;

$::d_info && msg("Converting $srcfile to $destfile\n");

# To do:
# Check for existence of target file before converting
# Use some sort of logic to only upgrade if source is newer
# or the tags have changed
# Use command-line switches to override default behaviour

# Get all flac tags
my $flac = Audio::FLAC::Header->new($srcfile);
my $tags = $flac->tags();

# Start building command used to convert file
my $convert_command = "$flaccmd @flacargs '$srcfile' | $lamecmd @lameargs ";

# Look for all the tags in whcih we are interested
for my $tag (keys(%flactags)) {
# Check the tag exists
if ($tags->{$flactags{$tag}}) {
# Add a switch to lame to add the tag
$convert_command .= " $lametags{$tag} '$tags->{$flactags{$tag}}'";
}
}

# add the last few bits of the conversion command
$convert_command .= " - '$destfile'";

$::d_debug && msg("$convert_command\n");

# Convert the file
system ($convert_command);

# Should check the return value of the system command

# should optionally reset the destfile timestamp to the same as the srcfile
}
# vim:set tabstop=3:

Robin Bowes
2005-02-15, 19:39
Robin Bowes wrote:
>
> Fixed version attached.
>

v0.1.1 didn't completely fix the problems.

Attached is v0.1.2 which I *think* should solve all
special-characters-in-filename problems.

R.
--
http://robinbowes.com

#!/usr/bin/perl -w
#
# flac2mp3.pl
#
# Version 0.1.2
#
# Converts a directory full of flac files into a corresponding directory of mp3 files
#
# Usage:
#
# flac2mp3.pl [options] srcdir destdir
#
# Robin Bowes <robin (AT) robinbowes (DOT) com>
#
# Revision History:
#
# v0.1.2 Fixed filename quoting
# v0.1.1 Changes to filename quoting
# v0.1 Initial version

use strict;
use Getopt::Long;
use File::Basename;
use Audio::FLAC::Header;

our $flaccmd = "/usr/bin/flac";
our $lamecmd = "/usr/bin/lame";
our @flacargs = qw ( --decode --stdout );
our @lameargs = qw ( --preset standard --replaygain-accurate );

# Flac: ALBUM ARTIST TITLE DATE GENRE TRACKNUMBER COMMENT
# ID3v2: ALBUM ARTIST TITLE YEAR GENRE TRACK COMMENT

# Tags used in conversion
our %flactags = ( 'ALBUM' => 'ALBUM',
'ARTIST' => 'ARTIST',
'TITLE' => 'TITLE',
'DATE' => 'DATE',
'GENRE' => 'GENRE',
'TRACKNUMBER' => 'TRACKNUMBER',
'COMMENT' => 'COMMENT', );

our %lametags = ( 'ALBUM' => '--tl',
'ARTIST' => '--ta',
'TITLE' => '--tt',
'DATE' => '--ty',
'GENRE' => '--tg',
'TRACKNUMBER' => '--tn',
'COMMENT' => '--tc', );

use vars qw ( $d_info
$d_debug
);

GetOptions (
"d_info!"=>\$d_info,
"d_debug!"=>\$d_debug
);

showusage() unless (scalar @ARGV == 2);

my ($srcdirroot, $destdirroot) = @ARGV;

mkdir($destdirroot, 0777) or die "Can't create directory $destdirroot\n"
unless -d $destdirroot;

die "Source directory not found: $srcdirroot\n"
unless -d $srcdirroot;

find_files($srcdirroot, $destdirroot, $srcdirroot);

1;
## End of main program

sub find_files {
my ($srcdirroot, $destdirroot, $srcdir) = @_;

$::d_info && msg("Processing directory: $srcdir\n");

# remove the src root directory from the beginning
# of the current directory to give the additional
# path information to be added to the destination root.
# Try to create the new destination directory.
(my $extra_path = $srcdir) =~ s/^$srcdirroot//;
my $destdir = $destdirroot . $extra_path;
mkdir($destdir, 0777) or die "Can't create directory $destdir\n"
unless -d $destdir;

# get all directory entries
opendir(SRCDIR, $srcdir) or die "Couldn't open directory $srcdir\n";
my @direntries = readdir(SRCDIR) or
die "Couldn't read directory entries for directory $srcdir\n";
closedir(SRCDIR);

# get all target files within the present directory
my @target_files = map { $_->[1] } # extract pathnames
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { /\.flac$/ } # just flac files
@direntries;

# get all subdirs of the present directory
my @subdirs = map { $_->[1] } # extract pathnames
grep { -d $_->[1] } # only directories
map { [ $_, "$srcdir/$_" ] } # form (name, path)
grep { !/^\.\.?$/ } # not . or ..
@direntries;

# process all files found in this directory
foreach my $srcfile (@target_files) {
my ($fbase, $fdir, $fext) = fileparse( $srcfile, '\..*$' );
my $destfile = $destdir . "/" . $fbase . ".mp3";
convert_file($srcfile, $destfile);
}

# process any subdirs of present directory
foreach my $srcsubdir (@subdirs) {
&find_files($srcdirroot, $destdirroot, $srcsubdir);
}
}

sub showusage {
print "Usage goes here\n";
exit 1;
}

sub msg {
my $msg = shift;
print "$msg";
}

sub convert_file {
my ($srcfile, $destfile) = @_;

$::d_info && msg("Converting $srcfile to $destfile\n");

# To do:
# Check for existence of target file before converting
# Use some sort of logic to only upgrade if source is newer
# or the tags have changed
# Use command-line switches to override default behaviour

# Get all flac tags
my $flac = Audio::FLAC::Header->new($srcfile);
my $tags = $flac->tags();

# Start building command used to convert file
my $convert_command = "$flaccmd @flacargs \"$srcfile\" | $lamecmd @lameargs ";

# Look for all the tags in which we are interested
for my $tag (keys(%flactags)) {
# Check the tag exists
if ($tags->{$flactags{$tag}}) {
# Add a switch to lame to add the tag
$convert_command .= " $lametags{$tag} \"$tags->{$flactags{$tag}}\"";
}
}

# add the last few bits of the conversion command
$convert_command .= " - \"$destfile\"";

$::d_debug && msg("$convert_command\n");

# Convert the file
system ($convert_command);

# Should check the return value of the system command

# should optionally reset the destfile timestamp to the same as the srcfile
}
# vim:set tabstop=3:

Robin Bowes
2005-02-16, 20:00
I am pleased to announce v0.1.3 of my flac2mp3.pl script.

Download it here: http://robinbowes.com/filemgmt/visit.php?lid=3

The addition of a check for the existence of the mp3 file means you can
kill the script and re-start it and it will not try to process files
that have already been done.

This version appears to be a reasonably robust - it's chugging away in
the background as a type.

Changelog:

v0.1.3
- added --quiet option to flac and lame commands
- only run conversion if dest file doesn't exist or if src file is newer
then dest file
- set modification time of dest file to same as src file
- check exit value of conversion command
- fixed problem with certain characters in file/directory name
- added rudimentary SIGINT handling
v0.1.2
- Fixed filename quoting
v0.1.1
- Changes to filename quoting
v0.1.0
- Initial version

I welcome feedback/bug reports/patches :)

R.

--
http://robinbowes.com

Robin Bowes
2005-02-17, 16:31
Hi,

I am pleased to announce v0.1.4 of my flac2mp3.pl script.

Download it here: http://robinbowes.com/filemgmt/visit.php?lid=4

Changelog:

# v0.1.4
# - Fix for files with multiple periods in filename, e.g. "01 - Back In
# The U.S.S.R..flac" would be converted as "Back In The U.mp3"
# - Fix for timestamp comparison (got it the right way round this
# time!)
# v0.1.3
# - added --quiet option to flac and lame commands
# - only run conversion if dest file doesn't exist or if src file is
# newer than dest file
# - set modification time of dest file to same as src file
# - check exit value of conversion command
# - fixed problem with certain characters in file/directory name
# - added rudimentary SIGINT handling
# v0.1.2
# - Fixed filename quoting
# v0.1.1
# - Changes to filename quoting
# v0.1.0
# - Initial version

I see I've had a few downloads; I'd be interested to hear how it's
working for those who've tried it. Are more instructions needed or is it
fairly self-explanatory?

R.
--
http://robinbowes.com