diff --git a/.gitignore b/.gitignore index cf48ccf..f32d5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ mypy.ini .python-version get_next_track.py endpoints/radio.py -utils/radio_util.py \ No newline at end of file +utils/radio_util.py +redis_playlist.py +endpoints/radio2 +endpoints/radio2/** \ No newline at end of file diff --git a/base.py b/base.py index ceeb62d..9d4bc2d 100644 --- a/base.py +++ b/base.py @@ -97,7 +97,6 @@ routes: dict = { "radio": importlib.import_module("endpoints.radio").Radio( app, util, constants, loop ), - "mgr": importlib.import_module("endpoints.mgr.mgr_test").Mgr(app, util, constants), "meme": importlib.import_module("endpoints.meme").Meme(app, util, constants), } diff --git a/endpoints/radio.py b/endpoints/radio.py index b61d621..1e65d23 100644 --- a/endpoints/radio.py +++ b/endpoints/radio.py @@ -23,7 +23,6 @@ from fastapi import ( from fastapi_throttle import RateLimiter from fastapi.responses import RedirectResponse, JSONResponse - class Radio(FastAPI): """Radio Endpoints""" @@ -34,7 +33,6 @@ class Radio(FastAPI): self.loop = loop self.radio_util = radio_util.RadioUtil(self.constants, self.loop) self.playlists_loaded: bool = False - self.endpoints: dict = { "radio/np": self.radio_now_playing, "radio/request": self.radio_request, @@ -67,7 +65,7 @@ class Radio(FastAPI): async def on_start(self) -> None: stations = ", ".join(self.radio_util.db_queries.keys()) logging.info("radio: Initializing stations:\n%s", stations) - self.loop.run_in_executor(None, self.radio_util.load_playlists) + await self.radio_util.load_playlists() async def radio_skip( self, data: ValidRadioNextRequest, request: Request @@ -300,6 +298,8 @@ class Radio(FastAPI): - **station**: default: "main" """ logging.info("Radio get next") + if data.station not in self.radio_util.active_playlist.keys(): + raise HTTPException(status_code=500, detail="No such station/not ready") if not self.util.check_key(path=request.url.path, req_type=4, key=data.key): raise HTTPException(status_code=403, detail="Unauthorized") if ( diff --git a/utils/radio_util.py b/utils/radio_util.py index e05eb15..eb2d030 100644 --- a/utils/radio_util.py +++ b/utils/radio_util.py @@ -3,7 +3,6 @@ import traceback import time import datetime import os -import random from uuid import uuid4 as uuid from typing import Union, Optional, Iterable from aiohttp import ClientSession, ClientTimeout @@ -14,6 +13,12 @@ import gpt import music_tag # type: ignore from rapidfuzz import fuzz from endpoints.constructors import RadioException +import redis.asyncio as redis +from redis.commands.search.query import Query # noqa +from redis.commands.search.indexDefinition import IndexDefinition, IndexType # noqa +from redis.commands.search.field import TextField # noqa +from redis.commands.json.path import Path # noqa +from lyric_search.sources import private double_space: Pattern = regex.compile(r"\s{2,}") non_alnum: Pattern = regex.compile(r"[^a-zA-Z0-9]") @@ -28,6 +33,7 @@ class RadioUtil: self.loop = loop self.gpt = gpt.GPT(self.constants) self.ls_uri: str = self.constants.LS_URI + self.redis_client = redis.Redis(password=private.REDIS_PW) self.sqlite_exts: list[str] = [ "/home/kyle/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so" ] @@ -63,10 +69,18 @@ class RadioUtil: # # "pop punk", # # "pop-punk", ] + self.playlists: list = [ + "main", + "rock", + "rap", + "electronic", + "classical", + "pop", + ] self.active_playlist: dict[str, list[dict]] = {} self.playlists_loaded: bool = False self.now_playing: dict[str, dict] = { - k: { + playlist: { "artist": "N/A", "song": "N/A", "album": "N/A", @@ -77,7 +91,7 @@ class RadioUtil: "end": 0, "file_path": None, "id": None, - } for k in self.db_queries.keys() + } for playlist in self.playlists } self.webhooks: dict = { "gpt": { @@ -360,94 +374,76 @@ class RadioUtil: traceback.print_exc() return "Not Found" - def load_playlists(self) -> None: + async def load_playlists(self) -> None: """Load Playlists""" try: logging.info("Loading playlists...") if isinstance(self.active_playlist, dict): self.active_playlist.clear() - with sqlite3.connect( - f"file:{self.playback_db_path}?mode=ro", uri=True, timeout=30 - ) as db_conn: - db_conn.row_factory = sqlite3.Row - for station in self.db_queries: - db_query = self.db_queries.get(station) - if not db_query: - logging.critical("No query found for %s", station) - continue - if station not in self.active_playlist: - self.active_playlist[station] = [] - time_start = time.time() - logging.info("[%s] Running query: %s", - time_start, db_query) - db_cursor = db_conn.execute(db_query) - results: list[sqlite3.Row] = db_cursor.fetchall() - time_end = time.time() - logging.info("[%s] Query completed; Time taken: %s", time_end, (time_end - time_start)) - self.active_playlist[station] = [ - { - "uuid": str(uuid().hex), - "id": r["id"], - "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 "Not Found", - "artistsong": double_space.sub( - " ", r["artistdashsong"] - ).strip(), - "file_path": r["file_path"], - "duration": r["duration"], - } - for r in results - if r not in self.active_playlist[station] - ] - logging.info( + for playlist in self.playlists: + playlist_redis_key: str = f"playlist:{playlist}" + _playlist = await self.redis_client.json().get(playlist_redis_key) + if playlist not in self.active_playlist.keys(): + self.active_playlist[playlist] = [] + self.active_playlist[playlist] = [ + { + "uuid": str(uuid().hex), + "id": r["id"], + "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 "Not Found", + "artistsong": double_space.sub( + " ", r["artistdashsong"] + ).strip(), + "file_path": r["file_path"], + "duration": r["duration"], + } for r in _playlist + if r not in self.active_playlist[playlist] + ] + logging.info( "Populated playlist: %s with %s items", - station, len(self.active_playlist[station]), + playlist, len(self.active_playlist[playlist]), ) - - if not station == "rock":# REMOVE ME, AFI RELATED - random.shuffle(self.active_playlist[station]) + + """Dedupe""" + logging.info("Removing duplicate tracks...") + dedupe_processed = [] + for item in self.active_playlist[playlist]: + artistsongabc: str = non_alnum.sub("", item.get("artistsong", None)) + if not artistsongabc: + logging.info("Missing artistsong: %s", item) + continue + if artistsongabc in dedupe_processed: + self.active_playlist[playlist].remove(item) + dedupe_processed.append(artistsongabc) - """Dedupe""" - logging.info("Removing duplicate tracks...") - dedupe_processed = [] - for item in self.active_playlist[station]: - artistsongabc: str = non_alnum.sub("", item.get("artistsong", None)) - if not artistsongabc: - logging.info("Missing artistsong: %s", item) - continue - if artistsongabc in dedupe_processed: - self.active_playlist[station].remove(item) - dedupe_processed.append(artistsongabc) + logging.info( + "Duplicates for playlist: %s removed. New playlist size: %s", + playlist, len(self.active_playlist[playlist]), + ) + if playlist == 'main' and self.playback_genres: + new_playlist: list[dict] = [] + logging.info("Limiting playback genres") + for item in self.active_playlist[playlist]: + item_genres = item.get("genre", "").strip().lower() + # Check if any genre matches and item isn't already in new_playlist + if any(genre.strip().lower() in item_genres for genre in self.playback_genres): + if item not in new_playlist: + new_playlist.append(item) + self.active_playlist[playlist] = new_playlist logging.info( - "Duplicates for playlist: %s removed. New playlist size: %s", - station, len(self.active_playlist[station]), + "%s items for playlist: %s remain for playback after filtering", + playlist, len(self.active_playlist[playlist]), ) - # logging.info( - # "Playlist: %s", - # [str(a.get("artistsong", "")) for a in self.active_playlist[station]], - # ) + """Loading Complete""" + logging.info(f"Skipping: {playlist}") + await self._ls_skip(playlist) # Request skip from LS to bring streams current - if station == 'main' and self.playback_genres: - new_playlist: list[dict] = [] - logging.info("Limiting playback genres") - for item in self.active_playlist[station]: - item_genres = item.get("genre", "").strip().lower() - # Check if any genre matches and item isn't already in new_playlist - if any(genre.strip().lower() in item_genres for genre in self.playback_genres): - if item not in new_playlist: - new_playlist.append(item) - self.active_playlist[station] = new_playlist - logging.info( - "%s items for playlist: %s remain for playback after filtering", - station, len(self.active_playlist[station]), - ) self.playlists_loaded = True - # self.loop.run_until_complete(self._ls_skip()) except Exception as e: logging.info("Playlist load failed: %s", str(e)) traceback.print_exc()