2024-08-10 22:49:00 -04:00
|
|
|
#!/usr/bin/env python3.12
|
2025-01-11 20:59:10 -05:00
|
|
|
# pylint: disable=bare-except, broad-exception-raised, broad-exception-caught
|
2024-08-10 22:49:00 -04:00
|
|
|
|
|
|
|
import importlib
|
2025-01-11 20:59:10 -05:00
|
|
|
import traceback
|
|
|
|
import logging
|
2024-08-10 22:49:00 -04:00
|
|
|
import urllib.parse
|
|
|
|
import regex
|
2024-11-29 15:33:12 -05:00
|
|
|
import aiohttp
|
2024-08-10 22:49:00 -04:00
|
|
|
|
2024-08-11 13:49:07 -04:00
|
|
|
from fastapi import FastAPI, HTTPException
|
2025-01-13 20:47:39 -05:00
|
|
|
from pydantic import BaseModel
|
|
|
|
from lyric_search_new.sources import aggregate
|
2024-08-10 22:49:00 -04:00
|
|
|
|
|
|
|
|
2024-08-11 07:42:47 -04:00
|
|
|
class ValidLyricRequest(BaseModel):
|
|
|
|
"""
|
|
|
|
- **a**: artist
|
|
|
|
- **s**: song
|
|
|
|
- **t**: track (artist and song combined) [used only if a & s are not used]
|
|
|
|
- **extra**: include extra details in response [optional, default: false]
|
2024-11-29 15:33:12 -05:00
|
|
|
- **lrc**: Request LRCs?
|
2024-08-11 13:49:07 -04:00
|
|
|
- **sub**: text to search within lyrics, if found lyrics will begin at found verse [optional]
|
2024-08-11 07:42:47 -04:00
|
|
|
- **src**: the script/utility which initiated the request
|
|
|
|
"""
|
2024-08-11 08:12:44 -04:00
|
|
|
|
2024-08-10 22:49:00 -04:00
|
|
|
a: str | None = None
|
|
|
|
s: str | None = None
|
|
|
|
t: str | None = None
|
|
|
|
sub: str | None = None
|
|
|
|
extra: bool | None = False
|
2024-11-29 15:33:12 -05:00
|
|
|
lrc: bool | None = False
|
2024-08-10 22:57:45 -04:00
|
|
|
src: str
|
2024-08-10 22:49:00 -04:00
|
|
|
|
2024-08-11 13:49:07 -04:00
|
|
|
class Config: # pylint: disable=missing-class-docstring too-few-public-methods
|
2024-08-11 09:50:41 -04:00
|
|
|
schema_extra = {
|
|
|
|
"example": {
|
|
|
|
"a": "eminem",
|
|
|
|
"s": "rap god",
|
|
|
|
"src": "WEB",
|
2024-11-29 15:33:12 -05:00
|
|
|
"extra": True,
|
|
|
|
"lrc": False,
|
2024-08-11 09:50:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-13 19:50:02 -04:00
|
|
|
|
|
|
|
class ValidLyricSearchLogRequest(BaseModel):
|
|
|
|
"""
|
|
|
|
- **webradio**: whether or not to include requests generated automatically by the radio page on codey.lol, defaults to False
|
|
|
|
"""
|
|
|
|
|
|
|
|
webradio: bool = False
|
|
|
|
|
2024-08-10 22:49:00 -04:00
|
|
|
class LyricSearch(FastAPI):
|
2024-08-11 13:49:07 -04:00
|
|
|
"""Lyric Search Endpoint"""
|
2024-08-13 19:21:48 -04:00
|
|
|
def __init__(self, app: FastAPI, util, constants, glob_state): # pylint: disable=super-init-not-called
|
2024-08-10 22:49:00 -04:00
|
|
|
self.app = app
|
|
|
|
self.util = util
|
|
|
|
self.constants = constants
|
2024-08-13 19:21:48 -04:00
|
|
|
self.glob_state = glob_state
|
2024-08-10 22:49:00 -04:00
|
|
|
self.lyrics_engine = importlib.import_module("lyrics_engine").LyricsEngine()
|
|
|
|
|
2024-08-11 17:04:06 -04:00
|
|
|
self.endpoint_name = "lyric_search"
|
|
|
|
self.endpoint2_name = "lyric_cache_list"
|
2024-08-13 10:36:53 -04:00
|
|
|
|
|
|
|
self.endpoints = {
|
|
|
|
"lyric_search": self.lyric_search_handler,
|
|
|
|
"lyric_cache_list": self.lyric_cache_list_handler,
|
2025-01-13 20:47:39 -05:00
|
|
|
"lyric_search_history": self.lyric_search_log_handler,
|
|
|
|
"lyric_search_test": self.new_test,
|
2024-08-13 10:36:53 -04:00
|
|
|
}
|
|
|
|
|
2024-08-10 22:49:00 -04:00
|
|
|
self.acceptable_request_sources = [
|
|
|
|
"WEB",
|
2024-08-17 06:01:18 -04:00
|
|
|
"WEB-RADIO",
|
2024-08-10 22:49:00 -04:00
|
|
|
"IRC-MS",
|
|
|
|
"IRC-FS",
|
|
|
|
"IRC-KALI",
|
|
|
|
"DISC-ACES",
|
|
|
|
"DISC-HAVOC",
|
2024-08-17 05:58:01 -04:00
|
|
|
"IRC-SHARED"
|
2024-08-10 22:49:00 -04:00
|
|
|
]
|
|
|
|
|
2024-11-29 15:33:12 -05:00
|
|
|
self.lrc_regex = regex.compile(r'\[([0-9]{2}:[0-9]{2})\.[0-9]{1,3}\](\s(.*)){0,}')
|
|
|
|
|
2024-08-13 10:36:53 -04:00
|
|
|
for endpoint, handler in self.endpoints.items():
|
|
|
|
app.add_api_route(f"/{endpoint}/", handler, methods=["POST"])
|
2024-08-11 17:04:06 -04:00
|
|
|
|
|
|
|
async def lyric_cache_list_handler(self):
|
|
|
|
"""
|
|
|
|
Get currently cached lyrics entries
|
|
|
|
"""
|
|
|
|
return {
|
|
|
|
'err': False,
|
|
|
|
'data': await self.lyrics_engine.listCacheEntries()
|
|
|
|
}
|
2024-08-13 19:50:02 -04:00
|
|
|
|
|
|
|
async def lyric_search_log_handler(self, data: ValidLyricSearchLogRequest):
|
2025-01-11 20:59:10 -05:00
|
|
|
"""Lyric Search Log Handler"""
|
2024-08-13 19:50:02 -04:00
|
|
|
include_radio = data.webradio
|
|
|
|
await self.glob_state.increment_counter('lyrichistory_requests')
|
|
|
|
last_10k_sings = await self.lyrics_engine.getHistory(limit=10000, webradio=include_radio)
|
|
|
|
return {
|
|
|
|
'err': False,
|
|
|
|
'history': last_10k_sings
|
|
|
|
}
|
2025-01-13 20:47:39 -05:00
|
|
|
|
|
|
|
async def new_test(self, data: ValidLyricRequest):
|
|
|
|
"""
|
|
|
|
Search for lyrics (testing)
|
2024-08-13 19:50:02 -04:00
|
|
|
|
2025-01-13 20:47:39 -05:00
|
|
|
- **a**: artist
|
|
|
|
- **s**: song
|
|
|
|
- **t**: track (artist and song combined) [used only if a & s are not used] [unused]
|
|
|
|
- **extra**: include extra details in response [optional, default: false] [unused]
|
|
|
|
- **lrc**: Request LRCs? [unused]
|
|
|
|
- **sub**: text to search within lyrics, if found lyrics will begin at found verse [optional, default: none] [unused]
|
|
|
|
- **src**: the script/utility which initiated the request [unused]
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not data.a or not data.s:
|
|
|
|
raise HTTPException(detail="Invalid request", status_code=500)
|
|
|
|
|
|
|
|
aggregate_search = aggregate.Aggregate()
|
|
|
|
result = await aggregate_search.search(data.a, data.s)
|
|
|
|
return result.dict()
|
|
|
|
|
|
|
|
|
2024-08-11 13:49:07 -04:00
|
|
|
|
2024-08-11 09:08:00 -04:00
|
|
|
async def lyric_search_handler(self, data: ValidLyricRequest):
|
2024-08-11 07:42:47 -04:00
|
|
|
"""
|
|
|
|
Search for lyrics
|
|
|
|
|
|
|
|
- **a**: artist
|
|
|
|
- **s**: song
|
|
|
|
- **t**: track (artist and song combined) [used only if a & s are not used]
|
|
|
|
- **extra**: include extra details in response [optional, default: false]
|
2024-11-29 15:33:12 -05:00
|
|
|
- **lrc**: Request LRCs?
|
2024-08-11 07:42:47 -04:00
|
|
|
- **sub**: text to search within lyrics, if found lyrics will begin at found verse [optional, default: none]
|
|
|
|
- **src**: the script/utility which initiated the request
|
|
|
|
"""
|
|
|
|
|
2024-11-29 15:33:12 -05:00
|
|
|
lrc = data.lrc
|
2024-08-10 22:49:00 -04:00
|
|
|
src = data.src.upper()
|
2024-08-11 13:49:07 -04:00
|
|
|
if not src in self.acceptable_request_sources:
|
2024-08-13 19:21:48 -04:00
|
|
|
raise HTTPException(detail="Invalid request source", status_code=403)
|
|
|
|
|
|
|
|
await self.glob_state.increment_counter('lyric_requests')
|
2024-08-11 13:49:07 -04:00
|
|
|
|
|
|
|
search_artist = data.a
|
|
|
|
search_song = data.s
|
|
|
|
search_text = data.t
|
|
|
|
add_extras = data.extra
|
|
|
|
sub_search = data.sub
|
|
|
|
search_object = None
|
|
|
|
|
|
|
|
random_song_requested = (search_artist == "!" and search_song == "!")
|
2024-08-10 22:49:00 -04:00
|
|
|
query_valid = (
|
2024-08-11 13:49:07 -04:00
|
|
|
not(search_artist is None) and
|
|
|
|
not(search_song is None) and
|
|
|
|
len(search_artist) >= 1 and
|
|
|
|
len(search_song) >= 1 and
|
|
|
|
len(search_artist) + len(search_song) >= 3
|
2024-08-10 22:49:00 -04:00
|
|
|
)
|
|
|
|
|
2024-08-11 13:49:07 -04:00
|
|
|
if not random_song_requested and (not search_text and not query_valid):
|
2024-08-10 22:49:00 -04:00
|
|
|
return {
|
|
|
|
"err": True,
|
|
|
|
"errorText": "Invalid parameters"
|
2024-08-11 13:49:07 -04:00
|
|
|
}
|
|
|
|
if search_artist and search_song:
|
|
|
|
search_artist = self.constants.DOUBLE_SPACE_REGEX.sub(" ", search_artist.strip())
|
|
|
|
search_song = self.constants.DOUBLE_SPACE_REGEX.sub(" ", search_song.strip())
|
|
|
|
search_artist = urllib.parse.unquote(search_artist)
|
|
|
|
search_song = urllib.parse.unquote(search_song)
|
|
|
|
|
|
|
|
if search_text is None:
|
|
|
|
# pylint: disable=consider-using-f-string
|
|
|
|
search_object = self.lyrics_engine.create_query_object("%s : %s" % (search_artist, search_song))
|
|
|
|
if sub_search:
|
2024-09-11 07:53:19 -04:00
|
|
|
sub_search = regex.sub(r'\s{2,}', ' ', sub_search.strip())
|
|
|
|
search_object = self.lyrics_engine.create_query_object("%s : %s : %s" % (search_artist, search_song, sub_search))
|
2024-08-11 08:03:36 -04:00
|
|
|
else:
|
2024-08-11 13:49:07 -04:00
|
|
|
search_object = self.lyrics_engine.create_query_object(str(search_text))
|
|
|
|
|
2024-11-29 15:33:12 -05:00
|
|
|
if lrc:
|
|
|
|
search_worker = await self.lyrics_engine.grabFromSpotify(searching=search_object,
|
|
|
|
lrc=True)
|
|
|
|
|
|
|
|
spotify_lyrics_unsynced = True
|
|
|
|
if search_worker and search_worker.get('l'):
|
|
|
|
for line in search_worker.get('l'):
|
|
|
|
if line.get('timeTag') and line.get('timeTag') != "00:00.00":
|
|
|
|
spotify_lyrics_unsynced = False
|
|
|
|
if not search_worker or spotify_lyrics_unsynced:
|
|
|
|
# Try LRCLib before failing out
|
|
|
|
try:
|
|
|
|
lrclib_api_url = "https://lrclib.net/api/get"
|
|
|
|
sane_artist = urllib.parse.quote_plus(search_artist)
|
|
|
|
sane_track = urllib.parse.quote_plus(search_song)
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
|
|
async with session.get(f"{lrclib_api_url}?artist_name={sane_artist}&track_name={sane_track}") as request:
|
|
|
|
request.raise_for_status()
|
|
|
|
response_json = await request.json()
|
|
|
|
if not "syncedLyrics" in response_json:
|
|
|
|
raise BaseException("LRCLib Fallback Failed")
|
|
|
|
lrc_content = response_json.get('syncedLyrics')
|
|
|
|
returned_artist = response_json.get('artistName')
|
|
|
|
returned_song = response_json.get('trackName')
|
2025-01-11 20:59:10 -05:00
|
|
|
logging.debug("Synced Lyrics [LRCLib]: %s",
|
|
|
|
lrc_content)
|
2024-11-29 15:33:12 -05:00
|
|
|
lrc_content_out = []
|
|
|
|
for line in lrc_content.split("\n"):
|
|
|
|
_timetag = None
|
|
|
|
_words = None
|
|
|
|
if not line.strip():
|
|
|
|
continue
|
|
|
|
reg_helper = regex.findall(self.lrc_regex, line.strip())
|
|
|
|
if not reg_helper:
|
|
|
|
continue
|
|
|
|
reg_helper = reg_helper[0]
|
2025-01-11 20:59:10 -05:00
|
|
|
logging.debug("Reg helper: %s for line: %s; len: %s",
|
|
|
|
reg_helper, line, len(reg_helper))
|
2024-11-29 15:33:12 -05:00
|
|
|
_timetag = reg_helper[0]
|
|
|
|
if not reg_helper[1].strip():
|
|
|
|
_words = "♪"
|
|
|
|
else:
|
|
|
|
_words = reg_helper[1]
|
|
|
|
lrc_content_out.append({
|
|
|
|
"timeTag": _timetag,
|
|
|
|
"words": _words,
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
'err': False,
|
|
|
|
'artist': returned_artist,
|
|
|
|
'song': returned_song,
|
|
|
|
'combo_lev': "N/A",
|
|
|
|
'lrc': lrc_content_out,
|
|
|
|
'from_cache': False,
|
|
|
|
'src': 'Alt LRC SRC',
|
|
|
|
'reqn': await self.glob_state.get_counter('lyric_requests'),
|
|
|
|
}
|
|
|
|
except:
|
2025-01-11 20:59:10 -05:00
|
|
|
traceback.print_exc()
|
2024-11-29 15:33:12 -05:00
|
|
|
return {
|
|
|
|
'err': True,
|
|
|
|
'errorText': 'Search failed!',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
'err': True,
|
|
|
|
'errorText': 'Search failed!',
|
|
|
|
}
|
2025-01-11 20:59:10 -05:00
|
|
|
if lrc:
|
2024-11-29 15:33:12 -05:00
|
|
|
return {
|
|
|
|
'err': False,
|
|
|
|
'artist': search_worker['artist'],
|
|
|
|
'song': search_worker['song'],
|
|
|
|
'combo_lev': search_worker['combo_lev'],
|
|
|
|
'lrc': search_worker['l'],
|
|
|
|
'from_cache': False,
|
|
|
|
'src': search_worker['method'],
|
|
|
|
'reqn': await self.glob_state.get_counter('lyric_requests'),
|
|
|
|
}
|
|
|
|
|
2025-01-11 20:59:10 -05:00
|
|
|
search_worker = await self.lyrics_engine.lyrics_worker(searching=search_object)
|
2024-11-29 15:33:12 -05:00
|
|
|
|
2024-08-10 22:49:00 -04:00
|
|
|
|
2024-08-11 13:49:07 -04:00
|
|
|
if not search_worker or not 'l' in search_worker.keys():
|
2024-08-13 19:21:48 -04:00
|
|
|
await self.glob_state.increment_counter('failedlyric_requests')
|
2024-08-10 22:49:00 -04:00
|
|
|
return {
|
|
|
|
'err': True,
|
|
|
|
'errorText': 'Sources exhausted, lyrics not located.'
|
|
|
|
}
|
2024-08-17 05:58:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
await self.lyrics_engine.storeHistEntry(artist=search_worker.get('artist'),
|
|
|
|
song=search_worker.get('song'),
|
|
|
|
retr_method=search_worker.get('method'),
|
|
|
|
request_src=src.strip())
|
2024-08-10 22:49:00 -04:00
|
|
|
|
|
|
|
return {
|
|
|
|
'err': False,
|
2024-08-11 13:49:07 -04:00
|
|
|
'artist': search_worker['artist'],
|
|
|
|
'song': search_worker['song'],
|
|
|
|
'combo_lev': f'{search_worker['combo_lev']:.2f}',
|
|
|
|
'lyrics': regex.sub(r"\s/\s", "<br>", " ".join(search_worker['l'])),
|
|
|
|
'from_cache': search_worker['method'].strip().lower().startswith("local cache"),
|
|
|
|
'src': search_worker['method'] if add_extras else None,
|
2024-08-13 19:21:48 -04:00
|
|
|
'reqn': await self.glob_state.get_counter('lyric_requests')
|
2024-08-10 22:49:00 -04:00
|
|
|
}
|