From c0bb1a4f34c90a0b836ee68b909063fca3ce0657 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:45:27 -0700 Subject: [PATCH] Save cleared tags to disk before metadata enhancement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, tags.clear() only cleared in memory — if any later step threw (metadata extraction, API calls, album art download), the file was moved with its original Soulseek source tags intact. This caused album fragmentation in media servers when some tracks had MusicBrainz IDs and others didn't. Now the cleared tags are saved to disk immediately after wiping. If enhancement succeeds, the file is saved again with full metadata (identical to before). If it fails, the file has clean empty tags instead of inconsistent junk — media servers group by folder structure which is always correct. --- web_server.py | 24 ++++++++++++++++++++++-- webui/static/helper.js | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/web_server.py b/web_server.py index 2a9e7741..65a5bae7 100644 --- a/web_server.py +++ b/web_server.py @@ -15690,14 +15690,16 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in print(f"❌ Could not load audio file with Mutagen: {file_path}") return False - # ── Wipe ALL existing tags in memory (NO save yet) ── + # ── Wipe ALL existing tags and save immediately ── # Files from Soulseek carry random metadata (wrong comments, # encoder info, ReplayGain, old album art, random TXXX frames). + # Save the cleared state FIRST so that if anything below throws, + # the file at least has clean (empty) tags instead of junk that + # causes album fragmentation in media servers. if hasattr(audio_file, 'clear_pictures'): audio_file.clear_pictures() if audio_file.tags is not None: - # Log what's being cleared for debugging if len(audio_file.tags) > 0: tag_keys = list(audio_file.tags.keys())[:15] print(f"🧹 Clearing {len(audio_file.tags)} existing tags: " @@ -15706,6 +15708,14 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in else: audio_file.add_tags() + # Persist the wipe — guarantees junk tags are gone even if later steps fail + if isinstance(audio_file.tags, ID3): + audio_file.save(v1=0, v2_version=4) + elif isinstance(audio_file, FLAC): + audio_file.save(deleteid3=True) + else: + audio_file.save() + metadata = _extract_spotify_metadata(context, artist, album_info) if not metadata: print("⚠️ Could not extract Spotify metadata, saving with cleared tags.") @@ -18999,6 +19009,16 @@ def get_version_info(): "title": "What's New in SoulSync", "subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes", "sections": [ + { + "title": "🔧 Fix Soulseek Junk Tags Surviving Post-Processing", + "description": "Tags from Soulseek source files are now wiped to disk immediately, before metadata enhancement", + "features": [ + "• Clears and saves tags before any API calls or metadata extraction", + "• If enhancement fails, file has clean empty tags instead of inconsistent junk", + "• Fixes album fragmentation in Navidrome/Jellyfin/Plex caused by partial MusicBrainz data", + "• Happy path unchanged — full metadata still written on success" + ] + }, { "title": "👁️ Watch All Unwatched Preview Modal", "description": "The Watch All Unwatched button now opens a modal showing exactly which artists will be added", diff --git a/webui/static/helper.js b/webui/static/helper.js index defe47d7..f6bd6e3b 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3403,6 +3403,7 @@ function closeHelperSearch() { const WHATS_NEW = { '2.1': [ // Newest features first + { title: 'Fix Junk Tags Surviving', desc: 'Soulseek source tags are now wiped to disk immediately — no more album fragmentation from partial metadata' }, { title: 'Watch All Preview Modal', desc: 'Watch All Unwatched now opens a modal showing which artists will be added before confirming', page: 'library', selector: '#library-watchlist-all-btn' }, { title: 'Fix Watch All Unwatched', desc: 'Watch All Unwatched now works for Deezer users — was silently skipping artists with only Deezer IDs' }, { title: 'Fix Path Mismatch Fixes', desc: 'Library Maintenance path fixes now use fresh config and show error reasons in the toast' },