From 335c9dc977cef4496382b0f3e7daa04ba0e45328 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Tue, 17 Feb 2026 09:12:29 -0800 Subject: [PATCH] Fix MP3 metadata being wiped by intermediate save after tag clear --- web_server.py | 122 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/web_server.py b/web_server.py index 4eea3d03..f9b554e7 100644 --- a/web_server.py +++ b/web_server.py @@ -8143,6 +8143,11 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in """ Core function to enhance audio file metadata using Spotify data. Thread-safe with per-file locking to prevent concurrent metadata writes. + + Opens the file once in non-easy mode, clears all tags in memory, writes + new tags using format-specific frames/keys, embeds album art and source + IDs, then saves once. This avoids the old clearβ†’saveβ†’reopen pattern + which stripped the ID3v2 header from MP3 files, leaving them tagless. """ if not config_manager.get('metadata_enhancement.enabled', True): print("🎡 Metadata enhancement disabled in config.") @@ -8153,62 +8158,97 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in with file_lock: print(f"🎡 Enhancing metadata for: {os.path.basename(file_path)}") try: - # Wipe ALL existing tags first β€” files from Soulseek carry random metadata - # (wrong comments, encoder info, ReplayGain, old album art, random TXXX frames) - audio_raw = MutagenFile(file_path) - if audio_raw is None: + audio_file = MutagenFile(file_path) + if audio_file is None: print(f"❌ Could not load audio file with Mutagen: {file_path}") return False - if hasattr(audio_raw, 'clear_pictures'): - audio_raw.clear_pictures() + # ── Wipe ALL existing tags in memory (NO save yet) ── + # Files from Soulseek carry random metadata (wrong comments, + # encoder info, ReplayGain, old album art, random TXXX frames). + if hasattr(audio_file, 'clear_pictures'): + audio_file.clear_pictures() - if audio_raw.tags is not None: - audio_raw.tags.clear() - audio_raw.save() - - # Re-open in easy mode for writing standard tags - audio_file = MutagenFile(file_path, easy=True) - if audio_file is None: - print(f"❌ Could not reopen audio file in easy mode: {file_path}") - return False - - if audio_file.tags is None: + if audio_file.tags is not None: + audio_file.tags.clear() + else: audio_file.add_tags() metadata = _extract_spotify_metadata(context, artist, album_info) if not metadata: - print("⚠️ Could not extract Spotify metadata, preserving original tags.") + print("⚠️ Could not extract Spotify metadata, saving with cleared tags.") + audio_file.save() return True - # Use 'easy' tags for broad compatibility first - audio_file['title'] = metadata.get('title', '') - audio_file['artist'] = metadata.get('artist', '') - audio_file['albumartist'] = metadata.get('album_artist', '') - audio_file['album'] = metadata.get('album', '') - if metadata.get('date'): - audio_file['date'] = metadata['date'] - if metadata.get('genre'): - audio_file['genre'] = metadata['genre'] - + # ── Write standard tags using format-specific API ── track_num_str = f"{metadata.get('track_number', 1)}/{metadata.get('total_tracks', 1)}" - audio_file['tracknumber'] = track_num_str - if metadata.get('disc_number'): - audio_file['discnumber'] = str(metadata.get('disc_number')) + if isinstance(audio_file.tags, ID3): + # MP3: write ID3 frames directly + if metadata.get('title'): + audio_file.tags.add(TIT2(encoding=3, text=[metadata['title']])) + if metadata.get('artist'): + audio_file.tags.add(TPE1(encoding=3, text=[metadata['artist']])) + if metadata.get('album_artist'): + audio_file.tags.add(TPE2(encoding=3, text=[metadata['album_artist']])) + if metadata.get('album'): + audio_file.tags.add(TALB(encoding=3, text=[metadata['album']])) + if metadata.get('date'): + audio_file.tags.add(TDRC(encoding=3, text=[metadata['date']])) + if metadata.get('genre'): + audio_file.tags.add(TCON(encoding=3, text=[metadata['genre']])) + audio_file.tags.add(TRCK(encoding=3, text=[track_num_str])) + if metadata.get('disc_number'): + audio_file.tags.add(TPOS(encoding=3, text=[str(metadata['disc_number'])])) + elif isinstance(audio_file, (FLAC, OggVorbis)): + # FLAC / OGG Vorbis: dict-style VorbisComment tags + if metadata.get('title'): + audio_file['title'] = [metadata['title']] + if metadata.get('artist'): + audio_file['artist'] = [metadata['artist']] + if metadata.get('album_artist'): + audio_file['albumartist'] = [metadata['album_artist']] + if metadata.get('album'): + audio_file['album'] = [metadata['album']] + if metadata.get('date'): + audio_file['date'] = [metadata['date']] + if metadata.get('genre'): + audio_file['genre'] = [metadata['genre']] + audio_file['tracknumber'] = [track_num_str] + if metadata.get('disc_number'): + audio_file['discnumber'] = [str(metadata['disc_number'])] + + elif isinstance(audio_file, MP4): + # MP4 / M4A: Apple-style tag keys + if metadata.get('title'): + audio_file['\xa9nam'] = [metadata['title']] + if metadata.get('artist'): + audio_file['\xa9ART'] = [metadata['artist']] + if metadata.get('album_artist'): + audio_file['aART'] = [metadata['album_artist']] + if metadata.get('album'): + audio_file['\xa9alb'] = [metadata['album']] + if metadata.get('date'): + audio_file['\xa9day'] = [metadata['date']] + if metadata.get('genre'): + audio_file['\xa9gen'] = [metadata['genre']] + track_num = metadata.get('track_number', 1) + total_tracks = metadata.get('total_tracks', 1) + audio_file['trkn'] = [(track_num, total_tracks)] + if metadata.get('disc_number'): + audio_file['disk'] = [(metadata['disc_number'], 0)] + + # ── Embed album art on the same object ── + if config_manager.get('metadata_enhancement.embed_album_art', True): + _embed_album_art_metadata(audio_file, metadata) + + # ── Embed source IDs (Spotify, MusicBrainz, etc.) on the same object ── + _embed_source_ids(audio_file, metadata) + + # ── Single save for everything ── audio_file.save() - # Re-open in non-easy mode for album art and source ID embedding - audio_file_raw = MutagenFile(file_path) - if audio_file_raw is not None: - if config_manager.get('metadata_enhancement.embed_album_art', True): - _embed_album_art_metadata(audio_file_raw, metadata) - - _embed_source_ids(audio_file_raw, metadata) - - audio_file_raw.save() - print("βœ… Metadata enhanced successfully.") return True except Exception as e: