@remixmate/cli 0.9.50 → 0.9.52
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 +27 -14
- package/package.json +10 -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 +19 -18
- package/skills/gen-script/scripts/gen_script.py +170 -38
- 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 +53 -42
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/README.md +4 -15
- package/skills/template-registry/SKILL.md +20 -52
- package/skills/template-registry/scripts/countdown_spec.py +56 -6
- package/skills/template-registry/scripts/list_templates.py +133 -8
- package/skills/template-registry/scripts/registry_loader.py +3 -4
- package/skills/template-registry/skill.json +2 -2
- 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 +108 -35
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +129 -0
- 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
|
@@ -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:
|
|
@@ -72,6 +73,14 @@ except ImportError:
|
|
|
72
73
|
_HAS_ENHANCER = False
|
|
73
74
|
SKELETON_PREFIXES = ("[骨架待填充]", "[skeleton placeholder]")
|
|
74
75
|
|
|
76
|
+
# 旁白字数预算:写进骨架占位旁白,让模型写脚本时就知道这一场放得下多少字。
|
|
77
|
+
# 守护式导入同上;缺失时占位文案退回不带预算的旧格式。
|
|
78
|
+
try:
|
|
79
|
+
from video_dsl.runtime import narration_budget as _nb
|
|
80
|
+
_HAS_BUDGET = True
|
|
81
|
+
except ImportError:
|
|
82
|
+
_HAS_BUDGET = False
|
|
83
|
+
|
|
75
84
|
# 场景数上限的唯一实现在 dsl_validator(它同时是门禁方)。守护式导入的理由同上:
|
|
76
85
|
# 共享目录可能不在(单测 / 裁剪安装),那时退回历史常量 20,行为与改动前一致。
|
|
77
86
|
try:
|
|
@@ -109,7 +118,8 @@ except ImportError:
|
|
|
109
118
|
VALID_RATIOS = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"]
|
|
110
119
|
VALID_PURPOSES = ["opening", "point", "example", "explanation", "highlight", "cta", "ending"]
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
# 规划用语速 / 行间停顿 / 场景尾巴统一在 video_dsl.runtime.narration_budget。
|
|
122
|
+
# 这里原有的 CHARS_PER_SECOND = 5.0 从未参与计算(只出现在注释里),已删。
|
|
113
123
|
|
|
114
124
|
# ── Language contract (mirrors apps/ab-skill/src/handlers/template-meta.ts) ──
|
|
115
125
|
# template.json may declare:
|
|
@@ -229,20 +239,25 @@ def _localize_label(language, key, **fmt):
|
|
|
229
239
|
"label_point": {"zh": "内容要点 {n}", "en": "Point {n}"},
|
|
230
240
|
"label_cta": {"zh": "结尾 CTA", "en": "Closing CTA"},
|
|
231
241
|
"narration_opening": {
|
|
232
|
-
"zh": "[骨架待填充] 关于{topic}的开场 Hook({duration}s)",
|
|
233
|
-
"en": "[skeleton placeholder] Opening hook about {topic} ({duration}s)",
|
|
242
|
+
"zh": "[骨架待填充] 关于{topic}的开场 Hook({duration}s{budget})",
|
|
243
|
+
"en": "[skeleton placeholder] Opening hook about {topic} ({duration}s{budget})",
|
|
234
244
|
},
|
|
235
245
|
"narration_point": {
|
|
236
|
-
"zh": "[骨架待填充] {label}:关于{topic}的一个要点({duration}s)",
|
|
237
|
-
"en": "[skeleton placeholder] {label}: a point about {topic} ({duration}s)",
|
|
246
|
+
"zh": "[骨架待填充] {label}:关于{topic}的一个要点({duration}s{budget})",
|
|
247
|
+
"en": "[skeleton placeholder] {label}: a point about {topic} ({duration}s{budget})",
|
|
238
248
|
},
|
|
239
249
|
"narration_cta": {
|
|
240
|
-
"zh": "[骨架待填充] 关于{topic}的 CTA 结尾({duration}s)",
|
|
241
|
-
"en": "[skeleton placeholder] Closing CTA about {topic} ({duration}s)",
|
|
250
|
+
"zh": "[骨架待填充] 关于{topic}的 CTA 结尾({duration}s{budget})",
|
|
251
|
+
"en": "[skeleton placeholder] Closing CTA about {topic} ({duration}s{budget})",
|
|
252
|
+
},
|
|
253
|
+
# {budget} 由 _budget_suffix 填:「,整段≤32 字;分行每多一行约少 4 字」。
|
|
254
|
+
"budget_suffix": {
|
|
255
|
+
"zh": ",整段≤{chars} 字;分行旁白每多一行约少 {per_line} 字",
|
|
256
|
+
"en": ", ≤{chars} words as one block; ~{per_line} fewer per extra narration line",
|
|
242
257
|
},
|
|
243
258
|
"narration_default": {
|
|
244
|
-
"zh": "[骨架待填充] 关于{topic}的内容({duration}s)",
|
|
245
|
-
"en": "[skeleton placeholder] Content about {topic} ({duration}s)",
|
|
259
|
+
"zh": "[骨架待填充] 关于{topic}的内容({duration}s{budget})",
|
|
260
|
+
"en": "[skeleton placeholder] Content about {topic} ({duration}s{budget})",
|
|
246
261
|
},
|
|
247
262
|
# NOTE: 这里曾有 "point_description"("关于{topic}的要点内容")与
|
|
248
263
|
# "more_about"("更多关于{topic}的内容")两条,用来给 no-visual 场景拼画面
|
|
@@ -256,6 +271,9 @@ def _localize_label(language, key, **fmt):
|
|
|
256
271
|
}
|
|
257
272
|
entry = table.get(key, {})
|
|
258
273
|
template = entry.get(language) or entry.get(DEFAULT_LANGUAGE) or ""
|
|
274
|
+
# 字数预算只有按场景规划的路径才算得出来(轮播 / 按字幕定时长的模板没有
|
|
275
|
+
# 「每场秒数」这个前提),没给就是不带预算的旧格式。
|
|
276
|
+
fmt.setdefault("budget", "")
|
|
259
277
|
return template.format(**fmt)
|
|
260
278
|
|
|
261
279
|
|
|
@@ -362,17 +380,47 @@ def plan_scenes(
|
|
|
362
380
|
return scenes
|
|
363
381
|
|
|
364
382
|
|
|
365
|
-
def
|
|
383
|
+
def _budget_suffix(duration: int, language: str, speed: float, tail_pad_sec: float | None) -> str:
|
|
384
|
+
"""占位旁白里的字数预算。拿不到预算模块时返回空串(退回旧格式)。
|
|
385
|
+
|
|
386
|
+
为什么写进占位文案而不是单独字段:模型填旁白时一定会读到这一句,而骨架多一个
|
|
387
|
+
字段就得在校验白名单、TTS payload 两头都处理,漏一头就是新的静默问题。
|
|
388
|
+
"""
|
|
389
|
+
if not _HAS_BUDGET:
|
|
390
|
+
return ""
|
|
391
|
+
tail = _nb.DEFAULT_TAIL_PAD_SEC if tail_pad_sec is None else tail_pad_sec
|
|
392
|
+
lang = "zh" if language == "zh" else "en"
|
|
393
|
+
chars = _nb.speech_budget(duration, speed, lang, lines=1, tail_pad_sec=tail)
|
|
394
|
+
per_line = max(1, round(_nb.LINE_PAUSE_SEC * _nb.speech_rate(lang, speed)))
|
|
395
|
+
return _localize_label(language, "budget_suffix", chars=chars, per_line=per_line)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def estimate_narration_text(
|
|
399
|
+
label: str,
|
|
400
|
+
purpose: str,
|
|
401
|
+
duration: int,
|
|
402
|
+
topic: str,
|
|
403
|
+
language: str = DEFAULT_LANGUAGE,
|
|
404
|
+
speed: float = 1.0,
|
|
405
|
+
tail_pad_sec: float | None = None,
|
|
406
|
+
) -> str:
|
|
366
407
|
"""Generate a short skeleton narration hint for a scene.
|
|
367
408
|
|
|
368
409
|
This returns a *skeleton only* — the agent is expected to replace this
|
|
369
410
|
text with real narration before submitting DSL to prepare_video_assets. We
|
|
370
411
|
deliberately return a short, clearly-placeholder-looking string rather
|
|
371
|
-
than padding to
|
|
412
|
+
than padding to the scene's length, because padding with a
|
|
372
413
|
repeating filler sentence historically caused the skeleton to be
|
|
373
414
|
submitted verbatim to TTS (audio narration mismatch bug).
|
|
415
|
+
|
|
416
|
+
It does carry the scene's narration *budget* (see narration_budget): the
|
|
417
|
+
one number the agent needs while writing, and never had — a 2026-09-27
|
|
418
|
+
session wrote 45–58 chars into 8s scenes and shipped 117s for a 75s target.
|
|
374
419
|
"""
|
|
375
|
-
fmt = {
|
|
420
|
+
fmt = {
|
|
421
|
+
"label": label, "topic": topic, "duration": duration,
|
|
422
|
+
"budget": _budget_suffix(duration, language, speed, tail_pad_sec),
|
|
423
|
+
}
|
|
376
424
|
if purpose == "opening":
|
|
377
425
|
return _localize_label(language, "narration_opening", **fmt)
|
|
378
426
|
if purpose == "point":
|
|
@@ -1267,31 +1315,18 @@ def _plan_countdown_scenes(spec: dict, count: int, duration: float, headline: st
|
|
|
1267
1315
|
]
|
|
1268
1316
|
|
|
1269
1317
|
|
|
1270
|
-
def _warn_on_count_mismatch(headline: str, subheadline: str, count: int) -> None:
|
|
1318
|
+
def _warn_on_count_mismatch(spec: dict, headline: str, subheadline: str, count: int) -> None:
|
|
1271
1319
|
"""画面文字里写的数量与实际节拍数不一致时出声提醒(不阻断、不改写)。
|
|
1272
1320
|
|
|
1273
|
-
**刻意只 warn 不改写。**
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
在 ``_echo_template_llm_hint`` 里写明要避免的事:**CLI 出机制,模板出内容**。标题
|
|
1277
|
-
该怎么写、数量该以什么措辞出现(``Top 5`` / ``前 5 名`` / ``5 个项目``)是模板的
|
|
1278
|
-
版式事实,真源是模板自己。
|
|
1279
|
-
|
|
1280
|
-
去掉那份移植不会让成片出错:模板在渲染时本来就会同步(github-repo-rank 在
|
|
1281
|
-
``Opening.tsx`` / ``Cover.tsx`` 里做),而且它**必须**保留这个能力 —— 手写 DSL、
|
|
1282
|
-
历史计划、ab-web 改过的标题都不经过 gen_script。CLI 这一份自始至终只影响用户在
|
|
1283
|
-
确认脚本那一步看到的文字,为它复制一套会漂移的正则不值得。
|
|
1321
|
+
**刻意只 warn 不改写。** 标题该怎么写、数量以什么措辞出现是模板的版式事实,
|
|
1322
|
+
模板在渲染时本来就会同步;手写 DSL、历史计划、ab-web 改过的标题都不经过
|
|
1323
|
+
gen_script,所以这里替它改既不必要,也会静默改掉调用方给的文案。
|
|
1284
1324
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
这里认的措辞(``Top N`` / ``N 个项目`` / ``前 N 名``)只用来**发现不一致**,
|
|
1289
|
-
不用来生成文案,所以它不构成一份需要跟模板同步的版式规则。
|
|
1325
|
+
认哪些措辞由模板的 ``countdown.countPatterns`` 声明(见 countdown_spec),
|
|
1326
|
+
CLI 不内置任何词表;没声明的模板不做这项检查。
|
|
1290
1327
|
"""
|
|
1291
|
-
pattern = r"(?<![a-zA-Z0-9_])top\s*([0-9]+)(?![a-zA-Z0-9_])|(?:^|[^0-9])([0-9]+)\s*(?:个(?=\s*(?:开源项目|项目|仓库|工具))|(?=\s*名))"
|
|
1292
1328
|
for flag, value in (("--headline", headline), ("--subheadline", subheadline)):
|
|
1293
|
-
stated =
|
|
1294
|
-
for g in match.groups() if g is not None}
|
|
1329
|
+
stated = countdown_stated_counts(spec, value)
|
|
1295
1330
|
if stated and count not in stated:
|
|
1296
1331
|
print(
|
|
1297
1332
|
f"⚠️ {flag} says {sorted(stated)} item(s) but --item-count is {count}. "
|
|
@@ -1305,6 +1340,73 @@ def _warn_on_count_mismatch(headline: str, subheadline: str, count: int) -> None
|
|
|
1305
1340
|
)
|
|
1306
1341
|
|
|
1307
1342
|
|
|
1343
|
+
def _template_background_presets(template_config: dict | None) -> list[str]:
|
|
1344
|
+
"""模板在 ``customPayloadSchema.background.preset`` 声明的背景取值(没声明 = 空表)。
|
|
1345
|
+
|
|
1346
|
+
背景词汇是**模板自己的**:同样叫「网格」,不同模板的取值名各不相同,默认值也
|
|
1347
|
+
各不相同。所以这里只读声明,不在 CLI 里维护任何取值表。
|
|
1348
|
+
"""
|
|
1349
|
+
schema = (template_config or {}).get("customPayloadSchema") or {}
|
|
1350
|
+
background = schema.get("background") if isinstance(schema, dict) else None
|
|
1351
|
+
props = background.get("properties") if isinstance(background, dict) else None
|
|
1352
|
+
preset = props.get("preset") if isinstance(props, dict) else None
|
|
1353
|
+
enum = preset.get("enum") if isinstance(preset, dict) else None
|
|
1354
|
+
return [str(v) for v in enum] if isinstance(enum, list) else []
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def _apply_look(
|
|
1358
|
+
dsl: dict,
|
|
1359
|
+
template_config: dict | None,
|
|
1360
|
+
template_id: str | None,
|
|
1361
|
+
variant: str | None,
|
|
1362
|
+
background_preset: str | None,
|
|
1363
|
+
) -> None:
|
|
1364
|
+
"""把用户选的视觉风格 / 背景样式写进 DSL(就地改)。
|
|
1365
|
+
|
|
1366
|
+
这两个值以前没有任何一跳能落进管线:复刻弹窗让用户选了,gen_script 却只写模板
|
|
1367
|
+
默认背景,merge 模式又只替换旁白——成片照出、不报错,用户只觉得「好像没生效」。
|
|
1368
|
+
|
|
1369
|
+
- ``--variant`` → ``meta.templateVariant``:match_template 把它当**显式**选择,
|
|
1370
|
+
优先于 meta.style 的模糊匹配,最终成为 inputProps.variantId;
|
|
1371
|
+
- ``--background-preset`` → 每个场景 ``customPayload.background.preset``:
|
|
1372
|
+
customPayload 原样透传给模板,模板读的正是这个字段。保留模板默认背景里的其它键。
|
|
1373
|
+
|
|
1374
|
+
取值不在模板声明里就直接退出:静默丢掉用户的选择,正是这里要修的那个 bug。
|
|
1375
|
+
"""
|
|
1376
|
+
if not variant and not background_preset:
|
|
1377
|
+
return
|
|
1378
|
+
if not template_id or template_config is None:
|
|
1379
|
+
raise ValueError("--variant / --background-preset need --template-id: the allowed values come from the template.")
|
|
1380
|
+
|
|
1381
|
+
if variant:
|
|
1382
|
+
variants = list((template_config.get("variants") or {}).keys())
|
|
1383
|
+
if variant not in variants:
|
|
1384
|
+
raise ValueError(
|
|
1385
|
+
f"--variant {variant!r} is not a variant of template {template_id!r}; "
|
|
1386
|
+
f"choose one of: {', '.join(variants) or '(this template declares no variants)'}"
|
|
1387
|
+
)
|
|
1388
|
+
dsl.setdefault("meta", {})["templateVariant"] = variant
|
|
1389
|
+
|
|
1390
|
+
if background_preset:
|
|
1391
|
+
presets = _template_background_presets(template_config)
|
|
1392
|
+
if background_preset not in presets:
|
|
1393
|
+
raise ValueError(
|
|
1394
|
+
f"--background-preset {background_preset!r} is not supported by template {template_id!r}; "
|
|
1395
|
+
f"choose one of: {', '.join(presets) or '(this template has no background option)'}"
|
|
1396
|
+
)
|
|
1397
|
+
for scene in dsl.get("scenes", []):
|
|
1398
|
+
payload = scene.setdefault("customPayload", {})
|
|
1399
|
+
existing = payload.get("background") if isinstance(payload.get("background"), dict) else {}
|
|
1400
|
+
payload["background"] = {**existing, "preset": background_preset}
|
|
1401
|
+
if background_preset in ("image", "video"):
|
|
1402
|
+
print(
|
|
1403
|
+
f"ℹ️ background preset {background_preset!r} needs a user asset: set "
|
|
1404
|
+
f"customPayload.background.assetRef on each scene to that asset's id, "
|
|
1405
|
+
f"otherwise the template falls back to its default background.",
|
|
1406
|
+
file=sys.stderr,
|
|
1407
|
+
)
|
|
1408
|
+
|
|
1409
|
+
|
|
1308
1410
|
def build_dsl(
|
|
1309
1411
|
topic: str,
|
|
1310
1412
|
duration: int,
|
|
@@ -1393,7 +1495,7 @@ def build_dsl(
|
|
|
1393
1495
|
_echo_template_llm_hint(template_config, template_id)
|
|
1394
1496
|
_warn_on_url_text_layer(effective_headline, effective_subheadline)
|
|
1395
1497
|
if beat_count is not None:
|
|
1396
|
-
_warn_on_count_mismatch(effective_headline, effective_subheadline, beat_count)
|
|
1498
|
+
_warn_on_count_mismatch(countdown, effective_headline, effective_subheadline, beat_count)
|
|
1397
1499
|
|
|
1398
1500
|
# ── carousel-caption 模板(单场景、图/视频轮播 + 打字机)单独装配并直接返回 ──
|
|
1399
1501
|
if payload_style == "carousel-caption":
|
|
@@ -1522,6 +1624,7 @@ def build_dsl(
|
|
|
1522
1624
|
image_compose = _HAS_ENHANCER and is_compose_mode(image_style_guide)
|
|
1523
1625
|
image_ratio = image_style_guide.get("ratio") or ratio
|
|
1524
1626
|
|
|
1627
|
+
tail_pad_sec = _nb.template_tail_pad_sec(template_config) if _HAS_BUDGET else None
|
|
1525
1628
|
for idx, plan in enumerate(scene_plans):
|
|
1526
1629
|
scene_id = f"scene-{idx + 1:02d}"
|
|
1527
1630
|
# 视觉素材的 assetId 命名遵循类型前缀:image → img-,video → video-
|
|
@@ -1531,7 +1634,10 @@ def build_dsl(
|
|
|
1531
1634
|
visual_asset_id = f"img-{scene_id}"
|
|
1532
1635
|
narration_asset_id = f"narration-{scene_id}" if needs_narration else None
|
|
1533
1636
|
narration_text = (
|
|
1534
|
-
estimate_narration_text(
|
|
1637
|
+
estimate_narration_text(
|
|
1638
|
+
plan["label"], plan["purpose"], plan["duration"], topic, language=output_language,
|
|
1639
|
+
speed=narration_speed, tail_pad_sec=tail_pad_sec,
|
|
1640
|
+
)
|
|
1535
1641
|
if needs_narration else ""
|
|
1536
1642
|
)
|
|
1537
1643
|
|
|
@@ -1817,7 +1923,9 @@ def build_dsl(
|
|
|
1817
1923
|
f"is only a placeholder.\n"
|
|
1818
1924
|
f" Write the full DSL yourself from the user's material (scenes, assets, visuals, "
|
|
1819
1925
|
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."
|
|
1926
|
+
f"not merged onto this skeleton. Keep meta.templateId, and copy meta.templateVariant and "
|
|
1927
|
+
f"each scene's customPayload.background from this skeleton when present (they carry the "
|
|
1928
|
+
f"user's --variant / --background-preset choice).",
|
|
1821
1929
|
file=sys.stderr,
|
|
1822
1930
|
)
|
|
1823
1931
|
|
|
@@ -1846,7 +1954,7 @@ Examples:
|
|
|
1846
1954
|
parser.add_argument(
|
|
1847
1955
|
"--source-document-url", default=None,
|
|
1848
1956
|
help="Source document URL (.pptx) for templates that build their per-scene payload from a "
|
|
1849
|
-
"document (capabilities.payloadHydration
|
|
1957
|
+
"document (capabilities.payloadHydration). Recorded at "
|
|
1850
1958
|
"meta.sourceDocument; prepare_video_assets then fetches the layout itself and fills "
|
|
1851
1959
|
"every scene's customPayload. Pass the same URL you gave doc_parse. Without it such a "
|
|
1852
1960
|
"template's DSL is rejected downstream.",
|
|
@@ -1875,6 +1983,19 @@ Examples:
|
|
|
1875
1983
|
),
|
|
1876
1984
|
)
|
|
1877
1985
|
parser.add_argument("--font-name", default=None, help="Human-readable font name. Logged and stored for display only; never used for rendering.")
|
|
1986
|
+
parser.add_argument(
|
|
1987
|
+
"--variant", default=None,
|
|
1988
|
+
help="Template visual variant id (a key of the template's `variants`, listed as variantIds by "
|
|
1989
|
+
"template-registry --list-templates). Written to meta.templateVariant, which overrides the "
|
|
1990
|
+
"fuzzy --style match. When the user picked a style in the replicate card (shown as "
|
|
1991
|
+
"`名字(id)`, e.g. `紫色科技(tech-purple)`), pass that id. Requires --template-id.",
|
|
1992
|
+
)
|
|
1993
|
+
parser.add_argument(
|
|
1994
|
+
"--background-preset", default=None,
|
|
1995
|
+
help="Scene background preset (one of the template's customPayloadSchema.background.preset enum). "
|
|
1996
|
+
"Written to every scene's customPayload.background.preset. When the user picked a background in "
|
|
1997
|
+
"the replicate card (e.g. `纯色(solid)`), pass that id. Requires --template-id.",
|
|
1998
|
+
)
|
|
1878
1999
|
parser.add_argument("--allow-digital-human", action="store_true", help="Allow digital-human assets")
|
|
1879
2000
|
parser.add_argument("--allow-ai-video", action="store_true", help="Allow AI-generated video assets")
|
|
1880
2001
|
parser.add_argument("--template-id", default=None, help="Template id. The template owns outputLanguage and may declare a defaultVoiceId.")
|
|
@@ -1920,7 +2041,7 @@ Examples:
|
|
|
1920
2041
|
"--scene-images",
|
|
1921
2042
|
action="append",
|
|
1922
2043
|
default=None,
|
|
1923
|
-
help="One scene's visual for a visual-overlay template (
|
|
2044
|
+
help="One scene's visual for a visual-overlay template (capabilities.payloadStyle). "
|
|
1924
2045
|
"Can be repeated: --scene-images url1 --scene-images ai --scene-images url2. "
|
|
1925
2046
|
"Each entry is either a URL (use that existing asset) or the literal 'ai' / "
|
|
1926
2047
|
"'ai:<prompt>' (leave this scene to gen-image, optionally with the user's prompt). "
|
|
@@ -2073,6 +2194,17 @@ Examples:
|
|
|
2073
2194
|
# as a message instead of a traceback.
|
|
2074
2195
|
parser.error(str(exc))
|
|
2075
2196
|
|
|
2197
|
+
try:
|
|
2198
|
+
_apply_look(
|
|
2199
|
+
dsl,
|
|
2200
|
+
_load_template_config(args.template_id) if args.template_id else None,
|
|
2201
|
+
args.template_id,
|
|
2202
|
+
args.variant,
|
|
2203
|
+
args.background_preset,
|
|
2204
|
+
)
|
|
2205
|
+
except ValueError as exc:
|
|
2206
|
+
parser.error(str(exc))
|
|
2207
|
+
|
|
2076
2208
|
# Post-process for --skip-asset-generation: rewrite all gen-voice / gen-digital-human
|
|
2077
2209
|
# assets to be already-generated stubs. Image / video are already covered by the
|
|
2078
2210
|
# stub_image_url / stub_video_url params threaded through build_dsl above.
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scenes": {
|
|
36
36
|
"type": "number",
|
|
37
|
-
"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 (
|
|
37
|
+
"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."
|
|
38
38
|
},
|
|
39
39
|
"voice_id": {
|
|
40
40
|
"type": "string",
|
|
@@ -44,13 +44,21 @@
|
|
|
44
44
|
"type": "number",
|
|
45
45
|
"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."
|
|
46
46
|
},
|
|
47
|
+
"variant": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"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."
|
|
50
|
+
},
|
|
51
|
+
"background_preset": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"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."
|
|
54
|
+
},
|
|
47
55
|
"template_id": {
|
|
48
56
|
"type": "string",
|
|
49
|
-
"description": "Template id (
|
|
57
|
+
"description": "Template id (from template_registry). The template owns outputLanguage and may also declare a defaultVoiceId; both flow into the produced DSL."
|
|
50
58
|
},
|
|
51
59
|
"source_document_url": {
|
|
52
60
|
"type": "string",
|
|
53
|
-
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration
|
|
61
|
+
"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."
|
|
54
62
|
},
|
|
55
63
|
"headline": {
|
|
56
64
|
"type": "string",
|
|
@@ -72,7 +80,7 @@
|
|
|
72
80
|
"items": {
|
|
73
81
|
"type": "string"
|
|
74
82
|
},
|
|
75
|
-
"description": "Per-scene visuals for a visual-overlay template (
|
|
83
|
+
"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."
|
|
76
84
|
},
|
|
77
85
|
"caption_lines": {
|
|
78
86
|
"type": "array",
|
|
@@ -117,6 +125,8 @@
|
|
|
117
125
|
"scenes",
|
|
118
126
|
"voice_id",
|
|
119
127
|
"speed",
|
|
128
|
+
"variant",
|
|
129
|
+
"background_preset",
|
|
120
130
|
"template_id",
|
|
121
131
|
"source_document_url",
|
|
122
132
|
"headline",
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "gen-script",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "474",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V18",
|
|
6
6
|
"skillDescription": "Video-script generation skill. Turns a user-supplied topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.\n\nUse this skill as soon as the user mentions any of these intents:\n- Write a video script, generate a video script, plan a video, write storyboards\n- Create a short video, plan video content, generate a Video DSL\n- Turn a topic into a video structure / video content plan\n\nEven when the user does not say \"generate the DSL\", use this skill whenever they want to turn a topic into a structured video plan.\n\n⚠️ Stop-and-confirm gate: after this skill returns a DSL, show the full script and wait for the user's explicit confirmation. Never call `prepare_video_assets` in the same turn."
|
|
7
7
|
}
|
|
@@ -73,7 +73,7 @@ The asset resolver invokes the following skills based on `AssetRef` declarations
|
|
|
73
73
|
```bash
|
|
74
74
|
python3 <SkillDir>/scripts/prepare_video_assets.py \
|
|
75
75
|
--dsl-json '<DSL_JSON_STRING>' \
|
|
76
|
-
--template-id
|
|
76
|
+
--template-id <templateId>
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
- `<DSL_JSON_STRING>` is the JSON the agent received from `gen_script` — pass inline; **do not** write it to disk.
|
|
@@ -86,7 +86,7 @@ python3 <SkillDir>/scripts/prepare_video_assets.py \
|
|
|
86
86
|
```bash
|
|
87
87
|
python3 <SkillDir>/scripts/prepare_video_assets.py \
|
|
88
88
|
--dsl my-video.dsl.json \
|
|
89
|
-
--template-id
|
|
89
|
+
--template-id <templateId>
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
`--template-id` automatically calls template-registry internally; or pass `--binding my-video.binding.json` if you have a pre-generated binding.
|
|
@@ -162,7 +162,7 @@ During dev / debug the user may want to exercise the pipeline without burning ge
|
|
|
162
162
|
```bash
|
|
163
163
|
python3 <SkillDir>/scripts/prepare_video_assets.py \
|
|
164
164
|
--dsl-json '<DSL_JSON_STRING>' \
|
|
165
|
-
--template-id
|
|
165
|
+
--template-id <templateId> \
|
|
166
166
|
--stub-image-url "https://cdn.example.com/placeholder.jpg"
|
|
167
167
|
```
|
|
168
168
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"source_document_url": {
|
|
37
37
|
"type": "string",
|
|
38
|
-
"description": "Source document URL (.pptx) for a template that builds its per-scene picture from that document (capabilities.payloadHydration
|
|
38
|
+
"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."
|
|
39
39
|
},
|
|
40
40
|
"binding_json": {
|
|
41
41
|
"type": "string",
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "prepare-video-assets",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "476",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V4",
|
|
6
6
|
"skillDescription": "Asset-preparation skill: resolves and generates every asset (image / audio / video) referenced by a Video DSL, persists a RenderPlan to the database, and returns a `job_id` for the subsequent `render_video` call.\n\nUse this skill as soon as the user mentions any of these intents:\n- Generate / prepare video assets, resolve assets, render-ready\n- \"Make me a video about X\" (the agent calls gen_script → prepare_video_assets → render_video)\n- Regenerate one asset (image / audio) for a specific scene\n\nNext step: after the user confirms the resolved assets, call `render_video` with the `job_id` returned by this skill.\n\n⚠️ Stop-and-confirm gate: this skill runs only after the user has confirmed the script, and after it returns you must show the resolved assets and wait for the user's explicit confirmation. Never call `render_video` in the same turn."
|
|
7
7
|
}
|