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.
This commit is contained in:
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user