Downloads: additively surface each track's real file path (analysis + completed)
The download analysis already matches every track to a library row via check_track_exists / manual match, then discarded the result. Keep it: each analysis_results entry now carries matched_file_path + matched_track_id (the owned file's real location, or None). Symmetrically, a completed download task now records final_file_path (where the import landed). Purely additive, no behavior change, no new matching, zero perf cost — just stops throwing away what the pipeline already computed. This is the foundation for playlist materialization: owned + downloaded tracks both report where their real file is, so the folder can be built by name match, not source IDs.
This commit is contained in:
parent
550fca0fe5
commit
37431ea82b
2 changed files with 28 additions and 4 deletions
|
|
@ -505,13 +505,19 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
track_name = track_data.get('name', '')
|
track_name = track_data.get('name', '')
|
||||||
artists = track_data.get('artists', [])
|
artists = track_data.get('artists', [])
|
||||||
found, confidence = False, 0.0
|
found, confidence = False, 0.0
|
||||||
|
# Additive payload: the owned library track (DatabaseTrack) when this
|
||||||
|
# item is found in the library, so downstream (playlist materialization)
|
||||||
|
# knows WHERE the real file is without re-matching. None when not owned.
|
||||||
|
matched_track = None
|
||||||
|
|
||||||
# Manual library matches are authoritative unless the user explicitly
|
# Manual library matches are authoritative unless the user explicitly
|
||||||
# requested a force re-download from the normal download modal.
|
# requested a force re-download from the normal download modal.
|
||||||
_stid = track_data.get('spotify_track_id') or track_data.get('source_track_id') or track_data.get('id', '')
|
_stid = track_data.get('spotify_track_id') or track_data.get('source_track_id') or track_data.get('id', '')
|
||||||
if not ignore_manual_matches and _stid and _mlm.get_match_for_track(
|
_manual_match = (
|
||||||
db, batch_profile_id, track_data, default_source=batch_source
|
_mlm.get_match_for_track(db, batch_profile_id, track_data, default_source=batch_source)
|
||||||
):
|
if (not ignore_manual_matches and _stid) else None
|
||||||
|
)
|
||||||
|
if _manual_match:
|
||||||
logger.info(f"[Manual Match] '{track_name}' already matched in library — skipping download")
|
logger.info(f"[Manual Match] '{track_name}' already matched in library — skipping download")
|
||||||
try:
|
try:
|
||||||
deps.check_and_remove_track_from_wishlist_by_metadata(track_data)
|
deps.check_and_remove_track_from_wishlist_by_metadata(track_data)
|
||||||
|
|
@ -523,6 +529,8 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
'found': True,
|
'found': True,
|
||||||
'confidence': 1.0,
|
'confidence': 1.0,
|
||||||
'match_reason': 'manual_library_match',
|
'match_reason': 'manual_library_match',
|
||||||
|
'matched_file_path': _manual_match.get('library_file_path'),
|
||||||
|
'matched_track_id': _manual_match.get('library_track_id'),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -568,8 +576,10 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
# Direct title match (try both raw and normalized)
|
# Direct title match (try both raw and normalized)
|
||||||
if track_name_lower in album_tracks_map:
|
if track_name_lower in album_tracks_map:
|
||||||
found, confidence = True, 1.0
|
found, confidence = True, 1.0
|
||||||
|
matched_track = album_tracks_map[track_name_lower]
|
||||||
elif _normalized_source_title and _normalized_source_title in album_tracks_map:
|
elif _normalized_source_title and _normalized_source_title in album_tracks_map:
|
||||||
found, confidence = True, 1.0
|
found, confidence = True, 1.0
|
||||||
|
matched_track = album_tracks_map[_normalized_source_title]
|
||||||
else:
|
else:
|
||||||
# Fuzzy match against album tracks using string similarity.
|
# Fuzzy match against album tracks using string similarity.
|
||||||
# Compare BOTH the raw and normalized source titles —
|
# Compare BOTH the raw and normalized source titles —
|
||||||
|
|
@ -577,14 +587,17 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
# matching when the album doesn't imply version
|
# matching when the album doesn't imply version
|
||||||
# context (helper returns the input unchanged).
|
# context (helper returns the input unchanged).
|
||||||
best_sim = 0.0
|
best_sim = 0.0
|
||||||
|
best_track = None
|
||||||
for db_title_lower, _db_track in album_tracks_map.items():
|
for db_title_lower, _db_track in album_tracks_map.items():
|
||||||
sim_raw = db._string_similarity(track_name_lower, db_title_lower)
|
sim_raw = db._string_similarity(track_name_lower, db_title_lower)
|
||||||
sim_norm = db._string_similarity(_normalized_source_title, db_title_lower) if _normalized_source_title else 0.0
|
sim_norm = db._string_similarity(_normalized_source_title, db_title_lower) if _normalized_source_title else 0.0
|
||||||
sim = max(sim_raw, sim_norm)
|
sim = max(sim_raw, sim_norm)
|
||||||
if sim > best_sim:
|
if sim > best_sim:
|
||||||
best_sim = sim
|
best_sim = sim
|
||||||
|
best_track = _db_track
|
||||||
if best_sim >= 0.7:
|
if best_sim >= 0.7:
|
||||||
found, confidence = True, best_sim
|
found, confidence = True, best_sim
|
||||||
|
matched_track = best_track
|
||||||
else:
|
else:
|
||||||
# Fall back to global per-track search for this track
|
# Fall back to global per-track search for this track
|
||||||
# When allow_duplicates is on for album downloads, skip global
|
# When allow_duplicates is on for album downloads, skip global
|
||||||
|
|
@ -605,6 +618,7 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
)
|
)
|
||||||
if db_track and track_confidence >= 0.7:
|
if db_track and track_confidence >= 0.7:
|
||||||
found, confidence = True, track_confidence
|
found, confidence = True, track_confidence
|
||||||
|
matched_track = db_track
|
||||||
break
|
break
|
||||||
elif allow_duplicates and batch_is_album:
|
elif allow_duplicates and batch_is_album:
|
||||||
# Allow duplicates + album download + album not in DB yet → treat all as missing
|
# Allow duplicates + album download + album not in DB yet → treat all as missing
|
||||||
|
|
@ -624,10 +638,15 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
||||||
)
|
)
|
||||||
if db_track and track_confidence >= 0.7:
|
if db_track and track_confidence >= 0.7:
|
||||||
found, confidence = True, track_confidence
|
found, confidence = True, track_confidence
|
||||||
|
matched_track = db_track
|
||||||
break
|
break
|
||||||
|
|
||||||
analysis_results.append({
|
analysis_results.append({
|
||||||
'track_index': track_index, 'track': track_data, 'found': found, 'confidence': confidence
|
'track_index': track_index, 'track': track_data, 'found': found, 'confidence': confidence,
|
||||||
|
# Additive: real on-disk location of the owned track (None when not
|
||||||
|
# owned), so playlist materialization links the right file.
|
||||||
|
'matched_file_path': getattr(matched_track, 'file_path', None),
|
||||||
|
'matched_track_id': getattr(matched_track, 'id', None),
|
||||||
})
|
})
|
||||||
|
|
||||||
# WISHLIST REMOVAL: If track is found in database, check if it should be removed from wishlist
|
# WISHLIST REMOVAL: If track is found in database, check if it should be removed from wishlist
|
||||||
|
|
|
||||||
|
|
@ -695,6 +695,7 @@ def post_process_matched_download(context_key, context, file_path, runtime, meta
|
||||||
if task_id in download_tasks:
|
if task_id in download_tasks:
|
||||||
download_tasks[task_id]['stream_processed'] = True
|
download_tasks[task_id]['stream_processed'] = True
|
||||||
download_tasks[task_id]['status'] = 'completed'
|
download_tasks[task_id]['status'] = 'completed'
|
||||||
|
download_tasks[task_id]['final_file_path'] = context.get('_final_processed_path')
|
||||||
logger.info(f"[Playlist Folder Mode] Marked task {task_id} as completed")
|
logger.info(f"[Playlist Folder Mode] Marked task {task_id} as completed")
|
||||||
_notify_download_completed(batch_id, task_id, success=True)
|
_notify_download_completed(batch_id, task_id, success=True)
|
||||||
return
|
return
|
||||||
|
|
@ -1026,6 +1027,10 @@ def post_process_matched_download(context_key, context, file_path, runtime, meta
|
||||||
if task_id in download_tasks:
|
if task_id in download_tasks:
|
||||||
download_tasks[task_id]['stream_processed'] = True
|
download_tasks[task_id]['stream_processed'] = True
|
||||||
download_tasks[task_id]['status'] = 'completed'
|
download_tasks[task_id]['status'] = 'completed'
|
||||||
|
# Additive: record where the imported file landed so downstream
|
||||||
|
# (playlist materialization) knows the real path of a freshly
|
||||||
|
# downloaded track without re-resolving it.
|
||||||
|
download_tasks[task_id]['final_file_path'] = context.get('_final_processed_path')
|
||||||
logger.info(f"[Post-Process] Marked task {task_id} as completed")
|
logger.info(f"[Post-Process] Marked task {task_id} as completed")
|
||||||
_notify_download_completed(batch_id, task_id, success=True)
|
_notify_download_completed(batch_id, task_id, success=True)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue