Embed lyrics directly in audio file tags
Lyrics from LRClib are now embedded in audio file tags (USLT for MP3, lyrics for FLAC/OGG, ©lyr for M4A) in addition to the .lrc sidecar file. Navidrome, Jellyfin, and Plex can read embedded lyrics but not all support .lrc files. Embedding is best-effort — if it fails, the .lrc file still exists. Only adds to existing tags, never creates tags from scratch.
This commit is contained in:
parent
f68cae64a7
commit
7cff379aa7
3 changed files with 49 additions and 1 deletions
|
|
@ -111,8 +111,11 @@ class LyricsClient:
|
|||
with open(lrc_path, 'w', encoding='utf-8') as f:
|
||||
f.write(lrc_content)
|
||||
|
||||
# Embed lyrics directly in audio file tags (Navidrome/Jellyfin read these)
|
||||
self._embed_lyrics(audio_file_path, lrc_content)
|
||||
|
||||
lyrics_type = "synced" if getattr(lyrics_data, 'synced_lyrics', None) else "plain"
|
||||
logger.info(f"✅ Created {lyrics_type} LRC file: {os.path.basename(lrc_path)}")
|
||||
logger.info(f"✅ Created {lyrics_type} LRC file + embedded: {os.path.basename(lrc_path)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -120,5 +123,40 @@ class LyricsClient:
|
|||
return False
|
||||
|
||||
|
||||
def _embed_lyrics(self, audio_file_path: str, lyrics_text: str):
|
||||
"""Embed lyrics directly into audio file tags."""
|
||||
try:
|
||||
from mutagen import File as MutagenFile
|
||||
from mutagen.flac import FLAC
|
||||
from mutagen.oggvorbis import OggVorbis
|
||||
from mutagen.mp4 import MP4
|
||||
from mutagen.id3 import ID3, USLT
|
||||
|
||||
audio = MutagenFile(audio_file_path)
|
||||
if audio is None:
|
||||
return
|
||||
|
||||
if audio.tags is None:
|
||||
return # Don't create tags just for lyrics
|
||||
|
||||
if isinstance(audio.tags, ID3):
|
||||
audio.tags.delall('USLT')
|
||||
audio.tags.add(USLT(encoding=3, lang='eng', desc='', text=lyrics_text))
|
||||
audio.save(v1=0, v2_version=4)
|
||||
elif isinstance(audio, (FLAC, OggVorbis)) or type(audio).__name__ == 'OggOpus':
|
||||
audio['lyrics'] = [lyrics_text]
|
||||
if isinstance(audio, FLAC):
|
||||
audio.save(deleteid3=True)
|
||||
else:
|
||||
audio.save()
|
||||
elif isinstance(audio, MP4):
|
||||
audio['\xa9lyr'] = [lyrics_text]
|
||||
audio.save()
|
||||
|
||||
logger.debug(f"Embedded lyrics in: {os.path.basename(audio_file_path)}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not embed lyrics in {os.path.basename(audio_file_path)}: {e}")
|
||||
|
||||
|
||||
# Global instance for easy import
|
||||
lyrics_client = LyricsClient()
|
||||
|
|
@ -19009,6 +19009,15 @@ def get_version_info():
|
|||
"title": "What's New in SoulSync",
|
||||
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
|
||||
"sections": [
|
||||
{
|
||||
"title": "🎵 Embedded Lyrics in Audio Files",
|
||||
"description": "Lyrics are now embedded directly in audio file tags alongside the .lrc sidecar file",
|
||||
"features": [
|
||||
"• Lyrics embedded as USLT (MP3), lyrics (FLAC/OGG), or ©lyr (M4A) tags",
|
||||
"• Navidrome, Jellyfin, and Plex can now display lyrics without .lrc file support",
|
||||
"• .lrc sidecar files are still created for compatibility with other players"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "🔧 Fix AcoustID False Positives for Non-English Tracks",
|
||||
"description": "AcoustID no longer quarantines correct files when titles are in different languages",
|
||||
|
|
|
|||
|
|
@ -3403,6 +3403,7 @@ function closeHelperSearch() {
|
|||
const WHATS_NEW = {
|
||||
'2.1': [
|
||||
// Newest features first
|
||||
{ title: 'Embedded Lyrics', desc: 'Lyrics now embedded directly in audio file tags — Navidrome, Jellyfin, and Plex can display them' },
|
||||
{ title: 'Fix AcoustID False Positives', desc: 'High-confidence fingerprints no longer quarantine correct files when titles are in different languages' },
|
||||
{ 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' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue