Last active
August 29, 2025 20:31
-
-
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
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 | |
| 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