Hear the script before paying to film it
Filming is $0.40 a shot. Speaking the same words is a few hundredths of a cent. So the narrative — does it flow, is the joke in the right place, does it drag — can now be judged for about one percent of the cost, using the opening stills as placeholders and the pet's real voice. It also replaces a guess with a measurement. Line length was estimated from a word count and hoped to fit eight seconds; speaking it says exactly how long it takes, and a line that runs over is flagged before anything is filmed rather than after.
This commit is contained in:
parent
13a45d2e87
commit
fb1d9f229e
1 changed files with 129 additions and 1 deletions
130
script.html
130
script.html
|
|
@ -371,7 +371,16 @@ function render(r) {
|
|||
+ 'pet’s likeness. Cutaways sit across the joins so the cut is never on screen. '
|
||||
+ 'All of it has been chosen for you — change anything you disagree with, or '
|
||||
+ 'go straight to filming.</p>'
|
||||
+ '<h2 class="sec">The shots</h2>'
|
||||
+ '<h2 class="sec">Hear it first</h2>'
|
||||
+ '<div class="card" id="pzcard">'
|
||||
+ '<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’s voice '
|
||||
+ '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>'
|
||||
+ '<span class="foot" style="margin-left:12px" id="pznote"></span>'
|
||||
+ '<div id="pzout" style="margin-top:16px"></div>'
|
||||
+ '</div>'
|
||||
+ '<h2 class="sec" style="margin-top:34px">The shots</h2>'
|
||||
+ s.segments.map(function (x) { return shotHtml(x, false); }).join("")
|
||||
+ own
|
||||
+ film;
|
||||
|
|
@ -521,6 +530,7 @@ function wire(locked) {
|
|||
};
|
||||
});
|
||||
bindPics();
|
||||
bindPreviz();
|
||||
bindFilm((DATA && DATA.project) || {});
|
||||
bindPost();
|
||||
if (locked) { bindBottom(); return; }
|
||||
|
|
@ -910,6 +920,124 @@ async function loadTakes() {
|
|||
});
|
||||
}
|
||||
|
||||
// The run-through: each opening still held for exactly as long as its line
|
||||
// takes to say. No video is generated, so this costs nothing to watch again.
|
||||
var PZ = null, PZTIMER = null;
|
||||
|
||||
function pzPaint(lines) {
|
||||
var el = document.getElementById("pzout");
|
||||
if (!el) return;
|
||||
var segs = ((DATA && DATA.script && DATA.script.segments) || []);
|
||||
var byIdx = {};
|
||||
(lines || []).forEach(function (l) { byIdx[l.index] = l; });
|
||||
PZ = segs.map(function (s) {
|
||||
var l = byIdx[s.index] || {};
|
||||
return { index: s.index, url: l.url || "", seconds: l.seconds || 0,
|
||||
over: !!l.over, plate: s.plate_url || "", line: s.line || "" };
|
||||
}).filter(function (x) { return x.url; });
|
||||
|
||||
if (!PZ.length) { el.innerHTML = ""; return; }
|
||||
var total = PZ.reduce(function (a, b) { return a + (b.seconds || 0); }, 0);
|
||||
var bought = segs.length * 8;
|
||||
var over = PZ.filter(function (x) { return x.over; });
|
||||
|
||||
el.innerHTML = '<div class="stage" style="margin-bottom:12px">'
|
||||
+ '<img id="pzimg" alt="opening still" style="width:100%;aspect-ratio:9/16;'
|
||||
+ 'border-radius:10px;background:#000;object-fit:cover;display:block">'
|
||||
+ '</div>'
|
||||
+ '<audio id="pzaud"></audio>'
|
||||
+ '<button type="button" class="btn" id="pzplay">▶ Play the run-through</button>'
|
||||
+ '<span class="foot" style="margin-left:12px" id="pzwhere">'
|
||||
+ PZ.length + ' lines · ' + total.toFixed(1) + 's spoken, '
|
||||
+ bought + 's of shots</span>'
|
||||
+ (total > bought
|
||||
? '<div class="warn" style="margin-top:12px">Spoken, this runs '
|
||||
+ total.toFixed(1) + ' seconds but only ' + bought + ' seconds are being '
|
||||
+ 'filmed. Something will be cut off. Shorten the words, or buy more length.</div>'
|
||||
: '')
|
||||
+ (over.length
|
||||
? '<div class="warn" style="margin-top:12px">Shot' + (over.length > 1 ? 's ' : ' ')
|
||||
+ over.map(function (x) { return x.index; }).join(", ")
|
||||
+ ' will not fit in eight seconds: '
|
||||
+ over.map(function (x) { return x.index + " runs " + x.seconds.toFixed(1) + "s"; }).join(", ")
|
||||
+ '. Trim those lines before filming.</div>'
|
||||
: '')
|
||||
+ '<div style="margin-top:14px">'
|
||||
+ PZ.map(function (x) {
|
||||
return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">'
|
||||
+ 'shot ' + x.index + ' · ' + x.seconds.toFixed(1) + 's'
|
||||
+ (x.over ? ' <span style="color:var(--accent2)">over</span>' : '')
|
||||
+ ' · ' + esc(x.line.slice(0, 64)) + '</div>';
|
||||
}).join("")
|
||||
+ '</div>';
|
||||
|
||||
var img = document.getElementById("pzimg"),
|
||||
aud = document.getElementById("pzaud"),
|
||||
btn = document.getElementById("pzplay"),
|
||||
where = document.getElementById("pzwhere");
|
||||
|
||||
function stop() {
|
||||
aud.pause();
|
||||
if (PZTIMER) { clearTimeout(PZTIMER); PZTIMER = null; }
|
||||
btn.innerHTML = "▶ Play the run-through";
|
||||
}
|
||||
|
||||
function playFrom(i) {
|
||||
if (i >= PZ.length) { stop(); return; }
|
||||
var x = PZ[i];
|
||||
img.src = x.plate || "";
|
||||
where.textContent = "shot " + x.index + " of " + PZ.length + " · " + x.seconds.toFixed(1) + "s";
|
||||
aud.src = x.url;
|
||||
aud.onended = function () { playFrom(i + 1); };
|
||||
aud.play().catch(function () { stop(); });
|
||||
}
|
||||
|
||||
btn.onclick = function () {
|
||||
if (!aud.paused) { stop(); return; }
|
||||
btn.innerHTML = "▮▮ Stop";
|
||||
playFrom(0);
|
||||
};
|
||||
}
|
||||
|
||||
function bindPreviz() {
|
||||
var b = document.getElementById("pzmake");
|
||||
if (!b) return;
|
||||
var note = document.getElementById("pznote");
|
||||
|
||||
async function poll() {
|
||||
var waited = 0;
|
||||
return await new Promise(function (done) {
|
||||
var t = setInterval(async function () {
|
||||
waited += 4;
|
||||
var g = await api("make_previz", { email: email, project_id: pid });
|
||||
if (g.ok && !g.pending) { clearInterval(t); done(g); return; }
|
||||
if (g.ok && g.pending && note) note.textContent = g.status_text || "";
|
||||
if (waited > 240) { clearInterval(t); done({ ok: false, error: "That is taking unusually long." }); }
|
||||
}, 4000);
|
||||
});
|
||||
}
|
||||
|
||||
b.onclick = async function () {
|
||||
hideMsg("msg");
|
||||
b.disabled = true; b.textContent = "Speaking…";
|
||||
var r = await api("make_previz", { email: email, project_id: pid });
|
||||
if (r.ok && r.pending) { note.textContent = r.status_text || ""; r = await poll(); }
|
||||
b.disabled = false; b.textContent = "Speak it again";
|
||||
if (!r.ok) { note.textContent = ""; showMsg("msg", r.error || "Could not speak that."); return; }
|
||||
note.textContent = r.status_text || "";
|
||||
pzPaint(r.lines || []);
|
||||
};
|
||||
|
||||
// Already spoken? Show it without asking again.
|
||||
api("make_previz", { email: email, project_id: pid }).then(function (r) {
|
||||
if (r && r.ok && !r.pending && (r.lines || []).length) {
|
||||
b.textContent = "Speak it again";
|
||||
note.textContent = r.status_text || "";
|
||||
pzPaint(r.lines);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindPost() {
|
||||
loadTakes();
|
||||
// The editing bay is opt-in. Most people want to watch the thing and leave.
|
||||
|
|
|
|||
Loading…
Reference in a new issue