feat: [api] Fetch audio file for adventure entry

This commit is contained in:
wilson 2026-05-06 21:29:36 +01:00
parent cc9b951b05
commit e40574ae9d
3 changed files with 33 additions and 3 deletions

View file

@ -192,7 +192,7 @@ class AdventureService:
max_tokens=2048, max_tokens=2048,
) )
story_text, choices_parsed, gm_notes = parse_entry_response(raw_text) story_text, choices_parsed, gm_notes, story_so_far = parse_entry_response(raw_text)
await self.entry_repo.update_content( await self.entry_repo.update_content(
entry_id=entry_id, entry_id=entry_id,
@ -222,7 +222,8 @@ class AdventureService:
) )
voice = self.gemini_client.get_voice_by_language(adventure.language) voice = self.gemini_client.get_voice_by_language(adventure.language)
wav_bytes = await self.gemini_client.generate_audio(story_text, voice) story_text_with_tag = "[like a dungeons and dragons gamemaster] " + story_text
wav_bytes = await self.gemini_client.generate_audio(story_text_with_tag, voice)
audio_key = f"adventure-audio/{entry_id}.wav" audio_key = f"adventure-audio/{entry_id}.wav"
upload_audio(audio_key, wav_bytes) upload_audio(audio_key, wav_bytes)
await self.audio_repo.create( await self.audio_repo.create(

View file

@ -35,7 +35,7 @@ def build_entry_system_prompt(
f"only \"-----\".\n" f"only \"-----\".\n"
f"- Part 1: the story entry, {min_length}{max_length} words, speaking directly to the player.\n" f"- Part 1: the story entry, {min_length}{max_length} words, speaking directly to the player.\n"
f"- Part 2: exactly 4 numbered player options, one per line, labelled \"1.\", \"2.\", \"3.\", \"4.\".\n" f"- Part 2: exactly 4 numbered player options, one per line, labelled \"1.\", \"2.\", \"3.\", \"4.\".\n"
f"- Part 3: GM notes to your future self (hidden from the player). " f"- Part 3: GM notes to your future self (hidden from the player). \n"
f"If no notes, write \"no notes\".\n" f"If no notes, write \"no notes\".\n"
f"- Your first message must establish: who the player is, the setting, and the broad direction.\n" f"- Your first message must establish: who the player is, the setting, and the broad direction.\n"
f"- No sexual content or graphic violence. Romance, threat, and adventure are fine (12-certificate)." f"- No sexual content or graphic violence. Romance, threat, and adventure are fine (12-certificate)."

View file

@ -1,3 +1,5 @@
import uuid
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import Response from fastapi.responses import Response
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -5,11 +7,37 @@ from botocore.exceptions import ClientError
from ..outbound.postgres.database import get_db from ..outbound.postgres.database import get_db
from ..outbound.postgres.repositories.translated_article_repository import TranslatedArticleRepository from ..outbound.postgres.repositories.translated_article_repository import TranslatedArticleRepository
from ..outbound.postgres.repositories.adventure_repository import PostgresAdventureEntryAudioRepository
from ..storage import download_audio from ..storage import download_audio
router = APIRouter(prefix="/media", tags=["media"]) router = APIRouter(prefix="/media", tags=["media"])
@router.get("/adventure-audio/{filename:path}")
async def get_adventure_audio_file(
filename: str,
db: AsyncSession = Depends(get_db),
) -> Response:
try:
eid = uuid.UUID(filename.rsplit(".", 1)[0])
except ValueError:
raise HTTPException(status_code=400, detail="Invalid file ID")
print(f"Looking for adventure audio with entry ID: {eid}")
adventure_audio = await PostgresAdventureEntryAudioRepository(db).get_for_entry(entry_id=eid, component_type="story_text")
if adventure_audio is None:
raise HTTPException(status_code=404, detail="File not found")
try:
audio_bytes, content_type = download_audio("adventure-audio/" + filename)
except ClientError as e:
if e.response["Error"]["Code"] in ("NoSuchKey", "404"):
raise HTTPException(status_code=404, detail="File not found")
raise HTTPException(status_code=500, detail="Storage error")
return Response(content=audio_bytes, media_type=content_type)
@router.get("/{filename:path}") @router.get("/{filename:path}")
async def get_media_file( async def get_media_file(
filename: str, filename: str,
@ -28,3 +56,4 @@ async def get_media_file(
raise HTTPException(status_code=500, detail="Storage error") raise HTTPException(status_code=500, detail="Storage error")
return Response(content=audio_bytes, media_type=content_type) return Response(content=audio_bytes, media_type=content_type)