diff --git a/core/metadata/art_apply.py b/core/metadata/art_apply.py index f81a35cd..cee020ed 100644 --- a/core/metadata/art_apply.py +++ b/core/metadata/art_apply.py @@ -176,7 +176,10 @@ def apply_art_to_album_files( # the read-only flag it sets back. cover_ctx = context if isinstance(context, dict) else {} try: - download_cover_art(album_info, target_dir, cover_ctx) + # force=True: the Cover Art Filler always writes the cover.jpg sidecar, + # regardless of the import-time "Download cover.jpg" toggle — running + # the art filler is an explicit request for the complete art (Sokhi). + download_cover_art(album_info, target_dir, cover_ctx, force=True) result["cover_written"] = folder_has_cover_sidecar(target_dir) except Exception as exc: if getattr(exc, "errno", None) == errno.EROFS: diff --git a/core/metadata/artwork.py b/core/metadata/artwork.py index 091ee1c4..758fe206 100644 --- a/core/metadata/artwork.py +++ b/core/metadata/artwork.py @@ -470,9 +470,17 @@ def embed_album_art_metadata(audio_file, metadata: dict): return False -def download_cover_art(album_info: dict, target_dir: str, context: dict = None): +def download_cover_art(album_info: dict, target_dir: str, context: dict = None, force: bool = False): + """Write cover.jpg into ``target_dir``. + + ``force`` bypasses the import-time "Download cover.jpg to album folder" + toggle — used by the Cover Art Filler, whose whole job is to add cover art + (if you explicitly run the filler you want the sidecar regardless of the + auto-import preference). The import pipeline calls this WITHOUT force, so it + still honors the user's setting. + """ cfg = get_config_manager() - if cfg.get("metadata_enhancement.cover_art_download", True) is False: + if not force and cfg.get("metadata_enhancement.cover_art_download", True) is False: return try: diff --git a/tests/test_art_apply.py b/tests/test_art_apply.py index 3a590ef1..4e3d2470 100644 --- a/tests/test_art_apply.py +++ b/tests/test_art_apply.py @@ -109,7 +109,7 @@ def test_apply_embeds_into_each_file_and_writes_cover(tmp_path, monkeypatch): monkeypatch.setattr(aa, 'embed_album_art_metadata', lambda a, m: embed_calls.append(m) or True) # download_cover_art is the standard cover.jpg writer — stub it to drop one. monkeypatch.setattr(aa, 'download_cover_art', - lambda album_info, folder, ctx=None: open(f"{folder}/cover.jpg", 'wb').close()) + lambda album_info, folder, ctx=None, **k: open(f"{folder}/cover.jpg", 'wb').close()) meta = {'artist': 'A', 'album': 'B', 'album_art_url': 'http://x/y.jpg'} res = aa.apply_art_to_album_files([str(f1), str(f2)], meta, {'album_name': 'B'}, folder=str(tmp_path)) @@ -203,7 +203,7 @@ def test_cover_only_read_only_is_detected(tmp_path, monkeypatch): audio = SimpleNamespace(pictures=['pic'], tags={'ok': 1}) # already has art → embed skipped monkeypatch.setattr(aa, 'get_mutagen_symbols', lambda: _fake_symbols(audio)) - def _fake_download(album_info, target_dir, ctx): + def _fake_download(album_info, target_dir, ctx, **k): # mimic download_cover_art's EROFS handling: record on context, swallow if isinstance(ctx, dict): ctx['_cover_read_only'] = True @@ -231,3 +231,19 @@ def test_apply_normal_failure_not_flagged_read_only(tmp_path, monkeypatch): assert res['failed'] == 1 + + +def test_filler_forces_cover_sidecar_write(tmp_path, monkeypatch): + # The Cover Art Filler must write cover.jpg regardless of the import-time + # "Download cover.jpg" toggle — so apply passes force=True (Sokhi). + f = tmp_path / 'a.mp3'; f.write_bytes(b'') + audio = SimpleNamespace(pictures=[], tags={'ok': 1}, save=lambda: None) + monkeypatch.setattr(aa, 'get_mutagen_symbols', lambda: _fake_symbols(audio)) + monkeypatch.setattr(aa, 'embed_album_art_metadata', lambda *a, **k: True) + captured = {} + monkeypatch.setattr(aa, 'download_cover_art', + lambda album_info, target_dir, ctx, **k: captured.update(k)) + + aa.apply_art_to_album_files([str(f)], {}, {}, folder=str(tmp_path)) + + assert captured.get('force') is True