radio_util: db restructuring related changes, misc refactoring, add todos

This commit is contained in:
codey 2025-04-22 07:52:39 -04:00
parent 4e3940e297
commit f946a6f81c

View File

@ -1,19 +1,28 @@
import logging import logging
import traceback import traceback
import time import time
import regex
from regex import Pattern
import datetime import datetime
import os import os
import gpt
from aiohttp import ClientSession, ClientTimeout
import aiosqlite as sqlite3
from typing import Union, Optional, LiteralString, Iterable
from uuid import uuid4 as uuid from uuid import uuid4 as uuid
from typing import Union, Optional, Iterable
from aiohttp import ClientSession, ClientTimeout
import regex
from regex import Pattern
import aiosqlite as sqlite3
import gpt
from endpoints.constructors import RadioException from endpoints.constructors import RadioException
double_space: Pattern = regex.compile(r"\s{2,}") double_space: Pattern = regex.compile(r"\s{2,}")
"""
TODO:
- Album art rework
- Allow tracks to be queried again based on genre; unable to query tracks based on genre presently,
as genre was moved outside track_file_map to artist_genre_map
- Ask GPT when we encounter an untagged (no genre defined) artist, automation is needed for this tedious task
- etc..
"""
class RadioUtil: class RadioUtil:
""" """
@ -27,9 +36,12 @@ class RadioUtil:
self.sqlite_exts: list[str] = [ self.sqlite_exts: list[str] = [
"/home/api/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so" "/home/api/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so"
] ]
self.active_playlist_path: Union[str, LiteralString] = os.path.join( self.active_playlist_path: str = os.path.join(
"/usr/local/share", "sqlite_dbs", "track_file_map.db" "/usr/local/share", "sqlite_dbs", "track_file_map.db"
) )
self.artist_genre_db_path: str = os.path.join(
"/usr/local/share", "sqlite_dbs", "artist_genre_map.db"
)
self.active_playlist_name = "default" # not used self.active_playlist_name = "default" # not used
self.active_playlist: list[dict] = [] self.active_playlist: list[dict] = []
self.now_playing: dict = { self.now_playing: dict = {
@ -64,6 +76,13 @@ class RadioUtil:
return str(datetime.timedelta(seconds=s)).split(".", maxsplit=1)[0] return str(datetime.timedelta(seconds=s)).split(".", maxsplit=1)[0]
async def trackdb_typeahead(self, query: str) -> Optional[list[str]]: async def trackdb_typeahead(self, query: str) -> Optional[list[str]]:
"""
Query track db for typeahead
Args:
query (str): The search query
Returns:
Optional[list[str]]
"""
if not query: if not query:
return None return None
async with sqlite3.connect(self.active_playlist_path, timeout=1) as _db: async with sqlite3.connect(self.active_playlist_path, timeout=1) as _db:
@ -126,7 +145,7 @@ class RadioUtil:
result: Optional[sqlite3.Row | bool] = await db_cursor.fetchone() result: Optional[sqlite3.Row | bool] = await db_cursor.fetchone()
if not result or not isinstance(result, sqlite3.Row): if not result or not isinstance(result, sqlite3.Row):
return False return False
pushObj: dict = { push_obj: dict = {
"id": result["id"], "id": result["id"],
"uuid": str(uuid().hex), "uuid": str(uuid().hex),
"artist": result["artist"].strip(), "artist": result["artist"].strip(),
@ -136,17 +155,43 @@ class RadioUtil:
"file_path": result["file_path"], "file_path": result["file_path"],
"duration": result["duration"], "duration": result["duration"],
} }
self.active_playlist.insert(0, pushObj) self.active_playlist.insert(0, push_obj)
return True return True
except Exception as e: except Exception as e:
logging.critical("search_playlist:: Search error occurred: %s", str(e)) logging.critical("search_playlist:: Search error occurred: %s", str(e))
traceback.print_exc() traceback.print_exc()
return False return False
async def get_genre(self, artist: str) -> str:
"""
Retrieve Genre for given Artist
Args:
artist (str): The artist to query
Returns:
str
"""
try:
artist = artist.strip()
query = "SELECT genre FROM artist_genre WHERE artist LIKE ?"
params = (f"%{artist}",)
async with sqlite3.connect(self.artist_genre_db_path, timeout=2) as _db:
_db.row_factory = sqlite3.Row
async with await _db.execute(query, params) as _cursor:
res = await _cursor.fetchone()
if not res:
raise RadioException(
f"Could not locate {artist} in artist_genre_map db."
)
return res["genre"]
except Exception as e:
logging.info("Failed to look up genre for artist: %s (%s)", artist, str(e))
traceback.print_exc()
return "Not Found"
async def load_playlist(self) -> None: async def load_playlist(self) -> None:
"""Load Playlist""" """Load Playlist"""
try: try:
logging.info(f"Loading playlist...") logging.info("Loading playlist...")
self.active_playlist.clear() self.active_playlist.clear()
# db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\ # db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\
# GROUP BY artistdashsong ORDER BY RANDOM()' # GROUP BY artistdashsong ORDER BY RANDOM()'
@ -155,36 +200,10 @@ class RadioUtil:
LIMITED GENRES LIMITED GENRES
""" """
# db_query: str = """SELECT distinct(LOWER(TRIM(artist)) || " - " || LOWER(TRIM(song))), (TRIM(artist) || " - " || TRIM(song)) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\ db_query: str = (
# WHERE (genre LIKE "%metalcore%"\ 'SELECT distinct(LOWER(TRIM(artist)) || " - " || LOWER(TRIM(song))), (TRIM(artist) || " - " || TRIM(song))'
# OR genre LIKE "%math rock%"\ "AS artistdashsong, id, artist, song, album, file_path, duration FROM tracks GROUP BY artistdashsong ORDER BY RANDOM()"
# OR genre LIKE "%punk rock%"\ )
# OR genre LIKE "%metal%"\
# OR genre LIKE "%punk%"\
# OR genre LIKE "%electronic%"\
# OR genre LIKE "%nu metal%"\
# OR genre LIKE "%EDM%"\
# OR genre LIKE "%post-hardcore%"\
# OR genre LIKE "%pop rock%"\
# OR genre LIKE "%experimental%"\
# OR genre LIKE "%post-punk%"\
# OR genre LIKE "%death metal%"\
# OR genre LIKE "%electronicore%"\
# OR genre LIKE "%hard rock%"\
# OR genre LIKE "%psychedelic rock%"\
# OR genre LIKE "%grunge%"\
# OR genre LIKE "%house%"\
# OR genre LIKE "%dubstep%"\
# OR genre LIKE "%hardcore%"\
# OR genre LIKE "%hair metal%"\
# OR genre LIKE "%horror punk%"\
# OR genre LIKE "%breakcore%"\
# OR genre LIKE "%post-rock%"\
# OR genre LIKE "%deathcore%"\
# OR genre LIKE "%hardcore punk%"\
# OR genre LIKE "%indie pop%"\
# OR genre LIKE "%dnb%")\
# GROUP BY artistdashsong ORDER BY RANDOM()"""
""" """
LIMITED TO ONE/SMALL SUBSET OF GENRES LIMITED TO ONE/SMALL SUBSET OF GENRES
@ -200,14 +219,14 @@ class RadioUtil:
# db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\ # db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\
# WHERE (artist LIKE "%rise against%" OR artist LIKE "%i prevail%" OR artist LIKE "%volumes%" OR artist LIKE "%movements%" OR artist LIKE "%woe%" OR artist LIKE "%smittyztop%" OR artist LIKE "%chunk! no,%" OR artist LIKE "%fame on fire%" OR artist LIKE "%our last night%" OR artist LIKE "%animal in me%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC' # WHERE (artist LIKE "%rise against%" OR artist LIKE "%i prevail%" OR artist LIKE "%volumes%" OR artist LIKE "%movements%" OR artist LIKE "%woe%" OR artist LIKE "%smittyztop%" OR artist LIKE "%chunk! no,%" OR artist LIKE "%fame on fire%" OR artist LIKE "%our last night%" OR artist LIKE "%animal in me%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC'
db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\ # db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\
WHERE (artist LIKE "%sullivan king%" OR artist LIKE "%kayzo%" OR artist LIKE "%adventure club%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC' # WHERE (artist LIKE "%sullivan king%" OR artist LIKE "%kayzo%" OR artist LIKE "%adventure club%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC'
# db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\ # db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\
# WHERE (artist LIKE "%akira the don%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC' # WHERE (artist LIKE "%akira the don%") AND (NOT song LIKE "%%stripped%%" AND NOT song LIKE "%(2022)%" AND NOT song LIKE "%(live%%" AND NOT song LIKE "%%acoustic%%" AND NOT song LIKE "%%instrumental%%" AND NOT song LIKE "%%remix%%" AND NOT song LIKE "%%reimagined%%" AND NOT song LIKE "%%alternative%%" AND NOT song LIKE "%%unzipped%%") GROUP BY artistdashsong ORDER BY RANDOM()'# ORDER BY album ASC, id ASC'
async with sqlite3.connect( async with sqlite3.connect(
f"file:{self.active_playlist_path}?mode=ro", uri=True, timeout=2 f"file:{self.active_playlist_path}?mode=ro", uri=True, timeout=15
) as db_conn: ) as db_conn:
db_conn.row_factory = sqlite3.Row db_conn.row_factory = sqlite3.Row
async with await db_conn.execute(db_query) as db_cursor: async with await db_conn.execute(db_query) as db_cursor:
@ -219,7 +238,9 @@ class RadioUtil:
"artist": double_space.sub(" ", r["artist"]).strip(), "artist": double_space.sub(" ", r["artist"]).strip(),
"song": double_space.sub(" ", r["song"]).strip(), "song": double_space.sub(" ", r["song"]).strip(),
"album": double_space.sub(" ", r["album"]).strip(), "album": double_space.sub(" ", r["album"]).strip(),
"genre": r["genre"] if r["genre"] else "Unknown", "genre": await self.get_genre(
double_space.sub(" ", r["artist"]).strip()
),
"artistsong": double_space.sub( "artistsong": double_space.sub(
" ", r["artistdashsong"] " ", r["artistdashsong"]
).strip(), ).strip(),
@ -232,7 +253,8 @@ class RadioUtil:
"Populated active playlists with %s items", "Populated active playlists with %s items",
len(self.active_playlist), len(self.active_playlist),
) )
except: except Exception as e:
logging.info("Playlist load failed: %s", str(e))
traceback.print_exc() traceback.print_exc()
async def cache_album_art(self, track_id: int, album_art: bytes) -> None: async def cache_album_art(self, track_id: int, album_art: bytes) -> None:
@ -244,18 +266,19 @@ class RadioUtil:
Returns: Returns:
None None
""" """
try: return None # TODO: Album art is being reworked, temporarily return None
async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn: # try:
async with await db_conn.execute( # async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
"UPDATE tracks SET album_art = ? WHERE id = ?", # async with await db_conn.execute(
( # "UPDATE tracks SET album_art = ? WHERE id = ?",
album_art, # (
track_id, # album_art,
), # track_id,
) as db_cursor: # ),
await db_conn.commit() # ) as db_cursor:
except: # await db_conn.commit()
traceback.print_exc() # except:
# traceback.print_exc()
async def get_album_art( async def get_album_art(
self, track_id: Optional[int] = None, file_path: Optional[str] = None self, track_id: Optional[int] = None, file_path: Optional[str] = None
@ -268,28 +291,29 @@ class RadioUtil:
Returns: Returns:
bytes bytes
""" """
try: return None # TODO: Album art is being reworked, temporarily return None
async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn: # try:
db_conn.row_factory = sqlite3.Row # async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
query: str = "SELECT album_art FROM tracks WHERE id = ?" # db_conn.row_factory = sqlite3.Row
query_params: tuple = (track_id,) # query: str = "SELECT album_art FROM tracks WHERE id = ?"
# query_params: tuple = (track_id,)
if file_path and not track_id: # if file_path and not track_id:
query = "SELECT album_art FROM tracks WHERE file_path = ?" # query = "SELECT album_art FROM tracks WHERE file_path = ?"
query_params = (file_path,) # query_params = (file_path,)
async with await db_conn.execute(query, query_params) as db_cursor: # async with await db_conn.execute(query, query_params) as db_cursor:
result: Optional[Union[sqlite3.Row, bool]] = ( # result: Optional[Union[sqlite3.Row, bool]] = (
await db_cursor.fetchone() # await db_cursor.fetchone()
) # )
if not result or not isinstance(result, sqlite3.Row): # if not result or not isinstance(result, sqlite3.Row):
return None # return None
return result["album_art"] # return result["album_art"]
except: # except:
traceback.print_exc() # traceback.print_exc()
return None # return None
def get_queue_item_by_uuid(self, uuid: str) -> Optional[tuple[int, dict]]: def get_queue_item_by_uuid(self, _uuid: str) -> Optional[tuple[int, dict]]:
""" """
Get queue item by UUID Get queue item by UUID
Args: Args:
@ -298,7 +322,7 @@ class RadioUtil:
Optional[tuple[int, dict]] Optional[tuple[int, dict]]
""" """
for x, item in enumerate(self.active_playlist): for x, item in enumerate(self.active_playlist):
if item.get("uuid") == uuid: if item.get("uuid") == _uuid:
return (x, item) return (x, item)
return None return None
@ -340,22 +364,25 @@ class RadioUtil:
return response return response
async def webhook_song_change(self, track: dict) -> None: async def webhook_song_change(self, track: dict) -> None:
"""
Handles Song Change Outbounds (Webhooks)
Args:
track (dict)
Returns:
None
"""
try: try:
"""
Handles Song Change Outbounds (Webhooks)
Args:
track (dict)
Returns:
None
"""
# First, send track info # First, send track info
friendly_track_start: str = time.strftime( """
"%Y-%m-%d %H:%M:%S", time.localtime(track["start"]) TODO:
) Review friendly_track_start and friendly_track_end, not currently in use
friendly_track_end: str = time.strftime( """
"%Y-%m-%d %H:%M:%S", time.localtime(track["end"]) # friendly_track_start: str = time.strftime(
) # "%Y-%m-%d %H:%M:%S", time.localtime(track["start"])
# )
# friendly_track_end: str = time.strftime(
# "%Y-%m-%d %H:%M:%S", time.localtime(track["end"])
# )
hook_data: dict = { hook_data: dict = {
"username": "serious.FM", "username": "serious.FM",
"embeds": [ "embeds": [
@ -443,4 +470,5 @@ class RadioUtil:
) as request: ) as request:
request.raise_for_status() request.raise_for_status()
except Exception as e: except Exception as e:
logging.info("Webhook error occurred: %s", str(e))
traceback.print_exc() traceback.print_exc()