Fix single import source handling

- pass the selected manual match through singles import
- keep the import context source-aware so artist and album stay correct
- avoid treating non-Spotify IDs as wishlist Spotify IDs
- make wishlist logging and local variable names source-neutral
pull/378/head
Antti Kettunen 2 months ago
parent 594c8c1b93
commit d04573f397
No known key found for this signature in database
GPG Key ID: C6B2A3D250359BD7

@ -119,7 +119,7 @@ def _pick_best_track_match(search_results: List[Any], title: str, artist: str =
return search_results[0]
def _search_tracks_for_source(source: str, client: Any, query: str, limit: int = 1) -> List[Any]:
def search_tracks_for_source(source: str, client: Any, query: str, limit: int = 1) -> List[Any]:
if not client or not hasattr(client, 'search_tracks'):
return []
@ -366,11 +366,11 @@ def get_single_track_import_context(
if not search_query:
continue
search_results = _search_tracks_for_source(source, client, search_query, limit=5)
search_results = search_tracks_for_source(source, client, search_query, limit=5)
if not search_results and search_query != title:
search_results = _search_tracks_for_source(source, client, title, limit=5)
search_results = search_tracks_for_source(source, client, title, limit=5)
if not search_results and artist and search_query != artist:
search_results = _search_tracks_for_source(source, client, artist, limit=5)
search_results = search_tracks_for_source(source, client, artist, limit=5)
if not search_results:
continue

@ -484,10 +484,10 @@ def record_retag_download(context: Dict[str, Any], artist_context: Dict[str, Any
)
file_format = os.path.splitext(str(final_path))[1].lstrip(".").lower()
spotify_track_id = None
source_track_id = None
itunes_track_id = None
if source == "spotify":
spotify_track_id = source_ids.get("track_id", "") or None
source_track_id = source_ids.get("track_id", "") or None
elif source == "itunes":
itunes_track_id = source_ids.get("track_id", "") or None
@ -499,7 +499,7 @@ def record_retag_download(context: Dict[str, Any], artist_context: Dict[str, Any
title=title,
file_path=str(final_path),
file_format=file_format,
spotify_track_id=spotify_track_id,
spotify_track_id=source_track_id,
itunes_track_id=itunes_track_id,
)
logger.info("[Retag] Recorded track for retag: '%s' in '%s'", title, album_name)
@ -513,31 +513,38 @@ def check_and_remove_from_wishlist(context: Dict[str, Any]) -> None:
"""Check whether a successful download should be removed from the wishlist."""
try:
wishlist_service = get_wishlist_service()
spotify_track_id = None
source = get_import_source(context)
source_ids = get_import_source_ids(context)
source_label = {
"spotify": "Spotify",
"itunes": "iTunes",
"deezer": "Deezer",
"discogs": "Discogs",
"hydrabase": "Hydrabase",
}.get(source, "Source")
track_info = context.get("track_info", {})
if track_info.get("id"):
spotify_track_id = track_info["id"]
logger.info("[Wishlist] Found Spotify ID from track_info: %s", spotify_track_id)
elif context.get("original_search_result", {}).get("id"):
spotify_track_id = context["original_search_result"]["id"]
logger.info("[Wishlist] Found Spotify ID from original_search_result: %s", spotify_track_id)
track_id = None
if source == "spotify":
track_id = source_ids.get("track_id") or None
if track_id:
logger.info("[Wishlist] Found %s track ID from source_ids: %s", source_label, track_id)
elif "wishlist_id" in track_info:
wishlist_id = track_info["wishlist_id"]
logger.info("[Wishlist] Found wishlist_id in context: %s", wishlist_id)
wishlist_tracks = _all_profile_wishlist_tracks(wishlist_service)
for wishlist_track in wishlist_tracks:
if wishlist_track.get("wishlist_id") == wishlist_id:
spotify_track_id = wishlist_track.get("spotify_track_id") or wishlist_track.get("id")
logger.info("[Wishlist] Found Spotify ID from wishlist entry: %s", spotify_track_id)
track_id = wishlist_track.get("spotify_track_id") or wishlist_track.get("id")
logger.info("[Wishlist] Found track ID from wishlist entry: %s", track_id)
break
if not spotify_track_id:
if not track_id:
track_name = track_info.get("name") or context.get("original_search_result", {}).get("title", "")
artist_name = _primary_track_artist_name(track_info) or _primary_track_artist_name(context.get("original_search_result", {}))
if track_name and artist_name:
logger.warning("[Wishlist] No Spotify ID found, checking for fuzzy match: '%s' by '%s'", track_name, artist_name)
logger.warning("[Wishlist] No track ID found, checking for fuzzy match: '%s' by '%s'", track_name, artist_name)
wishlist_tracks = _all_profile_wishlist_tracks(wishlist_service)
for wishlist_track in wishlist_tracks:
@ -550,18 +557,18 @@ def check_and_remove_from_wishlist(context: Dict[str, Any]) -> None:
else:
wl_artist_name = str(wl_artists[0]).lower()
if wl_name == track_name.lower() and wl_artist_name == artist_name.lower():
spotify_track_id = wishlist_track.get("spotify_track_id") or wishlist_track.get("id")
logger.info("[Wishlist] Found fuzzy match - Spotify ID: %s", spotify_track_id)
track_id = wishlist_track.get("spotify_track_id") or wishlist_track.get("id")
logger.info("[Wishlist] Found fuzzy match - track ID: %s", track_id)
break
if spotify_track_id:
logger.info("[Wishlist] Attempting to remove track from wishlist: %s", spotify_track_id)
removed = wishlist_service.mark_track_download_result(spotify_track_id, success=True)
if track_id:
logger.info("[Wishlist] Attempting to remove track from wishlist: %s", track_id)
removed = wishlist_service.mark_track_download_result(track_id, success=True)
if removed:
logger.info("[Wishlist] Successfully removed track from wishlist: %s", spotify_track_id)
logger.info("[Wishlist] Successfully removed track from wishlist: %s", track_id)
else:
logger.warning(" [Wishlist] Track not found in wishlist or already removed: %s", spotify_track_id)
logger.warning(" [Wishlist] Track not found in wishlist or already removed: %s", track_id)
else:
logger.warning(" [Wishlist] No Spotify track ID found for wishlist removal check")
logger.warning(" [Wishlist] No track ID found for wishlist removal check")
except Exception as exc:
logger.error("[Wishlist] Error in wishlist removal check: %s", exc)

@ -137,9 +137,9 @@ def _search_albums_for_source(source: str, client: Any, query: str, limit: int =
def _search_tracks_for_source(source: str, client: Any, query: str, limit: int = 5):
from core.metadata_service import _search_tracks_for_source as _metadata_search_tracks_for_source
from core.imports.resolution import search_tracks_for_source
return _metadata_search_tracks_for_source(source, client, query, limit=limit)
return search_tracks_for_source(source, client, query, limit=limit)
def _extract_value(value: Any, *names: str, default: Any = None) -> Any:

@ -18625,32 +18625,43 @@ def _check_and_remove_from_wishlist(context):
"""
try:
from core.wishlist_service import get_wishlist_service
from core.imports.context import get_import_source, get_import_source_ids
wishlist_service = get_wishlist_service()
# Try to extract Spotify track ID from various sources in the context
# Try to extract a source-aware track ID from the context
spotify_track_id = None
# Populated lazily by Method 3 or Method 4. Initialized here so Method 4's
# `if not wishlist_tracks` guard doesn't UnboundLocalError when Methods 1/2
# found nothing and Method 3 never ran (no wishlist_id in track_info).
wishlist_tracks = []
# Method 1: Direct track_info with id
# Method 1: Source-specific track lookup from track_info / source_ids
track_info = context.get('track_info', {})
if track_info.get('id'):
spotify_track_id = track_info['id']
logger.info(f"[Wishlist] Found Spotify ID from track_info: {spotify_track_id}")
# Method 2: From original search result
elif context.get('original_search_result', {}).get('id'):
source = get_import_source(context)
source_ids = get_import_source_ids(context)
source_label = {
'spotify': 'Spotify',
'itunes': 'iTunes',
'deezer': 'Deezer',
'discogs': 'Discogs',
'hydrabase': 'Hydrabase',
}.get(source, 'Source')
if source == 'spotify' and source_ids.get('track_id'):
spotify_track_id = source_ids['track_id']
logger.info(f"[Wishlist] Found {source_label} track ID from source_ids: {spotify_track_id}")
# Method 2: Fallback to the original search result for source-specific IDs
elif source == 'spotify' and context.get('original_search_result', {}).get('id'):
spotify_track_id = context['original_search_result']['id']
logger.info(f"[Wishlist] Found Spotify ID from original_search_result: {spotify_track_id}")
logger.info(f"[Wishlist] Found {source_label} track ID from original_search_result: {spotify_track_id}")
# Method 3: Check if this is a wishlist download (context has wishlist_id)
elif 'wishlist_id' in track_info:
wishlist_id = track_info['wishlist_id']
logger.info(f"[Wishlist] Found wishlist_id in context: {wishlist_id}")
# Get the Spotify track ID from the wishlist entry (search all profiles)
# Get the track ID from the wishlist entry (search all profiles)
database = get_database()
all_profiles = database.get_all_profiles()
wishlist_tracks = []
@ -18659,16 +18670,16 @@ def _check_and_remove_from_wishlist(context):
for wl_track in wishlist_tracks:
if wl_track.get('wishlist_id') == wishlist_id:
spotify_track_id = wl_track.get('spotify_track_id') or wl_track.get('id')
logger.info(f"[Wishlist] Found Spotify ID from wishlist entry: {spotify_track_id}")
logger.info(f"[Wishlist] Found track ID from wishlist entry: {spotify_track_id}")
break
# Method 4: Try to construct ID from track metadata for fuzzy matching
# Method 4: Try to construct a track ID from metadata for fuzzy matching
if not spotify_track_id:
track_name = track_info.get('name') or context.get('original_search_result', {}).get('title', '')
artist_name = _get_track_artist_name(track_info) or _get_track_artist_name(context.get('original_search_result', {}))
if track_name and artist_name:
logger.warning(f"[Wishlist] No Spotify ID found, checking for fuzzy match: '{track_name}' by '{artist_name}'")
logger.warning(f"[Wishlist] No track ID found, checking for fuzzy match: '{track_name}' by '{artist_name}'")
# Get all wishlist tracks and find potential matches (search all profiles)
if not wishlist_tracks:
@ -18692,10 +18703,10 @@ def _check_and_remove_from_wishlist(context):
# Simple fuzzy matching
if (wl_name == track_name.lower() and wl_artist_name == artist_name.lower()):
spotify_track_id = wl_track.get('spotify_track_id') or wl_track.get('id')
logger.info(f"[Wishlist] Found fuzzy match - Spotify ID: {spotify_track_id}")
logger.info(f"[Wishlist] Found fuzzy match - track ID: {spotify_track_id}")
break
# If we found a Spotify track ID, remove it from wishlist
# If we found a track ID, remove it from wishlist
if spotify_track_id:
logger.info(f"[Wishlist] Attempting to remove track from wishlist: {spotify_track_id}")
removed = wishlist_service.mark_track_download_result(spotify_track_id, success=True)
@ -18704,7 +18715,7 @@ def _check_and_remove_from_wishlist(context):
else:
logger.warning(f" [Wishlist] Track not found in wishlist or already removed: {spotify_track_id}")
else:
logger.warning(" [Wishlist] No Spotify track ID found for wishlist removal check")
logger.warning(" [Wishlist] No track ID found for wishlist removal check")
except Exception as e:
logger.error(f"[Wishlist] Error in wishlist removal check: {e}")
@ -48259,7 +48270,7 @@ def import_singles_process():
title = file_info.get('title', '')
artist = file_info.get('artist', '')
manual_match = file_info.get('manual_match')
manual_match = file_info.get('manual_match') or file_info.get('spotify_override')
if manual_match is not None and not isinstance(manual_match, dict):
manual_match = None

@ -1214,6 +1214,9 @@ function importPageRenderMatchList() {
// Build rows
let matchedCount = 0;
const rows = data.matches.map((m, idx) => {
const track = m.track || m.spotify_track || {};
const trackNumber = track.track_number ?? track.trackNumber;
const displayTrackNumber = trackNumber ? trackNumber : (idx + 1);
let file = null;
let confidence = m.confidence;
let isOverride = false;
@ -1253,8 +1256,8 @@ function importPageRenderMatchList() {
<div class="import-page-match-row ${file ? 'matched' : ''}"
ondragover="importPageHandleDragOver(event)" ondragleave="this.classList.remove('drag-over')" ondrop="importPageHandleDrop(event, ${idx})"
onclick="importPageTapAssign(${idx})">
<span class="import-page-match-num">${m.spotify_track.track_number}</span>
<span class="import-page-match-track">${_esc(m.spotify_track.name)}</span>
<span class="import-page-match-num">${displayTrackNumber}</span>
<span class="import-page-match-track">${_esc(track.name || track.title || 'Unknown Track')}</span>
<span class="import-page-match-file ${file ? 'has-file' : ''}">
${file
? `<span class="import-page-match-file-name">${_esc(file.filename)}</span>
@ -1622,7 +1625,7 @@ function importPageProcessSingles() {
const f = importPageState.stagingFiles[i];
const manualMatch = importPageState.singlesManualMatches[i];
if (manualMatch) {
return { ...f, spotify_override: manualMatch };
return { ...f, manual_match: manualMatch };
}
return f;
});
@ -7608,4 +7611,3 @@ window.updateEnhanceSelectedCount = updateEnhanceSelectedCount;
window.submitEnhanceQuality = submitEnhanceQuality;
// ===== END ENHANCE QUALITY MODAL =====

Loading…
Cancel
Save