From 4d553585d6398b94c5d99a6c7d0262f1ff485d07 Mon Sep 17 00:00:00 2001 From: gator Date: Tue, 8 Sep 2026 14:46:15 +0000 Subject: [PATCH] Cut the shots where the clock says, not where a word count guesses "I am not" and "extraordinarily unimpressed" are both three words and nowhere near the same length, so any word budget is a guess about something we can measure. Three attempts at tuning one were wrong in both directions. Speaking the monologue once returns the start and end of every character, so word boundaries are exact. This cuts each shot at the word whose end sits nearest the boundary, preferring a sentence ending when one is close. On the first real test it produced a 20 word shot landing at 7.8s and cutting on a full stop, where the budget had said 18. --- script.html | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/script.html b/script.html index fe1c37d..9e9bf17 100644 --- a/script.html +++ b/script.html @@ -377,7 +377,12 @@ function render(r) { + 'words costs about a hundredth of a cent. Run it through in your pet’s voice ' + 'over the opening stills first, and only film once the timing and the jokes land.

' + '' + + '' + '' + + '

Cutting to the clock speaks the whole piece once, ' + + 'measures where every word actually falls, and moves the shot boundaries to real word ' + + 'endings. It rewrites how the lines are divided — not the words themselves.

' + + '
' + '
' + '' + '

The shots

' @@ -531,6 +536,7 @@ function wire(locked) { }); bindPics(); bindPreviz(); + bindAlign(); bindFilm((DATA && DATA.project) || {}); bindPost(); if (locked) { bindBottom(); return; } @@ -1013,6 +1019,67 @@ function pzPaint(lines) { }; } +function bindAlign() { + var b = document.getElementById("alignbtn"); + if (!b) return; + var out = document.getElementById("alignout"); + + function paint(r) { + var bought = ((DATA && DATA.script && DATA.script.segments) || []).length * 8; + var filmed = r.filmed || 0; + out.innerHTML = '

Measured: ' + + (r.spoken || 0).toFixed(1) + 's spoken, about ' + filmed.toFixed(1) + + 's filmed, against ' + bought + 's of shots.

' + + (r.audio ? '' : '') + + (r.shots || []).map(function (s) { + var fit = Math.abs((s.filmed || 0) - 8) <= 1.2; + return '
' + + 'shot ' + s.index + ' · ' + (s.start || 0).toFixed(1) + '-' + + (s.end || 0).toFixed(1) + 's spoken · ~' + (s.filmed || 0).toFixed(1) + 's filmed' + + (fit ? '' : ' off the clock') + + '
' + esc(s.line || "") + '
'; + }).join("") + + (filmed < bought - 1.5 + ? '
The whole piece only runs about ' + + filmed.toFixed(1) + ' seconds filmed, but ' + bought + ' seconds are being shot. ' + + 'No arrangement of these words fills the video — it needs more written, or a ' + + 'shorter length.
' + : '') + + (filmed > bought + 1.5 + ? '
The whole piece runs about ' + + filmed.toFixed(1) + ' seconds filmed against ' + bought + ' seconds of video, so the ' + + 'end would be cut off. Trim it, or buy more length.
' + : ''); + } + + b.onclick = async function () { + hideMsg("msg"); + b.disabled = true; b.textContent = "Measuring…"; + var r = await api("align_script", { email: email, project_id: pid, refresh: true }); + if (r.ok && r.pending) { + var waited = 0; + r = await new Promise(function (done) { + var t = setInterval(async function () { + waited += 4; + var g = await api("align_script", { email: email, project_id: pid }); + if (g.ok && !g.pending) { clearInterval(t); done(g); return; } + if (waited > 240) { clearInterval(t); done({ ok: false, error: "That is taking unusually long." }); } + }, 4000); + }); + } + b.disabled = false; b.textContent = "Cut it to the clock"; + if (!r.ok) { showMsg("msg", r.error || "Could not measure that."); return; } + if (r.note) { showMsg("msg", r.note); return; } + paint(r); + showMsg("msg", "Shots re-cut at real word endings.", "ok"); + load(); + }; + + api("align_script", { email: email, project_id: pid }).then(function (r) { + if (r && r.ok && !r.pending && r.audio) paint(r); + }); +} + function bindPreviz() { var b = document.getElementById("pzmake"); if (!b) return;