This commit is contained in:
2025-11-25 13:06:07 -05:00
parent 476c4e6e51
commit 85298b861d
3 changed files with 54 additions and 33 deletions

View File

@@ -80,9 +80,9 @@ class LyricSearch(FastAPI):
) )
for endpoint, handler in self.endpoints.items(): for endpoint, handler in self.endpoints.items():
times: int = 20 times: int = 5
seconds: int = 2 seconds: int = 2
rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds) rate_limit: tuple[int, int] = (times, seconds) # Default; (Times, Seconds)
_schema_include = endpoint in ["lyric/search"] _schema_include = endpoint in ["lyric/search"]
if ( if (

View File

@@ -307,7 +307,7 @@ class Radio(FastAPI):
} }
) )
async def album_art_handler( def album_art_handler(
self, request: Request, track_id: Optional[int] = None, self, request: Request, track_id: Optional[int] = None,
station: Station = "main" station: Station = "main"
) -> Response: ) -> Response:
@@ -371,6 +371,14 @@ class Radio(FastAPI):
ret_obj.pop("file_path") ret_obj.pop("file_path")
return JSONResponse(content=ret_obj) return JSONResponse(content=ret_obj)
def _bg_cache_art(self, track_id: int, file_path: str):
try:
album_art = self.radio_util.get_album_art(track_id=track_id)
if not album_art:
self.radio_util.cache_album_art(track_id, file_path)
except Exception as e:
logging.error(f"Background art cache error: {e}")
async def radio_get_next( async def radio_get_next(
self, self,
data: ValidRadioNextRequest, data: ValidRadioNextRequest,
@@ -448,13 +456,7 @@ class Radio(FastAPI):
logging.info("radio_get_next Exception: %s", str(e)) logging.info("radio_get_next Exception: %s", str(e))
traceback.print_exc() traceback.print_exc()
try: background_tasks.add_task(self._bg_cache_art, next["id"], next["file_path"])
album_art = self.radio_util.get_album_art(track_id=next["id"])
if not album_art:
self.radio_util.cache_album_art(next["id"], next["file_path"])
except Exception as e:
logging.info("radio_get_next Exception: %s", str(e))
traceback.print_exc()
return JSONResponse(content=next) return JSONResponse(content=next)
@@ -496,8 +498,12 @@ class Radio(FastAPI):
}, },
) )
search: bool = self.radio_util.search_db( loop = asyncio.get_running_loop()
artistsong=artistsong, artist=artist, song=song, station=data.station search: bool = await loop.run_in_executor(
None,
lambda: self.radio_util.search_db(
artistsong=artistsong, artist=artist, song=song, station=data.station
)
) )
if data.alsoSkip: if data.alsoSkip:
await self.radio_util._ls_skip(data.station) await self.radio_util._ls_skip(data.station)
@@ -764,7 +770,22 @@ class Radio(FastAPI):
logging.info(f"[LRC] Starting fetch for {station}: {artist} - {title}") logging.info(f"[LRC] Starting fetch for {station}: {artist} - {title}")
# Try SR first with timeout # Try LRCLib first with timeout
try:
async with asyncio.timeout(10.0): # 10 second timeout
logging.info("[LRC] Trying LRCLib")
lrclib_result = await self.lrclib.search(artist, title, plain=False, raw=True)
if lrclib_result and lrclib_result.lyrics and isinstance(lrclib_result.lyrics, str):
logging.info("[LRC] Found from LRCLib")
return lrclib_result.lyrics, "LRCLib"
except asyncio.TimeoutError:
logging.warning("[LRC] LRCLib fetch timed out")
except Exception as e:
logging.error(f"[LRC] LRCLib fetch error: {e}")
logging.info("[LRC] LRCLib fetch completed without results")
# Try SR as fallback with timeout
try: try:
async with asyncio.timeout(10.0): # 10 second timeout async with asyncio.timeout(10.0): # 10 second timeout
lrc = await self.sr_util.get_lrc_by_artist_song( lrc = await self.sr_util.get_lrc_by_artist_song(
@@ -778,21 +799,6 @@ class Radio(FastAPI):
except Exception as e: except Exception as e:
logging.error(f"[LRC] SR fetch error: {e}") logging.error(f"[LRC] SR fetch error: {e}")
logging.info("[LRC] SR fetch completed without results")
# Try LRCLib as fallback with timeout
try:
async with asyncio.timeout(10.0): # 10 second timeout
logging.info("[LRC] Trying LRCLib fallback")
lrclib_result = await self.lrclib.search(artist, title, plain=False)
if lrclib_result and lrclib_result.lyrics and isinstance(lrclib_result.lyrics, str):
logging.info("[LRC] Found from LRCLib")
return lrclib_result.lyrics, "LRCLib"
except asyncio.TimeoutError:
logging.warning("[LRC] LRCLib fetch timed out")
except Exception as e:
logging.error(f"[LRC] LRCLib fetch error: {e}")
logging.info("[LRC] No lyrics found from any source") logging.info("[LRC] No lyrics found from any source")
return None, "None" return None, "None"
except Exception as e: except Exception as e:
@@ -804,11 +810,21 @@ class Radio(FastAPI):
try: try:
async with self.lrc_cache_locks[station]: async with self.lrc_cache_locks[station]:
self.lrc_cache.pop(station, None) self.lrc_cache.pop(station, None)
lrc, source = await self._fetch_and_cache_lrc(station, track_json)
if lrc: lrc, source = await self._fetch_and_cache_lrc(station, track_json)
self.lrc_cache[station] = lrc
async with self.lrc_cache_locks[station]:
# Verify we are still on the same song
current_track = self.radio_util.now_playing.get(station)
if current_track and current_track.get("uuid") == track_json.get("uuid"):
if lrc:
self.lrc_cache[station] = lrc
else:
self.lrc_cache[station] = None
else: else:
self.lrc_cache[station] = None logging.info(f"[LRC] Discarding fetch result for {station} as track changed.")
return
if lrc: if lrc:
await self.broadcast_lrc(station, lrc, source) await self.broadcast_lrc(station, lrc, source)
except Exception as e: except Exception as e:

View File

@@ -26,6 +26,7 @@ class LRCLib:
song: str, song: str,
plain: Optional[bool] = True, plain: Optional[bool] = True,
duration: Optional[int] = None, duration: Optional[int] = None,
raw: bool = False,
) -> Optional[LyricsResult]: ) -> Optional[LyricsResult]:
""" """
LRCLib Local Database Search LRCLib Local Database Search
@@ -34,6 +35,7 @@ class LRCLib:
song (str): the song to search song (str): the song to search
plain (bool): return plain lyrics (True) or synced lyrics (False) plain (bool): return plain lyrics (True) or synced lyrics (False)
duration (int): optional track duration for better matching duration (int): optional track duration for better matching
raw (bool): return raw LRC string instead of parsed object (only for synced)
Returns: Returns:
Optional[LyricsResult]: The result, if found - None otherwise. Optional[LyricsResult]: The result, if found - None otherwise.
""" """
@@ -119,7 +121,10 @@ class LRCLib:
logging.info("No synced lyrics available on %s", self.label) logging.info("No synced lyrics available on %s", self.label)
return None return None
returned_lyrics = best_match.synced_lyrics returned_lyrics = best_match.synced_lyrics
lrc_obj = self.datautils.create_lrc_object(returned_lyrics) if raw:
lrc_obj = returned_lyrics
else:
lrc_obj = self.datautils.create_lrc_object(returned_lyrics)
# Calculate match confidence # Calculate match confidence
input_track = f"{artist} - {song}" input_track = f"{artist} - {song}"