fant0men

CUE/BIN 2 CUE/BIN/OGG bash script

Feb 28th, 2020
349
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/bash
  2. # This script is meant to take an input BIN/CUE file, extract the ISO
  3. # (as well as WAV audio tracks) from it, encode the WAV files to high
  4. # bitrate Ogg Vorbis files, and then generate a new CUE file, which
  5. # lists the Ogg Vorbis audio files.
  6. # The purpose of the script is to take DOS games that have CD Audio,
  7. # and getting rid of the need to store the uncompressed CD Audio
  8. # tracks. Ogg Vorbis takes less space and is near the same quality. The
  9. # generated CUE files can be used with DOSBox, by using the 'IMGMOUNT'
  10. # command.
  11. #
  12. # https://www.dosbox.com/wiki/IMGMOUNT
  13.  
  14. # The '-byteswap' switch is optional. It depends on what byte order
  15. # the input BIN file has. A byteswap may or may not be needed.
  16. # If the byte order is wrong, the Ogg files will be white noise.
  17. # So, it's easy to tell whether or not the byte order is correct.
  18.  
  19. # You can convert the ISO back to BIN (since it's now stripped of
  20. # its audio tracks). Use PowerISO for Linux, or the Windows version in
  21. # Wine.
  22. #
  23. # https://www.poweriso.com/
  24.  
  25. if=$(readlink -f "$1")
  26.  
  27. # Creates a function called 'usage', which will print usage and quit.
  28. usage () {
  29.     echo -e "\nUsage: $(basename "$0") [CUE] [-byteswap]\n"
  30.     exit
  31. }
  32.  
  33. # If $if is not a real file, print usage and quit.
  34. if [[ ! -f $if ]]; then
  35.     usage
  36. fi
  37.  
  38. if_bn=$(basename "$if")
  39. if_bn_lc=$(sed 's/[[:upper:]]/\L&/g' <<<"$if_bn")
  40. if_name="${if_bn_lc%.cue}"
  41. of_name=$(tr '[:blank:]' '_' <<<"$if_name")
  42.  
  43. if_dn=$(dirname "$if")
  44. of_dn="${PWD}/${of_name}-${RANDOM}"
  45.  
  46. of_cue="${of_dn}/${of_name}01.cue"
  47. of_bin="${of_dn}/${of_name}01.bin"
  48.  
  49. cue="$if"
  50. cue_tmp_f="/dev/shm/${if_bn%.cue}-${RANDOM}.cue"
  51. bin=$(find "$if_dn" -maxdepth 1 -iname "${if_name}.bin" | head -n 1)
  52.  
  53. declare -a cue_lines bchunk_list of_cue_list
  54.  
  55. # trap ctrl-c and call ctrl_c()
  56. trap ctrl_c INT
  57.  
  58. ctrl_c () {
  59.     rm -f "$cue_tmp_f"
  60.     echo '** Trapped CTRL-C'
  61.     exit
  62. }
  63.  
  64. # Creates a function called 'check_cmd', which will check if the
  65. # necessary commands are installed.
  66. check_cmd () {
  67.     for cmd in "$@"; do
  68.         command -v "$cmd" &>/dev/null
  69.  
  70.         if [[ $? -ne 0 ]]; then
  71.             echo -e "\nYou need to install '${cmd}' through your package manager!\n"
  72.             exit
  73.         fi
  74.     done
  75. }
  76.  
  77. # Creates a function called 'read_cue', which will read the input CUE
  78. # file, add full path to filenames listed in the CUE file, and create a
  79. # new temporary CUE file in /dev/shm based on this.
  80. read_cue () {
  81.     mapfile -t cue_lines < <(cat "$cue")
  82.  
  83.     declare -a bin_list not_found
  84.     n='0'
  85.  
  86.     touch "$cue_tmp_f"
  87.  
  88.     for (( i=0; i<${#cue_lines[@]}; i++ )); do
  89.         cue_lines[${i}]=$(sed -E 's/^[[:blank:]]*(.*)[[:blank:]]*$/\1/' <<<"${cue_lines[${i}]}")
  90.    
  91.         if [[ ${cue_lines[${i}]} =~ ^FILE ]]; then
  92.  
  93.             n=$(( n + 1 ))
  94.  
  95. # Extracting the filename from line, and removing path from it.
  96.             bin_tmp=$(sed -E 's/^FILE (\"{0,1}.*\"{0,1}) .*$/\1/' <<<"${cue_lines[${i}]}")
  97.             bin_tmp=$(tr -d '"' <<<"$bin_tmp")
  98.             bin_tmp=$(sed 's_.*/__' <<<"$bin_tmp")
  99.  
  100. # Adding the full path to filename.
  101.             bin_tmp="${if_dn}/${bin_tmp}"
  102.             bin_list+=("$bin_tmp")
  103.  
  104. # If $bin isn't set, set it. That means that the find command in the
  105. # beginning of the script didn't find a BIN file, but now we've found it
  106. # by parsing the input CUE file.
  107. # If the number of FILE commands is greater than 1, quit.
  108.             if [[ -z $bin ]]; then
  109.                 bin="$bin_tmp"
  110.             elif [[ $n -gt 1 ]]; then
  111.                 echo -e "\nThis CUE file contains multiple FILE commands!"
  112.                 echo -e "You need to merge all the containing files into one BIN file, using a tool like PowerISO.\n"
  113.                 rm -f "$cue_tmp_f"
  114.                 exit
  115.             fi
  116.  
  117. # Getting the filetype information from line, and replacing line with a
  118. # new one containing the full path to file. We need this, since we're
  119. # creating a temporary input CUE file in /dev/shm, so its location will
  120. # be different from the files it points to.
  121.             f_type=$(sed -E 's/^FILE \"{0,1}.*\"{0,1} (.*)$/\1/' <<<"${cue_lines[${i}]}")
  122.             cue_lines[${i}]="FILE \"${bin_tmp}\" ${f_type}"
  123.         fi
  124.  
  125.         echo "${cue_lines[${i}]}" >> "$cue_tmp_f"
  126.     done
  127.  
  128. # If the filenames in the CUE aren't real files, the print the filenames
  129. # and quit.
  130.     for (( i=0; i<${#bin_list[@]}; i++ )); do
  131.         if [[ ! -f ${bin_list[${i}]} ]]; then
  132.             not_found+=("${bin_list[${i}]}")
  133.         fi
  134.     done
  135.  
  136.     if [[ ${not_found[0]} ]]; then
  137.         echo -e "\nThe files below were not found:\n"
  138.  
  139.         for (( i=0; i<${#bin_list[@]}; i++ )); do
  140.             echo "${bin_list[${i}]}"
  141.         done
  142.  
  143.         echo
  144.  
  145.         rm -f "$cue_tmp_f"
  146.         exit
  147.     fi
  148. }
  149.  
  150. # Creates a function called 'bin_split', which will run 'bchunk' on the
  151. # input file, capture the output, and make a list of all the files
  152. # created.
  153. bin_split () {
  154.     args_tmp=(\""$bin"\" \""$cue_tmp_f"\" \""$of_name"\")
  155.  
  156. # Creates a function called 'print_stdout', which will be used to print
  157. # the output from 'bchunk' in case it quits with a non-zero exit status.
  158.     print_stdout() {
  159.         for (( i=0; i<${last}; i++ )); do
  160.             echo "${bchunk_stdout[${i}]}"
  161.         done
  162.     }
  163.  
  164.     if [[ $1 == '-byteswap' ]]; then
  165.         args=(bchunk -w -s "${args_tmp[@]}")
  166.     else
  167.         args=(bchunk -w "${args_tmp[@]}")
  168.     fi
  169.  
  170.     mapfile -t bchunk_stdout < <(eval "${args[@]}"; echo "$?")
  171.  
  172.     if [[ ${#bchunk_stdout[@]} -gt 0 ]]; then
  173.         last=$(( ${#bchunk_stdout[@]} - 1 ))
  174.     fi
  175.  
  176.     if [[ ${bchunk_stdout[${last}]} -ne 0 ]]; then
  177.         print_stdout
  178.  
  179.         exit
  180.     fi
  181.  
  182.     for (( i=0; i<${#bchunk_stdout[@]}; i++ )); do
  183.         line="${bchunk_stdout[${i}]}"
  184.  
  185.         if [[ $line == 'Writing tracks:' ]]; then
  186.  
  187.             n=$(( i + 2 ))
  188.             break
  189.         fi
  190.     done
  191.  
  192.     for (( i=${n}; i<${#bchunk_stdout[@]}; i++ )); do
  193.         line=$(sed -E 's/^ *[0-9]+: (.*\.[[:alpha:]]{3}).*/\1/' <<<"${bchunk_stdout[${i}]}")
  194.  
  195.         if [[ $line =~ .wav$ ]]; then
  196.             bchunk_list+=( "$(sed 's/.wav$/.ogg/' <<<"$line")" )
  197.         fi
  198.  
  199.         if [[ $line =~ .iso$ ]]; then
  200.             bchunk_list+=("$line")
  201.         fi
  202.     done
  203. }
  204.  
  205. # Creates a function called 'wav2ogg', which will encode the WAVs
  206. # created by 'bchunk'.
  207. wav2ogg () {
  208.     oggenc --quality=10 "${of_dn}"/*.wav || exit
  209.  
  210.     rm -f "${of_dn}"/*.wav || exit
  211. }
  212.  
  213. # Creates a function called 'create_cue', which will create a new CUE
  214. # file, based on the file list created by the 'bin_split' function.
  215. create_cue () {
  216.     n='0'
  217.  
  218.     mode_regex='^TRACK [0-9]{2,} MODE'
  219.  
  220.     declare -A gaps
  221.     declare -a modes
  222.  
  223. # Creates a function called 'add_gap', which will add pregap or postgap,
  224. # if they exist in the source CUE file.
  225.     add_gap () {
  226.         if [[ $1 == 'pre' ]]; then
  227.             if [[ ${gaps[pre,${n}]} ]]; then
  228.                 of_cue_list+=("    ${gaps[pre,${n}]}")
  229.             fi
  230.         fi
  231.         if [[ $1 == 'post' ]]; then
  232.             if [[ ${gaps[post,${n}]} ]]; then
  233.                 of_cue_list+=("    ${gaps[post,${n}]}")
  234.             fi
  235.         fi
  236.     }
  237.  
  238.     for (( i=0; i<${#cue_lines[@]}; i++ )); do
  239.         line="${cue_lines[${i}]}"
  240.  
  241.         case $line in
  242.             'TRACK'*)
  243.                 n=$(( n + 1 ))
  244.  
  245.                 if [[ $line =~ $mode_regex ]]; then
  246.                     modes[${n}]="$line"
  247.                 fi
  248.             ;;
  249.             'PREGAP'*)
  250.                 gaps[pre,${n}]="$line"
  251.             ;;
  252.             'POSTGAP'*)
  253.                 gaps[post,${n}]="$line"
  254.             ;;
  255.         esac
  256.     done
  257.  
  258.     n='0'
  259.  
  260.     for (( i=0; i<${#bchunk_list[@]}; i++ )); do
  261.         line="${bchunk_list[${i}]}"
  262.  
  263.         n=$(( n + 1 ))
  264.  
  265.         if [[ $line =~ .iso$ ]]; then
  266.             line=$(sed 's/.iso$/.bin/' <<<"$line")
  267.  
  268.             of_cue_list+=("FILE \"${line}\" BINARY")
  269.             of_cue_list+=("  ${modes[${n}]}")
  270.             add_gap pre
  271.             of_cue_list+=("    INDEX 01 00:00:00")
  272.             add_gap post
  273.         fi
  274.  
  275.         if [[ $line =~ .ogg$ ]]; then
  276.             of_cue_list+=("FILE \"${line}\" OGG")
  277.             of_cue_list+=( "$(printf "  TRACK %02d AUDIO" "$n")" )
  278.             add_gap pre
  279.             of_cue_list+=("    INDEX 01 00:00:00")
  280.             add_gap post
  281.         fi
  282.     done
  283. }
  284.  
  285. # Check if 'oggenc' and 'bchunk' are installed.
  286. check_cmd oggenc bchunk
  287.  
  288. # Create the output directory and change into it.
  289. mkdir "$of_dn" || exit
  290. cd "$of_dn" || exit
  291.  
  292. # Run the functions.
  293. read_cue
  294. bin_split "$2"
  295. wav2ogg
  296. create_cue
  297.  
  298. # Create output file, or quit.
  299. touch "$of_cue" || exit
  300.  
  301. echo
  302.  
  303. # Print the created CUE file to the terminal, and to the output file.
  304. for (( i=0; i<${#of_cue_list[@]}; i++ )); do
  305.     echo "${of_cue_list[${i}]}" | tee --append "$of_cue"
  306. done
  307.  
  308. echo
  309.  
  310. rm -f "$cue_tmp_f"
RAW Paste Data