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