from __future__ import annotations import threading import uuid from pathlib import Path from fastapi import FastAPI, UploadFile, File, Form, Request from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from fastapi.templating import Jinja2Templates from app.services.job_store import jobs from app.services.subtitle_service import translate_srt_content, parse_srt app = FastAPI() templates = Jinja2Templates(directory="app/templates") OUTPUT_DIR = Path("data/output") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) @app.get("/", response_class=HTMLResponse) async def home(request: Request): return templates.TemplateResponse( request=request, name="index.html", context={}, ) @app.post("/start") async def start_translation( file: UploadFile = File(...), mode: str = Form("fast"), source_lang: str = Form("auto"), target_lang: str = Form("da"), model: str = Form("gpt-4o-mini"), workers: int = Form(4), ): try: raw = await file.read() content = raw.decode("utf-8-sig") except UnicodeDecodeError: return JSONResponse( {"error": "Kunne ikke læse filen som UTF-8/UTF-8-SIG"}, status_code=400, ) if workers < 1: workers = 1 if workers > 16: workers = 16 job_id = str(uuid.uuid4()) output_path = OUTPUT_DIR / f"{job_id}.srt" try: parsed_blocks = parse_srt(content) block_count = len(parsed_blocks) except Exception: block_count = 0 jobs[job_id] = { "status": "queued", "progress": 0, "filename": file.filename or "translated.srt", "output_path": str(output_path), "blocks": block_count, "error": "", "done": False, "mode": mode, "source_lang": source_lang, "target_lang": target_lang, "model": model, "workers": workers, } def progress_callback(done_blocks: int, total_blocks: int): if total_blocks <= 0: jobs[job_id]["progress"] = 1 return percent = int((done_blocks / total_blocks) * 100) if done_blocks > 0: percent = max(2, percent) percent = min(99, percent) jobs[job_id]["progress"] = percent jobs[job_id]["status"] = "running" jobs[job_id]["blocks"] = total_blocks def worker(): try: jobs[job_id]["status"] = "starting" jobs[job_id]["progress"] = 1 translated_srt = translate_srt_content( content=content, mode=mode, source_lang=source_lang, target_lang=target_lang, job_id=job_id, progress_callback=progress_callback, model=model, workers=workers, ) output_path.write_text(translated_srt, encoding="utf-8") jobs[job_id]["status"] = "done" jobs[job_id]["progress"] = 100 jobs[job_id]["done"] = True except Exception as e: jobs[job_id]["status"] = "error" jobs[job_id]["progress"] = 0 jobs[job_id]["error"] = str(e) jobs[job_id]["done"] = False threading.Thread(target=worker, daemon=True).start() return JSONResponse({"job_id": job_id}) @app.get("/status/{job_id}") async def get_status(job_id: str): job = jobs.get(job_id) if not job: return JSONResponse( {"status": "unknown", "progress": 0, "error": "Job not found"}, status_code=404, ) return JSONResponse(job) @app.get("/download/{job_id}") async def download_result(job_id: str): job = jobs.get(job_id) if not job: return JSONResponse({"error": "Job not found"}, status_code=404) if not job.get("done"): return JSONResponse({"error": "File not ready yet"}, status_code=400) path = Path(job["output_path"]) if not path.exists(): return JSONResponse({"error": "Output file missing"}, status_code=404) original_name = job.get("filename", "translated.srt") download_name = f"translated_{original_name}" return FileResponse( path=path, media_type="application/x-subrip", filename=download_name, )