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 @@

4 · How long

- 16 seconds - 32 seconds - 48 seconds + 2 shots + 4 shots + 6 shots
- 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) {