playlists have been stored to redis for faster retrieval; additional work needed (playlist management, typeahead, etc- to move away from SQLite)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,3 +19,6 @@ mypy.ini
|
||||
get_next_track.py
|
||||
endpoints/radio.py
|
||||
utils/radio_util.py
|
||||
redis_playlist.py
|
||||
endpoints/radio2
|
||||
endpoints/radio2/**
|
1
base.py
1
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),
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user