From b7ef2606098a42e475375775caaf6ec41eb0b9bc Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Mon, 14 Jul 2025 11:29:53 -0700 Subject: [PATCH] better --- .spotify_cache | 2 +- .../__pycache__/downloads.cpython-310.pyc | Bin 214155 -> 218980 bytes .../__pycache__/downloads.cpython-312.pyc | Bin 395309 -> 402577 bytes ui/pages/downloads.py | 211 +++++++++++++++++- 4 files changed, 210 insertions(+), 3 deletions(-) diff --git a/.spotify_cache b/.spotify_cache index 88e57138..e988b969 100644 --- a/.spotify_cache +++ b/.spotify_cache @@ -1 +1 @@ -{"access_token": "BQBSqtKDkqV6UKUvbjiQwVB9gKAkFH77HtkUMg1ZL6xiqnBvWilNsahaVug2bEetLc6yJ-kQYSlihYdduklgYnWk4Yy-S985W8l6-4YeiBUTBUauA33_mEUHZm7Fsl_cU-3OMiWexQSyuEN-Mzi_dKqSQyISZSItvgMnuBpM8lMuqeANGmX_mQeYFVztUaSFTmuXOE5j1-UIiz18vHJzpCUc34cZcIJ1SUzxbLY9JJLxrX81MTMCfoynkGepJWne", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752516257, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file +{"access_token": "BQDXSOsKBrR2sR0Bf23nD1sV3aZh52IJ9vp7SV1X7ETZHjQFSDN3adTAlMKi8XKtTnpgx402dvCMt-IFyhFD5aX6rs6GmEAFlw49sFxlPZbaoHPoXMXsXZDoxLWDJE8rGGQmOce86VDqJXBqRWzlqIdhxYkHvRjNk1ouYZvwEj1Bq8k15zBxBHR89dIsyI_kSBeXWjt_yHzD8VWX7VLUeFILVn3TTeMtYRho7XB8D4hibhBnjWD2YrQPXIU10HPk", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752520100, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file diff --git a/ui/pages/__pycache__/downloads.cpython-310.pyc b/ui/pages/__pycache__/downloads.cpython-310.pyc index 84134e5a2ecfb2e2f591e04c2699c64fa24fecdb..9983b2aeba9a202b256a1104bda744eee74a9909 100644 GIT binary patch delta 14027 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 AX8-^I delta 9103 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 JL_{2#{BJ=hs!0F< delta 9470 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 resolved clean album name + self.album_artists = {} # Maps original album name -> artist for consistency + self.album_editions = {} # Maps original album name -> edition level ("standard", "deluxe") + # Initialize audio player for streaming self.audio_player = AudioPlayer(self) self.audio_player.playback_finished.connect(self.on_audio_playback_finished) @@ -6058,6 +6063,18 @@ class DownloadsPage(QWidget): # Determine if this is a single or album track album_info = self._detect_album_info(download_item, artist) + # Resolve consistent album name for grouping tracks from same album + if album_info and album_info['is_album']: + print(f"\n๐ŸŽฏ SMART ALBUM GROUPING for track: '{download_item.title}'") + print(f" Original album: '{getattr(download_item, 'album', 'None')}'") + print(f" Detected album: '{album_info.get('album_name', 'None')}'") + + consistent_album_name = self._resolve_album_group(download_item, artist, album_info) + album_info['album_name'] = consistent_album_name + + print(f" Final album name: '{consistent_album_name}'") + print(f"๐Ÿ”— โœ… Album grouping complete!\n") + if album_info and album_info['is_album']: # Album track structure: Transfer/ARTIST/ARTIST - ALBUM/TRACK# TRACK.ext print(f"๐Ÿ” Creating album folder:") @@ -6189,7 +6206,7 @@ class DownloadsPage(QWidget): for pattern in quality_patterns: cleaned = re.sub(pattern, ' ', cleaned, flags=re.IGNORECASE) - # Remove source/torrent indicators + # Remove source/torrent indicators source_patterns = [ r'\s*[\[\(]album[\]\)]\s*', r'\s*[\[\(]itunes[\]\)]\s*', @@ -6200,7 +6217,10 @@ class DownloadsPage(QWidget): r'\s*[\[\(]bonus\s*tracks?[\]\)]\s*', r'\s*[\[\(]remaster(ed)?[\]\)]\s*', r'\s*[\[\(]clean[\]\)]\s*', - r'\s*[\[\(]explicit[\]\)]\s*' + r'\s*[\[\(]explicit[\]\)]\s*', + r'\s*[\[\(]album\+itunes\+bonus\s*tracks?[\]\)]\s*', # [Album+iTunes+Bonus Tracks] + r'\s*[\[\(]itunes\+bonus[\]\)]\s*', + r'\s*[\[\(]bonus[\]\)]\s*' ] for pattern in source_patterns: cleaned = re.sub(pattern, ' ', cleaned, flags=re.IGNORECASE) @@ -6276,6 +6296,193 @@ class DownloadsPage(QWidget): print(f"๐Ÿงน Track Title Result: '{original}' -> '{cleaned}'") return cleaned + def _resolve_album_group(self, download_item, artist: Artist, album_info: dict) -> str: + """ + Smart album grouping: Start with standard, upgrade to deluxe if ANY track is deluxe. + This ensures all tracks from the same album get the same folder name. + """ + try: + # Get the original album name from the download item (if it has one) + original_album = getattr(download_item, 'album', None) + detected_album = album_info.get('album_name', '') + + # Extract base album name (without edition indicators) + if album_info.get('spotify_track'): + # Use Spotify album name for base + base_album = self._get_base_album_name(detected_album) + elif original_album: + # Clean the original Soulseek album name + cleaned_original = self._clean_album_title(original_album, artist.name) + base_album = self._get_base_album_name(cleaned_original) + else: + base_album = self._get_base_album_name(detected_album) + + # Normalize the base name (handle case variations, etc.) + base_album = self._normalize_base_album_name(base_album, artist.name) + + # Create a key for this album group (artist + base album) + album_key = f"{artist.name}::{base_album}" + + print(f"๐Ÿ” Album grouping - Key: '{album_key}', Detected: '{detected_album}'") + + # Check if this track indicates a deluxe edition + is_deluxe_track = False + if album_info.get('spotify_track'): + is_deluxe_track = self._detect_deluxe_edition(detected_album) + elif original_album: + is_deluxe_track = self._detect_deluxe_edition(original_album) + + # Get current edition level for this album group (default to standard) + current_edition = self.album_editions.get(album_key, "standard") + + # SMART ALGORITHM: Upgrade to deluxe if this track is deluxe + if is_deluxe_track and current_edition == "standard": + print(f"๐ŸŽฏ UPGRADE: Album '{base_album}' upgraded from standard to deluxe!") + self.album_editions[album_key] = "deluxe" + current_edition = "deluxe" + + # Build final album name based on edition level + if current_edition == "deluxe": + final_album_name = f"{base_album} (Deluxe Edition)" + else: + final_album_name = base_album + + # Store the resolution + self.album_groups[album_key] = final_album_name + self.album_artists[album_key] = artist.name + + print(f"๐Ÿ”— Album resolution: '{detected_album}' -> '{final_album_name}' (edition: {current_edition})") + + return final_album_name + + except Exception as e: + print(f"โŒ Error resolving album group: {e}") + return album_info.get('album_name', download_item.title) + + def _normalize_base_album_name(self, base_album: str, artist_name: str) -> str: + """ + Normalize the base album name to handle case variations and known corrections. + """ + import re + + # Apply known album corrections for consistent naming + normalized_lower = base_album.lower().strip() + + known_corrections = { + 'good kid maad city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d. city': 'good kid, m.A.A.d city', + 'good kid m a a d city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d city': 'good kid, m.A.A.d city' + } + + for key, correction in known_corrections.items(): + if key == normalized_lower: + print(f"๐Ÿ“ Base album correction: '{base_album}' -> '{correction}'") + return correction + + # If no specific correction, return cleaned version + return base_album.strip() + + def _normalize_spotify_album_variants(self, album_name: str, artist_name: str) -> str: + """ + Normalize different Spotify album variants to a consistent name. + E.g., 'good kid, m.A.A.d city' and 'good kid, m.A.A.d city (Deluxe)' + should both resolve to 'good kid, m.A.A.d city (Deluxe Edition)' + """ + import re + + normalized = album_name.strip() + + # Convert various deluxe indicators to standard format + deluxe_patterns = [ + (r'\s*\(deluxe\)\s*$', ' (Deluxe Edition)'), + (r'\s*\[deluxe\]\s*$', ' (Deluxe Edition)'), + (r'\s*deluxe\s*$', ' (Deluxe Edition)'), + (r'\s*\(deluxe\s+edition\)\s*$', ' (Deluxe Edition)'), + (r'\s*\[deluxe\s+edition\]\s*$', ' (Deluxe Edition)') + ] + + for pattern, replacement in deluxe_patterns: + if re.search(pattern, normalized, re.IGNORECASE): + normalized = re.sub(pattern, replacement, normalized, flags=re.IGNORECASE) + break + + # Normalize case inconsistencies for known albums + # This handles cases like "good kid, m.A.A.d city" vs "Good Kid M.A.A.D City" + known_album_corrections = { + 'good kid maad city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d. city': 'good kid, m.A.A.d city', + 'good kid m.a.a.d city': 'good kid, m.A.A.d city', + 'good kid m a a d city': 'good kid, m.A.A.d city' + } + + normalized_lower = normalized.lower() + for key, correction in known_album_corrections.items(): + if key in normalized_lower: + # Preserve any edition suffix + suffix = '' + if '(deluxe edition)' in normalized_lower: + suffix = ' (Deluxe Edition)' + elif '(deluxe)' in normalized_lower: + suffix = ' (Deluxe Edition)' + + normalized = correction + suffix + break + + print(f"๐Ÿ“€ Album variant normalization: '{album_name}' -> '{normalized}'") + return normalized + + def _detect_deluxe_edition(self, album_name: str) -> bool: + """ + Detect if an album name indicates a deluxe/special edition. + Returns True if it's a deluxe variant, False for standard. + """ + if not album_name: + return False + + album_lower = album_name.lower() + + # Check for deluxe indicators + deluxe_indicators = [ + 'deluxe', + 'deluxe edition', + 'special edition', + 'expanded edition', + 'extended edition', + 'bonus', + 'remastered', + 'anniversary', + 'collectors edition', + 'limited edition' + ] + + for indicator in deluxe_indicators: + if indicator in album_lower: + print(f"๐ŸŽฏ Detected deluxe edition: '{album_name}' contains '{indicator}'") + return True + + return False + + def _get_base_album_name(self, album_name: str) -> str: + """ + Extract the base album name without edition indicators. + E.g., 'good kid, m.A.A.d city (Deluxe Edition)' -> 'good kid, m.A.A.d city' + """ + import re + + # Remove common edition suffixes + base_name = album_name + + # Remove edition indicators in parentheses or brackets + base_name = re.sub(r'\s*[\[\(](deluxe|special|expanded|extended|bonus|remastered|anniversary|collectors?|limited).*?[\]\)]\s*$', '', base_name, flags=re.IGNORECASE) + + # Remove standalone edition words at the end + base_name = re.sub(r'\s+(deluxe|special|expanded|extended|bonus|remastered|anniversary|collectors?|limited)\s*(edition)?\s*$', '', base_name, flags=re.IGNORECASE) + + return base_name.strip() + def _detect_album_info(self, download_item, artist: Artist) -> Optional[dict]: """Detect if track is part of an album using Spotify API as primary source""" try: