From 0dabc60006c830c5c71e41fa2fc07b32c2a32bbc Mon Sep 17 00:00:00 2001 From: gator Date: Mon, 14 Sep 2026 13:32:57 +0000 Subject: [PATCH] script: rebuild part 3 of 6 --- script.html | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/script.html b/script.html index bebb78c..d02a815 100644 --- a/script.html +++ b/script.html @@ -593,4 +593,214 @@ function wire(locked) { [].forEach.call(document.querySelectorAll("[data-broll]"), function (b) { b.addEventListener("click", async function () { + hideMsg("msg"); + var idx = parseInt(b.getAttribute("data-broll"), 10); + b.disabled = true; b.textContent = "Making…"; + var r = await api("make_broll", { email: email, project_id: pid, index: idx }); + if (!r.ok) { b.disabled = false; b.textContent = "Make cutaway"; showMsg("msg", r.error || "Could not make that."); return; } + var waited = 0; + var t = setInterval(async function () { + waited += 5; + var g = await api("get_project", { email: email, project_id: pid }); + var seg = (g.ok && g.script && g.script.segments || []).filter(function (x) { + return parseInt(x.index, 10) === idx; })[0]; + if (seg && seg.broll_url) { clearInterval(t); load(); return; } + if (waited > 180) { + clearInterval(t); + b.disabled = false; b.textContent = "Make cutaway"; + showMsg("msg", "That cutaway is taking unusually long."); + } + }, 5000); + }); + }); + + [].forEach.call(document.querySelectorAll("[data-make]"), function (b) { + b.addEventListener("click", async function () { + hideMsg("msg"); + var idx = parseInt(b.getAttribute("data-make"), 10); + // Flush any pending edits in this card before drawing from them. + var card = b.closest(".shot"); + var sc = card.querySelector("[data-scene]"), cm = card.querySelector("[data-cam]"), + ph = card.querySelector("[data-photo]"); + await api("update_shot", { email: email, project_id: pid, index: idx, + scene: sc ? sc.value : undefined, camera: cm ? cm.value : undefined, + photo_id: ph ? ph.value : undefined }); + b.disabled = true; b.textContent = "Drawing…"; + var r = await api("make_shot_image", { email: email, project_id: pid, index: idx }); + if (!r.ok) { + b.disabled = false; b.textContent = "Try again"; + showMsg("msg", r.error || "Could not make that frame."); + return; + } + // Drawing takes about half a minute and runs on the server, so the call + // above returns straight away. Watch the shot until the frame appears. + var waited = 0; + var tick = setInterval(async function () { + waited += 4; + var g = await api("get_project", { email: email, project_id: pid }); + var seg = (g.ok && g.script && g.script.segments || []).filter(function (x) { + return parseInt(x.index, 10) === idx; })[0]; + if (seg && seg.draw_error) { + clearInterval(tick); + b.disabled = false; b.textContent = "Try again"; + showMsg("msg", "That frame did not draw: " + seg.draw_error); + return; + } + if (seg && seg.plate_url) { clearInterval(tick); load(); return; } + if (waited > 120) { + clearInterval(tick); + b.disabled = false; b.textContent = "Try again"; + showMsg("msg", "That frame is taking unusually long. Try again in a moment."); + } + }, 4000); + }); + }); + + bindBottom(); +} + +async function bindFilm(p) { + var bal = document.getElementById("balsec"); + if (bal) { + var b = await api("get_balance", { email: email }); + var have = (b.ok && b.balance_seconds) || 0; + var need = videoSecondsOf(p); + bal.textContent = have + "s left" + (have < need ? " — not enough" : ""); + if (have < need) bal.style.color = "var(--bad)"; + } + + // While it films, keep the page honest about where it has got to. + if (p.status === "queued_to_film" || p.status === "filming") { + var t = setInterval(async function () { + var g = await api("get_project", { email: email, project_id: pid }); + if (!g.ok) return; + var st = g.project.status; + var note = document.getElementById("filmnote"); + if (note) note.textContent = g.project.film_note || st; + if (st === "ready" || st === "film_failed") { clearInterval(t); load(); } + }, 8000); + } + + var fb = document.getElementById("film"); + if (fb) fb.onclick = async function () { + hideMsg("msg"); + fb.disabled = true; fb.textContent = "Starting…"; + var r = await api("request_render", { email: email, project_id: pid }); + if (!r.ok) { + fb.disabled = false; fb.textContent = "Film it"; + showMsg("msg", r.error || "Could not start filming."); + return; + } + showMsg("msg", r.status_text || "Filming.", "ok"); + load(); + }; +} + +// ---------------------------------------------------------------- the cut +// EDL is {duration, cuts:[{shot,start,seconds,kind,src,in,moved}], segments}. +// Only the cut positions are editable; the server composes the rest, so the +// page never has to be trusted about source urls or trim points. +var EDL = null, DIRTY = false; + +function tlDraw() { + var tl = document.getElementById("tl"); + if (!tl || !EDL) return; + var dur = EDL.duration || 1; + + // Above the bar: where each shot begins, with the frame it opens on. The end + // of a shot is always the start of the next one, so only starts are marked. + var strip = document.getElementById("tlshots"); + if (strip) { + var segs = ((DATA && DATA.script && DATA.script.segments) || []); + strip.innerHTML = segs.map(function (s, k) { + var at = k * 8, wide = (8 / dur) * 100; + return '
' + + 'shot ' + s.index + + (s.plate_url ? 'shot ' + s.index + ' opening frame' : '') + + '
'; + }).join(""); + } + + var html = ""; + (EDL.cuts || []).forEach(function (c, i) { + var left = (c.start / dur) * 100, wide = (c.seconds / dur) * 100; + // Show the actual picture. A plain rectangle says nothing about what is + // going to be on screen, which is the only thing worth judging here. + var thumb = c.kind === "video" + ? '' + : ''; + html += '
' + + thumb + '' + c.seconds + 's' + + '
'; + }); + html += '
'; + html += '
'; + for (var t = 0; t <= dur; t += 8) { + // The end labels are centred on the track edges, so half of each falls + // outside it. Pin the first left and the last right instead. + var pos = (t / dur) * 100; + var style = t === 0 ? 'left:2px;transform:none' + : (t >= dur ? 'right:2px;left:auto;transform:none' + : 'left:' + pos.toFixed(2) + '%'); + html += '' + t + 's'; + } + html += '
'; + tl.innerHTML = html; + var n = (EDL.cuts || []).length; + document.getElementById("tlinfo").textContent = n + ? n + " cutaway" + (n === 1 ? "" : "s") + " over " + dur + " seconds. Drag to move one, or pull its right edge to change how long it runs. The voice underneath never stops." + : "No cutaways on this one, so the talking head runs the whole way."; + document.getElementById("edlsave").disabled = !DIRTY; +} + +// Move the picture to a moment without disturbing the edit. +function seekTo(t) { + var v = document.getElementById("pvid"); + if (!v || !EDL) return; + v.currentTime = Math.max(0, Math.min(t, EDL.duration || 0)); + v.dispatchEvent(new Event("timeupdate")); +} + +function tlBind() { + var tl = document.getElementById("tl"); + if (!tl) return; + var drag = null; + + var strip = document.getElementById("tlshots"); + if (strip) strip.addEventListener("click", function (e) { + var s = e.target.closest("[data-seek]"); + if (s) seekTo(parseFloat(s.getAttribute("data-seek"))); + }); + + function timeAt(clientX) { + var r = tl.getBoundingClientRect(); + return ((clientX - r.left) / (r.width || 1)) * (EDL.duration || 0); + } + + tl.addEventListener("pointerdown", function (e) { + var grip = e.target.closest("[data-grip]"); + var block = e.target.closest("[data-i]"); + if (!block) { + // Empty track: scrub. This is how you get to a particular moment. + drag = { mode: "seek" }; + seekTo(timeAt(e.clientX)); + tl.setPointerCapture(e.pointerId); + e.preventDefault(); + return; + } + var i = parseInt((grip || block).getAttribute(grip ? "data-grip" : "data-i"), 10); + drag = { i: i, mode: grip ? "len" : "move", x0: e.clientX, + start0: EDL.cuts[i].start, secs0: EDL.cuts[i].seconds }; + tl.setPointerCapture(e.pointerId); + e.preventDefault(); + }); + + tl.addEventListener("pointermove", function (e) { + if (!drag) return; + if (drag.mode === "seek") { seekTo(timeAt(e.clientX)); return; } + var dur = EDL.duration || 1, w = tl.clientWidth || 1; + var d = ((e.clientX - drag.x0) / w) * dur; \ No newline at end of file