diff --git a/docs/upscaler/index.html b/docs/upscaler/index.html index 4baae5d46..1fa73ec05 100644 --- a/docs/upscaler/index.html +++ b/docs/upscaler/index.html @@ -125,12 +125,22 @@ type="file" id="universalInput" accept="video/*" + multiple class="sr-only" /> - +
+ Usage Guide +
++ Tip: + Folder upload and directory save + require Chrome or Edge. Processing + happens locally — your videos never + leave your device. +
++ Results auto-saved to this folder +
++ Click a result to load it into the + comparison viewer. +
+| + Metric + | ++ Original + | ++ Upscaled + | ++ Change + | +
|---|
${escapeHtml(file.name)}
+${formatBytes(file.size)}${dirName ? ` · ${escapeHtml(dirName)}` : ""}
+${escapeHtml(st.error)}
` : ""} +${escapeHtml(error)}
`; + errorBlock.classList.remove("hidden"); + } else { + errorBlock.classList.add("hidden"); + errorBlock.innerHTML = ""; + } + } + } + + // Retry a single failed video + async function retryBatchItem(index) { + const file = currentVideoFiles[index]; + if (!file) return; + if (isBatchProcessing) return; + + const processBtn = document.getElementById("processBtn"); + processBtn.disabled = true; + isBatchProcessing = true; + + const networkToggle = document.getElementById("networkToggle"); + const activeBtn = networkToggle?.querySelector( + "button.bg-slate-800[data-network]", + ); + const networkSize = activeBtn?.dataset.network || "medium"; + + updateBatchItemStatus(index, "processing", "Retrying"); + + // Update preview to show the file being retried + showVideoPreview(file); + + const retryStartTime = Date.now(); + + upscaler.onProgress((progress) => { + document.getElementById( + "progressFill", + ).style.width = progress + "%"; + document.getElementById( + "progressText", + ).textContent = `[Retry] ${Math.round(progress)}% — ${truncateName(file.name)}`; + }); + + try { + const resultBlob = await upscaler.upscaleVideo( + file, + networkSize, + ); + + const saved = await writeToSaveDirectory(file, resultBlob); + updateBatchItemStatus(index, "done", saved ? "Saved" : "Done"); + + const retryDuration = Date.now() - retryStartTime; + addBatchResult(file, resultBlob, index, retryDuration); + + // Load the retried result into comparison viewer + const resultIdx = batchResults.findIndex( + (r) => r.index === index, + ); + if (resultIdx >= 0) { + loadResultIntoComparison(resultIdx); + } + } catch (error) { + console.error(`Retry failed for ${file.name}:`, error); + const errDetail = error.message + ? error.message.substring(0, 120) + : String(error).substring(0, 120); + updateBatchItemStatus(index, "error", "Failed", errDetail); + } finally { + isBatchProcessing = false; + processBtn.disabled = false; + } + } + + // Add a completed result to the right-side comparison list + function addBatchResult(file, blob, index, durationMs) { + const result = { + file, + blob, + index, + durationMs, + enhancedUrl: URL.createObjectURL(blob), + originalUrl: URL.createObjectURL(file), + }; + + // Replace existing entry (e.g. retry), revoking old URLs + const existing = batchResults.findIndex( + (r) => r.index === index, + ); + if (existing >= 0) { + URL.revokeObjectURL(batchResults[existing].enhancedUrl); + URL.revokeObjectURL(batchResults[existing].originalUrl); + batchResults[existing] = result; + } else { + batchResults.push(result); + } + renderBatchResultList(); + } + + // Render the right-side result list + function renderBatchResultList() { + const panel = document.getElementById("batchResultPanel"); + const container = document.getElementById( + "batchResultItems", + ); + const countLabel = document.getElementById( + "batchResultCount", + ); + if (!panel || !container) return; + + if (batchResults.length === 0) { + panel.classList.add("hidden"); + return; + } + + panel.classList.remove("hidden"); + countLabel.textContent = `${batchResults.length} done`; + + container.innerHTML = batchResults + .map((r, i) => { + const isActive = i === selectedResultIndex; + return ` + `; + }) + .join(""); + } + + // Render the video data comparison table + function renderComparisonData(result, orig, enh) { + const panel = document.getElementById( + "comparisonDataPanel", + ); + const body = document.getElementById("comparisonDataBody"); + if (!panel || !body) return; + + const origBitrate = + orig.duration > 0 + ? (orig.size * 8) / orig.duration / 1e6 + : 0; + const enhBitrate = + enh.duration > 0 + ? (enh.size * 8) / enh.duration / 1e6 + : 0; + const origPixels = orig.width * orig.height; + const enhPixels = enh.width * enh.height; + + const rows = [ + { + label: "Resolution", + orig: `${orig.width}×${orig.height}`, + enh: `${enh.width}×${enh.height}`, + change: `${((enhPixels / origPixels - 1) * 100).toFixed(0)}%`, + positive: enhPixels > origPixels, + }, + { + label: "File Size", + orig: formatBytes(orig.size), + enh: formatBytes(enh.size), + change: `${((enh.size / orig.size - 1) * 100).toFixed(0)}%`, + positive: null, + }, + { + label: "Duration", + orig: formatVideoDuration(orig.duration), + enh: formatVideoDuration(enh.duration), + change: + Math.abs(orig.duration - enh.duration) < 0.5 + ? "same" + : `${((enh.duration / orig.duration - 1) * 100).toFixed(1)}%`, + positive: null, + }, + { + label: "Est. Bitrate", + orig: origBitrate.toFixed(1) + " Mbps", + enh: enhBitrate.toFixed(1) + " Mbps", + change: `${((enhBitrate / origBitrate - 1) * 100).toFixed(0)}%`, + positive: enhBitrate > origBitrate, + }, + { + label: "Pixels", + orig: origPixels.toLocaleString(), + enh: enhPixels.toLocaleString(), + change: `${((enhPixels / origPixels - 1) * 100).toFixed(0)}%`, + positive: enhPixels > origPixels, + }, + { + label: "Process Time", + orig: "—", + enh: formatDuration(result.durationMs), + change: "—", + positive: null, + }, + ]; + + body.innerHTML = rows + .map((row) => { + let changeClass = "text-slate-400"; + let changeIcon = ""; + if (row.positive === true) { + changeClass = "text-emerald-600 font-medium"; + changeIcon = "↑ "; + } else if (row.positive === false) { + changeClass = "text-red-500"; + changeIcon = "↓ "; + } + return ` +