@remixmate/cli 0.9.49 → 0.9.51
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/README.md +14 -26
- package/dist/manifest.json +18 -8
- package/package.json +9 -13
- package/skills/doc-parse/SKILL.md +6 -6
- package/skills/doc-parse/scripts/extract_via_render.py +3 -2
- package/skills/doc-parse/skill.json +1 -1
- package/skills/gen-script/SKILL.md +13 -11
- package/skills/gen-script/scripts/gen_script.py +106 -25
- package/skills/gen-script/skill.json +14 -4
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/SKILL.md +3 -3
- package/skills/prepare-video-assets/skill.json +1 -1
- package/skills/prepare-video-assets/version.json +1 -1
- package/skills/render-video/scripts/render_video.py +77 -44
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/README.md +3 -14
- package/skills/template-registry/SKILL.md +12 -49
- package/skills/template-registry/scripts/countdown_spec.py +56 -6
- package/skills/template-registry/scripts/registry_loader.py +3 -4
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/README.md +10 -53
- package/skills/template-registry/video_dsl/runtime/__init__.py +0 -1
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +44 -35
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +1 -1
- package/skills/template-registry/scripts/check_contracts.py +0 -179
- package/skills/template-registry/scripts/sync_registry.py +0 -44
- package/skills/template-registry/scripts/template_paths.py +0 -72
- package/skills/template-registry/scripts/verify_props_contract.py +0 -273
- package/skills/template-registry/video_dsl/runtime/template_binder.py +0 -87
package/README.md
CHANGED
|
@@ -259,32 +259,20 @@ npm run smoke
|
|
|
259
259
|
# CLI unit tests (argv parser + skill schema)
|
|
260
260
|
npm run test:cli
|
|
261
261
|
|
|
262
|
-
# ──
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
# template
|
|
266
|
-
npm run
|
|
267
|
-
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
npm run test:narration-speed
|
|
277
|
-
|
|
278
|
-
# L3 — selective, real Remotion render of chosen template(s). NOT full-library
|
|
279
|
-
# by design (rendering is slow + needs asset generation). Catches crashes /
|
|
280
|
-
# black frames the static layers can't. Needs PRIV_TOKEN for narration TTS;
|
|
281
|
-
# pass --stub-image-url to keep image-heavy renders cheap.
|
|
282
|
-
npm run check:render -- --template spotlight-card --frame
|
|
283
|
-
|
|
284
|
-
# Template defs come from the registry (ab-api HTTP + /tmp cache), exactly like
|
|
285
|
-
# render_video in production. A warm cache or reachable ab-api is required.
|
|
286
|
-
# In a multi-repo checkout the scripts auto-detect template-library; override
|
|
287
|
-
# with AB_TEMPLATES_SRC=/path/to/template-library/packages/templates/src.
|
|
262
|
+
# ── Unit tests for the Python pipeline ──
|
|
263
|
+
# Fully offline. They exercise the CLI's generic mechanisms against the synthetic
|
|
264
|
+
# templates in scripts/fixtures/ — never against a real template library, so
|
|
265
|
+
# template changes cannot turn them red.
|
|
266
|
+
npm run test:unit
|
|
267
|
+
|
|
268
|
+
# ── Integration: run the CLI against a real template library ──
|
|
269
|
+
# Validation, binding, RenderPlan and props contracts over every template (and
|
|
270
|
+
# dsl-example) in AB_TEMPLATES_SRC. Names no template; needs no ab-api or login.
|
|
271
|
+
AB_TEMPLATES_SRC=/path/to/template-library/packages/templates/src npm run integration
|
|
272
|
+
|
|
273
|
+
# Selective real Remotion render of chosen template(s). Needs PRIV_TOKEN for
|
|
274
|
+
# narration TTS; pass --stub-image-url to keep image-heavy renders cheap.
|
|
275
|
+
AB_TEMPLATES_SRC=... npm run integration:render -- --template <id> --frame
|
|
288
276
|
|
|
289
277
|
# Python skills forward --help to the underlying script:
|
|
290
278
|
remixmate gen-script --help
|
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.51",
|
|
4
|
+
"generatedAt": "2026-09-27T12:37:01.962Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"tier": "tool",
|
|
10
10
|
"category": "consuming",
|
|
11
11
|
"title": "Document Extraction",
|
|
12
|
-
"summary": "Extract a document (.pptx) into structured data: per-page title and body text, the speaker notes, and — when asked for the layout layer — the exact shapes, coordinates, colors and animation beats of every slide. Runs server-side (ab-render /extract); nothing is installed locally and no model is involved, so the result is deterministic. Two layers via `fields`: `content` is lightweight text (title / text / rawNotes) and is what you want for narration or for summarizing a deck; `layout` additionally returns the slide payload that
|
|
12
|
+
"summary": "Extract a document (.pptx) into structured data: per-page title and body text, the speaker notes, and — when asked for the layout layer — the exact shapes, coordinates, colors and animation beats of every slide. Runs server-side (ab-render /extract); nothing is installed locally and no model is involved, so the result is deterministic. Two layers via `fields`: `content` is lightweight text (title / text / rawNotes) and is what you want for narration or for summarizing a deck; `layout` additionally returns the per-page slide payload that templates declaring capabilities.payloadHydration (docKind pptx) render one-to-one from the original file — it embeds every icon as a data URI and is large, so it is written to a file rather than printed.",
|
|
13
13
|
"triggers": [
|
|
14
14
|
"Turn this PPT / slide deck into a video",
|
|
15
15
|
"Extract text or speaker notes from a .pptx",
|
|
@@ -437,7 +437,7 @@
|
|
|
437
437
|
},
|
|
438
438
|
"scenes": {
|
|
439
439
|
"type": "number",
|
|
440
|
-
"description": "Scene count. Only applies to templates with capabilities.sceneStrategy=arc (the default); single / fixed / countdown templates own their own count. Bounded below by 3 and above by the template's capabilities.maxScenes (default 20) — a clamp is reported on stderr and is NOT an error, so always read the real count back from the returned DSL (scenes[].length) before describing the script to the user. For a template that maps one scene per source page (
|
|
440
|
+
"description": "Scene count. Only applies to templates with capabilities.sceneStrategy=arc (the default); single / fixed / countdown templates own their own count. Bounded below by 3 and above by the template's capabilities.maxScenes (default 20) — a clamp is reported on stderr and is NOT an error, so always read the real count back from the returned DSL (scenes[].length) before describing the script to the user. For a template that maps one scene per source page (capabilities.payloadHydration), pass the page count you saw in doc_parse's summary."
|
|
441
441
|
},
|
|
442
442
|
"voice_id": {
|
|
443
443
|
"type": "string",
|
|
@@ -447,13 +447,21 @@
|
|
|
447
447
|
"type": "number",
|
|
448
448
|
"description": "Narration speech rate, 0.5-2.0 (default 1.0). Stored at global.narration.speed and applied when prepare_video_assets synthesizes the voice-over. **Must** pass when the user picked a speech rate — a template's 语速 / speed parameter arrives as a multiplier like 1.1, and without this flag it is silently dropped and the narration comes back at 1.0. Above ~1.3 subtitle alignment drifts and the delivery sounds mechanical; suggest shortening the script instead of raising it further."
|
|
449
449
|
},
|
|
450
|
+
"variant": {
|
|
451
|
+
"type": "string",
|
|
452
|
+
"description": "Template visual variant id (a key of the template's variants; template_registry lists them as variantIds). Written to meta.templateVariant, which beats the fuzzy `style` match. **Must** pass when the user picked a style: the replicate card shows it as `名字(id)` — e.g. `视觉风格: 紫色科技(tech-purple)` or `板面颜色: 黑板(black-board)` → pass the id in the parentheses. Without it the choice is silently dropped. Requires template_id; an id the template does not declare is rejected."
|
|
453
|
+
},
|
|
454
|
+
"background_preset": {
|
|
455
|
+
"type": "string",
|
|
456
|
+
"description": "Scene background preset, one of the template's customPayloadSchema.background.preset values. Written to every scene's customPayload.background.preset. **Must** pass when the user picked a background: the replicate card shows `背景样式: 纯色(solid)` → pass `solid`. Without it the scene keeps the template default. Requires template_id; unsupported values are rejected. `image` / `video` also need customPayload.background.assetRef on each scene."
|
|
457
|
+
},
|
|
450
458
|
"template_id": {
|
|
451
459
|
"type": "string",
|
|
452
|
-
"description": "Template id (
|
|
460
|
+
"description": "Template id (from template_registry). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL."
|
|
453
461
|
},
|
|
454
462
|
"source_document_url": {
|
|
455
463
|
"type": "string",
|
|
456
|
-
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration
|
|
464
|
+
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration). **Must** pass it for such a template: it is recorded at meta.sourceDocument, and prepare_video_assets then fetches the layout itself and fills the customPayload field the template declares (payloadHydration.payloadField) on every scene. Pass the same URL you gave doc_parse. You are NOT expected to author or even read that layout data — one page is hundreds of KB, so it never travels through the conversation; without this reference the DSL is rejected downstream after you have already written all the narration."
|
|
457
465
|
},
|
|
458
466
|
"headline": {
|
|
459
467
|
"type": "string",
|
|
@@ -475,7 +483,7 @@
|
|
|
475
483
|
"items": {
|
|
476
484
|
"type": "string"
|
|
477
485
|
},
|
|
478
|
-
"description": "Per-scene visuals for a visual-overlay template (
|
|
486
|
+
"description": "Per-scene visuals for a visual-overlay template (capabilities.payloadStyle=visual-overlay) — 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."
|
|
479
487
|
},
|
|
480
488
|
"caption_lines": {
|
|
481
489
|
"type": "array",
|
|
@@ -520,6 +528,8 @@
|
|
|
520
528
|
"scenes",
|
|
521
529
|
"voice_id",
|
|
522
530
|
"speed",
|
|
531
|
+
"variant",
|
|
532
|
+
"background_preset",
|
|
523
533
|
"template_id",
|
|
524
534
|
"source_document_url",
|
|
525
535
|
"headline",
|
|
@@ -784,7 +794,7 @@
|
|
|
784
794
|
},
|
|
785
795
|
"source_document_url": {
|
|
786
796
|
"type": "string",
|
|
787
|
-
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration
|
|
797
|
+
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration). Normally unnecessary: gen_script already recorded it at meta.sourceDocument when you passed source_document_url there, and the cached skeleton carries it through. Pass it here only to recover when the skeleton lacks the reference — it saves a full gen_script round trip. The layout data is fetched here and written into the customPayload field the template declares (payloadHydration.payloadField) on every scene; you neither author nor see it."
|
|
788
798
|
},
|
|
789
799
|
"binding_json": {
|
|
790
800
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.51",
|
|
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",
|
|
@@ -20,29 +20,25 @@
|
|
|
20
20
|
"list": "node dist/cli.js --list",
|
|
21
21
|
"smoke": "node scripts/smoke.mjs",
|
|
22
22
|
"test:cli": "npm run build && node --test test/*.test.mjs",
|
|
23
|
-
"test:validators": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-validators.py",
|
|
24
23
|
"test:web-read": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-web-read.py",
|
|
25
24
|
"test:web-read:browser": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-web-read-browser.py",
|
|
26
|
-
"test:template-pipeline": "PYTHONDONTWRITEBYTECODE=1 node dist/cli.js exec -- python3 scripts/test-template-pipeline.py",
|
|
27
|
-
"test:props-contract": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-props-contract.py",
|
|
28
|
-
"test:card-array-keys": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-card-array-keys.py",
|
|
29
25
|
"test:narration-speed": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-narration-speed.py",
|
|
26
|
+
"test:template-look": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-template-look.py",
|
|
30
27
|
"test:narration-tts": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-narration-tts.py",
|
|
31
28
|
"test:render-resume": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-render-resume.py",
|
|
32
29
|
"test:required-props": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-required-props.py",
|
|
33
|
-
"test:render-plan": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-render-plan-snapshot.py",
|
|
34
|
-
"test:render-plan:update": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-render-plan-snapshot.py --update",
|
|
35
|
-
"test:contracts": "PYTHONDONTWRITEBYTECODE=1 python3 skills/template-registry/scripts/check_contracts.py",
|
|
36
|
-
"regress": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/regress.py",
|
|
37
|
-
"regress:update": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/regress.py --update",
|
|
38
|
-
"check:render": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/render-template-check.py",
|
|
39
30
|
"clean": "node scripts/clean.mjs",
|
|
40
31
|
"test:countdown": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-countdown-strategy.py",
|
|
41
32
|
"test:prepare-contracts": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-prepare-contracts.py",
|
|
42
33
|
"test:image-sequence": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-image-sequence.py",
|
|
43
|
-
"test:ranking-count": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-ranking-count.py",
|
|
44
34
|
"test:image-prompt-compose": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-image-prompt-compose.py",
|
|
45
|
-
"test:
|
|
35
|
+
"test:page-contracts": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-page-contracts.py",
|
|
36
|
+
"test:declared-keys": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-declared-keys.py",
|
|
37
|
+
"test:payload-hydration": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-payload-hydration.py",
|
|
38
|
+
"test:sentence-alignment": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-sentence-alignment.py",
|
|
39
|
+
"test:unit": "npm run -s test:narration-speed && npm run -s test:template-look && npm run -s test:narration-tts && npm run -s test:render-resume && npm run -s test:required-props && npm run -s test:countdown && npm run -s test:prepare-contracts && npm run -s test:image-sequence && npm run -s test:image-prompt-compose && npm run -s test:page-contracts && npm run -s test:declared-keys && npm run -s test:payload-hydration && npm run -s test:sentence-alignment",
|
|
40
|
+
"integration": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/integration/regress.py",
|
|
41
|
+
"integration:render": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/integration/render-template-check.py"
|
|
46
42
|
},
|
|
47
43
|
"keywords": [
|
|
48
44
|
"claude-code",
|
|
@@ -5,7 +5,7 @@ description: |
|
|
|
5
5
|
|
|
6
6
|
Two layers, picked with `--fields`:
|
|
7
7
|
- **content** — per-page `title`, `text[]`, and `rawNotes` (the speaker notes). Small, no base64. This is the narration source material.
|
|
8
|
-
- **layout** — the full `slide` payload
|
|
8
|
+
- **layout** — the full per-page `slide` payload: every shape, coordinate, color, clip path and the animation `beats`, plus the `mode` verdict. This is what templates declaring `capabilities.payloadHydration` with `docKind: pptx` render from. Large (icons are embedded as data URIs).
|
|
9
9
|
|
|
10
10
|
Deterministic: the endpoint parses OOXML, it does **not** call a model. Notes come back verbatim — rewriting them into narration is your job, not the endpoint's.
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ description: |
|
|
|
14
14
|
- Read / extract / summarize a .pptx, pull the text or the speaker notes out of a deck
|
|
15
15
|
- Use a PowerPoint file as source material for a script
|
|
16
16
|
|
|
17
|
-
You call this skill for the **words** (notes, body text) and for the **page inventory** (how many pages, each page's animation tier and beat count). You do not call it to obtain
|
|
17
|
+
You call this skill for the **words** (notes, body text) and for the **page inventory** (how many pages, each page's animation tier and beat count). You do not call it to obtain the layout payload for a template — that payload never passes through you; see "Using the result".
|
|
18
18
|
triggers:
|
|
19
19
|
- Turn this PPT / slide deck into a video
|
|
20
20
|
- Extract text or speaker notes from a .pptx
|
|
@@ -108,12 +108,12 @@ So the picture travels **by reference**:
|
|
|
108
108
|
doc_parse --url "https://…/deck.pptx"
|
|
109
109
|
|
|
110
110
|
# 2. pass the SAME url to gen_script, plus one scene per page
|
|
111
|
-
gen_script --template-id
|
|
111
|
+
gen_script --template-id <templateId> \
|
|
112
112
|
--source-document-url "https://…/deck.pptx" \
|
|
113
113
|
--scenes 23
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
`gen_script` records
|
|
116
|
+
`<templateId>` is a template that declares `capabilities.payloadHydration` with `docKind: pptx`. `gen_script` records the URL at `meta.sourceDocument`, and `prepare_video_assets` then fetches the layout itself and fills every scene's `customPayload.<payloadHydration.payloadField>` before validation. You never see that data, and you never hand-write it.
|
|
117
117
|
|
|
118
118
|
What you *do* own from this result:
|
|
119
119
|
|
|
@@ -124,9 +124,9 @@ What you *do* own from this result:
|
|
|
124
124
|
| `mode` + beat count per page | cut the narration into that many sentences |
|
|
125
125
|
| `flags` | tell the user which elements the extractor could not reconstruct |
|
|
126
126
|
|
|
127
|
-
Do not
|
|
127
|
+
Do not second-guess `mode`: the verdict comes from the extractor, which can see reconstruction problems you cannot (`modeReason` says why). Whether and how a template lets you override it is in that template's `llmHint`.
|
|
128
128
|
|
|
129
|
-
> If `prepare_video_assets` ever
|
|
129
|
+
> If `prepare_video_assets` ever reports that hydrated field as `(required)`, the missing thing is not the payload — it is the **reference**. Re-run `gen_script` with `--source-document-url`, or pass `source_document_url` to `prepare_video_assets` to recover without regenerating the script.
|
|
130
130
|
|
|
131
131
|
**For the words** — `rawNotes` and `text` are two different kinds of raw material, and they need different treatment:
|
|
132
132
|
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
⚠️ 关于 layout 层的用法,看清楚这一条:**它不是给你读的**。在 ab-agent 里模型没有
|
|
18
18
|
任何文件读取工具(这是有意的安全收窄),所以落盘的那个文件你打不开。需要 layout 的
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
模板(声明了 capabilities.payloadHydration)按引用取数:把同一个文档 URL 传给
|
|
20
|
+
``gen_script --source-document-url``,渲染管线会自己把每一页填进模板声明的 customPayload
|
|
21
|
+
字段。见 SKILL.md「Using the result」。
|
|
21
22
|
|
|
22
23
|
必要环境变量(由 ab-agent 自动注入):
|
|
23
24
|
RENDER_API_URL ab-render 服务地址(默认 https://api-render.remixmate.ai)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"tier": "tool",
|
|
5
5
|
"category": "consuming",
|
|
6
6
|
"title": "Document Extraction",
|
|
7
|
-
"description": "Extract a document (.pptx) into structured data: per-page title and body text, the speaker notes, and — when asked for the layout layer — the exact shapes, coordinates, colors and animation beats of every slide. Runs server-side (ab-render /extract); nothing is installed locally and no model is involved, so the result is deterministic. Two layers via `fields`: `content` is lightweight text (title / text / rawNotes) and is what you want for narration or for summarizing a deck; `layout` additionally returns the slide payload that
|
|
7
|
+
"description": "Extract a document (.pptx) into structured data: per-page title and body text, the speaker notes, and — when asked for the layout layer — the exact shapes, coordinates, colors and animation beats of every slide. Runs server-side (ab-render /extract); nothing is installed locally and no model is involved, so the result is deterministic. Two layers via `fields`: `content` is lightweight text (title / text / rawNotes) and is what you want for narration or for summarizing a deck; `layout` additionally returns the per-page slide payload that templates declaring capabilities.payloadHydration (docKind pptx) render one-to-one from the original file — it embeds every icon as a data URI and is large, so it is written to a file rather than printed.",
|
|
8
8
|
"auth": "required",
|
|
9
9
|
"envVars": [
|
|
10
10
|
"RENDER_API_URL",
|
|
@@ -64,12 +64,12 @@ The intent is to avoid the failure mode where "the generic DSL looks compatible
|
|
|
64
64
|
|
|
65
65
|
### Document-driven templates: pass the source URL, don't author the payload
|
|
66
66
|
|
|
67
|
-
Some templates build each scene's picture from a source file rather than from JSON you write. They declare it as `capabilities.payloadHydration`
|
|
67
|
+
Some templates build each scene's picture from a source file rather than from JSON you write. They declare it as `capabilities.payloadHydration` — `payloadField` names the `customPayload` field the pipeline fills, `docKind` the kind of source file (e.g. `.pptx`).
|
|
68
68
|
|
|
69
69
|
For those, pass **`--source-document-url`** — the same URL you gave `doc_parse`:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
gen_script --template-id
|
|
72
|
+
gen_script --template-id <templateId> \
|
|
73
73
|
--source-document-url "https://…/deck.pptx" \
|
|
74
74
|
--scenes 23 --duration 600
|
|
75
75
|
```
|
|
@@ -93,7 +93,7 @@ If the count you need is above the template's maximum, say so and stop — do no
|
|
|
93
93
|
|
|
94
94
|
### Multi-layout templates: pick a layout per scene
|
|
95
95
|
|
|
96
|
-
When a template's `customPayloadSchema.slideId.enum` holds more than one value
|
|
96
|
+
When a template's `customPayloadSchema.slideId.enum` holds more than one value, **every scene must name one**, chosen to fit that scene's information shape — a comparison, a timeline, a code walkthrough and a set of numbers are four different layouts.
|
|
97
97
|
|
|
98
98
|
`gen_script.py` cannot make that choice: it emits the placeholder `"slideId": "__CHOOSE_SLIDE__"`, and the DSL validator rejects any DSL that still carries it, listing the legal values in the error. Replace every placeholder, and do not reuse one layout for the whole video.
|
|
99
99
|
|
|
@@ -103,7 +103,7 @@ A missing `slideId` is the quietest failure in the pipeline: the renderer drops
|
|
|
103
103
|
|
|
104
104
|
Picking the layout is only half of it. Each layout reads its own field names out of `templateData`, and a field the layout does not read is **dropped without a word** — that slot renders empty, or shows the component's built-in placeholder text, and the render still reports success.
|
|
105
105
|
|
|
106
|
-
This has shipped broken videos: a 7-scene deck
|
|
106
|
+
This has shipped broken videos: a 7-scene deck guessed generic names (`title`, `description`, `ctaText`) where the chosen layouts read different ones. Five of seven scenes rendered as empty cards and placeholder copy — after paying for TTS and the render. Which names each layout reads is the template's fact; the template's `llmHint` calls out the usual traps.
|
|
107
107
|
|
|
108
108
|
So: **copy the field names out of `slideSchemas[slideId]`.** The DSL validator now rejects unknown field names before any asset is generated and tells you the declared ones, but that check only fires for templates that ship `slideSchemas` — reading the contract is still the primary move, not the fallback.
|
|
109
109
|
|
|
@@ -257,7 +257,7 @@ Every URL the user gave becomes one carousel item, in order. How long each item
|
|
|
257
257
|
## Agent behavior: user-supplied images for scene-based templates (`--scene-images`)
|
|
258
258
|
|
|
259
259
|
The section above covers `carousel-caption` templates. Templates whose `payloadStyle` is
|
|
260
|
-
`visual-overlay`
|
|
260
|
+
`visual-overlay` have **real scenes**, each with its own background
|
|
261
261
|
image and its own narration — for those, user-supplied images go through `--scene-images`,
|
|
262
262
|
not `--carousel-items`.
|
|
263
263
|
|
|
@@ -342,13 +342,13 @@ describes an `ai:` prompt as if it were a picture you have seen.
|
|
|
342
342
|
|
|
343
343
|
### Command example
|
|
344
344
|
|
|
345
|
-
User: "
|
|
345
|
+
User: "用这个模板做一条讲 RAG 的视频,配图用这两张"(a `visual-overlay` template)
|
|
346
346
|
+ two asset lines with `desc:`
|
|
347
347
|
|
|
348
348
|
```bash
|
|
349
349
|
python3 <SkillDir>/scripts/gen_script.py \
|
|
350
350
|
--topic "三分钟看懂 RAG" \
|
|
351
|
-
--template-id
|
|
351
|
+
--template-id <templateId> \
|
|
352
352
|
--scene-images "https://cdn.example.com/a.png" \
|
|
353
353
|
--scene-images "ai: 赛博朋克风格的服务器机房" \
|
|
354
354
|
--scene-images "https://cdn.example.com/b.png"
|
|
@@ -388,14 +388,14 @@ Typical phrasings (non-exhaustive):
|
|
|
388
388
|
```bash
|
|
389
389
|
python3 <SkillDir>/scripts/gen_script.py \
|
|
390
390
|
--topic "English picture-book story" \
|
|
391
|
-
--template-id
|
|
391
|
+
--template-id <templateId> \
|
|
392
392
|
--stub-image-url "https://cdn.example.com/placeholder.jpg"
|
|
393
393
|
```
|
|
394
394
|
|
|
395
395
|
```bash
|
|
396
396
|
python3 <SkillDir>/scripts/gen_script.py \
|
|
397
397
|
--topic "GitHub project tour" \
|
|
398
|
-
--template-id
|
|
398
|
+
--template-id <templateId> \
|
|
399
399
|
--stub-image-url "https://cdn.example.com/img.jpg" \
|
|
400
400
|
--stub-video-url "https://cdn.example.com/demo.mp4"
|
|
401
401
|
```
|
|
@@ -407,7 +407,7 @@ Env vars `STUB_IMAGE_URL` / `STUB_VIDEO_URL` also work — their priority is low
|
|
|
407
407
|
1. **Script path**: read the system-injected `Base directory for this skill: <path>` as `<SkillDir>`. Every command below uses `<SkillDir>/scripts/gen_script.py`; never hard-code paths.
|
|
408
408
|
2. **Understand the request**: extract topic, duration, style, audience, etc.
|
|
409
409
|
3. **If the user specifies a template, read it first**: load the matching `template.json` via `template-registry` and confirm its template-specific fields and asset requirements. Do not skip this step.
|
|
410
|
-
4. **Generate the DSL**: pick the
|
|
410
|
+
4. **Generate the DSL**: pick the flags the target template's `capabilities` call for (see *Common CLI flags*); the template's `llmHint` says what to fill in afterwards.
|
|
411
411
|
5. **Schema validation**: make sure the output DSL conforms to v1alpha1.
|
|
412
412
|
6. **User confirmation**: show the script summary and wait for confirmation, per the rules above.
|
|
413
413
|
|
|
@@ -454,6 +454,8 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
454
454
|
| `--scenes` | Scene count. | auto-planned |
|
|
455
455
|
| `--voice-id` | Narration voice id. | resolved from template + language fallback |
|
|
456
456
|
| `--speed` | Narration speech rate (0.5–2.0), written to `global.narration.speed` and applied when `prepare_video_assets` calls gen-voice. Pass it whenever the user picked a speed (the replicate form's 语速 field sends one). Above ~1.3 subtitle alignment drifts and the delivery turns mechanical — shorten the script instead. | `1.0` |
|
|
457
|
+
| `--variant` | Template visual variant id (a key of the template's `variants`; `template-registry --list-templates` prints them as `variantIds`). Written to `meta.templateVariant`, which takes precedence over the fuzzy `--style` match. Pass it whenever the user picked a style — the replicate card shows it as `名字(id)`, e.g. `视觉风格: 紫色科技(tech-purple)`; pass the id in the parentheses. Requires `--template-id`; unknown ids are rejected. | template default |
|
|
458
|
+
| `--background-preset` | Scene background preset, one of the template's `customPayloadSchema.background.preset` values. Written to every scene's `customPayload.background.preset` (other keys of the template's default background are kept). Pass it whenever the user picked a background, e.g. `背景样式: 纯色(solid)` → `solid`. `image` / `video` also need `customPayload.background.assetRef`. Requires `--template-id`; unsupported values are rejected. | template default |
|
|
457
459
|
| `--allow-digital-human` | Whether to allow digital-human assets. | off |
|
|
458
460
|
| `--allow-ai-video` | Whether to allow AI-generated video assets. | off |
|
|
459
461
|
| `--validate` | Validate-only mode: only validate the input DSL. | — |
|
|
@@ -462,7 +464,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
462
464
|
| `--stub-image-url` | Test mode: every image AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_IMAGE_URL`). | — |
|
|
463
465
|
| `--stub-video-url` | Test mode: every video AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_VIDEO_URL`). | — |
|
|
464
466
|
| `--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. | — |
|
|
465
|
-
| `--scene-images` | Repeatable. One scene's visual for a **`visual-overlay`** template
|
|
467
|
+
| `--scene-images` | Repeatable. One scene's visual for a **`visual-overlay`** template, 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. | — |
|
|
466
468
|
| `--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. | — |
|
|
467
469
|
|
|
468
470
|
## DSL generation principles
|
|
@@ -36,7 +36,7 @@ if os.path.isdir(_TEMPLATE_DSL_DIR):
|
|
|
36
36
|
sys.path.insert(0, os.path.abspath(os.path.join(_TEMPLATE_DSL_DIR, "..")))
|
|
37
37
|
|
|
38
38
|
# Shared Python lib lives under skills/template-registry/scripts/ — the de-facto
|
|
39
|
-
# cross-skill module location (registry_loader,
|
|
39
|
+
# cross-skill module location (registry_loader, countdown_spec, etc.). See
|
|
40
40
|
# AGENTS.md for the convention.
|
|
41
41
|
_SHARED_SCRIPTS_DIR = os.path.join(
|
|
42
42
|
os.path.dirname(__file__), "..", "..", "template-registry", "scripts"
|
|
@@ -54,6 +54,7 @@ try:
|
|
|
54
54
|
resolve_count as resolve_countdown_count,
|
|
55
55
|
resolve_countdown_spec,
|
|
56
56
|
scene_payload as countdown_scene_payload,
|
|
57
|
+
stated_counts as countdown_stated_counts,
|
|
57
58
|
)
|
|
58
59
|
_HAS_COUNTDOWN = True
|
|
59
60
|
except ImportError:
|
|
@@ -1267,31 +1268,18 @@ def _plan_countdown_scenes(spec: dict, count: int, duration: float, headline: st
|
|
|
1267
1268
|
]
|
|
1268
1269
|
|
|
1269
1270
|
|
|
1270
|
-
def _warn_on_count_mismatch(headline: str, subheadline: str, count: int) -> None:
|
|
1271
|
+
def _warn_on_count_mismatch(spec: dict, headline: str, subheadline: str, count: int) -> None:
|
|
1271
1272
|
"""画面文字里写的数量与实际节拍数不一致时出声提醒(不阻断、不改写)。
|
|
1272
1273
|
|
|
1273
|
-
**刻意只 warn 不改写。**
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
在 ``_echo_template_llm_hint`` 里写明要避免的事:**CLI 出机制,模板出内容**。标题
|
|
1277
|
-
该怎么写、数量该以什么措辞出现(``Top 5`` / ``前 5 名`` / ``5 个项目``)是模板的
|
|
1278
|
-
版式事实,真源是模板自己。
|
|
1274
|
+
**刻意只 warn 不改写。** 标题该怎么写、数量以什么措辞出现是模板的版式事实,
|
|
1275
|
+
模板在渲染时本来就会同步;手写 DSL、历史计划、ab-web 改过的标题都不经过
|
|
1276
|
+
gen_script,所以这里替它改既不必要,也会静默改掉调用方给的文案。
|
|
1279
1277
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
历史计划、ab-web 改过的标题都不经过 gen_script。CLI 这一份自始至终只影响用户在
|
|
1283
|
-
确认脚本那一步看到的文字,为它复制一套会漂移的正则不值得。
|
|
1284
|
-
|
|
1285
|
-
换成告警的另一个好处:静默改写调用方给的文案本身就不妥(同 ``_warn_on_url_text_layer``
|
|
1286
|
-
的分寸)。把数量告诉它,让它自己把文案写对,比替它改更不容易出意外。
|
|
1287
|
-
|
|
1288
|
-
这里认的措辞(``Top N`` / ``N 个项目`` / ``前 N 名``)只用来**发现不一致**,
|
|
1289
|
-
不用来生成文案,所以它不构成一份需要跟模板同步的版式规则。
|
|
1278
|
+
认哪些措辞由模板的 ``countdown.countPatterns`` 声明(见 countdown_spec),
|
|
1279
|
+
CLI 不内置任何词表;没声明的模板不做这项检查。
|
|
1290
1280
|
"""
|
|
1291
|
-
pattern = r"(?<![a-zA-Z0-9_])top\s*([0-9]+)(?![a-zA-Z0-9_])|(?:^|[^0-9])([0-9]+)\s*(?:个(?=\s*(?:开源项目|项目|仓库|工具))|(?=\s*名))"
|
|
1292
1281
|
for flag, value in (("--headline", headline), ("--subheadline", subheadline)):
|
|
1293
|
-
stated =
|
|
1294
|
-
for g in match.groups() if g is not None}
|
|
1282
|
+
stated = countdown_stated_counts(spec, value)
|
|
1295
1283
|
if stated and count not in stated:
|
|
1296
1284
|
print(
|
|
1297
1285
|
f"⚠️ {flag} says {sorted(stated)} item(s) but --item-count is {count}. "
|
|
@@ -1305,6 +1293,73 @@ def _warn_on_count_mismatch(headline: str, subheadline: str, count: int) -> None
|
|
|
1305
1293
|
)
|
|
1306
1294
|
|
|
1307
1295
|
|
|
1296
|
+
def _template_background_presets(template_config: dict | None) -> list[str]:
|
|
1297
|
+
"""模板在 ``customPayloadSchema.background.preset`` 声明的背景取值(没声明 = 空表)。
|
|
1298
|
+
|
|
1299
|
+
背景词汇是**模板自己的**:同样叫「网格」,不同模板的取值名各不相同,默认值也
|
|
1300
|
+
各不相同。所以这里只读声明,不在 CLI 里维护任何取值表。
|
|
1301
|
+
"""
|
|
1302
|
+
schema = (template_config or {}).get("customPayloadSchema") or {}
|
|
1303
|
+
background = schema.get("background") if isinstance(schema, dict) else None
|
|
1304
|
+
props = background.get("properties") if isinstance(background, dict) else None
|
|
1305
|
+
preset = props.get("preset") if isinstance(props, dict) else None
|
|
1306
|
+
enum = preset.get("enum") if isinstance(preset, dict) else None
|
|
1307
|
+
return [str(v) for v in enum] if isinstance(enum, list) else []
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
def _apply_look(
|
|
1311
|
+
dsl: dict,
|
|
1312
|
+
template_config: dict | None,
|
|
1313
|
+
template_id: str | None,
|
|
1314
|
+
variant: str | None,
|
|
1315
|
+
background_preset: str | None,
|
|
1316
|
+
) -> None:
|
|
1317
|
+
"""把用户选的视觉风格 / 背景样式写进 DSL(就地改)。
|
|
1318
|
+
|
|
1319
|
+
这两个值以前没有任何一跳能落进管线:复刻弹窗让用户选了,gen_script 却只写模板
|
|
1320
|
+
默认背景,merge 模式又只替换旁白——成片照出、不报错,用户只觉得「好像没生效」。
|
|
1321
|
+
|
|
1322
|
+
- ``--variant`` → ``meta.templateVariant``:match_template 把它当**显式**选择,
|
|
1323
|
+
优先于 meta.style 的模糊匹配,最终成为 inputProps.variantId;
|
|
1324
|
+
- ``--background-preset`` → 每个场景 ``customPayload.background.preset``:
|
|
1325
|
+
customPayload 原样透传给模板,模板读的正是这个字段。保留模板默认背景里的其它键。
|
|
1326
|
+
|
|
1327
|
+
取值不在模板声明里就直接退出:静默丢掉用户的选择,正是这里要修的那个 bug。
|
|
1328
|
+
"""
|
|
1329
|
+
if not variant and not background_preset:
|
|
1330
|
+
return
|
|
1331
|
+
if not template_id or template_config is None:
|
|
1332
|
+
raise ValueError("--variant / --background-preset need --template-id: the allowed values come from the template.")
|
|
1333
|
+
|
|
1334
|
+
if variant:
|
|
1335
|
+
variants = list((template_config.get("variants") or {}).keys())
|
|
1336
|
+
if variant not in variants:
|
|
1337
|
+
raise ValueError(
|
|
1338
|
+
f"--variant {variant!r} is not a variant of template {template_id!r}; "
|
|
1339
|
+
f"choose one of: {', '.join(variants) or '(this template declares no variants)'}"
|
|
1340
|
+
)
|
|
1341
|
+
dsl.setdefault("meta", {})["templateVariant"] = variant
|
|
1342
|
+
|
|
1343
|
+
if background_preset:
|
|
1344
|
+
presets = _template_background_presets(template_config)
|
|
1345
|
+
if background_preset not in presets:
|
|
1346
|
+
raise ValueError(
|
|
1347
|
+
f"--background-preset {background_preset!r} is not supported by template {template_id!r}; "
|
|
1348
|
+
f"choose one of: {', '.join(presets) or '(this template has no background option)'}"
|
|
1349
|
+
)
|
|
1350
|
+
for scene in dsl.get("scenes", []):
|
|
1351
|
+
payload = scene.setdefault("customPayload", {})
|
|
1352
|
+
existing = payload.get("background") if isinstance(payload.get("background"), dict) else {}
|
|
1353
|
+
payload["background"] = {**existing, "preset": background_preset}
|
|
1354
|
+
if background_preset in ("image", "video"):
|
|
1355
|
+
print(
|
|
1356
|
+
f"ℹ️ background preset {background_preset!r} needs a user asset: set "
|
|
1357
|
+
f"customPayload.background.assetRef on each scene to that asset's id, "
|
|
1358
|
+
f"otherwise the template falls back to its default background.",
|
|
1359
|
+
file=sys.stderr,
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
|
|
1308
1363
|
def build_dsl(
|
|
1309
1364
|
topic: str,
|
|
1310
1365
|
duration: int,
|
|
@@ -1393,7 +1448,7 @@ def build_dsl(
|
|
|
1393
1448
|
_echo_template_llm_hint(template_config, template_id)
|
|
1394
1449
|
_warn_on_url_text_layer(effective_headline, effective_subheadline)
|
|
1395
1450
|
if beat_count is not None:
|
|
1396
|
-
_warn_on_count_mismatch(effective_headline, effective_subheadline, beat_count)
|
|
1451
|
+
_warn_on_count_mismatch(countdown, effective_headline, effective_subheadline, beat_count)
|
|
1397
1452
|
|
|
1398
1453
|
# ── carousel-caption 模板(单场景、图/视频轮播 + 打字机)单独装配并直接返回 ──
|
|
1399
1454
|
if payload_style == "carousel-caption":
|
|
@@ -1817,7 +1872,9 @@ def build_dsl(
|
|
|
1817
1872
|
f"is only a placeholder.\n"
|
|
1818
1873
|
f" Write the full DSL yourself from the user's material (scenes, assets, visuals, "
|
|
1819
1874
|
f"customPayload) and submit all of it to prepare_video_assets — it is used as-is, "
|
|
1820
|
-
f"not merged onto this skeleton. Keep meta.templateId."
|
|
1875
|
+
f"not merged onto this skeleton. Keep meta.templateId, and copy meta.templateVariant and "
|
|
1876
|
+
f"each scene's customPayload.background from this skeleton when present (they carry the "
|
|
1877
|
+
f"user's --variant / --background-preset choice).",
|
|
1821
1878
|
file=sys.stderr,
|
|
1822
1879
|
)
|
|
1823
1880
|
|
|
@@ -1846,7 +1903,7 @@ Examples:
|
|
|
1846
1903
|
parser.add_argument(
|
|
1847
1904
|
"--source-document-url", default=None,
|
|
1848
1905
|
help="Source document URL (.pptx) for templates that build their per-scene payload from a "
|
|
1849
|
-
"document (capabilities.payloadHydration
|
|
1906
|
+
"document (capabilities.payloadHydration). Recorded at "
|
|
1850
1907
|
"meta.sourceDocument; prepare_video_assets then fetches the layout itself and fills "
|
|
1851
1908
|
"every scene's customPayload. Pass the same URL you gave doc_parse. Without it such a "
|
|
1852
1909
|
"template's DSL is rejected downstream.",
|
|
@@ -1875,6 +1932,19 @@ Examples:
|
|
|
1875
1932
|
),
|
|
1876
1933
|
)
|
|
1877
1934
|
parser.add_argument("--font-name", default=None, help="Human-readable font name. Logged and stored for display only; never used for rendering.")
|
|
1935
|
+
parser.add_argument(
|
|
1936
|
+
"--variant", default=None,
|
|
1937
|
+
help="Template visual variant id (a key of the template's `variants`, listed as variantIds by "
|
|
1938
|
+
"template-registry --list-templates). Written to meta.templateVariant, which overrides the "
|
|
1939
|
+
"fuzzy --style match. When the user picked a style in the replicate card (shown as "
|
|
1940
|
+
"`名字(id)`, e.g. `紫色科技(tech-purple)`), pass that id. Requires --template-id.",
|
|
1941
|
+
)
|
|
1942
|
+
parser.add_argument(
|
|
1943
|
+
"--background-preset", default=None,
|
|
1944
|
+
help="Scene background preset (one of the template's customPayloadSchema.background.preset enum). "
|
|
1945
|
+
"Written to every scene's customPayload.background.preset. When the user picked a background in "
|
|
1946
|
+
"the replicate card (e.g. `纯色(solid)`), pass that id. Requires --template-id.",
|
|
1947
|
+
)
|
|
1878
1948
|
parser.add_argument("--allow-digital-human", action="store_true", help="Allow digital-human assets")
|
|
1879
1949
|
parser.add_argument("--allow-ai-video", action="store_true", help="Allow AI-generated video assets")
|
|
1880
1950
|
parser.add_argument("--template-id", default=None, help="Template id. The template owns outputLanguage and may declare a defaultVoiceId.")
|
|
@@ -1920,7 +1990,7 @@ Examples:
|
|
|
1920
1990
|
"--scene-images",
|
|
1921
1991
|
action="append",
|
|
1922
1992
|
default=None,
|
|
1923
|
-
help="One scene's visual for a visual-overlay template (
|
|
1993
|
+
help="One scene's visual for a visual-overlay template (capabilities.payloadStyle). "
|
|
1924
1994
|
"Can be repeated: --scene-images url1 --scene-images ai --scene-images url2. "
|
|
1925
1995
|
"Each entry is either a URL (use that existing asset) or the literal 'ai' / "
|
|
1926
1996
|
"'ai:<prompt>' (leave this scene to gen-image, optionally with the user's prompt). "
|
|
@@ -2073,6 +2143,17 @@ Examples:
|
|
|
2073
2143
|
# as a message instead of a traceback.
|
|
2074
2144
|
parser.error(str(exc))
|
|
2075
2145
|
|
|
2146
|
+
try:
|
|
2147
|
+
_apply_look(
|
|
2148
|
+
dsl,
|
|
2149
|
+
_load_template_config(args.template_id) if args.template_id else None,
|
|
2150
|
+
args.template_id,
|
|
2151
|
+
args.variant,
|
|
2152
|
+
args.background_preset,
|
|
2153
|
+
)
|
|
2154
|
+
except ValueError as exc:
|
|
2155
|
+
parser.error(str(exc))
|
|
2156
|
+
|
|
2076
2157
|
# Post-process for --skip-asset-generation: rewrite all gen-voice / gen-digital-human
|
|
2077
2158
|
# assets to be already-generated stubs. Image / video are already covered by the
|
|
2078
2159
|
# stub_image_url / stub_video_url params threaded through build_dsl above.
|