burncd 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. #!/bin/sh
  2. # burncd - Burn audio files to a blank CD.
  3. # Copyright 2016-2020 orbea
  4. # All rights reserved.
  5. #
  6. # Redistribution and use of this script, with or without modification, is
  7. # permitted provided that the following conditions are met:
  8. #
  9. # 1. Redistributions of this script must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. #
  12. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
  13. # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  14. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  15. # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  16. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  17. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  18. # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  19. # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  20. # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  21. # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. # This is required for the legacy ash in Slackware and can be removed
  23. # once ash is no longer included in any supported Slackware releases.
  24. # shellcheck disable=SC2004
  25. # https://github.com/koalaman/shellcheck/wiki/SC2004
  26. tab=' '
  27. IFS=" $tab
  28. "
  29. \unset -f command printf read unalias : 2>/dev/null
  30. \unalias -a 2>/dev/null
  31. PATH="$(command -p getconf PATH):$PATH"
  32. LC_ALL=C; export LC_ALL
  33. set -euf
  34. chkdir () {
  35. if [ ! -d "$1" ]; then
  36. die 1 "$PRGNAM: '$1': Not a directory." "$help"
  37. elif [ ! -x "$1" ]; then
  38. die 1 "$PRGNAM: Cannot open directory '$1': Permission denied." "$help"
  39. elif [ ! -r "$1" ]; then
  40. die 1 "$PRGNAM: Cannot read directory '$1': Permission denied." "$help"
  41. else
  42. return 0
  43. fi
  44. return 1
  45. }
  46. chkformat () {
  47. case "$1" in
  48. au|flac|mp3|wav ) return 0 ;;
  49. * ) return 1 ;;
  50. esac
  51. }
  52. chkformat2 () {
  53. if chkformat "$1"; then
  54. eval "tmpv=\"\$$2\""
  55. match "$1" "$tmpv" || eval "$2=\"\$${2} \$1\""
  56. else
  57. die 1 "$PRGNAM: Unrecognized audio format '$1'" "$help"
  58. fi
  59. }
  60. chkvar () {
  61. chkvar=1
  62. case "$2" in
  63. ''|CONFIG|PLAYLIST ) addvar "$1" "${2:-files}" ;;
  64. EXCLUDE|FORMAT ) chkformat2 "$1" "$2" ;;
  65. INPUT ) ! chkdir "$1" || addvar "${1%${1##*[!/]}}/" "$2" ;;
  66. * ) eval "$2=\"$1\""; VAR= ;;
  67. esac
  68. }
  69. die () {
  70. ret="$1"; shift
  71. case "$ret" in
  72. : ) printf %s\\n "$@" >&2; return 0 ;;
  73. 0 ) printf %s\\n "$@" ;;
  74. * ) printf %s\\n "$@" >&2 ;;
  75. esac
  76. exit "$ret"
  77. }
  78. escape () {
  79. word="$1"; base=
  80. case "$word" in
  81. *\\*|*\$*|*\"*|*\`* )
  82. while [ "$word" != '' ]; do
  83. first="$(printf %s "${word%"${word#?}"}")"
  84. word="$(printf %s "${word#?}")"
  85. case "$first" in
  86. \\|\$|\"|\` ) first="\\$first" ;;
  87. * ) : ;;
  88. esac
  89. base="${base}$first"
  90. done
  91. ;;
  92. * )
  93. base="$word"
  94. ;;
  95. esac
  96. }
  97. handleargs () {
  98. case "$1" in
  99. -- ) shift; for i do addvar "$i" files; done; return 1 ;;
  100. - ) while IFS= read -r stdin; do addvar "$stdin" files; done; VAR= ;;
  101. -c|--config ) VAR=CONFIG ;;
  102. -C|--cdrecord ) VAR=CDRECORD ;;
  103. -d|--debug ) DEBUG=1; VAR= ;;
  104. -e|--exclude ) VAR=EXCLUDE ;;
  105. -f|--format ) VAR=FORMAT ;;
  106. -F|--flac ) VAR=FLAC ;;
  107. -h|--help ) die 0 "$usage" ;;
  108. -i|--input ) VAR=INPUT ;;
  109. -M|--mpg123 ) VAR=MPG123 ;;
  110. -n|--dry-run ) DRYRUN=1; VAR= ;;
  111. -N|--no-conf ) NOCONFIG=1; VAR= ;;
  112. -o|--output ) VAR=OUTPUT ;;
  113. -p|--playlist ) VAR=PLAYLIST ;;
  114. -S|--sox ) VAR=SOX ;;
  115. -v|--verbose ) VERBOSE=1; VAR= ;;
  116. -V|--version ) die 0 "$PRGNAM 0.0" ;;
  117. -z|--device ) VAR=DEVICE ;;
  118. --* ) die 1 "$PRGNAM: Unrecognized option '$1'" "$help" ;;
  119. -* ) shortargs "${1#-}" ;;
  120. * ) chkvar "$1" "$VAR" ;;
  121. esac
  122. }
  123. shortargs () {
  124. arg="$1"
  125. while [ -n "${arg}" ]; do
  126. flag="$(printf %s "${arg%"${arg#?}"}")"
  127. arg="$(printf %s "${arg#?}")"
  128. case "$flag" in
  129. c ) option=config ;;
  130. C ) option=cdrecord ;;
  131. d ) option=debug ;;
  132. e ) option=exclude ;;
  133. f ) option=format ;;
  134. F ) option=flac ;;
  135. h ) option=help ;;
  136. i ) option=input ;;
  137. M ) option=mpg123 ;;
  138. n ) option=dry-run ;;
  139. N ) option=no-conf ;;
  140. o ) option=output ;;
  141. p ) option=playlist ;;
  142. S ) option=sox ;;
  143. v ) option=verbose ;;
  144. V ) option=version ;;
  145. z ) option=device ;;
  146. * ) die 1 "$PRGNAM: Unrecognized option -- '$flag'" "$help" ;;
  147. esac
  148. handleargs "--$option"
  149. [ -z "${VAR}" ] || { [ -z "${arg}" ] || chkvar "$arg" "$VAR"; break; }
  150. done
  151. }
  152. addvar () { escape "$1"; eval "$2=\"\${$2} \\\"\$base\"\\\""; }
  153. match () { case "$2" in *$1*) return 0 ;; *) return 1 ;; esac; }
  154. next () { case "$1" in -*) return 0 ;; *) return 1 ;; esac; }
  155. files=; tmpv=
  156. CDRECORD=; CONFIG=; DEBUG=; DEVICE=; DRYRUN=; EXCLUDE=; FORMAT=; FLAC=; INPUT=
  157. MPG123=; NOCONFIG=; OUTPUT=; PLAYLIST=; SOX=; VAR=; VERBOSE=
  158. PRGNAM=burncd
  159. CONF=$PRGNAM.conf
  160. help="Try '$PRGNAM --help' for more information."
  161. usage="$PRGNAM - Burn audio files to a blank CD from the command line, current
  162. directory, playlist or standard input.
  163. Usage: $PRGNAM [OPTIONS] [file ...]
  164. -c, --config, Path of the configuration files.
  165. -C, --cdrecord, Path of the cdrecord binary.
  166. -d, --debug, Debug output from external programs.
  167. -e, --exclude, Exclude audio formats.
  168. -f, --format, Choose the audio format.
  169. -F, --flac, Path of the flac binary.
  170. -h, --help, Show this help message.
  171. -i, --input, Path of the default directories.
  172. -M, --mpg123, Path of the mpg123 binary.
  173. -n, --dry-run, Enable a test run without burning to a CD.
  174. -N, --no-conf, Do not load default configuration files.
  175. -o, --output, Path of the temporary directory.
  176. -p, --playlist, Read audio files from a playlist.
  177. -S, --sox, Path of the sox binary.
  178. -v, --verbose, Verbose logging.
  179. -V. --version, Show the $PRGNAM version number.
  180. -z, --device, Path of the CD drive.
  181. When 'file' is -, read standard input.
  182. Supported audio formats:
  183. au, flac, mp3, wav
  184. Environment variables:
  185. XDG_CONFIG_HOME : XDG base directory.
  186. XDG_CONFIG_DIRS : List of colon-separated directories.
  187. Configuration files:
  188. $PRGNAM looks for configuration files in the following order:
  189. 1. \$XDG_CONFIG_HOME/$PRGNAM/$CONF
  190. (Default: \$HOME/.config/$PRGNAM/$CONF)
  191. 2. \$HOME/.$CONF
  192. 3. \$XDG_CONFIG_DIRS/$PRGNAM/$CONF
  193. (Default: /etc/xdg/$PRGNAM/$CONF)
  194. 4. /etc/$CONF
  195. 5. ./$CONF
  196. To use cdrecord as a normal user:
  197. # chown root:somegroup /usr/bin/cdrecord
  198. # chmod 4710 /usr/bin/cdrecord"
  199. while [ $# -gt 0 ]; do
  200. chkvar=
  201. flag="$1"
  202. handleargs "$@" || break
  203. shift
  204. if [ -n "${VAR}" ] && [ -z "${chkvar}" ] && next "${1--}"; then
  205. next "${flag#-}" ||
  206. die 1 "$PRGNAM: option requires an argument -- '${flag#-}'" "$help"
  207. die 1 "$PRGNAM: option '$flag' requires an argument" "$help"
  208. fi
  209. done
  210. chkdep () {
  211. for deps do
  212. bin="${deps##*:}"
  213. DEP="$(exists "$bin")" || return 1
  214. eval "${deps%:*}=\"\$DEP\""
  215. done
  216. return 0
  217. }
  218. exists () {
  219. r=0; cwd="$(pwd)"
  220. while [ $# -gt 0 ]; do
  221. v=1; arg="$1"; shift
  222. case "$arg" in
  223. ''|*/ )
  224. :
  225. ;;
  226. /* )
  227. if [ -f "$arg" ] && [ -x "$arg" ]; then
  228. printf %s\\n "$arg"
  229. v=0
  230. fi
  231. ;;
  232. ./* )
  233. if [ -f "$arg" ] && [ -x "$arg" ]; then
  234. pre="$(cd -- "${arg%%/*}/" && pwd)"
  235. printf %s\\n "${pre%/}/$arg"
  236. v=0
  237. fi
  238. ;;
  239. */* )
  240. if [ -f "$arg" ] && [ -x "$arg" ]; then
  241. printf %s\\n "$(cd -- "${arg%%/*}/.." && pwd)/$arg"
  242. v=0
  243. fi
  244. ;;
  245. * )
  246. if [ -n "${PATH+x}" ]; then
  247. p=":${PATH:-$cwd}"
  248. while [ "$p" != "${p#*:}" ] && [ -n "${p#*:}" ]; do
  249. p="${p#*:}"; x="${p%%:*}"; z="${x:-$cwd}"; d="${z%/}/$arg"
  250. if [ -f "$d" ] && [ -x "$d" ]; then
  251. case "$d" in
  252. /* ) : ;;
  253. ./* ) pre="$(cd -- "${d%/*}/" && pwd)"; d="${pre%/}/$d" ;;
  254. * ) d="$(cd -- "${d%/*}/" && pwd)/$arg" ;;
  255. esac
  256. printf %s\\n "$d"
  257. v=0
  258. break
  259. fi
  260. done
  261. fi
  262. ;;
  263. esac
  264. [ $v = 0 ] || r=1
  265. done
  266. return $r
  267. }
  268. chkdep FILE:file LS:ls MKDIR:mkdir MV:mv READLINK:readlink RM:rm ||
  269. die 1 "ERROR: no $bin in ($PATH)"
  270. skipping="${tab}Skipping"
  271. # contains: '\r', '^M', ' ', '\t' and '\n'
  272. SPACE=" $IFS"
  273. addconf () {
  274. [ -n "${1}" ] || return 0
  275. eval "CONF_$var=\"\${2:-$1}\""
  276. addvar "${tab}$var='$1'" set
  277. }
  278. chkconf () {
  279. case "$1" in
  280. CDRECORD|DEBUG|DEVICE|DRYRUN|EXCLUDE|FORMAT|FLAC|INPUT|MPG123|OUTPUT|SOX| \
  281. VERBOSE )
  282. return 0
  283. ;;
  284. esac
  285. return 1
  286. }
  287. chkfile () {
  288. if [ -h "$1" ]; then
  289. in="$(command -p "${READLINK:?}" -- "$1")"
  290. else
  291. in="$1"
  292. fi
  293. shift
  294. file="$(command -p "${FILE:?}" "$@" "$in")"
  295. type="$(printf %s "${file#"$in: "}")"
  296. }
  297. chktext () {
  298. if [ ! -r "$1" ]; then
  299. die : "WARNING: $2 file cannot be read." "$skipping '$1'."
  300. return 1
  301. fi
  302. chkfile "$1" -i --
  303. case "$type" in
  304. *empty*|*text* ) return 0 ;;
  305. * ) die : "WARNING: $2 is not a text file." "$skipping '$1'."; return 1 ;;
  306. esac
  307. }
  308. conf () {
  309. if chktext "$1" Config; then
  310. set=
  311. line=1
  312. while read -r var chr val cmt; do
  313. if ! comment "$var"; then
  314. if [ "$chr" = = ]; then
  315. if chkconf "$var"; then
  316. if ! comment "$val"; then
  317. eval "tmpv=\"\$$var\""
  318. if empty "$tmpv"; then
  319. add=
  320. if [ "$var" = EXCLUDE ] || [ "$var" = FORMAT ]; then
  321. for i in $(printf %s "$val $cmt"); do
  322. if comment "$i"; then
  323. break
  324. elif chkformat "$i"; then
  325. match "$i" "$add" || add="${add} $i"
  326. else
  327. die : "WARNING: $1:$line:" \
  328. "$skipping unknown audio format '$i'."
  329. fi
  330. done
  331. addconf "${add# }"
  332. elif [ "$var" = INPUT ]; then
  333. for i in $(printf %s "$val $cmt"); do
  334. if comment "$i"; then
  335. break
  336. elif [ ! -d "$i" ]; then
  337. die : "WARNING: $1:$line:" \
  338. "${tab}'$i': Not a directory."
  339. elif [ ! -x "$i" ]; then
  340. die : "WARNING: $1:$line:" \
  341. "${tab}Cannot open directory '$i': Permission denied."
  342. elif [ ! -r "$i" ]; then
  343. die : "WARNING: $1:$line:" \
  344. "${tab}Cannot read directory '$i': Permission denied."
  345. else
  346. add="${add} \"${i%${i##*[!/]}}/\""
  347. fi
  348. done
  349. addconf "${add# }"
  350. else
  351. escape "$val"
  352. addconf "$base" "$val"
  353. fi
  354. match "$var" "$confv" || confv="${confv} $var"
  355. fi
  356. [ "$var" = EXCLUDE ] || [ "$var" = FORMAT ] ||
  357. [ "$var" = INPUT ] || comment "$cmt" ||
  358. die : "WARNING: $1:$line:" "$skipping excess values '$cmt'."
  359. fi
  360. else
  361. die : "WARNING: $1:$line:" "$skipping unknown variable '$var'."
  362. fi
  363. elif comment "$chr"; then
  364. die : "WARNING: $1:$line:" "${tab}Missing = operand."
  365. else
  366. die : "WARNING: $1:$line:" "${tab}Unrecognized character '$chr'."
  367. fi
  368. fi
  369. line=$(($line+1))
  370. done < "$in"
  371. addvar "Loaded configuration file '$1'." confl
  372. [ -z "$set" ] || { eval "set -- $set"
  373. confl="${confl} $(printf ' "%s"' "$@")"; }
  374. return 0
  375. fi
  376. return 1
  377. }
  378. play () {
  379. if chktext "$1" Playlist; then
  380. while read -r title; do
  381. empty "$title" || addvar "$title" files
  382. done < "$in"
  383. addvar "Loaded playlist file '$1'." playl
  384. return 0
  385. fi
  386. return 1
  387. }
  388. xdgdirs () {
  389. p=":$1"
  390. while [ "$p" != "${p#*:}" ]; do
  391. p="${p#*:}"
  392. dir="${p%%:*}"
  393. { ! empty "$dir" && conf "$dir/$PRGNAM/$CONF"; } || continue
  394. return 0
  395. done
  396. return 1
  397. }
  398. chkesc () { case "$1" in '') base="$2" ;; *) escape "$2" ;; esac ; }
  399. comment () { case "$1" in \#*|'') return 0 ;; *) return 1 ;; esac; }
  400. empty () { case "${1:-}" in *[!$SPACE]*) return 1 ;; *) return 0 ;; esac; }
  401. exclude () { case "$EXCLUDE" in *$1*) fmt= ;; *) fmt="$2"; suf="$3" ;; esac; }
  402. printn () { eval "set -- $1"; printf %s\\n $#; }
  403. quit () { err=$?; eval "set -- $out $tmp"; command -p "${RM:?}" -f -- "$@"; }
  404. play=; playl=
  405. eval "set -- $PLAYLIST"
  406. for f do
  407. ! play "$f" || play="${play} $f"
  408. done
  409. conf=; confl=; confv=
  410. eval "set -- $CONFIG"
  411. for f do
  412. ! conf "$f" || conf="${conf} $f"
  413. done
  414. [ -n "${NOCONFIG}" ] || [ -n "${conf}" ] ||
  415. conf "${XDG_CONFIG_HOME:-${HOME:=}/.config}/$PRGNAM/$CONF" ||
  416. conf "$HOME/.$CONF" || xdgdirs "${XDG_CONFIG_DIRS:-/etc/xdg}" ||
  417. conf /etc/$CONF || conf ./$CONF || :
  418. eval "set -- $confv"
  419. for var do
  420. eval "$var=\"\$CONF_$var\""
  421. done
  422. if [ -z "${files}" ] && [ -z "${play}" ]; then
  423. input=
  424. eval "set -- $INPUT"
  425. for f do
  426. empty "$(command -p "${LS:?}" "$f")" || input="${input} \"$f\"*"
  427. done
  428. fi
  429. if [ -n "${files}" ] || [ -n "${play}" ]; then
  430. eval "set -- $files"
  431. elif [ -n "${input}" ]; then
  432. set +f
  433. eval "set -- $input"
  434. set -f
  435. elif [ -z "${INPUT}" ] && ! empty "$(command -p "${LS:?}")"; then
  436. set +f
  437. set -- ./*
  438. set -f
  439. else
  440. die 1 'ERROR: Directory is empty.'
  441. fi
  442. case "$DRYRUN" in
  443. 1 ) dry () { :; } ;;
  444. * ) dry () { "$@"; } ;;
  445. esac
  446. case "$VERBOSE" in
  447. 1 ) log () { printf %s\\n "$@"; } ;;
  448. * ) log () { :; } ;;
  449. esac
  450. case "$DEBUG" in
  451. 1 ) quiet () { "$@"; } ;;
  452. * ) quiet () { "$@" 2>/dev/null; } ;;
  453. esac
  454. addtrack () {
  455. track="${2%.*}"
  456. eval "set -- $1"
  457. for i do
  458. a="${i##*/}"
  459. [ "${a%.*}" != "$track" ] || return 1
  460. done
  461. return 0
  462. }
  463. DEVICE="${DEVICE:-/dev/sr0}"
  464. OUTPUT="${OUTPUT:-/tmp/CD}"
  465. CP='cp'
  466. CDRECORD="${CDRECORD:-cdrecord}"
  467. FLAC="${FLAC:-flac}"
  468. MPG123="${MPG123:-mpg123}"
  469. SOX="${SOX:-sox}"
  470. if [ "$VERBOSE" = 1 ]; then
  471. [ -z "${confl}" ] || ( eval "set -- $confl"; log '' "$@" )
  472. [ -z "${playl}" ] || ( eval "set -- $playl"; log '' "$@" )
  473. fi
  474. log '' 'Configured options:' \
  475. "${tab}DEVICE = $DEVICE" \
  476. "${tab}OUTPUT = $OUTPUT" \
  477. "${tab}Preferred audio formats = ${FORMAT# }" \
  478. "${tab}Excluded audio formats = ${EXCLUDE# }" \
  479. "${tab}Playlist files = ${play# }" \
  480. "${tab}Configuration files = ${conf# }" ''
  481. if [ -z "${FORMAT}" ]; then
  482. FORMAT='wav au flac mp3'
  483. else
  484. for l in wav au flac mp3; do
  485. match "$l" "$FORMAT" || FORMAT="${FORMAT} $l"
  486. done
  487. fi
  488. if [ -z "${EXCLUDE}" ]; then
  489. final="$FORMAT"
  490. else
  491. final=
  492. for l in $(printf %s "$FORMAT"); do
  493. match "$l" "$EXCLUDE" || final="${final} $l"
  494. done
  495. fi
  496. cmp=; skip=
  497. au=; flac=; mp3=; wav=
  498. m=0; n=0
  499. for f do
  500. [ -d "$f" ] || [ -r "$f" ] ||
  501. { die : 'WARNING: File cannot be read.' "$skipping '$f'."; continue; }
  502. chkfile "$f" --
  503. case "$type" in
  504. *FLAC* ) exclude flac farr wav ;;
  505. *layer\ III* ) exclude mp3 marr wav ;;
  506. *Sun/NeXT* ) exclude au narr au ;;
  507. *WAVE* ) exclude wav warr wav ;;
  508. * ) fmt= ;;
  509. esac
  510. if [ -n "${fmt}" ]; then
  511. if addtrack "$cmp" "${in##*/}"; then
  512. name="${track%.*}.$suf"
  513. if [ $m -ge 99 ]; then
  514. chkesc "$files" "$f"
  515. skip="$skip \"${tab}$base\""
  516. continue
  517. elif [ -e "$OUTPUT/$name" ] || [ -e "$OUTPUT/tmp-$name" ]; then
  518. die : "WARNING: File found in $OUTPUT." "$skipping '$f'."
  519. continue
  520. fi
  521. m=$(($m+1))
  522. fi
  523. chkesc "$files" "$in"
  524. cmp="$cmp \"$base\""
  525. cmn="$type::${base}:$n:$fmt:$suf"
  526. case $fmt in
  527. farr ) flac="$flac \"${cmn}:\\\$FLAC:16 bit:44.1 kHz:stereo\"" ;;
  528. marr ) mp3="$mp3 \"${cmn}:\\\$MPG123: :44.1 kHz:Stereo\"" ;;
  529. narr ) au="$au \"${cmn}:\\\$CP:16-bit:44100 Hz:stereo\"" ;;
  530. warr ) wav="$wav \"${cmn}:\\\$CP:16 bit:44100 Hz:stereo\"" ;;
  531. * ) die 1 "ERROR: Unknown format name '$fmt'." ;;
  532. esac
  533. n=$(($n+1))
  534. elif [ -n "${files}" ]; then
  535. die : 'WARNING: Not a recognized audio file.' "$skipping '$f'."
  536. fi
  537. done
  538. [ -n "${cmp}" ] || die 1 'ERROR: No music files found.'
  539. if [ -n "${skip}" ]; then
  540. eval "set -- $skip"
  541. die : 'WARNING: Audio CDs only allows 99 tracks.' "$skipping:" "$@" ''
  542. fi
  543. audio=
  544. eval "set -- $final"
  545. for f do
  546. eval "audio=\"\${audio} \${$f}\""
  547. done
  548. cmd=; new=; ord=; out=; req=; tmp=
  549. farr=; marr=; narr=; warr=
  550. eval "set -- $audio"
  551. for f do
  552. song="${f#*::}"
  553. for i in cnl khz bit ext end lst pos; do
  554. eval "$i=\"\${song##*:}\""
  555. song="${song%:*}"
  556. done
  557. addtrack "$new" "${song##*/}" || continue
  558. var=
  559. type="${f%%::*}"
  560. addvar "$song" new
  561. head="${base##*/}"
  562. name="${head%.*}.${end:?}"
  563. ord="$ord \"$OUTPUT/$name:${pos:?}\""
  564. out="$out \"$OUTPUT/$name\""
  565. tmp="$tmp \"$OUTPUT/tmp-$name\""
  566. eval "${lst:?}=\"\${$lst} \\\"\$base\"\\\""
  567. match "${ext:?}" "$req" || req="${req} $ext"
  568. for s in "${bit:?}" "${khz:?}" "${cnl:?}"; do
  569. case "$type" in *$s*) : ;; *)
  570. case "$s" in
  571. "$bit" ) var="${var} -b 16" ;;
  572. "$khz" ) var="${var} -r 44.1k" ;;
  573. "$cnl" ) var="${var} -c 2" ;;
  574. esac ;;
  575. esac
  576. done
  577. [ -z "${var}" ] || cmd="$cmd \"$name:${var#"${var%%[! ]*}"}\""
  578. done
  579. for prgnam in \$CDRECORD $(printf %s "$req"); do
  580. dep="$(eval "printf %s \"$prgnam\"")"
  581. if chkdep "${prgnam#$}:$dep"; then
  582. log "$(eval "printf %s \"$prgnam\""): ok"
  583. continue
  584. elif empty "$dep"; then
  585. case "$prgnam" in
  586. \$CDRECORD ) dep=cdrecord ;;
  587. \$FLAC ) dep=flac ;;
  588. \$MPG123 ) dep=mpg123 ;;
  589. * ) die 1 "ERROR: Unknown variable name '$prgnam'." ;;
  590. esac
  591. fi
  592. die 1 "ERROR: no $dep in ($PATH)."
  593. done
  594. if SOX="$(exists "$SOX")"; then
  595. sec=
  596. eval "set -- $new"
  597. for t in $(quiet command -p "$SOX" --i -D -- "$@"); do
  598. sec="$(($sec+$(printf '%.0f' "$t")))"
  599. done
  600. dur="$(printf '%dh %dm %ds' $(($sec/3600)) $(($sec%3600/60)) $(($sec%60)))"
  601. log "$SOX: ok" "Total duration: $dur"
  602. [ "$sec" -le 4800 ] ||
  603. die 1 "ERROR: Total duration of $dur exceeds 80 minutes."
  604. elif [ -n "${cmd}" ]; then
  605. die 1 'ERROR: sox is required for converting to 16 bit / 44.1 kHz / stereo.'
  606. else
  607. die : 'WARNING: sox is required for checking the duration of the audio files.'
  608. fi
  609. while [ ! -e "$DEVICE" ]; do
  610. die : "WARNING: $DEVICE not found." 'Configure the device. (i.e. /dev/sr0)'
  611. read -r DEVICE
  612. done
  613. if [ -z "${DRYRUN}" ]; then
  614. while :; do
  615. blank="$(quiet command -p "$CDRECORD" dev="$DEVICE" -minfo)" ||
  616. die 1 "$CDRECORD: operation failed."
  617. case "$blank" in *Blank*) : ;; *)
  618. die : "WARNING: Blank CD not found in $DEVICE." \
  619. 'Insert a blank CD. Continue? (y/n)'
  620. read -r answer
  621. case "$answer" in
  622. [yY]|[yY][eE][sS] ) continue ;;
  623. * ) die 0 "Blank CD not found in $DEVICE. Aborting ..." ;;
  624. esac ;;
  625. esac
  626. break
  627. done
  628. fi
  629. log "Using blank CD found in $DEVICE."
  630. list=; fburn=; fmsg=; mburn=; mmsg=; nburn=; nmsg=; wburn=; wmsg=
  631. for b in "$farr" "$marr" "$narr" "$warr"; do
  632. case "$b" in
  633. '' )
  634. :
  635. ;;
  636. "$farr" )
  637. fburn="quiet command -p \"\$FLAC\" --output-prefix=\"\$OUTPUT\"/ \
  638. --decode -- \"\$@\""
  639. fmsg='Decoding flac files ...'
  640. list="$list \"'\\\$farr' '\\\$fburn' '\\\$fmsg'\""
  641. ;;
  642. "$marr" )
  643. mburn="for a do fn=\"\${a##*/}\"; quiet command -p \"\$MPG123\" \
  644. --no-control -w \"\$OUTPUT/\${fn%.*}.wav\" \"\$a\"; done"
  645. mmsg='Converting mp3 files ...'
  646. list="$list \"'\\\$marr' '\\\$mburn' '\\\$mmsg'\""
  647. ;;
  648. "$narr" )
  649. nburn="quiet printf '%s\\n' \"\$@\"; for a do fn=\"\${a##*/}\"; \
  650. command -p \"\$CP\" -- \"\$a\" \"\$OUTPUT/\${fn%.*}.au\"; done"
  651. nmsg='Copying Sun/NeXT audio files ...'
  652. list="$list \"'\\\$narr' '\\\$nburn' '\\\$nmsg'\""
  653. ;;
  654. "$warr" )
  655. wburn="quiet printf '%s\\n' \"\$@\"; for a do fn=\"\${a##*/}\"; \
  656. command -p \"\$CP\" -- \"\$a\" \"\$OUTPUT/\${fn%.*}.wav\"; done"
  657. wmsg='Copying wav files ...'
  658. list="$list \"'\\\$warr' '\\\$wburn' '\\\$wmsg'\""
  659. ;;
  660. esac
  661. done
  662. : "$fburn" "$fmsg" "$mburn" "$mmsg" "$nburn" "$nmsg" "$wburn" "$wmsg" "$CP" \
  663. "$DEP" "$SPACE"
  664. command -p "${MKDIR:?}" -p -- "$OUTPUT"
  665. if [ "$(printn "$list")" -gt 1 ]; then
  666. out=; n=0
  667. arg=$(printn "$ord")
  668. while [ "$(printn "$out")" -lt "$arg" ]; do
  669. eval "set -- $ord"
  670. for i do
  671. if [ "${i##*:}" = $n ]; then
  672. addvar "${i%:*}" out
  673. break
  674. fi
  675. done
  676. n=$(($n+1))
  677. done
  678. fi
  679. trap 'quit; trap - EXIT; exit $err' EXIT INT
  680. eval "set -- $list"
  681. for i do
  682. eval "set -- $i"
  683. eval "burn=$2"
  684. eval "printf '%s\\n' \"$3\""
  685. eval "eval \"set -- $1\""
  686. eval "${burn:?}" || die 1 "ERROR: $PRGNAM operation failed."
  687. done
  688. eval "set -- $cmd"
  689. for i do
  690. file="${i%:*}"
  691. opt="${i##*:}"
  692. log "Converting $file" "sox options: $opt"
  693. eval "set -- $opt"
  694. quiet command -p "$SOX" -- "$OUTPUT/$file" "$@" -- "$OUTPUT/tmp-$file"
  695. command -p "${RM:?}" -f -- "$OUTPUT/$file"
  696. command -p "${MV:?}" -- "$OUTPUT/tmp-$file" "$OUTPUT/$file"
  697. done
  698. eval "set -- $out"
  699. log 'Burning audio files:' "$@"
  700. dry quiet command -p "$CDRECORD" dev="$DEVICE" -dao -audio -pad -- "$@"
  701. die 0 'CD burning finished successfully.'