#!/usr/bin/perl -w # # ogg2mp3, MNG 2006 # License: GNU GPL. # # See documentatiom in POD format at end of this file. # use constant PROG_DESCRIPTION => "OGG to MP3 converter"; use constant PROG_COPYRIGHT => "Distributed under the terms of the GNU GPL version 2"; use constant PROG_VERSION => "0.01"; use constant PROG_AUTHOR => "Matthew Gates"; use strict; use Getopt::Long; use File::Basename; my $gs_debug = 0; my $gs_quiet = 0; my $gs_thisscript = basename($0); my $gs_output_directory = "."; my $gs_overwrite = 0; my $gs_prefix = ""; my $gs_suffix = ""; my $gs_id3tags = 1; my $gs_verbose = 0; GetOptions( 'debug=i' => \$gs_debug, 'id3-tags!' => \$gs_id3tags, 'force-overwrite' => \$gs_overwrite, 'help' => sub { system("pod2usage -verbose 1 $0"); exit 0; }, 'output-directory=s' => \$gs_output_directory, 'prefix=s' => \$gs_prefix, 'quiet' => \$gs_quiet, 'suffix=s' => \$gs_suffix, 'verbose' => \$gs_verbose, 'version' => sub { print &PROG_DESCRIPTION . " by " . &PROG_AUTHOR . "\n" . "version " . &PROG_VERSION . "\n" . &PROG_COPYRIGHT . "\n"; }, ) or usage(1); # Check we can find all required dependencies prereqtest(); # some script scopers my $filenum = 0; my $numlen = length(($#ARGV + 1)); foreach my $oggfile (@ARGV) { $filenum++; my $mp3file = basename($oggfile); $mp3file =~ s/\.ogg$//i; $mp3file = $gs_output_directory . "/" . $gs_prefix . $mp3file . $gs_suffix . ".mp3"; # get rid of multiple /// in path (usually when someone put a trailing # / on the --output-directory argument, e.g. /tmp/ $mp3file =~ s|/+|/|g; if ( -e $mp3file && ! $gs_overwrite ) { db_out(0, "WARNING: file $mp3file already exists, SKIPPING"); next; } my %tags; my $lame_tag_opts = ""; if ( $gs_id3tags ) { %tags = get_ogg_tags($oggfile); $lame_tag_opts = get_lame_tagopt(%tags); } if ( ! $gs_quiet ) { printf("processing %${numlen}d / %${numlen}d : %s -> %s\n", $filenum, $#ARGV + 1, $oggfile, $mp3file); if ( $gs_verbose ) { if ( ! $gs_id3tags ) { print "ID3 tagging disabled with command line option\n"; } else { print "ID3 Tags:\n"; foreach my $tag (sort keys %tags) { printf(" %-12s => %s\n", $tag, $tags{$tag}); } print "\n"; } } } my $cmd = "oggdec -Q -o - '$oggfile' | lame --quiet $lame_tag_opts - - > '$mp3file'"; db_out(2, "command is: $cmd\n"); system($cmd); } exit 0; #################### ### Sub-routines ### #################### sub get_ogg_tags { my $oggfile = shift; my %tags = (); if ( ! open(INFO, "ogginfo \"$oggfile\"|") ) { db_out(0, "WARNING: could not get tags for $oggfile"); return; } my $in_tag_section = 0; while () { chomp; if ( /^User comments section follows/ ) { $in_tag_section = 1; db_out(5, "Found tag section in vorbis headers"); next; } if ( $in_tag_section ) { s/\s+$//; if ( /^\s*(\w+)=(.*)/ ) { db_out(6, "Found tag: $1 with value: \"$2\""); $tags{lc($1)} = $2; } else { db_out(6, "End of tag section"); last; } } } return %tags; } sub get_lame_tagopt { my %tags = @_; # lame tag syntax is: # lame --ignore-tag-errors \ # --tt %{title} \ # --ta %{artist} \ # --tl %{albumtitle} \ # --ty %{year} \ # --tn %{number} \ # --tg %{genre} my %lametags = ( "title" => "--tt", "artist" => "--ta", "album" => "--tl", "albumtitle" => "--tl", "year" => "--ty", "date" => "--ty", "number" => "--tn", "track" => "--tn", "tracknumber"=> "--tn", "genre" => "--tg", ); my $lameopts = ""; # Verify that ' characters in the %tags values are properly escaped to # prevent the command from having un-balanced quotes later on. foreach my $k (%tags) { # This is A zero-width negative look-behind assertion see perlre # manpage for details. Basically it means, "replace ' with \' unless # it already has a \ in front of the '". if ( defined($tags{$k}) ) { $tags{$k} =~ s/\'/\'\"\'\"\'/g; } } foreach my $tagname (keys %lametags) { db_out(7, "checking for tag $tagname"); if ( defined($tags{$tagname}) ) { if ( $tags{$tagname} ne "" ) { $lameopts .= $lametags{$tagname}; $lameopts .= " '" . $tags{$tagname} . "' "; db_out(7, "found tag $tagname: $tags{$tagname}, opts now: $lameopts"); } } } if ( $lameopts ne "" ) { $lameopts =~ s/\s+$//; $lameopts = "--ignore-tag-errors " . $lameopts; } db_out(4, "lameopts: $lameopts"); return($lameopts); } sub prereqtest { my @dependencies = ("oggdec", "lame", "pod2usage", "ogginfo"); my @missing_deps = (); foreach my $dep (@dependencies) { db_out(2, "checking for dependency: $dep:"); my $found = 0; foreach my $d (split(":", $ENV{PATH})) { if ( -x "$d/$dep" ) { db_out(2, "found: $d/$dep"); $found = 1; last; } } if ( $found == 0 ) { db_out (-1,"ERROR: $dep not found - please install it\n"); exit 1; } } } sub usage { my $lev = shift || 0; system("pod2usage -verbose 1 $0"); exit($lev); } sub db_out { my $lev = shift; return if ( $gs_debug < $lev ); my $message = shift; print STDERR "$gs_thisscript: $message\n"; } exit 0 __END__ =head1 NAME ogg2mp3 - batch convert ogg vorbis files to mp3 format =head1 SYNOPSIS ogg2mp3 [options] file.ogg [file2.ogg] =head1 DESCRIPTION ogg2mp3 takes a list of OGG Vorbis formatted files and converts them to mp3 format. This is useful when you want to transfer OGG encoded files to a device which doesn't support the format (e.g. an iPod). Each file specified on the command line is converted to a file with the same name except the ".ogg" at the end is replaced with ".mp3". The new file is created in the current working directory unless otherwise specified with the B<--output-directory> option. By default, ogg2mp3 prints one line of output per file processed (printing it as the processing of that file starts) as follows: processing 1 / 10 : 01_big_exit.ogg -> ./01_big_exit.mp3 Where 01_big_exit.ogg is the first of ten files to be processed (1 / 10), and the output file is called ./01_big_exit.mp3. This behaviour can be modified using the B<--quiet> and B<--verbose> options. =head1 OPTIONS Note that all command line options may be abbreviated to the shortest unique version, e.g. B<--output-directory> can be shortened to B<-o> because there are no other options beginning with B<-o>. However, to see the version, at least B<--vers> is required since there is also the B<--verbose> option, thus "vers" is the shortest unique abbreviation of "version". =over =item B<--debug> I Set the debugging level to I. Ranges from 0 to 10, 10 being really verbose, 0 being just warnings (the default). =item B<--id3-tags> or B<--noid3-tags> Specify that you want (or not) ID3 tags to be copied into the MP3 file. =item B<--force-overwrite> If an output .mp3 file already exists, overewrite it without prompting (the default is to skip such files and issue a warning). =item B<--help> Show a brief command line help message and exit. =item B<--output-directory> I Tell ogg2mp3 to create the .mp3 files in the directorry I instead of the current directory. =item B<--prefix> I Prefix all output filenames with I. =item B<--quiet> Don't print output unless there is an error. =item B<--suffix> I Append I to all output filenames just before the .mp3 extension. =item B<--verbose> If B<--quiet> is not spcified, this switch increases the amount of information displayed as each file is processed. The extra information details the ID3 tags found in the source file. =item B<--version> Print the program name and version and exit. =back =head1 EXAMPLES =over =item Example 1 $ ogg2mp3 echo.ogg got_two_legs.ogg pripple_ipple.ogg processing 1 / 3 : echo.ogg -> ./echo.mp3 processing 2 / 3 : got_two_legs.ogg -> ./got_two_legs.mp3 processing 3 / 3 : pripple_ipple.ogg -> ./pripple_ipple.mp3 This command will create three new files: F, F, and F in mp3 format in the current directory. =item Example 2 $ ogg2mp3 -o /media/ipod my_song.ogg processing 1 / 1 : my_song.ogg -> /media/ipod/my_song.mp3 This command creates the file F in the directory F. =item Example 3 $ ls *.ogg track_01.ogg track_02.ogg track_03.ogg $ ogg2mp3 -o /tmp --verbose *.ogg processing 1 / 3 : track_01.ogg -> /tmp/track_01.mp3 ID3 Tags: album => Frengers artist => Mew date => 2003 genre => Pop title => Am I Wry? No tracknumber => 1 processing 2 / 3 : track_02.ogg -> /tmp/track_02.mp3 ID3 Tags: album => Frengers artist => Mew date => 2003 genre => Pop title => 156 tracknumber => 2 processing 3 / 3 : track_03.ogg -> /tmp/track_03.mp3 ID3 Tags: album => Frengers artist => Mew date => 2003 genre => Pop title => Snow Brigade tracknumber => 3 As you can see, adding the B<--verbose> option turns on display of ID3 tags. =back =head1 DEPENDENCIES ogg2mp3 is a simple perl program which acts as a wrapper for other programs which do all the heavy work. The following tooks must be installed on your system before youo can use ogg2mp3: =over =item lame (Ubuntu package: lame) =item oggdec, ogginfo (Ubuntu package: vorbis-tools) =item pod2usage, perl itself (Ubuntu package: perl) =back =head1 LICENSE ogg2mp3 is released under the GNU GPL (version 2, June 1991). A copy of the license should have been provided in the distribution of the software in a file called "LICENSE". If you can't find this, then try here: http://www.gnu.org/copyleft/gpl.html =head1 AUTHOR Matthew Gates http://porpoisehead.net/mysw/ =head1 CHANGELOG =over =item Date:2006-08-31 Created, Author MNG Fleshed out from a really tiny script, added docs. =back =head1 BUGS Filenames with embedded ' characters are going to fail. Failures in oggdec or lame will not result in a non-zero error level, just error messages. =head1 SEE ALSO oggdec(1), lame(1), ogginfo(1) =cut