]> diplodocus.org Git - flac-archive/blob - fa-flacd
Add URL keyword.
[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) 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('log', "../$artist/$outfile.log")
105 or die("rename(log, ../$artist/$outfile.log): $!");
106 chdir('..') or die("chdir(..): $!");
107 rmdir($dir) or die("rmdir($dir): $!");
108
109 rename("$artist/$outfile.flac-tmp", "$artist/$outfile.flac")
110 or die("rename($artist/$outfile.flac-tmp, $artist/$outfile.flac): $!");
111
112 return 0;
113 }
114
115 sub reaper {
116 my $pid;
117
118 while (($pid = waitpid(-1, WNOHANG)) > 0) {
119 push(@finished, [$pid, $?]);
120 }
121
122 $SIG{CHLD} = \&reaper;
123 }
124
125 sub newjob {
126 my $dir = shift;
127 my $pid;
128
129 $pid = fork();
130 if (not defined($pid)) {
131 die("fork: $!");
132 } elsif ($pid == 0) {
133 $SIG{CHLD} = 'IGNORE';
134 open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
135 exit(flac($dir));
136 }
137
138 verbose("new job $pid for $dir\n");
139 return $pid;
140 }
141
142 sub deljob {
143 my $i = shift;
144 my $j;
145 my $pid;
146 my $status;
147
148 $pid = $finished[$i][0];
149 $status = $finished[$i][1];
150
151 verbose("$pid finished (");
152 if (WIFEXITED($status)) {
153 verbose('exited ', WEXITSTATUS($status));
154 } elsif (WIFSIGNALED($status)) {
155 verbose('signalled ', WTERMSIG($status));
156 } elsif (WIFSTOPPED($status)) {
157 verbose('stopped ', WSTOPSIG($status));
158 }
159 verbose(")\n");
160
161 for ($j = 0; $j <= $#jobs; $j++) {
162 $pid == $jobs[$j] and splice(@jobs, $j, 1) and last;
163 }
164
165 splice(@finished, $i, 1);
166 }
167
168 sub flacloop {
169 my $MAXJOBS = shift;
170 my $i;
171 my $j;
172
173
174 $SIG{CHLD} = \&reaper;
175 while (1) {
176 if (scalar(@jobs) <= $MAXJOBS) {
177 foreach $i (glob('*/tags')) {
178 push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last;
179 }
180 }
181
182 for ($i = 0; $i <= $#finished; $i++) {
183 deljob($i);
184 }
185
186 verbose(scalar(@jobs), " jobs\n");
187 sleep(5);
188 }
189 }
190
191 MAIN: {
192 my $jobs;
193 my $help;
194
195 $jobs = 4;
196 GetOptions(
197 'jobs|j=i' => \$jobs,
198 'verbose|v' => \$verbose,
199 'help|h|?' => \$help,
200 ) or pod2usage();
201 $help and pod2usage(-exitstatus=>0, -verbose=>1);
202
203 flacloop($jobs);
204 }
205
206 \f
207 __END__
208
209 =head1 DESCRIPTION
210
211 B<fa-flacd> and B<fa-rip> together comprise B<flac-archive>, a system
212 for archiving audio CDs to single FLAC files. B<fa-flacd> is the guts
213 of the system. It runs in the directory where the audio archives are
214 stored, scanning for new ripped CDs to encode and rename; it never
215 exits. B<fa-rip> generates the inputs for B<fa-flacd>: the ripped WAV
216 file, Vorbis tags, and a cuesheet.
217
218 Both programs expect to be run from the same directory. They use that
219 directory to manage directories named by artist. Intermediate files
220 are written to temporary directories here. B<fa-flacd> processes the
221 temporary directories into per-album files in the artist directories.
222
223 Every 5 seconds, B<fa-flacd> scans its current directory for
224 directories with a file called "tags" and creates a processing job for
225 each one. The number of jobs B<fa-flacd> attempts to run is
226 controlled by the B<-j> option and defaults to 4. B<fa-flacd> will
227 print diagnostic output when the B<-v> option is given.
228
229 A processing job first renames the directory's "tags" file to
230 "using-tags" so that B<ra-flacd> will not try to start another job for
231 this directory. This file is left as is when an error is encountered,
232 so a new job will not be started until the user corrects the error
233 condition and renames "using-tags" back to "tags". Next, it encodes
234 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
235 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
236 the "log" file. Finally, B<fa-flacd> moves the "cue" and "log" files
237 to the artist directory (named by album) and removes the temporary
238 directory.
239
240 =head1 OPTIONS
241
242 =over 4
243
244 =item B<-j> [B<--jobs>] I<jobs>
245
246 Run up to I<jobs> jobs instead of the default 4.
247
248 =item B<-v> [B<--verbose>]
249
250 Print diagnostic information.
251
252 =back
253
254 =head1 AUTHORS
255
256 Written by Eric Gillespie <epg@pretzelnet.org>.
257
258 flac-archive is free software; you may redistribute it and/or modify
259 it under the same terms as Perl itself.
260
261 =cut
262
263 # Local variables:
264 # cperl-indent-level: 4
265 # perl-indent-level: 4
266 # indent-tabs-mode: nil
267 # End:
268
269 # vi: set tabstop=4 expandtab: