]> diplodocus.org Git - flac-archive/blob - fa-flacd
Document zsh 4 requirement.
[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 map({ print(STDERR $_) } @_);
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);
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
120 sub reaper {
121 my $pid;
122
123 while (($pid = waitpid(0, WNOHANG)) > 0) {
124 push(@finished, [$pid, $?]);
125 }
126
127 $SIG{CHLD} = \&reaper;
128 }
129
130 sub newjob {
131 my $dir = shift;
132 my $pid;
133
134 $pid = fork();
135 if ($pid == -1) {
136 die("fork: $!");
137 } elsif ($pid == 0) {
138 $SIG{CHLD} = 'IGNORE';
139 open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
140 flac($dir);
141 exit(0);
142 }
143
144 verbose("new job $pid for $dir\n");
145 return $pid;
146 }
147
148 sub deljob {
149 my $i = shift;
150 my $j;
151 my $pid;
152 my $status;
153
154 $pid = $finished[$i][0];
155 $status = WEXITSTATUS($finished[$i][1]);
156
157 verbose("$pid finished (");
158 if (WIFEXITED($status)) {
159 verbose('exited ', WEXITSTATUS($status));
160 } elsif (WIFSIGNALED($status)) {
161 verbose('signalled ', WTERMSIG($status));
162 } elsif (WIFSTOPPED($status)) {
163 verbose('stopped ', WSTOPSIG($status));
164 }
165 verbose(")\n");
166
167 for ($j = 0; $j <= $#jobs; $j++) {
168 $pid == $jobs[$j] and splice(@jobs, $j, 1) and last;
169 }
170
171 splice(@finished, $i, 1);
172 }
173
174 sub flacloop {
175 my $MAXJOBS = shift;
176 my $i;
177 my $j;
178
179
180 $SIG{CHLD} = \&reaper;
181 while (1) {
182 if (scalar(@jobs) <= $MAXJOBS) {
183 foreach $i (glob('*/tags')) {
184 push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last;
185 }
186 }
187
188 for ($i = 0; $i <= $#finished; $i++) {
189 deljob($i);
190 }
191
192 verbose(scalar(@jobs), " jobs\n");
193 sleep(5);
194 }
195 }
196
197 MAIN: {
198 my %opts;
199
200 $opts{'j'} = 4;
201 $opts{'v'} = 0;
202 if (not getopts('j:v', \%opts)) {
203 print("usage: flacd [-jN -v]\n");
204 exit(2);
205 }
206
207 $verbose = $opts{'v'};
208
209 flacloop($opts{'j'});
210 }
211
212 \f
213 __END__
214
215 =head1 DESCRIPTION
216
217 B<fa-flacd>, B<fa-rip>, and B<fa-tags> together comprise
218 B<flac-archive>, a system for archiving audio CDs to single FLAC
219 files. B<fa-flacd> is the guts of the system. It runs in the
220 directory where the audio archives are stored, scanning for new CDs to
221 encode and rename; it never exits. B<fa-rip> generates the inputs for
222 B<fa-flacd>: the ripped WAV file, Vorbis tags, and a cuesheet.
223 B<fa-tags> is not meant to be run directly; B<fa-rip> uses it to
224 generate the candidate Vorbis tags.
225
226 All three programs expect to be run from the same directory. They use
227 that directory to manage directories named by artist and by disc ID.
228 Intermediate files are written to the disc ID directory. B<fa-flacd>
229 processes the disc ID directories into per-album files in the artist
230 directories.
231
232 =head2 FA-FLACD
233
234 B<fa-flacd> does not exit; it runs until the user kills it. Every 5
235 seconds it scans its current directory for directories with a file
236 called "tags" and creates a processing job for each one. The number
237 of jobs B<fa-flacd> attempts to run is controlled by the B<-j> option
238 and defaults to 4. B<fa-flacd> will print diagnostic output when the
239 B<-v> option is given.
240
241 A processing job first renames the directory's "tags" file to
242 "using-tags" so that B<ra-flacd> will not try to start another job for
243 this directory. This file is left as is when an error is encountered,
244 so a new job will not be started until the user corrects the error
245 condition and renames "using-tags" back to "tags". Next, it encodes
246 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
247 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
248 the "log" file. Finally, the "cue" and "log" files are moved to the
249 artist directory (and named by album) and the ID directory is removed.
250
251 =head2 FA-RIP
252
253 B<fa-rip> uses L<cd-discid(1)> to retrieve the disc ID and track
254 information. It creates a directory named by ID for storage of its
255 intermediate files. It passes the L<cd-discid(1)> output as
256 command-line arguments to B<fa-tags> in the background. It then uses
257 L<cdrdao(1)> to create the "cue" file in the background. Finally, it
258 execs ><cdparanoia(1)> to rip the CD to the "wav" file.
259
260 In order for this CD to be processed by B<fa-flacd>, the user must
261 create a "tags" file. This is usually done by renaming one of the
262 candidate-tags files and deleting the others.
263
264 =head2 FA-TAGS
265
266 B<fa-tags> uses L<cddb-tool(1)> (from the B<abcde> package) to
267 populate candidate-tags files. These are numbered in the order of
268 entries read from CDDB, e.g. candidate-tags-1, candidate-tags-2, etc.
269 B<fa-tags> also creates candidate-tags-0, which has the correct fields
270 for this CD (including correct number of TITLE= lines), but with all
271 fields blank.
272
273 B<fa-tags> expects the output of L<cd-discid(1)> as command-line
274 arguments. That is, the disc ID, number of tracks, list of track
275 offsets, and total length of the CD in seconds.
276
277 =head1 ENVIRONMENT
278
279 =over 4
280
281 =item CDDBURL
282
283 B<fa-tags> uses this to retrieve candidate Vorbis tags. Defaults to
284 "http://freedb.freedb.org/~cddb/cddb.cgi".
285
286 =item CDDEV
287
288 B<fa-rip> uses this to rip audio and save the cuesheet for a CD. It
289 makes some effort to check some common device names for FreeBSD,
290 Linux, and NetBSD by default.
291
292 =back
293
294 =head1 AUTHORS
295
296 Written by Eric Gillespie <epg@pretzelnet.org>. B<fa-tags> contains
297 code from B<abcde>, which bears the following notice:
298
299 # Copyright (c) 1998-2001 Robert Woodcock <rcw@debian.org>
300 # Copyright (c) 2003-2004 Jesus Climent <jesus.climent@hispalinux.es>
301 # This code is hereby licensed for public consumption under either the
302 # GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice.
303
304 B<flac-archive> is hereby licensed for public consumption under either
305 the GNU GPL v2 or greater, or Larry Wall's Artistic license - your
306 choice.
307
308 =cut
309
310 # Local variables:
311 # cperl-indent-level: 4
312 # perl-indent-level: 4
313 # indent-tabs-mode: nil
314 # End:
315
316 # vi: set tabstop=4 expandtab: