@remixmate/cli 0.9.72 → 0.9.74
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/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -192
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +44 -3
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -22
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
22
|
|
|
23
23
|
import difflib
|
|
24
24
|
import re
|
|
@@ -30,25 +30,25 @@ VALID_RESOLUTIONS = {"480p", "720p", "1080p", "4k"}
|
|
|
30
30
|
VALID_PURPOSES = {"opening", "intro", "point", "example", "explanation", "transition", "highlight", "cta", "ending"}
|
|
31
31
|
VALID_LAYOUTS = {"full-visual", "split-left-right", "split-top-bottom", "picture-in-picture", "text-overlay", "avatar-with-bg", "kenburns"}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
36
|
DEFAULT_MAX_SCENES = 20
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
|
|
40
40
|
MAX_SCENES_CEILING = 200
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def max_scenes_for_template(template_config: Optional[dict]) -> int:
|
|
44
|
-
"""模板声明的场景数上限,缺省 ``DEFAULT_MAX_SCENES``。
|
|
45
44
|
|
|
46
|
-
单一实现:gen-script 规划场景时用它决定 clamp 到几,dsl_validator 用它做门禁。
|
|
47
|
-
两边各写一份 ``min(n, 20)`` 正是这个仓库反复栽的那种跟头。
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
52
|
caps = (template_config or {}).get("capabilities")
|
|
53
53
|
if not isinstance(caps, dict):
|
|
54
54
|
return DEFAULT_MAX_SCENES
|
|
@@ -62,24 +62,24 @@ VALID_ASSET_TYPES = {"image", "video", "audio", "avatar", "subtitle", "bgm"}
|
|
|
62
62
|
VALID_ASSET_SOURCES = {"existing", "gen-image", "gen-video", "gen-voice", "gen-digital-human"}
|
|
63
63
|
|
|
64
64
|
def _frame_level_keys(tpl: dict) -> frozenset:
|
|
65
|
-
"""模板声明的 frame 级字段(``capabilities.frameLevelKeys.keys``)。
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
72
|
spec = (tpl.get("capabilities") or {}).get("frameLevelKeys")
|
|
73
73
|
keys = spec.get("keys") if isinstance(spec, dict) else None
|
|
74
74
|
return frozenset(k for k in keys if isinstance(k, str)) if isinstance(keys, list) else frozenset()
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
_CJK_PATTERN = re.compile(r"[一-鿿㐀-䶿- -〿]")
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class ValidationError:
|
|
@@ -96,28 +96,28 @@ class ValidationError:
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def errors_as_strings(errors: Iterable[ValidationError]) -> list[str]:
|
|
99
|
-
"""Convert ValidationError objects to ``"path: message"`` strings.
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
106
|
return [f"{e.path}: {e.message}" for e in errors]
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def _pick_template_id(dsl: dict) -> Optional[str]:
|
|
115
|
-
"""Pick the templateId from DSL meta (single source of truth).
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
121
|
meta = dsl.get("meta") or {}
|
|
122
122
|
if isinstance(meta, dict):
|
|
123
123
|
tid = meta.get("templateId")
|
|
@@ -131,33 +131,33 @@ def _pick_template_id(dsl: dict) -> Optional[str]:
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def _line_sync_slides_for_template(template_id: Optional[str]) -> dict[str, str]:
|
|
134
|
-
"""Look up ``lineSyncSlides`` for a template from registry.json.
|
|
135
134
|
|
|
136
|
-
新增多项卡片幻灯片只需在 template.json 加一行 ``lineSyncSlides[slideId] = key``,
|
|
137
|
-
不再需要改 remixmate。registry 读取失败(环境缺 registry_loader、HTTP 不通等)
|
|
138
|
-
时返回空 dict,调用方将跳过相关校验。
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
143
|
return line_sync_slides(_get_template_cfg(template_id))
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
def line_sync_slides(tpl: Optional[dict]) -> dict[str, str]:
|
|
147
|
-
"""模板的 ``lineSyncSlides`` 声明:slideId → templateData 里卡片数组的键。
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
152
|
spec = (tpl or {}).get("lineSyncSlides") or {}
|
|
153
153
|
return {k: v for k, v in spec.items() if not k.startswith("_") and isinstance(v, str)}
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
def step_alignment_field(tpl: Optional[dict]) -> Optional[str]:
|
|
157
|
-
"""模板 ``capabilities.stepAlignment`` 声明的动作数组字段;未声明返回 None。
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
161
|
caps = (tpl or {}).get("capabilities")
|
|
162
162
|
spec = caps.get("stepAlignment") if isinstance(caps, dict) else None
|
|
163
163
|
if not isinstance(spec, dict):
|
|
@@ -169,23 +169,23 @@ def step_alignment_field(tpl: Optional[dict]) -> Optional[str]:
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
def _get_template_cfg(template_id: Optional[str]) -> Optional[dict]:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
185
|
if not template_id:
|
|
186
186
|
return None
|
|
187
187
|
try:
|
|
188
|
-
from registry_loader import get_template
|
|
188
|
+
from registry_loader import get_template
|
|
189
189
|
except ImportError:
|
|
190
190
|
return None
|
|
191
191
|
try:
|
|
@@ -195,25 +195,25 @@ def _get_template_cfg(template_id: Optional[str]) -> Optional[dict]:
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def _check_template_status_gate(dsl: dict) -> list[ValidationError]:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
206
|
template_id = _pick_template_id(dsl)
|
|
207
207
|
if not template_id:
|
|
208
208
|
return []
|
|
209
209
|
try:
|
|
210
|
-
from registry_loader import gated_status
|
|
210
|
+
from registry_loader import gated_status
|
|
211
211
|
except ImportError:
|
|
212
212
|
return []
|
|
213
213
|
try:
|
|
214
214
|
status = gated_status(template_id)
|
|
215
215
|
except Exception:
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
return []
|
|
218
218
|
if not status:
|
|
219
219
|
return []
|
|
@@ -231,9 +231,9 @@ def _contains_cjk(text: str) -> bool:
|
|
|
231
231
|
return bool(text) and bool(_CJK_PATTERN.search(text))
|
|
232
232
|
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
def _check_version(dsl: dict) -> list[ValidationError]:
|
|
@@ -246,7 +246,7 @@ def _check_version(dsl: dict) -> list[ValidationError]:
|
|
|
246
246
|
|
|
247
247
|
|
|
248
248
|
def _check_meta_presence(dsl: dict) -> list[ValidationError]:
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
errors: list[ValidationError] = []
|
|
251
251
|
meta = dsl.get("meta")
|
|
252
252
|
if not isinstance(meta, dict):
|
|
@@ -258,14 +258,14 @@ def _check_meta_presence(dsl: dict) -> list[ValidationError]:
|
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
def _check_global_presence(dsl: dict) -> list[ValidationError]:
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
if not isinstance(dsl.get("global"), dict):
|
|
263
263
|
return [ValidationError("global", "required and must be an object")]
|
|
264
264
|
return []
|
|
265
265
|
|
|
266
266
|
|
|
267
267
|
def _check_global_enums(dsl: dict) -> list[ValidationError]:
|
|
268
|
-
|
|
268
|
+
|
|
269
269
|
errors: list[ValidationError] = []
|
|
270
270
|
g = dsl.get("global")
|
|
271
271
|
if isinstance(g, dict):
|
|
@@ -283,7 +283,7 @@ def _check_global_enums(dsl: dict) -> list[ValidationError]:
|
|
|
283
283
|
|
|
284
284
|
|
|
285
285
|
def _check_scenes_min(dsl: dict) -> list[ValidationError]:
|
|
286
|
-
|
|
286
|
+
|
|
287
287
|
scenes = dsl.get("scenes", [])
|
|
288
288
|
if not isinstance(scenes, list) or len(scenes) == 0:
|
|
289
289
|
return [ValidationError("scenes", "must contain at least 1 scene")]
|
|
@@ -291,17 +291,17 @@ def _check_scenes_min(dsl: dict) -> list[ValidationError]:
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def _check_scenes_max(dsl: dict) -> list[ValidationError]:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
305
|
scenes = dsl.get("scenes", [])
|
|
306
306
|
if not isinstance(scenes, list):
|
|
307
307
|
return []
|
|
@@ -320,7 +320,7 @@ def _check_scenes_max(dsl: dict) -> list[ValidationError]:
|
|
|
320
320
|
|
|
321
321
|
|
|
322
322
|
def _check_scene_required_fields(dsl: dict) -> list[ValidationError]:
|
|
323
|
-
|
|
323
|
+
|
|
324
324
|
errors: list[ValidationError] = []
|
|
325
325
|
for i, scene in enumerate(dsl.get("scenes", []) or []):
|
|
326
326
|
if not scene.get("id"):
|
|
@@ -331,7 +331,7 @@ def _check_scene_required_fields(dsl: dict) -> list[ValidationError]:
|
|
|
331
331
|
|
|
332
332
|
|
|
333
333
|
def _check_scene_id_uniqueness(dsl: dict) -> list[ValidationError]:
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
errors: list[ValidationError] = []
|
|
336
336
|
seen: set = set()
|
|
337
337
|
for i, scene in enumerate(dsl.get("scenes", []) or []):
|
|
@@ -346,7 +346,7 @@ def _check_scene_id_uniqueness(dsl: dict) -> list[ValidationError]:
|
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
def _check_scene_enum_warnings(dsl: dict) -> list[ValidationError]:
|
|
349
|
-
|
|
349
|
+
|
|
350
350
|
errors: list[ValidationError] = []
|
|
351
351
|
for i, scene in enumerate(dsl.get("scenes", []) or []):
|
|
352
352
|
purpose = scene.get("purpose")
|
|
@@ -365,7 +365,7 @@ def _check_scene_enum_warnings(dsl: dict) -> list[ValidationError]:
|
|
|
365
365
|
|
|
366
366
|
|
|
367
367
|
def _check_asset_duplicates(dsl: dict) -> list[ValidationError]:
|
|
368
|
-
|
|
368
|
+
|
|
369
369
|
errors: list[ValidationError] = []
|
|
370
370
|
counts: dict[str, int] = {}
|
|
371
371
|
for a in dsl.get("assets", []) or []:
|
|
@@ -380,7 +380,7 @@ def _check_asset_duplicates(dsl: dict) -> list[ValidationError]:
|
|
|
380
380
|
|
|
381
381
|
|
|
382
382
|
def _check_asset_enums(dsl: dict) -> list[ValidationError]:
|
|
383
|
-
|
|
383
|
+
|
|
384
384
|
errors: list[ValidationError] = []
|
|
385
385
|
for i, asset in enumerate(dsl.get("assets", []) or []):
|
|
386
386
|
atype = asset.get("type")
|
|
@@ -397,10 +397,10 @@ def _check_asset_enums(dsl: dict) -> list[ValidationError]:
|
|
|
397
397
|
|
|
398
398
|
|
|
399
399
|
def _check_scene_asset_refs(dsl: dict) -> list[ValidationError]:
|
|
400
|
-
"""render-video 历史:scene 引用的 assetRef 必须在 assets[] 声明过。
|
|
401
400
|
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
404
|
errors: list[ValidationError] = []
|
|
405
405
|
declared = {
|
|
406
406
|
a.get("assetId") for a in (dsl.get("assets") or []) if a.get("assetId")
|
|
@@ -414,7 +414,7 @@ def _check_scene_asset_refs(dsl: dict) -> list[ValidationError]:
|
|
|
414
414
|
|
|
415
415
|
|
|
416
416
|
def _check_assetbindings_deprecation(dsl: dict) -> list[ValidationError]:
|
|
417
|
-
|
|
417
|
+
|
|
418
418
|
errors: list[ValidationError] = []
|
|
419
419
|
declared = {
|
|
420
420
|
a.get("assetId") for a in (dsl.get("assets") or []) if a.get("assetId")
|
|
@@ -432,20 +432,20 @@ def _check_assetbindings_deprecation(dsl: dict) -> list[ValidationError]:
|
|
|
432
432
|
|
|
433
433
|
|
|
434
434
|
def _check_narration_items_text_coexist(dsl: dict) -> list[ValidationError]:
|
|
435
|
-
"""分行旁白与整段 ``narration.text`` 并存 → warning(text 会被整个忽略)。
|
|
436
435
|
|
|
437
|
-
渲染侧是 **items 优先、text 完全忽略**:``extract_narration_lines`` 一旦拿到
|
|
438
|
-
items,TTS 文本就是 ``"\\n".join(items)``,字幕也改走 ``split_subtitle_from_lines``,
|
|
439
|
-
``narration.text`` 既不喂 TTS 也不切字幕。
|
|
440
436
|
|
|
441
|
-
为什么值得报:gen_script 的骨架**总是**写一条 ``narration.text``,agent 之后为
|
|
442
|
-
逐行高亮补 items 时不会删它,于是两者并存几乎是这类模板的默认结局。留着的那
|
|
443
|
-
一份是死数据 —— 它曾经在旁白编辑面板里被当成"可以单独改、单独重新生成"的一
|
|
444
|
-
行摆出来(实测一个场景因此显示三行,其中一行是另外两行拼起来的),改它不影响
|
|
445
|
-
成片,生成它照样扣积分。
|
|
446
437
|
|
|
447
|
-
|
|
448
|
-
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
449
449
|
errors: list[ValidationError] = []
|
|
450
450
|
for idx, scene in enumerate(dsl.get("scenes", []) or []):
|
|
451
451
|
sid = scene.get("id", f"scenes[{idx}]")
|
|
@@ -477,12 +477,12 @@ def _check_narration_items_text_coexist(dsl: dict) -> list[ValidationError]:
|
|
|
477
477
|
|
|
478
478
|
|
|
479
479
|
def _check_narration_items_count(dsl: dict) -> list[ValidationError]:
|
|
480
|
-
"""render-video / 完整版:结构化 narration items 数与 templateData 的对应数组等长。
|
|
481
480
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
486
|
errors: list[ValidationError] = []
|
|
487
487
|
template_id = _pick_template_id(dsl)
|
|
488
488
|
line_sync_slides = _line_sync_slides_for_template(template_id)
|
|
@@ -502,7 +502,7 @@ def _check_narration_items_count(dsl: dict) -> list[ValidationError]:
|
|
|
502
502
|
card_list = tdata.get(items_key)
|
|
503
503
|
if not isinstance(card_list, list):
|
|
504
504
|
continue
|
|
505
|
-
|
|
505
|
+
|
|
506
506
|
actual = len([x for x in items if (isinstance(x, str) and x.strip()) or isinstance(x, dict)])
|
|
507
507
|
expected = len(card_list)
|
|
508
508
|
if actual != expected:
|
|
@@ -515,29 +515,29 @@ def _check_narration_items_count(dsl: dict) -> list[ValidationError]:
|
|
|
515
515
|
return errors
|
|
516
516
|
|
|
517
517
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
523
|
SLIDE_ID_SENTINEL = "__CHOOSE_SLIDE__"
|
|
524
524
|
|
|
525
525
|
|
|
526
526
|
def _check_slide_id_chosen(dsl: dict) -> list[ValidationError]:
|
|
527
|
-
"""多版式模板的每个场景都必须显式选一个 ``customPayload.slideId``。
|
|
528
527
|
|
|
529
|
-
为什么需要这道门禁:slideId 缺失(或还是哨兵)时,渲染端会静默回落到
|
|
530
|
-
``DefaultSlide`` —— 渲染成功、零日志、退出码 0,画面只剩一行居中标题。这是整条
|
|
531
|
-
管线里最安静的失效方式,看起来"像是模板本来就长这样",只有付完渲染的钱才会发现。
|
|
532
528
|
|
|
533
|
-
**判定「是不是多版式模板」不写死模板 id**,而是看模板自己声明的
|
|
534
|
-
``customPayloadSchema.slideId.enum`` 有没有超过一个取值 —— 有得挑才要求挑。
|
|
535
|
-
单版式模板(枚举只有一项或压根没声明)不拦:那里没有可选的东西,报错只会变成
|
|
536
|
-
修不掉的常驻噪音。
|
|
537
529
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
|
|
541
541
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
542
542
|
if not tpl:
|
|
543
543
|
return []
|
|
@@ -574,19 +574,19 @@ def _check_slide_id_chosen(dsl: dict) -> list[ValidationError]:
|
|
|
574
574
|
|
|
575
575
|
|
|
576
576
|
def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
|
|
577
|
-
"""字段之间的一致性 —— 单看任何一个字段都合法,凑在一起才不成立。
|
|
578
577
|
|
|
579
|
-
schema 管不了这一类:它逐字段校验类型与枚举,表达不了"这个数必须落在另一个字段的
|
|
580
|
-
长度内"。三条规则都**静态可判定**,而且失败形态一致 —— 画面少东西、零报错:
|
|
581
578
|
|
|
582
|
-
· ``annotations[].line`` 指向不存在的代码行 → 那句旁白期间没有任何一行点亮
|
|
583
|
-
· ``highlightMap`` 的值越过卡片数组 → 那一行全屏变暗、无一张点亮
|
|
584
|
-
· 黑板版 ``highlight`` 不是 ``title`` 的子串 → 标题静默退回单色(模板 llmHint
|
|
585
|
-
自己写明了这个行为,正因为写明了才更该校验)
|
|
586
579
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
590
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
591
591
|
if not tpl:
|
|
592
592
|
return []
|
|
@@ -612,7 +612,7 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
|
|
|
612
612
|
continue
|
|
613
613
|
base = f"scenes[{idx}].customPayload.templateData"
|
|
614
614
|
|
|
615
|
-
|
|
615
|
+
|
|
616
616
|
code = tdata.get("code")
|
|
617
617
|
annotations = tdata.get("annotations")
|
|
618
618
|
if isinstance(code, str) and isinstance(annotations, list):
|
|
@@ -631,7 +631,7 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
|
|
|
631
631
|
"up at all. Line numbers are 1-based.",
|
|
632
632
|
))
|
|
633
633
|
|
|
634
|
-
|
|
634
|
+
|
|
635
635
|
hmap = tdata.get("highlightMap")
|
|
636
636
|
cards_key = line_sync.get(slide_id)
|
|
637
637
|
cards = tdata.get(cards_key) if cards_key else None
|
|
@@ -647,8 +647,8 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
|
|
|
647
647
|
"line every card goes dim and none lights up.",
|
|
648
648
|
))
|
|
649
649
|
|
|
650
|
-
|
|
651
|
-
|
|
650
|
+
|
|
651
|
+
|
|
652
652
|
if "highlight" in ((schemas.get(slide_id) or {}).get("properties") or {}):
|
|
653
653
|
highlight = tdata.get("highlight")
|
|
654
654
|
title = tdata.get("title")
|
|
@@ -669,23 +669,23 @@ def _check_slide_cross_field(dsl: dict) -> list[ValidationError]:
|
|
|
669
669
|
|
|
670
670
|
|
|
671
671
|
def _check_line_sync_narration(dsl: dict) -> list[ValidationError]:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
|
|
689
689
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
690
690
|
if not tpl:
|
|
691
691
|
return []
|
|
@@ -710,7 +710,7 @@ def _check_line_sync_narration(dsl: dict) -> list[ValidationError]:
|
|
|
710
710
|
if isinstance(items, list) and any(
|
|
711
711
|
(isinstance(x, str) and x.strip()) or isinstance(x, dict) for x in items
|
|
712
712
|
):
|
|
713
|
-
continue
|
|
713
|
+
continue
|
|
714
714
|
|
|
715
715
|
tdata = custom.get("templateData")
|
|
716
716
|
if not isinstance(tdata, dict):
|
|
@@ -745,24 +745,24 @@ def _check_line_sync_narration(dsl: dict) -> list[ValidationError]:
|
|
|
745
745
|
|
|
746
746
|
|
|
747
747
|
def _check_step_narration_alignment(dsl: dict) -> list[ValidationError]:
|
|
748
|
-
"""声明「动作与旁白一一对应」的模板:动作条数应当等于旁白句数。
|
|
749
748
|
|
|
750
|
-
由模板的 ``capabilities.stepAlignment`` 声明触发,不写死模板名::
|
|
751
749
|
|
|
752
|
-
"stepAlignment": {
|
|
753
|
-
"payloadField": "steps", # customPayload 里放动作数组的字段
|
|
754
|
-
"alignTo": "narration-sentences"
|
|
755
|
-
}
|
|
756
750
|
|
|
757
|
-
为什么值得单独查:这一类失配**渲得出来**。动作按句下标对齐,条数不等时多出的
|
|
758
|
-
句子没有任何动作(画面停住)或多出的动作挤在最后一句上,而退出码是 0、
|
|
759
|
-
renderJob 是 completed、漏斗记 rendered —— 缺陷只存在于成片里,没有任何
|
|
760
|
-
自动信号会告状。2026-09-23 线上那条鸡兔同笼就是这么交付的。
|
|
761
751
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
|
|
766
766
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
767
767
|
if not tpl:
|
|
768
768
|
return []
|
|
@@ -782,11 +782,11 @@ def _check_step_narration_alignment(dsl: dict) -> list[ValidationError]:
|
|
|
782
782
|
continue
|
|
783
783
|
steps = custom.get(field)
|
|
784
784
|
if not isinstance(steps, list) or not steps:
|
|
785
|
-
continue
|
|
785
|
+
continue
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
|
|
786
789
|
|
|
787
|
-
# 全部显式写了 atSec 的步骤不参与下标对齐,不该算失配。
|
|
788
|
-
# 排除 bool:Python 里 True 是 int 的实例,而渲染端用 `typeof === "number"`
|
|
789
|
-
# 判断,不认 true。两边口径必须一致,否则这里放行的东西那边仍按下标对齐。
|
|
790
790
|
def _has_explicit_at_sec(step: object) -> bool:
|
|
791
791
|
if not isinstance(step, dict):
|
|
792
792
|
return False
|
|
@@ -823,16 +823,16 @@ def _check_step_narration_alignment(dsl: dict) -> list[ValidationError]:
|
|
|
823
823
|
|
|
824
824
|
|
|
825
825
|
def _check_duration_estimate(dsl: dict) -> list[ValidationError]:
|
|
826
|
-
"""按旁白字数估出的成片时长明显超过 ``meta.targetDuration`` → warning。
|
|
827
826
|
|
|
828
|
-
为什么要有:旁白字数决定时长,但没有任何一环在出素材之前核对过。2026-09-27 线上
|
|
829
|
-
一条 html-slide 会话,确认稿写 75s、每场 8–9s,每场却写了 45–58 字,还有 7 场是
|
|
830
|
-
分行旁白(每行之间有停顿)—— 成片 116.9s,用户确认的是一个从来不可能兑现的数。
|
|
831
827
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
|
|
836
836
|
try:
|
|
837
837
|
from . import narration_budget as nb
|
|
838
838
|
except ImportError:
|
|
@@ -883,22 +883,22 @@ def _check_duration_estimate(dsl: dict) -> list[ValidationError]:
|
|
|
883
883
|
|
|
884
884
|
|
|
885
885
|
def _check_slide_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
|
|
902
902
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
903
903
|
if not tpl:
|
|
904
904
|
return []
|
|
@@ -925,11 +925,11 @@ def _check_slide_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
|
925
925
|
|
|
926
926
|
slide_id = custom.get("slideId") or data.get("slideId")
|
|
927
927
|
if not isinstance(slide_id, str) or not slide_id:
|
|
928
|
-
continue
|
|
928
|
+
continue
|
|
929
929
|
|
|
930
930
|
schema = schemas.get(slide_id)
|
|
931
931
|
if not isinstance(schema, dict):
|
|
932
|
-
|
|
932
|
+
|
|
933
933
|
errors.append(ValidationError(
|
|
934
934
|
f"scenes[{idx}].customPayload.slideId",
|
|
935
935
|
f"scene '{sid}' uses slideId '{slide_id}', which template '{tid}' does not "
|
|
@@ -944,8 +944,8 @@ def _check_slide_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
|
944
944
|
path=f"scenes[{idx}].customPayload.templateData",
|
|
945
945
|
scene=sid,
|
|
946
946
|
extra_allowed=shared,
|
|
947
|
-
|
|
948
|
-
|
|
947
|
+
|
|
948
|
+
|
|
949
949
|
skip_keys=_frame_level_keys(tpl),
|
|
950
950
|
)
|
|
951
951
|
|
|
@@ -953,15 +953,15 @@ def _check_slide_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
|
953
953
|
|
|
954
954
|
|
|
955
955
|
def _check_custom_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
956
|
-
"""单版式模板:按 ``customPayloadSchema`` 逐字段核对 ``customPayload``。
|
|
957
956
|
|
|
958
|
-
与 `_check_slide_payload_fields` 是同一件事的两种形态 —— 多版式模板的契约按 slideId
|
|
959
|
-
分支(``slideSchemas``),单版式模板只有一份(``customPayloadSchema``)。多版式模板在
|
|
960
|
-
这里**跳过**,避免同一份数据被两套规则各报一遍。
|
|
961
957
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
|
|
965
965
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
966
966
|
if not tpl:
|
|
967
967
|
return []
|
|
@@ -975,7 +975,7 @@ def _check_custom_payload_fields(dsl: dict) -> list[ValidationError]:
|
|
|
975
975
|
errors += [ValidationError(f"scenes[{idx}].customPayload", message)
|
|
976
976
|
for message in required_payload_errors(tpl, custom)]
|
|
977
977
|
if isinstance(tpl.get("slideSchemas"), dict):
|
|
978
|
-
return errors
|
|
978
|
+
return errors
|
|
979
979
|
cps = tpl.get("customPayloadSchema")
|
|
980
980
|
if not isinstance(cps, dict) or not cps:
|
|
981
981
|
return errors
|
|
@@ -1004,16 +1004,16 @@ def _walk_payload(
|
|
|
1004
1004
|
skip_keys: frozenset = frozenset(),
|
|
1005
1005
|
fallback_desc: Optional[str] = None,
|
|
1006
1006
|
) -> list[ValidationError]:
|
|
1007
|
-
"""沿 schema 递归下钻,逐层做「未声明字段 + 缺必填 + 空数组」核对。
|
|
1008
1007
|
|
|
1009
|
-
只下钻**描述了形状的**层:schema 节点没有 ``properties`` 就停(那是作者显式开的
|
|
1010
|
-
开放包,如 ppt-to-video 由抽取脚本生成的 ``slide.nodes[]``),不去猜它的字段。
|
|
1011
1008
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
1017
|
errors = _check_payload_object(
|
|
1018
1018
|
data,
|
|
1019
1019
|
schema,
|
|
@@ -1059,7 +1059,7 @@ def _check_payload_object(
|
|
|
1059
1059
|
skip_keys: frozenset = frozenset(),
|
|
1060
1060
|
fallback_desc: Optional[str] = None,
|
|
1061
1061
|
) -> list[ValidationError]:
|
|
1062
|
-
|
|
1062
|
+
|
|
1063
1063
|
props = schema.get("properties")
|
|
1064
1064
|
if not isinstance(props, dict):
|
|
1065
1065
|
return []
|
|
@@ -1086,8 +1086,8 @@ def _check_payload_object(
|
|
|
1086
1086
|
"the slide renders its built-in placeholder instead of your content.",
|
|
1087
1087
|
))
|
|
1088
1088
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
1091
|
for key, sub in props.items():
|
|
1092
1092
|
if not isinstance(sub, dict) or sub.get("type") != "array":
|
|
1093
1093
|
continue
|
|
@@ -1105,12 +1105,12 @@ def _check_payload_object(
|
|
|
1105
1105
|
|
|
1106
1106
|
|
|
1107
1107
|
def _is_blank(value: object) -> bool:
|
|
1108
|
-
"""必填判定:``None`` 与纯空白字符串都算没给。
|
|
1109
1108
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
1114
|
if value is None:
|
|
1115
1115
|
return True
|
|
1116
1116
|
return isinstance(value, str) and not value.strip()
|
|
@@ -1122,23 +1122,23 @@ def _suggest_field(
|
|
|
1122
1122
|
schema: dict,
|
|
1123
1123
|
fallback_desc: Optional[str] = None,
|
|
1124
1124
|
) -> str:
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
1137
|
for name, sub in props.items():
|
|
1138
1138
|
desc = (sub or {}).get("description") if isinstance(sub, dict) else None
|
|
1139
1139
|
if not isinstance(desc, str):
|
|
1140
1140
|
continue
|
|
1141
|
-
|
|
1141
|
+
|
|
1142
1142
|
if re.search(rf"不是\s*[`'\"]?{re.escape(wrong)}[`'\"]?(?![\w-])", desc):
|
|
1143
1143
|
return f" This layout calls it '{name}' — {desc}"
|
|
1144
1144
|
|
|
@@ -1147,10 +1147,10 @@ def _suggest_field(
|
|
|
1147
1147
|
return f" Did you mean '{hit[0]}'?"
|
|
1148
1148
|
|
|
1149
1149
|
tail = f" Declared fields: {', '.join(sorted(props))}."
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
1154
|
slide_desc = schema.get("description") or fallback_desc
|
|
1155
1155
|
if isinstance(slide_desc, str) and slide_desc.strip():
|
|
1156
1156
|
tail += f" About this layout: {slide_desc}"
|
|
@@ -1158,26 +1158,26 @@ def _suggest_field(
|
|
|
1158
1158
|
|
|
1159
1159
|
|
|
1160
1160
|
def _check_narration_language_strict(dsl: dict) -> list[ValidationError]:
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
1174
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
1175
1175
|
if not tpl:
|
|
1176
1176
|
return []
|
|
1177
1177
|
strict = (tpl.get("capabilities") or {}).get("narrationLanguageStrict")
|
|
1178
1178
|
if strict != "en":
|
|
1179
|
-
|
|
1180
|
-
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
1181
|
return []
|
|
1182
1182
|
tid = tpl.get("templateId", "<unknown>")
|
|
1183
1183
|
msg = (
|
|
@@ -1205,19 +1205,19 @@ _TEXT_LAYER_SKELETON_PREFIXES = ("[骨架待填充]", "[skeleton placeholder]")
|
|
|
1205
1205
|
|
|
1206
1206
|
|
|
1207
1207
|
def _check_text_layer_rules(dsl: dict) -> list[ValidationError]:
|
|
1208
|
-
"""模板声明 ``capabilities.textLayerRules`` 时,逐场景检查画面文字层。
|
|
1209
1208
|
|
|
1210
|
-
声明形状::
|
|
1211
1209
|
|
|
1212
|
-
"textLayerRules": {
|
|
1213
|
-
"required": ["headline", "subheadline"], # 每个场景都必须有且非空
|
|
1214
|
-
"language": {"headline": "en", "subheadline": "zh"} # en = 不含中文;zh = 须含中文
|
|
1215
|
-
}
|
|
1216
1210
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
1221
|
tpl = _get_template_cfg(_pick_template_id(dsl))
|
|
1222
1222
|
if not tpl:
|
|
1223
1223
|
return []
|
|
@@ -1272,19 +1272,19 @@ def _check_text_layer_rules(dsl: dict) -> list[ValidationError]:
|
|
|
1272
1272
|
return errors
|
|
1273
1273
|
|
|
1274
1274
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
1278
|
|
|
1279
1279
|
|
|
1280
1280
|
def validate_structural(dsl: dict) -> list[ValidationError]:
|
|
1281
|
-
"""gen-script 历史规则集 — 仅检查 DSL 整体结构 / 必填字段。
|
|
1282
1281
|
|
|
1283
|
-
与历史 ``gen_script.validate_dsl`` 同集合:
|
|
1284
|
-
version + meta(title) + global(presence) + scene 数 1–20 + 每场 id/purpose。
|
|
1285
1282
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
1288
|
errors: list[ValidationError] = []
|
|
1289
1289
|
errors += _check_version(dsl)
|
|
1290
1290
|
errors += _check_meta_presence(dsl)
|
|
@@ -1296,51 +1296,51 @@ def validate_structural(dsl: dict) -> list[ValidationError]:
|
|
|
1296
1296
|
|
|
1297
1297
|
|
|
1298
1298
|
def validate_integrity(dsl: dict) -> list[ValidationError]:
|
|
1299
|
-
"""render-video 历史规则集 — 关注 asset 引用一致性 + 数量对齐。
|
|
1300
1299
|
|
|
1301
|
-
与历史 ``render_video.validate_dsl`` 同集合:
|
|
1302
|
-
version + scene 数 + assetId 去重 + assetRef 可解析 + 结构化 narration items 数。
|
|
1303
1300
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
1306
|
errors: list[ValidationError] = []
|
|
1307
1307
|
errors += _check_version(dsl)
|
|
1308
1308
|
errors += _check_scenes_min(dsl)
|
|
1309
|
-
|
|
1310
|
-
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
1311
|
errors += _check_asset_duplicates(dsl)
|
|
1312
1312
|
errors += _check_scene_asset_refs(dsl)
|
|
1313
1313
|
errors += _check_narration_items_count(dsl)
|
|
1314
1314
|
errors += _check_narration_items_text_coexist(dsl)
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
1318
|
errors += _check_slide_id_chosen(dsl)
|
|
1319
|
-
|
|
1320
|
-
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
1321
|
errors += _check_template_status_gate(dsl)
|
|
1322
|
-
|
|
1323
|
-
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
1324
|
errors += _check_slide_payload_fields(dsl)
|
|
1325
1325
|
errors += _check_custom_payload_fields(dsl)
|
|
1326
1326
|
errors += _check_line_sync_narration(dsl)
|
|
1327
|
-
|
|
1328
|
-
|
|
1327
|
+
|
|
1328
|
+
|
|
1329
1329
|
errors += _check_step_narration_alignment(dsl)
|
|
1330
1330
|
errors += _check_slide_cross_field(dsl)
|
|
1331
1331
|
errors += _check_text_layer_rules(dsl)
|
|
1332
|
-
|
|
1332
|
+
|
|
1333
1333
|
errors += _check_duration_estimate(dsl)
|
|
1334
1334
|
return errors
|
|
1335
1335
|
|
|
1336
1336
|
|
|
1337
1337
|
def validate_dsl(dsl: dict) -> list[ValidationError]:
|
|
1338
|
-
"""完整 DSL 校验:结构 + 一致性 + 枚举 + capabilities.narrationLanguageStrict。
|
|
1339
1338
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
1344
|
errors: list[ValidationError] = []
|
|
1345
1345
|
errors += _check_version(dsl)
|
|
1346
1346
|
errors += _check_meta_presence(dsl)
|
|
@@ -1366,6 +1366,6 @@ def validate_dsl(dsl: dict) -> list[ValidationError]:
|
|
|
1366
1366
|
errors += _check_step_narration_alignment(dsl)
|
|
1367
1367
|
errors += _check_slide_cross_field(dsl)
|
|
1368
1368
|
errors += _check_text_layer_rules(dsl)
|
|
1369
|
-
|
|
1369
|
+
|
|
1370
1370
|
errors += _check_duration_estimate(dsl)
|
|
1371
1371
|
return errors
|