0.6.9: Finish the Stage 4 path revisit in the three remaining scripts
The 2026-07-08 path rewrite changed beets' displayed paths from the transitional /music/... mount to real /data/music/Library/... paths. dedup-library.sh was fixed in 0.6.8; three more scripts still assumed the old prefix, each failing silently: - replace-with-better.sh resolved every library match to a doubled nonexistent path, logged [stale-db], skipped the in-place replace, and then imported the staged FLAC as a NEW track via the leftover-import step. Net effect: the mp3-to-flac upgrade flow produced flac+mp3 twin pairs in the library (surfacing in the dedup queue) instead of replacing the MP3 in place. - fix-track-metadata.py queried beets only by the legacy /music/... form, matched nothing, and silently skipped beet update/move after retagging. - export-laptop-playlists.py filtered out every library track (no displayed path starts with /music/ anymore), producing empty exports. All three now use the real displayed path and keep the /music/... form only as a legacy fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,10 +73,10 @@ Optional, add these later if you want them:
|
|||||||
Pull the prebuilt image onto your Docker host:
|
Pull the prebuilt image onto your Docker host:
|
||||||
|
|
||||||
```bash
|
```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.)
|
(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
|
```yaml
|
||||||
services:
|
services:
|
||||||
alembic:
|
alembic:
|
||||||
image: git.kretzer.club/andrew/alembic:0.6.8
|
image: git.kretzer.club/andrew/alembic:0.6.9
|
||||||
container_name: alembic
|
container_name: alembic
|
||||||
ports:
|
ports:
|
||||||
- "8420:8420"
|
- "8420:8420"
|
||||||
|
|||||||
@@ -57,11 +57,12 @@ OUT_DIR = f"{_MUSIC_DATA_DIR}/playlists-laptop"
|
|||||||
# Absolute Windows path to the laptop's library root.
|
# Absolute Windows path to the laptop's library root.
|
||||||
LIBRARY_LAPTOP_ROOT = r"C:\Music\Library"
|
LIBRARY_LAPTOP_ROOT = r"C:\Music\Library"
|
||||||
|
|
||||||
# Beets stores paths with this prefix (the container-side mount of the
|
# Prefixes beets may display library paths under, tried in order: the real
|
||||||
# library); strip it to get the path relative to the library root. This is
|
# library dir (everything since the 2026-07-08 path rewrite) and the legacy
|
||||||
# still "/music/" through the migration's Stage 0-3 transitional mount;
|
# transitional mount (any stale pre-rewrite entry). Strip whichever matches
|
||||||
# revisit at Stage 4 once beets' directory: becomes MUSIC_DATA_DIR/Library.
|
# to get the path relative to the library root; when neither matches the
|
||||||
BEETS_LIBRARY_PREFIX = "/music/"
|
# track is outside the library and is skipped.
|
||||||
|
BEETS_LIBRARY_PREFIXES = (f"{_MUSIC_DATA_DIR}/Library/", "/music/")
|
||||||
|
|
||||||
M3U_EXT = ".m3u"
|
M3U_EXT = ".m3u"
|
||||||
# Older formats from earlier iterations of this script — swept by reconcile.
|
# 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:
|
if len(parts) != 4:
|
||||||
continue
|
continue
|
||||||
aa, alb, ti, path = parts
|
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
|
continue
|
||||||
rel = path[len(BEETS_LIBRARY_PREFIX):]
|
|
||||||
idx.setdefault((_normkey(alb), _normkey(ti)), []).append((aa, alb, ti, rel))
|
idx.setdefault((_normkey(alb), _normkey(ti)), []).append((aa, alb, ti, rel))
|
||||||
return idx
|
return idx
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
_MUSIC_DATA_DIR = os.environ.get("MUSIC_DATA_DIR", "/data/music")
|
||||||
|
|
||||||
LIBRARY = f"{_MUSIC_DATA_DIR}/Library"
|
LIBRARY = f"{_MUSIC_DATA_DIR}/Library"
|
||||||
# Still "/music" through the migration's Stage 0-3 transitional beets mount;
|
# Legacy prefix from the migration's transitional beets mount. Since the
|
||||||
# revisit at Stage 4 once beets' directory: becomes MUSIC_DATA_DIR/Library.
|
# 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"
|
CONTAINER_PREFIX = "/music"
|
||||||
SPOTIFY_ENV = f"{_ALEMBIC_CONFIG_DIR}/pipeline/_spotify.env"
|
SPOTIFY_ENV = f"{_ALEMBIC_CONFIG_DIR}/pipeline/_spotify.env"
|
||||||
SPOTIFY_GENRE = f"{os.environ.get('PIPELINE_DIR', '/app/pipeline')}/lib/spotify-genre.py"
|
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):
|
def beets_id_for(host_path):
|
||||||
cp = host_to_container(host_path)
|
# Real path first (how beets displays everything since the 2026-07-08
|
||||||
r = subprocess.run(
|
# path rewrite), legacy /music/... form second for any stale entry.
|
||||||
["beet", "ls", "-f", "$id|||$path", f"path:{cp}"],
|
# Querying only the legacy form (the old behavior) matched nothing after
|
||||||
capture_output=True, text=True, check=True)
|
# the rewrite, so retag-from-url silently skipped beet update/move.
|
||||||
for line in r.stdout.splitlines():
|
for cp in (host_path, host_to_container(host_path)):
|
||||||
if "|||" in line:
|
r = subprocess.run(
|
||||||
id_str, p = line.split("|||", 1)
|
["beet", "ls", "-f", "$id|||$path", f"path:{cp}"],
|
||||||
if p == cp:
|
capture_output=True, text=True, check=True)
|
||||||
return int(id_str)
|
for line in r.stdout.splitlines():
|
||||||
|
if "|||" in line:
|
||||||
|
id_str, p = line.split("|||", 1)
|
||||||
|
if p == cp:
|
||||||
|
return int(id_str)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -195,7 +195,18 @@ while IFS= read -r -d '' new_file; do
|
|||||||
existing_id="${match%%$'\x1f'*}"
|
existing_id="${match%%$'\x1f'*}"
|
||||||
existing_container="${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
|
if [[ ! -f "$existing" ]]; then
|
||||||
echo "[stale-db] beets has $existing_container but file missing" | tee -a "$LOG"
|
echo "[stale-db] beets has $existing_container but file missing" | tee -a "$LOG"
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user