Last active
February 10, 2026 01:09
-
-
Save crischutu07/9b43a24a84d667d68d74f1280fec666d to your computer and use it in GitHub Desktop.
flac to opus audio conversion with fancy stuff (just why?)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # flac2opus.sh | |
| # | |
| # a conversion script that i use for doing cross directories audio conversion (from FLAC to opus container/codec or file format) | |
| # | |
| # Author: Nguyen Hong Son (crischutu07) <cris@crischutu07.is-a.dev> | |
| # | |
| # enable this => /**/* | |
| shopt -s globstar | |
| # exit if a command fails | |
| set -e | |
| set -o pipefail | |
| DEPS=(ffmpeg opusenc) | |
| function debug() { | |
| if [ "$DEBUG" -eq 1 ]; then | |
| echo "[debug] $@" | |
| fi | |
| } | |
| function log() { | |
| echo "$@" | |
| } | |
| function warn() { | |
| echo "[warn] $@" | |
| } | |
| function fatal() { | |
| echo "[fatal] $@" | |
| [ -z "$2" ] && exit 1 || exit $2 | |
| } | |
| # force overwrite data when encoding | |
| # also this is easier | |
| OVERWRITE=1 | |
| DEBUG=1 | |
| OUTPUT_FOLDER="out" | |
| COVERJPG=0 | |
| INSTALLED=() | |
| debug "List dependencies: ${DEPS[@]}" | |
| for i in "${DEPS[@]}"; do | |
| if ! command -v "$i" >/dev/null 2>&1; then | |
| case "$i" in | |
| opusenc) | |
| warn "You don't have opusenc installed!" | |
| warn "FFmpeg doesn't support embedding album cover for opus file except opusenc. We will create a cover.jpg as a replacement." | |
| warn "If you don't want to create cover.jpg, set COVERJPG=0" | |
| OPUSENC=0 | |
| ;; | |
| *) | |
| debug "Can't find $i, exitting.." | |
| fatal "This script requires $i" | |
| ;; | |
| esac | |
| else | |
| INSTALLED+="$i" | |
| debug "Installed tool: $i" | |
| fi | |
| done | |
| unset DEPS INSTALLED | |
| AUDIO_FILES=() | |
| OPUSENC=1 | |
| function scanfile() { | |
| for i in "$1"/**/*; do | |
| if [[ "${i##*.}" == "flac" ]]; then | |
| debug "scanfile: $i" | |
| AUDIO_FILES+=("$i") | |
| fi | |
| done | |
| debug "scanfile: scanned ${#AUDIO_FILES[@]} file(s) inside AUDIO_FILES" | |
| } | |
| # TODO: scan folder that may or may not contains a flac file, otherwise do the code below: | |
| if [ -z "$1" ]; then | |
| AUDIO_FOLDER="$(pwd)" | |
| else | |
| AUDIO_FOLDER="${1}" | |
| fi | |
| debug "AUDIO_FOLDER: $AUDIO_FOLDER" | |
| scanfile "$AUDIO_FOLDER" | |
| if [ -z "$AUDIO_FILES" ]; then | |
| log "$0: cannot find any file inside: ${AUDIO_FOLDER}" | |
| exit 1 | |
| fi | |
| if [ -z "${OVERWRITE}" ]; then | |
| OVERWRITE="-n" | |
| else | |
| OVERWRITE="-y" | |
| fi | |
| for i in "${AUDIO_FILES[@]}"; do | |
| coverfile=$(mktemp --suffix=".jpg") | |
| # remove relative path | |
| file="${i/"$AUDIO_FOLDER"}" | |
| debug "1st parse: $file" | |
| # remove "/" suffix | |
| file="${file#/}" | |
| debug "2nd parse: $file" | |
| # remove extension | |
| file="${file%%.*}" | |
| debug "3rd parse: $file" | |
| # relative path without filename for create folder | |
| filedir="${OUTPUT_FOLDER}/${file%/*}" | |
| debug "creating filedir: ${filedir}" | |
| mkdir -p "${filedir}" | |
| filepath="${OUTPUT_FOLDER}/${file}.opus" | |
| debug "input: $i" | |
| debug "filepath: ${filepath}" | |
| if [ "$OPUSENC" -eq 0 ]; then | |
| debug "opusenc is disabled, fallback to ffmpeg" | |
| ffmpeg -i "$i" \ | |
| -hide_banner \ | |
| -loglevel warning \ | |
| -stats \ | |
| -c:a libopus \ | |
| -b:a 512k \ | |
| -vbr on \ | |
| -ar 48000 \ | |
| -map_metadata 0 \ | |
| "${filepath}" \ | |
| "$OVERWRITE" | |
| else | |
| debug "opusenc is enabled, attempting to use it" | |
| opusenc \ | |
| --bitrate 512k \ | |
| --comp 10 \ | |
| --vbr \ | |
| --framesize 20 \ | |
| "$i" \ | |
| "${filepath}" | |
| fi | |
| # create front cover art. | |
| # compress level 10 is lossless | |
| # ^ i know, it's the default value but i'm too lazy to read ffmpeg docs. | |
| if [ "$COVERJPG" -eq 1 ]; then | |
| COVERPATH="${filedir}/cover.jpg" | |
| debug "creating $COVERPATH" | |
| ffmpeg -i "$i" \ | |
| -loglevel warning \ | |
| -an \ | |
| -frames:v 1 \ | |
| -c:v copy \ | |
| "$coverfile" -y | |
| cp "$coverfile" "$COVERPATH" | |
| else | |
| # get only the directory path | |
| dir="$(basename "$i")" | |
| if [ -f "$dir/cover.jpg" ]; then | |
| # copy directory's cover.jpg to temp coverfile | |
| # if it's available. | |
| cp -v "$dir/cover.jpg" "$coverfile" | |
| fi | |
| fi | |
| rm "$coverfile" | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment