From 13dca2dea6bbfabe80cf402df56f368045be785b Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 8 Jun 2026 18:25:50 -0700 Subject: [PATCH] =?UTF-8?q?Cover=20Art=20Filler:=20write=20cover.jpg=20to?= =?UTF-8?q?=20the=20RESOLVED=20folder,=20not=20the=20raw=20DB=20path=20(So?= =?UTF-8?q?khi=20=E2=80=94=20the=20actual=20bug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THE bug behind "embeds art but never writes cover.jpgs": _fix_missing_cover_art passed `details['album_folder']` (= dirname of the raw DB path, e.g. Jellyfin's /data/music/...) as the target folder. That path doesn't exist inside the SoulSync container — only the resolved /app/... path does. So apply_art_to_album_ files' `os.path.isdir(target_dir)` was False and the ENTIRE cover.jpg block was silently skipped: embedding still worked (it uses the resolved paths) and the DB thumb updated, but the sidecar was never written. Exactly Sokhi's symptom + the "Cover art already present — database thumbnail updated" toast (cover_written stayed False). Fix: - _fix_missing_cover_art: derive the folder from the RESOLVED file (os.path.dirname(resolved[0])), never the raw album_folder. - apply_art_to_album_files: bulletproof it — if the passed folder doesn't exist, fall back to the real directory of the files instead of silently skipping. Tests: a non-existent folder still writes cover.jpg to the real file dir. 1348 cover/art/repair tests pass. --- core/metadata/art_apply.py | 11 +++++++++-- core/repair_worker.py | 9 ++++++++- tests/test_art_apply.py | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/metadata/art_apply.py b/core/metadata/art_apply.py index 0da1dcea..d1ff6639 100644 --- a/core/metadata/art_apply.py +++ b/core/metadata/art_apply.py @@ -198,8 +198,15 @@ def apply_art_to_album_files( logger.warning("Could not embed art into %s: %s", fp, exc) result["failed"] += 1 - target_dir = folder or (os.path.dirname(paths[0]) if paths else None) - if target_dir and os.path.isdir(target_dir) and not folder_has_cover_sidecar(target_dir): + # Prefer the caller's folder, but if it doesn't actually exist (e.g. a raw + # DB path that isn't mounted in this container), fall back to the real + # directory of the files we just wrote to — never silently skip the sidecar + # because a passed-in folder was wrong (Sokhi: cover.jpg never written). + target_dir = folder if (folder and os.path.isdir(folder)) else None + if not target_dir and paths: + cand = os.path.dirname(paths[0]) + target_dir = cand if os.path.isdir(cand) else None + if target_dir and not folder_has_cover_sidecar(target_dir): # Prefer the album's OWN embedded art for the cover.jpg sidecar: it's # always present once the files are arted (we may have just embedded it), # needs no API call, and the sidecar matches the files exactly diff --git a/core/repair_worker.py b/core/repair_worker.py index dabc96fc..60c81ff8 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -1394,7 +1394,14 @@ class RepairWorker: 'album_name': album_title, 'album_image_url': artwork_url, 'musicbrainz_release_id': mbid, } - folder = details.get('album_folder') or os.path.dirname(resolved[0]) + # Use the RESOLVED file's directory — NOT details['album_folder'], which + # is the raw DB path (e.g. Jellyfin's /data/music) and frequently does + # NOT exist inside the SoulSync container (only the resolved /app/... + # path does). Passing the raw folder made os.path.isdir() fail in + # apply_art_to_album_files, silently skipping the cover.jpg write while + # embedding (which uses the resolved paths) still worked — Sokhi's + # "embeds art but never writes cover.jpgs". + folder = os.path.dirname(resolved[0]) art_result = apply_art_to_album_files(resolved, metadata, album_info, folder=folder) embedded = art_result.get('embedded', 0) diff --git a/tests/test_art_apply.py b/tests/test_art_apply.py index e070d522..d9177fb6 100644 --- a/tests/test_art_apply.py +++ b/tests/test_art_apply.py @@ -314,3 +314,23 @@ def test_apply_skips_cover_when_sidecar_exists(tmp_path, monkeypatch): aa.apply_art_to_album_files([str(f)], {}, {}, folder=str(tmp_path)) assert (tmp_path / 'cover.jpg').read_bytes() == b'EXISTING' # untouched assert dl_called == [] + + +def test_apply_cover_falls_back_to_file_dir_when_folder_doesnt_exist(tmp_path, monkeypatch): + # The Sokhi bug: the caller passed the raw DB folder (e.g. Jellyfin's + # /data/music/...) which doesn't exist in the container, so the cover.jpg + # write was silently skipped. Now it falls back to the real directory of + # the files and writes there anyway. + real_dir = tmp_path / 'real' + real_dir.mkdir() + f = real_dir / '01.flac'; f.write_bytes(b'') + audio = SimpleNamespace(pictures=[SimpleNamespace(data=b'EMB')], tags=None, save=lambda: None) + monkeypatch.setattr(aa, 'get_mutagen_symbols', lambda: _fake_symbols(audio)) + monkeypatch.setattr(aa, 'embed_album_art_metadata', lambda *a, **k: True) + monkeypatch.setattr(aa, 'download_cover_art', lambda *a, **k: None) + + res = aa.apply_art_to_album_files([str(f)], {}, {}, + folder='/data/music/does/not/exist/in/container') + + assert (real_dir / 'cover.jpg').read_bytes() == b'EMB' # written to the REAL dir + assert res['cover_written'] is True