#!/bin/zsh ## Copyright (C) 2006 - 2013 Steve Youngs ## Author: Steve Youngs ## Maintainer: Steve Youngs ## Created: <2006-08-08> ## Time-stamp: ## Redistribution and use in source and binary forms, with or without ## modification, are permitted provided that the following conditions ## are met: ## ## 1. Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## ## 2. Redistributions in binary form must reproduce the above copyright ## notice, this list of conditions and the following disclaimer in the ## documentation and/or other materials provided with the distribution. ## ## 3. Neither the name of the author nor the names of any contributors ## may be used to endorse or promote products derived from this ## software without specific prior written permission. ## ## THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR ## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ## WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ## DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ## FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ## SUBSTITUTE GOODS OR SERVICES# LOSS OF USE, DATA, OR PROFITS# OR ## BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE ## OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN ## IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### Commentary: ## ## Ease the process of ripping audio CDs to .mp3 or .ogg. See ## help (zcdrip -h) for full details. ## ### Todo: ## ## o Partial album ripping. Specify track numbers or a range of ## track numbers to only rip/encode those tracks. ## ## o Rewrite online metadata retrieval to use MusicBrainz instead ## of freedb.org as the latter no longer exists. ## ### Bugs: ## ## Bugs? What bugs? Seriously though, if you find any, let me ## know. ## # Debug [[ -n $DEBUG ]] && set -x; zdebug=${TMPPREFIX}/zdebug ### Code: ourname=${0##*/} # Version info. VERSION=@VERSION@ COPYRIGHT="Copyright (C) 2006 - 2021 Steve Youngs " version_str="${ourname}: ${VERSION} ${COPYRIGHT}" _version () { echo $version_str } # Help usage () { # If you use eshell you will have a problem displaying this help. # This is the only part of the script that eshell has a problem # with, but if you really want to be able to view this help in # eshell, create a symlink and call it `ecdrip'. [[ ${LINES} -lt 70 && $TERM != dumb ]] && _doc=${PAGER:-less} || _doc=cat [[ $ourname == ecdrip ]] && _doc=cat $_doc<, $ourname will be called with "set -x". "set -x" spams your stdout with a _LOT_ of messages, it would most likely pay you to redirect stdout/stderr to a file. ${COPYRIGHT} EOF } # Cover Art ARTWORK=$(pwd)/cover.jpg # Convert song title to something resembling a sane filename... no # whitespace, no punctuation, etc. Also adds .ogg/.mp3 extension. fnamefix () { local psub="[[:cntrl:],\*\?\`';/\\\\\"]" local prep="" local wsub="[[:space:][:blank:]]" local wrep="_" setopt extendedglob for (( i=1; i<=$tracks; ++i )); do tf=$filename[$i] tf=${tf//\\t/ } tf=${tf//${~psub}/$prep} tf=${tf//[ ]##/ } tf=${tf//${~wsub}/$wrep} filename[$i]=$tf.$prepargs[1] done } ### FIXME: freedb.org no longer exists. Convert to MusicBrainz. ### freedb magic _freedb_login () { local args if [[ -z $1 ]]; then print invalid args >&2 return 1 fi args="$@" # login tcp_send "cddb hello $args" tcp_expect -T 10 -s $ourname "*Hello and welcome*" if [[ $? -eq 2 ]]; then print Timeout waiting for cddb server >&2 exit 4 fi # set protocol level tcp_send "proto 6" tcp_expect -T 10 -s $ourname "*CDDB protocol*" if [[ $? -eq 2 ]]; then print Timeout waiting for cddb server >&2 exit 4 fi return 0 } ### FIXME: freedb.org no longer exists. Convert to MusicBrainz. _parse_cddb () { local various=yes art=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f1|sed '1!d') art=${art/%[[:space:]]/} art=${art//'&'/'\&'} # Possibly override. art=${ARTIST:-$art} # Artist if [[ "$art" != "Various Artists" && "$art" != "Various" ]]; then various=no fi # Album, year, genre, number or tracks album=$(grep DTITLE $zlog|cut -d= -f2|cut -d/ -f2) album=${album/#[[:space:]]/} album=${album//[[:cntrl:]]/} year=$(grep DYEAR $zlog|cut -d= -f2) year=${year//[[:cntrl:]]/} genre=$(grep DGENRE $zlog|cut -d= -f2) genre=${genre//[[:cntrl:]]/} tracks=$zid[2] # Possibly override album, year, genre album=${ALBUM:-$album} year=${YEAR:-$year} genre=${GENRE:-$genre} # print what we got when we're debugging if [[ -n $DEBUG ]]; then printf "Artist: %s\nAlbum: %s\nYear: %s\nGenre: %s\nNum Tracks: %s\n" \ $art $album $year $genre $tracks > $zdebug printf "Various: %s\n" $various >> $zdebug echo --- Titles --- >> $zdebug grep TTITLE $zlog >> $zdebug fi # create an rcfile :>$rcfile # make sure it is empty for (( i=0; i<$tracks; i++ )); do TRACK="" for TITLE in $(grep TTITLE${i}= ${zlog}|cut -d= -f2); do TRACK="${TRACK} ${TITLE}" done TRACK=${TRACK//[[:cntrl:]]/} TRACK=${TRACK/[[:space:]]/} echo ${TRACK} >> ${rcfile} done if [[ $various == yes || -n $(grep TTITLE $zlog|grep $art) ]]; then sed -i 's@ [-/] @|@g' $rcfile else sed -i "s@^@${art}|@g" $rcfile fi } ### FIXME: freedb.org no longer exists. Convert to MusicBrainz. _freedb_read () { if [[ -z $1 ]]; then print invalid args >&2 return 1 fi tcp_send "cddb read $1 $2" tcp_expect -T 25 -s $ourname "*PLAYORDER*" if [[ $? -eq 2 ]]; then print Timeout waiting for cddb server >&2 exit 4 fi _parse_cddb } ### FIXME: freedb.org no longer exists. Convert to MusicBrainz. _freedb_query () { if [[ -z $1 ]]; then print invalid args >&2 return 1 fi tcp_send "cddb query $zid" tcp_expect -T 10 -p idx -s $ourname "*No match for disc ID*" \ "<-\[$ourname\] 200*" "<-\[$ourname\] .*" if [[ $? -eq 2 ]]; then print Timeout waiting for cddb server >&2 exit 4 fi case $idx in (1) print No entry listed for disc: $zid[1] >&2 print You will have to do this disc manually >&2 exit 4 ;; (2) category=$TCP_LINE category=${category/*200 /} category=${category/ $zid[1]*/} _freedb_read $category $zid[1] ;; (3) echo Multiple entries found... echo tmulti=(${(ps:\r:)${tcp_expect_lines}}) [[ $tmulti[1] == *inexact* ]] && exact=no tmulti[1]=${tmulti[1]:#${tmulti[1]}} multi=($tmulti) for (( i=1; i<${#multi}; ++i )); do multi[$i]=${multi[$i]/<-\[$ourname\] /} printf "\t[%s]%s\n" $i $multi[$i] done echo echo -n "Which CDDB entry (q to quit): " read choice case $choice in ([qQ]) do_offline; exit 0 ;; ([^[:digit:]]) print Invalid response >&2 do_offline exit 2 ;; ([[:digit:]]) category=$multi[$choice] if [[ $exact == no ]]; then tcategory=(${(ps: :)category}) _freedb_read $tcategory[1,2] else category=${category/ $zid[1]*/} _freedb_read $category $zid[1] fi ;; (*) print Unknown error >&2 do_offline exit 2 ;; esac ;; (*) print Unknown error >&2 ;; esac } ### FIXME: freedb.org no longer exists. Convert to MusicBrainz. do_online () { autoload -U tcp_open autoload -U tcp_log [[ -n $DEBUG ]] || TCP_SILENT=nodebugging zcddb=${zcddb:-freedb.freedb.org} port=8880 zuser=${zuser:-$USER} zhost=${zhost:-$HOST} proto=6 zlog=${TMPPREFIX}/zcdrip.log # open the log and connect tcp_log $zlog tcp_open $zcddb $port $ourname tcp_expect -T 10 -s $ourname "<-\[$ourname\] 201*ready*" if [[ $? -eq 2 ]]; then print Timeout waiting for cddb server >&2 exit 4 else _freedb_login $zuser $zhost $ourname $VERSION fi # query cddb server to find out if the CD is listed _freedb_query $zid } # Close tcp connection do_offline () { tcp_close $ourname tcp_log -c } # Tidy up if not debugging cleanup () { rm -f $zlog $rcfile $zdebug unset ARTIST ALBUM YEAR GENRE } # convert album/artist name into a sane filename for output dir _album_to_dir () { local talbum=$album local tart=${ARTIST:-$art} local psub="[[:cntrl:],\`';/\\\\\"]" local prep="" local wsub="[[:space:][:blank:]]" local wrep="_" setopt extendedglob talbum=${talbum//\\t/ } talbum=${talbum//${~psub}/$prep} talbum=${talbum//[ ]##/ } talbum=${talbum//${~wsub}/$wrep} tart=${tart//\\t/ } tart=${tart//${~psub}/$prep} tart=${tart//[ ]##/ } tart=${tart//${~wsub}/$wrep} # Various Artists is sometimes "Various" and other times "Various # Artists", we'll call them all "Various_Artists". if [[ "${tart}" == "Various" ]]; then tart=Various_Artists fi dir=$(pwd)/${tart}/${talbum} [[ -d ${dir} ]] || mkdir -p ${dir} if [[ $keep_flac == yes ]]; then fdir=${dir}/flac mkdir -p ${fdir} fi } # Store song titles, artist, and album names. prep () { zid=($(zdiscid)) tracks=$zid[2] [[ -d ${TMPPREFIX} ]] || mkdir -p ${TMPPREFIX} if [[ $online == yes ]]; then do_online do_offline fi typeset -A artist typeset -A song typeset -A filename if [[ $zedit == yes && -n $rcfile ]]; then _zedit=${EDITOR:-vi} cat>$rcfile<&1 | grep -q ${genre} || genre=Other for (( i=1; i<=$tracks; ++i )); do echo MP3-Ripping: $song[$i], by: $artist[$i], to: $filename[$i] cdparanoia -X $i $wavfile if [[ -f $wavfile ]]; then if [[ $keep_flac == yes ]]; then flacfile=${filename[$i]/%mp3/flac} flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \ $year $genre $i $zcomment flactomp3 ${flacfile} ${dir}/$filename[$i] else lame -h --tt $song[$i] --ta $artist[$i] --tl $album \ --ty $year --tg $genre --tc $zcomment $wavfile \ --tn $i ${dir}/$filename[$i] fi [[ $write_playlist == yes ]] && echo ${dir}$filename[$i] >> $plfile fi [[ -f $wavfile ]] && rm -f $wavfile done } # Encode to OGG Vorbis oggrip () { for (( i=1; i<=$tracks; ++i )); do echo OGG-Ripping: $song[$i], by: $artist[$i], to: $filename[$i] cdparanoia -X $i $wavfile if [[ -f $wavfile ]]; then if [[ $keep_flac == yes ]]; then flacfile=${filename[$i]/%ogg/flac} flacenc ${flacfile} ${wavfile} $song[$i] $artist[$i] $album \ $year $genre $i $zcomment oggenc -q3 -o ${dir}/$filename[$i] ${fdir}/$flacfile else oggenc -q3 -o ${dir}/$filename[$i] -t $song[$i] -a $artist[$i] \ -l $album -d $year -G $genre -c "comment=$zcomment" \ -N $i $wavfile rm $wavfile fi [ -f ${ARTWORK} ] && album_art ${dir}/$filename[$i] [[ $write_playlist == yes ]] && echo ${dir}/$filename[$i] >> $plfile fi done } # Make sure we have everything we need. # This might be a little over the top, but I don't see any point in # checking for `oggenc' if you are encoding to MP3, likewise, there's # no point in checking for `lame' if you are encoding to Ogg/Vorbis. chkreqs () { reqs=(cdparanoia lame oggenc flac) whence -w $reqs[1] 1>/dev/null if [[ $? -gt 0 ]]; then echo $ourname: error: Missing $reqs[1] >&2 rv=3 fi case $atype in (mp3) whence -w $reqs[2] 1>/dev/null if [[ $? -gt 0 ]]; then echo $ourname: error: Missing $reqs[2] >&2 rv=3 fi ;; (ogg) whence -w $reqs[3] 1>/dev/null if [[ $? -gt 0 ]]; then echo $ourname: error: Missing $reqs[3] >&2 rv=3 fi ;; esac if [[ $keep_flac == yes ]]; then whence -w $reqs[4] 1>/dev/null if [[ $? -gt 0 ]]; then echo $ourname: error: Missing $reqs[4] >&2 rv=3 fi fi if [[ $rv -eq 3 ]]; then exit $rv else return 0 fi } # Command line parsing args=mofihVO-:r:a:s:t:e:d:p: flavour=0 rc=0 interactive=no rcfile=" " zedit=" " while getopts $args opts; do case $opts in (-) case $OPTARG in (mp3) atype=mp3; (( ++flavour )) ;; (ogg) atype=ogg; (( ++flavour )) ;; (edit_rcfile?*) rcfile=${OPTARG/edit_rcfile=/} (( ++rc )) zedit=yes ;; (flac_copy) keep_flac=yes ;; (type?*) atype=${OPTARG/type=/}; (( ++flavour )) ;; (interactive) interactive=yes; (( ++rc )) ;; (online) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;; (rcfile?*) rcfile=${OPTARG/rcfile=/}; (( ++rc )) ;; (album?*) album=${OPTARG/album=/} ;; (sameartist?*) art=${OPTARG/sameartist=/} ;; (dir?*) dir=${OPTARG/dir=/} ;; (playlist?*) write_playlist=yes; plfile=${OPTARG/playlist=/} ;; (version) _version; exit 0 ;; (help) cmd=usage; rv=0 ;; (*) echo $ourname: error: bad option: --$OPTARG >&2 exit 2 ;; esac ;; (i) interactive=yes; (( ++rc )) ;; (O) online=yes; (( ++rc )); rcfile=${TMPPREFIX}/zrcfile ;; (m) atype=mp3; (( ++flavour )) ;; (o) atype=ogg; (( ++flavour )) ;; (e) rcfile=$OPTARG; (( ++rc )); zedit=yes ;; (f) keep_flac=yes ;; (r) rcfile=$OPTARG; (( ++rc )) ;; (a) album=$OPTARG ;; (s) art=$OPTARG ;; (t) atype=$OPTARG; (( ++flavour )) ;; (d) dir=$OPTARG ;; (p) write_playlist=yes; plfile=$OPTARG ;; (V) _version; exit 0 ;; (h) cmd=usage; rv=0 ;; (*) rv=2 ;; esac done shift $(( $OPTIND - 1 )) if [[ $cmd != usage ]]; then case $flavour in (0) echo $ourname: error: no audio format specified >&2 if [[ $rc -ne 1 ]]; then echo $ourname: error: improper use of interactice/rcfile option >&2 fi rv=2 ;; (1) if [[ $rc -ne 1 ]]; then echo $ourname: error: improper use of interactive/rcfile option >&2 rv=2 else typeset -A prepargs prepargs=(1 $atype 2 $interactive 3 $rcfile 4 $zedit) chkreqs prep $prepargs fi ;; (*) echo $ourname: error: multiple or invalid audio formats specified >&2 if [[ $rc -ne 1 ]]; then echo $ourname: error: improper use of interactice/rcfile option >&2 fi rv=2 ;; esac else usage fi exit $rv ### zcdrip ends here