This commit is contained in:
2025-02-15 21:09:33 -05:00
parent 60416c493f
commit 39d1ddaffa
22 changed files with 509 additions and 525 deletions

View File

@ -2,22 +2,16 @@
import logging
import traceback
import os
import aiosqlite as sqlite3
import time
import random
import asyncio
import regex
import music_tag
from . import radio_util
from .constructors import ValidRadioNextRequest, ValidRadioReshuffleRequest, ValidRadioQueueShiftRequest,\
ValidRadioQueueRemovalRequest, ValidRadioSongRequest,\
ValidRadioQueueGetRequest, RadioException
ValidRadioQueueRemovalRequest, ValidRadioSongRequest, RadioException
from uuid import uuid4 as uuid
from typing import Optional, LiteralString
from typing import Optional
from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException
from fastapi.responses import RedirectResponse
from aiohttp import ClientSession, ClientTimeout
from fastapi.responses import RedirectResponse, JSONResponse
# pylint: disable=bare-except, broad-exception-caught, invalid-name
@ -28,8 +22,9 @@ TODO:
class Radio(FastAPI):
"""Radio Endpoints"""
def __init__(self, app: FastAPI, my_util, constants) -> None: # pylint: disable=super-init-not-called
self.app = app
def __init__(self, app: FastAPI,
my_util, constants) -> None: # pylint: disable=super-init-not-called
self.app: FastAPI = app
self.util = my_util
self.constants = constants
self.radio_util = radio_util.RadioUtil(self.constants)
@ -55,7 +50,8 @@ class Radio(FastAPI):
asyncio.get_event_loop().run_until_complete(self.radio_util.load_playlist())
asyncio.get_event_loop().run_until_complete(self.radio_util._ls_skip())
async def radio_skip(self, data: ValidRadioNextRequest, request: Request) -> bool:
async def radio_skip(self, data: ValidRadioNextRequest,
request: Request) -> JSONResponse:
"""
Skip to the next track in the queue, or to uuid specified in skipTo if provided
"""
@ -65,17 +61,28 @@ class Radio(FastAPI):
if data.skipTo:
queue_item = self.radio_util.get_queue_item_by_uuid(data.skipTo)
if not queue_item:
return False
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'No such queue item.',
})
self.radio_util.active_playlist = self.radio_util.active_playlist[queue_item[0]:]
if not self.radio_util.active_playlist:
await self.radio_util.load_playlist()
return await self.radio_util._ls_skip()
skip_result: bool = await self.radio_util._ls_skip()
status_code = 200 if skip_result else 500
return JSONResponse(status_code=status_code, content={
'success': skip_result,
})
except Exception as e:
traceback.print_exc()
return False
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'General failure.',
})
async def radio_reshuffle(self, data: ValidRadioReshuffleRequest, request: Request) -> dict:
async def radio_reshuffle(self, data: ValidRadioReshuffleRequest,
request: Request) -> JSONResponse:
"""
Reshuffle the play queue
"""
@ -83,16 +90,16 @@ class Radio(FastAPI):
raise HTTPException(status_code=403, detail="Unauthorized")
random.shuffle(self.radio_util.active_playlist)
return {
return JSONResponse(content={
'ok': True
}
})
async def radio_get_queue(self, request: Request, limit: Optional[int] = 15_000) -> dict:
async def radio_get_queue(self, request: Request,
limit: Optional[int] = 15_000) -> JSONResponse:
"""
Get current play queue, up to limit [default: 15k]
"""
queue_out: list[dict] = []
for x, item in enumerate(self.radio_util.active_playlist[0:limit]):
queue_out.append({
@ -104,45 +111,52 @@ class Radio(FastAPI):
'artistsong': item.get('artistsong'),
'duration': item.get('duration'),
})
return {
return JSONResponse(content={
'items': queue_out
}
})
async def radio_queue_shift(self, data: ValidRadioQueueShiftRequest, request: Request) -> dict:
"""Shift position of a UUID within the queue [currently limited to playing next or immediately]"""
async def radio_queue_shift(self, data: ValidRadioQueueShiftRequest,
request: Request) -> JSONResponse:
"""
Shift position of a UUID within the queue
[currently limited to playing next or immediately]
"""
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)
if not queue_item:
return {
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'Queue item not found.',
}
})
(x, item) = queue_item
self.radio_util.active_playlist.pop(x)
self.radio_util.active_playlist.insert(0, item)
if not data.next:
await self.radio_util._ls_skip()
return {
return JSONResponse(content={
'ok': True,
}
})
async def radio_queue_remove(self, data: ValidRadioQueueRemovalRequest, request: Request) -> dict:
"""Remove an item from the current play queue"""
async def radio_queue_remove(self, data: ValidRadioQueueRemovalRequest,
request: Request) -> JSONResponse:
"""
Remove an item from the current play queue
"""
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)
if not queue_item:
return {
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'Queue item not found.',
}
})
self.radio_util.active_playlist.pop(queue_item[0])
return {
return JSONResponse(content={
'ok': True,
}
})
async def album_art_handler(self, request: Request, track_id: Optional[int] = None) -> Response:
"""
@ -164,22 +178,22 @@ class Radio(FastAPI):
return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg",
status_code=302)
async def radio_now_playing(self, request: Request) -> dict:
"""Get currently playing track info"""
async def radio_now_playing(self, request: Request) -> JSONResponse:
"""
Get currently playing track info
"""
ret_obj: dict = {**self.radio_util.now_playing}
cur_elapsed: int = self.radio_util.now_playing.get('elapsed', -1)
cur_duration: int = self.radio_util.now_playing.get('duration', 999999)
try:
ret_obj['elapsed'] = int(time.time()) - ret_obj['start']
except KeyError:
traceback.print_exc()
ret_obj['elapsed'] = 0
ret_obj.pop('file_path')
return ret_obj
return JSONResponse(content=ret_obj)
async def radio_get_next(self, data: ValidRadioNextRequest, request: Request,
background_tasks: BackgroundTasks) -> Optional[dict]:
background_tasks: BackgroundTasks) -> JSONResponse:
"""
Get next track
Track will be removed from the queue in the process.
@ -189,13 +203,19 @@ class Radio(FastAPI):
if not isinstance(self.radio_util.active_playlist, list) or not self.radio_util.active_playlist:
await self.radio_util.load_playlist()
await self.radio_util._ls_skip()
return None
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'General failure occurred, prompting playlist reload.',
})
next = self.radio_util.active_playlist.pop(0)
if not isinstance(next, dict):
logging.critical("next is of type: %s, reloading playlist...", type(next))
await self.radio_util.load_playlist()
await self.radio_util._ls_skip()
return None
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'General failure occurred, prompting playlist reload.',
})
duration: int = next['duration']
time_started: int = int(time.time())
@ -216,37 +236,41 @@ class Radio(FastAPI):
try:
if not await self.radio_util.get_album_art(file_path=next['file_path']):
album_art = await self.radio_util.get_album_art(file_path=next['file_path'])
if not album_art:
return None
await self.radio_util.cache_album_art(next['id'], album_art)
if album_art:
await self.radio_util.cache_album_art(next['id'], album_art)
else:
logging.debug("Could not read album art for %s",
next['file_path'])
except:
traceback.print_exc()
return next
return JSONResponse(content=next)
async def radio_request(self, data: ValidRadioSongRequest, request: Request) -> dict:
"""Song request handler"""
async def radio_request(self, data: ValidRadioSongRequest, request: Request) -> JSONResponse:
"""
Song request handler
"""
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
raise HTTPException(status_code=403, detail="Unauthorized")
artistsong: Optional[str] = data.artistsong
artist: Optional[str] = data.artist
song: Optional[str] = data.song
if artistsong and (artist or song):
return {
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'Invalid request',
}
})
if not artistsong and (not artist or not song):
return {
return JSONResponse(status_code=500, content={
'err': True,
'errorText': 'Invalid request',
}
})
search: bool = await self.radio_util.search_playlist(artistsong=artistsong,
artist=artist,
song=song)
if data.alsoSkip:
await self.radio_util._ls_skip()
return {
return JSONResponse(content={
'result': search
}
})