]> diplodocus.org Git - flac-archive/blob - fa-flacd
No more .toc file now that we use mkcue.
[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> I<track-count>
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 $outfile;
74 my $status;
75
76 verbose("Renaming $dir/tags\n");
77 rename("$dir/tags", "$dir/using-tags")
78 or die("rename($dir/tags, $dir/using-tags): $!");
79
80 ($artist, $album, @tags) = get_tags("$dir/using-tags");
81
82 verbose("mkdir($artist)\n");
83 -d $artist or mkdir($artist) or die("mkdir($artist): $!");
84
85 verbose("chdir($dir)\n");
86 chdir($dir) or die("chdir($dir): $!");
87
88 $outfile = "$album";
89 $outfile =~ s/\//_/g;
90
91 verbose("Running flac\n");
92 $status = system('flac', '-o', "../$artist/$outfile.flac-tmp",
93 '--delete-input-file', '-V', '--cuesheet',
94 'cue', '--no-padding', '--best',
95 map({ ('-T', $_) } @tags),
96 'wav');
97 if (WIFEXITED($status) and ($status = WEXITSTATUS($status)) != 0) {
98 die("flac: $status");
99 } elsif (WIFSIGNALED($status)) {
100 die("flac killed with signal ", WTERMSIG($status));
101 } elsif (WIFSTOPPED($status)) {
102 die("flac stopped with signal ", WSTOPSIG($status));
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 ', WEXITSTATUS($status));
158 } elsif (WIFSIGNALED($status)) {
159 verbose('signalled ', WTERMSIG($status));
160 } elsif (WIFSTOPPED($status)) {
161 verbose('stopped ', 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 %opts;
197
198 $opts{'j'} = 4;
199 $opts{'v'} = 0;
200 if (not getopts('j:v', \%opts)) {
201 print(STDERR "usage: flacd [-jN -v]\n");
202 exit(2);
203 }
204
205 $verbose = $opts{'v'};
206
207 flacloop($opts{'j'});
208 }
209
210 \f
211 __END__
212
213 =head1 DESCRIPTION
214
215 B<fa-flacd>, B<fa-rip>, and B<fa-tags> together comprise
216 B<flac-archive>, a system for archiving audio CDs to single FLAC
217 files. B<fa-flacd> is the guts of the system. It runs in the
218 directory where the audio archives are stored, scanning for new CDs to
219 encode and rename; it never exits. B<fa-rip> generates the inputs for
220 B<fa-flacd>: the ripped WAV file, Vorbis tags, and a cuesheet.
221 B<fa-tags> is not meant to be run directly; B<fa-rip> uses it to
222 generate the candidate Vorbis tags.
223
224 All three programs expect to be run from the same directory. They use
225 that directory to manage directories named by artist and by disc ID.
226 Intermediate files are written to the disc ID directory. B<fa-flacd>
227 processes the disc ID directories into per-album files in the artist
228 directories.
229
230 =head2 FA-FLACD
231
232 B<fa-flacd> does not exit; it runs until the user kills it. Every 5
233 seconds it scans its current directory for directories with a file
234 called "tags" and creates a processing job for each one. The number
235 of jobs B<fa-flacd> attempts to run is controlled by the B<-j> option
236 and defaults to 4. B<fa-flacd> will print diagnostic output when the
237 B<-v> option is given.
238
239 A processing job first renames the directory's "tags" file to
240 "using-tags" so that B<ra-flacd> will not try to start another job for
241 this directory. This file is left as is when an error is encountered,
242 so a new job will not be started until the user corrects the error
243 condition and renames "using-tags" back to "tags". Next, it encodes
244 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
245 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
246 the "log" file. Finally, the "cue" and "log" files are moved to the
247 artist directory (and named by album) and the ID directory is removed.
248
249 =head2 FA-RIP
250
251 B<fa-rip> uses C<mktemp(1)> to create a directory for storage of its
252 intermediate files. It uses C<cdrdao(1)> to create the "cue" file and
253 then passes the number of tracks (from the "cue" file) as command-line
254 arguments to B<fa-tags>. Finally, it execs C<cdparanoia(1)> to rip
255 the CD to the "wav" file.
256
257 In order for this CD to be processed by B<fa-flacd>, the user must
258 create a "tags" file. This is usually done by renaming one of the
259 candidate-tags files and deleting the others.
260
261 =head2 FA-TAGS
262
263 B<fa-tags> uses C<MusicBrainz::Client> to populate candidate-tags
264 files. These are numbered in the order of entries read from
265 MusicBrainz, e.g. candidate-tags-1, candidate-tags-2, etc. B<fa-tags>
266 also creates candidate-tags-0, which has the correct fields for this
267 CD (including correct number of TITLE= lines), but with all fields
268 blank.
269
270 B<fa-tags> requires the number of tracks as its sole argument.
271
272 =head1 ENVIRONMENT
273
274 =over 4
275
276 =item CDDEV
277
278 B<fa-rip> uses this to rip audio and save the cuesheet for a CD. It
279 makes some effort to check some common device names for FreeBSD,
280 Linux, and NetBSD by default.
281
282 =back
283
284 =head1 AUTHORS
285
286 Written by Eric Gillespie <epg@pretzelnet.org>.
287
288 flac-archive is free software; you may redistribute it and/or modify
289 it under the same terms as Perl itself.
290
291 =cut
292
293 # Local variables:
294 # cperl-indent-level: 4
295 # perl-indent-level: 4
296 # indent-tabs-mode: nil
297 # End:
298
299 # vi: set tabstop=4 expandtab: