#!/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.4

# 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 -nostdin -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 -nostdin -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 "$@";

