From 862b864fceca22746a0037859c3ee6653af80985 Mon Sep 17 00:00:00 2001 From: gator Date: Mon, 7 Sep 2026 23:15:09 +0000 Subject: [PATCH] Show earlier takes, and let one be brought back Filming again used to overwrite the video outright. Models drift between generations, so re-filming to fix one thing can make another worse, and there was no way back. Every take is now kept and listed under the cut, newest first, with what it cost and which voice it used. Restoring one keeps the take you are leaving, so going back is reversible too. This deliberately lands before the "remake this shot" button, because that is what will make people re-film often enough to need it. --- script.html | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/script.html b/script.html index c4f8870..e117965 100644 --- a/script.html +++ b/script.html @@ -295,6 +295,7 @@ function render(r) { + '' + '
' + + '
' + ''; } @@ -799,7 +800,185 @@ async function loadEdl(p) { }; } +// Every film keeps the take it replaced. Without this, a re-film to fix one +// thing could quietly make another worse with no way back. +async function loadTakes() { + var el = document.getElementById("takes"); + if (!el) return; + var r = await api("list_versions", { email: email, project_id: pid }); + if (!r.ok || !(r.versions || []).length) { el.innerHTML = ""; return; } + el.innerHTML = '

Earlier takes

' + + '

' + esc(r.status_text || "") + ' ' + + 'Bringing one back keeps the one you are leaving.

' + + '
' + + r.versions.map(function (v, i) { + return '
' + + '' + + '
' + esc(when(v.at) || "earlier") + + (v.voice_name ? ' · ' + esc(v.voice_name) : '') + + (v.cost_usd ? ' · + var eo = document.getElementById("edlopen"); + if (eo) eo.onclick = function () { + var w = document.getElementById("edlwrap"); + var open = w.style.display === "none"; + w.style.display = open ? "" : "none"; + this.textContent = open ? "Close the video editor" : "Open the video editor"; + if (open) { + if (!EDL) loadEdl((DATA && DATA.project) || {}); + else tlDraw(); + w.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }; + var sb = document.getElementById("seams"); + function paintSeams(r) { + var el = document.getElementById("seamout"); + if (!el) return; + el.innerHTML = '

' + esc(r.status_text || "") + '

' + + (r.seams || []).map(function (x) { + var col = x.verdict === "smooth" ? "var(--good)" + : (x.verdict === "visible" ? "var(--accent2)" : "var(--bad)"); + return '
' + + 'shot ' + x.after_shot + ' → ' + x.before_shot + ' at ' + x.at_seconds + 's · ' + + 'shift ' + x.shift_x + ',' + x.shift_y + ' · drift ' + x.drift + ' · ' + + '' + esc(x.verdict) + '' + + (x.recommend_broll ? ' · worth a cutaway' : '') + + '
'; + }).join(""); + } + + if (sb) sb.onclick = async function () { + sb.disabled = true; sb.textContent = "Measuring…"; + var el = document.getElementById("seamout"); + var r = await api("seam_report", { email: email, project_id: pid }); + if (!r.ok) { + sb.disabled = false; sb.textContent = "Measure the joins"; + showMsg("msg", r.error || "Could not measure."); + return; + } + // Measuring downloads every shot and searches each join for the best + // alignment, which is far too slow to answer inside one request. + if (r.pending) { + el.innerHTML = '

' + esc(r.status_text || "Measuring…") + '

'; + var waited = 0; + var t = setInterval(async function () { + waited += 4; + var g = await api("seam_report", { email: email, project_id: pid }); + if (g.ok && !g.pending) { + clearInterval(t); + sb.disabled = false; sb.textContent = "Measure the joins"; + paintSeams(g); + return; + } + if (waited > 180) { + clearInterval(t); + sb.disabled = false; sb.textContent = "Measure the joins"; + showMsg("msg", "Measuring is taking unusually long."); + } + }, 4000); + return; + } + sb.disabled = false; sb.textContent = "Measure the joins"; + if (!r.seams || !r.seams.length) { + el.innerHTML = '

' + esc(r.status_text || "Nothing to measure.") + '

'; + return; + } + el.innerHTML = '

' + esc(r.status_text || "") + '

' + + r.seams.map(function (x) { + var col = x.verdict === "smooth" ? "var(--good)" + : (x.verdict === "visible" ? "var(--accent2)" : "var(--bad)"); + return '
' + + 'shot ' + x.after_shot + ' → ' + x.before_shot + ' at ' + x.at_seconds + 's · ' + + 'shift ' + x.shift_x + ',' + x.shift_y + ' · drift ' + x.drift + ' · ' + + '' + esc(x.verdict) + '' + + (x.recommend_broll ? ' · worth a cutaway' : '') + + '
'; + }).join(""); + }; + + var cb = document.getElementById("cut"); + if (cb) cb.onclick = async function () { + hideMsg("msg"); + cb.disabled = true; cb.textContent = "Cutting…"; + var r = await api("render_cut", { email: email, project_id: pid }); + if (!r.ok) { cb.disabled = false; cb.textContent = "Cut the B-roll in"; showMsg("msg", r.error || "Could not cut."); return; } + var waited = 0; + var t = setInterval(async function () { + waited += 5; + var g = await api("get_project", { email: email, project_id: pid }); + if (g.ok && g.project && g.project.cut_url) { clearInterval(t); load(); return; } + if (waited > 300) { + clearInterval(t); + cb.disabled = false; cb.textContent = "Cut the B-roll in"; + showMsg("msg", "That is taking unusually long. Try again shortly."); + } + }, 5000); + }; +} + +function bindBottom() { + var rv = document.getElementById("revise"); + if (rv) rv.onclick = async function () { + hideMsg("msg"); + var fb = document.getElementById("fb").value.trim(); + if (fb.length < 5) { showMsg("msg", "Tell us what you would like changed."); return; } + rv.disabled = true; rv.textContent = "Sending…"; + var res = await api("revise_script", { email: email, project_id: pid, feedback: fb }); + if (!res.ok) { rv.disabled = false; rv.textContent = "Rewrite the words"; showMsg("msg", res.error || "That did not go through."); return; } + showMsg("msg", "Rewriting now…", "ok"); + load(); + }; + var ap = document.getElementById("approve"); + if (ap) ap.onclick = async function () { + hideMsg("msg"); + ap.disabled = true; ap.textContent = "Approving…"; + var res = await api("approve_script", { email: email, project_id: pid }); + if (!res.ok) { ap.disabled = false; ap.textContent = "Approve and lock it"; showMsg("msg", res.error || "That did not go through."); return; } + showMsg("msg", res.status_text || "Approved.", "ok"); + load(); + }; +} + +async function load() { + if (!pid || !email) return; + if (!PRESETS.length) { + var c = await api("camera_presets", {}); + if (c.ok) PRESETS = c.presets || []; + } + var r = await api("get_project", { email: email, project_id: pid }); + if (!r.ok) { showMsg("msg", r.error || "Could not load that script."); return; } + if (!PHOTOS.length && r.project && r.project.pet_id) { + var ph = await api("list_photos", { email: email, pet_id: r.project.pet_id }); + if (ph.ok) PHOTOS = ph.photos || []; + } + render(r); +} +load(); + + + + + Number(v.cost_usd).toFixed(2) : '') + '
' + + (v.note ? '
' + esc(v.note) + '
' : '') + + '' + + '
'; + }).join("") + + '
'; + [].forEach.call(el.querySelectorAll("[data-restore]"), function (b) { + b.onclick = async function () { + if (!confirm("Bring that take back as the current video? The one you are leaving is kept.")) return; + b.disabled = true; b.textContent = "Restoring…"; + var s = await api("restore_version", { email: email, project_id: pid, + index: parseInt(b.getAttribute("data-restore"), 10) }); + if (!s.ok) { b.disabled = false; b.textContent = "Use this one"; showMsg("msg", s.error || "Could not restore that."); return; } + showMsg("msg", s.status_text || "Restored.", "ok"); + load(); + }; + }); +} + function bindPost() { + loadTakes(); // The editing bay is opt-in. Most people want to watch the thing and leave. var eo = document.getElementById("edlopen"); if (eo) eo.onclick = function () {