diff --git a/script.html b/script.html index 30bd572..07862a8 100644 --- a/script.html +++ b/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.

' - + '

The shots

' + + '

Hear it first

' + + '
' + + '

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.

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

The shots

' + 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 = '
' + + 'opening still' + + '
' + + '' + + '' + + '' + + PZ.length + ' lines · ' + total.toFixed(1) + 's spoken, ' + + bought + 's of shots' + + (total > bought + ? '
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.
' + : '') + + (over.length + ? '
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.
' + : '') + + '
' + + PZ.map(function (x) { + return '
' + + 'shot ' + x.index + ' · ' + x.seconds.toFixed(1) + 's' + + (x.over ? ' over' : '') + + ' · ' + esc(x.line.slice(0, 64)) + '
'; + }).join("") + + '
'; + + 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.