3 ## Copyright (C) 2006 - 2008 Steve Youngs
5 ## Author: Steve Youngs <steve@sxemacs.org>
6 ## Maintainer: Steve Youngs <steve@sxemacs.org>
7 ## Created: <2006-08-08>
8 ## Time-stamp: <Thursday Jun 12, 2008 12:29:31 steve>
10 ## Redistribution and use in source and binary forms, with or without
11 ## modification, are permitted provided that the following conditions
14 ## 1. Redistributions of source code must retain the above copyright
15 ## notice, this list of conditions and the following disclaimer.
17 ## 2. Redistributions in binary form must reproduce the above copyright
18 ## notice, this list of conditions and the following disclaimer in the
19 ## documentation and/or other materials provided with the distribution.
21 ## 3. Neither the name of the author nor the names of any contributors
22 ## may be used to endorse or promote products derived from this
23 ## software without specific prior written permission.
25 ## THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
26 ## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27 ## WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28 ## DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 ## FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 ## SUBSTITUTE GOODS OR SERVICES# LOSS OF USE, DATA, OR PROFITS# OR
32 ## BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33 ## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
34 ## OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
35 ## IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ## Ease the process of ripping audio CDs to .mp3 or .ogg. See
40 ## help (zcdrip -h) for full details.
45 ## o Partial album ripping. Specify track numbers or a range of
46 ## track numbers to only rip/encode those tracks.
48 ## o Ripping from music DVDs
53 ## Bugs? What bugs? Seriously though, if you find any, let me
58 [[ -n $DEBUG ]] && set -x; zdebug=${TMPPREFIX}/zdebug
65 COPYRIGHT="Copyright (C) 2006 - 2008 Steve Youngs <steve@sxemacs.org>"
67 version_str="${ourname}: ${VERSION}
70 _version () { echo $version_str }
75 # If you use eshell you will have a problem displaying this help.
76 # This is the only part of the script that eshell has a problem
77 # with, but if you really want to be able to view this help in
78 # eshell, create a symlink and call it `ecdrip'.
80 [[ ${LINES} -lt 70 && $TERM != dumb ]] && _doc=${PAGER:-less} || _doc=cat
81 [[ $ourname == ecdrip ]] && _doc=cat
92 [ -moi ] [ --mp3 | --ogg ] [ -r FILE ] [ --interactive | --rcfile=FILE ]
93 [ -a ALBUM ] [ --album=ALBUM ] [ -s ARTIST ] [ --sameartist=ARTIST ]
94 [ -t ENCODING_TYPE ] [ --type=ENCODING_TYPE ] [ -f | --flac_copy ]
95 [ -e FILE | --edit_rcfile=FILE ] [ -O | --online ] [ -d DIR | --dir=DIR ]
104 $ourname is a "frontend" to the process of ripping audio (cdda) CDs
105 to a hard disk and encoding the audio tracks to either MP3 format or
108 File names are auto-generated from track titles, with illegal
109 characters removed and spaces converted to underscores.
111 Track title, artist, and album names are added to the ID3 tags or
112 Vorbis comments header.
119 Specify the ALBUM name. Argument ALBUM is _NOT_ optional.
120 Whitespace and punctuation is permitted, but you must protect
121 them from the shell. If this option is omitted, the album
122 name will be prompted for.
126 Save encoded files to the directory, DIR. If flac encoding
127 is also requested, see '--flac_copy' option below, the flac
128 files will be saved to DIR/flac. If this option is omitted,
129 it defaults to 'PWD/ARTIST/ALBUM' the album name is first
130 converted to a sane filename.
134 Edit/create rcfile, FILE, in an editor. If the environment
135 variable EDITOR is not set, vi is used. This implicitly sets
136 rcfile, see '--rcfile' option below.
140 In addition to encoding the CD audio tracks to MP3 or Ogg
141 Vorbis, create a FLAC audio file for archival purposes.
145 Display this help text and exit.
149 Prompt for track titles and artist names.
153 Specify MP3 encoding.
157 Specify Ogg Vorbis encoding.
161 Attempt to grab the track info from freedb.freedb.org.
165 Get track titles and artist names from FILE. Argument FILE
166 is _NOT_ optional. The format of FILE is:
168 Artist Name|Track Title
170 One "record" per line. Whitespace and punctuation is permitted,
171 blank lines are not. And there is no "comment" lines.
175 All tracks on the album are by the same ARTIST. Argument
176 ARTIST is _NOT_ optional.
180 This is an alternative to using the -m or -o options.
181 Use ENCODING_TYPE to encode the audio. Valid values for
182 ENCODING_TYPE are "mp3", "ogg".
186 Display version info and exit.
191 zsh http://www.zsh.org/ (mandatory)
193 cdparanoia http://www.xiph.org/paranoia/
194 For ripping the audio tracks from CD. (mandatory)
196 lame http://www.mp3dev.org/
197 For encoding to MP3 format. (optional if encoding to
200 oggenc http://www.xiph.org/
201 For encoding to Ogg Vorbis format (optional if encoding
204 flac http://flac.sourceforge.net/
205 For additionally storing audio tracks in FLAC format.
211 ${TMPPREFIX}/$ourname.log
212 A log of the incoming TCP traffic from the CDDB server.
213 This will be kept if debugging is turned on, otherwise
214 it is deleted upon successful exit.
217 Some extra debugging info is logged here.
220 Where $ourname gets track info from. It can be
221 specified on the command line (see -r above) when
222 not using an online CDDB server, otherwise it is
223 created automatically by $ourname.
225 Environment Variables:
226 ---------------------
228 Some aspects of $ourname can be tweaked by setting the following
229 environment variables.
231 DEBUG Set to a non-nil string to turn on debugging.
233 zcddb CDDB server to use (default: freedb.freedb.org).
235 zuser Name used to log into CDDB server (default: $USER).
237 zhost Host used to log into CDDB server (default: $HOST).
239 zedit Set to a non-nil string to force $ourname to invoke an
240 editor to create a rcfile.
242 zcomment Extra comment field/value to add to ID3 tags/Vorbis
243 comments (default: "Encoded with $ourname").
245 Occasionally, the CDDB entries for a given CD are either wrong or
246 have not been formatted correctly. In an attempt to compensate for
247 these types of quirks you can set the following environment variables
248 with $ourname invocation to override the madness...
250 ARTIST Override the artist field.
252 ALBUM Override the album name.
256 GENRE Override genre.
261 Probably the most common usage would be to encode to Ogg/Vorbis
262 format using track info obtained from freedb.freedb.org. Like
267 Encode to MP3 format, prompting for album name, year, genre, artist,
272 Encode to Ogg/Vorbis, set album name on the command line, taking
273 track title info from a file, and make a FLAC format backup copy of
276 \$ $ourname -a "The Album Name" -o -f -r /path/to/rcfile
278 Encode to Ogg/Vorbis, taking track info from a online CDDB server,
279 and override the genre for the album...
281 \$ GENRE=alternate $ourname -oO
286 $ourname will return the following status codes on exit:
288 0 -- Clean exit (no errors)
289 2 -- Command line option error
290 3 -- Missing external command
296 If you call $ourname with the environment variable DEBUG set to a
297 non nil value, for example: DEBUG=yes $ourname <opts>, $ourname will
298 be called with "set -x".
300 "set -x" spams your stdout with a _LOT_ of messages, it would most
301 likely pay you to redirect stdout/stderr to a file.
308 # Convert song title to something resembling a sane filename... no
309 # whitespace, no punctuation, etc. Also adds .ogg/.mp3 extension.
312 local psub="[[:cntrl:],\*\?\`';/\\\\\"]"
314 local wsub="[[:space:][:blank:]]"
319 for (( i=1; i<=$tracks; ++i )); do
322 tf=${tf//${~psub}/$prep}
324 tf=${tf//${~wsub}/$wrep}
325 filename[$i]=$tf.$prepargs[1]
334 print invalid args >&2
340 tcp_send "cddb hello $args"
341 tcp_expect -T 10 -s $ourname "*Hello and welcome*"
342 if [[ $? -eq 2 ]]; then
343 print Timeout waiting for cddb server >&2
348 tcp_expect -T 10 -s $ourname "*CDDB protocol*"
349 if [[ $? -eq 2 ]]; then
350 print Timeout waiting for cddb server >&2
359 art=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f1|sed '1!d')
360 art=${art/%[[:space:]]/}
367 if [[ "$art" != "Various Artists" && "$art" != "Various" ]]; then
371 # Album, year, genre, number or tracks
372 album=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f2)
373 album=${album/#[[:space:]]/}
374 album=${album//[[:cntrl:]]/}
375 year=$(grep DYEAR $zlog|cut -d= -f2)
376 year=${year//[[:cntrl:]]/}
377 genre=$(grep DGENRE $zlog|cut -d= -f2)
378 genre=${genre//[[:cntrl:]]/}
381 # Possibly override album, year, genre
382 album=${ALBUM:-$album}
384 genre=${GENRE:-$genre}
386 # print what we got when we're debugging
387 if [[ -n $DEBUG ]]; then
388 printf "Artist: %s\nAlbum: %s\nYear: %s\nGenre: %s\nNum Tracks: %s\n" \
389 $art $album $year $genre $tracks > $zdebug
390 printf "Various: %s\n" $various >> $zdebug
391 echo --- Titles --- >> $zdebug
392 grep TTITLE $zlog >> $zdebug
396 if [[ $various == yes || -n $(grep TTITLE $zlog|grep $art) ]]; then
397 grep TTITLE $zlog|cut -d= -f2|sed 's@ [-/] @|@' > $rcfile
399 grep TTITLE $zlog|cut -d= -f2 > $rcfile
400 sed -i "s@^@${art}|@g" $rcfile
408 print invalid args >&2
412 tcp_send "cddb read $1 $2"
413 tcp_expect -T 25 -s $ourname "*PLAYORDER*"
414 if [[ $? -eq 2 ]]; then
415 print Timeout waiting for cddb server >&2
426 print invalid args >&2
430 tcp_send "cddb query $zid"
431 tcp_expect -T 10 -p idx -s $ourname "*No match for disc ID*" \
432 "<-\[$ourname\] 200*" "<-\[$ourname\] .*"
433 if [[ $? -eq 2 ]]; then
434 print Timeout waiting for cddb server >&2
439 print No entry listed for disc: $zid[1] >&2
440 print You will have to do this disc manually >&2
445 category=${category/*200 /}
446 category=${category/ $zid[1]*/}
447 _freedb_read $category $zid[1]
450 echo Multiple entries found...
452 tmulti=(${(ps:\r:)${tcp_expect_lines}})
453 [[ $tmulti[1] == *inexact* ]] && exact=no
454 tmulti[1]=${tmulti[1]:#${tmulti[1]}}
456 for (( i=1; i<${#multi}; ++i )); do
457 multi[$i]=${multi[$i]/<-\[$ourname\] /}
458 printf "\t[%s]%s\n" $i $multi[$i]
461 echo -n "Which CDDB entry (q to quit): "
464 ([qQ]) do_offline; exit 0 ;;
466 print Invalid response >&2
471 category=$multi[$choice]
472 if [[ $exact == no ]]; then
473 tcategory=(${(ps: :)category})
474 _freedb_read $tcategory[1,2]
476 category=${category/ $zid[1]*/}
477 _freedb_read $category $zid[1]
481 print Unknown error >&2
487 (*) print Unknown error >&2 ;;
495 [[ -n $DEBUG ]] || TCP_SILENT=nodebugging
497 zcddb=${zcddb:-freedb.freedb.org}
499 zuser=${zuser:-$USER}
500 zhost=${zhost:-$HOST}
502 zlog=${TMPPREFIX}/zcdrip.log
504 # open the log and connect
506 tcp_open $zcddb $port $ourname
507 tcp_expect -T 10 -s $ourname "<-\[$ourname\] 201*ready*"
508 if [[ $? -eq 2 ]]; then
509 print Timeout waiting for cddb server >&2
512 _freedb_login $zuser $zhost $ourname $VERSION
515 # query cddb server to find out if the CD is listed
520 # Close tcp connection
527 # Tidy up if not debugging
530 rm -f $zlog $rcfile $zdebug
531 unset ARTIST ALBUM YEAR GENRE
534 # convert album/artist name into a sane filename for output dir
539 local psub="[[:cntrl:],\`';/\\\\\"]"
541 local wsub="[[:space:][:blank:]]"
546 talbum=${talbum//\\t/ }
547 talbum=${talbum//${~psub}/$prep}
548 talbum=${talbum//[ ]##/ }
549 talbum=${talbum//${~wsub}/$wrep}
552 tart=${tart//${~psub}/$prep}
553 tart=${tart//[ ]##/ }
554 tart=${tart//${~wsub}/$wrep}
556 # Various Artists is sometimes "Various" and other times "Various
557 # Artists", we'll call them all "Various_Artists".
558 if [[ "${tart}" == "Various" ]]; then
562 dir=$(pwd)/${tart}/${talbum}
563 [[ -d ${dir} ]] || mkdir -p ${dir}
564 [[ $keep_flac == yes ]] && fdir=${dir}/flac; mkdir -p ${fdir}
568 # Store song titles, artist, and album names.
574 [[ -d ${TMPPREFIX} ]] || mkdir -p ${TMPPREFIX}
576 if [[ $online == yes ]]; then
585 if [[ $zedit == yes && -n $rcfile ]]; then
588 The format of this file is...
589 Artist Name|Track Title
590 One "record" per line. Punctuation and whitespace is OK.
591 You MUST delete these instructions before saving.
596 if [[ -z $album ]]; then
597 echo -n "Please enter the name of this album: "
602 [[ -n $dir ]] || _album_to_dir
604 if [[ ! -d ${dir} ]]; then
606 if [[ $keep_flac == yes && ! -d ${fdir} ]]; then
611 if [[ -z $year ]]; then
612 echo -n "Please enter the year this album was released: "
617 if [[ -z $genre ]]; then
618 echo -n "Please enter the genre of this album: "
623 zcomment=${zcomment:-"Encoded with $ourname"}
625 if [[ $prepargs[2] == yes ]]; then
626 for (( i=1; i<=$tracks; ++i )); do
627 if [[ -n $art ]]; then
630 echo -n "Track $i artist: "
635 echo -n "Track $i title: "
643 while read art sg; do
646 sg=${sg/#[[:space:]]/}
647 sg=${sg//[[:cntrl:]]/}
653 fnamefix $prepargs[1]
654 wavfile=dumpaudio.wav
657 [[ -n $DEBUG ]] || cleanup
664 shift # pop off $1 (flacfile)
665 flac -8 --delete-input-file -o ${fdir}/${file} ${1}
666 shift # pop off next arg (wavfile)
667 for tag in title artist album year genre tracknumber comment; do
668 metaflac --set-tag="$tag=${1}" ${fdir}/${file}
679 title=$(metaflac --show-tag=title|cut -d= -f2)
680 artist=$(metaflac --show-tag=artist|cut -d= -f2)
681 album=$(metaflac --show-tag=album|cut -d= -f2)
682 year=$(metaflac --show-tag=year|cut -d= -f2)
683 genre=$(metaflac --show-tag=genre|cut -d= -f2)
684 track=$(metaflac --show-tag=tracknumber|cut -d= -f2)
685 comment=$(metaflac --show-tag=comment|cut -d= -f2)
687 flac -c -d $flac|lame -h --tt ${title} --ta ${artist} --tl ${album} \
688 --ty $year --tg $genre --tn $track --tc ${comment} - $mp3
694 # Work around lame being too anal about genre
695 lame --genre-list|grep ${genre} 1>/dev/null || genre=Other
697 for (( i=1; i<=$tracks; ++i )); do
698 echo MP3-Ripping: $song[$i], by: $artist[$i], to: $filename[$i]
699 cdparanoia -X $i $wavfile
700 if [[ -f $wavfile ]]; then
701 if [[ $keep_flac == yes ]]; then
702 flacfile=${filename[$i]/%mp3/flac}
703 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
704 $year $genre $i $zcomment
705 flactomp3 ${flacfile} ${dir}/$filename[$i]
707 lame -h --tt $song[$i] --ta $artist[$i] --tl $album \
708 --ty $year --tg $genre --tc $zcomment $wavfile \
709 --tn $i ${dir}/$filename[$i]
712 [[ -f $wavfile ]] && rm -f $wavfile
716 # Encode to OGG Vorbis
719 for (( i=1; i<=$tracks; ++i )); do
720 echo OGG-Ripping: $song[$i], by: $artist[$i], to: $filename[$i]
721 cdparanoia -X $i $wavfile
722 if [[ -f $wavfile ]]; then
723 if [[ $keep_flac == yes ]]; then
724 flacfile=${filename[$i]/%ogg/flac}
725 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
726 $year $genre $i $zcomment
727 oggenc -q3 -o ${dir}/$filename[$i] ${fdir}/$flacfile
729 oggenc -q3 -o ${dir}/$filename[$i] -t $song[$i] -a $artist[$i] \
730 -l $album -d $year -G $genre -c "comment=$zcomment" \
738 # Make sure we have everything we need.
739 # This might be a little over the top, but I don't see any point in
740 # checking for `oggenc' if you are encoding to MP3, likewise, there's
741 # no point in checking for `lame' if you are encoding to Ogg Vorbis.
744 reqs=(cdparanoia lame oggenc flac)
745 whence -w $reqs[1] 1>/dev/null
746 if [[ $? -gt 0 ]]; then
747 echo $ourname: error: Missing $reqs[1] >&2
752 whence -w $reqs[2] 1>/dev/null
753 if [[ $? -gt 0 ]]; then
754 echo $ourname: error: Missing $reqs[2] >&2
759 whence -w $reqs[3] 1>/dev/null
760 if [[ $? -gt 0 ]]; then
761 echo $ourname: error: Missing $reqs[3] >&2
766 if [[ $keep_flac == yes ]]; then
767 whence -w $reqs[4] 1>/dev/null
768 if [[ $? -gt 0 ]]; then
769 echo $ourname: error: Missing $reqs[4] >&2
773 if [[ $rv -eq 3 ]]; then
781 # Command line parsing
782 args=mofihVO-:r:a:s:t:e:d:
789 while getopts $args opts; do
793 (mp3) atype=mp3; (( ++flavour )) ;;
794 (ogg) atype=ogg; (( ++flavour )) ;;
796 rcfile=${OPTARG/edit_rcfile=/}
800 (flac_copy) keep_flac=yes ;;
801 (type?*) atype=${OPTARG/type=/}; (( ++flavour )) ;;
802 (interactive) interactive=yes; (( ++rc )) ;;
803 (online) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
804 (rcfile?*) rcfile=${OPTARG/rcfile=/}; (( ++rc )) ;;
805 (album?*) album=${OPTARG/album=/} ;;
806 (sameartist?*) art=${OPTARG/sameartist=/} ;;
807 (dir?*) dir=${OPTARG/dir=/} ;;
808 (version) _version; exit 0 ;;
809 (help) cmd=usage; rv=0 ;;
811 echo $ourname: error: bad option: --$OPTARG >&2
816 (i) interactive=yes; (( ++rc )) ;;
817 (O) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
818 (m) atype=mp3; (( ++flavour )) ;;
819 (o) atype=ogg; (( ++flavour )) ;;
820 (e) rcfile=$OPTARG; (( ++rc )); zedit=yes ;;
822 (r) rcfile=$OPTARG; (( ++rc )) ;;
825 (t) atype=$OPTARG; (( ++flavour )) ;;
827 (V) _version; exit 0 ;;
828 (h) cmd=usage; rv=0 ;;
832 shift $(( $OPTIND - 1 ))
834 if [[ $cmd != usage ]]; then
837 echo $ourname: error: no audio format specified >&2
838 if [[ $rc -ne 1 ]]; then
839 echo $ourname: error: improper use of interactice/rcfile option >&2
844 if [[ $rc -ne 1 ]]; then
845 echo $ourname: error: improper use of interactive/rcfile option >&2
849 prepargs=(1 $atype 2 $interactive 3 $rcfile 4 $zedit)
855 echo $ourname: error: multiple or invalid audio formats specified >&2
856 if [[ $rc -ne 1 ]]; then
857 echo $ourname: error: improper use of interactice/rcfile option >&2