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 - **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"

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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