radio_util: db restructuring related changes, misc refactoring, add todos
This commit is contained in:
parent
4e3940e297
commit
f946a6f81c
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user