Split runtime builders into owning modules
- Move the import pipeline runtime factory into core.imports.pipeline - Move the metadata runtime factory into core.metadata.enrichment - Keep the web server wiring thin and drop the shared glue module - Add contract tests that keep the two runtime bundles separate
This commit is contained in:
parent
bcab54095e
commit
9b2b6d856f
5 changed files with 206 additions and 63 deletions
|
|
@ -6,6 +6,8 @@ import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from config.settings import config_manager
|
from config.settings import config_manager
|
||||||
from core.imports.file_ops import (
|
from core.imports.file_ops import (
|
||||||
|
|
@ -68,12 +70,35 @@ from utils.logging_config import get_logger
|
||||||
logger = get_logger("imports.pipeline")
|
logger = get_logger("imports.pipeline")
|
||||||
pp_logger = get_logger("post_processing")
|
pp_logger = get_logger("post_processing")
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"build_import_pipeline_runtime",
|
||||||
|
"post_process_matched_download",
|
||||||
|
"post_process_matched_download_with_verification",
|
||||||
|
]
|
||||||
|
|
||||||
def post_process_matched_download(context_key, context, file_path, runtime):
|
|
||||||
|
def build_import_pipeline_runtime(
|
||||||
|
*,
|
||||||
|
automation_engine: Any | None = None,
|
||||||
|
on_download_completed: Any | None = None,
|
||||||
|
web_scan_manager: Any | None = None,
|
||||||
|
repair_worker: Any | None = None,
|
||||||
|
) -> SimpleNamespace:
|
||||||
|
"""Build the runtime object consumed by core.imports.pipeline."""
|
||||||
|
return SimpleNamespace(
|
||||||
|
automation_engine=automation_engine,
|
||||||
|
on_download_completed=on_download_completed,
|
||||||
|
web_scan_manager=web_scan_manager,
|
||||||
|
repair_worker=repair_worker,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def post_process_matched_download(context_key, context, file_path, runtime, metadata_runtime=None):
|
||||||
on_download_completed = getattr(runtime, "on_download_completed", None)
|
on_download_completed = getattr(runtime, "on_download_completed", None)
|
||||||
automation_engine = getattr(runtime, "automation_engine", None)
|
automation_engine = getattr(runtime, "automation_engine", None)
|
||||||
web_scan_manager = getattr(runtime, "web_scan_manager", None)
|
web_scan_manager = getattr(runtime, "web_scan_manager", None)
|
||||||
repair_worker = getattr(runtime, "repair_worker", None)
|
repair_worker = getattr(runtime, "repair_worker", None)
|
||||||
|
metadata_runtime = metadata_runtime or runtime
|
||||||
|
|
||||||
def _notify_download_completed(batch_id, task_id, success=True):
|
def _notify_download_completed(batch_id, task_id, success=True):
|
||||||
if on_download_completed:
|
if on_download_completed:
|
||||||
|
|
@ -364,7 +389,7 @@ def post_process_matched_download(context_key, context, file_path, runtime):
|
||||||
f"[Metadata Input] Playlist mode - artist: '{artist_context.get('name', 'MISSING')}' "
|
f"[Metadata Input] Playlist mode - artist: '{artist_context.get('name', 'MISSING')}' "
|
||||||
f"(id: {artist_context.get('id', 'MISSING')})"
|
f"(id: {artist_context.get('id', 'MISSING')})"
|
||||||
)
|
)
|
||||||
enhance_file_metadata(file_path, context, artist_context, None, runtime=runtime)
|
enhance_file_metadata(file_path, context, artist_context, None, runtime=metadata_runtime)
|
||||||
except Exception as meta_err:
|
except Exception as meta_err:
|
||||||
import traceback
|
import traceback
|
||||||
pp_logger.info(f"[inner] Metadata enhancement FAILED for {context_key}: {meta_err}\n{traceback.format_exc()}")
|
pp_logger.info(f"[inner] Metadata enhancement FAILED for {context_key}: {meta_err}\n{traceback.format_exc()}")
|
||||||
|
|
@ -529,7 +554,7 @@ def post_process_matched_download(context_key, context, file_path, runtime):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info("[Metadata Input] album_info: None (single track)")
|
logger.info("[Metadata Input] album_info: None (single track)")
|
||||||
enhance_file_metadata(file_path, context, artist_context, album_info, runtime=runtime)
|
enhance_file_metadata(file_path, context, artist_context, album_info, runtime=metadata_runtime)
|
||||||
except Exception as meta_err:
|
except Exception as meta_err:
|
||||||
import traceback
|
import traceback
|
||||||
pp_logger.info(f"[inner] Metadata enhancement FAILED for {context_key}: {meta_err}\n{traceback.format_exc()}")
|
pp_logger.info(f"[inner] Metadata enhancement FAILED for {context_key}: {meta_err}\n{traceback.format_exc()}")
|
||||||
|
|
@ -762,7 +787,7 @@ def post_process_matched_download(context_key, context, file_path, runtime):
|
||||||
post_process_locks.pop(context_key, None)
|
post_process_locks.pop(context_key, None)
|
||||||
|
|
||||||
|
|
||||||
def post_process_matched_download_with_verification(context_key, context, file_path, task_id, batch_id, runtime):
|
def post_process_matched_download_with_verification(context_key, context, file_path, task_id, batch_id, runtime, metadata_runtime=None):
|
||||||
on_download_completed = getattr(runtime, "on_download_completed", None)
|
on_download_completed = getattr(runtime, "on_download_completed", None)
|
||||||
|
|
||||||
def _notify_download_completed(batch_id, task_id, success=True):
|
def _notify_download_completed(batch_id, task_id, success=True):
|
||||||
|
|
@ -773,7 +798,7 @@ def post_process_matched_download_with_verification(context_key, context, file_p
|
||||||
try:
|
try:
|
||||||
original_task_id = context.pop('task_id', None)
|
original_task_id = context.pop('task_id', None)
|
||||||
original_batch_id = context.pop('batch_id', None)
|
original_batch_id = context.pop('batch_id', None)
|
||||||
post_process_matched_download(context_key, context, file_path, runtime)
|
post_process_matched_download(context_key, context, file_path, runtime, metadata_runtime=metadata_runtime)
|
||||||
if original_task_id:
|
if original_task_id:
|
||||||
context['task_id'] = original_task_id
|
context['task_id'] = original_task_id
|
||||||
if original_batch_id:
|
if original_batch_id:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from core.metadata.artwork import embed_album_art_metadata
|
from core.metadata.artwork import embed_album_art_metadata
|
||||||
from core.metadata.common import (
|
from core.metadata.common import (
|
||||||
|
|
@ -19,6 +21,7 @@ from utils.logging_config import get_logger as _create_logger
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"build_metadata_enrichment_runtime",
|
||||||
"enhance_file_metadata",
|
"enhance_file_metadata",
|
||||||
"extract_source_metadata",
|
"extract_source_metadata",
|
||||||
"embed_source_ids",
|
"embed_source_ids",
|
||||||
|
|
@ -28,6 +31,32 @@ __all__ = [
|
||||||
logger = _create_logger("metadata.enrichment")
|
logger = _create_logger("metadata.enrichment")
|
||||||
|
|
||||||
|
|
||||||
|
def build_metadata_enrichment_runtime(
|
||||||
|
*,
|
||||||
|
mb_worker: Any | None = None,
|
||||||
|
deezer_worker: Any | None = None,
|
||||||
|
audiodb_worker: Any | None = None,
|
||||||
|
tidal_client: Any | None = None,
|
||||||
|
qobuz_enrichment_worker: Any | None = None,
|
||||||
|
lastfm_worker: Any | None = None,
|
||||||
|
genius_worker: Any | None = None,
|
||||||
|
spotify_enrichment_worker: Any | None = None,
|
||||||
|
itunes_enrichment_worker: Any | None = None,
|
||||||
|
) -> SimpleNamespace:
|
||||||
|
"""Build the runtime object consumed by core.metadata.enrichment/source."""
|
||||||
|
return SimpleNamespace(
|
||||||
|
mb_worker=mb_worker,
|
||||||
|
deezer_worker=deezer_worker,
|
||||||
|
audiodb_worker=audiodb_worker,
|
||||||
|
tidal_client=tidal_client,
|
||||||
|
qobuz_enrichment_worker=qobuz_enrichment_worker,
|
||||||
|
lastfm_worker=lastfm_worker,
|
||||||
|
genius_worker=genius_worker,
|
||||||
|
spotify_enrichment_worker=spotify_enrichment_worker,
|
||||||
|
itunes_enrichment_worker=itunes_enrichment_worker,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def enhance_file_metadata(file_path: str, context: dict, artist: dict, album_info: dict, runtime=None) -> bool:
|
def enhance_file_metadata(file_path: str, context: dict, artist: dict, album_info: dict, runtime=None) -> bool:
|
||||||
cfg = get_config_manager()
|
cfg = get_config_manager()
|
||||||
if cfg.get("metadata_enhancement.enabled", True) is False:
|
if cfg.get("metadata_enhancement.enabled", True) is False:
|
||||||
|
|
|
||||||
|
|
@ -126,3 +126,84 @@ def test_verification_wrapper_handles_simple_download(tmp_path, monkeypatch):
|
||||||
runtime_state.processed_download_ids.update(original_processed_ids)
|
runtime_state.processed_download_ids.update(original_processed_ids)
|
||||||
runtime_state.post_process_locks.clear()
|
runtime_state.post_process_locks.clear()
|
||||||
runtime_state.post_process_locks.update(original_post_locks)
|
runtime_state.post_process_locks.update(original_post_locks)
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_process_matched_download_forwards_separate_metadata_runtime(tmp_path, monkeypatch):
|
||||||
|
source_path = tmp_path / "source.flac"
|
||||||
|
source_path.write_bytes(b"audio")
|
||||||
|
target_path = tmp_path / "Album Folder" / "track.flac"
|
||||||
|
|
||||||
|
runtime = types.SimpleNamespace(
|
||||||
|
automation_engine=None,
|
||||||
|
on_download_completed=None,
|
||||||
|
web_scan_manager=None,
|
||||||
|
repair_worker=None,
|
||||||
|
)
|
||||||
|
metadata_runtime = types.SimpleNamespace(marker="metadata-runtime")
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
monkeypatch.setattr(import_pipeline, "config_manager", types.SimpleNamespace(
|
||||||
|
get=lambda key, default=None: {
|
||||||
|
"post_processing.replaygain_enabled": False,
|
||||||
|
"lossy_copy.enabled": False,
|
||||||
|
"lossy_copy.delete_original": False,
|
||||||
|
"import.replace_lower_quality": False,
|
||||||
|
"soulseek.download_path": str(tmp_path / "downloads"),
|
||||||
|
}.get(key, default)
|
||||||
|
))
|
||||||
|
monkeypatch.setattr(import_pipeline, "normalize_import_context", lambda context: context)
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_import_track_info", lambda context: {"_playlist_folder_mode": True, "_playlist_name": "Playlist"})
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_import_original_search", lambda context: {"title": "Track", "album": "Album"})
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_import_context_artist", lambda context: {"name": "Artist"})
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_import_has_clean_metadata", lambda context: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
import_pipeline,
|
||||||
|
"build_import_album_info",
|
||||||
|
lambda context, force_album=False: {
|
||||||
|
"is_album": True,
|
||||||
|
"album_name": "Album",
|
||||||
|
"track_number": 1,
|
||||||
|
"disc_number": 1,
|
||||||
|
"clean_track_name": "Track",
|
||||||
|
"source": "spotify",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(import_pipeline, "resolve_album_group", lambda artist_context, album_info, original_album: album_info["album_name"])
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_import_clean_title", lambda *args, **kwargs: "Track")
|
||||||
|
monkeypatch.setattr(import_pipeline, "get_audio_quality_string", lambda file_path: "")
|
||||||
|
monkeypatch.setattr(import_pipeline, "check_flac_bit_depth", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "build_final_path_for_track", lambda *args, **kwargs: (str(target_path), None))
|
||||||
|
|
||||||
|
def _capture_enhance(file_path, context, artist, album_info, runtime=None):
|
||||||
|
seen["runtime"] = runtime
|
||||||
|
return True
|
||||||
|
|
||||||
|
monkeypatch.setattr(import_pipeline, "enhance_file_metadata", _capture_enhance)
|
||||||
|
monkeypatch.setattr(import_pipeline, "safe_move_file", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "download_cover_art", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "generate_lrc_file", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "downsample_hires_flac", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "create_lossy_copy", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "cleanup_empty_directories", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "emit_track_downloaded", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "record_library_history_download", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "record_download_provenance", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "record_soulsync_library_entry", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "check_and_remove_from_wishlist", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(import_pipeline, "record_retag_download", lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"track_info": {"_playlist_folder_mode": True, "_playlist_name": "Playlist"},
|
||||||
|
"original_search_result": {"title": "Track", "album": "Album"},
|
||||||
|
"is_album_download": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
import_pipeline.post_process_matched_download(
|
||||||
|
"ctx-1",
|
||||||
|
context,
|
||||||
|
str(source_path),
|
||||||
|
runtime,
|
||||||
|
metadata_runtime=metadata_runtime,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert seen["runtime"] is metadata_runtime
|
||||||
|
|
|
||||||
56
tests/metadata/test_runtime_bundle.py
Normal file
56
tests/metadata/test_runtime_bundle.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import types
|
||||||
|
|
||||||
|
from core.imports.pipeline import build_import_pipeline_runtime
|
||||||
|
from core.metadata.enrichment import build_metadata_enrichment_runtime
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_import_pipeline_runtime_exposes_expected_contract():
|
||||||
|
import_fields = {
|
||||||
|
"automation_engine": object(),
|
||||||
|
"on_download_completed": object(),
|
||||||
|
"web_scan_manager": object(),
|
||||||
|
"repair_worker": object(),
|
||||||
|
}
|
||||||
|
runtime = build_import_pipeline_runtime(**import_fields)
|
||||||
|
|
||||||
|
assert isinstance(runtime, types.SimpleNamespace)
|
||||||
|
for name, value in import_fields.items():
|
||||||
|
assert hasattr(runtime, name)
|
||||||
|
assert getattr(runtime, name) is value
|
||||||
|
|
||||||
|
for name in (
|
||||||
|
"mb_worker",
|
||||||
|
"deezer_worker",
|
||||||
|
"audiodb_worker",
|
||||||
|
"tidal_client",
|
||||||
|
"qobuz_enrichment_worker",
|
||||||
|
"lastfm_worker",
|
||||||
|
"genius_worker",
|
||||||
|
"spotify_enrichment_worker",
|
||||||
|
"itunes_enrichment_worker",
|
||||||
|
):
|
||||||
|
assert not hasattr(runtime, name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_metadata_enrichment_runtime_exposes_expected_contract():
|
||||||
|
metadata_fields = {
|
||||||
|
"mb_worker": object(),
|
||||||
|
"deezer_worker": object(),
|
||||||
|
"audiodb_worker": object(),
|
||||||
|
"tidal_client": object(),
|
||||||
|
"qobuz_enrichment_worker": object(),
|
||||||
|
"lastfm_worker": object(),
|
||||||
|
"genius_worker": object(),
|
||||||
|
"spotify_enrichment_worker": object(),
|
||||||
|
"itunes_enrichment_worker": object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime = build_metadata_enrichment_runtime(**metadata_fields)
|
||||||
|
|
||||||
|
assert isinstance(runtime, types.SimpleNamespace)
|
||||||
|
for name, value in metadata_fields.items():
|
||||||
|
assert hasattr(runtime, name)
|
||||||
|
assert getattr(runtime, name) is value
|
||||||
|
|
||||||
|
for name in ("automation_engine", "on_download_completed", "web_scan_manager", "repair_worker"):
|
||||||
|
assert not hasattr(runtime, name)
|
||||||
|
|
@ -132,7 +132,9 @@ from core.imports.staging import (
|
||||||
start_import_suggestions_cache,
|
start_import_suggestions_cache,
|
||||||
)
|
)
|
||||||
from core.imports.paths import build_final_path_for_track as _build_final_path_for_track
|
from core.imports.paths import build_final_path_for_track as _build_final_path_for_track
|
||||||
|
from core.imports.pipeline import build_import_pipeline_runtime as _build_import_pipeline_runtime
|
||||||
from core.metadata.common import get_file_lock
|
from core.metadata.common import get_file_lock
|
||||||
|
from core.metadata.enrichment import build_metadata_enrichment_runtime as _build_metadata_enrichment_runtime
|
||||||
from core.metadata.source import (
|
from core.metadata.source import (
|
||||||
mb_release_cache,
|
mb_release_cache,
|
||||||
mb_release_cache_lock,
|
mb_release_cache_lock,
|
||||||
|
|
@ -19678,13 +19680,13 @@ import urllib.request
|
||||||
def _wipe_source_tags(file_path: str) -> bool:
|
def _wipe_source_tags(file_path: str) -> bool:
|
||||||
return metadata_enrichment.wipe_source_tags(file_path)
|
return metadata_enrichment.wipe_source_tags(file_path)
|
||||||
|
|
||||||
def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_info: dict, runtime=None) -> bool:
|
def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_info: dict, metadata_runtime=None) -> bool:
|
||||||
return metadata_enrichment.enhance_file_metadata(
|
return metadata_enrichment.enhance_file_metadata(
|
||||||
file_path,
|
file_path,
|
||||||
context,
|
context,
|
||||||
artist,
|
artist,
|
||||||
album_info,
|
album_info,
|
||||||
runtime=runtime or _build_import_pipeline_runtime(),
|
runtime=metadata_runtime or _build_metadata_enrichment_runtime(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19778,24 +19780,7 @@ def _post_process_matched_download_with_verification(context_key, context, file_
|
||||||
task_id,
|
task_id,
|
||||||
batch_id,
|
batch_id,
|
||||||
_build_import_pipeline_runtime(),
|
_build_import_pipeline_runtime(),
|
||||||
)
|
_build_metadata_enrichment_runtime(),
|
||||||
|
|
||||||
def _build_import_pipeline_runtime():
|
|
||||||
"""Collect live controller dependencies for the shared import pipeline."""
|
|
||||||
return types.SimpleNamespace(
|
|
||||||
automation_engine=automation_engine,
|
|
||||||
on_download_completed=_on_download_completed,
|
|
||||||
web_scan_manager=web_scan_manager,
|
|
||||||
repair_worker=repair_worker,
|
|
||||||
mb_worker=mb_worker,
|
|
||||||
deezer_worker=deezer_worker,
|
|
||||||
audiodb_worker=audiodb_worker,
|
|
||||||
tidal_client=tidal_client,
|
|
||||||
qobuz_enrichment_worker=qobuz_enrichment_worker,
|
|
||||||
lastfm_worker=lastfm_worker,
|
|
||||||
genius_worker=genius_worker,
|
|
||||||
spotify_enrichment_worker=spotify_enrichment_worker,
|
|
||||||
itunes_enrichment_worker=itunes_enrichment_worker,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19903,45 +19888,12 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
just move files to /Transfer without metadata enhancement.
|
just move files to /Transfer without metadata enhancement.
|
||||||
"""
|
"""
|
||||||
from core.imports.pipeline import post_process_matched_download
|
from core.imports.pipeline import post_process_matched_download
|
||||||
return post_process_matched_download(context_key, context, file_path, _build_import_pipeline_runtime())
|
return post_process_matched_download(
|
||||||
|
context_key,
|
||||||
def _build_import_pipeline_runtime():
|
context,
|
||||||
"""Collect the live controller dependencies needed by core.imports.pipeline."""
|
|
||||||
return types.SimpleNamespace(
|
|
||||||
automation_engine=automation_engine,
|
|
||||||
on_download_completed=_on_download_completed,
|
|
||||||
web_scan_manager=web_scan_manager,
|
|
||||||
repair_worker=repair_worker,
|
|
||||||
mb_worker=mb_worker,
|
|
||||||
deezer_worker=deezer_worker,
|
|
||||||
audiodb_worker=audiodb_worker,
|
|
||||||
tidal_client=tidal_client,
|
|
||||||
qobuz_enrichment_worker=qobuz_enrichment_worker,
|
|
||||||
lastfm_worker=lastfm_worker,
|
|
||||||
genius_worker=genius_worker,
|
|
||||||
spotify_enrichment_worker=spotify_enrichment_worker,
|
|
||||||
itunes_enrichment_worker=itunes_enrichment_worker,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _wipe_source_tags(file_path: str) -> bool:
|
|
||||||
return metadata_enrichment.wipe_source_tags(file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_info: dict, runtime=None) -> bool:
|
|
||||||
return metadata_enrichment.enhance_file_metadata(
|
|
||||||
file_path,
|
file_path,
|
||||||
context,
|
_build_import_pipeline_runtime(),
|
||||||
artist,
|
metadata_runtime=_build_metadata_enrichment_runtime(),
|
||||||
album_info,
|
|
||||||
runtime=runtime or _build_import_pipeline_runtime(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _download_cover_art(album_info: dict, target_dir: str, context: dict = None):
|
|
||||||
return metadata_enrichment.download_cover_art(
|
|
||||||
album_info,
|
|
||||||
target_dir,
|
|
||||||
context,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Track stale transfer keys (completed in slskd but no context — e.g., from before app restart)
|
# Track stale transfer keys (completed in slskd but no context — e.g., from before app restart)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue