diff --git a/create.html b/create.html
index d339609..bae5de0 100644
--- a/create.html
+++ b/create.html
@@ -45,6 +45,12 @@
This decides the set, the framing and how it is written, before a word exists.
+
+
+ Different engines shoot different shot lengths and cost different amounts. Leave it alone if you are not sure.
+
+
+
Leave blank to use the format's own set. Real or not: a field of tall grass, floating on a surfboard.
@@ -64,11 +70,11 @@
- The piece is written as one continuous monologue, then cut into 8 second shots that flow into each other.
+ The piece is written as one continuous monologue, then cut into shots that flow into each other.
$8.85
32 seconds · 4 shots
@@ -136,11 +142,42 @@
navBar("create");
var email = requireSession();
var stance = "auto", segments = 4, havePets = false, FORMATS = [];
+// The render engines the server will accept. Shot length and price both come
+// from here rather than being assumed, because they differ per engine: Veo
+// shoots 8s, Omni 1.1 shoots 10s.
+var MODELS = [], MODEL = null;
+function modelOf(id) {
+ for (var i = 0; i < MODELS.length; i++) if (MODELS[i].id === id) return MODELS[i];
+ return null;
+}
+function shotSeconds() { return (MODEL && MODEL.shot_seconds) || 8; }
(async function loadFormats() {
var r = await api("camera_presets", {});
if (!r.ok || !r.formats) return;
FORMATS = r.formats;
+
+ // Models. An older worker returns none, in which case the control stays
+ // hidden and the submission omits the field, so nothing breaks.
+ MODELS = r.models || [];
+ var msel = document.getElementById("model");
+ if (MODELS.length) {
+ msel.innerHTML = MODELS.map(function (m) {
+ return '';
+ }).join("");
+ msel.value = r.default_model || MODELS[0].id;
+ MODEL = modelOf(msel.value);
+ msel.addEventListener("change", function () {
+ MODEL = modelOf(msel.value);
+ paintModel();
+ paint();
+ });
+ paintModel();
+ paint();
+ } else {
+ msel.closest(".field").style.display = "none";
+ }
+
var sel = document.getElementById("format");
sel.innerHTML = FORMATS.map(function (f) {
return '';
@@ -167,8 +204,28 @@ function chipGroup(id, cb) {
chipGroup("stance", function (v) { stance = v; });
chipGroup("len", function (v) { segments = parseInt(v, 10); paint(); });
+// What the chosen engine costs us per shot, marked up the same way the server
+// marks up. Mirrors quote_for(); if one changes, change both.
+function paintModel() {
+ var el = document.getElementById("modelnote");
+ el.textContent = MODEL ? (MODEL.shot_seconds + "s per shot. " + (MODEL.note || "")) : "";
+ var chips = document.querySelectorAll("#len .chip");
+ for (var i = 0; i < chips.length; i++) {
+ var nshots = parseInt(chips[i].getAttribute("data-v"), 10);
+ chips[i].textContent = nshots + " shots · " + (nshots * shotSeconds()) + "s";
+ }
+ document.getElementById("lenhint").textContent =
+ "The piece is written as one continuous monologue, then cut into "
+ + shotSeconds() + " second shots that flow into each other.";
+}
+
function paint() {
var p = priceFor(segments);
+ if (MODEL && MODEL.usd_per_second) {
+ var cost = segments * MODEL.shot_seconds * MODEL.usd_per_second + 0.17;
+ p = { price: Math.round(cost * 5 * 100) / 100,
+ seconds: segments * MODEL.shot_seconds };
+ }
document.getElementById("price").textContent = "$" + p.price.toFixed(2);
document.getElementById("plen").textContent = p.seconds + " seconds · " + segments + " shots";
}
@@ -364,6 +421,7 @@ document.getElementById("form").addEventListener("submit", async function (e) {
pet_name: opt ? opt.getAttribute("data-name") : "",
source_url: url, owner_notes: notes, scene: scene,
format: document.getElementById("format").value,
+ render_model: MODEL ? MODEL.id : "",
stance: stance, segments: segments, hold: true
});
if (!r.ok) {