@remixmate/cli 0.9.52 → 0.9.54
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 +3 -3
- package/package.json +1 -1
- package/skills/gen-script/SKILL.md +1 -1
- package/skills/gen-script/scripts/gen_script.py +30 -4
- package/skills/prepare-video-assets/version.json +1 -1
- package/skills/render-video/scripts/render_video.py +26 -0
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +2 -1
- package/skills/template-registry/scripts/list_templates.py +1 -1
- package/skills/template-registry/skill.json +1 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +3 -0
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.54",
|
|
4
|
+
"generatedAt": "2026-09-28T11:58:29.708Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -944,7 +944,7 @@
|
|
|
944
944
|
},
|
|
945
945
|
"template_id": {
|
|
946
946
|
"type": "string",
|
|
947
|
-
"description": "Return this template's full authoring contract instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. Multi-layout templates return slideSchemas as a MENU (per layout: description, field names, card-list key / item fields / maxItems, and whether narration must be split into items) — enough to pick layouts and draft the script.
|
|
947
|
+
"description": "Return this template's full authoring contract instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. Multi-layout templates return slideSchemas as a MENU (per layout: description, field names, card-list key / item fields / maxItems, and whether narration must be split into items) — enough to pick layouts and draft the script. Tool results do not carry over between chat turns: read the menu in the turn that drafts the script; the turn that writes templateData only needs slide_id."
|
|
948
948
|
},
|
|
949
949
|
"slide_id": {
|
|
950
950
|
"type": "array",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.54",
|
|
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",
|
|
@@ -52,7 +52,7 @@ The intent is to avoid the failure mode where "the generic DSL looks compatible
|
|
|
52
52
|
|
|
53
53
|
### Mandatory steps
|
|
54
54
|
|
|
55
|
-
1. Read the requested template's full definition from the registry — `template_registry` with `template_id=<the id>` emits that one template's contract. This is the shipping source of truth.
|
|
55
|
+
1. Read the requested template's full definition from the registry — `template_registry` with `template_id=<the id>` emits that one template's contract. This is the shipping source of truth. Never fetch it twice in one turn. Tool results do **not** carry over between chat turns (each turn only sees the chat text), so the turn that writes `dsl_json` after the user confirms needs only step 3b's `slide_id` call, not the menu again. Do **not** ask for every template's full definition (`list_templates=true` + `full=true`): the whole registry is far more JSON than one tool result can carry, and the call fails instead of returning the contract.
|
|
56
56
|
2. Read its `llmHint` end to end — that is where the template states how its on-screen text and its layouts must be authored.
|
|
57
57
|
3. Read its `customPayloadSchema`: every template-specific field lives there, including the enum of legal `customPayload.slideId` values for multi-layout templates.
|
|
58
58
|
3b. If the template ships `slideSchemas` (multi-layout templates do), step 1 returns them as a **menu** — per layout: description, field names, card-list key / item fields / `maxItems`, and a `narration` note. Pick layouts and draft the script from the menu. Then, before writing `templateData`, call `template_registry` once with `template_id` and `slide_id=[every chosen layout]`, and **author `templateData` straight from those `slideSchemas[slideId]`** — it is the only place the per-layout field names exist. Do not infer them: the same concept is named `name` in one layout, `label` in another and `era` in a third, and a wrong name is dropped in silence (see below).
|
|
@@ -418,7 +418,7 @@ def estimate_narration_text(
|
|
|
418
418
|
session wrote 45–58 chars into 8s scenes and shipped 117s for a 75s target.
|
|
419
419
|
"""
|
|
420
420
|
fmt = {
|
|
421
|
-
"label": label, "topic": topic, "duration": duration,
|
|
421
|
+
"label": label, "topic": _clip(topic, _PLACEHOLDER_TOPIC_CHARS), "duration": duration,
|
|
422
422
|
"budget": _budget_suffix(duration, language, speed, tail_pad_sec),
|
|
423
423
|
}
|
|
424
424
|
if purpose == "opening":
|
|
@@ -728,6 +728,32 @@ def _placeholder_text_layers(rules: dict) -> list[dict]:
|
|
|
728
728
|
return layers
|
|
729
729
|
|
|
730
730
|
|
|
731
|
+
# 视频标题 / 占位旁白里引用 topic 的长度上限。topic 是给模型写脚本用的内容提要,
|
|
732
|
+
# 线上常见 200+ 字(2026-09-27 一条会话 250 字),原样拿去当标题或塞进每场占位旁白
|
|
733
|
+
# 都不合适。
|
|
734
|
+
_TITLE_MAX_CHARS = 60
|
|
735
|
+
_PLACEHOLDER_TOPIC_CHARS = 30
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _clip(text: str, limit: int) -> str:
|
|
739
|
+
text = " ".join((text or "").split())
|
|
740
|
+
return text if len(text) <= limit else text[: limit - 1].rstrip() + "…"
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _video_title(headline: str | None, topic: str) -> str:
|
|
744
|
+
"""meta.title:成片的标题。
|
|
745
|
+
|
|
746
|
+
它会成为上传点播的标题(render_video 的 upload_title_hint)和脚本产物列表的
|
|
747
|
+
标题(ab-api artifact_narration.dslTitle)。以前直接等于 topic —— 模型传进来的
|
|
748
|
+
是一段内容提要,于是作品标题成了「讲解 AI Agent 的核心底层逻辑「Agent Loop」:
|
|
749
|
+
传统大模型的单次响应 vs…」(render_video 只能截到 128 字兜底)。用户给了
|
|
750
|
+
headline 就用 headline(折掉换行),没给才退回截短的 topic;完整 topic 仍在
|
|
751
|
+
meta.topic。
|
|
752
|
+
"""
|
|
753
|
+
# 画面文案里的 **强调** 标记对标题没有意义,去掉。
|
|
754
|
+
return _clip((headline or "").replace("**", ""), _TITLE_MAX_CHARS) or _clip(topic, _TITLE_MAX_CHARS)
|
|
755
|
+
|
|
756
|
+
|
|
731
757
|
def _normalize_linebreaks(value: str | None) -> str:
|
|
732
758
|
"""把画面文字里的**字面量** ``\\n`` 折成真换行。
|
|
733
759
|
|
|
@@ -1168,7 +1194,7 @@ def _build_carousel_caption_dsl(
|
|
|
1168
1194
|
|
|
1169
1195
|
# ── Narration skeleton ────────────────────────────────────────────────
|
|
1170
1196
|
narration_text = _localize_label(output_language, "narration_opening",
|
|
1171
|
-
label="", topic=topic, duration=duration)
|
|
1197
|
+
label="", topic=_clip(topic, _PLACEHOLDER_TOPIC_CHARS), duration=duration)
|
|
1172
1198
|
|
|
1173
1199
|
# ── Effective scene duration ──────────────────────────────────────────
|
|
1174
1200
|
# fit-caption 策略:时长由打字机自然节奏决定,而不是盲取调用方传入的 targetDuration。
|
|
@@ -1220,7 +1246,7 @@ def _build_carousel_caption_dsl(
|
|
|
1220
1246
|
dsl = {
|
|
1221
1247
|
"version": "v1alpha1",
|
|
1222
1248
|
"meta": {
|
|
1223
|
-
"title": topic,
|
|
1249
|
+
"title": _video_title(headline, topic),
|
|
1224
1250
|
"topic": topic,
|
|
1225
1251
|
"headline": effective_headline,
|
|
1226
1252
|
"subheadline": effective_subheadline,
|
|
@@ -1879,7 +1905,7 @@ def build_dsl(
|
|
|
1879
1905
|
dsl = {
|
|
1880
1906
|
"version": "v1alpha1",
|
|
1881
1907
|
"meta": {
|
|
1882
|
-
"title": topic,
|
|
1908
|
+
"title": _video_title(effective_headline if headline else None, topic),
|
|
1883
1909
|
"topic": topic,
|
|
1884
1910
|
"headline": effective_headline,
|
|
1885
1911
|
"subheadline": effective_subheadline,
|
|
@@ -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": "V5",
|
|
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
|
}
|
|
@@ -196,6 +196,23 @@ def _narration_language_boost(narration: dict) -> Optional[str]:
|
|
|
196
196
|
return raw.strip()
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
def _narration_voice(narration: dict) -> Optional[str]:
|
|
200
|
+
"""Read a narration block's voice id, or None when unset.
|
|
201
|
+
|
|
202
|
+
Same tolerance as _narration_speed. Exists because the voice used to be read
|
|
203
|
+
only from the audio asset's payload.voiceId — which gen_script bakes once
|
|
204
|
+
when it drafts the skeleton — so changing global.narration.voiceId (or a
|
|
205
|
+
scene's voice) after that never reached TTS: the video kept the original
|
|
206
|
+
voice while every other layer reported the new one (2026-09-28).
|
|
207
|
+
"""
|
|
208
|
+
if not isinstance(narration, dict):
|
|
209
|
+
return None
|
|
210
|
+
raw = narration.get("voiceId")
|
|
211
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
212
|
+
return None
|
|
213
|
+
return raw.strip()
|
|
214
|
+
|
|
215
|
+
|
|
199
216
|
def extract_narration_lines(narration: dict) -> Optional[tuple[list[str], int, list[Optional[float]]]]:
|
|
200
217
|
"""If narration uses the structured {intro, items, outro} form, return
|
|
201
218
|
(lines, intro_line_count, at_sec_list). `at_sec_list` is parallel to
|
|
@@ -783,6 +800,7 @@ def build_render_plan(dsl: dict, binding: dict) -> dict:
|
|
|
783
800
|
global_narration = (dsl.get("global") or {}).get("narration") or {}
|
|
784
801
|
global_speed = _narration_speed(global_narration)
|
|
785
802
|
global_language_boost = _narration_language_boost(global_narration)
|
|
803
|
+
global_voice = _narration_voice(global_narration)
|
|
786
804
|
for scene in dsl.get("scenes", []):
|
|
787
805
|
narration = (scene.get("audio") or {}).get("narration") or {}
|
|
788
806
|
ref = narration.get("assetRef")
|
|
@@ -790,6 +808,14 @@ def build_render_plan(dsl: dict, binding: dict) -> dict:
|
|
|
790
808
|
if not asset:
|
|
791
809
|
continue
|
|
792
810
|
payload = asset.setdefault("payload", {})
|
|
811
|
+
# Voice follows the same precedence as speed: scene narration.voiceId >
|
|
812
|
+
# global.narration.voiceId. The asset's own payload.voiceId (written once
|
|
813
|
+
# by gen_script) is only the last resort for DSLs that declare neither —
|
|
814
|
+
# letting it win meant a voice change made after the script was drafted
|
|
815
|
+
# never reached TTS.
|
|
816
|
+
voice = _narration_voice(narration) or global_voice
|
|
817
|
+
if voice:
|
|
818
|
+
payload["voiceId"] = voice
|
|
793
819
|
# Speech rate follows the same route as the narration text: single source
|
|
794
820
|
# of truth on the DSL, copied down here because resolve_asset_audio only
|
|
795
821
|
# ever sees the asset. Scene-level overrides global (mirrors the narration
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "render-video",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "473",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V32",
|
|
6
6
|
"skillDescription": "Final-render skill: loads a persisted RenderPlan by `job_id` and drives the Remotion engine to produce the final video.\n\nUse this skill as soon as the user mentions any of these intents (after assets are already prepared):\n- Render the video, composite the video, export the video\n- Turn the prepared assets into the final clip\n- Render with Remotion\n\nPrerequisite: assets must already be generated via `prepare_video_assets`. This skill never resolves or regenerates assets — pass it a `job_id` from a previous `prepare_video_assets` call.\n\n⚠️ Stop-and-confirm gate: never call this skill until the user has explicitly confirmed the assets prepared by `prepare_video_assets`. If those assets were prepared in the current turn and the user has not replied since, stop and ask instead of rendering."
|
|
7
7
|
}
|
|
@@ -61,7 +61,7 @@ the registry is loaded **only** from the ab-api HTTP endpoint — explicit `VIDE
|
|
|
61
61
|
> The Python binding logic that maps DSL → TemplateBinding lives in `scripts/match_template.py` but is **not exposed as a CLI** — it is only consumed as a Python library by `render-video`'s `render_video.py` via `import match_template`.
|
|
62
62
|
|
|
63
63
|
1. **List available templates**: run `remixmate template-registry --list-templates` to view the templates in the registry along with their supported aspect ratios / style tags, and decide which `templateId` to pick.
|
|
64
|
-
2. **Read that template's contract**: run `remixmate template-registry --template-id <id>` for the full definition (llmHint, `customPayloadSchema` with the legal `slideId` values, `slotMapping`, `variants`). The list view only carries a 200-char llmHint preview, which is not enough to author against.
|
|
64
|
+
2. **Read that template's contract**: run `remixmate template-registry --template-id <id>` for the full definition (llmHint, `customPayloadSchema` with the legal `slideId` values, `slotMapping`, `variants`). The list view only carries a 200-char llmHint preview, which is not enough to author against. Never fetch it twice in one turn. In the chat agent, tool results do **not** carry over between turns, so fetch per turn only what that turn needs: the menu while drafting the script, `--slide-id` while writing `templateData`.
|
|
65
65
|
2b. **Multi-layout templates: expand only the layouts you use.** For templates that ship `slideSchemas`, step 2 returns a per-layout **menu** (description, field names, card-list key / item fields / `maxItems`, and a `narration` note on layouts that must split narration into `items`). Choose layouts and draft the script from the menu; then, right before writing `templateData`, call once more with `--slide-id <id>` for every chosen layout (repeatable) to get their exact field schemas.
|
|
66
66
|
3. **Write the DSL**: when generating the Video DSL, put the chosen `templateId` into `meta.templateId` (the canonical location). Use `meta.templateVariant` / `renderHints.templateVariant` to explicitly select a variant. The legacy `renderHints.templatePreference[0]` is still tolerated by `match_template.py` and `dsl_validator._pick_template_id` during transition, but new authors should write `meta.templateId`.
|
|
67
67
|
4. **Produce the TemplateBinding**: there is no standalone CLI for DSL → TemplateBinding; `prepare_video_assets` calls `match_template.build_binding(template, dsl)` inline during the asset-resolution pipeline and embeds the binding into the RenderPlan it hands to the renderer — no separate `*.binding.json` file is written.
|
|
@@ -169,6 +169,7 @@ Rules:
|
|
|
169
169
|
- If the author explicitly provides a `highlightMap`, the system keeps it as-is.
|
|
170
170
|
- The legacy flat `narration.text` form still works but the author then has to keep `highlightMap` in sync with the subtitle segmentation (error-prone, not recommended).
|
|
171
171
|
- `narration.languageBoost` (optional, also accepted at `global.narration`) pins the TTS language hint (`auto` / `Chinese` / `English` / …). Leave it unset unless auto-detection gets it wrong — the service applies its own default, and a scene-level value overrides the global one, exactly like `speed`.
|
|
172
|
+
- Voice: to change the narration voice, set `global.narration.voiceId` (whole video) or `narration.voiceId` on a scene (that scene only). Precedence when synthesizing is scene > global > the audio asset's `payload.voiceId`, which is only a fallback — editing the asset payload alone is not how you change a voice.
|
|
172
173
|
|
|
173
174
|
## Image sources
|
|
174
175
|
|
|
@@ -256,7 +256,7 @@ def _apply_slide_view(tpl: dict, slide_ids: list[str] | None) -> dict:
|
|
|
256
256
|
"MENU ONLY — per-layout summary, not the full schema. Use it to choose layouts and draft the "
|
|
257
257
|
"script. Before writing templateData, call template_registry again with this template_id and "
|
|
258
258
|
"slide_id=<each chosen layout> (repeatable) to get their exact field schemas. "
|
|
259
|
-
"
|
|
259
|
+
"Tool results do not carry over between chat turns: fetch the menu while drafting the script, slide_id while writing templateData."
|
|
260
260
|
),
|
|
261
261
|
}
|
|
262
262
|
for sid in ids:
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"type": "object",
|
|
13
13
|
"properties": {
|
|
14
14
|
"list_templates": { "type": "boolean", "description": "List available templates as summaries — templateId / name / description / aspect ratios / language / status / styleTags / variantIds / key capabilities / a truncated llmHint (default behavior; also implied when other flags are passed)." },
|
|
15
|
-
"template_id": { "type": "string", "description": "Return this template's full authoring contract instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. Multi-layout templates return slideSchemas as a MENU (per layout: description, field names, card-list key / item fields / maxItems, and whether narration must be split into items) — enough to pick layouts and draft the script.
|
|
15
|
+
"template_id": { "type": "string", "description": "Return this template's full authoring contract instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. Multi-layout templates return slideSchemas as a MENU (per layout: description, field names, card-list key / item fields / maxItems, and whether narration must be split into items) — enough to pick layouts and draft the script. Tool results do not carry over between chat turns: read the menu in the turn that drafts the script; the turn that writes templateData only needs slide_id." },
|
|
16
16
|
"slide_id": { "type": "array", "items": { "type": "string" }, "description": "With template_id: return the FULL templateData schema for these layouts (the slideIds you chose from the menu). Call this once, right before writing templateData, with every layout the script uses. 'all' returns every layout (large — avoid)." },
|
|
17
17
|
"full": { "type": "boolean", "description": "List mode only: emit full definitions for every listed template instead of summaries. Rejected when the result would be too large for one tool result — prefer template_id, or narrow with the filter_* parameters." },
|
|
18
18
|
"filter_tag": { "type": "string", "description": "Show only templates whose styleTags contain (or are contained in) this string. Case-insensitive. E.g. 'tech' / '科普' / 'walkthrough'." },
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "template-registry",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "475",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V25",
|
|
6
6
|
"skillDescription": "Video-template registry skill. Stores every video-template definition and lists the available templates (templateId / name / aspect ratio / style tags).\n\nUse this skill as soon as the user mentions any of these intents:\n- View available templates / list every template\n\nNote: DSL→TemplateBinding is no longer a separate exposed step — once prepare_video_assets receives a template_id it builds the binding internally."
|
|
7
7
|
}
|
|
@@ -247,6 +247,9 @@
|
|
|
247
247
|
},
|
|
248
248
|
"outro": { "type": "string", "description": "可选:收尾句(此期间无卡片高亮)" },
|
|
249
249
|
"assetRef": { "type": "string", "description": "旁白音频 assetId" },
|
|
250
|
+
"voiceId": { "type": "string", "description": "该场旁白音色,覆盖 global.narration.voiceId;留空跟随全局。合成时优先级:场景 > 全局 > 音频资产 payload.voiceId(仅作兜底)。" },
|
|
251
|
+
"voiceName": { "type": "string", "description": "voiceId 的展示名(仅展示用,不参与合成)" },
|
|
252
|
+
"speed": { "type": "number", "description": "该场旁白语速,覆盖 global.narration.speed;留空跟随全局。" },
|
|
250
253
|
"languageBoost": { "type": "string", "description": "TTS 语种提示(auto / Chinese / English …)。中英混排的旁白不给提示时,英文词容易被按中文音系读出来;覆盖 global.narration 的同名字段;留空则跟随全局/服务端默认。" }
|
|
251
254
|
}
|
|
252
255
|
},
|