Storyboard: choose the camera per shot (MiniMax H3, or Seedance 2.5 from Higgsfield or fal, to 30 s); takes say which camera, cost and wait

This commit is contained in:
gator 2026-10-12 02:14:12 +00:00
parent 72d811303d
commit b4543b9c13

View file

@ -444,7 +444,23 @@
// What a take really costs: the engine bills by the second (7 cents), five
// seconds at least, and a speaking shot runs its line plus the slate words.
function filmCost(x) { return Math.max(5, Math.ceil((Number(x.edit_seconds) || 5) + 1.5)) * 0.07; }
// rate: dollars a second; min/max: the lengths that camera can film.
var CAMERAS = { "": { name: "the usual camera", rate: 0.07, min: 5, max: 15 },
"hf-seedance-2.5-480p": { name: "Seedance 2.5 from Higgsfield, standard picture", rate: 0.2056, min: 4, max: 30 },
"hf-seedance-2.5-720p": { name: "Seedance 2.5 from Higgsfield, sharper picture", rate: 0.4622, min: 4, max: 30 },
"fal-seedance-2.5-480p": { name: "Seedance 2.5 from fal, standard picture", rate: 0.2205, min: 4, max: 30 },
"fal-seedance-2.5-720p": { name: "Seedance 2.5 from fal, sharper picture", rate: 0.4730, min: 4, max: 30 } };
CAMERAS["minimax-h3"] = CAMERAS[""];
function cameraOf(x) { return CAMERAS[(x && x.engine) || ""] || CAMERAS[""]; }
function filmCost(x) { var c = cameraOf(x);
return Math.min(c.max, Math.max(c.min, Math.ceil((Number(x.edit_seconds) || 5) + 1.5))) * c.rate; }
// "filmed on ... - $1.44 - ready in 95 s", for a shot or one of its takes.
function madeOn(t) {
if (!t || !t.filmed_on) return "";
return ' · ' + esc((CAMERAS[t.filmed_on] || { name: t.filmed_on }).name)
+ (Number(t.film_usd) ? ' · $' + Number(t.film_usd).toFixed(2) : '')
+ (Number(t.film_wait) ? ' · ready in ' + Math.round(Number(t.film_wait)) + ' s' : '');
}
function filmCostAll(list) { return list.reduce(function (a, x) { return a + filmCost(x); }, 0); }
function partsHtml(draft) {
@ -1299,6 +1315,14 @@
["native", "The camera makes the sound itself, in step with the action (a horn as the hand presses it, a bark as the mouth opens)"]].map(function (o) {
return '<option value="' + o[0] + '"' + ((s.sound_mode || "") === o[0] ? ' selected' : '') + '>' + o[1] + '</option>'; }).join("")
+ '</select></label>')
+ '<label style="margin-top:6px">Which camera films this shot '
+ '<select data-engine="' + esc(s.id) + '">'
+ ["", "hf-seedance-2.5-480p", "hf-seedance-2.5-720p", "fal-seedance-2.5-480p", "fal-seedance-2.5-720p"].map(function (k) {
var c = CAMERAS[k];
return '<option value="' + k + '"' + ((s.engine || "") === k ? ' selected' : '') + '>'
+ (k ? 'The long-take camera: ' + c.name : 'The usual camera')
+ ' — a shot up to ' + c.max + ' seconds, about $' + c.rate.toFixed(2) + ' a second</option>'; }).join("")
+ '</select></label>'
+ '</div>';
}
@ -1614,6 +1638,7 @@
media += '<div class="frames"><figure><video src="' + esc(s.clip_url) + '" controls '
+ 'preload="metadata"></video><figcaption>the filmed shot'
+ (s.clip_seconds ? ' &middot; ' + Number(s.clip_seconds).toFixed(1) + 's' : '')
+ madeOn(s)
+ (s.clip_stale ? ' &middot; <b style="color:#b4531f">filmed from an older picture — film it again</b>' : '')
+ '</figcaption></figure>'
+ (s.opening_url ? '<figure><img data-zoom="' + esc("shot " + s.id + " — the frame it was filmed from")
@ -1623,7 +1648,9 @@
var tk = (s.takes || []).slice().reverse();
if (tk.length) media += '<details class="takes" style="margin:4px 0 8px"><summary style="cursor:pointer;font-size:12px;color:var(--muted)">Earlier takes of this shot (' + tk.length + ')</summary><div class="frames">'
+ tk.map(function (t) { return '<figure><video src="' + esc(t.clip_url) + '" controls preload="none" style="max-width:220px"></video><figcaption>'
+ (t.replaced_at ? 'replaced ' + esc(String(t.replaced_at).replace("T", " ").slice(0, 16)) : '') + '<br><button type="button" class="btn small" data-usetake="' + esc(s.id) + '|' + esc(t.clip_url) + '">Use this take</button></figcaption></figure>'; }).join("")
+ (t.replaced_at ? 'replaced ' + esc(String(t.replaced_at).replace("T", " ").slice(0, 16)) : '')
+ (t.clip_seconds ? ' &middot; ' + Number(t.clip_seconds).toFixed(1) + 's' : '') + madeOn(t)
+ (t.verdict && t.verdict !== "no dialogue" ? ' &middot; words: ' + esc(t.verdict) : '') + '<br><button type="button" class="btn small" data-usetake="' + esc(s.id) + '|' + esc(t.clip_url) + '">Use this take</button></figcaption></figure>'; }).join("")
+ '</div></details>';
} else if (s.opening_url || s.closing_url) {
media += '<div class="frames">'
@ -1722,7 +1749,7 @@
+ '<input type="checkbox" data-floor="' + esc(s.id) + '"' + (s.floor ? ' checked' : '') + '> Cutting room floor</label>'
+ (s.floor ? '<div class="floornote">On the cutting room floor - left out of the film. Untick to put it back.</div>' : '')
+ '<div class="where">' + esc(s.setting || "") + ' &middot; '
+ '<span class="secs"><input type="number" step="0.5" min="3" max="15" value="' + Number(s.edit_seconds || 0).toFixed(1) + '" data-secs="' + esc(s.id) + '" style="width:58px;font:inherit;font-size:12px"> s '
+ '<span class="secs"><input type="number" step="0.5" min="3" max="' + cameraOf(s).max + '" value="' + Number(s.edit_seconds || 0).toFixed(1) + '" data-secs="' + esc(s.id) + '" style="width:58px;font:inherit;font-size:12px"> s '
+ '<button type="button" class="btn small ghost" data-estsecs="' + esc(s.id) + '" title="From the lines (speaking time plus a lead-in and a lead-out), or from the action for a silent shot">Estimate</button>'
+ '<button type="button" class="btn small ghost" data-setsecs="' + esc(s.id) + '">Set length</button>'
+ '<span class="tiny" data-secsnote="' + esc(s.id) + '"></span></span>'
@ -2333,6 +2360,15 @@
var el = e.target;
if (el.getAttribute && el.getAttribute("data-pagepick")) {
PAGE = Number(el.value) || 0; await refresh(); return; }
var esid = el.getAttribute && el.getAttribute("data-engine");
if (esid) {
var inp3 = document.querySelector('[data-filmnote="' + esid + '"]');
var r3 = await api("set_film_note", { draft_id: CUR, shot_id: esid, note: inp3 ? inp3.value : "", engine: el.value || "minimax-h3" });
await refresh();
if (r3.ok) showMsg("msg", "Saved. The price on the film button is now this camera’s. The take you have stays under Earlier takes when you film again, so the two can be compared.", "ok");
else showMsg("msg", r3.error || "That camera could not be chosen.");
return;
}
var ssid = el.getAttribute && el.getAttribute("data-soundmode");
if (ssid) {
var inp2 = document.querySelector('[data-filmnote="' + ssid + '"]');