Files
NovaSonic/scripts/fix-artist-separators.sh

124 lines
3.5 KiB
Bash

#!/usr/bin/env bash
# fix-artist-separators.sh
# Splits artist tags on "/" into ";" — with hardcoded exceptions for artist
# names that legitimately contain "/" (Au/Ra, HUNTR/X).
#
# Requires: ffprobe, ffmpeg, python3
#
# Usage:
# bash fix-artist-separators.sh [music_dir] -- dry run (no changes)
# bash fix-artist-separators.sh [music_dir] --apply -- actually modify files
MUSIC_DIR="${1:-/opt/navidrome/music}"
APPLY=false
[[ "${2:-}" == "--apply" ]] && APPLY=true
if [[ ! -d "$MUSIC_DIR" ]]; then
echo "Error: directory not found: $MUSIC_DIR"
exit 1
fi
if $APPLY; then
echo "🔧 APPLY mode — files will be modified"
else
echo "🔍 DRY RUN — no files will be changed (add --apply to apply)"
fi
echo "📂 Scanning: $MUSIC_DIR"
echo "🚫 Exceptions: Au/Ra, HUNTR/X"
echo ""
count_found=0
count_ok=0
count_fail=0
# Splits artist string on "/" while preserving exception names.
# Example: "HUNTR/X/EJAE/AUDREY NUNA" -> "HUNTR/X;EJAE;AUDREY NUNA"
# "-Prey/zecki" -> "-Prey;zecki"
# "Au/Ra" -> "Au/Ra" (exception, unchanged)
SPLIT_PY='
import sys
EXCEPTIONS = ["Au/Ra", "HUNTR/X"]
exc_lookup = [(exc, exc.lower(), exc.split("/")) for exc in EXCEPTIONS]
def smart_split(artist):
parts = artist.split("/")
result = []
i = 0
while i < len(parts):
matched = False
for exc, exc_lower, exc_parts in exc_lookup:
n = len(exc_parts)
candidate = parts[i:i+n]
if len(candidate) == n and "/".join(candidate).lower() == exc_lower:
result.append(exc)
i += n
matched = True
break
if not matched:
p = parts[i].strip()
if p:
result.append(p)
i += 1
return ";".join(result)
artist = sys.stdin.read().strip()
if "/" not in artist:
print(artist)
else:
print(smart_split(artist))
'
get_artist() {
ffprobe -v quiet -print_format json -show_format "$1" 2>/dev/null \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
tags = data.get('format', {}).get('tags', {})
print(next((v for k, v in tags.items() if k.lower() == 'artist'), ''))
" 2>/dev/null
}
while IFS= read -r -d '' file; do
artist=$(get_artist "$file") || continue
[[ "$artist" != *"/"* ]] && continue
new_artist=$(echo "$artist" | python3 -c "$SPLIT_PY" 2>/dev/null) || continue
[[ "$artist" == "$new_artist" ]] && continue
count_found=$((count_found + 1))
echo "📄 $file"
echo " Before : $artist"
echo " After : $new_artist"
if $APPLY; then
ext="${file##*.}"
tmp="${file%.*}.artist_fix_tmp.$ext"
if ffmpeg -i "$file" -map_metadata 0 -metadata "artist=$new_artist" \
-c copy "$tmp" -y -loglevel quiet 2>/dev/null; then
mv "$tmp" "$file"
echo " ✅ Updated"
count_ok=$((count_ok + 1))
else
rm -f "$tmp"
echo " ❌ Failed"
count_fail=$((count_fail + 1))
fi
fi
echo ""
done < <(find "$MUSIC_DIR" -type f \( \
-name "*.mp3" -o -name "*.flac" -o \
-name "*.m4a" -o -name "*.ogg" -o \
-name "*.opus" \) -print0)
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Files to change: $count_found"
if $APPLY; then
echo "Updated: $count_ok | Failed: $count_fail"
[[ $count_ok -gt 0 ]] && echo "" && echo "💡 Trigger a Navidrome rescan to pick up the changes."
else
echo ""
echo "Run with --apply to fix them:"
echo " bash fix-artist-separators.sh \"$MUSIC_DIR\" --apply"
fi