Opening picture: the last frame of an earlier shot in the scene (shot / reverse shot)

This commit is contained in:
gator 2026-09-24 02:04:41 +00:00
parent dbe7d43ff4
commit 9ddb90033b

View file

@ -1501,6 +1501,20 @@
var i = all.findIndex(function (x) { return x.id === s.id; }); var i = all.findIndex(function (x) { return x.id === s.id; });
return i > 0 ? all[i - 1] : null; return i > 0 ? all[i - 1] : null;
} }
// Shot / reverse shot: Em, Dorothy, Em. The shot before faces the other
// way; the frame to open on is the last one of the earlier shot from the
// same angle. Offer every filmed shot earlier in the same scene.
function earlierHtml(s, p) {
var all = (PKG.shots || []).slice().sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); });
var i = all.findIndex(function (x) { return x.id === s.id; });
var opts = all.slice(Math.max(0, i - 12), i).filter(function (x) {
return x.id !== p.id && x.clip_url && (x.scene || "") === (s.scene || "");
}).reverse();
if (!opts.length) return "";
return ' <span style="white-space:nowrap">or shot <select id="lastof-' + esc(s.id) + '" style="font:inherit;font-size:12px">'
+ opts.map(function (x) { return '<option value="' + esc(x.id) + '">' + esc(x.id) + (x.title ? ' - ' + esc(String(x.title).slice(0, 28)) : '') + '</option>'; }).join("")
+ '</select> <button type="button" class="btn small ghost" data-useframe="lastof|' + esc(s.id) + '" title="For shot / reverse shot: open on the last frame of an earlier shot from the same angle">its last frame</button></span>';
}
function openingHtml(s) { function openingHtml(s) {
var p = prevShot(s); var p = prevShot(s);
if (!p) return ""; if (!p) return "";
@ -1508,6 +1522,7 @@
return '<div class="sceneshare" style="margin:4px 0 8px"><b style="color:var(--ink)">Opening picture</b>' return '<div class="sceneshare" style="margin:4px 0 8px"><b style="color:var(--ink)">Opening picture</b>'
+ '<button type="button" class="btn small ghost" data-useframe="first|' + esc(s.id) + '"' + (p.opening_url ? '' : ' disabled title="Shot ' + esc(p.id) + ' has no picture yet"') + '>Same as shot ' + esc(p.id) + '\'s first frame</button>' + '<button type="button" class="btn small ghost" data-useframe="first|' + esc(s.id) + '"' + (p.opening_url ? '' : ' disabled title="Shot ' + esc(p.id) + ' has no picture yet"') + '>Same as shot ' + esc(p.id) + '\'s first frame</button>'
+ '<button type="button" class="btn small ghost" data-useframe="last|' + esc(s.id) + '"' + (p.clip_url ? '' : ' disabled title="Shot ' + esc(p.id) + ' has not been filmed yet"') + '>Same as shot ' + esc(p.id) + '\'s last frame</button>' + '<button type="button" class="btn small ghost" data-useframe="last|' + esc(s.id) + '"' + (p.clip_url ? '' : ' disabled title="Shot ' + esc(p.id) + ' has not been filmed yet"') + '>Same as shot ' + esc(p.id) + '\'s last frame</button>'
+ earlierHtml(s, p)
+ from + from
+ ' <span style="margin-left:10px"><b style="color:var(--ink)">Motion</b> ' + ' <span style="margin-left:10px"><b style="color:var(--ink)">Motion</b> '
+ (s.motion_from ? '<span class="tiny">follows shot ' + esc(s.motion_from.shot) + '\'s clip (direction of travel, camera side, pace)</span> <button type="button" class="btn small ghost" data-useframe="nomotion|' + esc(s.id) + '">stop following</button>' + (s.motion_from ? '<span class="tiny">follows shot ' + esc(s.motion_from.shot) + '\'s clip (direction of travel, camera side, pace)</span> <button type="button" class="btn small ghost" data-useframe="nomotion|' + esc(s.id) + '">stop following</button>'
@ -1724,7 +1739,9 @@
if ((el = hit("data-useframe"))) { if ((el = hit("data-useframe"))) {
var uf = el.getAttribute("data-useframe").split("|"); var uf = el.getAttribute("data-useframe").split("|");
el.disabled = true; el.disabled = true;
r = await act("use_frame", { draft_id: CUR, shot_id: uf[1], frame: uf[0] }); var ufreq = { draft_id: CUR, shot_id: uf[1], frame: uf[0] };
if (uf[0] === "lastof") { var sel = document.getElementById("lastof-" + uf[1]); ufreq.frame = "last"; ufreq.from_shot = sel ? sel.value : ""; }
r = await act("use_frame", ufreq);
if (r) { showMsg("msg", r.frame === "motion" ? "Shot " + uf[1] + " will follow the motion of shot " + r.from + "'s clip when it is filmed." : r.frame === "nomotion" ? "Shot " + uf[1] + " no longer follows a clip." : "Shot " + uf[1] + " opens on the " + r.frame + " frame of shot " + r.from + (r.frame === "last" ? " - taking the frame from the clip, a few seconds." : "."), "ok"); await refresh(); } if (r) { showMsg("msg", r.frame === "motion" ? "Shot " + uf[1] + " will follow the motion of shot " + r.from + "'s clip when it is filmed." : r.frame === "nomotion" ? "Shot " + uf[1] + " no longer follows a clip." : "Shot " + uf[1] + " opens on the " + r.frame + " frame of shot " + r.from + (r.frame === "last" ? " - taking the frame from the clip, a few seconds." : "."), "ok"); await refresh(); }
else el.disabled = false; else el.disabled = false;
return; return;