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. '
|
+ '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 '
|
+ 'All of it has been chosen for you — change anything you disagree with, or '
|
||||||
+ 'go straight to filming.</p>'
|
+ '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("")
|
+ s.segments.map(function (x) { return shotHtml(x, false); }).join("")
|
||||||
+ own
|
+ own
|
||||||
+ film;
|
+ film;
|
||||||
|
|
@ -521,6 +530,7 @@ function wire(locked) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
bindPics();
|
bindPics();
|
||||||
|
bindPreviz();
|
||||||
bindFilm((DATA && DATA.project) || {});
|
bindFilm((DATA && DATA.project) || {});
|
||||||
bindPost();
|
bindPost();
|
||||||
if (locked) { bindBottom(); return; }
|
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() {
|
function bindPost() {
|
||||||
loadTakes();
|
loadTakes();
|
||||||
// The editing bay is opt-in. Most people want to watch the thing and leave.
|
// The editing bay is opt-in. Most people want to watch the thing and leave.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue