diff --git a/endpoints/radio.py b/endpoints/radio.py index b6e39a5..7fb3ec9 100644 --- a/endpoints/radio.py +++ b/endpoints/radio.py @@ -12,6 +12,7 @@ import regex import music_tag from . import radio_util from uuid import uuid4 as uuid +from typing import Optional from pydantic import BaseModel from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException from fastapi.responses import RedirectResponse @@ -19,6 +20,11 @@ from aiohttp import ClientSession, ClientTimeout double_space = regex.compile(r'\s{2,}') +""" +TODO: + minor refactoring/type annotations/docstrings +""" + class RadioException(Exception): pass @@ -95,6 +101,7 @@ class Radio(FastAPI): 'start': 0, 'end': 0, 'file_path': None, + 'id': None, } self.endpoints = { "radio/np": self.radio_now_playing, @@ -113,7 +120,7 @@ class Radio(FastAPI): # NOTE: Not in loop because method is GET for this endpoint app.add_api_route("/radio/album_art", self.album_art_handler, methods=["GET"], - include_in_schema=False) + include_in_schema=True) asyncio.get_event_loop().run_until_complete(self.load_playlist()) @@ -181,6 +188,7 @@ class Radio(FastAPI): for x, item in enumerate(self.active_playlist[0:limit+1]): queue_out.append({ 'pos': x, + 'id': item.get('id'), 'uuid': item.get('uuid'), 'artist': item.get('artist'), 'song': item.get('song'), @@ -281,6 +289,7 @@ class Radio(FastAPI): results = await db_cursor.fetchall() self.active_playlist = [{ 'uuid': str(uuid().hex), + 'id': r['id'], 'artist': double_space.sub(' ', r['artist']).strip(), 'song': double_space.sub(' ', r['song']).strip(), 'artistsong': double_space.sub(' ', r['artistdashsong']).strip(), @@ -291,21 +300,75 @@ class Radio(FastAPI): len(self.active_playlist)) except: traceback.print_exc() - + + async def cache_album_art(self, track_id: int, album_art: bytes) -> None: + try: + logging.info("Attempting cache for %s", track_id) + async with sqlite3.connect(self.active_playlist_path, + timeout=2) as db_conn: + async with await db_conn.execute("UPDATE tracks SET album_art = ? WHERE id = ?", + (album_art, track_id,)) as db_cursor: + await db_conn.commit() + logging.info("Committed %s", track_id) + except: + traceback.print_exc() + + async def get_album_art(self, track_id: Optional[int] = None, + file_path: Optional[str] = None) -> bytes: + try: + async with sqlite3.connect(self.active_playlist_path, + timeout=2) as db_conn: + db_conn.row_factory = sqlite3.Row + query = "SELECT album_art FROM tracks WHERE id = ?" + query_params = (track_id,) + + if file_path: + query = "SELECT album_art FROM tracks WHERE file_path = ?" + query_params = (file_path,) + + async with await db_conn.execute(query, + query_params) as db_cursor: + result = await db_cursor.fetchone() + if not result: + return + return result['album_art'] + except: + traceback.print_exc() + return + + async def _get_album_art(self, track_id: Optional[int] = None, file_path: Optional[str] = None) -> bytes|None: + try: + if not file_path: + file_path = self.now_playing.get('file_path') + + if not file_path: + logging.info("_get_album_art:: No current file") + return + original_file_path = file_path + file_path = file_path.replace("/paul/toons/", + "/singer/gogs_toons/") + logging.info("Seeking %s", original_file_path) + cached_album_art = await self.get_album_art(file_path=original_file_path) + if cached_album_art: + logging.info("Returning from cache!") + return cached_album_art + # Not cached, read from file + tagged = music_tag.load_file(file_path) + album_art = tagged.get('artwork').first + logging.info("Returning from file read!") + return album_art.data + except: + traceback.print_exc() + # TODO: Optimize/cache async def album_art_handler(self, request: Request) -> bytes: try: - current_file = self.now_playing.get('file_path') - if not current_file: - logging.info("album_art_handler:: No current file") + album_art = await self._get_album_art() + if not album_art: return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg", - status_code=302) - current_file = current_file.replace("/paul/toons/", - "/singer/gogs_toons/") - tagged = music_tag.load_file(current_file) - album_art = tagged.get('artwork').first - return Response(content=album_art.data, - media_type=album_art.mime) + status_code=302) + return Response(content=album_art, + media_type="image/png") except Exception as e: traceback.print_exc() return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg", @@ -366,6 +429,12 @@ class Radio(FastAPI): background_tasks.add_task(self.radio_util.webhook_song_change, next) except Exception as e: traceback.print_exc() + try: + if not await self.get_album_art(file_path=next['file_path']): + album_art = await self._get_album_art(next['file_path']) + await self.cache_album_art(next['id'], album_art) + except: + traceback.print_exc() return next else: return await self.radio_pop_track(request, recursion_type="not list: self.active_playlist")