Advertisement
Guest User

downsampler-threaded.sh v03

a guest
Dec 15th, 2018
507
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 13.80 KB | None | 0 0
  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 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 used on successfully converted files to add padding and to optionally 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. #      realpath, nproc (from GNU coreutils). The script will run successfully without these in most cases. nproc
  30. #      is only needed for the "threads_not_used" option coupled with moreutils Parallel. Mac/BSD coreutils binaries
  31. #      with a 'g' prepended to their name will be detected and used if they are in the $PATH.
  32. #
  33. # VERSION: 03
  34. #
  35. #########################
  36.  
  37.  
  38.   ####           ####
  39. #### User Settings ####
  40.   ###            ####
  41.  
  42. # Threads - requires a parallel, default is to use all available threads, this script runs in single-threaded mode when both options are enabled
  43. threads_not_used="0"               # "0" to disable, or number of threads to keep available - moreutils requires nproc to use this option
  44. threads_used="0"                   # "0" to disable, or maximum number of threads to use
  45.  
  46. # FLAC Padding
  47. flac_padding="4096"                # length in bytes of the padding block added by metaflac to converted files (+4 more bytes for padding block header)
  48.  
  49. # SoX Verbosity - "0" absolutely nothing ever, "1" errors, "2" errors+warnings, "3" errors+warnings+sox_processing_info, "4"+ SoX_debugging
  50. sox_verbosity_level="1"            # "1" is recommended, must be set, with a number, anything between 0-4 is acceptable
  51.  
  52. # Script Features - setting the value for options below to "1" enables them, any other text (or lack thereof) between the double-quotes disables.
  53. use_24_44_and_24_48_input="1"      # output 16/44 and 16/48 from 24/44 and 24/48 sources
  54. use_24_88_and_24_96_output="0"     # output 24/88.2 and 24/96 from 24/176.4 and 24/192 sources
  55.  
  56. use_SOXCOMMAND_tag="1"             # create a tag detailing the SoX command used to convert the file
  57. use_SOXSOURCE_tag="1"              # create a tag detailing the source file's bit depth and sample rate
  58.  
  59. use_progress_bar="0"               # use GNU parallel's progress bar for SoX jobs - no effect when using moreutils parallel or single-threaded mode
  60.  
  61. #sox_stderr_logging="0"             # not yet implemented, redirection creates the file before there's any output...
  62.                                    # should we just accept that, and check for/delete empty log files after? hmmm
  63.   ####             ####
  64. #### End of Settings ####
  65.   ####             ####
  66.  
  67.  
  68. # ctrl+c exits script, not just sox/whatever
  69. trap "exit 1" INT
  70.  
  71. # for fewer following ifs and printfs
  72. if [[ "$use_SOXCOMMAND_tag" == "1" || "$use_SOXSOURCE_tag" == "1" ]] ;then tagmsg="and tags with metaflac" ;else tagmsg="with metaflac" ;fi
  73.  
  74. # colourful printf                # does the "tput" way work on OSX/BSD/not Debian?
  75.     red="$( tput setaf 1 )"       # RED='\033[0;31m'
  76.   green="$( tput setaf 2  )"      # GREEN='\033[0;32m'
  77.  orange="$( tput setaf 3 )"       # ORANGE='\033[0;33m'
  78. default="$( tput op )"            # NC='\033[0m' # not the same as "tput op"
  79.  
  80. # positioning printfs per the previous printf
  81. printf '\n   '
  82.  
  83. # argument(s) required
  84. [[ "$#" -ge "1" ]] || { printf '%sERROR%s: Nothing to do, please specify at least one file or folder.\n\n' "$orange" "$default" >&2 ; exit 1 ; }
  85.  
  86. # dependencies
  87. [[ "$PATH" != *"/usr/local/bin"* ]] && PATH=$PATH:/usr/local/bin # Automator defaults to ignoring Homebrew
  88. command -v basename >/dev/null 2>&1 || { printf '%sERROR%s: '\''basename'\'' not found, aborting.\n\n'           "$red" "$default" >&2 ; exit 1 ; }
  89. command -v dirname  >/dev/null 2>&1 || { printf '%sERROR%s: '\''dirname'\'' not found, aborting.\n\n'            "$red" "$default" >&2 ; exit 1 ; }
  90. command -v file     >/dev/null 2>&1 || { printf '%sERROR%s: '\''file'\'' (the program) not found, aborting.\n\n' "$red" "$default" >&2 ; exit 1 ; }
  91. command -v metaflac >/dev/null 2>&1 || { printf '%sERROR%s: '\''metaflac'\'' not found, aborting.\n\n'           "$red" "$default" >&2 ; exit 1 ; }
  92. command -v sox      >/dev/null 2>&1 || { printf '%sERROR%s: '\''sox'\'' not found, aborting.\n\n'                "$red" "$default" >&2 ; exit 1 ; }
  93.  
  94. # recommends
  95. 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
  96.  
  97. if command -v parallel >/dev/null 2>&1 ;then
  98.     parallel_help="$( parallel -h )" # or maybe we should $( file --mime-type ) = perl then_gnu else_moreutils ?
  99.     if [[ "$parallel_help" == *"GNU"* ]] ;then
  100.         our_parallel="GNU"
  101.         parallel_divider=":::"
  102.         parallel_citation="--will-cite"
  103.         [[ "$threads_not_used" -gt "0" ]] && paralleljobs[0]="-j -$threads_not_used"
  104.         [[ "$use_progress_bar" == "1" ]] && parallel_progress_bar[0]="--bar"
  105.     elif [[ "$parallel_help" == *"parallel [OPTIONS] command"* ]] ;then
  106.         our_parallel="moreutils"
  107.         parallel_divider="--"
  108.         if [[ "$threads_not_used" -gt "0" ]] ;then
  109.             if [[ "$nproc" == "no_nproc" ]] ;then
  110.                 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
  111.                 paralleljobs[0]=""
  112.             else
  113.                 paralleljobs[0]="-j $( "$nproc" --ignore="$threads_not_used" )"
  114.             fi
  115.         fi
  116.     else
  117.         printf '%sERROR%s: '\''parallel'\'' exists but is not recognized, running in single-threaded mode.\n\n   ' "$orange" "$default" >&2
  118.         our_parallel="no_parallel"
  119.     fi
  120. else
  121.     printf '%s'\''parallel'\'' not found%s, running in single-threaded mode.\n\n   ' "$orange" "$default"
  122.     our_parallel="no_parallel"
  123. fi
  124.  
  125. if [[ "$our_parallel" != "no_parallel" ]] ;then
  126.     [[ "$threads_not_used" -gt "0" && "$threads_used" -gt "0" ]] && {
  127.         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
  128.         our_parallel="no_parallel" ; }
  129.     [[ "$threads_used" -gt "0" ]] && paralleljobs[0]="-j $threads_used"
  130. fi
  131.  
  132. if command -v realpath >/dev/null 2>&1 ;then realpath="realpath" ;elif command -v grealpath >/dev/null 2>&1 ;then realpath="grealpath"
  133. else realpath() { ( cd -P "$( dirname "$1" )" 2>/dev/null && printf '%s/%s\n' "$PWD" "$( basename "$1" )" ) ; } ;realpath="realpath"
  134. fi
  135.  
  136.  
  137. # get all the items, including folder and subfolder contents, from command line arguments
  138. user_args=( "$@" )
  139. for arg in "${user_args[@]}" ; do
  140.     if [[ -d "$arg" ]] ; then
  141.         user_args+=( "$arg"/* )
  142.         for subdir in "$arg"/* ; do
  143.             if [[ -d "$subdir" ]] ; then
  144.                 user_args+=( "$subdir"/* )
  145.             fi
  146.         done
  147.     fi
  148. done
  149.  
  150. printf 'Reading input file(s). '
  151.  
  152. # just the flacs, just the 24 bit flacs
  153. for flacfile in "${user_args[@]}" ;do
  154.     [[ "$( file -b --mime-type "$flacfile" )" == "audio/x-flac" ]] && [[ "$( sox --i -b "$flacfile" )" -eq "24" ]] &&
  155.         absolute_flac_names+=( "$( "$realpath" "$flacfile" )" )
  156. done
  157.  
  158. # source data
  159. for index in "${!absolute_flac_names[@]}" ;do
  160.     flac_filenames[$index]="$( basename "${absolute_flac_names[$index]}" )"
  161.     absolute_flac_dirs[$index]="$( dirname "${absolute_flac_names[$index]}" )"
  162.     flac_sample_rates[$index]="$( sox --i -r "${absolute_flac_names[$index]}" )"
  163. done
  164.  
  165. printf 'Found %s candidate FLAC file(s). Configuring output. ' "${#absolute_flac_names[@]}"
  166.  
  167. # target data
  168. for index in "${!absolute_flac_names[@]}" ;do
  169.  
  170.     # 24/44 and 24/48 --> 16/44 and 16/48
  171.     if [[ "$use_24_44_and_24_48_input" == "1" ]] ;then
  172.         [[ "${flac_sample_rates[$index]}" -eq "44100" || "${flac_sample_rates[$index]}" -eq "48000" ]] && {
  173.             target_bit_depths[$index]="16"
  174.             target_sample_rates[$index]=""
  175.             target_folders[$index]="${absolute_flac_dirs[$index]}/unresampled-16bit"
  176.             target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}" ; }
  177.     fi
  178.  
  179.     # 24/176 and 24/192 --> 24/88 and 24/96
  180.     if [[ "$use_24_88_and_24_96_output" == "1" ]] ;then
  181.         if [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
  182.  
  183.             [[ "${flac_sample_rates[$index]}" -eq "176400" ]] && {
  184.                 target_sample_rates[$index]="rate -v -L 88200"
  185.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-88" ; }
  186.  
  187.             [[ "${flac_sample_rates[$index]}" -eq "192000" ]] && {
  188.                 target_sample_rates[$index]="rate -v -L 96000"
  189.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-24-96" ; }
  190.  
  191.             target_bit_depths[$index]="24"
  192.             target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
  193.  
  194.         # 24/88 and 24/96 --> 16/44 and 16/48
  195.         elif [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ;then
  196.  
  197.             [[ "${flac_sample_rates[$index]}" -eq "88200" ]] && {
  198.                 target_sample_rates[$index]="rate -v -L 44100"
  199.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
  200.  
  201.             [[ "${flac_sample_rates[$index]}" -eq "96000" ]] && {
  202.                 target_sample_rates[$index]="rate -v -L 48000"
  203.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
  204.  
  205.             target_bit_depths[$index]="16"
  206.             target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
  207.         fi
  208.  
  209.     else
  210.         # 24/{88,96,176,192} --> 16/$common_multiple
  211.         if [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "96000" ]] ||
  212.                [[ "${flac_sample_rates[$index]}" -eq "176400" || "${flac_sample_rates[$index]}" -eq "192000" ]] ;then
  213.  
  214.             [[ "${flac_sample_rates[$index]}" -eq "88200" || "${flac_sample_rates[$index]}" -eq "176400" ]] && {
  215.                 target_sample_rates[$index]="rate -v -L 44100"
  216.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-44" ; }
  217.  
  218.             [[ "${flac_sample_rates[$index]}" -eq "96000" || "${flac_sample_rates[$index]}" -eq "192000" ]] && {
  219.                 target_sample_rates[$index]="rate -v -L 48000"
  220.                 target_folders[$index]="${absolute_flac_dirs[$index]}/resampled-16-48" ; }
  221.  
  222.             target_bit_depths[$index]="16"
  223.             target_flacs[$index]="${target_folders[$index]}/${flac_filenames[$index]}"
  224.         fi
  225.     fi
  226. done
  227.  
  228. # construct lists of sox and metaflac commands
  229. for index in "${!target_flacs[@]}" ;do
  230.     [[ ! -d "${target_folders[$index]}" ]] && mkdir "${target_folders[$index]}"
  231.      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"
  232.     #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"
  233.     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]}\""
  234.     cmdlistmfss[$index]="metaflac --set-tag=SOXSOURCE=\"24 bit, \"${flac_sample_rates[$index]}\" Hz\" \"${target_flacs[$index]}\""
  235. done
  236.  
  237. # run command lists, if possible using multiple threads
  238. if [[ "$our_parallel" == "no_parallel" ]] ;then
  239.     printf 'Converting %s target(s) with SoX.\n' "${#target_flacs[@]}"
  240.     for index in "${!cmdlistsox[@]}" ;do
  241.         printf '\n   Converting %s.\n      ' "${target_flacs[$index]}"
  242.         if eval "${cmdlistsox[$index]}" ;then
  243.             printf '%sSuccess%s! Adding padding %s.\n' "$green" "$default" "$tagmsg"
  244.             metaflac --add-padding="$flac_padding" "${target_flacs[$index]}"      || printf '      %sERROR%s: Failure adding padding to %s.\n'        "$red" "$default" "${target_flacs[$index]}" >&2
  245.             [[ "$use_SOXCOMMAND_tag" == "1" ]] && { eval "${cmdlistmfsc[$index]}" || printf '      %sERROR%s: Failure adding SOXCOMMAND tag to %s.\n' "$red" "$default" "${target_flacs[$index]}" >&2 ; }
  246.             [[ "$use_SOXSOURCE_tag" == "1" ]]  && { eval "${cmdlistmfss[$index]}" || printf '      %sERROR%s: Failure adding SOXSOURCE tag to %s.\n'  "$red" "$default" "${target_flacs[$index]}" >&2 ; }
  247.         else
  248.             printf '%sERROR%s: SoX had non-zero exit status converting %s, aborting follow-up tasks.\n' "$red" "$default" "${target_flacs[$index]}" >&2
  249.         fi
  250.     done
  251.     printf '\n'
  252. else
  253.     printf 'Converting %s target(s) with SoX and %s%s%s Parallel.\n' "${#target_flacs[@]}" "$orange" "$our_parallel" "$default"
  254.     if parallel ${parallel_citation[0]} ${parallel_progress_bar[0]} ${paralleljobs[0]} "${parallel_divider[0]}" "${cmdlistsox[@]}" ;then
  255.         printf '   Adding padding %s. ' "$tagmsg"
  256.         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
  257.         [[ "$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 ; }
  258.         [[ "$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 ; }
  259.         printf '%sDone%s!\n\n' "$green" "$default"
  260.     else
  261.         printf '%sERROR%s: Parallel/SoX had non-zero exit status, aborting follow-up tasks.\n\n' "$red" "$default" >&2
  262.         exit 1
  263.     fi
  264. fi
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement