motionrender 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4430,6 +4430,13 @@ var init_template = __esm({
4430
4430
  };
4431
4431
  templatesFor = (templates, size) => templates.filter((template) => sameShape(template, size));
4432
4432
  systemText = `You fill in ready-made video templates. Reply with JSON only: {"template": "<id>", "theme": "<theme id, only for templates that take a theme>", "values": {"<variable>": <value>, ...}}.
4433
+ Choose the template that best matches the story structure and topic of the brief (stories and stats -> story-tall; features and cards -> showcase-tall; tips and steps -> social-reel).
4434
+ For templates that take a theme, ALWAYS choose the theme that best matches the mood, industry, and visual tone of the brief:
4435
+ - "midnight": modern tech, SaaS, B2B, cloud, finance
4436
+ - "neon": gaming, nightlife, social apps, crypto, cutting-edge AI
4437
+ - "espresso": artisan craft, food & beverage, coffee, lifestyle, podcasts, warmth
4438
+ - "paper": clean editorial, education, publishing, thought leadership, light minimalist
4439
+ - "signal": high-impact, fitness, sports, breaking news, urgency
4433
4440
  Picture variables (image, image2\u2026) take the id of one supplied picture, or "" for none; never invent ids. Examples only show the length and kind of text wanted: never reuse their wording (including labels like INTRODUCING); use the brief's own names, numbers and details, and prefer a concrete line over a generic one such as "Ready to start?". Rules: write the words viewers will read, never a description of the video; do not invent dates, prices or claims that are not in the brief; use only the listed variable names; write real copy for the brief, about as long as each example (never much longer); "color" values are hex like "#f4d58d"; list values are arrays of objects with the listed fields.
4434
4441
  If no template suits the brief, reply {"template": "none"}.`;
4435
4442
  }
