#! /usr/bin/env perl
# $Id$
+# $URL$
=head1 NAME
=head1 SYNOPSIS
-B<fa-flacd> [B<-jv>]
+B<fa-flacd> [B<-j> I<jobs>] [B<-v>]
=cut
use warnings;
use File::Basename;
-use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1;
+use Getopt::Long qw(:config gnu_getopt no_ignore_case);
use POSIX ':sys_wait_h';
+use Pod::Usage;
-our $VERSION = 1;
-
+my $debug;
my $verbose;
my @jobs;
my @finished;
my $value;
my $artist;
my $album;
+ my $discnum;
my @tags;
verbose("Opening tags file $fn\n");
($tag, $value) = split(/=/, $_, 2);
- if (/^ARTIST=/) {
+ if (/^ARTIST=/i) {
$artist = $value;
verbose("ARTIST $artist from $fn\n");
- } elsif (/^ALBUM=/) {
+ } elsif (/^ALBUM=/i) {
$album = $value;
- verbose("ALBUM $album from $fn\n");
+ verbose("ALBUM $album from $fn\n"); # cperl-mode sucks "
+ } elsif (/^DISCNUMBER=/i) {
+ $discnum = int($value);
+ verbose("DISCNUMBER $discnum from $fn\n");
}
}
close(TAGS) or die("close($fn): $!");
- return ($artist, $album, @tags);
+ return ($artist, $album, $discnum, @tags);
+}
+
+# Hash of Hashes of Lists Push
+sub hhlp {
+ my $hash = shift;
+ my $key1 = shift;
+ my $key2 = shift;
+ my $val = shift;
+
+ return push(@{$hash->{$key1}->{$key2}}, $val);
+}
+
+sub bork_tags {
+ my $h = shift;
+ my @result;
+
+ while (my ($key, $vall) = each(%$h)) {
+ for my $val (@$vall) {
+ push(@result, "$key=$val")
+ }
+ }
+
+ return @result;
}
# Process the fa-rip output in the directory DIR.
my $dir = shift;
my $artist;
my $album;
+ my $discnum;
my @tags;
my $outfile;
my $status;
rename("$dir/tags", "$dir/using-tags")
or die("rename($dir/tags, $dir/using-tags): $!");
- ($artist, $album, @tags) = get_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): $!");
+ my $outdir = "$artist/$album";
+ verbose("mkdir($outdir)\n");
+ -d "$outdir" or mkdir("$outdir") or die("mkdir($outdir): $!");
+
verbose("chdir($dir)\n");
chdir($dir) or die("chdir($dir): $!");
- $outfile = "$album";
- $outfile =~ s/\//_/g;
-
- verbose("Running flac\n");
- $status = system('flac', '-o', "../$artist/$outfile.flac-tmp",
- '--delete-input-file', '-V', '--cuesheet',
- 'cue', '--no-padding', '--best',
- map({ ('-T', $_) } @tags),
- 'wav');
- if (WIFEXITED($status) and ($status = WEXITSTATUS($status)) != 0) {
- die("flac: $status");
- } elsif (WIFSIGNALED($status)) {
- die("flac killed with signal ", WTERMSIG($status));
- } elsif (WIFSTOPPED($status)) {
- die("flac stopped with signal ", WSTOPSIG($status));
+ my @artist;
+ my %things;
+ my (@bork, @titles);
+ for my $tag (@tags) {
+ if ($tag =~ /^([^[]+)\[(\d+)]=(.*)/) {
+ hhlp(\%things, $2, $1, $3);
+ } elsif ($tag =~ /^ARTIST=/) {
+ push(@artist, $tag);
+ } else {
+ push(@bork, $tag);
+ }
+ }
+ @tags = @bork;
+
+ my @files;
+ for my $tracknum (sort(map(int, keys(%things)))) {
+ my $title = join(' ', map(split, @{$things{$tracknum}->{'TITLE'}}));
+ $title =~ s|/|_|g;
+ $outfile = join(' ',
+ (defined($discnum)
+ ? sprintf('%02d', $discnum)
+ : ()),
+ sprintf('%02d', $tracknum),
+ $title);
+ push(@files, "$outdir/$outfile.flac");
+ $outfile = "../$outdir/$outfile";
+
+ my @lartist;
+ if (exists($things{$tracknum}->{'ARTIST'})) {
+ @lartist = ();
+ } else {
+ @lartist = @artist;
+ }
+
+ verbose("Running flac\n");
+ $status = system('flac', '-o', "$outfile.flac-tmp",
+ '--delete-input-file', '-V', '--no-padding', '--best',
+ map({ ('-T', $_) }
+ @lartist,
+ grep({ $_ !~ /^ARTIST=/ } @tags),
+ bork_tags($things{$tracknum})),
+ sprintf('track%02d.cdda.wav', $tracknum));
+ if (WIFEXITED($status)) {
+ if (($status = WEXITSTATUS($status)) != 0) {
+ die("flac exited with status $status");
+ }
+ } elsif (WIFSIGNALED($status)) {
+ die("flac killed with signal ", WTERMSIG($status));
+ } elsif (WIFSTOPPED($status)) {
+ die("flac stopped with signal ", WSTOPSIG($status));
+ } else {
+ die("Major horkage on system(flac): \$? = $? \$! = $!");
+ }
+
+ rename("$outfile.flac-tmp", "$outfile.flac")
+ or die("rename($outfile.flac-tmp, $outfile.flac): $!");
}
verbose("Cleaning up $dir\n");
unlink('using-tags') or die("unlink(using-tags): $!");
unlink('cue') or die("unlink(cue): $!");
- rename('log', "../$artist/$outfile.log")
- or die("rename(log, ../$artist/$outfile.log): $!");
+ rename('log', "../$outdir/log")
+ or die("rename(log, ../$outdir/log): $!");
chdir('..') or die("chdir(..): $!");
- rmdir($dir) or die("rmdir($dir): $!");
- rename("$artist/$outfile.flac-tmp", "$artist/$outfile.flac")
- or die("rename($artist/$outfile.flac-tmp, $artist/$outfile.flac): $!");
+ if (-x "$dir/post-processor") {
+ verbose("Running './$dir/post-processor'\n");
+ system("./$dir/post-processor", @files);
+ unlink("$dir/post-processor") or die("unlink($dir/post-processor): $!");
+ }
+
+ rmdir($dir) or die("rmdir($dir): $!");
return 0;
}
my $dir = shift;
my $pid;
- $pid = fork();
- if (not defined($pid)) {
- die("fork: $!");
- } elsif ($pid == 0) {
- $SIG{CHLD} = 'IGNORE';
+ if (not $debug) {
+ $pid = fork();
+ if (not defined($pid)) {
+ die("fork: $!");
+ }
+ }
+
+ if ($debug or $pid == 0) {
+ $SIG{CHLD} = 'DEFAULT';
open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
exit(flac($dir));
}
verbose("$pid finished (");
if (WIFEXITED($status)) {
- verbose('exited ', WEXITSTATUS($status));
+ verbose('exited with status ', WEXITSTATUS($status));
} elsif (WIFSIGNALED($status)) {
- verbose('signalled ', WTERMSIG($status));
+ verbose('killed with signal ', WTERMSIG($status));
} elsif (WIFSTOPPED($status)) {
- verbose('stopped ', WSTOPSIG($status));
+ verbose('stopped with signal ', WSTOPSIG($status));
}
verbose(")\n");
}
MAIN: {
- my %opts;
-
- $opts{'j'} = 4;
- $opts{'v'} = 0;
- if (not getopts('j:v', \%opts)) {
- print(STDERR "usage: flacd [-jN -v]\n");
- exit(2);
- }
-
- $verbose = $opts{'v'};
-
- flacloop($opts{'j'});
+ my $jobs;
+ my $help;
+
+ $jobs = 4;
+ GetOptions(
+ 'debug|X' => \$debug,
+ 'jobs|j=i' => \$jobs,
+ 'verbose|v' => \$verbose,
+ 'help|h|?' => \$help,
+ ) or pod2usage();
+ $help and pod2usage(-exitstatus=>0, -verbose=>1);
+
+ flacloop($jobs);
}
\f
to the artist directory (named by album) and removes the temporary
directory.
+If the temporary directory contains an executable file named
+"post-processor", B<fa-flacd> executes that file with the relative
+path to the output FLAC file as an argument. The output files are in
+their final location when "post-processor" starts. Possible uses are
+running B<flac2mp3>, moving the output files to a different location,
+removing the lock file, or adding to a database. The standard input,
+output, and error streams are inherited from B<fa-flacd>, so they may
+be connected to anything from a tty to /dev/null. This means that you
+may want to redirect these streams, if you want to save them or do any
+logging.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-j> [B<--jobs>] I<jobs>
+
+Run up to I<jobs> jobs instead of the default 4.
+
+=item B<-v> [B<--verbose>]
+
+Print diagnostic information.
+
+=back
+
=head1 AUTHORS
Written by Eric Gillespie <epg@pretzelnet.org>.