From: epg <> Date: Fri, 2 Apr 2004 01:53:41 +0000 (+0000) Subject: Here's flac-archive, version 1. X-Git-Url: https://diplodocus.org/git/flac-archive/commitdiff_plain/890a5921fe12b21fbc65d88a0e59080dc8cc9a5b Here's flac-archive, version 1. --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..256aa06 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +# $Id$ + +prefix=/usr/local +exec_prefix=${prefix} +bindir=${exec_prefix}/bin +datadir=${prefix}/share +mandir=${datadir}/man +man1dir=${mandir}/man1 +catdir=${mandir} +cat1dir=${catdir}/cat1 + +INSTALL= install +INSTALL_DATA= ${INSTALL} -m 444 +INSTALL_DIRECTORY=${INSTALL} -d -m 755 +INSTALL_SCRIPT= ${INSTALL} -m 555 +POD2MAN= pod2man -c '' -r '' +ROFF= nroff -Tascii -mandoc + +SCRIPTS=\ + fa-flacd \ + fa-rip \ + fa-tags + +MAN=\ + fa-flacd.1 fa-flacd.0 + +all: ${SCRIPTS} ${MAN} + +fa-flacd.1: fa-flacd + ${POD2MAN} fa-flacd > $@ + +.SUFFIXES: .1 .0 +.1.0: + ${ROFF} $< > $@ + +clean: + rm -f ${MAN} + +install: ${SCRIPTS} ${bindir} ${man1dir} ${cat1dir} ${SCRIPTS} ${MAN} + ${INSTALL_SCRIPT} ${SCRIPTS} ${bindir} + ${INSTALL_DATA} fa-flacd.1 ${man1dir} + ln -sf fa-flacd.1 ${man1dir}/fa-rip.1 + ln -sf fa-flacd.1 ${man1dir}/fa-tags.1 + ${INSTALL_DATA} fa-flacd.0 ${cat1dir} + ln -sf fa-flacd.0 ${cat1dir}/fa-rip.0 + ln -sf fa-flacd.0 ${cat1dir}/fa-tags.0 + +${bindir}: + ${INSTALL_DIRECTORY} $@ + +${man1dir}: + ${INSTALL_DIRECTORY} $@ + +${cat1dir}: + ${INSTALL_DIRECTORY} $@ + +uninstall: + cd ${bindir} && rm -f ${SCRIPTS} + cd ${man1dir} && rm -f ${MAN} fa-rip.1 fa-tags.1 + cd ${cat1dir} && rm -f ${MAN} fa-rip.0 fa-tags.0 + +.PHONY: all clean install uninstall diff --git a/README b/README new file mode 100644 index 0000000..b3e01e6 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +$Id$ + +Run perldoc fa-flacd for more information. + +flac-archive requires Perl >= 5.6, abcde and cd-discid, cdparanoia, +and cdrdao. diff --git a/fa-flacd b/fa-flacd new file mode 100755 index 0000000..72c8923 --- /dev/null +++ b/fa-flacd @@ -0,0 +1,316 @@ +#! /usr/bin/env perl + +# $Id$ + +=head1 NAME + +B, B, B - archive CDs to single FLAC files + +=head1 SYNOPSIS + +B [B<-jv>] + +B + +B ID TRACKCOUNT OFFSET [OFFSET ...] LENGTH + +=cut + +use strict; +use warnings; + +use File::Basename; +use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; +use POSIX ':sys_wait_h'; + +our $VERSION = 1; + +my $verbose; +my @jobs; +my @finished; + +sub verbose { + $verbose and map({ print(STDERR $_) } @_); +} + +# Return the ARTIST, ALBUM, and DATE followed by a list of all the +# lines in the file FN. +sub get_tags { + my $fn = shift; + my $tag; + my $value; + my $artist; + my $album; + my $date; + my @tags; + + verbose("Opening tags file $fn\n"); + open(TAGS, $fn) or die("open($fn): $!"); + while () { + chomp; + push(@tags, $_); + + ($tag, $value) = split(/=/, $_, 2); + + if (/^ARTIST=/) { + $artist = $value; + verbose("ARTIST $artist from $fn\n"); + } elsif (/^ALBUM=/) { + $album = $value; + verbose("ALBUM $album from $fn\n"); + } elsif (/^DATE=/) { + $date = $value; + verbose("DATE $date from $fn\n"); + } + } + close(TAGS); + + return ($artist, $album, $date, @tags); +} + +# Process the fa-rip output in the directory DIR. +sub flac { + my $dir = shift; + my $artist; + my $album; + my $date; + my @tags; + my $status; + + verbose("Renaming $dir/tags\n"); + rename("$dir/tags", "$dir/using-tags") + or die("rename($dir/tags, $dir/using-tags): $!"); + + ($artist, $album, $date, @tags) = get_tags("$dir/using-tags"); + + verbose("mkdir($artist)\n"); + -d $artist or mkdir($artist) or die("mkdir($artist): $!"); + + verbose("chdir($dir)\n"); + chdir($dir) or die("chdir($dir): $!"); + + verbose("Running flac\n"); + $status = system('flac', '-o', "../$artist/$album.flac-tmp", + '--delete-input-file', '-V', '--cuesheet', + 'cue', '--no-padding', '--best', + map({ ('-T', $_) } @tags), + 'wav'); + if (WIFEXITED($status) and ($status = WEXITSTATUS($status)) != 0) { + die("flac: $status"); + } elsif (WIFSIGNALED($status)) { + die("flac killed with signal ", WTERMSIG($status)); + } elsif (WIFSTOPPED($status)) { + die("flac stopped with signal ", WSTOPSIG($status)); + } + + verbose("Cleaning up $dir\n"); + unlink('using-tags') or die("unlink(using-tags): $!"); + unlink('cue') or die("unlink(cue): $!"); + rename('toc', "../$artist/$album.toc") + or die("rename(toc, ../$artist/$album.toc): $!"); + rename('log', "../$artist/$album.log") + or die("rename(log, ../$artist/$album.log): $!"); + chdir('..') or die("chdir(..): $!"); + rmdir($dir) or die("rmdir($dir): $!"); + + rename("$artist/$album.flac-tmp", "$artist/$album.flac") + or die("rename($artist/$album.flac-tmp, $artist/$album.flac): $!"); +} + +sub reaper { + my $pid; + + while (($pid = waitpid(0, WNOHANG)) > 0) { + push(@finished, [$pid, $?]); + } + + $SIG{CHLD} = \&reaper; +} + +sub newjob { + my $dir = shift; + my $pid; + + $pid = fork(); + if ($pid == -1) { + die("fork: $!"); + } elsif ($pid == 0) { + $SIG{CHLD} = 'IGNORE'; + open(STDERR, ">$dir/log") or die("open(STDERR, >$dir/log): $!"); + flac($dir); + exit(0); + } + + verbose("new job $pid for $dir\n"); + return $pid; +} + +sub deljob { + my $i = shift; + my $j; + my $pid; + my $status; + + $pid = $finished[$i][0]; + $status = WEXITSTATUS($finished[$i][1]); + + verbose("$pid finished ("); + if (WIFEXITED($status)) { + verbose('exited ', WEXITSTATUS($status)); + } elsif (WIFSIGNALED($status)) { + verbose('signalled ', WTERMSIG($status)); + } elsif (WIFSTOPPED($status)) { + verbose('stopped ', WSTOPSIG($status)); + } + verbose(")\n"); + + for ($j = 0; $j <= $#jobs; $j++) { + $pid == $jobs[$j] and splice(@jobs, $j, 1) and last; + } + + splice(@finished, $i, 1); +} + +sub flacloop { + my $MAXJOBS = shift; + my $i; + my $j; + + + $SIG{CHLD} = \&reaper; + while (1) { + if (scalar(@jobs) <= $MAXJOBS) { + foreach $i (glob('*/tags')) { + push(@jobs, newjob(dirname($i))) <= $MAXJOBS or last; + } + } + + for ($i = 0; $i <= $#finished; $i++) { + deljob($i); + } + + verbose(scalar(@jobs), " jobs\n"); + sleep(5); + } +} + +MAIN: { + my %opts; + + $opts{'j'} = 4; + $opts{'v'} = 0; + if (not getopts('j:v', \%opts)) { + print("usage: flacd [-jN -v]\n"); + exit(2); + } + + $verbose = $opts{'v'}; + + flacloop($opts{'j'}); +} + + +__END__ + +=head1 DESCRIPTION + +B, B, and B together comprise +B, a system for archiving audio CDs to single FLAC +files. B is the guts of the system. It runs in the +directory where the audio archives are stored, scanning for new CDs to +encode and rename; it never exits. B generates the inputs for +B: the ripped WAV file, Vorbis tags, and a cuesheet. +B is not meant to be run directly; B uses it to +generate the candidate Vorbis tags. + +All three programs expect to be run from the same directory. They use +that directory to manage directories named by artist and by disc ID. +Intermediate files are written to the disc ID directory. B +processes the disc ID directories into per-album files in the artist +directories. + +=head2 FA-FLACD + +B does not exit; it runs until the user kills it. Every 5 +seconds it scans its current directory for directories with a file +called "tags" and creates a processing job for each one. The number +of jobs B attempts to run is controlled by the B<-j> option +and defaults to 4. B will print diagnostic output when the +B<-v> option is given. + +A processing job first renames the directory's "tags" file to +"using-tags" so that B will not try to start another job for +this directory. This file is left as is when an error is encountered, +so a new job will not be started until the user corrects the error +condition and renames "using-tags" back to "tags". Next, it encodes +the "wav" file to a FLAC file, using the "cue" file for the cuesheet +and "using-tags" for Vorbis tags. Any diagnostic output is saved in +the "log" file. Finally, the "cue" and "log" files are moved to the +artist directory (and named by album) and the ID directory is removed. + +=head2 FA-RIP + +B uses L to retrieve the disc ID and track +information. It creates a directory named by ID for storage of its +intermediate files. It passes the L output as +command-line arguments to B in the background. It then uses +L to create the "cue" file in the background. Finally, it +execs > to rip the CD to the "wav" file. + +In order for this CD to be processed by B, the user must +create a "tags" file. This is usually done by renaming one of the +candidate-tags files and deleting the others. + +=head2 FA-TAGS + +B uses L (from the B package) to +populate candidate-tags files. These are numbered in the order of +entries read from CDDB, e.g. candidate-tags-1, candidate-tags-2, etc. +B also creates candidate-tags-0, which has the correct fields +for this CD (including correct number of TITLE= lines), but with all +fields blank. + +B expects the output of L as command-line +arguments. That is, the disc ID, number of tracks, list of track +offsets, and total length of the CD in seconds. + +=head1 ENVIRONMENT + +=over 4 + +=item CDDBURL + +B uses this to retrieve candidate Vorbis tags. Defaults to +"http://freedb.freedb.org/~cddb/cddb.cgi". + +=item CDDEV + +B uses this to rip audio and save the cuesheet for a CD. It +makes some effort to check some common device names for FreeBSD, +Linux, and NetBSD by default. + +=back + +=head1 AUTHORS + +Written by Eric Gillespie . B contains +code from B, which bears the following notice: + +# Copyright (c) 1998-2001 Robert Woodcock +# Copyright (c) 2003-2004 Jesus Climent +# This code is hereby licensed for public consumption under either the +# GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice. + +B is hereby licensed for public consumption under either +the GNU GPL v2 or greater, or Larry Wall's Artistic license - your +choice. + +=cut + +# Local variables: +# cperl-indent-level: 4 +# perl-indent-level: 4 +# indent-tabs-mode: nil +# End: + +# vi: set tabstop=4 expandtab: diff --git a/fa-rip b/fa-rip new file mode 100755 index 0000000..55b2fb2 --- /dev/null +++ b/fa-rip @@ -0,0 +1,36 @@ +#! /usr/bin/env zsh + +# $Id$ + +set -e +setopt NULL_GLOB + +get_cddev () { + local raw + raw=$(sysctl -n kern.rawpartition > /dev/null | awk '{printf "%c",97+$0}') + + for CDDEV in /dev/{cdroms/cdrom*,cdrom*,rcd*${raw},{a,}cd*c}; do + [[ -e ${CDDEV} ]] && return 0 + done + + return 1 +} + +if [[ -z ${CDDEV} ]]; then + if ! get_cddev; then + echo 'CDDEV environment variable not set, defaults did not work' + exit 2 + fi +fi + +discid=($(cd-discid ${CDDEV})) + +[[ -d $discid[1] ]] || mkdir $discid[1] +cd $discid[1] + +eval fa-tags $discid & + +cdrdao read-toc --device ${CDDEV} --driver generic-mmc toc +toc2cue toc cue & + +exec cdparanoia -d ${CDDEV} 1-$discid[2] wav diff --git a/fa-tags b/fa-tags new file mode 100755 index 0000000..34eafb8 --- /dev/null +++ b/fa-tags @@ -0,0 +1,103 @@ +#! /usr/bin/env zsh + +# $Id$ + +# Copyright (c) 1998-2001 Robert Woodcock +# Copyright (c) 2003-2004 Jesus Climent +# Copyright (c) 2004 Eric Gillespie +# This code is hereby licensed for public consumption under either the +# GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice. + +set -e + +if [[ -z ${CDDBURL} ]]; then + CDDBURL="http://freedb.freedb.org/~cddb/cddb.cgi" +fi + +get_cddb_protocol () { + for CDDBPROTO in 5 4 3; do + if [[ \ + $(cddb-tool stat $CDDBURL $USER $HOST $CDDBPROTO \ + | sed 's/^\([0-9][0-9]*\).*/\1/;q') = '210' ]]; then + return 0 + fi + done + + echo "${CDDBURL} does not work" + exit 2 +} + +handle_read=$(< candidate-tags-$i +# XXX The awk script has at least one problem; since we can't split to +# exactly two components, we break on track names with equal signs. +# Use Perl instead: +# cddb-tool read $CDDBURL $CDDBPROTO $USER $HOST $F[1] $F[2] \ +# | perl -lne $handle_read > candidate-tags-$i + i=$(( $i + 1 )) + done +} + +get_cddb_protocol +(cddb-tool query $CDDBURL $CDDBPROTO $USER $HOST "$@" | handle_queries) & + +cat > candidate-tags-0 <