diff --git a/backend/server.py b/backend/server.py index 03fbefe..c7e3118 100644 --- a/backend/server.py +++ b/backend/server.py @@ -707,6 +707,16 @@ async def retry_job(job_id: str, user: dict = Depends(require_admin)): return {"ok": True, "job_id": new_job["id"]} +@api.post("/transcode/queue/retry-failed") +async def retry_all_failed(user: dict = Depends(require_admin)): + jobs = await db.transcode_queue.find({"status": {"$in": ["failed", "cancelled"]}}, {"_id": 0}).to_list(500) + retried = 0 + for job in jobs: + await _enqueue_transcode(job["movie_id"], job["quality"], triggered_by=job.get("triggered_by", "manual"), content_type=job.get("content_type", "movie")) + retried += 1 + return {"ok": True, "retried": retried} + + @api.post("/transcode/queue/clear") async def clear_finished(user: dict = Depends(require_admin)): res = await db.transcode_queue.delete_many({"status": {"$in": ["done", "failed", "cancelled"]}}) diff --git a/backend/transcode.py b/backend/transcode.py index e496ec6..5e2059f 100644 --- a/backend/transcode.py +++ b/backend/transcode.py @@ -13,6 +13,11 @@ from typing import Optional, List, Tuple, Callable, Awaitable logger = logging.getLogger("kino.transcode") +# Moderate priority (was 19, the absolute lowest) and a thread cap so an ABR encode can't +# starve the rest of the stack (Mongo, *arr apps, the API itself) on this 8-core host. +TRANSCODE_NICE = 10 +TRANSCODE_THREADS = 6 + async def probe_video(source: Path) -> Tuple[int, int]: """Return (width, height) using ffprobe; (0, 0) on failure.""" @@ -58,7 +63,7 @@ async def transcode_quick(source: Path, out_dir: Path, on_status: Callable[..., return out_dir.mkdir(parents=True, exist_ok=True) cmd = [ - "nice", "-n", "19", + "nice", "-n", str(TRANSCODE_NICE), "ffmpeg", "-y", "-i", str(source), "-c:v", "copy", "-c:a", "copy", "-bsf:v", "h264_mp4toannexb", @@ -88,13 +93,14 @@ async def transcode_abr(source: Path, out_dir: Path, on_status: Callable[..., Aw fc_parts.append(f"[v{i}]scale=w=-2:h={h}[v{i}out]") filter_complex = ";".join(fc_parts) - cmd: List[str] = ["nice", "-n", "19", "ffmpeg", "-y", "-i", str(source), "-filter_complex", filter_complex] + cmd: List[str] = ["nice", "-n", str(TRANSCODE_NICE), "ffmpeg", "-y", "-threads", str(TRANSCODE_THREADS), "-i", str(source), "-filter_complex", filter_complex] for i, (_h, vb, maxr, buf, _ab) in enumerate(variants): cmd += [ "-map", f"[v{i}out]", f"-c:v:{i}", "libx264", f"-preset:v:{i}", "veryfast", + f"-threads:v:{i}", str(TRANSCODE_THREADS), f"-profile:v:{i}", "main", f"-pix_fmt:v:{i}", "yuv420p", f"-b:v:{i}", vb, diff --git a/frontend/src/pages/TranscodeQueue.jsx b/frontend/src/pages/TranscodeQueue.jsx index a9128ef..d5c7207 100644 --- a/frontend/src/pages/TranscodeQueue.jsx +++ b/frontend/src/pages/TranscodeQueue.jsx @@ -44,6 +44,14 @@ export default function TranscodeQueue() { catch (err) { toast.error(err.response?.data?.detail || "Could not retry"); } }; + const retryAllFailed = async () => { + try { + const { data } = await api.post("/transcode/queue/retry-failed"); + toast.success(`Re-queued ${data.retried} job(s)`); + load(); + } catch (err) { toast.error(err.response?.data?.detail || "Could not retry"); } + }; + const togglePause = async () => { try { const { data } = await api.post("/transcode/queue/pause", { paused: !stats.paused }); @@ -64,9 +72,9 @@ export default function TranscodeQueue() { Background
- One job runs at a time at low CPU priority. Auto-transcode is currently:{" "} + One job runs at a time, moderate CPU priority, capped threads so it can't starve the rest of the stack. Auto-transcode is currently:{" "} {stats.auto_transcode === "off" ? "off" : `auto ยท ${stats.auto_transcode}`} - {" โ "}change in Settings + {" โ "}change in Settings. Failed and cancelled jobs never delete the movie โ retry re-queues the same file.