]> diplodocus.org Git - flac-archive/blob - fa-flacd
Make Getopt::Long behave sanely, as i do elsewhere.
[flac-archive] / fa-flacd
1 #! /usr/bin/env perl
2
3 # $Id$
4
5 =head1 NAME
6
7 B<fa-flacd> - archive CDs to single FLAC files
8
9 =head1 SYNOPSIS
10
11 B<fa-flacd> [B<-j> I<jobs>] [B<-v>]
12
13 =cut
14
15 use strict;
16 use warnings;
17
18 use File::Basename;
19 use Getopt::Long qw(:config gnu_getopt no_ignore_case);
20 use POSIX ':sys_wait_h';
21 use Pod::Usage;
22
23 my $verbose;
24 my @jobs;
25 my @finished;
26
27 sub verbose {
28 $verbose and print(STDERR $_) for @_;
29 }
30
31 # Return the ARTIST, ALBUM, and DATE followed by a list of all the
32 # lines in the file FN.
33 sub get_tags {
34 my $fn = shift;
35 my $tag;
36 my $value;
37 my $artist;
38 my $album;
39 my @tags;
40
41 verbose("Opening tags file $fn\n");
42 open(TAGS, $fn) or die("open($fn): $!");
43 while (<TAGS>) {
44 chomp;
45 push(@tags, $_);
46
47 ($tag, $value) = split(/=/, $_, 2);
48
49 if (/^ARTIST=/) {
50 $artist = $value;
51 verbose("ARTIST $artist from $fn\n");
52 } elsif (/^ALBUM=/) {
53 $album = $value;
54 verbose("ALBUM $album from $fn\n");
55 }
56 }
57 close(TAGS) or die("close($fn): $!");
58
59 return ($artist, $album, @tags);
60 }
61
62 # Process the fa-rip output in the directory DIR.
63 sub flac {
64 my $dir = shift;
65 my $artist;
66 my $album;
67 my @tags;
68 my $outfile;
69 my $status;
70
71 verbose("Renaming $dir/tags\n");
72 rename("$dir/tags", "$dir/using-tags")
73 or die("rename($dir/tags, $dir/using-tags): $!");
74
75 ($artist, $album, @tags) = get_tags("$dir/using-tags");
76
77 verbose("mkdir($artist)\n");
78 -d $artist or mkdir($artist) or die("mkdir($artist): $!");
79
80 verbose("chdir($dir)\n");
81 chdir($dir) or die("chdir($dir): $!");
82
83 $outfile = "$album";
84 $outfile =~ s/\//_/g;
85
86 verbose("Running flac\n");
87 $status = system('flac', '-o', "../$artist/$outfile.flac-tmp",
88 '--delete-input-file', '-V', '--cuesheet',
89 'cue', '--no-padding', '--best',
90 map({ ('-T', $_) } @tags),
91 'wav');
92 if (WIFEXITED($status) and ($status = WEXITSTATUS($status)) != 0) {
93 die("flac: $status");
94 } elsif (WIFSIGNALED($status)) {
95 die("flac killed with signal ", WTERMSIG($status));
96 } elsif (WIFSTOPPED($status)) {
97 die("flac stopped with signal ", WSTOPSIG($status));
98 }
99
100 verbose("Cleaning up $dir\n");
101 unlink('using-tags') or die("unlink(using-tags): $!");
102 unlink('cue') or die("unlink(cue): $!");
103 rename('log', "../$artist/$outfile.log")
104 or die("rename(log, ../$artist/$outfile.log): $!");
105 chdir('..') or die("chdir(..): $!");
106 rmdir($dir) or die("rmdir($dir): $!");
107
108 rename("$artist/$outfile.flac-tmp", "$artist/$outfile.flac")
109 or die("rename($artist/$outfile.flac-tmp, $artist/$outfile.flac): $!");
110
111 return 0;
112 }
113
114 sub reaper {
115 my $pid;
116
117 while (($pid = waitpid(-1, WNOHANG)) > 0) {
118 push(@finished, [$pid, $?]);
119 }
120
121 $SIG{CHLD} = \&reaper;
122 }
123
124 sub newjob {
125 my $dir = shift;
126 my $pid;
127
128 $pid = fork();
129 if (not defined($pid)) {
130 die("fork: $!");
131 } elsif ($pid == 0) {
132 $SIG{CHLD} = 'IGNORE';
133 open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!");
134 exit(flac($dir));
135 }
136
137 verbose("new job $pid for $dir\n");
138 return $pid;
139 }
140
141 sub deljob {
142 my $i = shift;
143 my $j;
144 my $pid;
145 my $status;
146
147 $pid = $finished[$i][0];
148 $status = $finished[$i][1];
149
150 verbose("$pid finished (");
151 if (WIFEXITED($status)) {
152 verbose('exited ', WEXITSTATUS($status));
153 } elsif (WIFSIGNALED($status)) {
154 verbose('signalled ', WTERMSIG($status));
155 } elsif (WIFSTOPPED($status)) {
156 verbose('stopped ', WSTOPSIG($status));
157 }
158 verbose(")\n");
159
160 for ($j = 0; $j <= $#jobs; $j++) {
161 $pid == $jobs[$j] and splice(@jobs, $j, 1) and last;
162 }
163
164 splice(@finished, $i, 1);
165 }
166
167 sub flacloop {
168 my $MAXJOBS = shift;
169 my $i;
170 my $j;
171
172
173 $SIG{CHLD} = \&reaper;
174 while (1) {
175 if (scalar(@jobs) <= $MAXJOBS) {
176 foreach $i (glob('*/tags')) {
177 push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last;
178 }
179 }
180
181 for ($i = 0; $i <= $#finished; $i++) {
182 deljob($i);
183 }
184
185 verbose(scalar(@jobs), " jobs\n");
186 sleep(5);
187 }
188 }
189
190 MAIN: {
191 my $jobs;
192 my $help;
193
194 $jobs = 4;
195 GetOptions(
196 'jobs|j=i' => \$jobs,
197 'verbose|v' => \$verbose,
198 'help|h|?' => \$help,
199 ) or pod2usage();
200 $help and pod2usage(-exitstatus=>0, -verbose=>1);
201
202 flacloop($jobs);
203 }
204
205 \f
206 __END__
207
208 =head1 DESCRIPTION
209
210 B<fa-flacd> and B<fa-rip> together comprise B<flac-archive>, a system
211 for archiving audio CDs to single FLAC files. B<fa-flacd> is the guts
212 of the system. It runs in the directory where the audio archives are
213 stored, scanning for new ripped CDs to encode and rename; it never
214 exits. B<fa-rip> generates the inputs for B<fa-flacd>: the ripped WAV
215 file, Vorbis tags, and a cuesheet.
216
217 Both programs expect to be run from the same directory. They use that
218 directory to manage directories named by artist. Intermediate files
219 are written to temporary directories here. B<fa-flacd> processes the
220 temporary directories into per-album files in the artist directories.
221
222 Every 5 seconds, B<fa-flacd> scans its current directory for
223 directories with a file called "tags" and creates a processing job for
224 each one. The number of jobs B<fa-flacd> attempts to run is
225 controlled by the B<-j> option and defaults to 4. B<fa-flacd> will
226 print diagnostic output when the B<-v> option is given.
227
228 A processing job first renames the directory's "tags" file to
229 "using-tags" so that B<ra-flacd> will not try to start another job for
230 this directory. This file is left as is when an error is encountered,
231 so a new job will not be started until the user corrects the error
232 condition and renames "using-tags" back to "tags". Next, it encodes
233 the "wav" file to a FLAC file, using the "cue" file for the cuesheet
234 and "using-tags" for Vorbis tags. Any diagnostic output is saved in
235 the "log" file. Finally, B<fa-flacd> moves the "cue" and "log" files
236 to the artist directory (named by album) and removes the temporary
237 directory.
238
239 =head1 OPTIONS
240
241 =over 4
242
243 =item B<-j> [B<--jobs>] I<jobs>
244
245 Run up to I<jobs> jobs instead of the default 4.
246
247 =item B<-v> [B<--verbose>]
248
249 Print diagnostic information.
250
251 =back
252
253 =head1 AUTHORS
254
255 Written by Eric Gillespie <epg@pretzelnet.org>.
256
257 flac-archive is free software; you may redistribute it and/or modify
258 it under the same terms as Perl itself.
259
260 =cut
261
262 # Local variables:
263 # cperl-indent-level: 4
264 # perl-indent-level: 4
265 # indent-tabs-mode: nil
266 # End:
267
268 # vi: set tabstop=4 expandtab: