]> diplodocus.org Git - flac-archive/blob - fa-flacd
(verbose): Use for instead of map.
[flac-archive] / fa-flacd
1 #! /usr/bin/env perl
2
3 # $Id$
4
5 =head1 NAME
6
7 B<fa-flacd>, B<fa-rip>, B<fa-tags> - archive CDs to single FLAC files
8
9 =head1 SYNOPSIS
10
11 B<fa-flacd> [B<-jv>]
12
13 B<fa-rip>
14
15 B<fa-tags> ID TRACKCOUNT OFFSET [OFFSET ...] LENGTH
16
17 =cut
18
19 use strict;
20 use warnings;
21
22 use File::Basename;
23 use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1;
24 use POSIX ':sys_wait_h';
25
26 our $VERSION = 1;
27
28 my $verbose;
29 my @jobs;
30 my @finished;
31
32 sub verbose {
33 $verbose and print(STDERR $_) for @_;
34 }
35
36 # Return the ARTIST, ALBUM, and DATE followed by a list of all the
37 # lines in the file FN.
38 sub get_tags {
39 my $fn = shift;
40 my $tag;
41 my $value;
42 my $artist;
43 my $album;
44 my $date;
45 my @tags;
46
47 verbose("Opening tags file $fn\n");
48 open(TAGS, $fn) or die("open($fn): $!");
49 while (<TAGS>) {
50 chomp;
51 push(@tags, $_);
52
53 ($tag, $value) = split(/=/, $_, 2);
54
55 if (/^ARTIST=/) {
56 $artist = $value;
57 verbose("ARTIST $artist from $fn\n");
58 } elsif (/^ALBUM=/) {
59 $album = $value;
60 verbose("ALBUM $album from $fn\n");
61 } elsif (/^DATE=/) {
62 $date = $value;
63 verbose("DATE $date from $fn\n");
64 }
65 }
66 close(TAGS) or die("close($fn): $!");
67
68 return ($artist, $album, $date, @tags);
69 }
70
71 # Process the fa-rip output in the directory DIR.
72 sub flac {
73 my $dir = shift;
74 my $artist;
75 my $album;
76 my $date;
77 my @tags;
78 my $status;
79
80 verbose("Renaming $dir/tags\n");
81 rename("$dir/tags", "$dir/using-tags")
82 or die("rename($dir/tags, $dir/using-tags): $!");
83
84 ($artist, $album, $date, @tags) = get_tags("$dir/using-tags");
85
86 verbose("mkdir($artist)\n");
87 -d $artist or mkdir($artist) or die("mkdir($artist): $!");
88
89 verbose("chdir($dir)\n");
90 chdir($dir) or die("chdir($dir): $!");
91
92 verbose("Running flac\n");
93 $status = system('flac', '-o', "../$artist/$album.flac-tmp",
94 '--delete-input-file', '-V', '--cuesheet',
95 'cue', '--no-padding', '--best',
96 map({ ('-T', $_) } @tags),
97 'wav');
98 if (WIFEXITED($status) and ($status = WEXITSTATUS($status)) != 0) {
99 die("flac: $status");
100 } elsif (WIFSIGNALED($status)) {
101 die("flac killed with signal ", WTERMSIG($status));
102 } elsif (WIFSTOPPED($status)) {
103 die("flac stopped with signal ", WSTOPSIG($status));
104 }
105
106 verbose("Cleaning up $dir\n");
107 unlink('using-tags') or die("unlink(using-tags): $!");
108 unlink('cue') or die("unlink(cue): $!");
109 rename('toc', "../$artist/$album.toc")
110 or die("rename(toc, ../$artist/$album.toc): $!");
111 rename('log', "../$artist/$album.log")
112 or die("rename(log, ../$artist/$album.log): $!");
113 chdir('..') or die("chdir(..): $!");
114 rmdir($dir) or die("rmdir($dir): $!");
115
116 rename("$artist/$album.flac-tmp", "$artist/$album.flac")
117 or die("rename($artist/$album.flac-tmp, $artist/$album.flac): $!");
118
119 return 0;
120 }
121
122 sub reaper {
123 my $pid;
124
125 while (($pid = waitpid(0, WNOHANG)) > 0) {
126 push(@finished, [$pid, $?]);
127 }
128
129 $SIG{CHLD} = \&reaper;
130 }
131
132 sub newjob {
133 my $dir = shift;
134 my $pid;
135
136 $pid = fork();
137 if (not defined($pid)) {
138 die("fork: $!");
139 } elsif ($pid == 0) {
140 $SIG{CHLD} = 'IGNORE';
141 open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
142 exit(flac($dir));
143 }
144
145 verbose("new job $pid for $dir\n");
146 return $pid;
147 }
148
149 sub deljob {
150 my $i = shift;
151 my $j;
152 my $pid;
153 my $status;
154
155 $pid = $finished[$i][0];
156 $status = $finished[$i][1];
157
158 verbose("$pid finished (");
159 if (WIFEXITED($status)) {
160 verbose('exited ', WEXITSTATUS($status));
161 } elsif (WIFSIGNALED($status)) {
162 verbose('signalled ', WTERMSIG($status));
163 } elsif (WIFSTOPPED($status)) {
164 verbose('stopped ', WSTOPSIG($status));
165 }
166 verbose(")\n");
167
168 for ($j = 0; $j <= $#jobs; $j++) {
169 $pid == $jobs[$j] and splice(@jobs, $j, 1) and last;
170 }
171
172 splice(@finished, $i, 1);
173 }
174
175 sub flacloop {
176 my $MAXJOBS = shift;
177 my $i;
178 my $j;
179
180
181 $SIG{CHLD} = \&reaper;
182 while (1) {
183 if (scalar(@jobs) <= $MAXJOBS) {
184 foreach $i (glob('*/tags')) {
185 push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last;
186 }
187 }
188
189 for ($i = 0; $i <= $#finished; $i++) {
190 deljob($i);
191 }
192
193 verbose(scalar(@jobs), " jobs\n");
194 sleep(5);
195 }
196 }
197
198 MAIN: {
199 my %opts;
200
201 $opts{'j'} = 4;
202 $opts{'v'} = 0;
203 if (not getopts('j:v', \%opts)) {
204 print("usage: flacd [-jN -v]\n");
205 exit(2);
206 }
207
208 $verbose = $opts{'v'};
209
210 flacloop($opts{'j'});
211 }
212
213 \f
214 __END__
215
216 =head1 DESCRIPTION
217
218 B<fa-flacd>, B<fa-rip>, and B<fa-tags> together comprise
219 B<flac-archive>, a system for archiving audio CDs to single FLAC
220 files. B<fa-flacd> is the guts of the system. It runs in the
221 directory where the audio archives are stored, scanning for new CDs to
222 encode and rename; it never exits. B<fa-rip> generates the inputs for
223 B<fa-flacd>: the ripped WAV file, Vorbis tags, and a cuesheet.
224 B<fa-tags> is not meant to be run directly; B<fa-rip> uses it to
225 generate the candidate Vorbis tags.
226
227 All three programs expect to be run from the same directory. They use
228 that directory to manage directories named by artist and by disc ID.
229 Intermediate files are written to the disc ID directory. B<fa-flacd>
230 processes the disc ID directories into per-album files in the artist
231 directories.
232
233 =head2 FA-FLACD
234
235 B<fa-flacd> does not exit; it runs until the user kills it. Every 5
236 seconds it scans its current directory for directories with a file
237 called "tags" and creates a processing job for each one. The number
238 of jobs B<fa-flacd> attempts to run is controlled by the B<-j> option
239 and defaults to 4. B<fa-flacd> will print diagnostic output when the
240 B<-v> option is given.
241
242 A processing job first renames the directory's "tags" file to
243 "using-tags" so that B<ra-flacd> will not try to start another job for
244 this directory. This file is left as is when an error is encountered,
245 so a new job will not be started until the user corrects the error
246 condition and renames "using-tags" back to "tags". Next, it encodes
247 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
248 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
249 the "log" file. Finally, the "cue" and "log" files are moved to the
250 artist directory (and named by album) and the ID directory is removed.
251
252 =head2 FA-RIP
253
254 B<fa-rip> uses C<cd-discid(1)> to retrieve the disc ID and track
255 information. It creates a directory named by ID for storage of its
256 intermediate files. It passes the C<cd-discid(1)> output as
257 command-line arguments to B<fa-tags> in the background. It then uses
258 C<cdrdao(1)> to create the "cue" file in the background. Finally, it
259 execs C<cdparanoia(1)> to rip the CD to the "wav" file.
260
261 In order for this CD to be processed by B<fa-flacd>, the user must
262 create a "tags" file. This is usually done by renaming one of the
263 candidate-tags files and deleting the others.
264
265 =head2 FA-TAGS
266
267 B<fa-tags> uses C<cddb-tool(1)> (from the B<abcde> package) to
268 populate candidate-tags files. These are numbered in the order of
269 entries read from CDDB, e.g. candidate-tags-1, candidate-tags-2, etc.
270 B<fa-tags> also creates candidate-tags-0, which has the correct fields
271 for this CD (including correct number of TITLE= lines), but with all
272 fields blank.
273
274 B<fa-tags> expects the output of C<cd-discid(1)> as command-line
275 arguments. That is, the disc ID, number of tracks, list of track
276 offsets, and total length of the CD in seconds.
277
278 =head1 ENVIRONMENT
279
280 =over 4
281
282 =item CDDBURL
283
284 B<fa-tags> uses this to retrieve candidate Vorbis tags. Defaults to
285 "http://freedb.freedb.org/~cddb/cddb.cgi".
286
287 =item CDDEV
288
289 B<fa-rip> uses this to rip audio and save the cuesheet for a CD. It
290 makes some effort to check some common device names for FreeBSD,
291 Linux, and NetBSD by default.
292
293 =back
294
295 =head1 AUTHORS
296
297 Written by Eric Gillespie <epg@pretzelnet.org>. B<fa-tags> contains
298 code from B<abcde>, which bears the following notice:
299
300 # Copyright (c) 1998-2001 Robert Woodcock <rcw@debian.org>
301 # Copyright (c) 2003-2004 Jesus Climent <jesus.climent@hispalinux.es>
302 # This code is hereby licensed for public consumption under either the
303 # GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice.
304
305 B<flac-archive> is hereby licensed for public consumption under either
306 the GNU GPL v2 or greater, or Larry Wall's Artistic license - your
307 choice.
308
309 =cut
310
311 # Local variables:
312 # cperl-indent-level: 4
313 # perl-indent-level: 4
314 # indent-tabs-mode: nil
315 # End:
316
317 # vi: set tabstop=4 expandtab: