subfox/app/main.py

157 lines
4.2 KiB
Python

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,
)