@@ -5970,17 +5977,31 @@ var init_plan = __esm({
5970
5977
  init_registry();
5971
5978
  paths = ["template", "json", "react"];
5972
5979
  allowedPaths = (options3) => (options3.paths ?? paths).filter((path) => path !== "react" || options3.allowReact);
5973
- systemText2 = (options3) => `You are the production planner for Open Video Engine. Decide how the brief below should be produced, before anything is generated.
5980
+ systemText2 = (options3) => `You are the creative production planner for Open Video Engine. Decide how the brief below should be produced, before anything is generated.
5974
5981
  Reply with ONE JSON object and nothing else:
5975
5982
  {
5976
5983
  "path": "template" | "json" | "react",
5977
- "template": "<template id>", // only with "template": the one template that suits the brief
5984
+ "template": "<template id>", // only with "template": the one template that best suits the brief's structure and topic
5978
5985
  "theme": "<theme id>", // optional, only with a template that takes a theme
5979
5986
  "idea": "<what the video says, in one line>",
5980
5987
  "beats": [{ "name": "Hook", "onScreen": "what is on screen, concretely", "seconds": 3, "motion": "how it moves in" }],
5981
5988
  "reasons": ["why this path suits this brief"]
5982
5989
  }
5983
- Templates are the primary path: choose "template" whenever one listed template suits the brief's kind and aspect ratio, even when its length differs \u2014 a template's length is its own, and a length difference is not a reason to leave the template route. Choose "json" only when no template fits the kind and aspect: a bespoke composition with scene layers, camera moves and kinetic type. Choose "react" only when allowed: it writes React code for bespoke high-end motion graphics. Every beat needs a concrete picture, not a mood.
5990
+ Templates provide proven layouts, but pick the template that specifically fits the brief's narrative style:
5991
+ - Testimonials, customer stories, quotes, metrics, or personal founder stories: choose "story-tall" or "quote-card".
5992
+ - Multi-feature tours, app UI cards, feature lists, and product showcases: choose "showcase-tall", "product-wide" or "product-card".
5993
+ - Quick tips, 3-step processes, how-to guides, and numbered reels: choose "social-reel".
5994
+ - Company keynotes, presentations, and product launch films: choose "keynote-wide", "launch-wide" or "case-study".
5995
+ - Choose "json" when the brief requires a bespoke kinetic video with custom motion graphics, camera moves, and scene architecture.
5996
+ - Choose "react" only when allowed: it writes React code for bespoke high-end motion graphics.
5997
+
5998
+ Themes: For templates that take a theme, ALWAYS choose the theme matching the tone and brand of the brief:
5999
+ - "midnight": modern tech, SaaS, B2B, cloud, finance (deep slate navy, sky blue).
6000
+ - "neon": gaming, nightlife, social apps, crypto, cutting-edge AI (dark violet, hot magenta, electric lime).
6001
+ - "espresso": artisan craft, food & beverage, coffee, lifestyle, podcasts, warmth (dark espresso, amber gold).
6002
+ - "paper": clean editorial, education, publishing, thought leadership (clean light aesthetic, deep ink).
6003
+ - "signal": high-impact, fitness, sports, breaking news, urgency (bold monochrome with alert red).
6004
+ Every beat needs a concrete picture, not a mood.
5984
6005
  Reply with "path" set to one of: ${allowedPaths(options3).join(" | ")}.
5985
6006
  Available capabilities:
5986
6007
  ${describeCapabilities(options3.registry, options3)}`;
@@ -6115,7 +6136,7 @@ async function cliRegistry() {
6115
6136
  async function composeSmart(provider, options3, mode = {}) {
6116
6137
  const needsFull = mode.full || !!options3.fields?.length || (options3.assets ?? []).some((asset) => asset.type !== "image");
6117
6138
  let plan;
6118
- if (!mode.noPlan) {
6139
+ if (!mode.noPlan && !mode.full && !mode.template) {
6119
6140
  const registry = mode.registry ?? await cliRegistry();
6120
6141
  try {
6121
6142
  plan = await planBuild(provider, { ...options3, registry, maxAttempts: Math.min(options3.maxAttempts ?? 2, 2) });
@@ -6123,13 +6144,16 @@ async function composeSmart(provider, options3, mode = {}) {
6123
6144
  options3.onProgress?.(`Planning skipped (${error.message.split("\n")[0]}); using the built-in routing.`);
6124
6145
  }
6125
6146
  }
6126
- const templatePath = plan ? plan.path === "template" : !needsFull;
6147
+ const templatePath = (plan ? plan.path === "template" : !needsFull) || Boolean(mode.template);
6127
6148
  if (templatePath && !needsFull) {
6128
6149
  try {
6129
6150
  const choices = await templateChoices();
6130
- const picked = plan?.template ? choices.filter((choice) => choice.id === plan.template) : choices;
6131
- const themes = plan?.theme ? (await listThemes()).filter((theme) => theme.id === plan.theme) : await listThemes();
6132
- const filled2 = await composeFromTemplate(provider, picked.length ? picked : choices, { ...options3, themes });
6151
+ const targetTemplateId = mode.template ?? plan?.template;
6152
+ const picked = targetTemplateId ? choices.filter((choice) => choice.id === targetTemplateId) : choices;
6153
+ const targetThemeId = mode.theme ?? plan?.theme;
6154
+ const allThemes = await listThemes();
6155
+ const themes = targetThemeId ? allThemes.filter((theme) => theme.id === targetThemeId) : allThemes;
6156
+ const filled2 = await composeFromTemplate(provider, picked.length ? picked : choices, { ...options3, themes: themes.length ? themes : allThemes });
6133
6157
  if (filled2) {
6134
6158
  const wanted = options3.durationSeconds;
6135
6159
  const current = filled2.composition.durationFrames / filled2.composition.fps;
@@ -7188,7 +7212,9 @@ function createHandlers(env) {
7188
7212
  maxAttempts: integerIn(input.maxAttempts, 3, "maxAttempts", 1, 10),
7189
7213
  onProgress: (message2) => progress(message2)
7190
7214
  };
7191
- const result = await capabilities.composeSmart(ai, options3, { full });
7215
+ const theme = typeof input.theme === "string" && input.theme.trim() ? input.theme.trim() : void 0;
7216
+ const template = typeof input.template === "string" && input.template.trim() ? input.template.trim() : void 0;
7217
+ const result = await capabilities.composeSmart(ai, options3, { full, theme, template });
7192
7218
  let composition = result.composition;
7193
7219
  let narration;
7194
7220
  if (options3.narration) {
@@ -56,6 +56,8 @@ export declare function composeSmart(provider: AiProvider, options: ComposeOptio
56
56
  full?: boolean;
57
57
  noPlan?: boolean;
58
58
  registry?: Registry;
59
+ theme?: string;
60
+ template?: string;
59
61
  }): Promise<ComposeResult & {
60
62
  template?: string;
61
63
  source?: Record<string, unknown>;
@@ -30,6 +30,9 @@ export interface JobPaths {
30
30
  export type JobCapabilities = {
31
31
  composeSmart(provider: AiProvider, options: ComposeOptions, mode: {
32
32
  full?: boolean;
33
+ noPlan?: boolean;
34
+ theme?: string;
35
+ template?: string;
33
36
  }): Promise<ComposeResult & {
34
37
  template?: string;
35
38
  source?: Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "motionrender",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "JSON-first, frame-deterministic motion graphics video engine: render compositions to MP4, WebM, ProRes, GIF or PNG frames from Node or the command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",