@remixmate/cli 0.9.24 → 0.9.25
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/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"version": "0.9.25",
|
|
4
|
+
"generatedAt": "2026-09-08T15:32:17.519Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "export-jianying",
|
|
@@ -401,6 +401,13 @@
|
|
|
401
401
|
},
|
|
402
402
|
"description": "Media URLs for the template's image/video carousel. When provided together with a template_id whose capabilities.payloadStyle=carousel-caption, these URLs are placed directly into customPayload.carousel.items as existing assets — NO AI image generation is triggered. **Must** pass when the user explicitly provides image/video URLs for such a template. Each element is a full URL string."
|
|
403
403
|
},
|
|
404
|
+
"scene_images": {
|
|
405
|
+
"type": "array",
|
|
406
|
+
"items": {
|
|
407
|
+
"type": "string"
|
|
408
|
+
},
|
|
409
|
+
"description": "Per-scene visuals for a visual-overlay template (image-slide etc.) — the counterpart of carousel_items for templates that have real scenes. Each element is EITHER a full URL (use that existing asset for the scene, no gen-image call) OR the literal string 'ai' / 'ai:<prompt>' (leave that scene to gen-image; the text after the colon, when present, is the user's own image prompt for it). Mapping is POSITIONAL: element i is scene i, so an AI scene can sit anywhere in the order — pass ['url1','ai','url2'] verbatim when the user arranged it that way. Scenes past the last element still get an AI-generated image, so 2 images still produce a full video. When `scenes` is not set the scene count is raised (never lowered) to fit the elements, so nothing the user arranged is dropped and handing over fewer images does not shrink the video. **Must** pass when the user supplies images for such a template — otherwise their images are ignored entirely and every scene is AI-generated. Do NOT pass for carousel-caption templates; those take carousel_items."
|
|
410
|
+
},
|
|
404
411
|
"caption_lines": {
|
|
405
412
|
"type": "array",
|
|
406
413
|
"items": {
|
|
@@ -441,6 +448,7 @@
|
|
|
441
448
|
"headline",
|
|
442
449
|
"subheadline",
|
|
443
450
|
"carousel_items",
|
|
451
|
+
"scene_images",
|
|
444
452
|
"caption_lines",
|
|
445
453
|
"stub_image_url",
|
|
446
454
|
"stub_video_url",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.25",
|
|
4
4
|
"description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -200,6 +200,110 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
200
200
|
Every URL the user gave becomes one carousel item, in order. How long each item holds is the template's business — `fit-images` templates derive it from the item count, `fit-caption` templates from `capabilities.durationModel`; either way you pass the URLs and let the template decide.
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
## Agent behavior: user-supplied images for scene-based templates (`--scene-images`)
|
|
204
|
+
|
|
205
|
+
The section above covers `carousel-caption` templates. Templates whose `payloadStyle` is
|
|
206
|
+
`visual-overlay` (image-slide and friends) have **real scenes**, each with its own background
|
|
207
|
+
image and its own narration — for those, user-supplied images go through `--scene-images`,
|
|
208
|
+
not `--carousel-items`.
|
|
209
|
+
|
|
210
|
+
Without this flag the user's images are **ignored entirely** and every scene gets an
|
|
211
|
+
AI-generated picture. That failure is quiet: the video renders fine, it just isn't made of
|
|
212
|
+
the material the user handed you.
|
|
213
|
+
|
|
214
|
+
### The mapping rule (positional, then AI fills the rest)
|
|
215
|
+
|
|
216
|
+
Each entry is one scene's visual, and it is either a **URL** (use that existing asset) or the
|
|
217
|
+
literal **`ai`** / **`ai:<prompt>`** (leave that scene to gen-image). Entry i is scene i;
|
|
218
|
+
scenes past the last entry still get a generated image.
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
--scene-images A --scene-images B → scene 1: A, scene 2: B, scene 3+: AI-generated
|
|
222
|
+
--scene-images A --scene-images ai:机房 … → scene 1: A, scene 2: AI with the user's prompt
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Three consequences worth internalising:
|
|
226
|
+
|
|
227
|
+
- **Two images still produce a full video.** Do not ask the user to "provide enough images"
|
|
228
|
+
or pad the list; partial input is the designed case.
|
|
229
|
+
- **Order is meaning, not layout.** Pass the entries in the order the user gave them — an AI
|
|
230
|
+
scene can sit *between* two of their images, and that placement is the point.
|
|
231
|
+
- **`ai:<prompt>` is the user's own image prompt for that one scene.** Pass it through
|
|
232
|
+
verbatim; do not rewrite it, and do not apply it to the other scenes. It replaces the
|
|
233
|
+
prompt that would have been derived from the template, but the template's negative prompt
|
|
234
|
+
still applies.
|
|
235
|
+
|
|
236
|
+
When `--scenes` is absent the scene count is **raised, never lowered**, to fit the images:
|
|
237
|
+
more images than the planned scene count grows the plan so none are dropped, but fewer images
|
|
238
|
+
does **not** shrink it — handing over 2 images should not turn a 30s five-scene video into a
|
|
239
|
+
three-scene one. If the template's scene count is fixed (`sceneStrategy: single` / `fixed`)
|
|
240
|
+
and there are still more images than scenes, `gen_script.py` prints a warning naming how many
|
|
241
|
+
went unused — surface that to the user instead of pretending everything was used.
|
|
242
|
+
|
|
243
|
+
### Reading the image descriptions
|
|
244
|
+
|
|
245
|
+
Asset lines in the user's message carry a **`desc:` field** describing what is in the picture:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
 (1920×1080, desc: 一个人背对镜头站在雾中的松林里)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`desc:` is always the **last** item in the metadata parentheses, so everything from `desc:` to
|
|
252
|
+
the closing paren is the description — commas inside it are part of the text.
|
|
253
|
+
|
|
254
|
+
**Use it when writing narration.** This is the whole point of the field: for a
|
|
255
|
+
`visual-overlay` template you are writing the words that play over *that* picture, and the
|
|
256
|
+
description is the only thing telling you what the viewer will see. Narration that contradicts
|
|
257
|
+
the image is the most visible way this pipeline fails.
|
|
258
|
+
|
|
259
|
+
Two things `desc:` is **not**:
|
|
260
|
+
|
|
261
|
+
- It is **not an image-generation prompt.** The asset already exists; never route it through
|
|
262
|
+
`gen-image`, and never "improve" the picture to match the text.
|
|
263
|
+
- It is **not a headline.** Do not copy it onto the screen as `--headline` /
|
|
264
|
+
`--subheadline`; it is input for you, not on-screen copy.
|
|
265
|
+
|
|
266
|
+
A line with no `desc:` simply has no description — do not treat the file name in the alt text
|
|
267
|
+
(`4519.png`) as one.
|
|
268
|
+
|
|
269
|
+
### Reading `ai:` lines
|
|
270
|
+
|
|
271
|
+
A line in the asset list may be an **AI scene** rather than an asset:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
 (1920×1080, desc: 终端里正在跑安装命令的截图)
|
|
275
|
+
ai: 赛博朋克风格的服务器机房
|
|
276
|
+
 (1920×1080)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
That is the user saying "scene 2 is AI-generated, and here is what I want in it". Pass it
|
|
280
|
+
straight through as the second `--scene-images` entry (`ai: 赛博朋克风格的服务器机房`),
|
|
281
|
+
keeping the position. A bare `ai:` with no text means "this scene is AI-generated, you decide
|
|
282
|
+
what it shows" — still pass it, as `ai`, so the position is preserved.
|
|
283
|
+
|
|
284
|
+
Note the two are different fields on purpose: `desc:` describes an image that **already
|
|
285
|
+
exists** (input for your narration), `ai:` prescribes an image that **does not exist yet**
|
|
286
|
+
(input for gen-image). Never feed a `desc:` into gen-image, and never write narration that
|
|
287
|
+
describes an `ai:` prompt as if it were a picture you have seen.
|
|
288
|
+
|
|
289
|
+
### Command example
|
|
290
|
+
|
|
291
|
+
User: "用 image-slide 做一条讲 RAG 的视频,配图用这两张"
|
|
292
|
+
+ two asset lines with `desc:`
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
python3 <SkillDir>/scripts/gen_script.py \
|
|
296
|
+
--topic "三分钟看懂 RAG" \
|
|
297
|
+
--template-id image-slide \
|
|
298
|
+
--scene-images "https://cdn.example.com/a.png" \
|
|
299
|
+
--scene-images "ai: 赛博朋克风格的服务器机房" \
|
|
300
|
+
--scene-images "https://cdn.example.com/b.png"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Then write each scene's narration against that scene's image description, and pass the
|
|
304
|
+
narration through `prepare_video_assets`'s `dsl_json` as usual.
|
|
305
|
+
|
|
306
|
+
|
|
203
307
|
## Test mode: skip asset generation (`--stub-image-url` / `--stub-video-url`)
|
|
204
308
|
|
|
205
309
|
**Purpose**: during dev / debug the user wants to exercise the whole pipeline without burning gen-image / gen-video quota. In the DSL this becomes: image / video AssetRefs are written as `source:"existing"` + `status:"generated"` + `url:<stub>`, no `payload.prompt`; the downstream `prepare-video-assets` resolver skips the matching atomic skill.
|
|
@@ -305,6 +409,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
305
409
|
| `--stub-image-url` | Test mode: every image AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_IMAGE_URL`). | — |
|
|
306
410
|
| `--stub-video-url` | Test mode: every video AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_VIDEO_URL`). | — |
|
|
307
411
|
| `--carousel-items` | Repeatable. Media URL placed directly into `customPayload.carousel.items` for `carousel-caption` templates. Bypasses gen-image. **Required** for `carousel-caption` templates when the user supplies images. | — |
|
|
412
|
+
| `--scene-images` | Repeatable. One scene's visual for a **`visual-overlay`** template (image-slide etc.), mapped **positionally**. Each entry is a URL (that existing asset) or `ai` / `ai:<prompt>` (that scene goes to gen-image, optionally with the user's own prompt) — so an AI scene can sit anywhere in the order. Scenes past the last entry still get AI images. Scene count is raised (never lowered) to fit the entries when `--scenes` is absent. **Required** whenever the user supplies images for such a template — omitting it silently ignores every image they gave. | — |
|
|
308
413
|
| `--caption-lines` | Repeatable. On-screen caption line for `carousel-caption` templates → `customPayload.caption.lines`. Line limits and `**emphasis**` support are declared per template (see its `llmHint`). **Required** for `durationStrategy: fit-caption` templates — write the lines yourself if the user did not supply them. Optional only for `fit-images` templates. | — |
|
|
309
414
|
|
|
310
415
|
## DSL generation principles
|
|
@@ -313,6 +313,27 @@ def generate_image_prompt(
|
|
|
313
313
|
return {"prompt": prompts.get(purpose, f"{topic}相关配图{style_suffix}")}
|
|
314
314
|
|
|
315
315
|
|
|
316
|
+
def _parse_ai_slot(slot: str) -> tuple[bool, str]:
|
|
317
|
+
"""解析 --scene-images 的一项:是不是 AI 段,以及用户给的提示词。
|
|
318
|
+
|
|
319
|
+
返回 ``(is_ai, prompt)``:
|
|
320
|
+
|
|
321
|
+
- ``"https://…/a.png"`` → ``(False, "")`` 用已有素材
|
|
322
|
+
- ``"ai"`` → ``(True, "")`` 这一段交给 gen-image,提示词由模板推
|
|
323
|
+
- ``"ai: 深色调机房"`` → ``(True, "深色调机房")``
|
|
324
|
+
|
|
325
|
+
大小写不敏感。判定刻意收得很紧(整项等于 ``ai``,或以 ``ai:`` 开头):URL 才是这个
|
|
326
|
+
参数的常态,把 ``ai`` 判宽了会让 ``https://ai.example.com/x.png`` 这种正常地址被
|
|
327
|
+
误当成"生成一段",用户的图就此消失。
|
|
328
|
+
"""
|
|
329
|
+
text = (slot or "").strip()
|
|
330
|
+
if text.lower() == "ai":
|
|
331
|
+
return True, ""
|
|
332
|
+
if text[:3].lower() == "ai:":
|
|
333
|
+
return True, text[3:].strip()
|
|
334
|
+
return False, ""
|
|
335
|
+
|
|
336
|
+
|
|
316
337
|
def _load_template_config(template_id: str) -> dict | None:
|
|
317
338
|
"""Load template definition for the given template ID.
|
|
318
339
|
|
|
@@ -947,6 +968,7 @@ def build_dsl(
|
|
|
947
968
|
subheadline: str | None = None,
|
|
948
969
|
carousel_items: list[str] | None = None,
|
|
949
970
|
caption_lines: list[str] | None = None,
|
|
971
|
+
scene_images: list[str] | None = None,
|
|
950
972
|
) -> dict:
|
|
951
973
|
"""Build a complete Video DSL JSON.
|
|
952
974
|
|
|
@@ -965,6 +987,11 @@ def build_dsl(
|
|
|
965
987
|
通过 role=headline / role=subheadline 抽出对应的 props(如 titleText / projectName)。
|
|
966
988
|
注意:subtitle(CC 字幕)由 global.subtitle 与 render-plan.subtitleSegments
|
|
967
989
|
单独承载,与 subheadline 完全是两个东西,命名上刻意分开避免歧义。
|
|
990
|
+
|
|
991
|
+
scene_images 是用户**自带的配图**(visual-overlay 模板专用),按顺序占位:第 i 张
|
|
992
|
+
图给第 i 个场景,没被占到的场景照常走 gen-image 补图。这条规则不是随便定的——
|
|
993
|
+
见 docs/asset-annotation-design.md §6:另外两种立场(模型自由挑选 / 严格一一对应)
|
|
994
|
+
都会让"只传两张图"要么失去可预期性,要么直接跑不通。
|
|
968
995
|
"""
|
|
969
996
|
# Load template config(registry 单一数据源)→ 归一化 contract
|
|
970
997
|
template_config = _load_template_config(template_id) if template_id else None
|
|
@@ -1071,8 +1098,45 @@ def build_dsl(
|
|
|
1071
1098
|
return carousel_dsl
|
|
1072
1099
|
|
|
1073
1100
|
# ── 其余模板:统一场景规划(arc 叙事弧 / single / fixed)+ 统一装配循环 ──────
|
|
1101
|
+
#
|
|
1102
|
+
# scene_images 的每一项要么是一条 URL(用已有素材),要么是 `ai` / `ai:<提示词>`
|
|
1103
|
+
# (这一段留给 gen-image)。两者共用同一个位置序列 —— 用户在界面上排出来的顺序
|
|
1104
|
+
# 就是它,AI 段能插在任意位置,而不是只能挂在末尾。
|
|
1105
|
+
provided_slots = [s.strip() for s in (scene_images or []) if s and s.strip()]
|
|
1106
|
+
|
|
1074
1107
|
scene_plans = _plan_contract_scenes(contract, topic, duration, scene_count, output_language)
|
|
1075
1108
|
|
|
1109
|
+
# 条目比场景多时**抬高**场景数,让每一条都有地方放。
|
|
1110
|
+
#
|
|
1111
|
+
# 只抬高、不压低:给 2 张图不该把一条 30s 的片子从 5 段压成 3 段。用户给图表达的是
|
|
1112
|
+
# "这几张都要用上",不是"整条片子改成这么多段"——按条目数直接改写场景数,会让
|
|
1113
|
+
# "多传了两张图"变成"视频结构被换掉了",而他根本没要求这个。
|
|
1114
|
+
#
|
|
1115
|
+
# 反过来条目多于场景时必须抬:不抬的话第 N+1 条之后会被静默丢掉,而用户在成片里是
|
|
1116
|
+
# 看不出"我的图去哪了"的。arc 之外的策略场景数由模板钉死,抬不动(下面警告兜底)。
|
|
1117
|
+
if (
|
|
1118
|
+
provided_slots
|
|
1119
|
+
and scene_count is None
|
|
1120
|
+
and contract["scene_strategy"] == "arc"
|
|
1121
|
+
and primary_visual_type == "image"
|
|
1122
|
+
and has_visual
|
|
1123
|
+
and len(provided_slots) > len(scene_plans)
|
|
1124
|
+
):
|
|
1125
|
+
scene_plans = _plan_contract_scenes(
|
|
1126
|
+
contract, topic, duration, len(provided_slots), output_language
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
# 排完之后仍然装不下的(single / fixed 模板,或用户显式指定了更少的场景数)就明说。
|
|
1130
|
+
# 静默丢弃用户自带的素材是这条链上最难被发现的一类错。
|
|
1131
|
+
if provided_slots and len(provided_slots) > len(scene_plans):
|
|
1132
|
+
print(
|
|
1133
|
+
f"\u26a0\ufe0f {len(provided_slots)} scene-image entries provided but the template "
|
|
1134
|
+
f"plans only {len(scene_plans)} scene(s); the extra "
|
|
1135
|
+
f"{len(provided_slots) - len(scene_plans)} will not be used. "
|
|
1136
|
+
f"Raise --scenes or pick a template with more scenes.",
|
|
1137
|
+
file=sys.stderr,
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1076
1140
|
assets = []
|
|
1077
1141
|
scenes = []
|
|
1078
1142
|
|
|
@@ -1091,7 +1155,22 @@ def build_dsl(
|
|
|
1091
1155
|
|
|
1092
1156
|
# 仅 visual-overlay 模板生成视觉素材(slide 无视觉素材,carousel-caption 已提前返回)
|
|
1093
1157
|
if has_visual and primary_visual_type == "image":
|
|
1094
|
-
if
|
|
1158
|
+
slot = provided_slots[idx] if idx < len(provided_slots) else ""
|
|
1159
|
+
slot_is_ai, slot_prompt = _parse_ai_slot(slot)
|
|
1160
|
+
if slot and not slot_is_ai:
|
|
1161
|
+
# 用户自带的图排在 stub 之前:stub 是"别烧配额"的测试开关,而用户给的
|
|
1162
|
+
# 图同样一分钱不花,没有理由拿占位图把真素材盖掉。
|
|
1163
|
+
assets.append({
|
|
1164
|
+
"assetId": visual_asset_id,
|
|
1165
|
+
"type": "image",
|
|
1166
|
+
"source": "existing",
|
|
1167
|
+
"status": "generated",
|
|
1168
|
+
"url": slot,
|
|
1169
|
+
})
|
|
1170
|
+
# stub 盖得住 AI 段,盖不住用户自带的图。差别在于花不花钱:--stub-image-url
|
|
1171
|
+
# 的全部意义就是"这一趟别调 gen-image",而 AI 段恰恰是要调的那种;反过来
|
|
1172
|
+
# 用户自带的 URL 一分钱不花,拿占位图把真素材盖掉纯属损失。
|
|
1173
|
+
elif stub_image_url:
|
|
1095
1174
|
assets.append({
|
|
1096
1175
|
"assetId": visual_asset_id,
|
|
1097
1176
|
"type": "image",
|
|
@@ -1102,6 +1181,13 @@ def build_dsl(
|
|
|
1102
1181
|
else:
|
|
1103
1182
|
image_result = generate_image_prompt(plan["purpose"], topic, style, narration_text=narration_text)
|
|
1104
1183
|
|
|
1184
|
+
# 用户在 AI 段里写了提示词就用他的。**只替换主体,不动 negativePrompt /
|
|
1185
|
+
# guidanceScale**:那两项是模板的护栏("不要文字水印、不要变形"),把它们
|
|
1186
|
+
# 一起丢掉会让用户随手写的一句话换来一张带乱码文字的图,而他并没有要求
|
|
1187
|
+
# 关掉护栏——他只是想说这一段画什么。
|
|
1188
|
+
if slot_prompt:
|
|
1189
|
+
image_result = {**image_result, "prompt": slot_prompt}
|
|
1190
|
+
|
|
1105
1191
|
img_payload = {
|
|
1106
1192
|
"prompt": image_result["prompt"],
|
|
1107
1193
|
"model": os.environ.get("DEFAULT_IMAGE_MODEL", "doubao/doubao-seedream-5-0-260128"),
|
|
@@ -1383,6 +1469,20 @@ Examples:
|
|
|
1383
1469
|
"these URLs are placed directly into "
|
|
1384
1470
|
"customPayload.carousel.items as existing assets, bypassing AI image generation entirely.",
|
|
1385
1471
|
)
|
|
1472
|
+
parser.add_argument(
|
|
1473
|
+
"--scene-images",
|
|
1474
|
+
action="append",
|
|
1475
|
+
default=None,
|
|
1476
|
+
help="One scene's visual for a visual-overlay template (image-slide etc.). "
|
|
1477
|
+
"Can be repeated: --scene-images url1 --scene-images ai --scene-images url2. "
|
|
1478
|
+
"Each entry is either a URL (use that existing asset) or the literal 'ai' / "
|
|
1479
|
+
"'ai:<prompt>' (leave this scene to gen-image, optionally with the user's prompt). "
|
|
1480
|
+
"Mapping is POSITIONAL: the i-th entry is the i-th scene's background, so an AI "
|
|
1481
|
+
"scene can sit anywhere in the order, not just at the end; scenes past the last "
|
|
1482
|
+
"entry still get an AI-generated image. When --scenes is not given the scene count "
|
|
1483
|
+
"is raised (never lowered) to fit the entries. "
|
|
1484
|
+
"Not for carousel-caption templates — those take --carousel-items instead.",
|
|
1485
|
+
)
|
|
1386
1486
|
parser.add_argument(
|
|
1387
1487
|
"--caption-lines",
|
|
1388
1488
|
action="append",
|
|
@@ -1504,6 +1604,7 @@ Examples:
|
|
|
1504
1604
|
subheadline=args.subheadline,
|
|
1505
1605
|
carousel_items=args.carousel_items,
|
|
1506
1606
|
caption_lines=args.caption_lines,
|
|
1607
|
+
scene_images=args.scene_images,
|
|
1507
1608
|
)
|
|
1508
1609
|
|
|
1509
1610
|
# Post-process for --skip-asset-generation: rewrite all gen-voice / gen-digital-human
|
|
@@ -71,6 +71,13 @@
|
|
|
71
71
|
},
|
|
72
72
|
"description": "Media URLs for the template's image/video carousel. When provided together with a template_id whose capabilities.payloadStyle=carousel-caption, these URLs are placed directly into customPayload.carousel.items as existing assets — NO AI image generation is triggered. **Must** pass when the user explicitly provides image/video URLs for such a template. Each element is a full URL string."
|
|
73
73
|
},
|
|
74
|
+
"scene_images": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "string"
|
|
78
|
+
},
|
|
79
|
+
"description": "Per-scene visuals for a visual-overlay template (image-slide etc.) — the counterpart of carousel_items for templates that have real scenes. Each element is EITHER a full URL (use that existing asset for the scene, no gen-image call) OR the literal string 'ai' / 'ai:<prompt>' (leave that scene to gen-image; the text after the colon, when present, is the user's own image prompt for it). Mapping is POSITIONAL: element i is scene i, so an AI scene can sit anywhere in the order — pass ['url1','ai','url2'] verbatim when the user arranged it that way. Scenes past the last element still get an AI-generated image, so 2 images still produce a full video. When `scenes` is not set the scene count is raised (never lowered) to fit the elements, so nothing the user arranged is dropped and handing over fewer images does not shrink the video. **Must** pass when the user supplies images for such a template — otherwise their images are ignored entirely and every scene is AI-generated. Do NOT pass for carousel-caption templates; those take carousel_items."
|
|
80
|
+
},
|
|
74
81
|
"caption_lines": {
|
|
75
82
|
"type": "array",
|
|
76
83
|
"items": {
|
|
@@ -111,6 +118,7 @@
|
|
|
111
118
|
"headline",
|
|
112
119
|
"subheadline",
|
|
113
120
|
"carousel_items",
|
|
121
|
+
"scene_images",
|
|
114
122
|
"caption_lines",
|
|
115
123
|
"stub_image_url",
|
|
116
124
|
"stub_video_url",
|