#!/usr/bin/env python3.12 # pylint: disable=bare-except, broad-exception-caught, wrong-import-position import sys import time sys.path.insert(1,'..') import traceback import logging from typing import Optional from aiohttp import ClientTimeout, ClientSession from lyric_search_new import utils from lyric_search_new.constructors import LyricsResult from . import common from . import cache logger = logging.getLogger() log_level = logging.getLevelName(logger.level) class InvalidResponseException(Exception): """ Invalid Response Exception """ class LRCLib: """LRCLib Search Module""" def __init__(self): self.label: str = "LRCLib" self.lrclib_url: str = "https://lrclib.net/api/search" self.headers: dict = common.SCRAPE_HEADERS self.timeout = ClientTimeout(connect=2, sock_read=4) self.datautils = utils.DataUtils() self.matcher = utils.TrackMatcher() self.cache = cache.Cache() async def search(self, artist: str, song: str, plain: bool = True) -> Optional[LyricsResult]: """ @artist: the artist to search @song: the song to search """ try: artist: str = artist.strip().lower() song: str = song.strip().lower() time_start: float = time.time() logging.info("Searching %s - %s on %s", artist, song, self.label) input_track: str = f"{artist} - {song}" returned_lyrics: str = '' async with ClientSession() as client: async with client.get(self.lrclib_url, params = { 'artist_name': artist, 'track_name': song, }, timeout=self.timeout, headers=self.headers) as request: request.raise_for_status() text: str|None = await request.text() if len(text) < 100: raise InvalidResponseException("Search response text was invalid (len < 100 chars.)") search_data: dict|None = await request.json() # logging.info("Search Data:\n%s", search_data) if not isinstance(search_data, list): raise InvalidResponseException("Invalid JSON.") possible_matches = [(x, f"{result.get('artistName')} - {result.get('trackName')}") for x, result in enumerate(search_data)] best_match = self.matcher.find_best_match(input_track, possible_matches)[0] if not best_match: return best_match_id = best_match[0] if not isinstance(search_data[best_match_id]['artistName'], str): raise InvalidResponseException(f"Invalid JSON: Cannot find artistName key.\n{search_data}") if not isinstance(search_data[best_match_id]['trackName'], str): raise InvalidResponseException(f"Invalid JSON: Cannot find trackName key.\n{search_data}") returned_artist: str = search_data[best_match_id]['artistName'] returned_song: str = search_data[best_match_id]['trackName'] if plain: if not isinstance(search_data[best_match_id]['plainLyrics'], str): raise InvalidResponseException(f"Invalid JSON: Cannot find plainLyrics key.\n{search_data}") returned_lyrics: str = search_data[best_match_id]['plainLyrics'] returned_lyrics = self.datautils.scrub_lyrics(returned_lyrics) else: if not isinstance(search_data[best_match_id]['syncedLyrics'], str): raise InvalidResponseException(f"Invalid JSON: Cannot find syncedLyrics key.\n{search_data}") returned_lyrics: str = search_data[best_match_id]['syncedLyrics'] returned_track: str = f"{returned_artist} - {returned_song}" (_matched, confidence) = self.matcher.find_best_match(input_track=input_track, candidate_tracks=[(0, returned_track)]) if not confidence: return # No suitable match found logging.info("Result found on %s", self.label) time_end: float = time.time() time_diff: float = time_end - time_start matched = LyricsResult(artist=returned_artist, song=returned_song, src=self.label, lyrics=returned_lyrics, confidence=confidence, time=time_diff) await self.cache.store(matched) return matched except: if log_level == "DEBUG": traceback.print_exc() return