Skip to content

Instantly share code, notes, and snippets.

@juliavdkris
Last active August 29, 2025 20:31
Show Gist options
  • Select an option

  • Save juliavdkris/8e5824290ad38ba5b32fffc8c9fd5be0 to your computer and use it in GitHub Desktop.

Select an option

Save juliavdkris/8e5824290ad38ba5b32fffc8c9fd5be0 to your computer and use it in GitHub Desktop.
Modified version of Lily's script to convert music files from flac to opus <3
#!/usr/bin/env bash
set -euo pipefail
### ─── CONFIG ───────────────────────────────────────────────────────────────
INPUT_DIR="/home/julia/Music"
OUTPUT_DIR="/home/julia/MusicOpus"
HASH_FILE="$OUTPUT_DIR/.converted_hashes.txt"
COVER_HASH_FILE="$OUTPUT_DIR/.cover_hashes.txt"
LOG_FILE="$OUTPUT_DIR/conversion.log"
BITRATE="128" # opusenc bitrate in kbps
MAX_JOBS=$(nproc) # parallel jobs
### ─────────────────────────────────────────────────────────────────────────
# TODO: some conversions are still unsuccessful
# Check `fd -e flac | wc -l` against `fd -e opus | wc -l`
# prepare
mkdir -p "$OUTPUT_DIR"
touch "$HASH_FILE" "$COVER_HASH_FILE" "$LOG_FILE"
# tee all output into the log as well
exec > >(tee -a "$LOG_FILE") 2>&1
echo "===== Starting batch at $(date) ====="
# sanitize for filesystem
sanitize() {
echo "$1" |
tr '/\\?%*:|"<>.' '_' |
sed 's/[[:space:]]*$//'
}
artist_substitutions() {
echo "$1" |
sed 's/REN/Ren/' |
sed 's/Kyteman Orchestra, The/Kyteman Orchestra/'
}
convert_one() {
local flac="$1"
echo "→ Processing: $flac"
# 1) compute and check FLAC hash
local hash
hash=$(sha256sum "$flac" | cut -d' ' -f1)
if grep -Fxq "$hash" "$HASH_FILE"; then
echo " • Already converted, skipping."
return
fi
local album_dir
album_dir=$(dirname "$flac")
local dest_dir=${album_dir//$INPUT_DIR/$OUTPUT_DIR}
dest_dir=$(echo "$dest_dir" | sed 's/\[.*FLAC.*\]/\[OPUS\]/')
mkdir -p "$dest_dir"
# 5) convert to opus with embedded tags
local out="$dest_dir/$(basename "${flac%.flac}.opus")"
echo " • Converting → $out"
# Get existing albumartist and artist
album_artist=$(ffprobe -v quiet -show_entries format_tags=album_artist -of csv=p=0 "$flac")
artist=$(ffprobe -v quiet -show_entries format_tags=artist -of csv=p=0 "$flac")
# If no albumartist exists, use artist
if [ -z "$album_artist" ]; then
ffmpeg -i "$flac" -c:a libopus -b:a 128k -map_metadata 0 -metadata album_artist="$artist" "$out"
else
ffmpeg -i "$flac" -c:a libopus -b:a 128k -map_metadata 0 "$out"
fi
# 6) copy any cover art from the FLAC folder
local src_dir
src_dir=$(dirname "$flac")
find "$src_dir" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) |
while IFS= read -r img; do
local img_hash
img_hash=$(sha256sum "$img" | cut -d' ' -f1)
if grep -Fxq "$img_hash" "$COVER_HASH_FILE"; then
echo " • Cover already copied: $(basename "$img")"
else
echo " • Copying cover: $(basename "$img")"
cp "$img" "$dest_dir/"
echo "$img_hash" >>"$COVER_HASH_FILE"
fi
done
# if no external image was found, try extracting embedded art
if ! grep -q "^CoverCopied:" <<<"$dest_dir"; then
# dump the first embedded picture, if any
# metaflac will error out if there’s no picture, so we redirect stderr
if metaflac --export-picture-to=- "$flac" 2>/dev/null |
file --brief --mime-type - | grep -qE '^(image/(jpeg|png))$'; then
ext=$(metaflac --list --block-type=PICTURE "$flac" |
grep MIME-type | head -n1 | cut -d' ' -f2 | sed 's#image/##')
outimg="$dest_dir/cover.${ext:-jpg}"
echo " • Extracting embedded cover → $(basename "$outimg")"
metaflac --export-picture-to="$outimg" "$flac"
# record hash so we don’t do it twice
sha256sum "$outimg" | cut -d' ' -f1 >>"$COVER_HASH_FILE"
fi
fi
# 7) record FLAC hash
echo "$hash" >>"$HASH_FILE"
echo " ✔ Done."
}
export -f convert_one sanitize artist_substitutions
export INPUT_DIR OUTPUT_DIR HASH_FILE COVER_HASH_FILE BITRATE
# 8) find & parallelize
find "$INPUT_DIR" -type f -iname '*.flac' -print0 |
xargs -0 -n1 -P"$MAX_JOBS" bash -c 'convert_one "$0"'
echo "===== Batch finished at $(date) ====="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment