Layout view: stop crying wolf on short shots, allow removing a cutaway, make filming reachable

- "off the clock" fired on ANY shot not within 1.2s of the clip length, so a
  7.0s line in a 10s shot was flagged alongside a genuine 11.4s overrun. Under
  is fine - the engine finishes early and waits. Only an overrun gets cut off
  mid-word, and only that is now flagged, as "runs long".
- A cutaway could be made or remade but never removed, and the subject is the
  writer's guess so it is often wrong. There is now a Remove button.
- The Filming step was disabled until the project was already filming, so the
  only way to start was a button at the bottom of the Layout page. Approved now
  reaches step 3.
- "Cutting to the clock ... rewrites how the lines are divided" is no longer
  true - it measures and no longer moves boundaries, because Setup shots does
  the division locally. Copy corrected.
- "speaking the words costs about a hundredth of a cent" was wrong by three
  orders of magnitude; a measured run is about 16 cents.
This commit is contained in:
gator 2026-09-15 02:05:07 +00:00
parent 70fa5ae9bb
commit 58ccf6fc6d

View file

@ -148,6 +148,13 @@ function shotHtml(s, locked) {
: '') : '')
+ (locked ? '' : '<button type="button" class="btn ghost" data-broll="' + s.index + '" style="margin-top:10px">' + (locked ? '' : '<button type="button" class="btn ghost" data-broll="' + s.index + '" style="margin-top:10px">'
+ (s.broll_url ? 'Remake cutaway' : 'Make cutaway') + '</button>') + (s.broll_url ? 'Remake cutaway' : 'Make cutaway') + '</button>')
// The subject is the writer's guess at what the line is about, so the
// picture it draws is hit and miss. Making and remaking were possible;
// throwing one away was not, which left a wrong image in the film with
// no way to take it out.
+ ((locked || !s.broll_url) ? ''
: '<button type="button" class="btn ghost" data-brollrm="' + s.index + '" '
+ 'style="margin-top:10px;margin-left:8px">Remove cutaway</button>')
+ '</div>'; + '</div>';
} }
@ -191,12 +198,23 @@ function stageOf(st) {
return 1; return 1;
} }
function stepper(stage, view) { // How far the stepper may be CLICKED, as opposed to how far the project has
// got. Approved means the words are settled and filming is the next thing to
// do, so the Filming step has to be reachable - it is where the button lives.
// Until this existed the only way to start a film was a button at the bottom
// of the Layout page, below the shot cards and the photo uploader.
function reachOf(st) {
if (st === "approved") return 3;
return stageOf(st);
}
function stepper(stage, view, reach) {
if (reach === undefined || reach === null) reach = stage;
return '<div class="steps">' + STAGES.map(function (n, k) { return '<div class="steps">' + STAGES.map(function (n, k) {
var i = k + 1; var i = k + 1;
var cls = i === view ? "on" : (i <= stage ? "done" : ""); var cls = i === view ? "on" : (i <= stage ? "done" : "");
return '<button type="button" class="' + cls + '" data-stage="' + i + '"' return '<button type="button" class="' + cls + '" data-stage="' + i + '"'
+ (i <= stage ? '' : ' disabled') + '>' + i + ' &middot; ' + esc(n) + '</button>'; + (i <= reach ? '' : ' disabled') + '>' + i + ' &middot; ' + esc(n) + '</button>';
}).join("") + '</div>'; }).join("") + '</div>';
} }
@ -498,15 +516,22 @@ function render(r) {
+ 'go straight to filming.</p>' + 'go straight to filming.</p>'
+ '<h2 class="sec">Hear it first</h2>' + '<h2 class="sec">Hear it first</h2>'
+ '<div class="card" id="pzcard">' + '<div class="card" id="pzcard">'
// "A hundredth of a cent" was wrong by three orders of magnitude. A
// measured run of an 80s script came back at 15.89 cents - the
// ElevenLabs rate is 22 dollars per 131,000 characters.
+ '<p class="foot" style="margin:0 0 14px">Filming costs real money; speaking the ' + '<p class="foot" style="margin:0 0 14px">Filming costs real money; speaking the '
+ 'words costs about a hundredth of a cent. Run it through in your pet&rsquo;s voice ' + 'words costs a few cents. Run it through in your pet&rsquo;s voice '
+ 'over the opening stills first, and only film once the timing and the jokes land.</p>' + 'over the opening stills first, and only film once the timing and the jokes land.</p>'
+ '<button type="button" class="btn ghost" id="pzmake">Speak the script</button>' + '<button type="button" class="btn ghost" id="pzmake">Speak the script</button>'
+ '<button type="button" class="btn" id="alignbtn" style="margin-left:8px">Cut it to the clock</button>' + '<button type="button" class="btn" id="alignbtn" style="margin-left:8px">Cut it to the clock</button>'
+ '<span class="foot" style="margin-left:12px" id="pznote"></span>' + '<span class="foot" style="margin-left:12px" id="pznote"></span>'
+ '<p class="foot" style="margin:10px 0 0">Cutting to the clock speaks the whole piece once, ' // It no longer moves anything. Setup shots does the division, locally
+ 'measures where every word actually falls, and moves the shot boundaries to real word ' // and for nothing, at the performing engine's own measured rate. This
+ 'endings. It rewrites how the lines are divided &mdash; not the words themselves.</p>' // reads the result back so you can hear it and check the timing.
+ '<p class="foot" style="margin:10px 0 0">This speaks the whole piece once and measures '
+ 'where every word actually falls, so you can check the timing against the shots. '
+ 'It does not move the shot boundaries &mdash; Setup shots, on the words page, does '
+ 'that in code at the engine&rsquo;s own measured rate.</p>'
+ '<div id="alignout" style="margin-top:14px"></div>' + '<div id="alignout" style="margin-top:14px"></div>'
+ '<div id="pzout" style="margin-top:16px"></div>' + '<div id="pzout" style="margin-top:16px"></div>'
+ '</div>' + '</div>'
@ -520,7 +545,7 @@ function render(r) {
body = head + post + film; body = head + post + film;
} }
out.innerHTML = cams + stepper(stage, VIEW) + body; out.innerHTML = cams + stepper(stage, VIEW, reachOf(p.status)) + body;
wire(locked); wire(locked);
} }
@ -1160,11 +1185,19 @@ function bindAlign() {
+ 's filmed, against ' + bought + 's of shots.</p>' + 's filmed, against ' + bought + 's of shots.</p>'
+ (r.audio ? '<audio controls src="' + esc(r.audio) + '" style="width:100%;max-width:420px"></audio>' : '') + (r.audio ? '<audio controls src="' + esc(r.audio) + '" style="width:100%;max-width:420px"></audio>' : '')
+ (r.shots || []).map(function (s) { + (r.shots || []).map(function (s) {
var fit = Math.abs((s.filmed || 0) - each) <= 1.2; // Under the clock is FINE. The engine finishes the line and waits -
// measured, Omni "barely stretches, it finishes early". Only an
// OVERRUN is a problem, because that is cut off mid-word.
//
// This used to flag anything more than 1.2s from the clip length in
// either direction, so a 7.0s line in a 10s shot was marked exactly
// like a genuine 11.4s overrun, and sent people rewriting lines that
// fitted perfectly well.
var over = (s.filmed || 0) > each + 0.3;
return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">' return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">'
+ 'shot ' + s.index + ' &middot; ' + (s.start || 0).toFixed(1) + '-' + 'shot ' + s.index + ' &middot; ' + (s.start || 0).toFixed(1) + '-'
+ (s.end || 0).toFixed(1) + 's spoken &middot; ~' + (s.filmed || 0).toFixed(1) + 's filmed' + (s.end || 0).toFixed(1) + 's spoken &middot; ~' + (s.filmed || 0).toFixed(1) + 's filmed'
+ (fit ? '' : ' <span style="color:var(--accent2)">off the clock</span>') + (over ? ' <span style="color:var(--accent2)">runs long</span>' : '')
+ '<br>' + esc(s.line || "") + '</div>'; + '<br>' + esc(s.line || "") + '</div>';
}).join("") }).join("")
+ (filmed < bought - 1.5 + (filmed < bought - 1.5