Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- #########################
- #
- # NAME:
- # downsampler-threaded.sh - A Bash script to automate resampling of 24 bit FLAC files using multiple threads.
- #
- # SYNOPSIS:
- # downsampler-threaded.sh FILE_OR_FOLDER [FILE_OR_FOLDER...]
- #
- # DESCRIPTION:
- # Automatically resamples 24 bit FLAC files to 16 bit and a common multiple of their sample rate.
- #
- # Uses SoX with multiple threads if either GNU Parallel or moreutils Parallel is detected, or with a single
- # thread when neither 'parallel' variety is found.
- #
- # Optionally supports 24 bit outputs for 176.4 and 192 KHz sources, and/or dithering 24/44.1 and 24/48 sources
- # to 16 bit without resampling.
- #
- # metaflac is used on successfully converted files to add padding and to optionally add either of 2 tags
- # which detail 1) the sox command used, and 2) the source file's bit depth and sample rate.
- #
- # DEPENDS:
- # basename, dirname, file, metaflac, sox.
- #
- # RECOMMENDS:
- # parallel (GNU or moreutils).
- #
- # realpath, nproc (from GNU coreutils). The script will run successfully without these in most cases. nproc
- # is only needed for the "threads_not_used" option coupled with moreutils Parallel. Mac/BSD coreutils binaries
- # with a 'g' prepended to their name will be detected and used if they are in the $PATH.
- #
- # VERSION: 03
- #
- #########################
- #### ####
- #### User Settings ####
- ### ####
- # Threads - requires a parallel, default is to use all available threads, this script runs in single-threaded mode when both options are enabled
- threads_not_used="0" # "0" to disable, or number of threads to keep available - moreutils requires nproc to use this option
- threads_used="0" # "0" to disable, or maximum number of threads to use
- # FLAC Padding
- flac_padding="4096" # length in bytes of the padding block added by metaflac to converted files (+4 more bytes for padding block header)
- # SoX Verbosity - "0" absolutely nothing ever, "1" errors, "2" errors+warnings, "3" errors+warnings+sox_processing_info, "4"+ SoX_debugging
- sox_verbosity_level="1" # "1" is recommended, must be set, with a number, anything between 0-4 is acceptable
- # Script Features - setting the value for options below to "1" enables them, any other text (or lack thereof) between the double-quotes disables.
- use_24_44_and_24_48_input="1" # output 16/44 and 16/48 from 24/44 and 24/48 sources
- use_24_88_and_24_96_output="0" # output 24/88.2 and 24/96 from 24/176.4 and 24/192 sources
- use_SOXCOMMAND_tag="1" # create a tag detailing the SoX command used to convert the file
- use_SOXSOURCE_tag="1" # create a tag detailing the source file's bit depth and sample rate
- use_progress_bar="0" # use GNU parallel's progress bar for SoX jobs - no effect when using moreutils parallel or single-threaded mode
- #sox_stderr_logging="0" # not yet implemented, redirection creates the file before there's any output...
- # should we just accept that, and check for/delete empty log files after? hmmm
- #### ####
- #### End of Settings ####
- #### ####
- # ctrl+c exits script, not just sox/whatever
- trap "exit 1" INT
- # for fewer following ifs and printfs
- if [[ "$use_SOXCOMMAND_tag" == "1" || "$use_SOXSOURCE_tag" == "1" ]] ;then tagmsg="and tags with metaflac" ;else tagmsg="with metaflac" ;fi
- # colourful printf # does the "tput" way work on OSX/BSD/not Debian?
- red="$( tput setaf 1 )" # RED='\033[0;31m'
- green="$( tput setaf 2 )" # GREEN='\033[0;32m'
- orange="$( tput setaf 3 )" # ORANGE='\033[0;33m'
- default="$( tput op )" # NC='\033[0m' # not the same as "tput op"
- # positioning printfs per the previous printf
- printf '\n '
- # argument(s) required
- [[ "$#" -ge "1" ]] || { printf '%sERROR%s: Nothing to do, please specify at least one file or folder.\n\n' "$orange" "$default" >&2 ; exit 1 ; }
- # dependencies
- [[ "$PATH" != *"/usr/local/bin"* ]] && PATH=$PATH:/usr/local/bin # Automator defaults to ignoring Homebrew
- command -v basename >/dev/null 2>&1 || { printf '%sERROR%s: '\''basename'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
- command -v dirname >/dev/null 2>&1 || { printf '%sERROR%s: '\''dirname'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
- command -v file >/dev/null 2>&1 || { printf '%sERROR%s: '\''file'\'' (the program) not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
- command -v metaflac >/dev/null 2>&1 || { printf '%sERROR%s: '\''metaflac'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
- command -v sox >/dev/null 2>&1 || { printf '%sERROR%s: '\''sox'\'' not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
- # recommends
- if 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
- if command -v parallel >/dev/null 2>&1 ;then
- parallel_help="$( parallel -h )" # or maybe we should $( file --mime-type ) = perl then_gnu else_moreutils ?
- if [[ "$parallel_help" == *"GNU"* ]] ;then
- our_parallel="GNU"
- parallel_divider=":::"
- parallel_citation="--will-cite"
- [[ "$threads_not_used" -gt "0" ]] && paralleljobs[0]="-j -$threads_not_used"
- [[ "$use_progress_bar" == "1" ]] && parallel_progress_bar[0]="--bar"
- elif [[ "$parallel_help" == *"parallel [OPTIONS] command"* ]] ;then
- our_parallel="moreutils"
- parallel_divider="--"
- if [[ "$threads_not_used" -gt "0" ]] ;then
- if [[ "$nproc" == "no_nproc" ]] ;then
- printf '%sERROR%s: '\''nproc'\'' not found, required to use moreutils parallel with threads_not_used option -- try threads_used instead.\n\n ' "$orange" "$default" >&2
- paralleljobs[0]=""
- else
- paralleljobs[0]="-j $( "$nproc" --ignore="$threads_not_used" )"
- fi
- fi
- else
- printf '%sERROR%s: '\''parallel'\'' exists but is not recognized, running in single-threaded mode.\n\n ' "$orange" "$default" >&2
- our_parallel="no_parallel"
- fi
- else
- printf '%s'\''parallel'\'' not found%s, running in single-threaded mode.\n\n ' "$orange" "$default"
- our_parallel="no_parallel"
- fi
- if [[ "$our_parallel" != "no_parallel" ]] ;then
- [[ "$threads_not_used" -gt "0" && "$threads_used" -gt "0" ]] && {
- printf '%sERROR%s: the options '\''threads_used'\'' and '\''threads_not_used'\'' are mutually exclusive, running in %ssingle-threaded mode%s.\n\n ' "$red" "$default" "$orange" "$default" >&2
- our_parallel="no_parallel" ; }
- [[ "$threads_used" -gt "0" ]] && paralleljobs[0]="-j $threads_used"
- fi
- if command -v realpath >/dev/null 2>&1 ;then realpath="realpath" ;elif command -v grealpath >/dev/null 2>&1 ;then realpath="grealpath"
- else realpath() { ( cd -P "$( dirname "$1" )" 2>/dev/null && printf '%s/%s\n' "$PWD" "$( basename "$1" )" ) ; } ;realpath="realpath"
- fi
- # get all the items, including folder and subfolder contents, from command line arguments
- user_args=( "$@" )
- for arg in "${user_args[@]}" ; do
- if [[ -d "$arg" ]] ; then
- user_args+=( "$arg"/* )
- for subdir in "$arg"/* ; do
- if [[ -d "$subdir" ]] ; then
- user_args+=( "$subdir"/* )
- fi
- done
- fi
- done
- printf 'Reading input file(s). '
- # just the flacs, just the 24 bit flacs
- for flacfile in "${user_args[@]}" ;do
- [[ "$( file -b --mime-type "$flacfile" )" == "audio/x-flac" ]] && [[ "$( sox --i -b "$flacfile" )" -eq "24" ]] &&
- absolute_flac_names+=( "$( "$realpath" "$flacfile" )" )
- done
- # source data
- for index in "${!absolute_flac_names[@]}" ;do
- flac_filenames[$index]="$( basename "${absolute_flac_names[$index]}" )"
- absolute_flac_dirs[$index]="$( dirname "${absolute_flac_names[$index]}" )"
- flac_sample_rates[$index]="$( sox --i -r "${absolute_flac_names[$index]}" )"
- done
- printf 'Found %s candidate FLAC file(s). Configuring output. ' "${#absolute_flac_names[@]}"
- # target data
- for index in "${!absolute_flac_names[@]}" ;do
- # 24/44 and 24/48 --> 16/44 and 16/48
- if [[ "$use_24_44_and_24_48_input" == "1" ]] ;then
- [[ "${flac_sample_rates[$index]}" -eq "44100" || "${flac_sample_rates[$index]}" -eq "48000" ]] && {
- target_bit_depths[$index]="16"
- target_sample_rates[$index]=""
- target_folders[$index]="${absolute_flac_dirs[$index]}/unresampled-16bit"
- target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}" ; }
- fi
- # 24/176 and 24/192 --> 24/88 and 24/96
- if [[ "$use_24_88_and_24_96_output" == "1" ]] ;then
- if [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
- [[ "${flac_sample_rates[$index]}" -eq "176400" ]] && {
- target_sample_rates[$index]="rate -v -L 88200"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-88" ; }
- [[ "${flac_sample_rates[$index]}" -eq "192000" ]] && {
- target_sample_rates[$index]="rate -v -L 96000"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-96" ; }
- target_bit_depths[$index]="24"
- target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
- # 24/88 and 24/96 --> 16/44 and 16/48
- elif [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ;then
- [[ "${flac_sample_rates[$index]}" -eq "88200" ]] && {
- target_sample_rates[$index]="rate -v -L 44100"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
- [[ "${flac_sample_rates[$index]}" -eq "96000" ]] && {
- target_sample_rates[$index]="rate -v -L 48000"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
- target_bit_depths[$index]="16"
- target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
- fi
- else
- # 24/{88,96,176,192} --> 16/$common_multiple
- if [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ||
- [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
- [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "176400" ]] && {
- target_sample_rates[$index]="rate -v -L 44100"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
- [[ "${flac_sample_rates[$index]}" -eq "96000" || "${flac_sample_rates[$index]}" -eq "192000" ]] && {
- target_sample_rates[$index]="rate -v -L 48000"
- target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
- target_bit_depths[$index]="16"
- target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
- fi
- fi
- done
- # construct lists of sox and metaflac commands
- for index in "${!target_flacs[@]}" ;do
- [[ ! -d "${target_folders[$index]}" ]] && mkdir "${target_folders[$index]}"
- cmdlistsox[$index]="sox -V$sox_verbosity_level \"${absolute_flac_names[$index]}\" -G -b ${target_bit_depths[$index]} \"${target_flacs[$index]}\" ${target_sample_rates[$index]} dither"
- #cmdlistsox[$index]="sox -V$sox_verbosity_level \"${absolute_flac_names[$index]}\" -G -b ${target_bit_depths[$index]} \"${target_flacs[$index]}\" ${target_sample_rates[$index]} dither 2>\"$PWD\"/sox-stderr-\"$index\".txt"
- cmdlistmfsc[$index]="metaflac --set-tag=SOXCOMMAND=\"sox input.flac -G -b ${target_bit_depths[$index]} output.flac ${target_sample_rates[$index]} dither\" \"${target_flacs[$index]}\""
- cmdlistmfss[$index]="metaflac --set-tag=SOXSOURCE=\"24 bit, \"${flac_sample_rates[$index]}\" Hz\" \"${target_flacs[$index]}\""
- done
- # run command lists, if possible using multiple threads
- if [[ "$our_parallel" == "no_parallel" ]] ;then
- printf 'Converting %s target(s) with SoX.\n' "${#target_flacs[@]}"
- for index in "${!cmdlistsox[@]}" ;do
- printf '\n Converting %s.\n ' "${target_flacs[$index]}"
- if eval "${cmdlistsox[$index]}" ;then
- printf '%sSuccess%s! Adding padding %s.\n' "$green" "$default" "$tagmsg"
- metaflac --add-padding="$flac_padding" "${target_flacs[$index]}" || printf ' %sERROR%s: Failure adding padding to %s.\n' "$red" "$default" "${target_flacs[$index]}" >&2
- [[ "$use_SOXCOMMAND_tag" == "1" ]] && { eval "${cmdlistmfsc[$index]}" || printf ' %sERROR%s: Failure adding SOXCOMMAND tag to %s.\n' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
- [[ "$use_SOXSOURCE_tag" == "1" ]] && { eval "${cmdlistmfss[$index]}" || printf ' %sERROR%s: Failure adding SOXSOURCE tag to %s.\n' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
- else
- printf '%sERROR%s: SoX had non-zero exit status converting %s, aborting follow-up tasks.\n' "$red" "$default" "${target_flacs[$index]}" >&2
- fi
- done
- printf '\n'
- else
- printf 'Converting %s target(s) with SoX and %s%s%s Parallel.\n' "${#target_flacs[@]}" "$orange" "$our_parallel" "$default"
- if parallel ${parallel_citation[0]} ${parallel_progress_bar[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistsox[@]}" ;then
- printf ' Adding padding %s. ' "$tagmsg"
- parallel ${parallel_citation[0]} ${paralleljobs[0]} metaflac --add-padding="$flac_padding" "${parallel_divider[0]}" "${target_flacs[@]}" || printf '\n %sERROR%s: Failure adding padding to file(s).\n' "$red" "$default" >&2
- [[ "$use_SOXCOMMAND_tag" == "1" ]] && { parallel ${parallel_citation[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistmfsc[@]}" || printf '\n %sERROR%s: Failure adding SOXCOMMAND tag(s).\n' "$red" "$default" >&2 ; }
- [[ "$use_SOXSOURCE_tag" == "1" ]] && { parallel ${parallel_citation[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistmfss[@]}" || printf '\n %sERROR%s: Failure adding SOXSOURCE tag(s).\n' "$red" "$default" >&2 ; }
- printf '%sDone%s!\n\n' "$green" "$default"
- else
- printf '%sERROR%s: Parallel/SoX had non-zero exit status, aborting follow-up tasks.\n\n' "$red" "$default" >&2
- exit 1
- fi
- fi
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement