WIP: additional radio stations
This commit is contained in:
@ -235,15 +235,18 @@ class ValidRadioNextRequest(BaseModel):
|
|||||||
"""
|
"""
|
||||||
- **key**: API Key
|
- **key**: API Key
|
||||||
- **skipTo**: UUID to skip to [optional]
|
- **skipTo**: UUID to skip to [optional]
|
||||||
|
- **station**: Station (default: "main")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
skipTo: Optional[str] = None
|
skipTo: Optional[str] = None
|
||||||
|
station: str = "main"
|
||||||
|
|
||||||
|
|
||||||
class ValidRadioReshuffleRequest(ValidRadioNextRequest):
|
class ValidRadioReshuffleRequest(ValidRadioNextRequest):
|
||||||
"""
|
"""
|
||||||
- **key**: API Key
|
- **key**: API Key
|
||||||
|
- **station**: Station (default: "main")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -252,11 +255,13 @@ class ValidRadioQueueRequest(BaseModel):
|
|||||||
- **draw**: DataTables draw count, default 1
|
- **draw**: DataTables draw count, default 1
|
||||||
- **start**: paging start position, default 0
|
- **start**: paging start position, default 0
|
||||||
- **search**: Optional search query
|
- **search**: Optional search query
|
||||||
|
- **station**: Station (default: "main")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
draw: Optional[int] = 1
|
draw: Optional[int] = 1
|
||||||
start: Optional[int] = 0
|
start: Optional[int] = 0
|
||||||
search: Optional[str] = None
|
search: Optional[str] = None
|
||||||
|
station: str = "main"
|
||||||
|
|
||||||
|
|
||||||
class ValidRadioQueueShiftRequest(BaseModel):
|
class ValidRadioQueueShiftRequest(BaseModel):
|
||||||
@ -264,18 +269,22 @@ class ValidRadioQueueShiftRequest(BaseModel):
|
|||||||
- **key**: API Key
|
- **key**: API Key
|
||||||
- **uuid**: UUID to shift
|
- **uuid**: UUID to shift
|
||||||
- **next**: Play next if true, immediately if false, default False
|
- **next**: Play next if true, immediately if false, default False
|
||||||
|
- **station**: Station (default: "main")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
uuid: str
|
uuid: str
|
||||||
next: Optional[bool] = False
|
next: Optional[bool] = False
|
||||||
|
station: str = "main"
|
||||||
|
|
||||||
|
|
||||||
class ValidRadioQueueRemovalRequest(BaseModel):
|
class ValidRadioQueueRemovalRequest(BaseModel):
|
||||||
"""
|
"""
|
||||||
- **key**: API Key
|
- **key**: API Key
|
||||||
- **uuid**: UUID to remove
|
- **uuid**: UUID to remove
|
||||||
|
- **station**: Station (default: "main")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
uuid: str
|
uuid: str
|
||||||
|
station: str = "main"
|
||||||
|
@ -71,15 +71,22 @@ class LyricSearch(FastAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for endpoint, handler in self.endpoints.items():
|
for endpoint, handler in self.endpoints.items():
|
||||||
|
rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds)
|
||||||
_schema_include = endpoint in ["lyric/search"]
|
_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(
|
app.add_api_route(
|
||||||
f"/{endpoint}",
|
f"/{endpoint}",
|
||||||
handler,
|
handler,
|
||||||
methods=["POST"],
|
methods=["POST"],
|
||||||
include_in_schema=_schema_include,
|
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:
|
async def typeahead_handler(self, data: ValidTypeAheadRequest) -> JSONResponse:
|
||||||
"""
|
"""
|
||||||
Lyric search typeahead handler
|
Lyric search typeahead handler
|
||||||
|
@ -33,7 +33,7 @@ class Radio(FastAPI):
|
|||||||
self.constants = constants
|
self.constants = constants
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.radio_util = radio_util.RadioUtil(self.constants, self.loop)
|
self.radio_util = radio_util.RadioUtil(self.constants, self.loop)
|
||||||
self.playlist_loaded: bool = False
|
self.playlists_loaded: bool = False
|
||||||
|
|
||||||
self.endpoints: dict = {
|
self.endpoints: dict = {
|
||||||
"radio/np": self.radio_now_playing,
|
"radio/np": self.radio_now_playing,
|
||||||
@ -65,8 +65,9 @@ class Radio(FastAPI):
|
|||||||
app.add_event_handler("startup", self.on_start)
|
app.add_event_handler("startup", self.on_start)
|
||||||
|
|
||||||
async def on_start(self) -> None:
|
async def on_start(self) -> None:
|
||||||
logging.info("radio: Initializing")
|
stations = ", ".join(self.radio_util.db_queries.keys())
|
||||||
self.loop.run_in_executor(None, self.radio_util.load_playlist)
|
logging.info("radio: Initializing stations:\n%s", stations)
|
||||||
|
self.loop.run_in_executor(None, self.radio_util.load_playlists)
|
||||||
|
|
||||||
async def radio_skip(
|
async def radio_skip(
|
||||||
self, data: ValidRadioNextRequest, request: Request
|
self, data: ValidRadioNextRequest, request: Request
|
||||||
@ -89,7 +90,7 @@ class Radio(FastAPI):
|
|||||||
"errorText": "No such queue item.",
|
"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] :
|
queue_item[0] :
|
||||||
]
|
]
|
||||||
# if not self.radio_util.active_playlist:
|
# 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):
|
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
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})
|
return JSONResponse(content={"ok": True})
|
||||||
|
|
||||||
async def radio_get_queue(
|
async def radio_get_queue(
|
||||||
@ -146,7 +147,7 @@ class Radio(FastAPI):
|
|||||||
else:
|
else:
|
||||||
start: int = 0
|
start: int = 0
|
||||||
end: int = 20
|
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:
|
if not search:
|
||||||
queue_full: list = orig_queue
|
queue_full: list = orig_queue
|
||||||
else:
|
else:
|
||||||
@ -190,7 +191,7 @@ class Radio(FastAPI):
|
|||||||
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
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:
|
if not queue_item:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
@ -200,8 +201,8 @@ class Radio(FastAPI):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
(x, item) = queue_item
|
(x, item) = queue_item
|
||||||
self.radio_util.active_playlist.pop(x)
|
self.radio_util.active_playlist[data.station].pop(x)
|
||||||
self.radio_util.active_playlist.insert(0, item)
|
self.radio_util.active_playlist[data.station].insert(0, item)
|
||||||
if not data.next:
|
if not data.next:
|
||||||
await self.radio_util._ls_skip()
|
await self.radio_util._ls_skip()
|
||||||
return JSONResponse(
|
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):
|
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
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:
|
if not queue_item:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
@ -230,7 +231,7 @@ class Radio(FastAPI):
|
|||||||
"errorText": "Queue item not found.",
|
"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(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"ok": True,
|
"ok": True,
|
||||||
@ -238,7 +239,8 @@ class Radio(FastAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def album_art_handler(
|
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:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Get album art, optional parameter track_id may be specified.
|
Get album art, optional parameter track_id may be specified.
|
||||||
@ -247,7 +249,7 @@ class Radio(FastAPI):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not track_id:
|
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)
|
logging.debug("Seeking album art with trackId: %s", track_id)
|
||||||
album_art: Optional[bytes] = self.radio_util.get_album_art(
|
album_art: Optional[bytes] = self.radio_util.get_album_art(
|
||||||
track_id=track_id
|
track_id=track_id
|
||||||
@ -265,11 +267,13 @@ class Radio(FastAPI):
|
|||||||
url="https://codey.lol/images/radio_art_default.jpg", status_code=302
|
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
|
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:
|
try:
|
||||||
ret_obj["elapsed"] = int(time.time()) - ret_obj["start"]
|
ret_obj["elapsed"] = int(time.time()) - ret_obj["start"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -289,15 +293,17 @@ class Radio(FastAPI):
|
|||||||
(Track will be removed from the queue in the process.)
|
(Track will be removed from the queue in the process.)
|
||||||
- **key**: API key
|
- **key**: API key
|
||||||
- **skipTo**: Optional UUID to skip to
|
- **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):
|
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||||
if (
|
if (
|
||||||
not isinstance(self.radio_util.active_playlist, list)
|
not isinstance(self.radio_util.active_playlist[data.station], list)
|
||||||
or not self.radio_util.active_playlist
|
or not self.radio_util.active_playlist[data.station]
|
||||||
):
|
):
|
||||||
if self.radio_util.playlist_loaded:
|
if self.radio_util.playlists_loaded:
|
||||||
self.radio_util.playlist_loaded = False
|
self.radio_util.playlists_loaded = False
|
||||||
await self.on_start()
|
await self.on_start()
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
@ -306,7 +312,7 @@ class Radio(FastAPI):
|
|||||||
"errorText": "General failure occurred, prompting playlist reload.",
|
"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):
|
if not isinstance(next, dict):
|
||||||
logging.critical("next is of type: %s, reloading playlist...", type(next))
|
logging.critical("next is of type: %s, reloading playlist...", type(next))
|
||||||
await self.on_start()
|
await self.on_start()
|
||||||
@ -322,15 +328,16 @@ class Radio(FastAPI):
|
|||||||
time_started: int = int(time.time())
|
time_started: int = int(time.time())
|
||||||
time_ends: int = int(time_started + duration)
|
time_ends: int = int(time_started + duration)
|
||||||
|
|
||||||
if len(self.radio_util.active_playlist) > 1:
|
if len(self.radio_util.active_playlist[data.station]) > 1:
|
||||||
self.radio_util.active_playlist.append(next) # Push to end of playlist
|
self.radio_util.active_playlist[data.station].append(next) # Push to end of playlist
|
||||||
else:
|
else:
|
||||||
self.loop.run_in_executor(None, self.radio_util.load_playlist)
|
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["start"] = time_started
|
||||||
next["end"] = time_ends
|
next["end"] = time_ends
|
||||||
try:
|
try:
|
||||||
|
if data.station == "main":
|
||||||
background_tasks.add_task(self.radio_util.webhook_song_change, next)
|
background_tasks.add_task(self.radio_util.webhook_song_change, next)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("radio_get_next Exception: %s", str(e))
|
logging.info("radio_get_next Exception: %s", str(e))
|
||||||
@ -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
|
artistsong=artistsong, artist=artist, song=song
|
||||||
)
|
)
|
||||||
if data.alsoSkip:
|
if data.alsoSkip:
|
||||||
|
@ -39,6 +39,7 @@ class GPT:
|
|||||||
],
|
],
|
||||||
model="gpt-4o-mini",
|
model="gpt-4o-mini",
|
||||||
temperature=1.00,
|
temperature=1.00,
|
||||||
|
max_completion_tokens=512,
|
||||||
)
|
)
|
||||||
response: Optional[str] = chat_completion.choices[0].message.content
|
response: Optional[str] = chat_completion.choices[0].message.content
|
||||||
return response
|
return response
|
||||||
|
@ -18,15 +18,6 @@ from endpoints.constructors import RadioException
|
|||||||
double_space: Pattern = regex.compile(r"\s{2,}")
|
double_space: Pattern = regex.compile(r"\s{2,}")
|
||||||
non_alnum: Pattern = regex.compile(r"[^a-zA-Z0-9]")
|
non_alnum: Pattern = regex.compile(r"[^a-zA-Z0-9]")
|
||||||
|
|
||||||
"""
|
|
||||||
TODO:
|
|
||||||
- get_genre should only be called once for load_playlist, rework get_genre to (optionally) accept a list of artists,
|
|
||||||
and return (optionally) a list instead of an str
|
|
||||||
- Ask GPT when we encounter an untagged (no genre defined) artist, automation is needed for this tedious task
|
|
||||||
- etc..
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class RadioUtil:
|
class RadioUtil:
|
||||||
"""
|
"""
|
||||||
Radio Utils
|
Radio Utils
|
||||||
@ -40,7 +31,7 @@ class RadioUtil:
|
|||||||
self.sqlite_exts: list[str] = [
|
self.sqlite_exts: list[str] = [
|
||||||
"/home/kyle/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so"
|
"/home/kyle/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so"
|
||||||
]
|
]
|
||||||
self.active_playlist_path: str = os.path.join(
|
self.playback_db_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(
|
self.artist_genre_db_path: str = os.path.join(
|
||||||
@ -49,6 +40,14 @@ class RadioUtil:
|
|||||||
self.album_art_db_path: str = os.path.join(
|
self.album_art_db_path: str = os.path.join(
|
||||||
"/usr/local/share", "sqlite_dbs", "track_album_art.db"
|
"/usr/local/share", "sqlite_dbs", "track_album_art.db"
|
||||||
)
|
)
|
||||||
|
self.db_queries = {
|
||||||
|
'main': self.constants.RADIO_DB_QUERY,
|
||||||
|
'rap': self.constants.RADIO_DB_QUERY_RAP,
|
||||||
|
'pop': self.constants.RADIO_DB_QUERY_POP,
|
||||||
|
'classical': self.constants.RADIO_DB_QUERY_CLASSICAL,
|
||||||
|
'rock': self.constants.RADIO_DB_QUERY_ROCK,
|
||||||
|
'electronic': self.constants.RADIO_DB_QUERY_ELECTRONIC,
|
||||||
|
}
|
||||||
self.playback_genres: list[str] = [
|
self.playback_genres: list[str] = [
|
||||||
# "metal",
|
# "metal",
|
||||||
# # "hip hop",
|
# # "hip hop",
|
||||||
@ -64,9 +63,10 @@ class RadioUtil:
|
|||||||
# # "pop punk",
|
# # "pop punk",
|
||||||
# # "pop-punk",
|
# # "pop-punk",
|
||||||
]
|
]
|
||||||
self.active_playlist: list[dict] = []
|
self.active_playlist: dict[str, list[dict]] = {}
|
||||||
self.playlist_loaded: bool = False
|
self.playlists_loaded: bool = False
|
||||||
self.now_playing: dict = {
|
self.now_playing: dict[str, dict] = {
|
||||||
|
k: {
|
||||||
"artist": "N/A",
|
"artist": "N/A",
|
||||||
"song": "N/A",
|
"song": "N/A",
|
||||||
"album": "N/A",
|
"album": "N/A",
|
||||||
@ -77,6 +77,7 @@ class RadioUtil:
|
|||||||
"end": 0,
|
"end": 0,
|
||||||
"file_path": None,
|
"file_path": None,
|
||||||
"id": None,
|
"id": None,
|
||||||
|
} for k in self.db_queries.keys()
|
||||||
}
|
}
|
||||||
self.webhooks: dict = {
|
self.webhooks: dict = {
|
||||||
"gpt": {
|
"gpt": {
|
||||||
@ -107,7 +108,7 @@ class RadioUtil:
|
|||||||
"""
|
"""
|
||||||
if not query:
|
if not query:
|
||||||
return None
|
return None
|
||||||
with sqlite3.connect(self.active_playlist_path, timeout=1) as _db:
|
with sqlite3.connect(self.playback_db_path, timeout=1) as _db:
|
||||||
_db.row_factory = sqlite3.Row
|
_db.row_factory = sqlite3.Row
|
||||||
db_query: str = """SELECT DISTINCT(LOWER(TRIM(artist) || " - " || TRIM(song))),\
|
db_query: str = """SELECT DISTINCT(LOWER(TRIM(artist) || " - " || TRIM(song))),\
|
||||||
(TRIM(artist) || " - " || TRIM(song)) as artistsong FROM tracks WHERE\
|
(TRIM(artist) || " - " || TRIM(song)) as artistsong FROM tracks WHERE\
|
||||||
@ -118,7 +119,9 @@ class RadioUtil:
|
|||||||
out_result = [str(r["artistsong"]) for r in result]
|
out_result = [str(r["artistsong"]) for r in result]
|
||||||
return out_result
|
return out_result
|
||||||
|
|
||||||
def datatables_search(self, filter: str) -> Optional[list[dict]]:
|
def datatables_search(self,
|
||||||
|
filter: str,
|
||||||
|
station: str = "main") -> Optional[list[dict]]:
|
||||||
"""DataTables Search
|
"""DataTables Search
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -129,7 +132,7 @@ class RadioUtil:
|
|||||||
"""
|
"""
|
||||||
filter = filter.strip().lower()
|
filter = filter.strip().lower()
|
||||||
matched: list[dict] = []
|
matched: list[dict] = []
|
||||||
for item in self.active_playlist:
|
for item in self.active_playlist[station]:
|
||||||
artist: str = item.get("artist", None)
|
artist: str = item.get("artist", None)
|
||||||
song: str = item.get("song", None)
|
song: str = item.get("song", None)
|
||||||
artistsong: str = item.get("artistsong", None)
|
artistsong: str = item.get("artistsong", None)
|
||||||
@ -147,11 +150,12 @@ class RadioUtil:
|
|||||||
matched.append(item)
|
matched.append(item)
|
||||||
return matched
|
return matched
|
||||||
|
|
||||||
def search_playlist(
|
def search_db(
|
||||||
self,
|
self,
|
||||||
artistsong: Optional[str] = None,
|
artistsong: Optional[str] = None,
|
||||||
artist: Optional[str] = None,
|
artist: Optional[str] = None,
|
||||||
song: Optional[str] = None,
|
song: Optional[str] = None,
|
||||||
|
station: str = "main"
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Search for track, add it up next in play queue if found
|
Search for track, add it up next in play queue if found
|
||||||
@ -183,7 +187,7 @@ class RadioUtil:
|
|||||||
search_song.lower(),
|
search_song.lower(),
|
||||||
artistsong.lower(),
|
artistsong.lower(),
|
||||||
)
|
)
|
||||||
with sqlite3.connect(self.active_playlist_path, timeout=2) as db_conn:
|
with sqlite3.connect(self.playback_db_path, timeout=2) as db_conn:
|
||||||
db_conn.enable_load_extension(True)
|
db_conn.enable_load_extension(True)
|
||||||
for ext in self.sqlite_exts:
|
for ext in self.sqlite_exts:
|
||||||
db_conn.load_extension(ext)
|
db_conn.load_extension(ext)
|
||||||
@ -204,10 +208,10 @@ class RadioUtil:
|
|||||||
"file_path": result["file_path"],
|
"file_path": result["file_path"],
|
||||||
"duration": result["duration"],
|
"duration": result["duration"],
|
||||||
}
|
}
|
||||||
self.active_playlist.insert(0, push_obj)
|
self.active_playlist[station].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_db:: Search error occurred: %s", str(e))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -321,7 +325,7 @@ class RadioUtil:
|
|||||||
if not res:
|
if not res:
|
||||||
artist_genre[artist] = "N/A"
|
artist_genre[artist] = "N/A"
|
||||||
continue
|
continue
|
||||||
artist_genre[artist] = res["genre"].title()
|
artist_genre[artist] = res["genre"]
|
||||||
time_end: float = time.time()
|
time_end: float = time.time()
|
||||||
logging.info(f"Time taken: {time_end - time_start}")
|
logging.info(f"Time taken: {time_end - time_start}")
|
||||||
return artist_genre
|
return artist_genre
|
||||||
@ -356,28 +360,34 @@ class RadioUtil:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return "Not Found"
|
return "Not Found"
|
||||||
|
|
||||||
def load_playlist(self) -> None:
|
def load_playlists(self) -> None:
|
||||||
"""Load Playlist"""
|
"""Load Playlists"""
|
||||||
try:
|
try:
|
||||||
logging.info("Loading playlist...")
|
logging.info("Loading playlists...")
|
||||||
|
if isinstance(self.active_playlist, dict):
|
||||||
self.active_playlist.clear()
|
self.active_playlist.clear()
|
||||||
|
|
||||||
db_query: str = self.constants.RADIO_DB_QUERY
|
|
||||||
|
|
||||||
with sqlite3.connect(
|
with sqlite3.connect(
|
||||||
f"file:{self.active_playlist_path}?mode=ro", uri=True, timeout=15
|
f"file:{self.playback_db_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
|
||||||
|
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] = []
|
||||||
db_cursor = db_conn.execute(db_query)
|
db_cursor = db_conn.execute(db_query)
|
||||||
results: list[sqlite3.Row] = db_cursor.fetchall()
|
results: list[sqlite3.Row] = db_cursor.fetchall()
|
||||||
self.active_playlist = [
|
self.active_playlist[station] = [
|
||||||
{
|
{
|
||||||
"uuid": str(uuid().hex),
|
"uuid": str(uuid().hex),
|
||||||
"id": r["id"],
|
"id": r["id"],
|
||||||
"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"].title() if r["genre"] else "Not Found",
|
"genre": r["genre"] if r["genre"] else "Not Found",
|
||||||
"artistsong": double_space.sub(
|
"artistsong": double_space.sub(
|
||||||
" ", r["artistdashsong"]
|
" ", r["artistdashsong"]
|
||||||
).strip(),
|
).strip(),
|
||||||
@ -385,65 +395,52 @@ class RadioUtil:
|
|||||||
"duration": r["duration"],
|
"duration": r["duration"],
|
||||||
}
|
}
|
||||||
for r in results
|
for r in results
|
||||||
if r not in self.active_playlist
|
if r not in self.active_playlist[station]
|
||||||
]
|
]
|
||||||
logging.info(
|
logging.info(
|
||||||
"Populated active playlists with %s items",
|
"Populated playlist: %s with %s items",
|
||||||
len(self.active_playlist),
|
station, len(self.active_playlist[station]),
|
||||||
)
|
)
|
||||||
|
|
||||||
random.shuffle(self.active_playlist)
|
random.shuffle(self.active_playlist[station])
|
||||||
|
|
||||||
"""Dedupe"""
|
"""Dedupe"""
|
||||||
logging.info("Removing duplicate tracks...")
|
logging.info("Removing duplicate tracks...")
|
||||||
dedupe_processed = []
|
dedupe_processed = []
|
||||||
for item in self.active_playlist:
|
for item in self.active_playlist[station]:
|
||||||
artistsongabc: str = non_alnum.sub("", item.get("artistsong", None))
|
artistsongabc: str = non_alnum.sub("", item.get("artistsong", None))
|
||||||
if not artistsongabc:
|
if not artistsongabc:
|
||||||
logging.info("Missing artistsong: %s", item)
|
logging.info("Missing artistsong: %s", item)
|
||||||
continue
|
continue
|
||||||
if artistsongabc in dedupe_processed:
|
if artistsongabc in dedupe_processed:
|
||||||
self.active_playlist.remove(item)
|
self.active_playlist[station].remove(item)
|
||||||
dedupe_processed.append(artistsongabc)
|
dedupe_processed.append(artistsongabc)
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
"Duplicates removed.New playlist size: %s",
|
"Duplicates for playlist: %s removed. New playlist size: %s",
|
||||||
len(self.active_playlist),
|
station, len(self.active_playlist[station]),
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info(
|
# logging.info(
|
||||||
"Playlist: %s",
|
# "Playlist: %s",
|
||||||
[str(a.get("artistsong", "")) for a in self.active_playlist],
|
# [str(a.get("artistsong", "")) for a in self.active_playlist[station]],
|
||||||
)
|
# )
|
||||||
|
|
||||||
if self.playback_genres:
|
if station == 'main' and self.playback_genres:
|
||||||
new_playlist: list[dict] = []
|
new_playlist: list[dict] = []
|
||||||
logging.info("Limiting playback genres")
|
logging.info("Limiting playback genres")
|
||||||
# for item in self.active_playlist:
|
for item in self.active_playlist[station]:
|
||||||
# matched_genre: bool = False
|
|
||||||
# item_genres: str = item.get("genre", "").strip().lower()
|
|
||||||
# for genre in self.playback_genres:
|
|
||||||
# genre = genre.strip().lower()
|
|
||||||
# if genre in item_genres:
|
|
||||||
# if item in new_playlist:
|
|
||||||
# continue
|
|
||||||
# new_playlist.append(item)
|
|
||||||
# matched_genre = True
|
|
||||||
# continue
|
|
||||||
# if matched_genre:
|
|
||||||
# continue
|
|
||||||
for item in self.active_playlist:
|
|
||||||
item_genres = item.get("genre", "").strip().lower()
|
item_genres = item.get("genre", "").strip().lower()
|
||||||
# Check if any genre matches and item isn't already in new_playlist
|
# 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 any(genre.strip().lower() in item_genres for genre in self.playback_genres):
|
||||||
if item not in new_playlist:
|
if item not in new_playlist:
|
||||||
new_playlist.append(item)
|
new_playlist.append(item)
|
||||||
self.active_playlist = new_playlist
|
self.active_playlist[station] = new_playlist
|
||||||
logging.info(
|
logging.info(
|
||||||
"%s items remain for playback after filtering",
|
"%s items for playlist: %s remain for playback after filtering",
|
||||||
len(self.active_playlist),
|
station, len(self.active_playlist[station]),
|
||||||
)
|
)
|
||||||
self.playlist_loaded = True
|
self.playlists_loaded = True
|
||||||
self.loop.run_until_complete(self._ls_skip())
|
self.loop.run_until_complete(self._ls_skip())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("Playlist load failed: %s", str(e))
|
logging.info("Playlist load failed: %s", str(e))
|
||||||
@ -509,7 +506,9 @@ class RadioUtil:
|
|||||||
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,
|
||||||
|
station: str = "main") -> Optional[tuple[int, dict]]:
|
||||||
"""
|
"""
|
||||||
Get queue item by UUID
|
Get queue item by UUID
|
||||||
Args:
|
Args:
|
||||||
@ -517,7 +516,7 @@ class RadioUtil:
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[tuple[int, dict]]
|
Optional[tuple[int, dict]]
|
||||||
"""
|
"""
|
||||||
for x, item in enumerate(self.active_playlist):
|
for x, item in enumerate(self.active_playlist[station]):
|
||||||
if item.get("uuid") == _uuid:
|
if item.get("uuid") == _uuid:
|
||||||
return (x, item)
|
return (x, item)
|
||||||
return None
|
return None
|
||||||
|
Reference in New Issue
Block a user