import os from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, HTTPException, BackgroundTasks from fastapi.responses import FileResponse from pydantic import BaseModel from app import downloader STATIC_DIR = Path(__file__).parent / "static" AUDIO_TMP_DIR = Path("/tmp/apoena-audio") @asynccontextmanager async def lifespan(app: FastAPI): AUDIO_TMP_DIR.mkdir(parents=True, exist_ok=True) yield app = FastAPI(title="apoena-transcript", lifespan=lifespan) class ExtractAudioRequest(BaseModel): url: str @app.get("/health") async def health(): return {"status": "ok"} @app.get("/worker.js") async def worker_js(): return FileResponse(STATIC_DIR / "worker.js", media_type="application/javascript") @app.get("/sw.js") async def sw_js(): return FileResponse(STATIC_DIR / "sw.js", media_type="application/javascript") @app.get("/manifest.json") async def manifest(): return FileResponse(STATIC_DIR / "manifest.json", media_type="application/manifest+json") @app.get("/icon.svg") async def icon(): return FileResponse(STATIC_DIR / "icon.svg", media_type="image/svg+xml") @app.get("/icon-maskable.svg") async def icon_maskable(): return FileResponse(STATIC_DIR / "icon-maskable.svg", media_type="image/svg+xml") @app.post("/extract-audio") async def extract_audio(body: ExtractAudioRequest, background_tasks: BackgroundTasks): try: audio_path = await downloader.extract_audio(body.url) except RuntimeError as e: raise HTTPException(status_code=422, detail=str(e)) background_tasks.add_task(_delete_file, audio_path) return FileResponse(audio_path, media_type="audio/mpeg", filename="audio.mp3") def _delete_file(path): try: os.unlink(path) except OSError: pass @app.get("/") async def index(): return FileResponse(STATIC_DIR / "index.html")