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: <Saturday Feb 20, 2021 12:38:28 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 Rewrite online metadata retrieval to use MusicBrainz instead
49 ## of freedb.org as the latter no longer exists.
54 ## Bugs? What bugs? Seriously though, if you find any, let me
59 [[ -n $DEBUG ]] && set -x; zdebug=${TMPPREFIX}/zdebug
66 COPYRIGHT="Copyright (C) 2006 - 2021 Steve Youngs <steve@steveyoungs.com>"
68 version_str="${ourname}: ${VERSION}
71 _version () { echo $version_str }
76 # If you use eshell you will have a problem displaying this help.
77 # This is the only part of the script that eshell has a problem
78 # with, but if you really want to be able to view this help in
79 # eshell, create a symlink and call it `ecdrip'.
81 [[ ${LINES} -lt 70 && $TERM != dumb ]] && _doc=${PAGER:-less} || _doc=cat
82 [[ $ourname == ecdrip ]] && _doc=cat
93 [ -moi ] [ --mp3 | --ogg ] [ -r FILE ] [ --interactive | --rcfile=FILE ]
94 [ -a ALBUM ] [ --album=ALBUM ] [ -s ARTIST ] [ --sameartist=ARTIST ]
95 [ -t ENCODING_TYPE ] [ --type=ENCODING_TYPE ] [ -f | --flac_copy ]
96 [ -e FILE | --edit_rcfile=FILE ] [ -O | --online ] [ -d DIR | --dir=DIR ]
97 [ -p PLAYLISTFILE | --playlist=PLAYLISTFILE ]
106 $ourname is a "frontend" to the process of ripping audio (cdda) CDs
107 to a hard disk and encoding the audio tracks to either MP3 format or
110 File names are auto-generated from track titles, with illegal
111 characters removed and spaces converted to underscores.
113 Track title, artist, and album names are added to the ID3 tags or
114 Vorbis comments header.
116 If the file: '\${PWD}/cover.jpg' exists it will be embedded into
117 each track file as "Front Cover" art. This is only supported for
118 Ogg/Vorbis and FLAC formats.
125 Specify the ALBUM name. Argument ALBUM is _NOT_ optional.
126 Whitespace and punctuation is permitted, but you must protect
127 them from the shell. If this option is omitted, the album
128 name will be prompted for.
132 Save encoded files to the directory, DIR. If flac encoding
133 is also requested, see '--flac_copy' option below, the flac
134 files will be saved to DIR/flac. If this option is omitted,
135 it defaults to: '\${PWD}/ARTIST/ALBUM' (with sanitised
140 Edit/create rcfile, FILE, in an editor. If the environment
141 variable EDITOR is not set, vi is used. This implicitly sets
142 rcfile, see '--rcfile' option below.
146 In addition to encoding the CD audio tracks to Ogg/Vorbis or
147 MP3, create a FLAC audio file for archival purposes.
151 Display this help text and exit.
155 Prompt for track titles and artist names.
159 Specify MP3 encoding.
163 Specify Ogg/Vorbis encoding.
167 Attempt to grab the track info from freedb.freedb.org.
170 freedb.org no longer exists. The plan is for zcdrip to
171 use MusicBrainz instead of freedb.org but it is not yet
175 --playlist=PLAYLISTFILE
176 Write a playlist file to PLAYLISTFILE. Argument PLAYLISTFILE
177 is _NOT_ optional, and should be given as the full path to the
182 Get track titles and artist names from FILE. Argument FILE
183 is _NOT_ optional. The format of FILE is:
185 Artist Name|Track Title
187 One "record" per line. Whitespace and punctuation is permitted,
188 blank lines are not. And there are no "comment" lines.
192 All tracks on the album are by the same ARTIST. Argument
193 ARTIST is _NOT_ optional.
197 This is an alternative to using the -m or -o options.
198 Use ENCODING_TYPE to encode the audio. Valid values for
199 ENCODING_TYPE are "mp3", "ogg".
203 Display version info and exit.
208 zsh http://www.zsh.org/ (mandatory)
210 cdparanoia http://www.xiph.org/paranoia/
211 For ripping the audio tracks from CD. (mandatory)
213 lame http://www.mp3dev.org/
214 For encoding to MP3 format. (optional if encoding to
217 oggenc http://www.xiph.org/
218 For encoding to Ogg/Vorbis format (optional if encoding
221 flac http://flac.sourceforge.net/
222 For additionally storing audio tracks in FLAC format.
228 ${TMPPREFIX}/$ourname.log
229 A log of the incoming TCP traffic from the CDDB server.
230 This will be kept if debugging is turned on, otherwise
231 it is deleted upon successful exit.
234 Some extra debugging info is logged here.
237 Where $ourname gets track info from. It can be
238 specified on the command line (see -r above) when
239 not using an online CDDB server, otherwise it is
240 created automatically by $ourname.
242 Environment Variables:
243 ---------------------
245 Some aspects of $ourname can be tweaked by setting the following
246 environment variables.
248 DEBUG Set to a non-nil string to turn on debugging.
250 zcddb CDDB server to use (default: freedb.freedb.org).
252 zuser Name used to log into CDDB server (default: $USER).
254 zhost Host used to log into CDDB server (default: $HOST).
256 zedit Set to a non-nil string to force $ourname to invoke an
257 editor to create a rcfile.
259 zcomment Extra comment field/value to add to ID3 tags/Vorbis
260 comments (default: "Encoded with $ourname").
262 Occasionally, the CDDB entries for a given CD are either wrong or
263 have not been formatted correctly. In an attempt to compensate for
264 these types of quirks you can set the following environment variables
265 with $ourname invocation to override the madness...
267 ARTIST Override the artist field.
269 ALBUM Override the album name.
273 GENRE Override genre.
278 Currently, online metadata retrieval is not possible because freedb.org
279 no longer exists. We will be converting to MusicBrainz but that is yet
280 to be implemented. In the meantime, the examples below that use the
281 '-O' option are presently not valid.
283 Probably the most common usage would be to encode to Ogg/Vorbis
284 format using track info obtained from freedb.freedb.org. Like
289 Encode to MP3 format, prompting for album name, year, genre, artist,
294 Encode to Ogg/Vorbis, set album name on the command line, taking
295 track title info from a file, and make a FLAC format backup copy of
298 \$ $ourname -a "The Album Name" -o -f -r /path/to/rcfile
300 Encode to Ogg/Vorbis, taking track info from a online CDDB server,
301 and override the genre for the album...
303 \$ GENRE=alternate $ourname -oO
308 $ourname will return the following status codes on exit:
310 0 -- Clean exit (no errors)
311 2 -- Command line option error
312 3 -- Missing external command
318 If you call $ourname with the environment variable DEBUG set to a
319 non nil value, for example: DEBUG=yes $ourname <opts>, $ourname will
320 be called with "set -x".
322 "set -x" spams your stdout with a _LOT_ of messages, it would most
323 likely pay you to redirect stdout/stderr to a file.
330 ARTWORK=$(pwd)/cover.jpg
332 # Convert song title to something resembling a sane filename... no
333 # whitespace, no punctuation, etc. Also adds .ogg/.mp3 extension.
336 local psub="[[:cntrl:],\*\?\`';/\\\\\"]"
338 local wsub="[[:space:][:blank:]]"
343 for (( i=1; i<=$tracks; ++i )); do
346 tf=${tf//${~psub}/$prep}
348 tf=${tf//${~wsub}/$wrep}
349 filename[$i]=$tf.$prepargs[1]
353 ### FIXME: freedb.org no longer exists. Convert to MusicBrainz.
359 print invalid args >&2
365 tcp_send "cddb hello $args"
366 tcp_expect -T 10 -s $ourname "*Hello and welcome*"
367 if [[ $? -eq 2 ]]; then
368 print Timeout waiting for cddb server >&2
373 tcp_expect -T 10 -s $ourname "*CDDB protocol*"
374 if [[ $? -eq 2 ]]; then
375 print Timeout waiting for cddb server >&2
381 ### FIXME: freedb.org no longer exists. Convert to MusicBrainz.
385 art=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f1|sed '1!d')
386 art=${art/%[[:space:]]/}
393 if [[ "$art" != "Various Artists" && "$art" != "Various" ]]; then
397 # Album, year, genre, number or tracks
398 album=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f2)
399 album=${album/#[[:space:]]/}
400 album=${album//[[:cntrl:]]/}
401 year=$(grep DYEAR $zlog|cut -d= -f2)
402 year=${year//[[:cntrl:]]/}
403 genre=$(grep DGENRE $zlog|cut -d= -f2)
404 genre=${genre//[[:cntrl:]]/}
407 # Possibly override album, year, genre
408 album=${ALBUM:-$album}
410 genre=${GENRE:-$genre}
412 # print what we got when we're debugging
413 if [[ -n $DEBUG ]]; then
414 printf "Artist: %s\nAlbum: %s\nYear: %s\nGenre: %s\nNum Tracks: %s\n" \
415 $art $album $year $genre $tracks > $zdebug
416 printf "Various: %s\n" $various >> $zdebug
417 echo --- Titles --- >> $zdebug
418 grep TTITLE $zlog >> $zdebug
422 :>$rcfile # make sure it is empty
424 for (( i=0; i<$tracks; i++ )); do
426 for TITLE in $(grep TTITLE${i}= ${zlog}|cut -d= -f2); do
427 TRACK="${TRACK} ${TITLE}"
429 TRACK=${TRACK//[[:cntrl:]]/}
430 TRACK=${TRACK/[[:space:]]/}
431 echo ${TRACK} >> ${rcfile}
434 if [[ $various == yes || -n $(grep TTITLE $zlog|grep $art) ]]; then
435 sed -i 's@ [-/] @|@g' $rcfile
437 sed -i "s@^@${art}|@g" $rcfile
442 ### FIXME: freedb.org no longer exists. Convert to MusicBrainz.
446 print invalid args >&2
450 tcp_send "cddb read $1 $2"
451 tcp_expect -T 25 -s $ourname "*PLAYORDER*"
452 if [[ $? -eq 2 ]]; then
453 print Timeout waiting for cddb server >&2
461 ### FIXME: freedb.org no longer exists. Convert to MusicBrainz.
465 print invalid args >&2
469 tcp_send "cddb query $zid"
470 tcp_expect -T 10 -p idx -s $ourname "*No match for disc ID*" \
471 "<-\[$ourname\] 200*" "<-\[$ourname\] .*"
472 if [[ $? -eq 2 ]]; then
473 print Timeout waiting for cddb server >&2
478 print No entry listed for disc: $zid[1] >&2
479 print You will have to do this disc manually >&2
484 category=${category/*200 /}
485 category=${category/ $zid[1]*/}
486 _freedb_read $category $zid[1]
489 echo Multiple entries found...
491 tmulti=(${(ps:\r:)${tcp_expect_lines}})
492 [[ $tmulti[1] == *inexact* ]] && exact=no
493 tmulti[1]=${tmulti[1]:#${tmulti[1]}}
495 for (( i=1; i<${#multi}; ++i )); do
496 multi[$i]=${multi[$i]/<-\[$ourname\] /}
497 printf "\t[%s]%s\n" $i $multi[$i]
500 echo -n "Which CDDB entry (q to quit): "
503 ([qQ]) do_offline; exit 0 ;;
505 print Invalid response >&2
510 category=$multi[$choice]
511 if [[ $exact == no ]]; then
512 tcategory=(${(ps: :)category})
513 _freedb_read $tcategory[1,2]
515 category=${category/ $zid[1]*/}
516 _freedb_read $category $zid[1]
520 print Unknown error >&2
526 (*) print Unknown error >&2 ;;
530 ### FIXME: freedb.org no longer exists. Convert to MusicBrainz.
535 [[ -n $DEBUG ]] || TCP_SILENT=nodebugging
537 zcddb=${zcddb:-freedb.freedb.org}
539 zuser=${zuser:-$USER}
540 zhost=${zhost:-$HOST}
542 zlog=${TMPPREFIX}/zcdrip.log
544 # open the log and connect
546 tcp_open $zcddb $port $ourname
547 tcp_expect -T 10 -s $ourname "<-\[$ourname\] 201*ready*"
548 if [[ $? -eq 2 ]]; then
549 print Timeout waiting for cddb server >&2
552 _freedb_login $zuser $zhost $ourname $VERSION
555 # query cddb server to find out if the CD is listed
560 # Close tcp connection
567 # Tidy up if not debugging
570 rm -f $zlog $rcfile $zdebug
571 unset ARTIST ALBUM YEAR GENRE
574 # convert album/artist name into a sane filename for output dir
578 local tart=${ARTIST:-$art}
579 local psub="[[:cntrl:],\`';/\\\\\"]"
581 local wsub="[[:space:][:blank:]]"
586 talbum=${talbum//\\t/ }
587 talbum=${talbum//${~psub}/$prep}
588 talbum=${talbum//[ ]##/ }
589 talbum=${talbum//${~wsub}/$wrep}
592 tart=${tart//${~psub}/$prep}
593 tart=${tart//[ ]##/ }
594 tart=${tart//${~wsub}/$wrep}
596 # Various Artists is sometimes "Various" and other times "Various
597 # Artists", we'll call them all "Various_Artists".
598 if [[ "${tart}" == "Various" ]]; then
602 dir=$(pwd)/${tart}/${talbum}
603 [[ -d ${dir} ]] || mkdir -p ${dir}
605 if [[ $keep_flac == yes ]]; then
612 # Store song titles, artist, and album names.
618 [[ -d ${TMPPREFIX} ]] || mkdir -p ${TMPPREFIX}
620 if [[ $online == yes ]]; then
629 if [[ $zedit == yes && -n $rcfile ]]; then
632 The format of this file is...
633 Artist Name|Track Title
634 One "record" per line. Punctuation and whitespace is OK.
635 You MUST delete these instructions before saving.
640 album=${ALBUM:-$album}
641 if [[ -z $album ]]; then
642 echo -n "Please enter the name of this album: "
647 [[ -n $dir ]] || _album_to_dir
649 if [[ ! -d ${dir} ]]; then
651 if [[ $keep_flac == yes && ! -d ${fdir} ]]; then
657 if [[ -z $year ]]; then
658 echo -n "Please enter the year this album was released: "
663 genre=${GENRE:-$genre}
664 if [[ -z $genre ]]; then
665 echo -n "Please enter the genre of this album: "
670 zcomment=${zcomment:-"Encoded with $ourname"}
672 if [[ $prepargs[2] == yes ]]; then
673 for (( i=1; i<=$tracks; ++i )); do
674 if [[ -n $art ]]; then
677 echo -n "Track $i artist: "
682 echo -n "Track $i title: "
690 while read art sg; do
693 sg=${sg/#[[:space:]]/}
694 sg=${sg//[[:cntrl:]]/}
700 fnamefix $prepargs[1]
701 wavfile=dumpaudio.wav
704 [[ -n $DEBUG ]] || cleanup
711 shift # pop off $1 (flacfile)
712 [ -f ${ARTWORK} ] && PIC="--picture=${ARTWORK}" || PIC=""
713 flac -8 ${PIC} --delete-input-file -o ${fdir}/${file} ${1}
714 shift # pop off next arg (wavfile)
715 for tag in title artist album year genre tracknumber comment; do
716 metaflac --set-tag="$tag=${1}" ${fdir}/${file}
727 title=$(metaflac --show-tag=title ${flac}|cut -d= -f2)
728 artist=$(metaflac --show-tag=artist ${flac}|cut -d= -f2)
729 album=$(metaflac --show-tag=album ${flac}|cut -d= -f2)
730 year=$(metaflac --show-tag=year ${flac}|cut -d= -f2)
731 genre=$(metaflac --show-tag=genre ${flac}|cut -d= -f2)
732 track=$(metaflac --show-tag=tracknumber ${flac}|cut -d= -f2)
733 comment=$(metaflac --show-tag=comment ${flac}|cut -d= -f2)
735 flac -c -d $flac|lame -h --tt ${title} --ta ${artist} --tl ${album} \
736 --ty $year --tg $genre --tn $track --tc ${comment} - $mp3
742 # Work around lame being too anal about genre
743 lame --genre-list 2>&1 | grep -q ${genre} || genre=Other
745 for (( i=1; i<=$tracks; ++i )); do
746 echo MP3-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]/%mp3/flac}
751 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
752 $year $genre $i $zcomment
753 flactomp3 ${flacfile} ${dir}/$filename[$i]
755 lame -h --tt $song[$i] --ta $artist[$i] --tl $album \
756 --ty $year --tg $genre --tc $zcomment $wavfile \
757 --tn $i ${dir}/$filename[$i]
759 [[ $write_playlist == yes ]] && echo ${dir}$filename[$i] >> $plfile
761 [[ -f $wavfile ]] && rm -f $wavfile
765 # Encode to OGG Vorbis
768 for (( i=1; i<=$tracks; ++i )); do
769 echo OGG-Ripping: $song[$i], by: $artist[$i], to: $filename[$i]
770 cdparanoia -X $i $wavfile
771 if [[ -f $wavfile ]]; then
772 if [[ $keep_flac == yes ]]; then
773 flacfile=${filename[$i]/%ogg/flac}
774 flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \
775 $year $genre $i $zcomment
776 oggenc -q3 -o ${dir}/$filename[$i] ${fdir}/$flacfile
778 oggenc -q3 -o ${dir}/$filename[$i] -t $song[$i] -a $artist[$i] \
779 -l $album -d $year -G $genre -c "comment=$zcomment" \
783 [ -f ${ARTWORK} ] && album_art ${dir}/$filename[$i]
784 [[ $write_playlist == yes ]] && echo ${dir}/$filename[$i] >> $plfile
789 # Make sure we have everything we need.
790 # This might be a little over the top, but I don't see any point in
791 # checking for `oggenc' if you are encoding to MP3, likewise, there's
792 # no point in checking for `lame' if you are encoding to Ogg/Vorbis.
795 reqs=(cdparanoia lame oggenc flac)
796 whence -w $reqs[1] 1>/dev/null
797 if [[ $? -gt 0 ]]; then
798 echo $ourname: error: Missing $reqs[1] >&2
803 whence -w $reqs[2] 1>/dev/null
804 if [[ $? -gt 0 ]]; then
805 echo $ourname: error: Missing $reqs[2] >&2
810 whence -w $reqs[3] 1>/dev/null
811 if [[ $? -gt 0 ]]; then
812 echo $ourname: error: Missing $reqs[3] >&2
817 if [[ $keep_flac == yes ]]; then
818 whence -w $reqs[4] 1>/dev/null
819 if [[ $? -gt 0 ]]; then
820 echo $ourname: error: Missing $reqs[4] >&2
824 if [[ $rv -eq 3 ]]; then
832 # Command line parsing
833 args=mofihVO-:r:a:s:t:e:d:p:
840 while getopts $args opts; do
844 (mp3) atype=mp3; (( ++flavour )) ;;
845 (ogg) atype=ogg; (( ++flavour )) ;;
847 rcfile=${OPTARG/edit_rcfile=/}
851 (flac_copy) keep_flac=yes ;;
852 (type?*) atype=${OPTARG/type=/}; (( ++flavour )) ;;
853 (interactive) interactive=yes; (( ++rc )) ;;
854 (online) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
855 (rcfile?*) rcfile=${OPTARG/rcfile=/}; (( ++rc )) ;;
856 (album?*) album=${OPTARG/album=/} ;;
857 (sameartist?*) art=${OPTARG/sameartist=/} ;;
858 (dir?*) dir=${OPTARG/dir=/} ;;
859 (playlist?*) write_playlist=yes; plfile=${OPTARG/playlist=/} ;;
860 (version) _version; exit 0 ;;
861 (help) cmd=usage; rv=0 ;;
863 echo $ourname: error: bad option: --$OPTARG >&2
868 (i) interactive=yes; (( ++rc )) ;;
869 (O) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;;
870 (m) atype=mp3; (( ++flavour )) ;;
871 (o) atype=ogg; (( ++flavour )) ;;
872 (e) rcfile=$OPTARG; (( ++rc )); zedit=yes ;;
874 (r) rcfile=$OPTARG; (( ++rc )) ;;
877 (t) atype=$OPTARG; (( ++flavour )) ;;
879 (p) write_playlist=yes; plfile=$OPTARG ;;
880 (V) _version; exit 0 ;;
881 (h) cmd=usage; rv=0 ;;
885 shift $(( $OPTIND - 1 ))
887 if [[ $cmd != usage ]]; then
890 echo $ourname: error: no audio format specified >&2
891 if [[ $rc -ne 1 ]]; then
892 echo $ourname: error: improper use of interactice/rcfile option >&2
897 if [[ $rc -ne 1 ]]; then
898 echo $ourname: error: improper use of interactive/rcfile option >&2
902 prepargs=(1 $atype 2 $interactive 3 $rcfile 4 $zedit)
908 echo $ourname: error: multiple or invalid audio formats specified >&2
909 if [[ $rc -ne 1 ]]; then
910 echo $ourname: error: improper use of interactice/rcfile option >&2