]> diplodocus.org Git - flac-archive/blob - fa-flacd
Copy run_or_die from fa-rip and use that to run flac | lame.
[flac-archive] / fa-flacd
1 #! /usr/bin/env perl
2
3 # $Id$
4 # $URL$
5
6 =head1 NAME
7
8 B<fa-flacd> - archive CDs to single FLAC files
9
10 =head1 SYNOPSIS
11
12 B<fa-flacd> [B<-j> I<jobs>] [B<-v>]
13
14 =cut
15
16 use strict;
17 use warnings;
18
19 use File::Basename;
20 use Getopt::Long qw(:config gnu_getopt no_ignore_case);
21 use POSIX ':sys_wait_h';
22 use Pod::Usage;
23
24 my $verbose;
25 my @jobs;
26 my @finished;
27
28 sub verbose {
29 $verbose and print(STDERR $_) for @_;
30 }
31
32 # Return the ARTIST, ALBUM, and DATE followed by a list of all the
33 # lines in the file FN.
34 sub get_tags {
35 my $fn = shift;
36 my $tag;
37 my $value;
38 my $artist;
39 my $album;
40 my @tags;
41
42 verbose("Opening tags file $fn\n");
43 open(TAGS, $fn) or die("open($fn): $!");
44 while (<TAGS>) {
45 chomp;
46 push(@tags, $_);
47
48 ($tag, $value) = split(/=/, $_, 2);
49
50 if (/^ARTIST=/) {
51 $artist = $value;
52 verbose("ARTIST $artist from $fn\n");
53 } elsif (/^ALBUM=/) {
54 $album = $value;
55 verbose("ALBUM $album from $fn\n");
56 }
57 }
58 close(TAGS) or die("close($fn): $!");
59
60 return ($artist, $album, @tags);
61 }
62
63 # Process the fa-rip output in the directory DIR.
64 sub flac {
65 my $dir = shift;
66 my $artist;
67 my $album;
68 my @tags;
69 my $outfile;
70 my $status;
71
72 verbose("Renaming $dir/tags\n");
73 rename("$dir/tags", "$dir/using-tags")
74 or die("rename($dir/tags, $dir/using-tags): $!");
75
76 ($artist, $album, @tags) = get_tags("$dir/using-tags");
77
78 verbose("mkdir($artist)\n");
79 -d $artist or mkdir($artist) or die("mkdir($artist): $!");
80
81 verbose("chdir($dir)\n");
82 chdir($dir) or die("chdir($dir): $!");
83
84 $outfile = "$album";
85 $outfile =~ s/\//_/g;
86
87 verbose("Running flac\n");
88 $status = system('flac', '-o', "../$artist/$outfile.flac-tmp",
89 '--delete-input-file', '-V', '--cuesheet',
90 'cue', '--no-padding', '--best',
91 map({ ('-T', $_) } @tags),
92 'wav');
93 if (WIFEXITED($status)) {
94 if (($status = WEXITSTATUS($status)) != 0) {
95 die("flac exited with status $status");
96 }
97 } elsif (WIFSIGNALED($status)) {
98 die("flac killed with signal ", WTERMSIG($status));
99 } elsif (WIFSTOPPED($status)) {
100 die("flac stopped with signal ", WSTOPSIG($status));
101 } else {
102 die("Major horkage on system(flac): \$? = $? \$! = $!");
103 }
104
105 verbose("Cleaning up $dir\n");
106 unlink('using-tags') or die("unlink(using-tags): $!");
107 unlink('cue') or die("unlink(cue): $!");
108 rename('log', "../$artist/$outfile.log")
109 or die("rename(log, ../$artist/$outfile.log): $!");
110 chdir('..') or die("chdir(..): $!");
111 rmdir($dir) or die("rmdir($dir): $!");
112
113 rename("$artist/$outfile.flac-tmp", "$artist/$outfile.flac")
114 or die("rename($artist/$outfile.flac-tmp, $artist/$outfile.flac): $!");
115
116 return 0;
117 }
118
119 sub reaper {
120 my $pid;
121
122 while (($pid = waitpid(-1, WNOHANG)) > 0) {
123 push(@finished, [$pid, $?]);
124 }
125
126 $SIG{CHLD} = \&reaper;
127 }
128
129 sub newjob {
130 my $dir = shift;
131 my $pid;
132
133 $pid = fork();
134 if (not defined($pid)) {
135 die("fork: $!");
136 } elsif ($pid == 0) {
137 $SIG{CHLD} = 'IGNORE';
138 open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
139 exit(flac($dir));
140 }
141
142 verbose("new job $pid for $dir\n");
143 return $pid;
144 }
145
146 sub deljob {
147 my $i = shift;
148 my $j;
149 my $pid;
150 my $status;
151
152 $pid = $finished[$i][0];
153 $status = $finished[$i][1];
154
155 verbose("$pid finished (");
156 if (WIFEXITED($status)) {
157 verbose('exited with status ', WEXITSTATUS($status));
158 } elsif (WIFSIGNALED($status)) {
159 verbose('killed with signal ', WTERMSIG($status));
160 } elsif (WIFSTOPPED($status)) {
161 verbose('stopped with signal ', WSTOPSIG($status));
162 }
163 verbose(")\n");
164
165 for ($j = 0; $j <= $#jobs; $j++) {
166 $pid == $jobs[$j] and splice(@jobs, $j, 1) and last;
167 }
168
169 splice(@finished, $i, 1);
170 }
171
172 sub flacloop {
173 my $MAXJOBS = shift;
174 my $i;
175 my $j;
176
177
178 $SIG{CHLD} = \&reaper;
179 while (1) {
180 if (scalar(@jobs) <= $MAXJOBS) {
181 foreach $i (glob('*/tags')) {
182 push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last;
183 }
184 }
185
186 for ($i = 0; $i <= $#finished; $i++) {
187 deljob($i);
188 }
189
190 verbose(scalar(@jobs), " jobs\n");
191 sleep(5);
192 }
193 }
194
195 MAIN: {
196 my $jobs;
197 my $help;
198
199 $jobs = 4;
200 GetOptions(
201 'jobs|j=i' => \$jobs,
202 'verbose|v' => \$verbose,
203 'help|h|?' => \$help,
204 ) or pod2usage();
205 $help and pod2usage(-exitstatus=>0, -verbose=>1);
206
207 flacloop($jobs);
208 }
209
210 \f
211 __END__
212
213 =head1 DESCRIPTION
214
215 B<fa-flacd> and B<fa-rip> together comprise B<flac-archive>, a system
216 for archiving audio CDs to single FLAC files. B<fa-flacd> is the guts
217 of the system. It runs in the directory where the audio archives are
218 stored, scanning for new ripped CDs to encode and rename; it never
219 exits. B<fa-rip> generates the inputs for B<fa-flacd>: the ripped WAV
220 file, Vorbis tags, and a cuesheet.
221
222 Both programs expect to be run from the same directory. They use that
223 directory to manage directories named by artist. Intermediate files
224 are written to temporary directories here. B<fa-flacd> processes the
225 temporary directories into per-album files in the artist directories.
226
227 Every 5 seconds, B<fa-flacd> scans its current directory for
228 directories with a file called "tags" and creates a processing job for
229 each one. The number of jobs B<fa-flacd> attempts to run is
230 controlled by the B<-j> option and defaults to 4. B<fa-flacd> will
231 print diagnostic output when the B<-v> option is given.
232
233 A processing job first renames the directory's "tags" file to
234 "using-tags" so that B<ra-flacd> will not try to start another job for
235 this directory. This file is left as is when an error is encountered,
236 so a new job will not be started until the user corrects the error
237 condition and renames "using-tags" back to "tags". Next, it encodes
238 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
239 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
240 the "log" file. Finally, B<fa-flacd> moves the "cue" and "log" files
241 to the artist directory (named by album) and removes the temporary
242 directory.
243
244 =head1 OPTIONS
245
246 =over 4
247
248 =item B<-j> [B<--jobs>] I<jobs>
249
250 Run up to I<jobs> jobs instead of the default 4.
251
252 =item B<-v> [B<--verbose>]
253
254 Print diagnostic information.
255
256 =back
257
258 =head1 AUTHORS
259
260 Written by Eric Gillespie <epg@pretzelnet.org>.
261
262 flac-archive is free software; you may redistribute it and/or modify
263 it under the same terms as Perl itself.
264
265 =cut
266
267 # Local variables:
268 # cperl-indent-level: 4
269 # perl-indent-level: 4
270 # indent-tabs-mode: nil
271 # End:
272
273 # vi: set tabstop=4 expandtab: