130 lines
4.6 KiB
Python
130 lines
4.6 KiB
Python
"""Route handlers — wires the HTTP layer to jobs/validation/assembler."""
|
|
import io
|
|
import uuid
|
|
import zipfile
|
|
|
|
from flask import abort, redirect, render_template, request, send_file, url_for
|
|
|
|
from . import assembler, jobs, validation
|
|
|
|
|
|
def _process_pair(job_dir, index, sdr_file, hdr_file):
|
|
"""Runs one SDR/HDR pair through validation + assembly. Returns an item
|
|
dict for the job report; never raises — failures are recorded in it."""
|
|
name = hdr_file.filename or sdr_file.filename or f"pair {index}"
|
|
|
|
if not sdr_file.filename or not hdr_file.filename:
|
|
return {"name": name, "success": False, "warning": False, "output": None,
|
|
"log": "Missing SDR or HDR file for this pair."}
|
|
|
|
ext_error = validation.sdr_ext_error(sdr_file.filename) or validation.hdr_ext_error(hdr_file.filename)
|
|
if ext_error:
|
|
return {"name": name, "success": False, "warning": False, "output": None, "log": ext_error}
|
|
|
|
sdr_path = job_dir / f"sdr_{index}{validation.ext(sdr_file.filename)}"
|
|
hdr_path = job_dir / f"hdr_{index}{validation.ext(hdr_file.filename)}"
|
|
out_path = job_dir / f"instagram_hdr_output_{index}.jpg"
|
|
|
|
sdr_file.save(sdr_path)
|
|
hdr_file.save(hdr_path)
|
|
|
|
ig_error = validation.instagram_resolution_error(sdr_path, hdr_path)
|
|
if ig_error:
|
|
for p in (sdr_path, hdr_path):
|
|
p.unlink(missing_ok=True)
|
|
return {"name": name, "success": False, "warning": False, "output": None, "log": ig_error}
|
|
|
|
success, log, warning = assembler.run(sdr_path, hdr_path, out_path)
|
|
for p in (sdr_path, hdr_path):
|
|
p.unlink(missing_ok=True)
|
|
|
|
return {
|
|
"name": name,
|
|
"success": success,
|
|
"warning": warning,
|
|
"output": str(out_path) if success else None,
|
|
"log": log,
|
|
}
|
|
|
|
|
|
def register_routes(app):
|
|
@app.get("/")
|
|
def index():
|
|
return render_template("index.html")
|
|
|
|
@app.post("/convert")
|
|
def convert():
|
|
sdr_files = request.files.getlist("sdr[]")
|
|
hdr_files = request.files.getlist("hdr[]")
|
|
|
|
if not sdr_files or not hdr_files:
|
|
return render_template("index.html", error="Add at least one SDR/HDR pair."), 400
|
|
if len(sdr_files) != len(hdr_files):
|
|
return render_template("index.html", error="Each pair needs both an SDR and an HDR file."), 400
|
|
|
|
job_id = uuid.uuid4().hex[:12]
|
|
job_dir = jobs.new_job_dir(job_id)
|
|
|
|
items = [
|
|
_process_pair(job_dir, i, sdr_file, hdr_file)
|
|
for i, (sdr_file, hdr_file) in enumerate(zip(sdr_files, hdr_files), start=1)
|
|
]
|
|
|
|
jobs.register(job_id, items=items)
|
|
return redirect(url_for("result", job_id=job_id))
|
|
|
|
@app.get("/result/<job_id>")
|
|
def result(job_id):
|
|
job = jobs.get(job_id)
|
|
if not job:
|
|
abort(404)
|
|
items = job["items"]
|
|
ok_count = sum(1 for item in items if item["success"])
|
|
total_count = len(items)
|
|
if ok_count == total_count and total_count == 1:
|
|
heading = "JPEG assembled"
|
|
elif ok_count == total_count:
|
|
heading = f"All {total_count} assembled"
|
|
elif ok_count == 0:
|
|
heading = "Conversion failed"
|
|
else:
|
|
heading = f"{ok_count}/{total_count} assembled"
|
|
return render_template(
|
|
"result.html",
|
|
job_id=job_id,
|
|
items=items,
|
|
ok_count=ok_count,
|
|
total_count=total_count,
|
|
heading=heading,
|
|
)
|
|
|
|
@app.get("/download/<job_id>")
|
|
def download(job_id):
|
|
job = jobs.get(job_id)
|
|
if not job:
|
|
abort(404)
|
|
successes = [item for item in job["items"] if item["success"] and item["output"]]
|
|
if not successes:
|
|
abort(404)
|
|
|
|
if len(successes) == 1:
|
|
return send_file(successes[0]["output"], as_attachment=True, download_name="instagram_hdr_output.jpg")
|
|
|
|
buf = io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
for i, item in enumerate(successes, start=1):
|
|
zf.write(item["output"], arcname=f"instagram_hdr_output_{i}.jpg")
|
|
buf.seek(0)
|
|
return send_file(buf, as_attachment=True, download_name="instagram_hdr_outputs.zip", mimetype="application/zip")
|
|
|
|
@app.post("/heartbeat/<job_id>")
|
|
def heartbeat(job_id):
|
|
# Pinged by the result page while it's open/visible. Silently
|
|
# ignored for unknown/already-reaped job_ids — nothing to keep alive.
|
|
jobs.touch(job_id)
|
|
return ("", 204)
|
|
|
|
@app.errorhandler(413)
|
|
def too_large(_e):
|
|
return render_template("index.html", error="File too large (max 80 MB)."), 413
|