Make SubFox production-ready with parallel translation and UI controls
This commit is contained in:
parent
c40b8bed2b
commit
2b1d05f02c
6046 changed files with 798327 additions and 0 deletions
157
app/main.py
Normal file
157
app/main.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue