+#!/bin/bash
+# Desc: Converts a directory of mp3s into a single opus file
+# Usage: mp3s_to_opus.sh [DIR in] [DIR out] [BITRATE]
+# Example: mp3s_to_opus.sh ./dir_source ./dir_output 48k
+# Depends: GNU Coretils 8.32 (date)
+# Version: 0.0.3
+
+# plumbing
+opus_bitrate="$3"; # e.g. "48k"
+script_rundate="$(date +%s)";
+dir_tmp="/dev/shm";
+dir_in="$(readlink -f "$1")";
+dir_out="$(readlink -f "$2")";
+file_flist="$dir_tmp"/"$script_rundate"..flist.txt;
+file_out_opus="$dir_out"/output.opus;
+file_albumart="$dir_out"/albumart.png;
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+show_usage() {
+ # Desc: Display script usage information
+ # Usage: showUsage
+ # Version 0.0.2
+ # Input: none
+ # Output: stdout
+ # Depends: GNU-coreutils 8.30 (cat)
+ cat <<'EOF'
+ USAGE:
+ mp3s_to_opus.sh [DIR in] [DIR out] [BITRATE]
+
+ EXAMPLE:
+ mp3s_to_opus.sh ./source_dir ./out_dir 48k
+EOF
+} # Display information on how to use this script.
+check_depends() {
+ if ! command -v ffmpeg; then show_usage; die "FATAL:Missing ffmpeg."; fi;
+}; # check dependencies
+check_plumbing() {
+ if [[ $# -ne 3 ]]; then show_usage; die "FATAL:Invalid arg count:$#"; fi;
+ if [[ ! -d "$dir_in" ]]; then show_usage; die "FATAL:Not a dir:$dir_in"; fi;
+ if [[ ! -d "$dir_out" ]]; then mkdir -p "$2"; fi;
+}; # check arguments
+build_filelist() {
+ # Depends: var dir_tmp temporary directory
+ # var dir_in input dir
+ # var file_flist path file list
+ # Output: file: $file_flist list of mp3 files for ffmpeg
+
+ # Change directory to input dir
+ pushd "$dir_in" || die "FATAL:Directory error:$(pwd)";
+
+ while read -r line; do
+ yell "$(printf "file '%s'\n" "${line#./}")";
+ printf "file '%s'\n" "${line#./}" >> "$file_flist";
+ done < <(find "$dir_in" -type f -iname "*.mp3" | sort);
+
+ # Return to original dir
+ popd || die "FATAL:Directory error:$(pwd)";
+
+}; # build file list for ffmpeg
+ffmpeg_convert() {
+ # Depends: var dir_tmp
+ # dir_in
+ # dir_out
+ # Input: file $file_flist list of mp3 files for ffmpeg
+
+ # Change directory to input dir
+ pushd "$dir_in" || die "FATAL:Directory error:$(pwd)";
+
+ # Concatenate mp3 files into a single WAV file
+ # # Convert WAV to 48 kbps opus file
+ ffmpeg -f concat -safe 0 -i "$file_flist" -c:a pcm_s24le -rf64 auto -f wav - | \
+ ffmpeg -i - -c:a libopus -b:a "$opus_bitrate" "$file_out_opus";
+
+ # Return to original dir
+ popd || die "FATAL:Directory error:$(pwd)";
+}; # convert mp3s to opus via ffmpeg
+save_albumart() {
+ local file
+ file="$(find "$dir_in" -type f -iname "*.mp3" | sort | head -n1)";
+ file="$(readlink -f "$file")";
+ ffmpeg -i "$file" -an -vcodec copy "$file_albumart";
+}; # save album art from an mp3 to output dir
+main() {
+ check_depends && yell "DEBUG:check_depends OK";
+ check_plumbing "$@" && yell "DEBUG:check_plumbing OK";
+ build_filelist "$@" && yell "DEBUG:build_filelist OK";
+ ffmpeg_convert "$@" && yell "DEBUG:ffmpeg_convert OK";
+ save_albumart "$@" && yell "DEBUG:save_albumart OK";
+}; # main program
+
+main "$@";
+