cache album art

This commit is contained in:
codey 2025-02-11 09:50:31 -05:00
parent 028cfc197b
commit 5ee99664f3

View File

@ -12,6 +12,7 @@ import regex
import music_tag import music_tag
from . import radio_util from . import radio_util
from uuid import uuid4 as uuid from uuid import uuid4 as uuid
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
@ -19,6 +20,11 @@ from aiohttp import ClientSession, ClientTimeout
double_space = regex.compile(r'\s{2,}') double_space = regex.compile(r'\s{2,}')
"""
TODO:
minor refactoring/type annotations/docstrings
"""
class RadioException(Exception): class RadioException(Exception):
pass pass
@ -95,6 +101,7 @@ class Radio(FastAPI):
'start': 0, 'start': 0,
'end': 0, 'end': 0,
'file_path': None, 'file_path': None,
'id': None,
} }
self.endpoints = { self.endpoints = {
"radio/np": self.radio_now_playing, "radio/np": self.radio_now_playing,
@ -113,7 +120,7 @@ class Radio(FastAPI):
# NOTE: Not in loop because method is GET for this endpoint # NOTE: Not in loop because method is GET for this endpoint
app.add_api_route("/radio/album_art", self.album_art_handler, methods=["GET"], 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()) 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]): for x, item in enumerate(self.active_playlist[0:limit+1]):
queue_out.append({ queue_out.append({
'pos': x, 'pos': x,
'id': item.get('id'),
'uuid': item.get('uuid'), 'uuid': item.get('uuid'),
'artist': item.get('artist'), 'artist': item.get('artist'),
'song': item.get('song'), 'song': item.get('song'),
@ -281,6 +289,7 @@ class Radio(FastAPI):
results = await db_cursor.fetchall() results = await db_cursor.fetchall()
self.active_playlist = [{ self.active_playlist = [{
'uuid': str(uuid().hex), 'uuid': str(uuid().hex),
'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(),
'artistsong': double_space.sub(' ', r['artistdashsong']).strip(), 'artistsong': double_space.sub(' ', r['artistdashsong']).strip(),
@ -292,20 +301,74 @@ class Radio(FastAPI):
except: except:
traceback.print_exc() 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 # TODO: Optimize/cache
async def album_art_handler(self, request: Request) -> bytes: async def album_art_handler(self, request: Request) -> bytes:
try: try:
current_file = self.now_playing.get('file_path') album_art = await self._get_album_art()
if not current_file: if not album_art:
logging.info("album_art_handler:: No current file")
return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg", return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg",
status_code=302) status_code=302)
current_file = current_file.replace("/paul/toons/", return Response(content=album_art,
"/singer/gogs_toons/") media_type="image/png")
tagged = music_tag.load_file(current_file)
album_art = tagged.get('artwork').first
return Response(content=album_art.data,
media_type=album_art.mime)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg", 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) background_tasks.add_task(self.radio_util.webhook_song_change, next)
except Exception as e: except Exception as e:
traceback.print_exc() 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 return next
else: else:
return await self.radio_pop_track(request, recursion_type="not list: self.active_playlist") return await self.radio_pop_track(request, recursion_type="not list: self.active_playlist")