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 traceback
import time
import regex
from regex import Pattern
import datetime
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 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
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:
"""
@ -27,9 +36,12 @@ class RadioUtil:
self.sqlite_exts: list[str] = [
"/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"
)
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: list[dict] = []
self.now_playing: dict = {
@ -64,6 +76,13 @@ class RadioUtil:
return str(datetime.timedelta(seconds=s)).split(".", maxsplit=1)[0]
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:
return None
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()
if not result or not isinstance(result, sqlite3.Row):
return False
pushObj: dict = {
push_obj: dict = {
"id": result["id"],
"uuid": str(uuid().hex),
"artist": result["artist"].strip(),
@ -136,17 +155,43 @@ class RadioUtil:
"file_path": result["file_path"],
"duration": result["duration"],
}
self.active_playlist.insert(0, pushObj)
self.active_playlist.insert(0, push_obj)
return True
except Exception as e:
logging.critical("search_playlist:: Search error occurred: %s", str(e))
traceback.print_exc()
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:
"""Load Playlist"""
try:
logging.info(f"Loading playlist...")
logging.info("Loading playlist...")
self.active_playlist.clear()
# db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, album, genre, file_path, duration FROM tracks\
# GROUP BY artistdashsong ORDER BY RANDOM()'
@ -155,36 +200,10 @@ class RadioUtil:
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\
# WHERE (genre LIKE "%metalcore%"\
# OR genre LIKE "%math rock%"\
# 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()"""
db_query: str = (
'SELECT distinct(LOWER(TRIM(artist)) || " - " || LOWER(TRIM(song))), (TRIM(artist) || " - " || TRIM(song))'
"AS artistdashsong, id, artist, song, album, file_path, duration FROM tracks GROUP BY artistdashsong ORDER BY RANDOM()"
)
"""
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\
# 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\
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\
# 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\
# 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(
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:
db_conn.row_factory = sqlite3.Row
async with await db_conn.execute(db_query) as db_cursor:
@ -219,7 +238,9 @@ class RadioUtil:
"artist": double_space.sub(" ", r["artist"]).strip(),
"song": double_space.sub(" ", r["song"]).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(
" ", r["artistdashsong"]
).strip(),
@ -232,7 +253,8 @@ class RadioUtil:
"Populated active playlists with %s items",
len(self.active_playlist),
)
except:
except Exception as e:
logging.info("Playlist load failed: %s", str(e))
traceback.print_exc()
async def cache_album_art(self, track_id: int, album_art: bytes) -> None:
@ -244,18 +266,19 @@ class RadioUtil:
Returns:
None
"""
try:
async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
async with await db_conn.execute(
"UPDATE tracks SET album_art = ? WHERE id = ?",
(
album_art,
track_id,
),
) as db_cursor:
await db_conn.commit()
except:
traceback.print_exc()
return None # TODO: Album art is being reworked, temporarily return None
# try:
# async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
# async with await db_conn.execute(
# "UPDATE tracks SET album_art = ? WHERE id = ?",
# (
# album_art,
# track_id,
# ),
# ) as db_cursor:
# await db_conn.commit()
# except:
# traceback.print_exc()
async def get_album_art(
self, track_id: Optional[int] = None, file_path: Optional[str] = None
@ -268,28 +291,29 @@ class RadioUtil:
Returns:
bytes
"""
try:
async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
db_conn.row_factory = sqlite3.Row
query: str = "SELECT album_art FROM tracks WHERE id = ?"
query_params: tuple = (track_id,)
return None # TODO: Album art is being reworked, temporarily return None
# try:
# async with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
# db_conn.row_factory = sqlite3.Row
# query: str = "SELECT album_art FROM tracks WHERE id = ?"
# query_params: tuple = (track_id,)
if file_path and not track_id:
query = "SELECT album_art FROM tracks WHERE file_path = ?"
query_params = (file_path,)
# if file_path and not track_id:
# query = "SELECT album_art FROM tracks WHERE file_path = ?"
# query_params = (file_path,)
async with await db_conn.execute(query, query_params) as db_cursor:
result: Optional[Union[sqlite3.Row, bool]] = (
await db_cursor.fetchone()
)
if not result or not isinstance(result, sqlite3.Row):
return None
return result["album_art"]
except:
traceback.print_exc()
return None
# async with await db_conn.execute(query, query_params) as db_cursor:
# result: Optional[Union[sqlite3.Row, bool]] = (
# await db_cursor.fetchone()
# )
# if not result or not isinstance(result, sqlite3.Row):
# return None
# return result["album_art"]
# except:
# traceback.print_exc()
# 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
Args:
@ -298,7 +322,7 @@ class RadioUtil:
Optional[tuple[int, dict]]
"""
for x, item in enumerate(self.active_playlist):
if item.get("uuid") == uuid:
if item.get("uuid") == _uuid:
return (x, item)
return None
@ -340,22 +364,25 @@ class RadioUtil:
return response
async def webhook_song_change(self, track: dict) -> None:
"""
Handles Song Change Outbounds (Webhooks)
Args:
track (dict)
Returns:
None
"""
try:
"""
Handles Song Change Outbounds (Webhooks)
Args:
track (dict)
Returns:
None
"""
# First, send track info
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"])
)
"""
TODO:
Review friendly_track_start and friendly_track_end, not currently in use
"""
# 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 = {
"username": "serious.FM",
"embeds": [
@ -443,4 +470,5 @@ class RadioUtil:
) as request:
request.raise_for_status()
except Exception as e:
logging.info("Webhook error occurred: %s", str(e))
traceback.print_exc()