Play the storyboard: frames and narration, in order, at the top
Reviewing meant scrolling to a shot, pressing play, listening, scrolling to the next - which destroys exactly the flow a trailer is made of, so faults of sequence were the hardest to notice and the ones we kept shipping. This plays the whole thing: opening frame, then closing frame, while the shot's lines are spoken, then on to the next. Stop, or jump to any shot to leave a note. It is the trailer before it is filmed, for cents of speech rather than dollars of video.
This commit is contained in:
parent
70bf7758e3
commit
fc858729f3
1 changed files with 102 additions and 0 deletions
102
trailer.html
102
trailer.html
|
|
@ -432,6 +432,107 @@
|
||||||
return (Date.now() - t) < 15 * 60 * 1000;
|
return (Date.now() - t) < 15 * 60 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- the storyboard player -------------------------------------------
|
||||||
|
// One call fetches the whole reel; after that nothing touches the network,
|
||||||
|
// which is what makes stop and jump-to instant.
|
||||||
|
var REEL = null, RAT = -1, RTIMER = null;
|
||||||
|
var reelAudio = new Audio();
|
||||||
|
|
||||||
|
function reelHtml() {
|
||||||
|
var st = String(PKG.reel_state || "");
|
||||||
|
var shots = (PKG.shots || []);
|
||||||
|
if (!shots.length) return "";
|
||||||
|
var running = st && st !== "ready" && !/^failed/.test(st);
|
||||||
|
|
||||||
|
var out = '<div class="reel"><h4>Watch the storyboard</h4>'
|
||||||
|
+ '<p class="lede">The frames in order with the lines spoken over them — '
|
||||||
|
+ 'the trailer before it is filmed. Sequence problems are obvious here '
|
||||||
|
+ 'and nearly invisible in the script below.</p>';
|
||||||
|
|
||||||
|
if (running)
|
||||||
|
out += '<p class="busy">Speaking every shot… ' + esc(st) + '</p>';
|
||||||
|
else if (/^failed/.test(st))
|
||||||
|
out += '<p class="cont">' + esc(st) + '</p>';
|
||||||
|
|
||||||
|
if (!REEL) {
|
||||||
|
out += '<div class="tools" style="justify-content:center">'
|
||||||
|
+ '<button type="button" class="btn primary" data-reelgo="1"'
|
||||||
|
+ (running ? ' disabled' : '') + '>'
|
||||||
|
+ (st === "ready" ? "Play the storyboard" : "Prepare and play")
|
||||||
|
+ '</button></div></div>';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = REEL[RAT] || REEL[0] || {};
|
||||||
|
var lines = (s.lines || []).map(function (l) {
|
||||||
|
return '<div><span class="who">' + esc(l.who || "")
|
||||||
|
+ (l.lip_sync ? "" : " (v.o.)") + '</span>' + esc(l.text || "") + '</div>';
|
||||||
|
}).join("") || '<div class="w">no dialogue in this shot</div>';
|
||||||
|
|
||||||
|
out += '<div class="reelframes">'
|
||||||
|
+ '<figure><img id="rfa" class="on" src="' + esc(s.opening_url || "")
|
||||||
|
+ '" alt=""><figcaption>opens on</figcaption></figure>'
|
||||||
|
+ '<figure><img id="rfb" src="' + esc(s.closing_url || "")
|
||||||
|
+ '" alt=""><figcaption>ends on</figcaption></figure></div>'
|
||||||
|
+ '<div class="reelnow"><div class="t">' + esc(s.id) + ' · '
|
||||||
|
+ esc(s.title || "") + '</div>'
|
||||||
|
+ '<div class="w">' + esc(s.setting || "")
|
||||||
|
+ (s.scene ? ' · ' + esc(s.scene) : "")
|
||||||
|
+ (s.day ? ' · ' + esc(s.day) : "") + '</div>'
|
||||||
|
+ '<div class="said">' + lines + '</div></div>'
|
||||||
|
+ '<div class="reelprog"><i id="rprog"></i></div>'
|
||||||
|
+ '<div class="reelbar">'
|
||||||
|
+ '<button type="button" class="btn" data-reelstep="-1">◀ back</button>'
|
||||||
|
+ '<button type="button" class="btn primary" data-reeltoggle="1">'
|
||||||
|
+ (reelAudio.paused ? "Play" : "Pause") + '</button>'
|
||||||
|
+ '<button type="button" class="btn" data-reelstep="1">next ▶</button>'
|
||||||
|
+ '<button type="button" class="btn" data-reelstop="1">Stop</button>'
|
||||||
|
+ '<button type="button" class="btn" data-reeljump="' + esc(s.id)
|
||||||
|
+ '">Jump to this shot</button>'
|
||||||
|
+ '<span class="pos">' + (RAT + 1) + ' of ' + REEL.length + '</span>'
|
||||||
|
+ '</div></div>';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reelPaint() {
|
||||||
|
var host = document.getElementById("reelhost");
|
||||||
|
if (host) host.innerHTML = reelHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The two frames split the shot's running time: the opening frame holds for
|
||||||
|
// the first half, the closing frame for the second. It is a storyboard, not
|
||||||
|
// an animation, but it reads as movement and it marks where the shot lands.
|
||||||
|
function reelShow(i) {
|
||||||
|
if (!REEL || i < 0 || i >= REEL.length) { reelStop(); return; }
|
||||||
|
RAT = i;
|
||||||
|
reelPaint();
|
||||||
|
var s = REEL[i];
|
||||||
|
var dur = Math.max(Number(s.seconds) || 0, Number(s.edit_seconds) || 0, 2.5);
|
||||||
|
if (s.audio_url) { reelAudio.src = s.audio_url; reelAudio.play().catch(function(){}); }
|
||||||
|
else reelAudio.pause();
|
||||||
|
|
||||||
|
var t0 = Date.now(), swapped = false;
|
||||||
|
clearInterval(RTIMER);
|
||||||
|
RTIMER = setInterval(function () {
|
||||||
|
var gone = (Date.now() - t0) / 1000;
|
||||||
|
var bar = document.getElementById("rprog");
|
||||||
|
if (bar) bar.style.width = Math.min(100, 100 * gone / dur) + "%";
|
||||||
|
if (!swapped && gone > dur / 2) {
|
||||||
|
swapped = true;
|
||||||
|
var a = document.getElementById("rfa"), b = document.getElementById("rfb");
|
||||||
|
if (a && b && REEL[RAT].closing_url) { a.classList.remove("on"); b.classList.add("on"); }
|
||||||
|
}
|
||||||
|
if (gone >= dur) reelShow(RAT + 1);
|
||||||
|
}, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reelStop() {
|
||||||
|
clearInterval(RTIMER); RTIMER = null;
|
||||||
|
reelAudio.pause();
|
||||||
|
REEL = null; RAT = -1;
|
||||||
|
openTab(CUR);
|
||||||
|
}
|
||||||
|
|
||||||
// A cut is an ordering, not a render. Playing one is free, so all of them
|
// 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.
|
// can be looked at before anything is joined into a file.
|
||||||
function cutsHtml(meta) {
|
function cutsHtml(meta) {
|
||||||
|
|
@ -573,6 +674,7 @@
|
||||||
shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); });
|
shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); });
|
||||||
|
|
||||||
return head + probs + '<div class="bar">' + barHtml(meta) + '</div>'
|
return head + probs + '<div class="bar">' + barHtml(meta) + '</div>'
|
||||||
|
+ '<div id="reelhost">' + reelHtml() + '</div>'
|
||||||
+ spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + castHtml + body;
|
+ spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + castHtml + body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue