Add B-roll cutaways, seam measurement and final cut to the shot editor
This commit is contained in:
parent
dba954ad17
commit
c0a7f46094
1 changed files with 105 additions and 0 deletions
105
script.html
105
script.html
|
|
@ -88,6 +88,24 @@ function shotHtml(s, locked) {
|
||||||
: '<label class="mini">Which photo of them</label>'
|
: '<label class="mini">Which photo of them</label>'
|
||||||
+ '<select data-photo="' + s.index + '">' + photoOptions(s.photo_id) + '</select>';
|
+ '<select data-photo="' + s.index + '">' + photoOptions(s.photo_id) + '</select>';
|
||||||
|
|
||||||
|
// A cutaway belonging to this shot straddles the cut into the next one, so
|
||||||
|
// the registration jump happens off screen.
|
||||||
|
var br = s.broll || {};
|
||||||
|
var brollBlock = '';
|
||||||
|
if (br.subject) {
|
||||||
|
brollBlock = '<div style="margin-top:16px;padding-top:14px;border-top:1px solid var(--line)">'
|
||||||
|
+ '<label class="mini">Cutaway <span>— shown while this line is spoken, covering the cut</span></label>'
|
||||||
|
+ '<p class="foot" style="margin:0 0 8px">' + esc(br.subject) + ' · ' + esc(br.kind || 'image') + '</p>'
|
||||||
|
+ (s.broll_url
|
||||||
|
? (s.broll_kind === 'video'
|
||||||
|
? '<video src="' + esc(s.broll_url) + '" controls playsinline style="width:120px;border-radius:8px;border:1px solid var(--line)"></video>'
|
||||||
|
: '<img src="' + esc(s.broll_url) + '" alt="cutaway" style="width:120px;border-radius:8px;border:1px solid var(--line);display:block">')
|
||||||
|
: '')
|
||||||
|
+ (locked ? '' : '<button type="button" class="btn ghost" data-broll="' + s.index + '" style="margin-top:10px">'
|
||||||
|
+ (s.broll_url ? 'Remake cutaway' : 'Make cutaway') + '</button>')
|
||||||
|
+ '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
var controls = locked ? '' :
|
var controls = locked ? '' :
|
||||||
toggle
|
toggle
|
||||||
+ settingLabel
|
+ settingLabel
|
||||||
|
|
@ -109,6 +127,7 @@ function shotHtml(s, locked) {
|
||||||
+ '<p class="line">' + esc(s.line || "") + '</p>'
|
+ '<p class="line">' + esc(s.line || "") + '</p>'
|
||||||
+ '<p class="foot">' + (s.word_count || 0) + ' words · about ' + (s.est_seconds || 0) + 's</p>'
|
+ '<p class="foot">' + (s.word_count || 0) + ' words · about ' + (s.est_seconds || 0) + 's</p>'
|
||||||
+ '<div style="margin-top:16px">' + controls + '</div>'
|
+ '<div style="margin-top:16px">' + controls + '</div>'
|
||||||
|
+ brollBlock
|
||||||
+ '</div></div>';
|
+ '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,6 +170,22 @@ function render(r) {
|
||||||
var cams = '<datalist id="cams">' + PRESETS.map(function (c) {
|
var cams = '<datalist id="cams">' + PRESETS.map(function (c) {
|
||||||
return '<option value="' + esc(c) + '">'; }).join("") + '</datalist>';
|
return '<option value="' + esc(c) + '">'; }).join("") + '</datalist>';
|
||||||
|
|
||||||
|
// Post production only exists once there is footage to cut.
|
||||||
|
var post = '';
|
||||||
|
if (p.status === 'ready' || p.cut_url) {
|
||||||
|
post = '<h2 class="sec" style="margin-top:34px">The cut</h2><div class="card">'
|
||||||
|
+ (p.cut_url
|
||||||
|
? '<video src="' + esc(p.cut_url) + '" controls playsinline style="width:190px;aspect-ratio:9/16;border-radius:10px;border:1px solid var(--line);background:#000;display:block;margin-bottom:14px"></video>'
|
||||||
|
: '')
|
||||||
|
+ '<p class="foot" style="margin:0 0 12px">Cutaways are laid across the joins between shots, '
|
||||||
|
+ 'so the picture changes while the voice keeps going and the join is never on screen.</p>'
|
||||||
|
+ '<button type="button" class="btn ghost" id="seams">Measure the joins</button>'
|
||||||
|
+ '<button type="button" class="btn" id="cut" style="margin-left:10px">'
|
||||||
|
+ (p.cut_url ? 'Cut it again' : 'Cut the B-roll in') + '</button>'
|
||||||
|
+ '<div id="seamout" style="margin-top:14px"></div>'
|
||||||
|
+ '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
var actions = locked
|
var actions = locked
|
||||||
? '<div class="card"><p style="margin:0"><strong>Approved.</strong> Filming is switched on once payment is live. Nothing has been charged.</p></div>'
|
? '<div class="card"><p style="margin:0"><strong>Approved.</strong> Filming is switched on once payment is live. Nothing has been charged.</p></div>'
|
||||||
: '<div class="card">'
|
: '<div class="card">'
|
||||||
|
|
@ -167,12 +202,14 @@ function render(r) {
|
||||||
out.innerHTML = cams + head + warn + full
|
out.innerHTML = cams + head + warn + full
|
||||||
+ '<h2 class="sec">The shots</h2>'
|
+ '<h2 class="sec">The shots</h2>'
|
||||||
+ s.segments.map(function (x) { return shotHtml(x, locked); }).join("")
|
+ s.segments.map(function (x) { return shotHtml(x, locked); }).join("")
|
||||||
|
+ post
|
||||||
+ actions;
|
+ actions;
|
||||||
|
|
||||||
wire(locked);
|
wire(locked);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wire(locked) {
|
function wire(locked) {
|
||||||
|
bindPost();
|
||||||
if (locked) { bindBottom(); return; }
|
if (locked) { bindBottom(); return; }
|
||||||
|
|
||||||
// Save edits when the field loses focus, so typing is never interrupted.
|
// Save edits when the field loses focus, so typing is never interrupted.
|
||||||
|
|
@ -204,6 +241,29 @@ 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) {
|
[].forEach.call(document.querySelectorAll("[data-make]"), function (b) {
|
||||||
b.addEventListener("click", async function () {
|
b.addEventListener("click", async function () {
|
||||||
hideMsg("msg");
|
hideMsg("msg");
|
||||||
|
|
@ -249,6 +309,51 @@ function wire(locked) {
|
||||||
bindBottom();
|
bindBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bindPost() {
|
||||||
|
var sb = document.getElementById("seams");
|
||||||
|
if (sb) sb.onclick = async function () {
|
||||||
|
sb.disabled = true; sb.textContent = "Measuring…";
|
||||||
|
var r = await api("seam_report", { email: email, project_id: pid });
|
||||||
|
sb.disabled = false; sb.textContent = "Measure the joins";
|
||||||
|
var el = document.getElementById("seamout");
|
||||||
|
if (!r.ok) { showMsg("msg", r.error || "Could not measure."); return; }
|
||||||
|
if (!r.seams || !r.seams.length) {
|
||||||
|
el.innerHTML = '<p class="foot">' + esc(r.status_text || "Nothing to measure.") + '</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.innerHTML = '<p class="foot" style="margin:0 0 8px">' + esc(r.status_text || "") + '</p>'
|
||||||
|
+ r.seams.map(function (x) {
|
||||||
|
var col = x.verdict === "smooth" ? "var(--good)"
|
||||||
|
: (x.verdict === "visible" ? "var(--accent2)" : "var(--bad)");
|
||||||
|
return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">'
|
||||||
|
+ 'shot ' + x.after_shot + ' → ' + x.before_shot + ' at ' + x.at_seconds + 's · '
|
||||||
|
+ 'shift ' + x.shift_x + ',' + x.shift_y + ' · drift ' + x.drift + ' · '
|
||||||
|
+ '<span style="color:' + col + '">' + esc(x.verdict) + '</span>'
|
||||||
|
+ (x.recommend_broll ? ' · worth a cutaway' : '')
|
||||||
|
+ '</div>';
|
||||||
|
}).join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
var cb = document.getElementById("cut");
|
||||||
|
if (cb) cb.onclick = async function () {
|
||||||
|
hideMsg("msg");
|
||||||
|
cb.disabled = true; cb.textContent = "Cutting…";
|
||||||
|
var r = await api("render_cut", { email: email, project_id: pid });
|
||||||
|
if (!r.ok) { cb.disabled = false; cb.textContent = "Cut the B-roll in"; showMsg("msg", r.error || "Could not cut."); return; }
|
||||||
|
var waited = 0;
|
||||||
|
var t = setInterval(async function () {
|
||||||
|
waited += 5;
|
||||||
|
var g = await api("get_project", { email: email, project_id: pid });
|
||||||
|
if (g.ok && g.project && g.project.cut_url) { clearInterval(t); load(); return; }
|
||||||
|
if (waited > 300) {
|
||||||
|
clearInterval(t);
|
||||||
|
cb.disabled = false; cb.textContent = "Cut the B-roll in";
|
||||||
|
showMsg("msg", "That is taking unusually long. Try again shortly.");
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function bindBottom() {
|
function bindBottom() {
|
||||||
var rv = document.getElementById("revise");
|
var rv = document.getElementById("revise");
|
||||||
if (rv) rv.onclick = async function () {
|
if (rv) rv.onclick = async function () {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue