WIP: additional radio stations

This commit is contained in:
2025-07-17 06:55:16 -04:00
parent fd300743c8
commit 85182b7d8c
5 changed files with 162 additions and 139 deletions

View File

@ -235,15 +235,18 @@ class ValidRadioNextRequest(BaseModel):
"""
- **key**: API Key
- **skipTo**: UUID to skip to [optional]
- **station**: Station (default: "main")
"""
key: str
skipTo: Optional[str] = None
station: str = "main"
class ValidRadioReshuffleRequest(ValidRadioNextRequest):
"""
- **key**: API Key
- **station**: Station (default: "main")
"""
@ -252,11 +255,13 @@ class ValidRadioQueueRequest(BaseModel):
- **draw**: DataTables draw count, default 1
- **start**: paging start position, default 0
- **search**: Optional search query
- **station**: Station (default: "main")
"""
draw: Optional[int] = 1
start: Optional[int] = 0
search: Optional[str] = None
station: str = "main"
class ValidRadioQueueShiftRequest(BaseModel):
@ -264,18 +269,22 @@ class ValidRadioQueueShiftRequest(BaseModel):
- **key**: API Key
- **uuid**: UUID to shift
- **next**: Play next if true, immediately if false, default False
- **station**: Station (default: "main")
"""
key: str
uuid: str
next: Optional[bool] = False
station: str = "main"
class ValidRadioQueueRemovalRequest(BaseModel):
"""
- **key**: API Key
- **uuid**: UUID to remove
- **station**: Station (default: "main")
"""
key: str
uuid: str
station: str = "main"

View File

@ -71,14 +71,21 @@ class LyricSearch(FastAPI):
)
for endpoint, handler in self.endpoints.items():
rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds)
_schema_include = endpoint in ["lyric/search"]
if endpoint == "typeahead/lyrics": # More permissive rate limiting for typeahead
rate_limit = (20, 2)
(times, seconds) = rate_limit
app.add_api_route(
f"/{endpoint}",
handler,
methods=["POST"],
include_in_schema=_schema_include,
dependencies=[Depends(RateLimiter(times=2, seconds=3))],
dependencies=[Depends(RateLimiter(times=times, seconds=seconds))] if not endpoint == "typeahead/lyrics" else None
)
async def typeahead_handler(self, data: ValidTypeAheadRequest) -> JSONResponse:
"""

View File

@ -33,7 +33,7 @@ class Radio(FastAPI):
self.constants = constants
self.loop = loop
self.radio_util = radio_util.RadioUtil(self.constants, self.loop)
self.playlist_loaded: bool = False
self.playlists_loaded: bool = False
self.endpoints: dict = {
"radio/np": self.radio_now_playing,
@ -65,8 +65,9 @@ class Radio(FastAPI):
app.add_event_handler("startup", self.on_start)
async def on_start(self) -> None:
logging.info("radio: Initializing")
self.loop.run_in_executor(None, self.radio_util.load_playlist)
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)
async def radio_skip(
self, data: ValidRadioNextRequest, request: Request
@ -89,7 +90,7 @@ class Radio(FastAPI):
"errorText": "No such queue item.",
},
)
self.radio_util.active_playlist = self.radio_util.active_playlist[
self.radio_util.active_playlist[data.station] = self.radio_util.active_playlist[data.station][
queue_item[0] :
]
# if not self.radio_util.active_playlist:
@ -125,7 +126,7 @@ class Radio(FastAPI):
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
raise HTTPException(status_code=403, detail="Unauthorized")
random.shuffle(self.radio_util.active_playlist)
random.shuffle(self.radio_util.active_playlist[data.station])
return JSONResponse(content={"ok": True})
async def radio_get_queue(
@ -146,7 +147,7 @@ class Radio(FastAPI):
else:
start: int = 0
end: int = 20
orig_queue: list[dict] = self.radio_util.active_playlist
orig_queue: list[dict] = self.radio_util.active_playlist[data.station]
if not search:
queue_full: list = orig_queue
else:
@ -190,7 +191,7 @@ class Radio(FastAPI):
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
raise HTTPException(status_code=403, detail="Unauthorized")
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid)
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid, data.station)
if not queue_item:
return JSONResponse(
status_code=500,
@ -200,8 +201,8 @@ class Radio(FastAPI):
},
)
(x, item) = queue_item
self.radio_util.active_playlist.pop(x)
self.radio_util.active_playlist.insert(0, item)
self.radio_util.active_playlist[data.station].pop(x)
self.radio_util.active_playlist[data.station].insert(0, item)
if not data.next:
await self.radio_util._ls_skip()
return JSONResponse(
@ -221,7 +222,7 @@ class Radio(FastAPI):
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
raise HTTPException(status_code=403, detail="Unauthorized")
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid)
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid, data.station)
if not queue_item:
return JSONResponse(
status_code=500,
@ -230,7 +231,7 @@ class Radio(FastAPI):
"errorText": "Queue item not found.",
},
)
self.radio_util.active_playlist.pop(queue_item[0])
self.radio_util.active_playlist[data.station].pop(queue_item[0])
return JSONResponse(
content={
"ok": True,
@ -238,7 +239,8 @@ class Radio(FastAPI):
)
async def album_art_handler(
self, request: Request, track_id: Optional[int] = None
self, request: Request, track_id: Optional[int] = None,
station: Optional[str] = "main"
) -> Response:
"""
Get album art, optional parameter track_id may be specified.
@ -247,7 +249,7 @@ class Radio(FastAPI):
"""
try:
if not track_id:
track_id = self.radio_util.now_playing.get("id")
track_id = self.radio_util.now_playing[station].get("id")
logging.debug("Seeking album art with trackId: %s", track_id)
album_art: Optional[bytes] = self.radio_util.get_album_art(
track_id=track_id
@ -265,11 +267,13 @@ class Radio(FastAPI):
url="https://codey.lol/images/radio_art_default.jpg", status_code=302
)
async def radio_now_playing(self, request: Request) -> JSONResponse:
async def radio_now_playing(self, request: Request,
station: Optional[str] = "main") -> JSONResponse:
"""
Get currently playing track info
"""
ret_obj: dict = {**self.radio_util.now_playing}
ret_obj: dict = {**self.radio_util.now_playing[station]}
ret_obj["station"] = station
try:
ret_obj["elapsed"] = int(time.time()) - ret_obj["start"]
except KeyError:
@ -289,15 +293,17 @@ class Radio(FastAPI):
(Track will be removed from the queue in the process.)
- **key**: API key
- **skipTo**: Optional UUID to skip to
- **station**: Station (default: "main")
"""
logging.info("Radio get next")
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
raise HTTPException(status_code=403, detail="Unauthorized")
if (
not isinstance(self.radio_util.active_playlist, list)
or not self.radio_util.active_playlist
not isinstance(self.radio_util.active_playlist[data.station], list)
or not self.radio_util.active_playlist[data.station]
):
if self.radio_util.playlist_loaded:
self.radio_util.playlist_loaded = False
if self.radio_util.playlists_loaded:
self.radio_util.playlists_loaded = False
await self.on_start()
return JSONResponse(
status_code=500,
@ -306,7 +312,7 @@ class Radio(FastAPI):
"errorText": "General failure occurred, prompting playlist reload.",
},
)
next = self.radio_util.active_playlist.pop(0)
next = self.radio_util.active_playlist[data.station].pop(0)
if not isinstance(next, dict):
logging.critical("next is of type: %s, reloading playlist...", type(next))
await self.on_start()
@ -322,16 +328,17 @@ class Radio(FastAPI):
time_started: int = int(time.time())
time_ends: int = int(time_started + duration)
if len(self.radio_util.active_playlist) > 1:
self.radio_util.active_playlist.append(next) # Push to end of playlist
if len(self.radio_util.active_playlist[data.station]) > 1:
self.radio_util.active_playlist[data.station].append(next) # Push to end of playlist
else:
self.loop.run_in_executor(None, self.radio_util.load_playlist)
self.radio_util.now_playing = next
self.radio_util.now_playing[data.station] = next
next["start"] = time_started
next["end"] = time_ends
try:
background_tasks.add_task(self.radio_util.webhook_song_change, next)
if data.station == "main":
background_tasks.add_task(self.radio_util.webhook_song_change, next)
except Exception as e:
logging.info("radio_get_next Exception: %s", str(e))
traceback.print_exc()
@ -377,7 +384,7 @@ class Radio(FastAPI):
},
)
search: bool = self.radio_util.search_playlist(
search: bool = self.radio_util.search_db(
artistsong=artistsong, artist=artist, song=song
)
if data.alsoSkip: