Files
alembic/pipeline/lib/fix-genre.sh
T
andrew 68cb007e4c Stage 0: migrate pipeline scripts from /opt/sldl, scaffold repo
Moves all ~30 pipeline scripts, configs, and the vendored sldl binary into
this repo (source /opt/sldl left untouched). Removes all docker exec/docker
compose dependencies now that beets and sldl run in-process/as a subprocess
of this container instead of via soulbeet/on-demand sldl containers.
Replaces hardcoded host paths, Navidrome credentials, and Spotify credential
sourcing with env-var-driven paths and shared credential loaders. Adds
Dockerfile, entrypoint.sh, requirements.txt, docker-compose.snippet.yml, and
the initial app DB schema.
2026-07-08 13:10:55 -06:00

124 lines
3.7 KiB
Bash
Executable File

#!/bin/bash
# fix-genre.sh — quickly override the GENRE tag for every track by an artist.
#
# Use when spotify-genre.py picks something wrong (e.g. it tagged Yasuha as
# "Asian Music" when "Anime; Soundtrack" is what you actually want), or when
# you want to set a genre on an artist Spotify doesn't have data for.
#
# The new value is written to file tags AND beets's DB. The next nightly
# spotify-genre run will leave these alone (they already match the whitelist
# format), unless you pass --force.
#
# Usage:
# fix-genre.sh "<artist>" "<Genre1; Genre2; ...>" # apply
# fix-genre.sh --dry "<artist>" "<Genre1; ...>" # preview only
#
# Examples:
# sudo fix-genre.sh "Yasuha" "Anime; Soundtrack"
# sudo fix-genre.sh "Aphex Twin" "IDM; Electronic; Ambient"
# sudo fix-genre.sh --dry "Mac DeMarco" "Indie; Indie Rock"
set -u
DRY=0
if [[ "${1:-}" == "--dry" ]]; then
DRY=1
shift
fi
ARTIST="${1:?Usage: $0 [--dry] <artist> <Genre1; Genre2; ...>}"
GENRE="${2:?Usage: $0 [--dry] <artist> <Genre1; Genre2; ...>}"
LIBRARY=${MUSIC_DATA_DIR:-/data/music}/Library
# Pull paths via beets (in-process, same container).
mapfile -t paths < <(beet ls -f '$path' "artist:$ARTIST" 2>/dev/null)
if [[ "${#paths[@]}" -eq 0 ]]; then
echo "[fix-genre] no tracks found in beets for artist:$ARTIST"
exit 1
fi
echo "[fix-genre] artist=\"$ARTIST\""
echo "[fix-genre] new GENRE=\"$GENRE\""
echo "[fix-genre] ${#paths[@]} track(s) match"
[[ $DRY -eq 1 ]] && echo "[fix-genre] DRY RUN — no tags will be written" && exit 0
ok=0
fail=0
for cp in "${paths[@]}"; do
hp="${LIBRARY}${cp#/music}"
if [[ ! -f "$hp" ]]; then
echo " MISS $hp"
fail=$((fail + 1))
continue
fi
case "${hp,,}" in
*.flac)
metaflac --remove-tag=GENRE "$hp" 2>/dev/null
metaflac --set-tag="GENRE=$GENRE" "$hp" 2>/dev/null && ok=$((ok + 1)) || fail=$((fail + 1))
metaflac --remove-tag=GENRE_LOCK "$hp" 2>/dev/null
metaflac --set-tag="GENRE_LOCK=1" "$hp" 2>/dev/null
;;
*.mp3)
# mid3v2 / id3v2 don't have a clean way to set TCON via plain id3v2 here,
# so use mutagen via a one-liner.
python3 - "$hp" "$GENRE" <<'PY' && ok=$((ok + 1)) || fail=$((fail + 1))
import sys
from mutagen.id3 import ID3, TCON, TXXX, ID3NoHeaderError
path, g = sys.argv[1], sys.argv[2]
try:
t = ID3(path)
except ID3NoHeaderError:
t = ID3()
t.delall("TCON")
t.add(TCON(encoding=3, text=g))
t.delall("TXXX:GENRE_LOCK")
t.add(TXXX(encoding=3, desc="GENRE_LOCK", text="1"))
t.save(path)
PY
;;
*.m4a|*.mp4)
python3 - "$hp" "$GENRE" <<'PY' && ok=$((ok + 1)) || fail=$((fail + 1))
import sys
from mutagen.mp4 import MP4
path, g = sys.argv[1], sys.argv[2]
m = MP4(path)
m["\xa9gen"] = g
m.save()
PY
;;
*.opus|*.ogg)
python3 - "$hp" "$GENRE" <<'PY' && ok=$((ok + 1)) || fail=$((fail + 1))
import sys
from mutagen import File as MFile
path, g = sys.argv[1], sys.argv[2]
m = MFile(path)
m.tags["genre"] = [g]
m.save()
PY
;;
*)
echo " SKIP unsupported format: $hp"
fail=$((fail + 1))
;;
esac
done
echo "[fix-genre] tagged ok=$ok, failed=$fail"
# Sync beets DB so its $genres column matches the file tags.
# `-q` is NOT a flag for `beet update` (only `beet ls`); leaving it off
# lets beets actually apply the diff non-interactively.
echo "[fix-genre] syncing beets DB"
beet update "artist:$ARTIST" 2>&1 | grep -E '^\s+[+-]' || true
# Verify
echo "[fix-genre] new state:"
beet ls -f ' $title — $genres' "artist:$ARTIST" | head -5
remaining=$(beet ls "artist:$ARTIST" | wc -l)
shown=5
[[ "$remaining" -gt "$shown" ]] && echo " ...and $((remaining - shown)) more"
echo "[fix-genre] done. Trigger a Navidrome scan to surface the change in the UI."