diff --git a/app/main.py b/app/main.py index aa7ac4d..fce0485 100644 --- a/app/main.py +++ b/app/main.py @@ -261,7 +261,8 @@ worker_thread.start() class StartUpscaleRequest(BaseModel): - file_id: str + file_id: str | None = None + server_file_path: str | None = None model: str = "realesr-animevideov3" scale: int = 4 tile_size: int = 256 @@ -290,7 +291,9 @@ class StartUpscaleRequest(BaseModel): output_dir: str | None = None class StartBatchUpscaleRequest(BaseModel): - file_ids: List[str] + file_ids: List[str] | None = None + server_file_paths: List[str] | None = None + model: str = "realesr-animevideov3" scale: int = 4 tile_size: int = 256 @@ -319,7 +322,8 @@ class StartBatchUpscaleRequest(BaseModel): output_dir: str | None = None class PreviewRequest(BaseModel): - file_id: str + file_id: str | None = None + server_file_path: str | None = None timestamp_sec: float = 1.0 model: str = "realesr-animevideov3" scale: int = 4 @@ -327,6 +331,7 @@ class PreviewRequest(BaseModel): gpu_ids: str | None = None + # Endpoints UPLOAD_METADATA_FILE = os.path.join(upscaler.UPLOAD_DIR, "metadata.json") @@ -467,22 +472,31 @@ def start_upscale(req: StartUpscaleRequest): """Queue upscaling task""" # Find uploaded file file_path = None - exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] - for ext in exts: - test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}") - if os.path.exists(test_path): - file_path = test_path - break + if req.server_file_path: + if os.path.exists(req.server_file_path) and os.path.isfile(req.server_file_path): + file_path = req.server_file_path + else: + raise HTTPException(status_code=404, detail="Server file path not found.") + else: + if not req.file_id: + raise HTTPException(status_code=400, detail="Either file_id or server_file_path must be provided.") + exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] + for ext in exts: + test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}") + if os.path.exists(test_path): + file_path = test_path + break if not file_path: raise HTTPException(status_code=404, detail="Uploaded file not found.") metadata = load_upload_metadata() source_filename = None - if req.file_id in metadata: + if req.file_id and req.file_id in metadata: source_filename = metadata[req.file_id].get("original_filename") if not source_filename and file_path: source_filename = os.path.basename(file_path) + settings = load_settings() output_dir = req.output_dir or settings.get("default_output_dir") or None @@ -545,17 +559,27 @@ def start_upscale_batch(req: StartBatchUpscaleRequest): """Queue multiple upscaling tasks with one set of settings""" queued_jobs = [] - # 1. Validation loop - for file_id in req.file_ids: - file_path = None - exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] - for ext in exts: - test_path = os.path.join(upscaler.UPLOAD_DIR, f"{file_id}{ext}") - if os.path.exists(test_path): - file_path = test_path - break - if not file_path: - raise HTTPException(status_code=404, detail=f"Uploaded file {file_id} not found.") + file_sources = [] # list of (file_id, file_path) + if req.server_file_paths: + for path in req.server_file_paths: + if os.path.exists(path) and os.path.isfile(path): + file_sources.append((None, path)) + else: + raise HTTPException(status_code=404, detail=f"Server file {path} not found.") + else: + if not req.file_ids: + raise HTTPException(status_code=400, detail="Either file_ids or server_file_paths must be provided.") + for file_id in req.file_ids: + file_path = None + exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] + for ext in exts: + test_path = os.path.join(upscaler.UPLOAD_DIR, f"{file_id}{ext}") + if os.path.exists(test_path): + file_path = test_path + break + if not file_path: + raise HTTPException(status_code=404, detail=f"Uploaded file {file_id} not found.") + file_sources.append((file_id, file_path)) settings = load_settings() output_dir = req.output_dir or settings.get("default_output_dir") or None @@ -563,20 +587,14 @@ def start_upscale_batch(req: StartBatchUpscaleRequest): output_dir = output_dir.strip() # 2. Queueing loop - for file_id in req.file_ids: - file_path = None - for ext in [".mp4", ".mkv", ".avi", ".mov", ".webm"]: - test_path = os.path.join(upscaler.UPLOAD_DIR, f"{file_id}{ext}") - if os.path.exists(test_path): - file_path = test_path - break - + for file_id, file_path in file_sources: metadata = load_upload_metadata() source_filename = None - if file_id in metadata: + if file_id and file_id in metadata: source_filename = metadata[file_id].get("original_filename") if not source_filename and file_path: source_filename = os.path.basename(file_path) + job_id = str(uuid.uuid4()) job = upscaler.UpscaleJob( @@ -674,15 +692,24 @@ def cancel_job(job_id: str): async def generate_preview(req: PreviewRequest): """Generate high quality upscaled single frame preview""" file_path = None - exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] - for ext in exts: - test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}") - if os.path.exists(test_path): - file_path = test_path - break + if req.server_file_path: + if os.path.exists(req.server_file_path) and os.path.isfile(req.server_file_path): + file_path = req.server_file_path + else: + raise HTTPException(status_code=404, detail="Server file path not found.") + else: + if not req.file_id: + raise HTTPException(status_code=400, detail="Either file_id or server_file_path must be provided.") + exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"] + for ext in exts: + test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}") + if os.path.exists(test_path): + file_path = test_path + break if not file_path: raise HTTPException(status_code=404, detail="Video file not found.") + preview_id = str(uuid.uuid4()) preview_temp_dir = os.path.join(upscaler.TEMP_DIR, f"preview_{preview_id}") @@ -1414,7 +1441,7 @@ class CreateDirRequest(BaseModel): name: str @app.get("/api/system/browse-dir") -def browse_dir(path: str = ""): +def browse_dir(path: str = "", show_files: bool = False): """Browse directories on the system""" if not path: path = os.path.expanduser("~") @@ -1438,6 +1465,8 @@ def browse_dir(path: str = ""): "is_dir": True }) + video_extensions = {'.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv', '.m4v', '.wmv', '.mpg', '.mpeg'} + for name in sorted(os.listdir(path)): full_path = os.path.join(path, name) try: @@ -1447,6 +1476,14 @@ def browse_dir(path: str = ""): "path": full_path, "is_dir": True }) + elif show_files: + _, ext = os.path.splitext(name.lower()) + if ext in video_extensions: + entries.append({ + "name": name, + "path": full_path, + "is_dir": False + }) except PermissionError: continue @@ -1457,6 +1494,25 @@ def browse_dir(path: str = ""): except Exception as e: raise HTTPException(status_code=500, detail=str(e)) +@app.get("/api/system/file-metadata") +def get_file_metadata(path: str): + """Get video metadata for a local server file""" + if not os.path.exists(path) or not os.path.isfile(path): + raise HTTPException(status_code=404, detail="File not found.") + + info = upscaler.get_video_info(path) + if not info: + raise HTTPException(status_code=400, detail="Could not read video file metadata.") + + return { + "original_filename": os.path.basename(path), + "path": path, + "size_bytes": os.path.getsize(path), + "metadata": info + } + + + @app.post("/api/system/create-dir") def create_dir(req: CreateDirRequest): """Create a new directory""" diff --git a/static/app.js b/static/app.js index ffe43f0..29f2416 100644 --- a/static/app.js +++ b/static/app.js @@ -595,7 +595,7 @@ window.addEventListener("mousemove", (e) => { // Generate single frame preview trigger generatePreviewBtn.addEventListener("click", async () => { - if (!uploadFileId) return; + if (!uploadFileId && !window.selectedServerFilePath) return; const loaderTitle = document.getElementById("preview-loader-title"); const loaderContainer = document.getElementById("preview-loader-progress-container"); @@ -618,7 +618,8 @@ generatePreviewBtn.addEventListener("click", async () => { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - file_id: uploadFileId, + file_id: uploadFileId || null, + server_file_path: window.selectedServerFilePath || null, timestamp_sec: parseFloat(timelineSlider.value), model: modelSelect.value, scale: parseInt(scaleRange.value), @@ -801,7 +802,7 @@ function initComparisonSlider() { 6. Video Pipeline Jobs Orchestration ---------------------------------------------------- */ startUpscaleBtn.addEventListener("click", async () => { - if (!uploadFileId) return; + if (!uploadFileId && !window.selectedServerFilePath && (!window.selectedServerFilePaths || window.selectedServerFilePaths.length === 0)) return; startUpscaleBtn.disabled = true; startUpscaleBtn.innerHTML = `
Starting Job...`; @@ -821,12 +822,13 @@ startUpscaleBtn.addEventListener("click", async () => { const aiAudioDenoise = document.getElementById("ai-audio-denoise"); try { - if (window.isBatchMode && window.batchFileIds && window.batchFileIds.length > 0) { + if (window.isBatchMode && ((window.batchFileIds && window.batchFileIds.length > 0) || (window.selectedServerFilePaths && window.selectedServerFilePaths.length > 0))) { const res = await fetch("/api/upscale/start/batch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - file_ids: window.batchFileIds, + file_ids: (window.batchFileIds && window.batchFileIds.length > 0) ? window.batchFileIds : null, + server_file_paths: (window.selectedServerFilePaths && window.selectedServerFilePaths.length > 0) ? window.selectedServerFilePaths : null, model: modelSelect.value, scale: parseInt(scaleRange.value), tile_size: parseInt(tileRange.value), @@ -857,6 +859,7 @@ startUpscaleBtn.addEventListener("click", async () => { window.isBatchMode = false; window.batchFileIds = []; + window.selectedServerFilePaths = null; document.querySelectorAll(".batch-select-checkbox").forEach(cb => cb.checked = false); const batchConfigBar = document.getElementById("batch-config-bar"); @@ -875,7 +878,8 @@ startUpscaleBtn.addEventListener("click", async () => { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - file_id: uploadFileId, + file_id: uploadFileId || null, + server_file_path: window.selectedServerFilePath || null, model: modelSelect.value, scale: parseInt(scaleRange.value), tile_size: parseInt(tileRange.value), @@ -2077,6 +2081,8 @@ async function initializeApp() { document.querySelectorAll(".step-container").forEach(el => el.classList.remove("active")); document.getElementById("step-upload").classList.add("active"); uploadFileId = null; + window.selectedServerFilePath = null; + window.selectedServerFilePaths = null; loadUploadsBrowser(); }); } @@ -3052,3 +3058,258 @@ if (reimportForm) { } initializeApp(); + +/* ---------------------------------------------------- + 7. Server File Browser logic + ---------------------------------------------------- */ +let selectedServerFiles = []; +let fileBrowserCurrentPath = ""; + +window.selectedServerFilePath = null; +window.selectedServerFilePaths = null; + +function openServerFileBrowser() { + const modal = document.getElementById("file-browser-modal"); + if (!modal) return; + + selectedServerFiles = []; + document.getElementById("file-browser-selected-count").textContent = "0"; + document.getElementById("file-browser-select-btn").disabled = true; + document.getElementById("file-browser-modal-status").textContent = ""; + + modal.style.display = "flex"; + document.body.style.overflow = "hidden"; + + loadFileBrowserDir(""); +} + +function closeServerFileBrowser() { + const modal = document.getElementById("file-browser-modal"); + if (modal) { + modal.style.display = "none"; + document.body.style.overflow = ""; + } +} + +async function loadFileBrowserDir(path) { + const listContainer = document.getElementById("file-browser-list"); + const currentPathInput = document.getElementById("file-browser-current-path"); + const statusDiv = document.getElementById("file-browser-modal-status"); + if (!listContainer) return; + + listContainer.innerHTML = 'Supports MP4, MKV, AVI, MOV, WEBM (Max 500MB recommended for local upscaling)
- +Supports MP4, MKV, AVI, MOV, WEBM
+