script: rebuild part 3 of 6
This commit is contained in:
parent
ab1027e6db
commit
0dabc60006
1 changed files with 210 additions and 0 deletions
210
script.html
210
script.html
|
|
@ -593,4 +593,214 @@ function wire(locked) {
|
|||
|
||||
[].forEach.call(document.querySelectorAll("[data-broll]"), function (b) {
|
||||
b.addEventListener("click", async function () {
|
||||
hideMsg("msg");
|
||||
var idx = parseInt(b.getAttribute("data-broll"), 10);
|
||||
b.disabled = true; b.textContent = "Making…";
|
||||
var r = await api("make_broll", { email: email, project_id: pid, index: idx });
|
||||
if (!r.ok) { b.disabled = false; b.textContent = "Make cutaway"; showMsg("msg", r.error || "Could not make that."); return; }
|
||||
var waited = 0;
|
||||
var t = setInterval(async function () {
|
||||
waited += 5;
|
||||
var g = await api("get_project", { email: email, project_id: pid });
|
||||
var seg = (g.ok && g.script && g.script.segments || []).filter(function (x) {
|
||||
return parseInt(x.index, 10) === idx; })[0];
|
||||
if (seg && seg.broll_url) { clearInterval(t); load(); return; }
|
||||
if (waited > 180) {
|
||||
clearInterval(t);
|
||||
b.disabled = false; b.textContent = "Make cutaway";
|
||||
showMsg("msg", "That cutaway is taking unusually long.");
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll("[data-make]"), function (b) {
|
||||
b.addEventListener("click", async function () {
|
||||
hideMsg("msg");
|
||||
var idx = parseInt(b.getAttribute("data-make"), 10);
|
||||
// Flush any pending edits in this card before drawing from them.
|
||||
var card = b.closest(".shot");
|
||||
var sc = card.querySelector("[data-scene]"), cm = card.querySelector("[data-cam]"),
|
||||
ph = card.querySelector("[data-photo]");
|
||||
await api("update_shot", { email: email, project_id: pid, index: idx,
|
||||
scene: sc ? sc.value : undefined, camera: cm ? cm.value : undefined,
|
||||
photo_id: ph ? ph.value : undefined });
|
||||
b.disabled = true; b.textContent = "Drawing…";
|
||||
var r = await api("make_shot_image", { email: email, project_id: pid, index: idx });
|
||||
if (!r.ok) {
|
||||
b.disabled = false; b.textContent = "Try again";
|
||||
showMsg("msg", r.error || "Could not make that frame.");
|
||||
return;
|
||||
}
|
||||
// Drawing takes about half a minute and runs on the server, so the call
|
||||
// above returns straight away. Watch the shot until the frame appears.
|
||||
var waited = 0;
|
||||
var tick = setInterval(async function () {
|
||||
waited += 4;
|
||||
var g = await api("get_project", { email: email, project_id: pid });
|
||||
var seg = (g.ok && g.script && g.script.segments || []).filter(function (x) {
|
||||
return parseInt(x.index, 10) === idx; })[0];
|
||||
if (seg && seg.draw_error) {
|
||||
clearInterval(tick);
|
||||
b.disabled = false; b.textContent = "Try again";
|
||||
showMsg("msg", "That frame did not draw: " + seg.draw_error);
|
||||
return;
|
||||
}
|
||||
if (seg && seg.plate_url) { clearInterval(tick); load(); return; }
|
||||
if (waited > 120) {
|
||||
clearInterval(tick);
|
||||
b.disabled = false; b.textContent = "Try again";
|
||||
showMsg("msg", "That frame is taking unusually long. Try again in a moment.");
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
});
|
||||
|
||||
bindBottom();
|
||||
}
|
||||
|
||||
async function bindFilm(p) {
|
||||
var bal = document.getElementById("balsec");
|
||||
if (bal) {
|
||||
var b = await api("get_balance", { email: email });
|
||||
var have = (b.ok && b.balance_seconds) || 0;
|
||||
var need = videoSecondsOf(p);
|
||||
bal.textContent = have + "s left" + (have < need ? " — not enough" : "");
|
||||
if (have < need) bal.style.color = "var(--bad)";
|
||||
}
|
||||
|
||||
// While it films, keep the page honest about where it has got to.
|
||||
if (p.status === "queued_to_film" || p.status === "filming") {
|
||||
var t = setInterval(async function () {
|
||||
var g = await api("get_project", { email: email, project_id: pid });
|
||||
if (!g.ok) return;
|
||||
var st = g.project.status;
|
||||
var note = document.getElementById("filmnote");
|
||||
if (note) note.textContent = g.project.film_note || st;
|
||||
if (st === "ready" || st === "film_failed") { clearInterval(t); load(); }
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
var fb = document.getElementById("film");
|
||||
if (fb) fb.onclick = async function () {
|
||||
hideMsg("msg");
|
||||
fb.disabled = true; fb.textContent = "Starting…";
|
||||
var r = await api("request_render", { email: email, project_id: pid });
|
||||
if (!r.ok) {
|
||||
fb.disabled = false; fb.textContent = "Film it";
|
||||
showMsg("msg", r.error || "Could not start filming.");
|
||||
return;
|
||||
}
|
||||
showMsg("msg", r.status_text || "Filming.", "ok");
|
||||
load();
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- the cut
|
||||
// EDL is {duration, cuts:[{shot,start,seconds,kind,src,in,moved}], segments}.
|
||||
// Only the cut positions are editable; the server composes the rest, so the
|
||||
// page never has to be trusted about source urls or trim points.
|
||||
var EDL = null, DIRTY = false;
|
||||
|
||||
function tlDraw() {
|
||||
var tl = document.getElementById("tl");
|
||||
if (!tl || !EDL) return;
|
||||
var dur = EDL.duration || 1;
|
||||
|
||||
// Above the bar: where each shot begins, with the frame it opens on. The end
|
||||
// of a shot is always the start of the next one, so only starts are marked.
|
||||
var strip = document.getElementById("tlshots");
|
||||
if (strip) {
|
||||
var segs = ((DATA && DATA.script && DATA.script.segments) || []);
|
||||
strip.innerHTML = segs.map(function (s, k) {
|
||||
var at = k * 8, wide = (8 / dur) * 100;
|
||||
return '<div class="tlshot" style="left:' + ((at / dur) * 100).toFixed(3) + '%;width:'
|
||||
+ wide.toFixed(3) + '%" data-seek="' + at + '" title="shot ' + s.index + ' starts at ' + at + 's">'
|
||||
+ 'shot ' + s.index
|
||||
+ (s.plate_url ? '<img src="' + esc(s.plate_url) + '" alt="shot ' + s.index + ' opening frame">' : '')
|
||||
+ '</div>';
|
||||
}).join("");
|
||||
}
|
||||
|
||||
var html = "";
|
||||
(EDL.cuts || []).forEach(function (c, i) {
|
||||
var left = (c.start / dur) * 100, wide = (c.seconds / dur) * 100;
|
||||
// Show the actual picture. A plain rectangle says nothing about what is
|
||||
// going to be on screen, which is the only thing worth judging here.
|
||||
var thumb = c.kind === "video"
|
||||
? '<video src="' + esc(c.src) + '#t=' + (c["in"] || 0) + '" preload="metadata" muted></video>'
|
||||
: '<img src="' + esc(c.src) + '" alt="">';
|
||||
html += '<div class="tlcut' + (c.moved ? ' moved' : '') + '" data-i="' + i + '" '
|
||||
+ 'style="left:' + left.toFixed(3) + '%;width:' + wide.toFixed(3) + '%" '
|
||||
+ 'title="cutaway over the end of shot ' + c.shot + '">'
|
||||
+ thumb + '<b>' + c.seconds + 's</b>'
|
||||
+ '<div class="grip" data-grip="' + i + '"></div></div>';
|
||||
});
|
||||
html += '<div class="tlhead" id="tlhead" style="left:0"></div>';
|
||||
html += '<div class="tlruler">';
|
||||
for (var t = 0; t <= dur; t += 8) {
|
||||
// The end labels are centred on the track edges, so half of each falls
|
||||
// outside it. Pin the first left and the last right instead.
|
||||
var pos = (t / dur) * 100;
|
||||
var style = t === 0 ? 'left:2px;transform:none'
|
||||
: (t >= dur ? 'right:2px;left:auto;transform:none'
|
||||
: 'left:' + pos.toFixed(2) + '%');
|
||||
html += '<span style="' + style + '">' + t + 's</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
tl.innerHTML = html;
|
||||
var n = (EDL.cuts || []).length;
|
||||
document.getElementById("tlinfo").textContent = n
|
||||
? n + " cutaway" + (n === 1 ? "" : "s") + " over " + dur + " seconds. Drag to move one, or pull its right edge to change how long it runs. The voice underneath never stops."
|
||||
: "No cutaways on this one, so the talking head runs the whole way.";
|
||||
document.getElementById("edlsave").disabled = !DIRTY;
|
||||
}
|
||||
|
||||
// Move the picture to a moment without disturbing the edit.
|
||||
function seekTo(t) {
|
||||
var v = document.getElementById("pvid");
|
||||
if (!v || !EDL) return;
|
||||
v.currentTime = Math.max(0, Math.min(t, EDL.duration || 0));
|
||||
v.dispatchEvent(new Event("timeupdate"));
|
||||
}
|
||||
|
||||
function tlBind() {
|
||||
var tl = document.getElementById("tl");
|
||||
if (!tl) return;
|
||||
var drag = null;
|
||||
|
||||
var strip = document.getElementById("tlshots");
|
||||
if (strip) strip.addEventListener("click", function (e) {
|
||||
var s = e.target.closest("[data-seek]");
|
||||
if (s) seekTo(parseFloat(s.getAttribute("data-seek")));
|
||||
});
|
||||
|
||||
function timeAt(clientX) {
|
||||
var r = tl.getBoundingClientRect();
|
||||
return ((clientX - r.left) / (r.width || 1)) * (EDL.duration || 0);
|
||||
}
|
||||
|
||||
tl.addEventListener("pointerdown", function (e) {
|
||||
var grip = e.target.closest("[data-grip]");
|
||||
var block = e.target.closest("[data-i]");
|
||||
if (!block) {
|
||||
// Empty track: scrub. This is how you get to a particular moment.
|
||||
drag = { mode: "seek" };
|
||||
seekTo(timeAt(e.clientX));
|
||||
tl.setPointerCapture(e.pointerId);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
var i = parseInt((grip || block).getAttribute(grip ? "data-grip" : "data-i"), 10);
|
||||
drag = { i: i, mode: grip ? "len" : "move", x0: e.clientX,
|
||||
start0: EDL.cuts[i].start, secs0: EDL.cuts[i].seconds };
|
||||
tl.setPointerCapture(e.pointerId);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
tl.addEventListener("pointermove", function (e) {
|
||||
if (!drag) return;
|
||||
if (drag.mode === "seek") { seekTo(timeAt(e.clientX)); return; }
|
||||
var dur = EDL.duration || 1, w = tl.clientWidth || 1;
|
||||
var d = ((e.clientX - drag.x0) / w) * dur;
|
||||
<!--PTLCHUNK-->
|
||||
Loading…
Reference in a new issue