Skip to content

Instantly share code, notes, and snippets.

@elfakyn
Last active December 11, 2024 17:48
Show Gist options
  • Save elfakyn/dd62a92d701b494f1a06cdd57ac6da62 to your computer and use it in GitHub Desktop.
Save elfakyn/dd62a92d701b494f1a06cdd57ac6da62 to your computer and use it in GitHub Desktop.
Make a portable, lossy copy of your music library. https://elfakyn.com/music/organize-your-library
#!/usr/bin/env bash
# Make a portable, lossy copy of your music library. https://elfakyn.com/music/organize-your-library
SOURCE_DIR="/source/music/folder"
DEST_DIR="/destination/music/folder"
JOB_FILE="./conversion_jobs.txt"
ERROR_LOG="./conversion_errors.log"
> "$JOB_FILE"
> "$ERROR_LOG"
declare -a partial_files
convert_flac_to_opus() {
local file="$1"
local dest_opus_path="$2"
partial_files+=("$dest_opus_path")
if ! opusenc --quiet --vbr --bitrate 256 "$file" "$dest_opus_path" 2>&1 | tee -a "$ERROR_LOG"; then
# If conversion failed, clean up the partially written file
rm -f "$dest_opus_path" 2>/dev/null
fi
partial_files=("${partial_files[@]/$dest_opus_path}")
}
export -f convert_flac_to_opus
export ERROR_LOG
total_files=$(find "$SOURCE_DIR" -type f | wc -l)
files_processed=0
files_skipped=0
files_copied=0
flac_files_queued=0
trap 'echo -e "\nProcess interrupted. Exiting..."; clean_up_partial_files; kill 0' SIGINT
clean_up_partial_files() {
echo "Cleaning up partially written files..."
for file in "${partial_files[@]}"; do
rm -f "$file"
echo "Removed partial file: $file"
done
}
echo -n "Processing files: "
find "$SOURCE_DIR" -type f | while IFS= read -r file; do
relative_path="${file#$SOURCE_DIR/}"
dest_path="$DEST_DIR/$relative_path"
dest_folder=$(dirname "$dest_path")
mkdir -p "$dest_folder"
dest_opus_path="${dest_path%.flac}.opus"
if [ "${file##*.}" == "flac" ]; then
if [ -f "$dest_opus_path" ] && [ "$dest_opus_path" -nt "$file" ]; then
# Skip the file if the destination file is newer
files_skipped=$((files_skipped + 1))
else
echo "convert_flac_to_opus \"$file\" \"$dest_opus_path\"" >> "$JOB_FILE"
flac_files_queued=$((flac_files_queued + 1))
fi
else
# Check for non-FLAC files
if [ -f "$dest_path" ] && [ "$dest_path" -nt "$file" ]; then
# Skip the file if the destination file is newer
files_skipped=$((files_skipped + 1))
else
partial_files+=("$dest_path")
cp "$file" "$dest_path"
partial_files=("${partial_files[@]/$dest_path}")
files_copied=$((files_copied + 1))
fi
fi
files_processed=$((files_processed + 1))
echo -ne "\rProcessing files: $files_processed/$total_files (Copied: $files_copied, Skipped: $files_skipped, FLAC queued: $flac_files_queued)"
done
echo -e "\nDone copying files."
echo "Running conversions in parallel..."
cat "$JOB_FILE" | parallel --progress --eta --bar
# Update m3u and m3u8 playlists to reference .opus files instead of .flac
find "$DEST_DIR" -type f \( -iname "*.m3u" -o -iname "*.m3u8" \) -print0 | while IFS= read -r -d '' playlist; do
sed -i 's/\.flac/\.opus/g' "$playlist"
echo "Updated playlist: $playlist"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment