diff --git a/endpoints/lastfm.py b/endpoints/lastfm.py index 5954aff..35d4fc1 100644 --- a/endpoints/lastfm.py +++ b/endpoints/lastfm.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3.12 +# pylint: disable=bare-except import importlib +import traceback from fastapi import FastAPI from pydantic import BaseModel @@ -182,19 +184,25 @@ class LastFM(FastAPI): /get_track_info/ Get track info from Last.FM given an artist/track """ - artist = data.a - track = data.t - - if not artist or not track: + try: + artist = data.a + track = data.t + + if not artist or not track: + return { + 'err': True, + 'errorText': 'Invalid request' + } + + track_info_result = await self.lastfm.get_track_info(artist=artist, track=track) + assert not "err" in track_info_result.keys() + return { + 'success': True, + 'result': track_info_result + } + except: + traceback.print_exc() return { 'err': True, - 'errorText': 'Invalid request' - } - - track_info_result = await self.lastfm.get_track_info(artist=artist, track=track) - assert not "err" in track_info_result.keys() - return { - 'success': True, - 'result': track_info_result - } - + 'errorText': 'General error', + } diff --git a/endpoints/lyric_search.py b/endpoints/lyric_search.py index 01e97e5..e146114 100644 --- a/endpoints/lyric_search.py +++ b/endpoints/lyric_search.py @@ -194,6 +194,16 @@ class LyricSearch(FastAPI): result = result.dict() result['lyrics'] = regex.sub(r'(\s/\s|\n)', '
', result['lyrics']).strip() result['confidence'] = f'{float(result.get('confidence', 0)):.2f}' + result['time'] = f'{float(result['time']):.4f}' + if "cached" in result['src']: + result['from_cache'] = True + + """ + REMOVE BELOW AFTER TESTING IS DONE + """ + + # if not data.extra: + # result.pop('src') return result diff --git a/lyric_search_new/constructors.py b/lyric_search_new/constructors.py index 946f92d..e1fd767 100644 --- a/lyric_search_new/constructors.py +++ b/lyric_search_new/constructors.py @@ -10,6 +10,7 @@ class LyricsResult: src: str lyrics: str confidence: float + time: float = 0.00 def dict(self): """Return as dict""" diff --git a/lyric_search_new/sources/aggregate.py b/lyric_search_new/sources/aggregate.py index c2468f0..c59b14c 100644 --- a/lyric_search_new/sources/aggregate.py +++ b/lyric_search_new/sources/aggregate.py @@ -37,7 +37,6 @@ class Aggregate: continue search_result = await source.search(artist, song) if search_result: - return search_result + break logging.info("%s: NOT FOUND!", source.label) - return search_result diff --git a/lyric_search_new/sources/cache.py b/lyric_search_new/sources/cache.py index 3b167bd..8085a20 100644 --- a/lyric_search_new/sources/cache.py +++ b/lyric_search_new/sources/cache.py @@ -2,6 +2,8 @@ # pylint: disable=wrong-import-order, wrong-import-position bare-except, broad-exception-caught import os +import time +import regex import logging import sys import traceback @@ -39,9 +41,67 @@ class Cache: src=f"{original_src} (cached, id: {_id})", confidence=confidence) return None + + async def check_existence(self, artistsong: str) -> Optional[bool]: + """ + Check whether lyrics are already stored for track + @artistsong: artist and song in artist\nsong format + """ + logging.debug("Checking whether %s is already stored", + artistsong.replace("\n", " - ")) + check_query = "SELECT id FROM lyrics WHERE artistsong LIKE ? LIMIT 1" + params = (artistsong,) + async with sqlite3.connect(self.cache_db, timeout=2) as db_conn: + async with db_conn.execute(check_query, params) as db_cursor: + result = await db_cursor.fetchone() + if result: + logging.debug("%s is already stored.", + artistsong.replace("\n", " - ")) + return True + logging.debug("%s cleared to be stored.", + artistsong) + return False + + async def store(self, lyr_result: LyricsResult) -> None: + """ + store + @lyr_result: the returned lyrics to cache + """ + + logging.info("Storing %s", + f"{lyr_result.artist} - {lyr_result.song}") + + if lyr_result.src.lower() == "cache": + logging.info("Skipping cache storage - returned LyricsResult originated from cache") + return + + artistsong = f"{lyr_result.artist}\n{lyr_result.song}" + if await self.check_existence(artistsong): + logging.info("Skipping cache storage - %s is already stored.", + artistsong.replace("\n", " - ")) + return + + try: + lyrics = regex.sub(r'(
|\n|\r\n)', ' / ', lyr_result.lyrics.strip()) + lyrics = regex.sub(r'\s{2,}', ' ', lyrics) + + insert_query = "INSERT INTO lyrics (src, date_retrieved, artist, song, artistsong, confidence, lyrics) VALUES(?, ?, ?, ?, ?, ?, ?)" + params = (lyr_result.src, time.time(), lyr_result.artist, + lyr_result.song, artistsong, lyr_result.confidence, lyrics) + + async with sqlite3.connect(self.cache_db, timeout=2) as db_conn: + async with db_conn.execute(insert_query, params) as _cursor: + await db_conn.commit() + logging.info("Stored %s!", artistsong.replace("\n", " - ")) + except: + logging.critical("Cache storage error!") + traceback.print_exc() + + async def search(self, artist: str, song: str) -> Optional[LyricsResult]: """ + search @artist: the artist to search @song: the song to search Returns: @@ -52,6 +112,8 @@ class Cache: song: str = song.strip().lower() search_params: Optional[tuple] = None random_search: bool = False + time_start: float = time.time() + logging.info("Searching %s - %s on %s", artist, song, self.label) async with sqlite3.connect(self.cache_db, timeout=2) as db_conn: @@ -60,8 +122,8 @@ class Cache: await db_conn.load_extension(ext) async with await db_conn.executescript(self.cache_pre_query) as _db_cursor: search_query: str = 'SELECT id, artist, song, lyrics, src, confidence FROM lyrics\ - WHERE editdist3((artist || " " || song), (? || " " || ?))\ - <= 410 ORDER BY editdist3((artist || " " || song), ?) ASC LIMIT 10' + WHERE editdist3((lower(artist) || " " || lower(song)), (? || " " || ?))\ + <= 410 ORDER BY editdist3((lower(artist) || " " || lower(song)), ?) ASC LIMIT 10' search_params: tuple = (artist.strip(), song.strip(), f"{artist.strip()} {song.strip()}") if artist == "!" and song == "!": @@ -85,9 +147,13 @@ class Cache: return None (candidate, confidence) = best_match logging.info("Result found on %s", self.label) - return self.get_matched(sqlite_rows=results, + matched = self.get_matched(sqlite_rows=results, matched_candidate=candidate, confidence=confidence) + time_end: float = time.time() + time_diff: float = time_end - time_start + matched.time = time_diff + return matched except: if log_level == "DEBUG": traceback.print_exc() diff --git a/lyric_search_new/sources/genius.py b/lyric_search_new/sources/genius.py index 7b470ab..b899661 100644 --- a/lyric_search_new/sources/genius.py +++ b/lyric_search_new/sources/genius.py @@ -5,15 +5,18 @@ import sys sys.path.insert(1,'..') import traceback import logging +import time from typing import Optional from aiohttp import ClientTimeout, ClientSession from bs4 import BeautifulSoup, ResultSet import html as htm from . import private from . import common +from . import cache from lyric_search_new import utils from lyric_search_new.constructors import LyricsResult + logger = logging.getLogger() log_level = logging.getLevelName(logger.level) @@ -32,6 +35,7 @@ class Genius: 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) -> Optional[LyricsResult]: """ @@ -41,6 +45,7 @@ class Genius: 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) search_term: str = f'{artist}%20{song}' @@ -104,15 +109,20 @@ class Genius: artist: str = track.split(" - ", maxsplit=1)[0] song: str = track.split(" - ", maxsplit=1)[1] logging.info("Result found on %s", self.label) - return LyricsResult(artist=artist, + time_end: float = time.time() + time_diff: float = time_end - time_start + matched = LyricsResult(artist=artist, song=song, src=self.label, lyrics=returned_lyrics, - confidence=confidence) + confidence=confidence, + time=time_diff) + await self.cache.store(matched) + return matched except: - if log_level == "DEBUG": - traceback.print_exc() + # if log_level == "DEBUG": + traceback.print_exc() return diff --git a/lyric_search_new/sources/lrclib.py b/lyric_search_new/sources/lrclib.py index b906cf9..a28c483 100644 --- a/lyric_search_new/sources/lrclib.py +++ b/lyric_search_new/sources/lrclib.py @@ -2,6 +2,7 @@ # pylint: disable=bare-except, broad-exception-caught, wrong-import-position import sys +import time sys.path.insert(1,'..') import traceback import logging @@ -10,6 +11,7 @@ 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) @@ -28,6 +30,7 @@ class LRCLib: 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) -> Optional[LyricsResult]: """ @@ -37,6 +40,7 @@ class LRCLib: 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) @@ -91,11 +95,16 @@ class LRCLib: if not confidence: return # No suitable match found logging.info("Result found on %s", self.label) - return LyricsResult(artist=returned_artist, + 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) + confidence=confidence, + time=time_diff) + await self.cache.store(matched) + return matched except: if log_level == "DEBUG": traceback.print_exc()