From a0828c7aadc5077f83fff6593d3debd5e99ad101 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Wed, 25 Feb 2026 15:39:01 -0800 Subject: [PATCH] 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. --- core/acoustid_verification.py | 4 +-- web_server.py | 49 ++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/core/acoustid_verification.py b/core/acoustid_verification.py index ce0f7925..fff83dfd 100644 --- a/core/acoustid_verification.py +++ b/core/acoustid_verification.py @@ -38,8 +38,8 @@ def _normalize(text: str) -> str: if not text: return "" s = text.lower().strip() - # Remove common parenthetical suffixes like (Live), (Remastered), (Radio Edit) - s = re.sub(r'\s*\((?:live|remaster(?:ed)?|deluxe|bonus|radio\s*edit|single\s*version|visualize.*?)\)', '', s, flags=re.IGNORECASE) + # Remove common parenthetical suffixes like (Live), (Remastered), (2025 Remaster), (Radio Edit) + 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 ...)" s = re.sub(r'\s*\((?:feat\.?|ft\.?|featuring)\s+[^)]*\)', '', s, flags=re.IGNORECASE) # Remove trailing featuring info: "feat. ...", "ft. ...", "featuring ..." diff --git a/web_server.py b/web_server.py index 5ec1d90e..1189fb00 100644 --- a/web_server.py +++ b/web_server.py @@ -10410,23 +10410,28 @@ def _post_process_matched_download(context_key, context, file_path): print("šŸŽµ Single track download - attempting album detection") album_info = _detect_album_info_web(context, spotify_artist) - # --- CRITICAL FIX: Add GUI album grouping resolution --- - # This ensures consistent album naming across tracks like the GUI - if album_info and album_info['is_album']: + # --- Album grouping resolution --- + # Only run smart grouping for singles/auto-detected albums. + # 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" Original album: '{album_info.get('album_name', 'None')}'") - + # Get original album name from context if available original_album = None if context.get("original_search_result", {}).get("album"): original_album = context["original_search_result"]["album"] - + # Use the GUI's smart album grouping algorithm consistent_album_name = _resolve_album_group(spotify_artist, album_info, original_album) album_info['album_name'] = consistent_album_name - + print(f" Final album name: '{consistent_album_name}'") 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 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. - # Without this, the verification worker could write a different album - # name than the stream processor (e.g. raw API name vs resolved name), - # causing media servers to split tracks into separate albums. - try: - raw_album_ctx = original_search.get('album') - if isinstance(raw_album_ctx, str): - original_album_ctx = raw_album_ctx - elif isinstance(raw_album_ctx, dict) and 'name' in raw_album_ctx: - original_album_ctx = raw_album_ctx['name'] - else: - original_album_ctx = None - consistent_album_name = _resolve_album_group(spotify_artist, album_info, original_album_ctx) - album_info['album_name'] = consistent_album_name - except Exception as group_err: - print(f"āš ļø [Verification] Album grouping failed, using raw name: {group_err}") + # Only for singles/auto-detected — explicit album downloads already + # have the correct Spotify name and re-grouping would mangle it. + if not context.get("is_album_download", False): + try: + raw_album_ctx = original_search.get('album') + if isinstance(raw_album_ctx, str): + original_album_ctx = raw_album_ctx + elif isinstance(raw_album_ctx, dict) and 'name' in raw_album_ctx: + original_album_ctx = raw_album_ctx['name'] + else: + original_album_ctx = None + consistent_album_name = _resolve_album_group(spotify_artist, album_info, original_album_ctx) + album_info['album_name'] = consistent_album_name + except Exception as 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']}")