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
This commit is contained in:
parent
594c8c1b93
commit
d04573f397
5 changed files with 69 additions and 49 deletions
|
|
@ -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}")
|
||||
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: From original search result
|
||||
elif context.get('original_search_result', {}).get('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…
Reference in a new issue