X-Git-Url: https://diplodocus.org/git/flac-archive/blobdiff_plain/d177db7bfada46340eadca2a3d66155ad7cd9b6a..ed3b7d734a1cbea566f2d5cb9c4a9b3938e5a029:/fa-flacd?ds=sidebyside diff --git a/fa-flacd b/fa-flacd index b10e0c9..09bfe4a 100755 --- a/fa-flacd +++ b/fa-flacd @@ -18,57 +18,96 @@ package Jobs; use strict; use warnings; +use Errno; use POSIX ':sys_wait_h'; -our @jobs; -our @finished; - -sub reaper { - while ((my $pid = waitpid(-1, WNOHANG)) > 0) { - push(@finished, [$pid, $?]); - } - - # XXX if $pid == -1 handle errors? - - $SIG{CHLD} = \&reaper; -} - sub newjob { my $f = shift; - my %o = @_; + my $jobs = shift; + my $debug = shift; my $pid; - $SIG{CHLD} = \&reaper; - if (not $o{'debug'}) { + if (not $debug) { $pid = fork(); if (not defined($pid)) { die("fork: $!"); } } - if ($o{'debug'} or $pid == 0) { - $SIG{CHLD} = 'DEFAULT'; + if ($debug or $pid == 0) { exit($f->()); } - push(@jobs, $pid); + if ($pid == 0) { + exit($f->()); + } + + push(@$jobs, $pid); return $pid; } sub deljob { - my $i = shift; - my $j; + 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; + } + } - my ($pid, $status) = @{$finished[$i]}; + 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; - for ($j = 0; $j <= $#jobs; $j++) { - $pid == $jobs[$j] and splice(@jobs, $j, 1) and last; + # Call notifier function if given. + sub call { + my $f = shift or return; + ref($f) eq 'CODE' or return; + $f->(@_); } - splice(@finished, $i, 1); + 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; + } - return ($pid, $status); + # 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): $!"); + } + } } @@ -170,35 +209,34 @@ sub run_flac { or die("rename($outfile.flac-tmp, $outfile.flac): $!"); } -# Process the fa-rip output in the directory DIR. +# Encode a single wav file to a single flac file, whether the wav and +# flac files represent individual tracks or whole discs. sub flac { my $dir = shift; - my $artist; - my $album; - my $discnum; - my @tags; - my $single_file = -e "$dir/wav"; + my $artist = shift; + my $album = shift; + my $discnum = shift; + my $tracknum = shift; + my $track_tags = shift; + my $disc_artist = shift; + my $single_file = not defined($tracknum); + my @tags = @_; my $outdir; my $outfile; my $outlog; my @files; - verbose("Renaming $dir/tags\n"); - rename("$dir/tags", "$dir/using-tags") - or die("rename($dir/tags, $dir/using-tags): $!"); - - ($artist, $album, $discnum, @tags) = get_tags("$dir/using-tags"); for ($artist, $album) { s|/|_|g; } verbose("mkdir($artist)\n"); - -d $artist or mkdir($artist) or die("mkdir($artist): $!"); + mkdir($artist) or $!{EEXIST} or die("mkdir($artist): $!"); if (not $single_file) { $outdir = "$artist/$album"; verbose("mkdir($outdir)\n"); - -d "$outdir" or mkdir("$outdir") or die("mkdir($outdir): $!"); + mkdir("$outdir") or $!{EEXIST} or die("mkdir($outdir): $!"); } verbose("chdir($dir)\n"); @@ -210,62 +248,45 @@ sub flac { run_flac('wav', 'cue', "../$artist/$outfile", @tags); $outlog = "../$artist/$outfile.log"; @files = ("$artist/$outfile.flac"); + + unlink('cue') or die("unlink(cue): $!"); + rename('log', $outlog) + or die("rename(log, $outlog): $!"); } else { - # Go over @tags, store all [n] tags in a list keyed by n in - # %tracks_to_tags, store all ARTIST (not ARTIST[n]) tags in - # @disc_artist, and leave the rest in @tags. - my %tracks_to_tags; - my @disc_artist; - my @tmp; - for my $tag (@tags) { - if ($tag =~ /^([^[]+)\[(\d+)]=(.*)/) { - push(@{$tracks_to_tags{$2}->{$1}}, $3); - } elsif ($tag =~ /^ARTIST=/) { - push(@disc_artist, $tag); - } else { - push(@tmp, $tag); - } + my $title = join(' ', map(split, @{$track_tags->{'TITLE'}})); + $title =~ s|/|_|g; + $outfile = join('/', + $outdir, + join(' ', + (defined($discnum) + ? sprintf('%02d', $discnum) + : ()), + sprintf('%02d', $tracknum), + $title)); + + # If we have ARTIST[n] tags for this track, set @track_artist + # to the empty list; they will go in along with the other [n] + # tags. + my @track_artist; + if (exists($track_tags->{'ARTIST'})) { + @track_artist = (); + } else { + @track_artist = @$disc_artist; } - @tags = @tmp; - - for my $tracknum (sort(map(int, keys(%tracks_to_tags)))) { - my $title = join(' ', map(split, @{$tracks_to_tags{$tracknum}->{'TITLE'}})); - $title =~ s|/|_|g; - $outfile = join('/', - $outdir, - join(' ', - (defined($discnum) - ? sprintf('%02d', $discnum) - : ()), - sprintf('%02d', $tracknum), - $title)); - - # If we have ARTIST[n] tags for this track, set - # @track_artist to the empty list; they will go in along - # with the other [n] tags. - my @track_artist; - if (exists($tracks_to_tags{$tracknum}->{'ARTIST'})) { - @track_artist = (); - } else { - @track_artist = @disc_artist; - } - run_flac(sprintf('track%02d.cdda.wav', $tracknum), undef, - "../$outfile", - @track_artist, - @tags, - "TRACKNUMBER=$tracknum", - track_tags($tracks_to_tags{$tracknum})); - push(@files, "$outfile.flac"); - } - $outlog = "../$outdir/log"; + run_flac(sprintf('track%02d.cdda.wav', $tracknum), undef, + "../$outfile", + @track_artist, + @tags, + "TRACKNUMBER=$tracknum", + track_tags($track_tags)); + $outlog = "../$outfile.log"; + push(@files, "$outfile.flac"); + + rename("$tracknum.log", $outlog) + or die("rename($tracknum.log, $outlog): $!"); } - verbose("Cleaning up $dir\n"); - unlink('using-tags') or die("unlink(using-tags): $!"); - unlink('cue') or die("unlink(cue): $!"); - rename('log', $outlog) - or die("rename(log, $outlog): $!"); chdir('..') or die("chdir(..): $!"); if (-x "$dir/post-processor") { @@ -274,48 +295,114 @@ sub flac { unlink("$dir/post-processor") or die("unlink($dir/post-processor): $!"); } - rmdir($dir) or die("rmdir($dir): $!"); + # Clean up if we're the last job for $dir; for multi-file dirs, + # it's possible for more than one job to run cleanup at once, so + # don't fail if things are already clean. +# if (nothing but using-tags in $dir) { +# unlink("$dir/using-tags") or $!{ENOENT} or die("unlink($dir/using-tags): $!"); +# rmdir($dir) or or $!{ENOENT} die("rmdir($dir): $!"); +# } return 0; } sub flacloop { my $MAXJOBS = shift; - my $i; - - - $SIG{CHLD} = \&reaper; - while (1) { - if (scalar(@jobs) <= $MAXJOBS) { - foreach $i (glob('*/tags')) { - my $dir = dirname($i); - my $pid = - Jobs::newjob(sub { - open(STDERR, ">$dir/log") - or die("open(STDERR, >$dir/log): $!"); - return flac($dir); - }, 'debug'=>$debug); - verbose("new job $pid for $dir\n"); - @Jobs::jobs <= $MAXJOBS or last; + my $dir; + my @jobs; + + # Get a job for Jobs::run. On each call, look for new fa-rip + # directories and append an item to the queue @jobs for each wav + # file therein. Then, if we have anything in the queue, return a + # function to call flac for it, otherwise sleep for a bit. This + # looks forever, never returning undef, so Jobs::run never returns. + my $getjob = sub { + # Look for new fa-rip directories. + while (1) { + for my $i (glob('*/tags')) { + $dir = dirname($i); + + verbose("Renaming $dir/tags\n"); + rename("$dir/tags", "$dir/using-tags") + or die("rename($dir/tags, $dir/using-tags): $!"); + + my ($artist, $album, + $discnum, @tags) = get_tags("$dir/using-tags"); + if (-e "$dir/wav") { + # single-file + push(@jobs, + [$dir, $artist, $album, $discnum, + undef, undef, undef, @tags]); + } else { + #multi-file + # Don't need cue file. + unlink("$dir/cue") or die("unlink($dir/cue): $!"); + + # Go over @tags, store all [n] tags in a list keyed by + # n in %tracks_to_tags, store all ARTIST (not + # ARTIST[n]) tags in @disc_artist, and leave the rest + # in @tags. + my %tracks_to_tags; + my @disc_artist; + my @tmp; + for my $tag (@tags) { + if ($tag =~ /^([^[]+)\[(\d+)]=(.*)/) { + push(@{$tracks_to_tags{$2}->{$1}}, $3); + } elsif ($tag =~ /^ARTIST=/) { + push(@disc_artist, $tag); + } else { + push(@tmp, $tag); + } + } + @tags = @tmp; + + push(@jobs, + map { + [$dir, $artist, $album, $discnum, $_, + $tracks_to_tags{$_}, \@disc_artist, @tags] + } sort(map(int, keys(%tracks_to_tags)))); + } } - } - for ($i = 0; $i <= $#finished; $i++) { - my ($pid, $status) = Jobs::deljob($i); - verbose("$pid finished ("); - if (WIFEXITED($status)) { - verbose('exited with status ', WEXITSTATUS($status)); - } elsif (WIFSIGNALED($status)) { - verbose('killed with signal ', WTERMSIG($status)); - } elsif (WIFSTOPPED($status)) { - verbose('stopped with signal ', WSTOPSIG($status)); + # Return a job if we found any work. + if (my $job = shift(@jobs)) { + return sub { + my $log = defined($job->[4]) ? $job->[4] . '.log' : 'log'; + $dir = $job->[0]; + open(STDERR, ">$dir/$log") or die("open(STDERR, >$dir/$log): $!"); + return flac(@$job); + } } - verbose(")\n"); - } - verbose(scalar(@jobs), " jobs\n"); - sleep(5); - } + # Didn't find anything; wait a while and check again. + sleep(5); + } + }; + + # Never returns (see $getjob comment). + Jobs::run('max-jobs'=>$MAXJOBS, + 'debug'=>$debug, + 'get-job'=>$getjob, + + 'notify-start'=>sub { + my $pid = shift; + verbose("new job $pid for $dir\n"); + verbose(scalar(@_), " jobs\n"); + }, + + 'notify-finish'=>sub { + my $pid = shift; + my $status = shift; + verbose("$pid finished ("); + if (WIFEXITED($status)) { + verbose('exited with status ', WEXITSTATUS($status)); + } elsif (WIFSIGNALED($status)) { + verbose('killed with signal ', WTERMSIG($status)); + } elsif (WIFSTOPPED($status)) { + verbose('stopped with signal ', WSTOPSIG($status)); + } + verbose(")\n"); + }); } MAIN: {