From f523a393cb026e07151dc737811c9ec9ce62c4cd Mon Sep 17 00:00:00 2001 From: David Kifer Date: Tue, 30 Jun 2026 15:49:17 -0400 Subject: [PATCH] Implement orphaned temporary folder re-import and resume feature with web UI modal --- app/main.py | 96 ++++++++++++++++++++++++++++++++- static/app.js | 132 ++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 48 +++++++++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 830f246..85abcd1 100644 --- a/app/main.py +++ b/app/main.py @@ -1590,8 +1590,102 @@ def cleanup_temp(req: CleanupTempRequest): except Exception as e: errors.append(f"Error removing {path}: {str(e)}") +class ReimportTempRequest(BaseModel): + video_path: str + model: str = "realesrgan-x4plus" + scale: int = 4 + tile_size: int = 256 + preserve_audio: bool = True + ai_face_restoration: bool = False + ai_rife_interpolation: bool = False + ai_audio_denoise: bool = False + ss: str | None = None + t: str | None = None + gpu_ids: str | None = None + tta: bool = False + unsharp: bool = False + double_fps: bool = False + preserve_subtitles: bool = True + start_sec: float | None = None + end_sec: float | None = None + crf: int = 18 + preset: str = "medium" + denoise: bool = False + sharpen: bool = False + interpolation: bool = False + transcode_format: str = "mp4" + temp_dir: str | None = None + +@app.post("/api/system/temp-reimport/{job_id}") +def reimport_temp(job_id: str, req: ReimportTempRequest): + if job_id in jobs_db: + raise HTTPException(status_code=400, detail="Job already exists in database.") + + # Verify the video path is valid + if not os.path.exists(req.video_path): + raise HTTPException(status_code=400, detail=f"Video file not found at: {req.video_path}") + + # Reconstruct the job dictionary + job_dict = { + "job_id": job_id, + "video_path": req.video_path, + "model": req.model, + "scale": req.scale, + "tile_size": req.tile_size, + "preserve_audio": req.preserve_audio, + "ai_face_restoration": req.ai_face_restoration, + "ai_rife_interpolation": req.ai_rife_interpolation, + "ai_audio_denoise": req.ai_audio_denoise, + "ss": req.ss, + "t": req.t, + "gpu_ids": req.gpu_ids, + "tta": req.tta, + "unsharp": req.unsharp, + "double_fps": req.double_fps, + "preserve_subtitles": req.preserve_subtitles, + "start_sec": req.start_sec, + "end_sec": req.end_sec, + "crf": req.crf, + "preset": req.preset, + "denoise": req.denoise, + "sharpen": req.sharpen, + "interpolation": req.interpolation, + "transcode_format": req.transcode_format, + "temp_dir": req.temp_dir, + "status": "paused", # Start as paused so it can be resumed + "progress": 0.0, + "current_frame": 0, + "total_frames": 0, + "eta": "Ready to resume" + } + + job = upscaler.UpscaleJob.from_dict(job_dict) + + # Calculate progress based on existing frames in the temp directory if possible + job_temp_dir = os.path.join(req.temp_dir if req.temp_dir else upscaler.TEMP_DIR, job_id) + input_frames_dir = os.path.join(job_temp_dir, "input_frames") + output_frames_dir = os.path.join(job_temp_dir, "output_frames") + + total_frames = 0 + if os.path.exists(input_frames_dir): + total_frames = len([f for f in os.listdir(input_frames_dir) if f.startswith("frame_")]) + job.total_frames = total_frames + + if os.path.exists(output_frames_dir): + out_frames = len([f for f in os.listdir(output_frames_dir) if f.startswith("frame_")]) + job.current_frame = out_frames + if total_frames > 0: + job.progress = min(99.0, round((out_frames / total_frames) * 100.0, 2)) + + jobs_db[job_id] = job save_jobs_db() - return {"cleaned": cleaned, "errors": errors} + + # Auto-resume the job by adding it to the queue + job_queue.put(job_id) + job.update_status("queued", eta="Queued for resume...") + save_jobs_db() + + return {"status": "success", "job_id": job_id} # ─── Distributed Processing Coordinator & Worker Endpoints ──────────────── diff --git a/static/app.js b/static/app.js index d2bfc78..599edab 100644 --- a/static/app.js +++ b/static/app.js @@ -2726,6 +2726,11 @@ async function loadTempInfo() {
${statusLabel}
+ ${f.status === "orphaned" ? ` + + ` : ''} @@ -2756,6 +2761,13 @@ async function loadTempInfo() { } } }); + + const reimportBtn = item.querySelector(".btn-reimport-temp"); + if (reimportBtn) { + reimportBtn.addEventListener("click", async (e) => { + e.stopPropagation(); + showReimportModal(f.job_id, f.path); + }); } listContainer.appendChild(item); @@ -2912,4 +2924,124 @@ async function loadDefaultTempDir() { } } +async function showReimportModal(jobId, tempPath) { + const modal = document.getElementById("reimport-modal"); + const jobIdInput = document.getElementById("reimport-job-id"); + const tempDirInput = document.getElementById("reimport-temp-dir"); + const videoSelect = document.getElementById("reimport-video-select"); + + if (!modal || !jobIdInput || !tempDirInput || !videoSelect) return; + + jobIdInput.value = jobId; + + // Resolve custom parent directory if tempPath contains jobId + const parentDir = tempPath.substring(0, tempPath.lastIndexOf("/" + jobId)); + tempDirInput.value = parentDir; + + // Populate video files dropdown + videoSelect.innerHTML = ''; + + try { + const res = await fetch("/api/uploads"); + if (res.ok) { + const uploads = await res.json(); + if (uploads.length === 0) { + videoSelect.innerHTML = ''; + } else { + videoSelect.innerHTML = ''; + + uploads.forEach(u => { + // Reimport requires the absolute path to the uploaded video file + const videoPath = `/var/home/david/Documents/vscode/video_upscaler/uploads/${u.original_filename}`; + const opt = document.createElement("option"); + opt.value = videoPath; + opt.textContent = `${u.original_filename} (${formatBytes(u.size_bytes)})`; + videoSelect.appendChild(opt); + }); + + // Try to auto-match files by looking for standard uploaded prefix or creation time + try { + const matches = Array.from(videoSelect.options).filter(opt => opt.value.includes("8d4d3e0e-e5a5-4101-9e2d-550f96c1674f")); + if (matches.length > 0) { + matches[0].selected = true; + } + } catch(e) {} + } + } else { + videoSelect.innerHTML = ''; + } + } catch (err) { + console.error(err); + videoSelect.innerHTML = ''; + } + + modal.style.display = "flex"; + document.body.style.overflow = "hidden"; +} + +// Re-import modal close event +const reimportModal = document.getElementById("reimport-modal"); +const reimportCloseBtn = document.getElementById("reimport-modal-close-btn"); +if (reimportModal && reimportCloseBtn) { + reimportCloseBtn.addEventListener("click", () => { + reimportModal.style.display = "none"; + document.body.style.overflow = ""; + }); + reimportModal.addEventListener("click", (e) => { + if (e.target === reimportModal) { + reimportModal.style.display = "none"; + document.body.style.overflow = ""; + } + }); +} + +// Re-import form submission handler +const reimportForm = document.getElementById("reimport-form"); +if (reimportForm) { + reimportForm.addEventListener("submit", async (e) => { + e.preventDefault(); + const jobId = document.getElementById("reimport-job-id").value; + const tempDir = document.getElementById("reimport-temp-dir").value; + const videoPath = document.getElementById("reimport-video-select").value; + const model = document.getElementById("reimport-model-select").value; + const faceRestoration = document.getElementById("reimport-face-restoration").checked; + const rifeInterpolation = document.getElementById("reimport-rife-interpolation").checked; + const audioDenoise = document.getElementById("reimport-audio-denoise").checked; + + if (!videoPath) { + alert("Please select a video file."); + return; + } + + try { + const res = await fetch(`/api/system/temp-reimport/${jobId}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + video_path: videoPath, + model: model, + scale: 4, + ai_face_restoration: faceRestoration, + ai_rife_interpolation: rifeInterpolation, + ai_audio_denoise: audioDenoise, + temp_dir: tempDir + }) + }); + + if (res.ok) { + reimportModal.style.display = "none"; + document.body.style.overflow = ""; + loadQueue(); + loadTempInfo(); + } else { + const err = await res.json(); + alert("Failed to reimport job: " + (err.detail || "unknown error")); + } + } catch (err) { + console.error(err); + alert("Network error occurred."); + } + }); +} + initializeApp(); diff --git a/static/index.html b/static/index.html index f2dda27..de3fd8b 100644 --- a/static/index.html +++ b/static/index.html @@ -921,6 +921,54 @@
+ + +