]> diplodocus.org Git - flac-archive/blob - fa-flacd
Collapse fa-rip and fa-tags into a single Perl program, fa-rip.
[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<-jv>]
12
13 =cut
14
15 use strict;
16 use warnings;
17
18 use File::Basename;
19 use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1;
20 use POSIX ':sys_wait_h';
21
22 our $VERSION = 1;
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 %opts;
193
194 $opts{'j'} = 4;
195 $opts{'v'} = 0;
196 if (not getopts('j:v', \%opts)) {
197 print(STDERR "usage: flacd [-jN -v]\n");
198 exit(2);
199 }
200
201 $verbose = $opts{'v'};
202
203 flacloop($opts{'j'});
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 AUTHORS
241
242 Written by Eric Gillespie <epg@pretzelnet.org>.
243
244 flac-archive is free software; you may redistribute it and/or modify
245 it under the same terms as Perl itself.
246
247 =cut
248
249 # Local variables:
250 # cperl-indent-level: 4
251 # perl-indent-level: 4
252 # indent-tabs-mode: nil
253 # End:
254
255 # vi: set tabstop=4 expandtab: