X-Git-Url: https://diplodocus.org/git/flac-archive/blobdiff_plain/fe8996f09b7acb90ff10342f05deeb6c038af045..cd6b00d1cf4de2e18326ceb94a32366dbd4cd228:/flac2mp3 diff --git a/flac2mp3 b/flac2mp3 index 842f0cd..c8e9d11 100755 --- a/flac2mp3 +++ b/flac2mp3 @@ -1,6 +1,4 @@ -#! /usr/bin/env perl - -# $Id$ +#!/usr/local/bin/perl =head1 NAME @@ -8,7 +6,7 @@ B - transcode FLAC file to MP3 files =head1 SYNOPSIS -B [B<--lame-options> I] [B<-j> I] [B<-q>] [B<-v>] I [...] +B [B<--lame-options> I] [B<-q>] [B<-v>] I [...] =head1 DESCRIPTION @@ -17,343 +15,9 @@ may be the kind of FLAC file B generates. That is, it contains a cue sheet, one TITLE tag per track listed therein, and ARTIST, ALBUM, and DATE tags. -=cut - -package Jobs; - -use strict; -use warnings; - -use Errno; -use POSIX ':sys_wait_h'; - -sub newjob { - my $f = shift; - my $jobs = shift; - my $debug = shift; - my $pid; - - if (not $debug) { - $pid = fork(); - if (not defined($pid)) { - die("fork: $!"); - } - } - - if ($debug or $pid == 0) { - exit($f->()); - } - - if ($pid == 0) { - exit($f->()); - } - - push(@$jobs, $pid); - - return $pid; -} - -sub deljob { - my $pid = shift; - my $status = shift; - my $jobs = shift; - - for (my $i = 0; $i <= $#$jobs; $i++) { - if ($pid == $jobs->[$i]) { - splice(@$jobs, $i, 1); - last; - } - } - - return ($pid, $status); -} - -sub run { - my %o = @_; - my $maxjobs = $o{'max-jobs'}; - my $get_job = $o{'get-job'}; - my $notify_start = $o{'notify-start'}; - my $notify_finish = $o{'notify-finish'}; - my @jobs; - my $pid; - - # Call notifier function if given. - sub call { - my $f = shift or return; - ref($f) eq 'CODE' or return; - $f->(@_); - } - - while (1) { - if (@jobs < $maxjobs) { - my $job; - while (defined($job = $get_job->())) { - $pid = newjob($job, \@jobs, $o{'debug'}); - call($notify_start, $pid, @jobs); - @jobs < $maxjobs or last; - } - - # No jobs running and get-job returned undef; we're finished. - if (@jobs == 0 and not defined($job)) { - return; - } - } - - # Now running as many jobs as we can, block waiting for one to die. - do { - $pid = waitpid(-1, 0); - } while ($pid == 0 - or ($pid == -1 and ($!{ECHILD} or $!{EINTR}))); - $pid == -1 and die("waitpid(-1): $!"); - - # Before starting more, see if any others have finished. - do { - call($notify_finish, deljob($pid, $?, \@jobs), @jobs); - } while (($pid = waitpid(-1, WNOHANG)) > 0); - if ($pid == -1) { - $!{ECHILD} or $!{EINTR} or die("waitpid(-1): $!"); - } - } -} - - -################################################################################ -package main; - -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 { - return sprintf('%02d:%02d.%02d', @_); -} - -sub get_decode_args { - my $fn = shift; - my @l; - - open(F, '-|', 'metaflac', '--export-cuesheet-to=-', $fn); - while () { - /INDEX 01 (\d\d):(\d\d):(\d\d)$/ or next; - push(@l, [$1, $2, $3]); - } - - my @args; - for my $i (0..$#l) { - my $arg = ["--skip=" . tformat(@{$l[$i]})]; - my $next = $l[$i+1]; - if (defined($next)) { - if ($next->[2] == 0) { - if ($next->[1] == 0) { - push(@$arg, '--until=' . tformat($next->[0] - 1, 59, 74)); - } else { - push(@$arg, '--until=' . tformat($next->[0], $next->[1] - 1, - 74)); - } - } else { - push(@$arg, '--until=' . tformat($next->[0], $next->[1], - $next->[2] - 1)); - } - } - push(@args, $arg); - } - - # If no cue sheet, stick a dummy in here. - if (@args == 0) { - @args = ([]); - } - - return @args; -} - -# Return the ARTIST, ALBUM, and DATE tags followed by the TITLE tags -# 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 $discnum; - my $track; - - open(TAGS, '-|', 'metaflac', '--export-vc-to=-', $fn) - or die("open(metaflac --export-vc-to=- $fn): $!"); - while () { - chomp; - - ($tag, $value) = split(/=/, $_, 2); - - if (/^ARTIST=/i) { - $artist = $value; - } elsif (/^ALBUM=/i) { - $album = $value; - } elsif (/^DATE=/i) { - $date = $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(metaflac --export-vc-to=- $fn): $?"); - - # If no TITLEs, stick a dummy in here. - if (@$titles == 0) { - push(@$titles, undef); - } - - 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 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 = ''; - } - - 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; - - arg('--tt', \$title); - arg('--ta', \$artist); - arg('--tl', \$album); - arg('--ty', \$date); - arg('--tn', \$track); - - $skip_arg ||= ''; - $until_arg ||= ''; - run_or_die(join(' ', "flac $flac_options -cd $skip_arg $until_arg '$fn'", - " | lame $lame_options $title $artist $album $date $track", - " - '$outfile'")); -} - -MAIN: { - my $help; - my $debug; - my $maxjobs = 1; - GetOptions( - 'debug|X' => \$debug, - 'jobs|j=i' => \$maxjobs, - 'lame-options=s', \$lame_options, - 'quiet|q' => \$quiet, - 'verbose|v' => \$verbose, - 'help|h|?' => \$help, - ) or pod2usage(); - $help and pod2usage(-exitstatus=>0, -verbose=>1); - - @ARGV > 0 or pod2usage(); - - my @jobs; - 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) { - push(@jobs, [$fn, $titles[$i], ($artists[$i] or $artist), $album, - $date, $track, @{$args[$i]}]); - $track = $i + 2; - } - } - - Jobs::run('max-jobs'=>$maxjobs, - 'debug'=>$debug, - 'get-job'=>sub { - my $job = shift(@jobs) or return; - return sub { flac2mp3(@$job) } - }); -} - - -__END__ +Note that lame is retarded, and parses B directly itself! So, in order +for it to transcode textual tags, you must specify the encoding in LANG, e.g. +LANG=en_US.utf-8 =head1 OPTIONS @@ -365,10 +29,6 @@ Pass I to B. This ends up being passed to the shell, so feel free to take advantage of that. You'll almost certainly have to put I in single quotes. -=item B<-j> [B<--jobs>] I - -Run up to I jobs instead of the default 1. - =item B<-q> [B<--quiet>] Suppress status information. This option is passed along to B @@ -387,10 +47,92 @@ Written by Eric Gillespie . =cut -# Local variables: -# cperl-indent-level: 4 -# perl-indent-level: 4 -# indent-tabs-mode: nil -# End: +package epg::flac::archive::mp3; + +use v5.12; +use warnings; + +use File::Temp; +use FindBin; + +require "$FindBin::Bin/tags.pl"; +epg::flac::archive::tags->import( + qw[ + track_tags + read_tags_metaflac + mangle_for_file_name + quote + two_digits + ] +); + +sub flac2mp3 { + my $mp3 = shift; + my $flac = shift; + my $tags = shift; + + # This is an old TODO; what's wrong with --ty ? + # TODO: Look at TDOR, TDRL, TDRC for date. + say( + join( + ' ', + 'flac', + '-cd', + quote($flac), + '|', + 'lame', + '--id3v2-only', + '--id3v2-latin1', + '--pad-id3v2-size', 0, + '--preset standard', + '--ta', + quote($tags->{artist}), + '--tl', + quote($tags->{album}), + '--tn', + quote($tags->{tracknumber}), + '--tt', + quote($tags->{title}), + '--ty', + quote($tags->{date}), + '$pic_options', + + #(map { ('--tv', quote("TPE2=$_")) } @{$albumartist}), + (map { ('--tv', quote("TPOS=$_")) } @{$tags->{discnumber}}), + '-', + quote($mp3), + ) + ); +} + +sub main { + for my $flac (@_) { + say('metaflac --export-picture-to=flac2mp3.cover.$$ ', + quote($flac), ' && pic_options="--ti flac2mp3.cover.$$"'); + my %tags = track_tags(read_tags_metaflac($flac)); + flac2mp3( + mangle_for_file_name( + join(' ', + $tags{artist}, + $tags{album}, + (map { two_digits($_) } @{$tags{discnumber}}), + two_digits($tags{tracknumber}), + $tags{title}, + ) + ) + . '.mp3', + $flac, + \%tags, + ); + say('unset pic_options'); + } + say('rm -f flac2mp3.cover.$$'); + + return 0; +} + +if (!caller) { + exit(main(@ARGV)); +} -# vi: set tabstop=4 expandtab: +1;