Generative video models are improving quickly, but a surprisingly ordinary problem still wastes a lot of time: the input brief changes every time someone rewrites it.
One prompt includes the lens and lighting. The next forgets the aspect ratio. A later revision changes the hero product from ceramic to glass. The model may be capable, but the production input is unstable.
For repeatable work, it helps to separate two jobs:
- deciding what the creative direction should be;
- translating that decision into a prompt and storyboard.
The first job is creative. The second can be deterministic.
In this tutorial, we will build a small browser app that turns a structured form into:
- a master image or video prompt;
- continuity constraints;
- a three-to-six-shot storyboard;
- downloadable JSON for the rest of a production workflow.
It uses no framework, no build step, and no AI API.
Model the brief as data
Start with the information that should remain stable throughout a generation session:
const brief = {
projectType: "video",
subject: "a matte ceramic skincare bottle on a mirrored plinth",
style: "premium editorial product film, tactile, photorealistic",
camera: "slow macro push-in with a 50 mm lens",
lighting: "soft cobalt edge light with a warm amber key",
requirements: "readable label and consistent bottle geometry",
negative: "warped typography, duplicated objects, flicker",
aspectRatio: "9:16",
shotCount: "4",
duration: "12 seconds"
};
This object does more than organize form values. It gives every downstream function the same source of truth.
Normalize input before composing a prompt
Form input often contains accidental line breaks and repeated spaces. A tiny normalizer keeps the output readable:
const clean = (value) =>
String(value || "")
.trim()
.replace(/\s+/g, " ");
Next, convert optional fields into labeled sentences:
const sentence = (label, value) => {
const normalized = clean(value);
return normalized ? `${label}: ${normalized}.` : "";
};
Now the prompt builder can be explicit without being repetitive:
function buildPrompt(input) {
const subject = clean(input.subject);
if (!subject) {
throw new Error("A core idea is required.");
}
const parts = [
`Create a cinematic AI video featuring ${subject}.`,
sentence("Visual direction", input.style),
sentence("Camera and composition", input.camera),
sentence("Lighting and color", input.lighting),
sentence("Required details", input.requirements),
sentence("Avoid", input.negative),
sentence("Delivery format", `${clean(input.aspectRatio)} aspect ratio`)
];
return parts.filter(Boolean).join(" ");
}
The result is deterministic: identical input produces identical output. That is useful when comparing models or regenerating a shot because the brief is no longer another moving variable.
Treat continuity as a first-class object
A storyboard is more useful when global constraints are stored separately from individual shots.
function buildStoryboard(input) {
const requestedCount = Number.parseInt(input.shotCount, 10);
const shotCount = Math.min(
6,
Math.max(3, Number.isFinite(requestedCount) ? requestedCount : 4)
);
const beats = [
"Establish the subject and environment",
"Move closer to reveal the defining details",
"Introduce a clear transformation or action",
"Show the strongest hero composition",
"Add a contrasting angle or supporting detail",
"Resolve on a clean final frame"
];
return {
aspectRatio: clean(input.aspectRatio) || "16:9",
totalDuration: clean(input.duration) || "12 seconds",
continuity: {
subject: clean(input.subject),
visualStyle: clean(input.style),
lighting: clean(input.lighting),
requirements: clean(input.requirements),
negativeConstraints: clean(input.negative)
},
shots: beats.slice(0, shotCount).map((purpose, index) => ({
shot: index + 1,
purpose,
visual: `${clean(input.subject)}; maintain the ${clean(input.style)} direction`,
camera: clean(input.camera),
transition:
index === shotCount - 1
? "hold on final frame"
: "clean motivated cut"
}))
};
}
There are two deliberate limits here:
- at least three shots, so the output has a beginning, development, and resolution;
- at most six shots, so a simple concept does not turn into an unmanageable production.
Those numbers are not universal. The important part is making the rule visible and testable.
Export JSON in the browser
The browser can generate a downloadable file without a server:
function downloadStoryboard(storyboard) {
const blob = new Blob([JSON.stringify(storyboard, null, 2)], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "creative-storyboard.json";
link.click();
URL.revokeObjectURL(url);
}
That JSON can be stored with a project, reviewed in a pull request, or transformed for another tool later.
Add small tests where inconsistency hurts
The prompt does not need snapshot tests for every word. Test the rules that matter:
import assert from "node:assert/strict";
import test from "node:test";
test("shot count is clamped to six", () => {
const storyboard = buildStoryboard({
subject: "a glass bottle",
shotCount: "12"
});
assert.equal(storyboard.shots.length, 6);
});
test("a subject is required", () => {
assert.throws(
() => buildPrompt({ projectType: "image" }),
/core idea is required/i
);
});
The built-in Node test runner is enough for a project this size.
Where generation tools fit
This starter does not replace an image or video model. It prepares a stable instruction set for one.
A practical workflow looks like this:
- agree on the brief in the local app;
- save the JSON with the project;
- use the master prompt for the first generation;
- generate individual shots while preserving the continuity object;
- change one variable at a time when comparing results.
For an integrated generation workspace, the finished brief can be used with the Vynyo AI Video Generator.
Repository
The complete dependency-free starter, tests, and Replit configuration are available in the public Vynyo Creative Brief Starter repository.
The larger lesson is simple: better generations do not always begin with a more complicated prompt. They often begin with a smaller set of decisions that stays consistent.
Top comments (0)