diff --git a/README.md b/README.md index c7fe2ba..68d6f87 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,10 @@ Optional, add these later if you want them: Pull the prebuilt image onto your Docker host: ```bash -docker pull git.kretzer.club/andrew/alembic:0.6.8 +docker pull git.kretzer.club/andrew/alembic:0.6.9 ``` -That is the whole install. You do not need to download the source or build anything. The `0.6.8` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest. +That is the whole install. You do not need to download the source or build anything. The `0.6.9` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest. (If you would rather build it yourself from source, you can, but you do not need to.) @@ -136,7 +136,7 @@ Create a file called `docker-compose.yml` on your server (put it wherever you ke ```yaml services: alembic: - image: git.kretzer.club/andrew/alembic:0.6.8 + image: git.kretzer.club/andrew/alembic:0.6.9 container_name: alembic ports: - "8420:8420" diff --git a/pipeline/lib/export-laptop-playlists.py b/pipeline/lib/export-laptop-playlists.py index 52ab5d7..906f99a 100755 --- a/pipeline/lib/export-laptop-playlists.py +++ b/pipeline/lib/export-laptop-playlists.py @@ -57,11 +57,12 @@ OUT_DIR = f"{_MUSIC_DATA_DIR}/playlists-laptop" # Absolute Windows path to the laptop's library root. LIBRARY_LAPTOP_ROOT = r"C:\Music\Library" -# Beets stores paths with this prefix (the container-side mount of the -# library); strip it to get the path relative to the library root. This is -# still "/music/" through the migration's Stage 0-3 transitional mount; -# revisit at Stage 4 once beets' directory: becomes MUSIC_DATA_DIR/Library. -BEETS_LIBRARY_PREFIX = "/music/" +# Prefixes beets may display library paths under, tried in order: the real +# library dir (everything since the 2026-07-08 path rewrite) and the legacy +# transitional mount (any stale pre-rewrite entry). Strip whichever matches +# to get the path relative to the library root; when neither matches the +# track is outside the library and is skipped. +BEETS_LIBRARY_PREFIXES = (f"{_MUSIC_DATA_DIR}/Library/", "/music/") M3U_EXT = ".m3u" # Older formats from earlier iterations of this script — swept by reconcile. @@ -157,9 +158,12 @@ def build_beets_index() -> dict[tuple[str, str], list[tuple[str, str, str, str]] if len(parts) != 4: continue aa, alb, ti, path = parts - if not path.startswith(BEETS_LIBRARY_PREFIX): + rel = next( + (path[len(p):] for p in BEETS_LIBRARY_PREFIXES if path.startswith(p)), + None, + ) + if rel is None: continue - rel = path[len(BEETS_LIBRARY_PREFIX):] idx.setdefault((_normkey(alb), _normkey(ti)), []).append((aa, alb, ti, rel)) return idx diff --git a/pipeline/lib/fix-track-metadata.py b/pipeline/lib/fix-track-metadata.py index a2e89d2..cf08dba 100755 --- a/pipeline/lib/fix-track-metadata.py +++ b/pipeline/lib/fix-track-metadata.py @@ -49,8 +49,10 @@ _ALEMBIC_CONFIG_DIR = os.environ.get("ALEMBIC_CONFIG_DIR", "/config") _MUSIC_DATA_DIR = os.environ.get("MUSIC_DATA_DIR", "/data/music") LIBRARY = f"{_MUSIC_DATA_DIR}/Library" -# Still "/music" through the migration's Stage 0-3 transitional beets mount; -# revisit at Stage 4 once beets' directory: becomes MUSIC_DATA_DIR/Library. +# Legacy prefix from the migration's transitional beets mount. Since the +# 2026-07-08 path rewrite beets displays real LIBRARY paths, so this only +# matters for accepting pasted /music/... paths as input and as a fallback +# beets query form for any stale pre-rewrite DB entry. CONTAINER_PREFIX = "/music" SPOTIFY_ENV = f"{_ALEMBIC_CONFIG_DIR}/pipeline/_spotify.env" SPOTIFY_GENRE = f"{os.environ.get('PIPELINE_DIR', '/app/pipeline')}/lib/spotify-genre.py" @@ -316,15 +318,19 @@ def resolve_input_path(arg): def beets_id_for(host_path): - cp = host_to_container(host_path) - r = subprocess.run( - ["beet", "ls", "-f", "$id|||$path", f"path:{cp}"], - capture_output=True, text=True, check=True) - for line in r.stdout.splitlines(): - if "|||" in line: - id_str, p = line.split("|||", 1) - if p == cp: - return int(id_str) + # Real path first (how beets displays everything since the 2026-07-08 + # path rewrite), legacy /music/... form second for any stale entry. + # Querying only the legacy form (the old behavior) matched nothing after + # the rewrite, so retag-from-url silently skipped beet update/move. + for cp in (host_path, host_to_container(host_path)): + r = subprocess.run( + ["beet", "ls", "-f", "$id|||$path", f"path:{cp}"], + capture_output=True, text=True, check=True) + for line in r.stdout.splitlines(): + if "|||" in line: + id_str, p = line.split("|||", 1) + if p == cp: + return int(id_str) return None diff --git a/pipeline/lib/replace-with-better.sh b/pipeline/lib/replace-with-better.sh index 376a672..9234c5e 100755 --- a/pipeline/lib/replace-with-better.sh +++ b/pipeline/lib/replace-with-better.sh @@ -195,7 +195,18 @@ while IFS= read -r -d '' new_file; do existing_id="${match%%$'\x1f'*}" existing_container="${match#*$'\x1f'}" - existing="${MUSIC_DATA_DIR:-/data/music}/Library${existing_container#/music}" + # beets displays real ${MUSIC_DATA_DIR}/Library/... paths since the + # 2026-07-08 path rewrite -- use them as-is; only a legacy /music/... + # display path still needs the prefix swap. Blindly prepending (the old + # behavior) doubled the prefix, so every match logged [stale-db], the MP3 + # never got replaced, and the leftover-import step at the bottom imported + # the staged FLAC as a NEW track -- producing exactly the flac+mp3 twins + # this script exists to prevent. + if [[ "$existing_container" == /music/* ]]; then + existing="${MUSIC_DATA_DIR:-/data/music}/Library${existing_container#/music}" + else + existing="$existing_container" + fi if [[ ! -f "$existing" ]]; then echo "[stale-db] beets has $existing_container but file missing" | tee -a "$LOG" continue