Fix MP3 metadata being wiped by intermediate save after tag clear
This commit is contained in:
parent
71f362b79a
commit
335c9dc977
1 changed files with 81 additions and 41 deletions
122
web_server.py
122
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.
|
Core function to enhance audio file metadata using Spotify data.
|
||||||
Thread-safe with per-file locking to prevent concurrent metadata writes.
|
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):
|
if not config_manager.get('metadata_enhancement.enabled', True):
|
||||||
print("🎵 Metadata enhancement disabled in config.")
|
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:
|
with file_lock:
|
||||||
print(f"🎵 Enhancing metadata for: {os.path.basename(file_path)}")
|
print(f"🎵 Enhancing metadata for: {os.path.basename(file_path)}")
|
||||||
try:
|
try:
|
||||||
# Wipe ALL existing tags first — files from Soulseek carry random metadata
|
audio_file = MutagenFile(file_path)
|
||||||
# (wrong comments, encoder info, ReplayGain, old album art, random TXXX frames)
|
if audio_file is None:
|
||||||
audio_raw = MutagenFile(file_path)
|
|
||||||
if audio_raw is None:
|
|
||||||
print(f"❌ Could not load audio file with Mutagen: {file_path}")
|
print(f"❌ Could not load audio file with Mutagen: {file_path}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if hasattr(audio_raw, 'clear_pictures'):
|
# ── Wipe ALL existing tags in memory (NO save yet) ──
|
||||||
audio_raw.clear_pictures()
|
# 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:
|
if audio_file.tags is not None:
|
||||||
audio_raw.tags.clear()
|
audio_file.tags.clear()
|
||||||
audio_raw.save()
|
else:
|
||||||
|
|
||||||
# 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:
|
|
||||||
audio_file.add_tags()
|
audio_file.add_tags()
|
||||||
|
|
||||||
metadata = _extract_spotify_metadata(context, artist, album_info)
|
metadata = _extract_spotify_metadata(context, artist, album_info)
|
||||||
if not metadata:
|
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
|
return True
|
||||||
|
|
||||||
# Use 'easy' tags for broad compatibility first
|
# ── Write standard tags using format-specific API ──
|
||||||
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']
|
|
||||||
|
|
||||||
track_num_str = f"{metadata.get('track_number', 1)}/{metadata.get('total_tracks', 1)}"
|
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'):
|
if isinstance(audio_file.tags, ID3):
|
||||||
audio_file['discnumber'] = str(metadata.get('disc_number'))
|
# 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()
|
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.")
|
print("✅ Metadata enhanced successfully.")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue