"""Story audio cache storage helpers.""" from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path from app.core.config import settings @dataclass(frozen=True) class AudioCacheMetadata: """Metadata about one cached story audio file.""" exists: bool path: str | None = None size_bytes: int | None = None updated_at: datetime | None = None def build_story_audio_path(story_id: int) -> str: """Build the cache path for a story audio file.""" return str(Path(settings.story_audio_cache_dir) / f"story-{story_id}.mp3") def audio_cache_exists(audio_path: str | None) -> bool: """Whether the cached audio file exists on disk.""" return bool(audio_path) and Path(audio_path).is_file() def read_audio_cache(audio_path: str) -> bytes: """Read cached story audio bytes.""" return Path(audio_path).read_bytes() def get_audio_cache_metadata(audio_path: str | None) -> AudioCacheMetadata: """Return cache metadata without reading the audio bytes.""" if not audio_path: return AudioCacheMetadata(exists=False) path = Path(audio_path) if not path.is_file(): return AudioCacheMetadata(exists=False, path=str(path)) stat = path.stat() return AudioCacheMetadata( exists=True, path=str(path), size_bytes=stat.st_size, updated_at=datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc), ) def delete_audio_cache(audio_path: str | None) -> bool: """Delete cached story audio if it exists.""" if not audio_path: return False path = Path(audio_path) if not path.is_file(): return False path.unlink() return True def write_story_audio_cache(story_id: int, audio_data: bytes) -> str: """Persist story audio and return the saved file path.""" final_path = Path(build_story_audio_path(story_id)) final_path.parent.mkdir(parents=True, exist_ok=True) temp_path = final_path.with_suffix(".tmp") temp_path.write_bytes(audio_data) temp_path.replace(final_path) return str(final_path)