3 ## Copyright (C) 2006 - 2013 Steve Youngs
5 ## Author: Steve Youngs <steve@steveyoungs.com>
6 ## Maintainer: Steve Youngs <steve@steveyoungs.com>
7 ## Created: <2006-08-08>
8 ## Time-stamp: <Sunday Aug 11, 2013 12:05:54 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 - 2013 Steve Youngs <steve@steveyoungs.com>"
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 ]
96 [ -p PLAYLISTFILE | --playlist=PLAYLISTFILE ]
105 $ourname is a "frontend" to the process of ripping audio (cdda) CDs
106 to a hard disk and encoding the audio tracks to either MP3 format or
109 File names are auto-generated from track titles, with illegal
110 characters removed and spaces converted to underscores.
112 Track title, artist, and album names are added to the ID3 tags or
113 Vorbis comments header.
120 Specify the ALBUM name. Argument ALBUM is _NOT_ optional.
121 Whitespace and punctuation is permitted, but you must protect
122 them from the shell. If this option is omitted, the album
123 name will be prompted for.
127 Save encoded files to the directory, DIR. If flac encoding
128 is also requested, see '--flac_copy' option below, the flac
129 files will be saved to DIR/flac. If this option is omitted,
130 it defaults to 'PWD/ARTIST/ALBUM' the album name is first
131 converted to a sane filename.
135 Edit/create rcfile, FILE, in an editor. If the environment
136 variable EDITOR is not set, vi is used. This implicitly sets
137 rcfile, see '--rcfile' option below.
141 In addition to encoding the CD audio tracks to MP3 or Ogg
142 Vorbis, create a FLAC audio file for archival purposes.
146 Display this help text and exit.
150 Prompt for track titles and artist names.
154 Specify MP3 encoding.
158 Specify Ogg Vorbis encoding.
162 Attempt to grab the track info from freedb.freedb.org.
165 --playlist=PLAYLISTFILE
166 Write a playlist file to PLAYLISTFILE. Argument PLAYLISTFILE
167 is _NOT_ optional, and should be given as the full path to the
172 Get track titles and artist names from FILE. Argument FILE
173 is _NOT_ optional. The format of FILE is:
175 Artist Name|Track Title
177 One "record" per line. Whitespace and punctuation is permitted,
178 blank lines are not. And there are no "comment" lines.
182 All tracks on the album are by the same ARTIST. Argument
183 ARTIST is _NOT_ optional.
187 This is an alternative to using the -m or -o options.
188 Use ENCODING_TYPE to encode the audio. Valid values for
189 ENCODING_TYPE are "mp3", "ogg".
193 Display version info and exit.
198 zsh http://www.zsh.org/ (mandatory)
200 cdparanoia http://www.xiph.org/paranoia/
201 For ripping the audio tracks from CD. (mandatory)
203 lame http://www.mp3dev.org/
204 For encoding to MP3 format. (optional if encoding to
207 oggenc http://www.xiph.org/
208 For encoding to Ogg Vorbis format (optional if encoding
211 flac http://flac.sourceforge.net/
212 For additionally storing audio tracks in FLAC format.
218 ${TMPPREFIX}/$ourname.log
219 A log of the incoming TCP traffic from the CDDB server.
220 This will be kept if debugging is turned on, otherwise
221 it is deleted upon successful exit.
224 Some extra debugging info is logged here.
227 Where $ourname gets track info from. It can be
228 specified on the command line (see -r above) when
229 not using an online CDDB server, otherwise it is
230 created automatically by $ourname.
232 Environment Variables:
233 ---------------------
235 Some aspects of $ourname can be tweaked by setting the following
236 environment variables.
238 DEBUG Set to a non-nil string to turn on debugging.
240 zcddb CDDB server to use (default: freedb.freedb.org).
242 zuser Name used to log into CDDB server (default: $USER).
244 zhost Host used to log into CDDB server (default: $HOST).
246 zedit Set to a non-nil string to force $ourname to invoke an
247 editor to create a rcfile.
249 zcomment Extra comment field/value to add to ID3 tags/Vorbis
250 comments (default: "Encoded with $ourname").
252 Occasionally, the CDDB entries for a given CD are either wrong or
253 have not been formatted correctly. In an attempt to compensate for
254 these types of quirks you can set the following environment variables
255 with $ourname invocation to override the madness...
257 ARTIST Override the artist field.
259 ALBUM Override the album name.
263 GENRE Override genre.
268 Probably the most common usage would be to encode to Ogg/Vorbis
269 format using track info obtained from freedb.freedb.org. Like
274 Encode to MP3 format, prompting for album name, year, genre, artist,
279 Encode to Ogg/Vorbis, set album name on the command line, taking
280 track title info from a file, and make a FLAC format backup copy of
283 \$ $ourname -a "The Album Name" -o -f -r /path/to/rcfile
285 Encode to Ogg/Vorbis, taking track info from a online CDDB server,
286 and override the genre for the album...
288 \$ GENRE=alternate $ourname -oO
293 $ourname will return the following status codes on exit:
295 0 -- Clean exit (no errors)
296 2 -- Command line option error
297 3 -- Missing external command
303 If you call $ourname with the environment variable DEBUG set to a
304 non nil value, for example: DEBUG=yes $ourname <opts>, $ourname will
305 be called with "set -x".
307 "set -x" spams your stdout with a _LOT_ of messages, it would most
308 likely pay you to redirect stdout/stderr to a file.
315 # Convert song title to something resembling a sane filename... no
316 # whitespace, no punctuation, etc. Also adds .ogg/.mp3 extension.
319 local psub="[[:cntrl:],\*\?\`';/\\\\\"]"
321 local wsub="[[:space:][:blank:]]"
326 for (( i=1; i<=$tracks; ++i )); do
329 tf=${tf//${~psub}/$prep}
331 tf=${tf//${~wsub}/$wrep}
332 filename[$i]=$tf.$prepargs[1]
341 print invalid args >&2
347 tcp_send "cddb hello $args"
348 tcp_expect -T 10 -s $ourname "*Hello and welcome*"
349 if [[ $? -eq 2 ]]; then
350 print Timeout waiting for cddb server >&2
355 tcp_expect -T 10 -s $ourname "*CDDB protocol*"
356 if [[ $? -eq 2 ]]; then
357 print Timeout waiting for cddb server >&2
366 art=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f1|sed '1!d')
367 art=${art/%[[:space:]]/}
374 if [[ "$art" != "Various Artists" && "$art" != "Various" ]]; then
378 # Album, year, genre, number or tracks
379 album=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f2)
380 album=${album/#[[:space:]]/}
381 album=${album//[[:cntrl:]]/}
382 year=$(grep DYEAR $zlog|cut -d= -f2)
383 year=${year//[[:cntrl:]]/}
384 genre=$(grep DGENRE $zlog|cut -d= -f2)
385 genre=${genre//[[:cntrl:]]/}
388 # Possibly override album, year, genre
389 album=${ALBUM:-$album}
391 genre=${GENRE:-$genre}
393 # print what we got when we're debugging
394 if [[ -n $DEBUG ]]; then
395 printf "Artist: %s\nAlbum: %s\nYear: %s\nGenre: %s\nNum Tracks: %s\n" \
396 $art $album $year $genre $tracks > $zdebug
397 printf "Various: %s\n" $various >> $zdebug
398 echo --- Titles --- >> $zdebug
399 grep TTITLE $zlog >> $zdebug
403 :>$rcfile # make sure it is empty
405 for (( i=0; i<$tracks; i++ )); do
407 for TITLE in $(grep TTITLE${i}= ${zlog}|cut -d= -f2); do
408 TRACK="${TRACK} ${TITLE}"
410 TRACK=${TRACK//[[:cntrl:]]/}
411 TRACK=${TRACK/[[:space:]]/}
412 echo ${TRACK} >> ${rcfile}
415 if [[ $various == yes || -n $(grep TTITLE $zlog|grep $art) ]]; then
416 sed -i 's@ [-/] @|@g' $rcfile
418 sed -i "s@^@${art}|@g" $rcfile
426 print invalid args >&2
430 tcp_send "cddb read $1 $2"
431 tcp_expect -T 25 -s $ourname "*PLAYORDER*"
432 if [[ $? -eq 2 ]]; then
433 print Timeout waiting for cddb server >&2
444 print invalid args >&2
448 tcp_send "cddb query $zid"
449 tcp_expect -T 10 -p idx -s $ourname "*No match for disc ID*" \
450 "<-\[$ourname\] 200*" "<-\[$ourname\] .*"
451 if [[ $? -eq 2 ]]; then
452 print Timeout waiting for cddb server >&2
457 print No entry listed for disc: $zid[1] >&2
458 print You will have to do this disc manually >&2
463 category=${category/*200 /}
464 category=${category/ $zid[1]*/}
465 _freedb_read $category $zid[1]
468 echo Multiple entries found...
470 tmulti=(${(ps:\r:)${tcp_expect_lines}})
471 [[ $tmulti[1] == *inexact* ]] && exact=no
472 tmulti[1]=${tmulti[1]:#${tmulti[1]}}
474 for (( i=1; i<${#multi}; ++i )); do
475 multi[$i]=${multi[$i]/<-\[$ourname\] /}
476 printf "\t[%s]%s\n" $i $multi[$i]
479 echo -n "Which CDDB entry (q to quit): "
482 ([qQ]) do_offline; exit 0 ;;
484 print Invalid response >&2
489 category=$multi[$choice]
490 if [[ $exact == no ]]; then
491 tcategory=(${(ps: :)category})
492 _freedb_read $tcategory[1,2]
494 category=${category/ $zid[1]*/}
495 _freedb_read $category $zid[1]
499 print Unknown error >&2
505 (*) print Unknown error >&2 ;;
513 [[ -n $DEBUG ]] || TCP_SILENT=nodebugging
515 zcddb=${zcddb:-freedb.freedb.org}
517 zuser=${zuser:-$USER}
518 zhost=${zhost:-$HOST}
520 zlog=${TMPPREFIX}/zcdrip.log
522 # open the log and connect
524 tcp_open $zcddb $port $ourname
525 tcp_expect -T 10 -s $ourname "<-\[$ourname\] 201*ready*"
526 if [[ $? -eq 2 ]]; then
527 print Timeout waiting for cddb server >&2
530 _freedb_login $zuser $zhost $ourname $VERSION
533 # query cddb server to find out if the CD is listed
538 # Close tcp connection
545 # Tidy up if not debugging
548 rm -f $zlog $rcfile $zdebug
549 unset ARTIST ALBUM YEAR GENRE
552 # convert album/artist name into a sane filename for output dir
556 local tart=${ARTIST:-$art}
557 local psub="[[:cntrl:],\`';/\\\\\"]"
559 local wsub="[[:space:][:blank:]]"
564 talbum=${talbum//\\t/ }
565 talbum=${talbum//${~psub}/$prep}
566 talbum=${talbum//[ ]##/ }
567 talbum=${talbum//${~wsub}/$wrep}
570 tart=${tart//${~psub}/$prep}
571 tart=${tart//[ ]##/ }
572 tart=${tart//${~wsub}/$wrep}
574 # Various Artists is sometimes "Various" and other times "Various
575 # Artists", we'll call them all "Various_Artists".
576 if [[ "${tart}" == "Various" ]]; then
580 dir=$(pwd)/${tart}/${talbum}
581 [[ -d ${dir} ]] || mkdir -p ${dir}
583 if [[ $keep_flac == yes ]]; then
590 # Store song titles, artist, and album names.
596 [[ -d ${TMPPREFIX} ]] || mkdir -p ${TMPPREFIX}
598 if [[ $online == yes ]]; then
607 if [[ $zedit == yes && -n $rcfile ]]; then
610 The format of this file is...
611 Artist Name|Track Title
612 One "record" per line. Punctuation and whitespace is OK.
613 You MUST delete these instructions before saving.
618 album=${ALBUM:-$album}
619 if [[ -z $album ]]; then
620 echo -n "Please enter the name of this album: "
625 [[ -n $dir ]] || _album_to_dir
627 if [[ ! -d ${dir} ]]; then
629 if [[ $keep_flac == yes && ! -d ${fdir} ]]; then
635 if [[ -z $year ]]; then
636 echo -n "Please enter the year this album was released: "
641 genre=${GENRE:-$genre}
642 if [[ -z $genre ]]; then
643 echo -n "Please enter the genre of this album: "
648 zcomment=${zcomment:-"Encoded with $ourname"}
650 if [[ $prepargs[2] == yes ]]; then
651 for (( i=1; i<=$tracks; ++i )); do
652 if [[ -n $art ]]; then
655 echo -n "Track $i artist: "
660 echo -n "Track $i title: "
668 while read art sg; do
671 sg=${sg/#[[:space:]]/}
672 sg=${sg//[[:cntrl:]]/}
678 fnamefix $prepargs[1]
679 wavfile=dumpaudio.wav
682 [[ -n $DEBUG ]] || cleanup
689 shift # pop off $1 (flacfile)
690 flac -8 --delete-input-file -o ${fdir}/${file} ${1}
691 shift # pop off next arg (wavfile)
692 for tag in title artist album year genre tracknumber comment; do
693 metaflac --set-tag="$tag=${1}" ${fdir}/${file}
704 title=$(metaflac --show-tag=title ${flac}|cut -d= -f2)
705 artist=$(metaflac --show-tag=artist ${flac}|cut -d= -f2)
706 album=$(metaflac --show-tag=album ${flac}|cut -d= -f2)
707 year=$(metaflac --show-tag=year ${flac}|cut -d= -f2)
708 genre=$(metaflac --show-tag=genre ${flac}|cut -d= -f2)
709 track=$(metaflac --show-tag=tracknumber ${flac}|cut -d= -f2)
710 comment=$(metaflac --show-tag=comment ${flac}|cut -d= -f2)
712 flac -c -d $flac|lame -h --tt ${title} --ta ${artist} --tl ${album} \
713 --ty $year --tg $genre --tn $track --tc ${comment} - $mp3
719 # Work around lame being too anal about genre
720 lame --genre-list 2>&1 | grep -q ${genre} || genre=Other
722 for (( i=1; i<=$tracks; ++i )); do
723 echo MP3-Ripping: $song[$i], by: $artist[$i], to: $filename[$i]
724 cdparanoia -X $i $wavfile
725 if [[ -f $wavfile ]]; then
726 if [[ $keep_flac == yes ]]; then
727 flacfile=${filename[$i]/%mp3/flac}
728 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
729 $year $genre $i $zcomment
730 flactomp3 ${flacfile} ${dir}/$filename[$i]
732 lame -h --tt $song[$i] --ta $artist[$i] --tl $album \
733 --ty $year --tg $genre --tc $zcomment $wavfile \
734 --tn $i ${dir}/$filename[$i]
736 [[ $write_playlist == yes ]] && echo ${dir}$filename[$i] >> $plfile
738 [[ -f $wavfile ]] && rm -f $wavfile
742 # Encode to OGG Vorbis
745 for (( i=1; i<=$tracks; ++i )); do
746 echo OGG-Ripping: $song[$i], by: $artist[$i], to: $filename[$i]
747 cdparanoia -X $i $wavfile
748 if [[ -f $wavfile ]]; then
749 if [[ $keep_flac == yes ]]; then
750 flacfile=${filename[$i]/%ogg/flac}
751 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
752 $year $genre $i $zcomment
753 oggenc -q3 -o ${dir}/$filename[$i] ${fdir}/$flacfile
755 oggenc -q3 -o ${dir}/$filename[$i] -t $song[$i] -a $artist[$i] \
756 -l $album -d $year -G $genre -c "comment=$zcomment" \
760 [[ $write_playlist == yes ]] && echo ${dir}/$filename[$i] >> $plfile
765 # Make sure we have everything we need.
766 # This might be a little over the top, but I don't see any point in
767 # checking for `oggenc' if you are encoding to MP3, likewise, there's
768 # no point in checking for `lame' if you are encoding to Ogg Vorbis.
771 reqs=(cdparanoia lame oggenc flac)
772 whence -w $reqs[1] 1>/dev/null
773 if [[ $? -gt 0 ]]; then
774 echo $ourname: error: Missing $reqs[1] >&2
779 whence -w $reqs[2] 1>/dev/null
780 if [[ $? -gt 0 ]]; then
781 echo $ourname: error: Missing $reqs[2] >&2
786 whence -w $reqs[3] 1>/dev/null
787 if [[ $? -gt 0 ]]; then
788 echo $ourname: error: Missing $reqs[3] >&2
793 if [[ $keep_flac == yes ]]; then
794 whence -w $reqs[4] 1>/dev/null
795 if [[ $? -gt 0 ]]; then
796 echo $ourname: error: Missing $reqs[4] >&2
800 if [[ $rv -eq 3 ]]; then
808 # Command line parsing
809 args=mofihVO-:r:a:s:t:e:d:p:
816 while getopts $args opts; do
820 (mp3) atype=mp3; (( ++flavour )) ;;
821 (ogg) atype=ogg; (( ++flavour )) ;;
823 rcfile=${OPTARG/edit_rcfile=/}
827 (flac_copy) keep_flac=yes ;;
828 (type?*) atype=${OPTARG/type=/}; (( ++flavour )) ;;
829 (interactive) interactive=yes; (( ++rc )) ;;
830 (online) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
831 (rcfile?*) rcfile=${OPTARG/rcfile=/}; (( ++rc )) ;;
832 (album?*) album=${OPTARG/album=/} ;;
833 (sameartist?*) art=${OPTARG/sameartist=/} ;;
834 (dir?*) dir=${OPTARG/dir=/} ;;
835 (playlist?*) write_playlist=yes; plfile=${OPTARG/playlist=/} ;;
836 (version) _version; exit 0 ;;
837 (help) cmd=usage; rv=0 ;;
839 echo $ourname: error: bad option: --$OPTARG >&2
844 (i) interactive=yes; (( ++rc )) ;;
845 (O) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
846 (m) atype=mp3; (( ++flavour )) ;;
847 (o) atype=ogg; (( ++flavour )) ;;
848 (e) rcfile=$OPTARG; (( ++rc )); zedit=yes ;;
850 (r) rcfile=$OPTARG; (( ++rc )) ;;
853 (t) atype=$OPTARG; (( ++flavour )) ;;
855 (p) write_playlist=yes; plfile=$OPTARG ;;
856 (V) _version; exit 0 ;;
857 (h) cmd=usage; rv=0 ;;
861 shift $(( $OPTIND - 1 ))
863 if [[ $cmd != usage ]]; then
866 echo $ourname: error: no audio format specified >&2
867 if [[ $rc -ne 1 ]]; then
868 echo $ourname: error: improper use of interactice/rcfile option >&2
873 if [[ $rc -ne 1 ]]; then
874 echo $ourname: error: improper use of interactive/rcfile option >&2
878 prepargs=(1 $atype 2 $interactive 3 $rcfile 4 $zedit)
884 echo $ourname: error: multiple or invalid audio formats specified >&2
885 if [[ $rc -ne 1 ]]; then
886 echo $ourname: error: improper use of interactice/rcfile option >&2