From aafc0ca49fd9d33fe3536518501e7b38978df963 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Thu, 7 Aug 2025 10:35:56 -0700 Subject: [PATCH] consistent matching between sync and artists. --- .../matching_engine.cpython-310.pyc | Bin 0 -> 17148 bytes .../matching_engine.cpython-312.pyc | Bin 25956 -> 27777 bytes core/matching_engine.py | 45 ++- .../music_database.cpython-310.pyc | Bin 15246 -> 36110 bytes .../music_database.cpython-312.pyc | Bin 51439 -> 58442 bytes database/music_database.py | 282 ++++++++++++++---- ui/pages/__pycache__/sync.cpython-312.pyc | Bin 233645 -> 234004 bytes ui/pages/sync.py | 46 ++- 8 files changed, 301 insertions(+), 72 deletions(-) create mode 100644 core/__pycache__/matching_engine.cpython-310.pyc diff --git a/core/__pycache__/matching_engine.cpython-310.pyc b/core/__pycache__/matching_engine.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25e6e581350b697f422aaa55176a2b38887af304 GIT binary patch literal 17148 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 diff --git a/core/matching_engine.py b/core/matching_engine.py index 0b1cbc42..ac44bddf 100644 --- a/core/matching_engine.py +++ b/core/matching_engine.py @@ -336,21 +336,44 @@ class MusicMatchingEngine: queries.append(f"{artist} {cleaned_track}".strip()) print(f"🎯 PRIORITY 1: Album-cleaned query: '{artist} {cleaned_track}'") - # PRIORITY 2: Try with just the first part before any dash/parentheses - simple_patterns = [ - r'^([^-\(]+)', # Everything before first dash or parenthesis - r'^([^-]+)', # Everything before first dash only - ] + # PRIORITY 2: Try simplified versions, but preserve important version info + # Only remove content that's likely to be album names or noise, not version info - for pattern in simple_patterns: - match = re.search(pattern, original_title.strip()) - if match: - simple_title = match.group(1).strip() - if simple_title and len(simple_title) >= 3: # Avoid too-short titles + # Pattern 1: Remove content after " - " (likely album names) + dash_pattern = r'^([^-]+?)(?:\s*-\s*.+)?$' + match = re.search(dash_pattern, original_title.strip()) + if match: + dash_title = match.group(1).strip() + if dash_title and len(dash_title) >= 3 and dash_title != original_title: + dash_clean = self.clean_title(dash_title) + if dash_clean and dash_clean not in [self.clean_title(q.split(' ', 1)[1]) for q in queries if ' ' in q]: + queries.append(f"{artist} {dash_clean}".strip()) + print(f"🎯 PRIORITY 2: Dash-cleaned query: '{artist} {dash_clean}'") + + # Pattern 2: Only remove parentheses that contain noise (feat, explicit, etc), not version info + # Check if parentheses contain version-related keywords before removing + paren_pattern = r'^(.+?)\s*\(([^)]+)\)(.*)$' + paren_match = re.search(paren_pattern, original_title) + if paren_match: + before_paren = paren_match.group(1).strip() + paren_content = paren_match.group(2).strip().lower() + after_paren = paren_match.group(3).strip() + + # Define what we consider "noise" vs "important version info" + noise_keywords = ['feat', 'ft', 'featuring', 'explicit', 'clean', 'radio edit', 'radio version'] + version_keywords = ['extended', 'live', 'acoustic', 'remix', 'remaster', 'demo', 'instrumental', 'version', 'edit', 'mix'] + + # Only remove parentheses if they contain noise, not version info + is_noise = any(keyword in paren_content for keyword in noise_keywords) + is_version = any(keyword in paren_content for keyword in version_keywords) + + if is_noise and not is_version and before_paren: + simple_title = (before_paren + ' ' + after_paren).strip() + if simple_title and len(simple_title) >= 3: simple_clean = self.clean_title(simple_title) if simple_clean and simple_clean not in [self.clean_title(q.split(' ', 1)[1]) for q in queries if ' ' in q]: queries.append(f"{artist} {simple_clean}".strip()) - print(f"🎯 PRIORITY 2: Simple-cleaned query: '{artist} {simple_clean}'") + print(f"🎯 PRIORITY 2: Noise-removed query: '{artist} {simple_clean}'") # PRIORITY 3: Original query (ONLY if no album was detected or if it's different) original_track_clean = self.clean_title(original_title) diff --git a/database/__pycache__/music_database.cpython-310.pyc b/database/__pycache__/music_database.cpython-310.pyc index f4fdcf3a712b3091dcf3d8f20a20497e803c8fa5..4d19f9930516c66e1823c58b82bdfc196faa1d2f 100644 GIT binary patch literal 36110 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%0AS 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 X=kQyE?x&de=<0u?yRykqt(f~igh^Hp delta 2650 zcma)7U2Gf25#HT99*@T#DHKWZSE6Xy^2I_V*|O!ha&1et6(Ei*Tdke2O&OYda%A&I zU7o9okfJscl(ce;DvRbJMgS>63j_u7LKFcKq(Kp+MIVZyeF{_{Nc_^KFMcTckf3op zv$SP5$U~3Vk8`uLJKxOg?c8{k-Ix#6fXnBni zPPkUqE1tZkV&sjAH}9>Od9&in`_!CXwkrO-U&)?wpc2dnm28wlm9~5vB~BanuF!nD z3tHyB3gm{m` zsGF1!hvX?IdB$m8<9+fS<3qYv{>Yn38IIr4o%9S#kQv5%`R-LcpW%C^iEYb}b)7yU zZ(7|JCyS(@FOp4<{Ed~PhWyk@2KgY zbtik@cXoc(eKsp<^VykV$*qZn$wqwY{E3MtPGsXVHQ~%&t`@F33-#>L?Ab}%6psK6 zc{MQNeUpkolz$z_hL0jaJR=qhQ;PGo7Vn29gS<3k0Mo zf{HBNB5)#97@SUn1e94LFXM9Y~>c5?~!h9ZP923wSa8 zjyxA1FkS~uV^G?`jQmCXL~I(8?CJ|slq?yPG$O8N)8K~8CQ?4{lJT4YguL=(;->s0 zv71fW(n$70WCxPx0vou0F92hv{8=)pn_q@J@>j{@hCmm)z{S8g-PABVSKst-9cyxt zaM%-k8ViTPCwxE+AIy5WXHmOJUQ=~@9&DDh=d^h`Pi(KenX1u*Jf2R-N;*X+<%?-M z^(H#Upusscsii_RSvw6e`Iq$ZfCa~=aS4tc*&;HVIhGn&q*uIRfP1*HN>DyYR_Kzs zXo^F!k?Eu7JX?A@?r{t$ygY*wF++CyZ%Z*S}&h?q{jqbGkpnFyh zW;3j@uGHo1S^0jpPyQoYr03!}k@b-WSQ|7z<;Qlc zJ)j%^HoO5%|I+jr7{tNcsi+?;)rq+ULgW11Xf|@Yd{c`$hwo1G$oFyw$46c7rU@D| zLAGd>at&l3%szfs5Aqh})=S=t1j?!1lOR}41?yXwlf_+sqEE@8-e`;W)ACI3K`8X| zy`w{aL)LiQTe%KF#&wpYj2h~P|8W|j=I6|7es1_eta-clmy|vsU)i&N|0JvvXA#aJ zOe1{duwDRN1nMO&g3vfDkL9w>WM2>KcoL1D0yr)5fOZ?Canc_1J%jopfIRW7sC>)r zq^$WnJHeV+e|81*Hju832`| zKMqIeO7qt61x8Eq(GmMlL+d+|wS!;lXeVq$7RPJOI>|p0o4tJuXsL%I?QQ5 z-I8}ko@~ChvcTA9OvcwTY>CK`wVsj7V51(}5X$~EytOzlqhup20oWn&_>Q6|*Vl6N zG5OY7j~@fy_SD^?TbCcLb^Ess;nc5{U05amvu3josC@W^?=4TlO}!WQq2h=5*Cisj zT@)b(uz8V^vbcmZ*tCR7 z+GPU!i7WW7A*cgbMd=3!IfQwHMFa!k1^HV&zT5}mei-#EfJt=P&e~1WXP$}~W;_&* z^@mfjp;$IHnC?pE1X^#03-?+@VYUissg@iScq^5-SqVGpy$5Bisk&Y;C2p&?nl3b= zV=J@>3e|_Yv31sGtC!1;TdUshNB5jyH@Xh%6${8RR!$!MX=eGK;G&K(nHt;|RJ;UY z3C90CI~3xec8RcDvU#^37F8#_+YNhK-SGe6fvpd282NksA^G$5_I+O<4{U=v@Bk)v z#0|7~5x`EWS_?ez+pJcXx7TMLZk0}zN)-sR+lml5wvGwLB5pvbI_2Bf+XnmL)lz*Z dK+|xe)ZR>89-y(_11cq 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 diff --git a/database/music_database.py b/database/music_database.py index fe07cd0a..bec4eb70 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -458,67 +458,216 @@ class MusicDatabase: return [] def search_tracks(self, title: str = "", artist: str = "", limit: int = 50) -> List[DatabaseTrack]: - """Search tracks by title and/or artist name with fuzzy matching""" + """Search tracks by title and/or artist name with Unicode-aware fuzzy matching""" try: + if not title and not artist: + return [] + conn = self._get_connection() cursor = conn.cursor() - # Build dynamic query based on provided parameters - where_conditions = [] - params = [] - - if title: - where_conditions.append("tracks.title LIKE ?") - params.append(f"%{title}%") + # STRATEGY 1: Try basic SQL LIKE search first (fastest) + basic_results = self._search_tracks_basic(cursor, title, artist, limit) - if artist: - where_conditions.append("artists.name LIKE ?") - params.append(f"%{artist}%") + if basic_results: + logger.debug(f"πŸ” Basic search found {len(basic_results)} results") + return basic_results - if not where_conditions: - # If no search criteria, return empty list - return [] - - where_clause = " AND ".join(where_conditions) - params.append(limit) - - cursor.execute(f""" - SELECT tracks.*, artists.name as artist_name, albums.title as album_title - FROM tracks - JOIN artists ON tracks.artist_id = artists.id - JOIN albums ON tracks.album_id = albums.id - WHERE {where_clause} - ORDER BY tracks.title, artists.name - LIMIT ? - """, params) + # STRATEGY 2: If basic search fails and we have Unicode support, try normalized search + try: + from unidecode import unidecode + unicode_support = True + except ImportError: + unicode_support = False - rows = cursor.fetchall() + if unicode_support: + normalized_results = self._search_tracks_unicode_fallback(cursor, title, artist, limit) + if normalized_results: + logger.debug(f"πŸ” Unicode fallback search found {len(normalized_results)} results") + return normalized_results - tracks = [] - for row in rows: - track = DatabaseTrack( - id=row['id'], - album_id=row['album_id'], - artist_id=row['artist_id'], - title=row['title'], - track_number=row['track_number'], - duration=row['duration'], - file_path=row['file_path'], - bitrate=row['bitrate'], - created_at=datetime.fromisoformat(row['created_at']) if row['created_at'] else None, - updated_at=datetime.fromisoformat(row['updated_at']) if row['updated_at'] else None - ) - # Add artist and album info for compatibility with Plex responses - track.artist_name = row['artist_name'] - track.album_title = row['album_title'] - tracks.append(track) + # STRATEGY 3: Last resort - broader fuzzy search with Python filtering + fuzzy_results = self._search_tracks_fuzzy_fallback(cursor, title, artist, limit) + if fuzzy_results: + logger.debug(f"πŸ” Fuzzy fallback search found {len(fuzzy_results)} results") - return tracks + return fuzzy_results except Exception as e: logger.error(f"Error searching tracks with title='{title}', artist='{artist}': {e}") return [] + def _search_tracks_basic(self, cursor, title: str, artist: str, limit: int) -> List[DatabaseTrack]: + """Basic SQL LIKE search - fastest method""" + where_conditions = [] + params = [] + + if title: + where_conditions.append("tracks.title LIKE ?") + params.append(f"%{title}%") + + if artist: + where_conditions.append("artists.name LIKE ?") + params.append(f"%{artist}%") + + if not where_conditions: + return [] + + where_clause = " AND ".join(where_conditions) + params.append(limit) + + cursor.execute(f""" + SELECT tracks.*, artists.name as artist_name, albums.title as album_title + FROM tracks + JOIN artists ON tracks.artist_id = artists.id + JOIN albums ON tracks.album_id = albums.id + WHERE {where_clause} + ORDER BY tracks.title, artists.name + LIMIT ? + """, params) + + return self._rows_to_tracks(cursor.fetchall()) + + def _search_tracks_unicode_fallback(self, cursor, title: str, artist: str, limit: int) -> List[DatabaseTrack]: + """Unicode-aware fallback search - tries normalized versions""" + from unidecode import unidecode + + # Normalize search terms + title_norm = unidecode(title).lower() if title else "" + artist_norm = unidecode(artist).lower() if artist else "" + + # Try searching with normalized versions + where_conditions = [] + params = [] + + if title: + where_conditions.append("LOWER(tracks.title) LIKE ?") + params.append(f"%{title_norm}%") + + if artist: + where_conditions.append("LOWER(artists.name) LIKE ?") + params.append(f"%{artist_norm}%") + + if not where_conditions: + return [] + + where_clause = " AND ".join(where_conditions) + params.append(limit * 2) # Get more results for filtering + + cursor.execute(f""" + SELECT tracks.*, artists.name as artist_name, albums.title as album_title + FROM tracks + JOIN artists ON tracks.artist_id = artists.id + JOIN albums ON tracks.album_id = albums.id + WHERE {where_clause} + ORDER BY tracks.title, artists.name + LIMIT ? + """, params) + + rows = cursor.fetchall() + + # Filter results with proper Unicode normalization + filtered_tracks = [] + for row in rows: + db_title_norm = unidecode(row['title'].lower()) if row['title'] else "" + db_artist_norm = unidecode(row['artist_name'].lower()) if row['artist_name'] else "" + + title_matches = not title or title_norm in db_title_norm + artist_matches = not artist or artist_norm in db_artist_norm + + if title_matches and artist_matches: + filtered_tracks.append(row) + if len(filtered_tracks) >= limit: + break + + return self._rows_to_tracks(filtered_tracks) + + def _search_tracks_fuzzy_fallback(self, cursor, title: str, artist: str, limit: int) -> List[DatabaseTrack]: + """Broadest fuzzy search - partial word matching""" + # Get broader results by searching for individual words + search_terms = [] + if title: + # Split title into words and search for each + title_words = [w.strip() for w in title.lower().split() if len(w.strip()) >= 3] + search_terms.extend(title_words) + + if artist: + # Split artist into words and search for each + artist_words = [w.strip() for w in artist.lower().split() if len(w.strip()) >= 3] + search_terms.extend(artist_words) + + if not search_terms: + return [] + + # Build a query that searches for any of the words + like_conditions = [] + params = [] + + for term in search_terms[:5]: # Limit to 5 terms to avoid too broad search + like_conditions.append("(LOWER(tracks.title) LIKE ? OR LOWER(artists.name) LIKE ?)") + params.extend([f"%{term}%", f"%{term}%"]) + + if not like_conditions: + return [] + + where_clause = " OR ".join(like_conditions) + params.append(limit * 3) # Get more results for scoring + + cursor.execute(f""" + SELECT tracks.*, artists.name as artist_name, albums.title as album_title + FROM tracks + JOIN artists ON tracks.artist_id = artists.id + JOIN albums ON tracks.album_id = albums.id + WHERE {where_clause} + ORDER BY tracks.title, artists.name + LIMIT ? + """, params) + + rows = cursor.fetchall() + + # Score and filter results + scored_results = [] + for row in rows: + # Simple scoring based on how many search terms match + score = 0 + db_title_lower = row['title'].lower() + db_artist_lower = row['artist_name'].lower() + + for term in search_terms: + if term in db_title_lower or term in db_artist_lower: + score += 1 + + if score > 0: + scored_results.append((score, row)) + + # Sort by score and take top results + scored_results.sort(key=lambda x: x[0], reverse=True) + top_rows = [row for score, row in scored_results[:limit]] + + return self._rows_to_tracks(top_rows) + + def _rows_to_tracks(self, rows) -> List[DatabaseTrack]: + """Convert database rows to DatabaseTrack objects""" + tracks = [] + for row in rows: + track = DatabaseTrack( + id=row['id'], + album_id=row['album_id'], + artist_id=row['artist_id'], + title=row['title'], + track_number=row['track_number'], + duration=row['duration'], + file_path=row['file_path'], + bitrate=row['bitrate'], + created_at=datetime.fromisoformat(row['created_at']) if row['created_at'] else None, + updated_at=datetime.fromisoformat(row['updated_at']) if row['updated_at'] else None + ) + # Add artist and album info for compatibility with Plex responses + track.artist_name = row['artist_name'] + track.album_title = row['album_title'] + tracks.append(track) + return tracks + def search_albums(self, title: str = "", artist: str = "", limit: int = 50) -> List[DatabaseAlbum]: """Search albums by title and/or artist name with fuzzy matching""" try: @@ -1007,12 +1156,43 @@ class MusicDatabase: return unique_variations + def _normalize_for_comparison(self, text: str) -> str: + """Normalize text for comparison with Unicode accent handling""" + if not text: + return "" + + # Try to use unidecode for accent normalization, fallback to basic if not available + try: + from unidecode import unidecode + # Convert accents: Γ©β†’e, Γ±β†’n, ΓΌβ†’u, etc. + normalized = unidecode(text) + except ImportError: + # Fallback: basic normalization without accent handling + normalized = text + logger.warning("unidecode not available, accent matching may be limited") + + # Convert to lowercase and strip + return normalized.lower().strip() + def _calculate_track_confidence(self, search_title: str, search_artist: str, db_track: DatabaseTrack) -> float: - """Calculate confidence score for track match with enhanced cleaning""" + """Calculate confidence score for track match with enhanced cleaning and Unicode normalization""" try: - # Direct similarity - title_similarity = self._string_similarity(search_title.lower(), db_track.title.lower()) - artist_similarity = self._string_similarity(search_artist.lower(), db_track.artist_name.lower()) + # Unicode-aware normalization for accent matching (Γ©β†’e, Γ±β†’n, etc.) + search_title_norm = self._normalize_for_comparison(search_title) + search_artist_norm = self._normalize_for_comparison(search_artist) + db_title_norm = self._normalize_for_comparison(db_track.title) + db_artist_norm = self._normalize_for_comparison(db_track.artist_name) + + # Debug logging for Unicode normalization + if search_title != search_title_norm or search_artist != search_artist_norm or \ + db_track.title != db_title_norm or db_track.artist_name != db_artist_norm: + logger.debug(f"πŸ”€ Unicode normalization:") + logger.debug(f" Search: '{search_title}' β†’ '{search_title_norm}' | '{search_artist}' β†’ '{search_artist_norm}'") + logger.debug(f" Database: '{db_track.title}' β†’ '{db_title_norm}' | '{db_track.artist_name}' β†’ '{db_artist_norm}'") + + # Direct similarity with Unicode normalization + title_similarity = self._string_similarity(search_title_norm, db_title_norm) + artist_similarity = self._string_similarity(search_artist_norm, db_artist_norm) # Also try with cleaned versions (removing parentheses, brackets, etc.) clean_search_title = self._clean_track_title_for_comparison(search_title) diff --git a/ui/pages/__pycache__/sync.cpython-312.pyc b/ui/pages/__pycache__/sync.cpython-312.pyc index bedc18710aef2149f386ba35b85635a6e812cbcc..c50db33c701be50c81d6ac2efeeb6a3d4aaf3ee4 100644 GIT binary patch delta 24744 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 Q9KA*Ig)(71v5u(yKaT4r`~Uy| delta 24613 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 '{cleaned_name}'") + print(f"🧹 Intelligent track cleaning: '{track_name}' -> '{cleaned_name}'") return cleaned_name