]> diplodocus.org Git - flac-archive/blobdiff - flac2mp3
Oops, support for untagged files was not complete. Fix.
[flac-archive] / flac2mp3
index 2231b1f8d23a58b98267e963a6c6647ba3b6824e..21cf9c55e2af993ec363bed90f286a5b2e267532 100755 (executable)
--- a/flac2mp3
+++ b/flac2mp3
@@ -8,27 +8,51 @@ B<flac2mp3> - transcode FLAC file to MP3 files
 
 =head1 SYNOPSIS
 
-B<flac2mp3> I<file>
+B<flac2mp3> [B<--lame-options> I<lame-options>] [B<-q>] [B<-v>] I<file> [...]
 
 =head1 DESCRIPTION
 
-B<flac2mp3> transcodes the FLAC file I<file> to MP3 files.  I<file> is
-the kind of FLAC file B<fa-flacd> generates.  That is, it contains a
-cue sheet, one TITLE tag per track listed therein, and ARTIST, ALBUM,
-and DATE tags.
+B<flac2mp3> transcodes the FLAC files I<file> to MP3 files.  I<file>
+may be the kind of FLAC file B<fa-flacd> generates.  That is, it
+contains a cue sheet, one TITLE tag per track listed therein, and
+ARTIST, ALBUM, and DATE tags.
 
 =cut
 
 use strict;
 use warnings;
 
+use POSIX ':sys_wait_h';
 use Pod::Usage;
+use Getopt::Long qw(:config gnu_getopt no_ignore_case);
+
+my $flac_options;
+my $lame_options;
+my $quiet;
+my $verbose;
+
+sub run_or_die {
+    my $command = shift;
+    my $status;
+
+    $verbose and print(STDERR "$command\n");
+    $status = system($command);
+
+    if (WIFEXITED($status)) {
+        if (($status = WEXITSTATUS($status)) != 0) {
+            die("$command exited with status $status");
+        }
+    } elsif (WIFSIGNALED($status)) {
+        die("$command killed with signal ", WTERMSIG($status));
+    } elsif (WIFSTOPPED($status)) {
+        die("$command stopped with signal ", WSTOPSIG($status));
+    } else {
+        die("Major horkage on system($command): \$? = $? \$! = $!");
+    }
+}
 
 sub tformat {
-    my $min = shift;
-    my $sec = shift;
-    my $hun = shift;
-    return "$min:$sec.$hun";
+    return sprintf('%02d:%02d.%02d', @_);
 }
 
 sub get_decode_args {
@@ -48,10 +72,10 @@ sub get_decode_args {
         if (defined($next)) {
             if ($next->[2] == 0) {
                 if ($next->[1] == 0) {
-                    push(@$arg, '--until=' . tformat($next->[0] - 1, 59, 99));
+                    push(@$arg, '--until=' . tformat($next->[0] - 1, 59, 74));
                 } else {
                     push(@$arg, '--until=' . tformat($next->[0], $next->[1] - 1,
-                                                    99));
+                                                    74));
                 }
             } else {
                 push(@$arg, '--until=' . tformat($next->[0], $next->[1],
@@ -61,6 +85,11 @@ sub get_decode_args {
         push(@args, $arg);
     }
 
+    # If no cue sheet, stick a dummy in here.
+    if (@args == 0) {
+        @args = ([]);
+    }
+
     return @args;
 }
 
@@ -68,69 +97,172 @@ sub get_decode_args {
 # in the file FN.
 sub get_tags {
     my $fn = shift;
+    my $artists = shift;
+    my $titles = shift;
     my $tag;
     my $value;
     my $artist;
     my $album;
     my $date;
-    my @titles;
+    my $discnum;
+    my $track;
 
-    open(TAGS, '-|', 'metaflac', '--export-vc-to=-', $fn);
+    open(TAGS, '-|', 'metaflac', '--export-vc-to=-', $fn)
+      or die("open(metaflac --export-vc-to=- $fn): $!");
     while (<TAGS>) {
         chomp;
 
         ($tag, $value) = split(/=/, $_, 2);
 
-        if (/^ARTIST=/) {
+        if (/^ARTIST=/i) {
             $artist = $value;
-        } elsif (/^ALBUM=/) {
+        } elsif (/^ALBUM=/i) {
             $album = $value;
-        } elsif (/^DATE=/) {
+        } elsif (/^DATE=/i) {
             $date = $value;
-        } elsif (/TITLE=/) {
-            push(@titles, $value);
+        } elsif (/^DISCNUMBER=/i) {
+            $discnum = int($value);
+        } elsif (/^ARTIST\[/i) {
+            push(@$artists, $value);
+        } elsif (/^TRACKNUMBER=/i) {
+            $track = $value;
+
+        # Intentionally don't match the = on this one, to support the
+        # TITLE[1] .. TITLE[n] tag style.
+        } elsif (/^TITLE/i) {
+            push(@$titles, $value);
         }
     }
-    close(TAGS) or die("close($fn): $!");
+    close(TAGS) or die("close(metaflac --export-vc-to=- $fn): $?");
+
+    # If no TITLEs, stick a dummy in here.
+    if (@$titles == 0) {
+        push(@$titles, undef);
+    }
 
-    return ($artist, $album, $date, @titles);
+    return ($artist, $album, $date, $discnum, $track);
+}
+
+sub arg {
+    my $arg = shift;
+    my $var = shift;
+
+    if (defined($$var)) {
+        $$var = "$arg '$$var'";
+    } else {
+        $$var = ''
+    }
 }
 
 sub flac2mp3 {
     my $fn = shift;
-    my $title = shift;
-    my $artist = shift;
-    my $album = shift;
-    my $date = shift;
-    my $track = shift;
+    my $title = (shift or 'unknown');
+    my $artist = (shift or 'unknown');
+    my $album = (shift or 'unknown');
+    my $date = (shift or 'unknown');
+    my $track = int(shift);
     my $skip_arg = shift;
     my $until_arg = shift;
+    my @tmp;
+    my $outfile;
+
+    if ($quiet) {
+        $flac_options = '--silent';
+    } else {
+        $flac_options = '';
+    }
 
-    my $outfile = sprintf("$artist ($album) \%02s $title.mp3", $track);
+    if ($lame_options) {
+        push(@tmp, $lame_options);
+    } else {
+        push(@tmp, '--preset standard');
+    }
+    $quiet and push(@tmp, '--quiet');
+    $verbose and push(@tmp, '--verbose');
+    $lame_options = join(' ', @tmp);
+
+    # We'll be putting these in single quotes, so we need to escape
+    # any single quotes in the filename by closing the quote ('),
+    # putting an escaped quote (\'), and then reopening the quote (').
+    for ($fn, $title, $artist, $album, $date) {
+        defined and s/'/'\\''/g;
+    }
+
+    $outfile = sprintf("$artist ($album) \%02s $title.mp3", $track);
     $outfile =~ s/\//_/g;
-    $outfile =~ s/:/_/g;
-    $outfile =~ s/'/_/g;
-    $outfile =~ s/"/_/g;
 
-    # XXX
+    arg('--tt', \$title);
+    arg('--ta', \$artist);
+    arg('--tl', \$album);
+    arg('--ty', \$date);
+    arg('--tn', \$track);
+
+    $skip_arg ||= '';
     $until_arg ||= '';
-    system("flac -cd $skip_arg $until_arg '$fn' | lame --preset standard --tt '$title' --ta '$artist' --tl '$album' --ty '$date' --tn $track - '$outfile'");
+    run_or_die(join(' ', "flac $flac_options -cd $skip_arg $until_arg '$fn'",
+                    " | lame $lame_options $title $artist $album $date $track",
+                    " - '$outfile'"));
 }
 
 MAIN: {
-    my $fn = shift or pod2usage();
-    my @args = get_decode_args($fn);
-    my ($artist, $album, $date, @titles) = get_tags($fn);
+    my $help;
+    GetOptions(
+               'lame-options=s', \$lame_options,
+               'quiet|q' => \$quiet,
+               'verbose|v' => \$verbose,
+               'help|h|?' => \$help,
+              ) or pod2usage();
+    $help and pod2usage(-exitstatus=>0, -verbose=>1);
 
-    for my $i (0..$#titles) {
-        flac2mp3($fn, $titles[$i], $artist, $album, $date, $i + 1,
-                 @{$args[$i]});
+    @ARGV or pod2usage();
+    for my $fn (@ARGV) {
+        my @args = get_decode_args($fn);
+        my (@artists, @titles);
+        my ($artist, $album, $date, $discnum, $track) = get_tags($fn, \@artists,
+                                                                 \@titles);
+
+        # lame doesn't seem to support disc number.
+        defined($discnum) and $album .= " (disc $discnum)";
+
+        # Stupid hack: only a single-track file should have the
+        # TRACKNUMBER tag, so use it if set for the first pass through
+        # the loop.  At the end of the loop, we'll set $track for the
+        # next run, so this continues to work for multi-track files.
+        $track ||= 1;
+
+        for my $i (0..$#titles) {
+            flac2mp3($fn, $titles[$i], ($artists[$i] or $artist), $album, $date,
+                     $track, @{$args[$i]});
+            $track = $i + 2;
+        }
     }
 }
 
 \f
 __END__
 
+=head1 OPTIONS
+
+=over 4
+
+=item B<--lame-options> I<lame-options>
+
+Pass I<lame-options> to B<lame>.  This ends up being passed to the
+shell, so feel free to take advantage of that.  You'll almost
+certainly have to put I<lame-options> in single quotes.
+
+=item B<-q> [B<--quiet>]
+
+Suppress status information.  This option is passed along to B<flac>
+and B<lame>.
+
+=item B<-v> [B<--verbose>]
+
+Print diagnostic information.  This option is passed along to B<flac>
+and B<lame>.
+
+=back
+
 =head1 AUTHORS
 
 Written by Eric Gillespie <epg@pretzelnet.org>.