44import shutil
55from lyrics_client import lyrics_client
66
7- async def fetch_and_store_lyrics (filepath : Path , metadata : dict , audio_file = None ):
7+ async def fetch_and_store_lyrics (filepath : Path , metadata : dict , audio_file = None , is_mp3 = False ):
8+ """
9+ Fetch and store lyrics for an audio file.
10+ - Synced lyrics: Save as .lrc file + SYNCEDLYRICS tag (FLAC/Opus) or SYLT (MP3)
11+ - Plain lyrics: Embed in LYRICS tag (FLAC/Opus) or USLT (MP3)
12+ """
813 if metadata .get ('title' ) and metadata .get ('artist' ):
914 try :
1015 log_info ("Fetching lyrics..." )
@@ -18,25 +23,93 @@ async def fetch_and_store_lyrics(filepath: Path, metadata: dict, audio_file=None
1823 if lyrics_result :
1924 if lyrics_result .synced_lyrics :
2025 metadata ['synced_lyrics' ] = lyrics_result .synced_lyrics
21- log_success ("Synced lyrics found (will save to .lrc)" )
26+ # Save synced lyrics to .lrc sidecar file (most compatible)
27+ lrc_path = filepath .with_suffix ('.lrc' )
28+ try :
29+ with open (lrc_path , 'w' , encoding = 'utf-8' ) as f :
30+ f .write (lyrics_result .synced_lyrics )
31+ log_success (f"Saved synced lyrics to { lrc_path .name } " )
32+ except Exception as e :
33+ log_warning (f"Failed to save .lrc file: { e } " )
34+
35+ # Embed in tags (SYNCEDLYRICS for FLAC/Opus, SYLT for MP3)
36+ if is_mp3 :
37+ try :
38+ from mutagen .mp3 import MP3
39+ from mutagen .id3 import ID3 , SYLT , Encoding
40+ audio = MP3 (str (filepath ), ID3 = ID3 )
41+ if audio .tags is None :
42+ audio .add_tags ()
43+
44+ # Parse LRC format and create SYLT
45+ lines = []
46+ for line in lyrics_result .synced_lyrics .split ('\n ' ):
47+ # LRC format: [mm:ss.xx]text
48+ if line .startswith ('[' ) and ']' in line :
49+ timestamp_part = line [1 :line .index (']' )]
50+ text_part = line [line .index (']' )+ 1 :]
51+ if ':' in timestamp_part and text_part .strip ():
52+ try :
53+ parts = timestamp_part .split (':' )
54+ minutes = int (parts [0 ])
55+ seconds = float (parts [1 ])
56+ milliseconds = int ((minutes * 60 + seconds ) * 1000 )
57+ lines .append ((text_part , milliseconds ))
58+ except (ValueError , IndexError ):
59+ continue
60+
61+ if lines :
62+ audio .tags .delall ('SYLT' )
63+ audio .tags .add (SYLT (
64+ encoding = Encoding .UTF8 ,
65+ lang = 'eng' ,
66+ format = 2 , # milliseconds
67+ type = 1 , # lyrics
68+ text = lines
69+ ))
70+ audio .save ()
71+ log_success ("Embedded synced lyrics in SYLT frame" )
72+ except Exception as e :
73+ log_warning (f"Failed to embed MP3 SYLT: { e } " )
74+ elif audio_file :
75+ try :
76+ audio_file ['SYNCEDLYRICS' ] = lyrics_result .synced_lyrics
77+ log_success ("Embedded synced lyrics in SYNCEDLYRICS tag" )
78+ except Exception as e :
79+ log_warning (f"Failed to embed synced lyrics tag: { e } " )
80+
2281 elif lyrics_result .plain_lyrics :
2382 metadata ['plain_lyrics' ] = lyrics_result .plain_lyrics
24- log_success ("Plain lyrics found (will save to .txt)" )
83+
84+ # Embed plain lyrics
85+ if is_mp3 :
86+ try :
87+ from mutagen .mp3 import MP3
88+ from mutagen .id3 import ID3 , USLT , Encoding
89+ audio = MP3 (str (filepath ), ID3 = ID3 )
90+ if audio .tags is None :
91+ audio .add_tags ()
92+
93+ audio .tags .delall ('USLT' )
94+ audio .tags .add (USLT (
95+ encoding = Encoding .UTF8 ,
96+ lang = 'eng' ,
97+ desc = '' ,
98+ text = lyrics_result .plain_lyrics
99+ ))
100+ audio .save ()
101+ log_success ("Embedded plain lyrics in USLT frame" )
102+ except Exception as e :
103+ log_warning (f"Failed to embed MP3 USLT: { e } " )
104+ elif audio_file :
105+ try :
106+ audio_file ['LYRICS' ] = lyrics_result .plain_lyrics
107+ log_success ("Embedded plain lyrics in LYRICS tag" )
108+ except Exception as e :
109+ log_warning (f"Failed to embed plain lyrics tag: { e } " )
25110
26111 except Exception as e :
27112 log_warning (f"Failed to fetch lyrics: { e } " )
28-
29- if audio_file and metadata .get ('synced_lyrics' ):
30- try :
31- lyrics_text = metadata ['synced_lyrics' ]
32-
33- for i , line in enumerate (lyrics_text .split ('\n ' )):
34- if line .strip ():
35- audio_file [f'LYRICS_LINE_{ i + 1 } ' ] = line .strip ()
36-
37- log_success (f"Embedded { len (lyrics_text .splitlines ())} lines of lyrics" )
38- except Exception as e :
39- log_warning (f"Failed to embed lyrics: { e } " )
40113
41114async def embed_lyrics_with_ffmpeg (filepath : Path , metadata : dict ):
42115 """Embed lyrics into the audio file using FFmpeg"""
0 commit comments