123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- #!/bin/sh
- # Burn audio files to a blank CD.
- # Copyright 2016-2019 orbea
- # All rights reserved.
- #
- # Redistribution and use of this script, with or without modification, is
- # permitted provided that the following conditions are met:
- #
- # 1. Redistributions of this script must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- #
- # 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 AUTHOR 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.
- # This is required for the legacy ash in Slackware and can be removed
- # once ash is no longer included in any supported Slackware releases.
- # shellcheck disable=SC2004
- # https://github.com/koalaman/shellcheck/wiki/SC2004
- IFS='
- '
- \unset -f command printf read unalias : 2>/dev/null
- \unalias -a 2>/dev/null
- PATH="$(command -p getconf PATH):$PATH"
- LC_ALL=C; export LC_ALL
- set -euf
- au=; audio=; cmd=; farr=; final=; flac=; format=; fsize=; ignore=; list=; narr=
- new=; ord=; out=; req=; sec=; tmp=; warr=; wav=
- DEBUG=; DRYRUN=; VERBOSE=
- PRGNAM=burncd
- DEVICE='/dev/sr1'
- OUTPUT='/tmp/CD'
- die () {
- ret="$1"; shift
- case "$ret" in
- : ) printf %s\\n "$@" >&2; return 0 ;;
- 0 ) printf %s\\n "$@" ;;
- * ) printf %s\\n "$@" >&2 ;;
- esac
- exit "$ret"
- }
- escape () {
- word="$1"; base=
- case "$word" in
- *\\*|*\$*|*\"*|*\`* )
- while [ "$word" != '' ]; do
- first="$(printf %s "${word%"${word#?}"}")"
- word="$(printf %s "${word#?}")"
- case "$first" in
- \\|\$|\"|\` ) first="\\$first" ;;
- * ) : ;;
- esac
- base="${base}$first"
- done
- ;;
- * )
- base="$word"
- ;;
- esac
- }
- exists () {
- v=1
- while [ $# -gt 0 ]; do
- arg="$1"; shift
- case "$arg" in ''|*/) continue ;; esac
- x="${arg##*/}" z="${arg%/*}"
- [ ! -f "$z/$x" ] || [ ! -x "$z/$x" ] && [ "$z/$x" = "$arg" ] && continue
- [ "$x" = "$z" ] && [ -x "$z/$x" ] && [ ! -f "$arg" ] && z=
- p=":$z:$PATH"
- while [ "$p" != "${p#*:}" ]; do
- p="${p#*:}"; d="${p%%:*}"
- if [ -f "$d/$x" ] && [ -x "$d/$x" ]; then
- printf %s\\n "$d/$x"
- v=0
- break
- fi
- done
- done
- return $v
- }
- mklist () {
- ch=; ll=
- action="$1"; shift
- { empty "$action" + && { ml="$1"; eval "ch=\"\$$ml\""; shift; }; } || :
- while [ $# -gt 0 ]; do
- mk="$1"; shift
- case "$mk" in -*) break ;; esac
- n="$(($n+1))"
- empty "$action" + || { ll="$mk"; break; } || :
- case "$ll" in *$mk*) continue ;; esac
- case "$ch" in *$mk*) continue ;; esac
- case "$mk" in
- au|flac|wav ) ll="${ll} $mk" ;;
- * ) die 1 "Unrecognized audio format '$mk', use -h for help." ;;
- esac
- done
- { empty "$ll" && return 0; } || :
- case "$action" in
- + ) eval "$ml=\"\$${ml#${ml%%[! ]*}} ${ll#${ll%%[! ]*}}\"" ;;
- * ) eval "$action=${ll:-\$$action}" ;;
- esac
- }
- CDRECORD="${CDRECORD:-$(exists cdrecord || :)}"
- FLAC="${FLAC:-$(exists flac || :)}"
- SOX="${SOX:-$(exists sox || :)}"
- FILE="$(exists file || :)"
- dry () { case "${DRYRUN:-0}" in 1) : ;; *) "$@" ;; esac; }
- empty () { case "${1:-}" in "${2:-}") return 0 ;; *) return 1 ;; esac; }
- exclude () { case "$ignore" in *$1*) fmt= ;; *) fmt="$2"; suf="$3" ;; esac; }
- log () { case "${VERBOSE:-0}" in 1) printf %s\\n "$@" ;; *) : ;; esac; }
- printn () { eval "set -- $1"; printf %s\\n $#; }
- quiet () { case "${DEBUG:-0}" in 1) "$@" ;; *) "$@" 2>/dev/null ;; esac; }
- quit () { err=$?; eval "set -- $out $tmp"; command -p rm -f -- "$@"; }
- use () { :; }
- usage="$PRGNAM - Burn audio files from the current directory to a blank CD.
- Usage: $PRGNAM [OPTIONS]
- -d, --debug, Debug output from external programs.
- -e, --exclude, Exclude audio formats.
- -f, --format, Choose the audio format.
- -h, --help, Show this help message.
- -n, --dry-run, Enable a test run without burning to a CD.
- -o, --output, Path of the temporary directory.
- -v, --verbose, Verbose logging.
- -V. --version, Show the $PRGNAM version number.
- -z, --device, Path of the CD drive.
- Supported audio formats:
- au, flac, wav
- Environment variables:
- CDRECORD : Path of the cdrecord binary. ($CDRECORD)
- FLAC : Path of the flac binary. ($FLAC)
- SOX : Path of the sox binary. ($SOX)
- To use cdrecord as a normal user:
- # chown root:somegroup $CDRECORD
- # chmod 4710 $CDRECORD"
- for flag do
- case "$flag" in
- -- ) break ;;
- -d|--debug ) : ;;
- -e|--exclude ) : ;;
- -f|--format ) : ;;
- -h|--help ) die 0 "$usage" ;;
- -n|--dry-run ) : ;;
- -o|--output ) : ;;
- -v|--verbose ) : ;;
- -V|--version ) die 0 "$PRGNAM 0.0" ;;
- -z|--device ) : ;;
- * ) die 1 "Unrecognized option '$flag', use -h for help." ;;
- esac
- done
- while [ $# -gt 0 ]; do
- n=0; option="$1"; shift
- case "$option" in
- -- ) break ;;
- -d|--debug ) DEBUG=1 ;;
- -e|--exclude ) empty "${1+x}" || mklist + ignore "$@" ;;
- -f|--format ) empty "${1+x}" || mklist + format "$@" ;;
- -n|--dry-run ) DRYRUN=1 ;;
- -o|--output ) empty "${1+x}" || mklist OUTPUT "$1" ;;
- -v|--verbose ) VERBOSE=1 ;;
- -z|--device ) empty "${1+x}" || mklist DEVICE "$1" ;;
- esac
- shift "$n"
- done
- if ! empty "$(command -p ls)"; then
- set +f
- set -- ./*
- set -f
- else
- die 1 'ERROR: Directory is empty.'
- fi
- log '' 'Configured options:' " DEVICE = $DEVICE" " OUTPUT = $OUTPUT" \
- " Preferred audio formats = ${format# }" \
- " Excluded audio foramts = ${ignore# }" ''
- for l in wav au flac; do
- case "$format" in
- *$l* ) : ;;
- * ) format="${format} $l" ;;
- esac
- done
- for l in $(printf %s "$format"); do
- case "$ignore" in
- *$l* ) : ;;
- * ) final="${final} $l" ;;
- esac
- done
- n=0
- for f do
- [ -d "$f" ] || [ -r "$f" ] ||
- { die : 'WARNING: File can not be read.' "Skipping '$f'."; continue; }
- file="$(command -p "$FILE" "$f")"
- type="$(printf %s "${file#"$f: "}")"
- case "$type" in
- *FLAC* ) exclude flac farr wav ;;
- *Sun/NeXT* ) exclude au narr au ;;
- *WAVE* ) exclude wav warr wav ;;
- * ) fmt= ;;
- esac
- if [ "${fmt}" ]; then
- head="${f##*/}"
- name="${head%.*}.$suf"
- if [ -e "$OUTPUT/$name" ] || [ -e "$OUTPUT/tmp-$name" ]; then
- die : "WARNING: File found in $OUTPUT." "Skipping '$f'."
- continue
- fi
- escape "$head"
- cmn="$type::${base}:$n:$fmt:$suf"
- case $fmt in
- farr ) flac="$flac \"${cmn}:\\\$FLAC:16 bit:44.1 kHz:stereo\"" ;;
- narr ) au="$au \"${cmn}:null:16-bit:44100 Hz:stereo\"" ;;
- warr ) wav="$wav \"${cmn}:null:16 bit:44100 Hz:stereo\"" ;;
- esac
- n=$(($n+1))
- fi
- done
- for f in $(printf %s "$final"); do
- eval "audio=\"\${audio} \${$f}\""
- done
- eval "set -- $audio"
- for f do
- song="${f#*::}"
- for i in cnl khz bit ext end lst pos; do
- eval "$i=\"\${song##*:}\""
- song="${song%:*}"
- done
- add=1
- eval "set -- $new"
- while [ $# -gt 0 ]; do
- empty "${1%.*}" "${song%.*}" && { add=0; break; }
- shift
- done
- empty "$add" 1 || continue
- type="${f%%::*}"; var=
- escape "$song"
- name="${base%.*}.${end:?}"
- new="$new \"$base\""
- ord="$ord \"$OUTPUT/$name:${pos:?}\""
- out="$out \"$OUTPUT/$name\""
- tmp="$tmp \"$OUTPUT/tmp-$name\""
- eval "${lst:?}=\"\${$lst} \\\"\$base\"\\\""
- if ! empty "${ext:?}" null; then
- case "$req" in
- *$ext* ) : ;;
- * ) req="${req} $ext" ;;
- esac
- fi
- for s in "${bit:?}" "${khz:?}" "${cnl:?}"; do
- case "$type" in *$s*) : ;; *)
- case "$s" in
- "$bit" ) var="${var} -b 16" ;;
- "$khz" ) var="${var} -r 44.1k" ;;
- "$cnl" ) var="${var} -c 2" ;;
- esac ;;
- esac
- done
- empty "$var" || cmd="$cmd \"$name:${var#"${var%%[! ]*}"}\""
- done
- empty "$new" && die 1 "ERROR: No music files found in $PWD."
- for prgnam in \$CDRECORD $(printf %s "$req"); do
- dep="$(eval printf %s "$prgnam")"
- exists "$dep" >/dev/null 2>&1 && { log "$(exists "$dep"): ok"; continue; }
- if empty "$dep"; then
- case "$prgnam" in
- \$CDRECORD ) dep=cdrecord ;;
- \$FLAC ) dep=flac ;;
- esac
- fi
- die 1 "ERROR: $dep is not in $PATH."
- done
- if exists "$SOX" >/dev/null 2>&1; then
- eval "set -- $new"
- for t in $(quiet "$SOX" --i -D -- "$@"); do
- sec="$(($sec+$(printf '%.0f' "$t")))"
- done
- dur="$(printf '%dh %dm %ds' $(($sec/3600)) $(($sec%3600/60)) $(($sec%60)))"
- log "$(exists "$SOX"): ok" "Total duration: $dur"
- if [ "$sec" -gt 4800 ]; then
- die 1 "ERROR: Total duration of $dur exceeds 80 minutes."
- fi
- elif ! empty "$cmd"; then
- die 1 'ERROR: sox is required for converting to 16 bit / 44.1 kHz / stereo.'
- else
- die : 'WARNING: sox is required for checking the duration of the audio files.'
- fi
- while :; do
- if [ ! -e "$DEVICE" ]; then
- die : "WARNING: $DEVICE not found." 'Configure the device. (i.e. /dev/sr0)'
- read -r DEVICE
- continue
- fi
- if empty $DRYRUN; then
- blank="$(quiet "$CDRECORD" dev="$DEVICE" -minfo)" ||
- die 1 "$CDRECORD: operation failed."
- case "$blank" in *Blank*) : ;; *)
- die : "WARNING: Blank CD not found in $DEVICE." \
- 'Insert a blank CD. Continue? (y/n)'
- read -r answer
- case "$answer" in
- [yY]|[yY][eE][sS] ) continue ;;
- * ) die 0 "Blank CD not found in $DEVICE. Aborting ..." ;;
- esac ;;
- esac
- fi
- log "Using blank CD found in $DEVICE."
- break
- done
- farth=; fburn=; fmsg=; narth=; nburn=; nmsg=; warth=; wburn=; wmsg=
- for b in "$farr" "$narr" "$warr"; do
- case "$b" in
- '' )
- :
- ;;
- "$farr" )
- farth='21/10'
- fburn="quiet \"\$FLAC\" --output-prefix=\"\$OUTPUT\"/ --decode -- \"\$@\""
- fmsg='Decoding flac files ...'
- list="$list \"'\\\$farr' '\\\$farth' '\\\$fburn' '\\\$fmsg'\""
- ;;
- "$narr" )
- narth='19/13'
- nburn="quiet die : \"\$@\"; for a do command -p cp -- \
- \"\$a\" \"\$OUTPUT/\${a%.*}.au\"; done"
- nmsg='Copying Sun/NeXT audio files ...'
- list="$list \"'\\\$narr' '\\\$narth' '\\\$nburn' '\\\$nmsg'\""
- ;;
- "$warr" )
- warth='19/13'
- wburn="quiet die : \"\$@\"; for a do command -p cp -- \
- \"\$a\" \"\$OUTPUT/\${a%.*}.wav\"; done"
- wmsg='Copying wav files ...'
- list="$list \"'\\\$warr' '\\\$warth' '\\\$wburn' '\\\$wmsg'\""
- ;;
- esac
- done
- use "$farth" "$fburn" "$fmsg" "$narth" "$nburn" "$nmsg" "$warth" "$wburn" \
- "$wmsg"
- command -p mkdir -p -- "$OUTPUT"
- eval "set -- $list"
- for i do
- eval "set -- $i"
- eval "math=$2"
- eval "eval \"set -- $1\""
- fline="$(command -p wc -c -- "$@" | command -p tail -n1)"
- asize="$(((${fline%%[ ]*}+512)/1024*${math:?}))"
- fsize="$(($fsize+$asize))"
- done
- tline="$(command -p df -P -- "$OUTPUT" |
- { read -r _; read -r l; printf %s "$l"; })"
- eval "set -- $tline"
- tsize="$(eval "printf %s \${4}")"
- log "Estimated size of audio files: $fsize KB" \
- "Free space in $OUTPUT: $tsize KB"
- if [ "$fsize" -ge "$tsize" ]; then
- c=0
- for size in "$fsize" "$tsize"; do
- eval "mb$c=\"$((($size+512)/1024)) MB\""
- c="$(($c+1))"
- done
- die 1 "ERROR: ${mb0:?} required in $OUTPUT to decode flac files." \
- "Disk space: ${mb1:?}"
- fi
- if [ "$(printn "$list")" -gt 1 ]; then
- out=; n=0
- arg=$(printn "$ord")
- while [ "$(printn "$out")" -lt "$arg" ]; do
- eval "set -- $ord"
- for i do
- if empty "${i##*:}" $n; then
- escape "${i%:*}"
- out="$out \"$base\""
- break
- fi
- done
- n=$(($n+1))
- done
- fi
- trap 'quit; trap - EXIT; exit $err' EXIT INT
- eval "set -- $list"
- for i do
- eval "set -- $i"
- eval "burn=$3"
- eval "printf '%s\\n' \"$4\""
- eval "eval \"set -- $1\""
- eval "${burn:?}"
- done
- eval "set -- $cmd"
- for i do
- file="${i%:*}"
- opt="${i##*:}"
- log "Converting $file" "sox options: $opt"
- eval "set -- $opt"
- quiet "$SOX" -- "$OUTPUT/$file" "$@" -- "$OUTPUT/tmp-$file"
- command -p rm -f -- "$OUTPUT/$file"
- command -p mv -- "$OUTPUT/tmp-$file" "$OUTPUT/$file"
- done
- eval "set -- $out"
- log 'Burning audio files:' "$@"
- dry quiet "$CDRECORD" dev="$DEVICE" -dao -audio -pad -- "$@"
- die 0 'CD burning finished successfully.'
|