mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-12 20:45:20 +02:00
feat(podcasts): add audio storage
This commit is contained in:
parent
470af28688
commit
4271048dcf
1 changed files with 48 additions and 0 deletions
48
surfsense_backend/app/podcasts/storage.py
Normal file
48
surfsense_backend/app/podcasts/storage.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Durable storage for rendered podcast audio.
|
||||
|
||||
Wraps the shared :class:`StorageBackend` so the rest of the module never deals
|
||||
with object keys directly. Audio is stored under a per-podcast key, streamed for
|
||||
download, and purged when a podcast is deleted.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from app.file_storage.factory import get_storage_backend
|
||||
from app.podcasts.persistence import Podcast
|
||||
|
||||
_AUDIO_CONTENT_TYPE = "audio/mpeg"
|
||||
|
||||
|
||||
def build_audio_key(*, search_space_id: int, podcast_id: int) -> str:
|
||||
"""Object key for a podcast's audio.
|
||||
|
||||
Shape: ``podcasts/{search_space_id}/{podcast_id}/{uuid}.mp3``. The uuid lets
|
||||
a re-render write a fresh object before the old one is purged.
|
||||
"""
|
||||
return f"podcasts/{search_space_id}/{podcast_id}/{uuid.uuid4().hex}.mp3"
|
||||
|
||||
|
||||
async def store_audio(
|
||||
*, search_space_id: int, podcast_id: int, data: bytes
|
||||
) -> tuple[str, str]:
|
||||
"""Persist audio bytes and return ``(backend_name, storage_key)``."""
|
||||
backend = get_storage_backend()
|
||||
key = build_audio_key(search_space_id=search_space_id, podcast_id=podcast_id)
|
||||
await backend.put(key, data, content_type=_AUDIO_CONTENT_TYPE)
|
||||
return backend.backend_name, key
|
||||
|
||||
|
||||
def open_audio_stream(podcast: Podcast) -> AsyncIterator[bytes]:
|
||||
"""Stream a ready podcast's audio bytes. Raises if it has none."""
|
||||
if not podcast.storage_key:
|
||||
raise FileNotFoundError(f"podcast {podcast.id} has no stored audio")
|
||||
return get_storage_backend().open_stream(podcast.storage_key)
|
||||
|
||||
|
||||
async def purge_audio(podcast: Podcast) -> None:
|
||||
"""Delete a podcast's stored audio if present; a missing object is fine."""
|
||||
if podcast.storage_key:
|
||||
await get_storage_backend().delete(podcast.storage_key)
|
||||
Loading…
Add table
Add a link
Reference in a new issue