Improve album grouping and normalize suffixes
Expand track title normalization and refine album grouping logic. - core/acoustid_verification.py: Broadened the parenthetical-suffix regex to strip year-based remasters and additional variants (e.g. "2025 Remaster", "single edit", "album edit") while still removing common extras like (Live), (Deluxe), (Radio Edit), and featuring tags. - web_server.py: Restrict the smart album grouping to only run for singles/auto-detected albums; explicit album downloads now preserve the original Spotify album name to avoid mangling names (e.g. reworked/remastered vs deluxe). Added explicit logging for both smart grouping and skipped grouping paths. The verification post-processing worker now checks an is_album_download flag in context and skips re-grouping when true, with fallback logging on errors. These changes prevent unintended renaming of explicit album downloads and improve normalization of common title suffixes.
This commit is contained in:
parent
4bff57cb70
commit
a0828c7aad
2 changed files with 30 additions and 23 deletions
|
|
@ -38,8 +38,8 @@ def _normalize(text: str) -> str:
|
||||||
if not text:
|
if not text:
|
||||||
return ""
|
return ""
|
||||||
s = text.lower().strip()
|
s = text.lower().strip()
|
||||||
# Remove common parenthetical suffixes like (Live), (Remastered), (Radio Edit)
|
# Remove common parenthetical suffixes like (Live), (Remastered), (2025 Remaster), (Radio Edit)
|
||||||
s = re.sub(r'\s*\((?:live|remaster(?:ed)?|deluxe|bonus|radio\s*edit|single\s*version|visualize.*?)\)', '', s, flags=re.IGNORECASE)
|
s = re.sub(r'\s*\((?:live|(?:\d{4}\s*)?remaster(?:ed)?(?:\s*\d{4})?|deluxe|bonus|radio\s*edit|single\s*edit|album\s*edit|single\s*version|visualize.*?)\)', '', s, flags=re.IGNORECASE)
|
||||||
# Remove featuring info: "(feat. ...)", "(ft. ...)", "(featuring ...)"
|
# Remove featuring info: "(feat. ...)", "(ft. ...)", "(featuring ...)"
|
||||||
s = re.sub(r'\s*\((?:feat\.?|ft\.?|featuring)\s+[^)]*\)', '', s, flags=re.IGNORECASE)
|
s = re.sub(r'\s*\((?:feat\.?|ft\.?|featuring)\s+[^)]*\)', '', s, flags=re.IGNORECASE)
|
||||||
# Remove trailing featuring info: "feat. ...", "ft. ...", "featuring ..."
|
# Remove trailing featuring info: "feat. ...", "ft. ...", "featuring ..."
|
||||||
|
|
|
||||||
|
|
@ -10410,9 +10410,11 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
print("🎵 Single track download - attempting album detection")
|
print("🎵 Single track download - attempting album detection")
|
||||||
album_info = _detect_album_info_web(context, spotify_artist)
|
album_info = _detect_album_info_web(context, spotify_artist)
|
||||||
|
|
||||||
# --- CRITICAL FIX: Add GUI album grouping resolution ---
|
# --- Album grouping resolution ---
|
||||||
# This ensures consistent album naming across tracks like the GUI
|
# Only run smart grouping for singles/auto-detected albums.
|
||||||
if album_info and album_info['is_album']:
|
# Explicit album downloads already have the correct Spotify album name —
|
||||||
|
# re-grouping would mangle names like "(Reworked and Remastered)" into "(Deluxe Edition)".
|
||||||
|
if album_info and album_info['is_album'] and not is_album_download:
|
||||||
print(f"\n🎯 SMART ALBUM GROUPING for track: '{album_info.get('clean_track_name', 'Unknown')}'")
|
print(f"\n🎯 SMART ALBUM GROUPING for track: '{album_info.get('clean_track_name', 'Unknown')}'")
|
||||||
print(f" Original album: '{album_info.get('album_name', 'None')}'")
|
print(f" Original album: '{album_info.get('album_name', 'None')}'")
|
||||||
|
|
||||||
|
|
@ -10427,6 +10429,9 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
|
|
||||||
print(f" Final album name: '{consistent_album_name}'")
|
print(f" Final album name: '{consistent_album_name}'")
|
||||||
print(f"🔗 ✅ Album grouping complete!\n")
|
print(f"🔗 ✅ Album grouping complete!\n")
|
||||||
|
elif album_info and album_info['is_album'] and is_album_download:
|
||||||
|
print(f"\n🎯 EXPLICIT ALBUM DOWNLOAD - preserving Spotify album name: '{album_info.get('album_name', 'None')}'")
|
||||||
|
print(f" Skipping smart grouping (not needed for explicit album downloads)\n")
|
||||||
|
|
||||||
# 1. Get transfer path and create artist directory
|
# 1. Get transfer path and create artist directory
|
||||||
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
|
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
|
||||||
|
|
@ -14997,21 +15002,23 @@ def _run_post_processing_worker(task_id, batch_id):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply album grouping for consistency with stream processor path.
|
# Apply album grouping for consistency with stream processor path.
|
||||||
# Without this, the verification worker could write a different album
|
# Only for singles/auto-detected — explicit album downloads already
|
||||||
# name than the stream processor (e.g. raw API name vs resolved name),
|
# have the correct Spotify name and re-grouping would mangle it.
|
||||||
# causing media servers to split tracks into separate albums.
|
if not context.get("is_album_download", False):
|
||||||
try:
|
try:
|
||||||
raw_album_ctx = original_search.get('album')
|
raw_album_ctx = original_search.get('album')
|
||||||
if isinstance(raw_album_ctx, str):
|
if isinstance(raw_album_ctx, str):
|
||||||
original_album_ctx = raw_album_ctx
|
original_album_ctx = raw_album_ctx
|
||||||
elif isinstance(raw_album_ctx, dict) and 'name' in raw_album_ctx:
|
elif isinstance(raw_album_ctx, dict) and 'name' in raw_album_ctx:
|
||||||
original_album_ctx = raw_album_ctx['name']
|
original_album_ctx = raw_album_ctx['name']
|
||||||
else:
|
else:
|
||||||
original_album_ctx = None
|
original_album_ctx = None
|
||||||
consistent_album_name = _resolve_album_group(spotify_artist, album_info, original_album_ctx)
|
consistent_album_name = _resolve_album_group(spotify_artist, album_info, original_album_ctx)
|
||||||
album_info['album_name'] = consistent_album_name
|
album_info['album_name'] = consistent_album_name
|
||||||
except Exception as group_err:
|
except Exception as group_err:
|
||||||
print(f"⚠️ [Verification] Album grouping failed, using raw name: {group_err}")
|
print(f"⚠️ [Verification] Album grouping failed, using raw name: {group_err}")
|
||||||
|
else:
|
||||||
|
print(f"🎯 [Verification] Explicit album download - preserving Spotify album name: '{album_info['album_name']}'")
|
||||||
|
|
||||||
print(f"🎯 [Verification] Created proper album_info - track_number: {track_number}, album: {album_info['album_name']}")
|
print(f"🎯 [Verification] Created proper album_info - track_number: {track_number}, album: {album_info['album_name']}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue