Render the proposed cuts, with a free in-browser preview

A cut is an ordered list of shot ids, so previewing one costs nothing: the
page plays that cut's clips back to back. That is what makes asking for ten
proposals reasonable. Only a cut somebody chooses gets joined into a real
mp4 with its own share page, which is also what makes the watch-through
numbers comparable - one page per version.
This commit is contained in:
gator 2026-09-17 12:44:23 +00:00
parent 800ba28387
commit 73a8f5d166

View file

@ -379,6 +379,58 @@
+ (worst && over > 5 ? "<ul>" + worst + "</ul>" : "") + "</div>";
}
// A cut is an ordering, not a render. Playing one is free, so all of them
// can be looked at before anything is joined into a file.
function cutsHtml(meta) {
if (meta.writer !== RUN.lead_writer) return "";
var shots = PKG.shots || [];
var filmed = shots.filter(function (s) { return s.clip_url; }).length;
var cuts = PKG.cuts || [];
var st = String(PKG.cuts_state || "");
var out = '<div class="cuts"><h4>Shorter cuts of this same shoot</h4>'
+ '<p class="lede">Nothing here is re-filmed. A cut is an order to play '
+ 'the shots in, so making another one costs nothing — which is why '
+ 'the question is not “what is the right length” but “which '
+ 'version do people watch to the end”.</p>';
if (st === "thinking") out += '<p class="busy">Working out what to drop…</p>';
else if (/^failed/.test(st)) out += '<p class="cont">' + esc(st) + '</p>';
out += cuts.map(function (c) {
var stat = STATS[c.id];
var play = (PLAYING === c.id)
? '<div class="playbox"><video id="cutv" controls playsinline></video>'
+ '<div class="bar2"><span id="cutat">loading…</span>'
+ '<button type="button" class="btn" data-stop="1">Close</button></div></div>'
: '';
var tools = '<button type="button" class="btn" data-play="' + esc(c.id) + '">'
+ (PLAYING === c.id ? 'Playing' : 'Watch it · free') + '</button>';
if (c.state === "made" && c.video_url)
tools += '<a class="btn" href="/videos.html">Published · in My videos</a>';
else if (c.state === "making")
tools += '<span class="busy">joining…</span>';
else if (filmed)
tools += '<button type="button" class="btn primary" data-makecut="' + esc(c.id)
+ '">Publish this version</button>';
return '<div class="cut"><b>' + esc(c.name || "") + '</b>'
+ '<span class="len">' + mmss(c.seconds || 0) + ' · '
+ (c.shot_ids || []).length + ' shots</span>'
+ (c.strategy ? '<p class="strat">' + esc(c.strategy) + '</p>' : '')
+ (c.why ? '<p class="why">' + esc(c.why) + '</p>' : '')
+ (c.sacrifices ? '<p class="sac">gives up: ' + esc(c.sacrifices) + '</p>' : '')
+ (stat ? '<p class="stat">' + stat.views + ' viewer'
+ (stat.views === 1 ? '' : 's') + ' · median ' + stat.median_percent
+ '% watched · ' + stat.completion_rate + '% reached the end</p>' : '')
+ '<p class="ids">' + esc((c.shot_ids || []).join(" ")) + '</p>'
+ '<div class="tools">' + tools + '</div>' + play + '</div>';
}).join("");
if (!cuts.length && st !== "thinking")
out += '<p class="cont">No cuts proposed yet.</p>';
return out + '</div>';
}
function reviewsHtml(meta) {
var mine = REVIEWS.filter(function (r) {
return r.subject === meta.writer && Number(r.iteration) === Number(meta.iteration); });
@ -465,7 +517,7 @@
shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); });
return head + probs + '<div class="bar">' + barHtml(meta) + '</div>'
+ spineHtml() + reviewsHtml(meta) + castHtml + body;
+ spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + castHtml + body;
}
function insertHtml(after) {