· 7 years ago · Mar 03, 2019, 02:46 PM
1#!/bin/bash
2
3#########################
4#
5# NAME:
6# downsampler-threaded.sh - A Bash script to automate resampling of 24 bit FLAC files using multiple threads.
7#
8# SYNOPSIS:
9# downsampler-threaded.sh [OPTION [ARGUMENT]...] [--] FILE_OR_FOLDER [FILE_OR_FOLDER...]
10#
11# DESCRIPTION:
12# Automatically resamples 24 bit FLAC files to 16 bit and a common multiple of their sample rate.
13#
14# Uses SoX with multiple threads if either GNU Parallel or moreutils Parallel is detected, or with a single
15# thread when neither 'parallel' variety is found.
16#
17# Optionally supports 24 bit outputs for 176.4 and 192 KHz sources, and/or dithering 24/44.1 and 24/48 sources
18# to 16 bit without resampling.
19#
20# metaflac is optionally used on successfully converted files to add padding and/or to add either of 2 tags
21# which detail 1) the sox command used, and 2) the source file's bit depth and sample rate.
22#
23# DEPENDS:
24# basename, dirname, file, metaflac, sox.
25#
26# RECOMMENDS:
27# parallel (GNU or moreutils).
28#
29# nproc from GNU coreutils is only used for moreutils parallel with the "threads_unused / -T / --unthreads" option.
30#
31# realpath was used in previous versions, and can still be used by un-commenting lines 320, 321, and 323, but this
32# version now defaults to using the function (now named "absolute_path") formerly used only as a no-realpath fallback.
33#
34# For both realpath (if re-enabled) and nproc, Mac/BSD coreutils binaries with a 'g' prepended to their name will
35# be detected and used if they are in the $PATH.
36#
37# VERSION: 04
38#
39#########################
40
41
42
43 #### ####
44#### Default User Settings ####
45 #### ####
46
47# Threads - requires a parallel, default (when a parallel is present) is to use all available threads, this script runs in single-threaded mode when both options are enabled
48threads_used="0" # "0" to disable, or maximum number of threads to use
49threads_unused="0" # "0" to disable, or number of threads to keep available - moreutils requires nproc to use this option
50
51# SoX Verbosity - "0" absolutely nothing ever, "1" errors, "2" errors+warnings, "3" errors+warnings+sox_processing_info, "4"+ SoX_debugging
52sox_verbosity_level="1" # "1" is recommended, must be set, with a number, anything between 0-4 is acceptable
53
54# SoX dither # "dither" is recommended, but noise-shaped dither ("dither -s") may help with sources for which -G/--guard is
55sox_dither="dither" # not sufficient to prevent clipping, and any other valid dither effect command may be set here.
56
57# SoX rate
58sox_rate="rate -v -L" # Set any valid rate effect command for SoX here, "rate -v -L" is recommended.
59
60# FLAC Padding - set to "0" to disable adding padding to output files
61flac_padding="4096" # length in bytes of the padding block added by metaflac to converted files (+4 more bytes for padding block header)
62
63# Homebrew binaries folder on MacOS - in most cases only needed for Automator users, unless the correct path is not in your shell's $PATH by default.
64homebrew_bin="/usr/local/bin" # set the location of your Homebrew binaries
65
66# Script Features - setting the value for options below to "1" enables them, any other text (or lack thereof) between the double-quotes disables.
67use_24_44_and_24_48_input="1" # output 16/44 and 16/48 from 24/44 and 24/48 sources
68use_24_88_and_24_96_output="0" # output 24/88.2 and 24/96 from 24/176.4 and 24/192 sources
69
70use_SOXCOMMAND_tag="1" # create a tag detailing the SoX command used to convert the file
71use_SOXSOURCE_tag="1" # create a tag detailing the source file's bit depth and sample rate
72
73use_progress_bar="0" # use GNU parallel's progress bar for SoX jobs - no effect when using moreutils parallel or single-threaded mode
74
75#sox_stderr_logging="0" # not yet implemented, redirection creates the file before there's any output...
76 # should we just accept that, and check for/delete empty log files after? hmmm
77 #### ####
78#### End of Default Settings ####
79 #### ####
80
81
82
83print_usage() { printf 'Usage:
84
85 %s [OPTION [ARGUMENT]...] [--] FILE_OR_FOLDER [FILE_OR_FOLDER...]
86
87
88Options:
89
90Script:
91-h, -H, --help Print this help text and exit.
92
93-- End of options. Following arguments taken as files/folders for conversion.
94
95-f, --4448 Use 24/44 and 24/48 input files.
96-F, --no-4448 Don'\''t use 24/44 and 24/48 input files.
97
98-e, --8896 Output 24/88.2 and 24/96 from 24/176.4 and 24/192 sources.
99-E, --no-8896 Don'\''t output 24/88.2 & 24/96 from 24/176.4 & 24/192 (but do output 16/44 and 16/48)
100
101SoX:
102-v ARG, --sox-verbosity ARG SoX'\''s -V option, must be set with a number between 0 and 4.
103
104-d '\''ARG'\'', --dither '\''ARG'\'' Dither effect command for SoX, arguments with spaces should be quoted.
105-D, --default-dither Sets dither command to '\''dither'\''.
106
107-r '\''ARG'\'', --rate '\''ARG'\'' Rate effect command for SoX, arguments with spaces should be quoted
108-R, --default-rate Sets rate command to '\''rate -v -L'\'' (very high quality, linear phase response)
109
110Threads:
111-t ARG, --threads ARG Number of threads to use. Set to 0 for (parallel'\''s) defaults. Disables -T/--unthreads
112-T ARG, --unthreads ARG Number of threads to leave unused. Set to 0 for (parallel'\''s) defaults. Disables -t/--threads
113
114GNU Parallel:
115-b, --progress-bar Use GNU Parallel'\''s progress bar. No effect with moreutils parallel
116-B, --no-progress-bar Don'\''t use GNU Parallel'\''s progress bar. No effect with moreutils parallel
117
118FLAC:
119-p ARG, --padding ARG Number of bytes to use for FLAC padding. Set to 0 to disable.
120
121Tags:
122-c, --command-tag Tag output with the SoX command used to convert the file.
123-C, --no-command-tag Do not tag output with the SoX command used to convert the file.
124
125-s, --source-tag Tag output with the source file'\''s bit depth and sample rate.
126-S, --no-source-tag Do not tag output with the source file'\''s bit depth and sample rate.
127
128Homebrew:
129-w, --homebrew-bin '\''ARG'\'' On MacOS, set the location of your Homebrew binaries (in most cases only needed with Automator).
130-W, --default-homebrew On MacOS, use the default Homebrew binary location, /usr/local/bin (in most cases only needed with Automator).
131
132' "$0" ; }
133#-P ARG, --parallel-binary ARG let user specify their parallel binary - should be do-able with minimal changes...? worth doing?
134#-(?), --summary print summary of actions taken on each file / ie: script verbosity / eg: print table containing data from our arrays, easy
135
136absolute_path() { ( cd -P "$( dirname "$1" )" 2>/dev/null && printf '%s/%s\n' "$PWD" "$( basename "$1" )" ) ; }
137
138printf '\n'
139
140# colourful printf # tput works everywhere?
141 red="$( tput setaf 1 )" # RED='\033[0;31m'
142 green="$( tput setaf 2 )" # GREEN='\033[0;32m'
143 orange="$( tput setaf 3 )" # ORANGE='\033[0;33m'
144default="$( tput sgr0 )" # NC='\033[0m'
145
146# runtime options
147while : ;do
148 case "$1" in
149 -b|--progress-bar)
150 use_progress_bar="1"
151 shift
152 ;;
153 -B|--no-progress-bar)
154 use_progress_bar="0"
155 shift
156 ;;
157 -c|--command-tag)
158 use_SOXCOMMAND_tag="1"
159 shift
160 ;;
161 -C|--no-command-tag)
162 use_SOXCOMMAND_tag="0"
163 shift
164 ;;
165 -d|--dither)
166 printf '%sWARNING%s: Validity of the dither effect command is your responsibility. Arguments with spaces should be quoted.\n\n' "$orange" "$default"
167 sox_dither="$2"
168 shift 2
169 ;;
170 -D|--default-dither)
171 sox_dither="dither"
172 shift
173 ;;
174 -e|--8896)
175 use_24_88_and_24_96_output="1"
176 shift
177 ;;
178 -E|--no-8896)
179 use_24_88_and_24_96_output="0"
180 shift
181 ;;
182 -f|--4448)
183 use_24_44_and_24_48_input="1"
184 shift
185 ;;
186 -F|--no-4448)
187 use_24_44_and_24_48_input="0"
188 shift
189 ;;
190 -h|-H|--help)
191 print_usage ;exit
192 ;;
193 -p|--padding)
194 flac_padding="$2"
195 shift 2
196 ;;
197 -r|--rate)
198 printf '%sWARNING%s: Validity of the rate effect command is your responsibility. Arguments with spaces should be quoted.\n\n' "$orange" "$default"
199 sox_rate="$2"
200 shift 2
201 ;;
202 -R|--default-rate)
203 sox_rate="rate -v -L"
204 shift
205 ;;
206 -s|--source-tag)
207 use_SOXSOURCE_tag="1"
208 shift
209 ;;
210 -S|--no-source-tag)
211 use_SOXSOURCE_tag="0"
212 shift
213 ;;
214 -t|--threads)
215 threads_used="$2" ;threads_unused="0"
216 shift 2
217 ;;
218 -T|--unthreads)
219 threads_unused="$2" ;threads_used="0"
220 shift 2
221 ;;
222 -v|--sox-verbosity)
223 sox_verbosity_level="$2"
224 shift 2
225 ;;
226 -w|--homebrew-bin)
227 homebrew_bin="$2"
228 shift 2
229 ;;
230 -W|--default-homebrew)
231 homebrew_bin="/usr/local/bin"
232 shift
233 ;;
234 --)
235 break
236 ;;
237 -?*)
238 printf '%sERROR%s: Unknown option '\''%s'\''\n\n' "$red" "$default" "$1" >&2 ;print_usage ;exit 1
239 ;;
240 *)
241 break
242 ;;
243
244 esac
245done
246
247# argument(s) required
248[[ "$#" -ge "1" ]] || { printf '%sERROR%s: Nothing to do, please specify at least one file or folder.\n\n' "$red" "$default" >&2 ;print_usage ;exit 1 ; }
249
250# validate threads
251[[ "$threads_used" != *[!0123456789]* ]] || { printf '%sERROR%s: Argument for '\''-t / --threads / threads_used'\'' must be zero or a positive integer.\n\n' "$red" "$default" >&2 ;exit 1 ; }
252[[ "$threads_unused" != *[!0123456789]* ]] || { printf '%sERROR%s: Argument for '\''-T / --unthreads / threads_unused'\'' must be zero or a positive integer.\n\n' "$red" "$default" >&2 ;exit 1 ; }
253
254# validate flac padding
255if [[ "$flac_padding" != *[!0123456789]* ]] ;then
256 if [[ "$flac_padding" -ge "1" ]] ;then
257 [[ "$flac_padding" -le "512" ]] && printf '%sWARNING%s: Using only %s bytes for FLAC padding. Maybe this is enough for your needs, but it'\''s not very much.\n\n' "$orange" "$default" "$flac_padding" >&2
258 [[ "$flac_padding" -gt "8192" ]] && printf '%sWARNING%s: Using more than 8KB for FLAC padding. Maybe your needs require that much, but it'\''s quite a lot.\n\n' "$orange" "$default" >&2
259 fi
260else
261 printf '%sERROR%s: Argument for '\''-p / --padding / flac_padding'\'' must be zero or a positive integer.\n\n' "$red" "$default" >&2
262 exit 1
263fi
264
265# validate sox verbosity
266[[ "$sox_verbosity_level" == [01234] ]] || { printf '%sERROR%s: Argument for '\''-v / --sox-verbosity / sox_verbosity_level'\'' must be an integer between 0 and 4.\n\n' "$red" "$default" >&2 ;exit 1 ; }
267
268# ctrl+c exits script, not just sox/whatever
269trap "exit 1" INT
270
271# dependencies
272#[[ "$PATH" != *"/usr/local/bin"* ]] && PATH=$PATH:/usr/local/bin # Automator defaults to ignoring Homebrew
273[[ "$PATH" != *"$homebrew_bin"* ]] && PATH=$PATH:"$homebrew_bin" # Automator defaults to ignoring Homebrew
274command -v basename >/dev/null 2>&1 || { printf '%sERROR%s: '\''basename'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
275command -v dirname >/dev/null 2>&1 || { printf '%sERROR%s: '\''dirname'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
276command -v file >/dev/null 2>&1 || { printf '%sERROR%s: '\''file'\'' (the program) not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
277if [[ "$flac_padding" != "0" || "$use_SOXSOURCE_tag" == "1" || "$use_SOXCOMMAND_tag" == "1" ]] ;then
278 command -v metaflac >/dev/null 2>&1 || { printf '%sERROR%s: '\''metaflac'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
279fi
280command -v sox >/dev/null 2>&1 || { printf '%sERROR%s: '\''sox'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
281
282# recommends
283if command -v nproc >/dev/null 2>&1 ;then nproc="nproc" ;elif command -v gnproc >/dev/null 2>&1 ;then nproc="gnproc" ;else nproc="no_nproc" ;fi
284
285if command -v parallel >/dev/null 2>&1 ;then
286 parallel_help="$( parallel -h )" # or maybe we should $( file --mime-type ) = perl then_gnu else_moreutils ?
287 if [[ "$parallel_help" == *"GNU"* ]] ;then
288 our_parallel="GNU"
289 parallel_divider=":::"
290 parallel_citation="--will-cite"
291 [[ "$threads_unused" -gt "0" ]] && paralleljobs[0]="-j -$threads_unused"
292 [[ "$use_progress_bar" == "1" ]] && parallel_progress_bar[0]="--bar"
293 elif [[ "$parallel_help" == *"parallel [OPTIONS] command"* ]] ;then
294 our_parallel="moreutils"
295 parallel_divider="--"
296 if [[ "$threads_unused" -gt "0" ]] ;then
297 if [[ "$nproc" == "no_nproc" ]] ;then
298 printf '%sERROR%s: '\''nproc'\'' not found, required to use moreutils parallel with threads_unused option -- try threads_used instead.\n\n' "$orange" "$default" >&2
299 paralleljobs[0]=""
300 else
301 paralleljobs[0]="-j $( "$nproc" --ignore="$threads_unused" )"
302 fi
303 fi
304 else
305 printf '%sERROR%s: '\''parallel'\'' exists but is not recognized, %srunning in single-threaded mode%s.\n\n' "$orange" "$default" "$orange" "$default" >&2
306 our_parallel="no_parallel"
307 fi
308else
309 printf '%sWARNING%s: No '\''parallel'\'' found, %srunning in single-threaded mode%s.\n\n' "$orange" "$default" "$orange" "$default"
310 our_parallel="no_parallel"
311fi
312
313if [[ "$our_parallel" != "no_parallel" ]] ;then
314 [[ "$threads_unused" -gt "0" && "$threads_used" -gt "0" ]] && {
315 printf '%sERROR%s: the options '\''threads_used'\'' and '\''threads_unused'\'' are mutually exclusive, %srunning in single-threaded mode%s.\n\n' "$orange" "$default" "$orange" "$default" >&2
316 our_parallel="no_parallel" ; }
317 [[ "$threads_used" -gt "0" ]] && paralleljobs[0]="-j $threads_used"
318fi
319
320#if command -v realpath >/dev/null 2>&1 ;then realpath="realpath" ;elif command -v grealpath >/dev/null 2>&1 ;then realpath="grealpath"
321#else
322realpath="absolute_path"
323#fi
324
325
326# get all the items, including folder and subfolder contents, from command line arguments
327user_args=( "$@" )
328for arg in "${user_args[@]}" ; do
329 if [[ -d "$arg" ]] ; then
330 user_args+=( "$arg"/* )
331 for subdir in "$arg"/* ; do
332 if [[ -d "$subdir" ]] ; then
333 user_args+=( "$subdir"/* )
334 fi
335 done
336 fi
337done
338
339printf 'Reading input file(s). '
340
341# just the flacs, just the 24 bit flacs
342for flacfile in "${user_args[@]}" ;do
343 [[ "$( file -b --mime-type "$flacfile" )" == "audio/"*"flac" ]] && [[ "$( sox --i -b "$flacfile" )" -eq "24" ]] && absolute_flac_names+=( "$( "$realpath" "$flacfile" )" )
344done
345
346# candidate flacs must exist
347[[ "${#absolute_flac_names[@]}" -ge "1" ]] || { printf '%sERROR%s: No candidate FLAC files found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
348
349# source data
350for index in "${!absolute_flac_names[@]}" ;do
351 flac_filenames[$index]="$( basename "${absolute_flac_names[$index]}" )"
352 absolute_flac_dirs[$index]="$( dirname "${absolute_flac_names[$index]}" )"
353 flac_sample_rates[$index]="$( sox --i -r "${absolute_flac_names[$index]}" )"
354done
355
356printf 'Found %s candidate FLAC file(s). Configuring output. ' "${#absolute_flac_names[@]}"
357
358# target data
359for index in "${!absolute_flac_names[@]}" ;do
360
361 # 24/44 and 24/48 --> 16/44 and 16/48
362 if [[ "$use_24_44_and_24_48_input" == "1" ]] ;then
363 [[ "${flac_sample_rates[$index]}" -eq "44100" || "${flac_sample_rates[$index]}" -eq "48000" ]] && {
364 target_bit_depths[$index]="16"
365 target_sample_rates[$index]=""
366 target_folders[$index]="${absolute_flac_dirs[$index]}/unresampled-16bit"
367 target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}" ; }
368 fi
369
370 # 24/176 and 24/192 --> 24/88 and 24/96
371 if [[ "$use_24_88_and_24_96_output" == "1" ]] ;then
372 if [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
373
374 [[ "${flac_sample_rates[$index]}" -eq "176400" ]] && {
375 target_sample_rates[$index]="${sox_rate[0]} 88200"
376 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-88" ; }
377
378 [[ "${flac_sample_rates[$index]}" -eq "192000" ]] && {
379 target_sample_rates[$index]="${sox_rate[0]} 96000"
380 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-96" ; }
381
382 target_bit_depths[$index]="24"
383 target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
384
385 # 24/88 and 24/96 --> 16/44 and 16/48
386 elif [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ;then
387
388 [[ "${flac_sample_rates[$index]}" -eq "88200" ]] && {
389 target_sample_rates[$index]="${sox_rate[0]} 44100"
390 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
391
392 [[ "${flac_sample_rates[$index]}" -eq "96000" ]] && {
393 target_sample_rates[$index]="${sox_rate[0]} 48000"
394 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
395
396 target_bit_depths[$index]="16"
397 target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
398 fi
399
400 else
401 # 24/{88,96,176,192} --> 16/$common_multiple
402 if [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ||
403 [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
404
405 [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "176400" ]] && {
406 target_sample_rates[$index]="${sox_rate[0]} 44100"
407 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
408
409 [[ "${flac_sample_rates[$index]}" -eq "96000" || "${flac_sample_rates[$index]}" -eq "192000" ]] && {
410 target_sample_rates[$index]="${sox_rate[0]} 48000"
411 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
412
413 target_bit_depths[$index]="16"
414 target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
415 fi
416 fi
417done
418
419# target flacs must exist
420[[ "${#target_flacs[@]}" -ge "1" ]] || { printf '%sERROR%s: No targets for conversion, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
421
422# construct lists of sox and metaflac commands
423for index in "${!target_flacs[@]}" ;do
424 [[ ! -d "${target_folders[$index]}" ]] && mkdir "${target_folders[$index]}"
425 cmdlistsox[$index]="sox -V$sox_verbosity_level \"${absolute_flac_names[$index]}\" -G -b ${target_bit_depths[$index]} \"${target_flacs[$index]}\" ${target_sample_rates[$index]} ${sox_dither[0]}"
426 #cmdlistsox[$index]="sox -V$sox_verbosity_level \"${absolute_flac_names[$index]}\" -G -b ${target_bit_depths[$index]} \"${target_flacs[$index]}\" ${target_sample_rates[$index]} ${sox_dither[0]} 2>\"$PWD\"/sox-stderr-\"$index\".txt"
427 cmdlistmfsc[$index]="metaflac --set-tag=SOXCOMMAND=\"sox input.flac -G -b ${target_bit_depths[$index]} output.flac ${target_sample_rates[$index]} ${sox_dither[0]}\" \"${target_flacs[$index]}\""
428 cmdlistmfss[$index]="metaflac --set-tag=SOXSOURCE=\"24 bit, \"${flac_sample_rates[$index]}\" Hz\" \"${target_flacs[$index]}\""
429done
430
431# run command lists, if possible using multiple threads
432if [[ "$our_parallel" == "no_parallel" ]] ;then
433 printf 'Converting %s target(s) with SoX.\n\n' "${#target_flacs[@]}"
434 for index in "${!cmdlistsox[@]}" ;do
435 printf 'Converting %s.\n' "${target_flacs[$index]}"
436 if eval "${cmdlistsox[$index]}" ;then
437 printf ' %sSuccess%s! ' "$green" "$default"
438 [[ "$flac_padding" != "0" ]] && { printf 'Adding padding... ' ;metaflac --add-padding="$flac_padding" "${target_flacs[$index]}" || printf '%sERROR%s: Failure adding padding to %s.\n ' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
439 [[ "$use_SOXCOMMAND_tag" == "1" ]] && { printf 'Adding SoX command tag... ' ;eval "${cmdlistmfsc[$index]}" || printf '%sERROR%s: Failure adding SOXCOMMAND tag to %s.\n ' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
440 [[ "$use_SOXSOURCE_tag" == "1" ]] && { printf 'Adding SoX source tag... ' ;eval "${cmdlistmfss[$index]}" || printf '%sERROR%s: Failure adding SOXSOURCE tag to %s.\n ' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
441 printf '%sDone%s!\n\n' "$green" "$default"
442 else
443 printf '%sERROR%s: SoX had non-zero exit status converting %s, aborting follow-up tasks for this file.\n\n' "$red" "$default" "${target_flacs[$index]}" >&2
444 fi
445 done
446else
447 printf 'Converting %s target(s) with SoX and %s%s%s Parallel.\n' "${#target_flacs[@]}" "$orange" "$our_parallel" "$default"
448 if parallel ${parallel_citation[0]} ${parallel_progress_bar[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistsox[@]}" ;then
449 printf '%sSuccess%s! ' "$green" "$default"
450 [[ "$flac_padding" != "0" ]] && { printf 'Adding padding... ' ;parallel ${parallel_citation[0]} ${paralleljobs[0]} metaflac --add-padding="$flac_padding" "${parallel_divider[0]}" "${target_flacs[@]}" || printf '%sERROR%s: Failure adding padding to file(s).\n' "$red" "$default" >&2 ; }
451 [[ "$use_SOXCOMMAND_tag" == "1" ]] && { printf 'Adding SoX command tag... ' ;parallel ${parallel_citation[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistmfsc[@]}" || printf '%sERROR%s: Failure adding SOXCOMMAND tag(s).\n' "$red" "$default" >&2 ; }
452 [[ "$use_SOXSOURCE_tag" == "1" ]] && { printf 'Adding SoX source tag... ' ;parallel ${parallel_citation[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistmfss[@]}" || printf '%sERROR%s: Failure adding SOXSOURCE tag(s).\n' "$red" "$default" >&2 ; }
453 printf '%sDone%s!\n\n' "$green" "$default"
454 else
455 printf '%sERROR%s: Parallel/SoX had non-zero exit status, aborting follow-up tasks.\n\n' "$red" "$default" >&2
456 exit 1
457 fi
458fi