| 1 | #!/bin/bash |
| 2 | # Desc: Converts a directory of mp3s into a single opus file |
| 3 | # Usage: mp3s_to_opus.sh [DIR in] [DIR out] [BITRATE] |
| 4 | # Example: mp3s_to_opus.sh ./dir_source ./dir_output 48k |
| 5 | # Depends: GNU Coretils 8.32 (date) |
| 6 | # Version: 0.0.4 |
| 7 | |
| 8 | # plumbing |
| 9 | opus_bitrate="$3"; # e.g. "48k" |
| 10 | script_rundate="$(date +%s)"; |
| 11 | dir_tmp="/dev/shm"; |
| 12 | dir_in="$(readlink -f "$1")"; |
| 13 | dir_out="$(readlink -f "$2")"; |
| 14 | file_flist="$dir_tmp"/"$script_rundate"..flist.txt; |
| 15 | file_out_opus="$dir_out"/output.opus; |
| 16 | file_albumart="$dir_out"/albumart.png; |
| 17 | |
| 18 | yell() { echo "$0: $*" >&2; } # print script path and all args to stderr |
| 19 | die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status |
| 20 | must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails |
| 21 | show_usage() { |
| 22 | # Desc: Display script usage information |
| 23 | # Usage: showUsage |
| 24 | # Version 0.0.2 |
| 25 | # Input: none |
| 26 | # Output: stdout |
| 27 | # Depends: GNU-coreutils 8.30 (cat) |
| 28 | cat <<'EOF' |
| 29 | USAGE: |
| 30 | mp3s_to_opus.sh [DIR in] [DIR out] [BITRATE] |
| 31 | |
| 32 | EXAMPLE: |
| 33 | mp3s_to_opus.sh ./source_dir ./out_dir 48k |
| 34 | EOF |
| 35 | } # Display information on how to use this script. |
| 36 | check_depends() { |
| 37 | if ! command -v ffmpeg; then show_usage; die "FATAL:Missing ffmpeg."; fi; |
| 38 | }; # check dependencies |
| 39 | check_plumbing() { |
| 40 | if [[ $# -ne 3 ]]; then show_usage; die "FATAL:Invalid arg count:$#"; fi; |
| 41 | if [[ ! -d "$dir_in" ]]; then show_usage; die "FATAL:Not a dir:$dir_in"; fi; |
| 42 | if [[ ! -d "$dir_out" ]]; then mkdir -p "$2"; fi; |
| 43 | }; # check arguments |
| 44 | build_filelist() { |
| 45 | # Depends: var dir_tmp temporary directory |
| 46 | # var dir_in input dir |
| 47 | # var file_flist path file list |
| 48 | # Output: file: $file_flist list of mp3 files for ffmpeg |
| 49 | |
| 50 | # Change directory to input dir |
| 51 | pushd "$dir_in" || die "FATAL:Directory error:$(pwd)"; |
| 52 | |
| 53 | while read -r line; do |
| 54 | yell "$(printf "file '%s'\n" "${line#./}")"; |
| 55 | printf "file '%s'\n" "${line#./}" >> "$file_flist"; |
| 56 | done < <(find "$dir_in" -type f -iname "*.mp3" | sort); |
| 57 | |
| 58 | # Return to original dir |
| 59 | popd || die "FATAL:Directory error:$(pwd)"; |
| 60 | |
| 61 | }; # build file list for ffmpeg |
| 62 | ffmpeg_convert() { |
| 63 | # Depends: var dir_tmp |
| 64 | # dir_in |
| 65 | # dir_out |
| 66 | # Input: file $file_flist list of mp3 files for ffmpeg |
| 67 | |
| 68 | # Change directory to input dir |
| 69 | pushd "$dir_in" || die "FATAL:Directory error:$(pwd)"; |
| 70 | |
| 71 | # Concatenate mp3 files into a single WAV file |
| 72 | # # Convert WAV to 48 kbps opus file |
| 73 | ffmpeg -nostdin -f concat -safe 0 -i "$file_flist" -c:a pcm_s24le -rf64 auto -f wav - | \ |
| 74 | ffmpeg -i - -c:a libopus -b:a "$opus_bitrate" "$file_out_opus"; |
| 75 | |
| 76 | # Return to original dir |
| 77 | popd || die "FATAL:Directory error:$(pwd)"; |
| 78 | }; # convert mp3s to opus via ffmpeg |
| 79 | save_albumart() { |
| 80 | local file |
| 81 | file="$(find "$dir_in" -type f -iname "*.mp3" | sort | head -n1)"; |
| 82 | file="$(readlink -f "$file")"; |
| 83 | ffmpeg -nostdin -i "$file" -an -vcodec copy "$file_albumart"; |
| 84 | }; # save album art from an mp3 to output dir |
| 85 | main() { |
| 86 | check_depends && yell "DEBUG:check_depends OK"; |
| 87 | check_plumbing "$@" && yell "DEBUG:check_plumbing OK"; |
| 88 | build_filelist "$@" && yell "DEBUG:build_filelist OK"; |
| 89 | ffmpeg_convert "$@" && yell "DEBUG:ffmpeg_convert OK"; |
| 90 | save_albumart "$@" && yell "DEBUG:save_albumart OK"; |
| 91 | }; # main program |
| 92 | |
| 93 | main "$@"; |
| 94 | |