@remixmate/cli 0.9.72 → 0.9.73

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.
Files changed (179) hide show
  1. package/LICENSE +45 -21
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/argv.js +1 -51
  5. package/dist/auth/auth-lock.js +1 -100
  6. package/dist/auth/auto-login.js +7 -75
  7. package/dist/auth/commands.js +40 -290
  8. package/dist/auth/credential-store.js +1 -143
  9. package/dist/auth/device-flow-runner.js +13 -110
  10. package/dist/auth/device-flow.js +1 -132
  11. package/dist/auth/ensure.js +1 -62
  12. package/dist/auth/environment.js +1 -69
  13. package/dist/auth/pending-store.js +1 -45
  14. package/dist/auth/resolve.js +1 -44
  15. package/dist/billing.js +3 -76
  16. package/dist/capabilities.js +1 -75
  17. package/dist/cli.js +39 -211
  18. package/dist/diagnostics.js +2 -70
  19. package/dist/doctor.js +9 -188
  20. package/dist/errors.js +1 -33
  21. package/dist/exec.js +4 -47
  22. package/dist/handlers/gen-digital-human.js +33 -264
  23. package/dist/handlers/gen-image.js +9 -113
  24. package/dist/handlers/gen-post.js +15 -192
  25. package/dist/handlers/gen-video.js +7 -185
  26. package/dist/handlers/gen-voice.js +19 -133
  27. package/dist/handlers/index.js +1 -20
  28. package/dist/handlers/shared.js +1 -66
  29. package/dist/handlers/template-init.js +1 -21
  30. package/dist/http.js +3 -155
  31. package/dist/index.js +1 -8
  32. package/dist/install.js +40 -448
  33. package/dist/manifest.json +44 -3
  34. package/dist/progress.js +2 -15
  35. package/dist/project/commands.js +34 -156
  36. package/dist/project/host.js +1 -151
  37. package/dist/project/resolve.js +1 -65
  38. package/dist/project/store.js +2 -97
  39. package/dist/project/take.js +1 -106
  40. package/dist/project/trace.js +5 -278
  41. package/dist/registry.js +3 -70
  42. package/dist/runner.js +7 -225
  43. package/dist/skill-schema.js +1 -194
  44. package/dist/template/args.js +1 -69
  45. package/dist/template/check.js +2 -30
  46. package/dist/template/commands.js +34 -163
  47. package/dist/template/constants.js +1 -46
  48. package/dist/template/init.js +5 -133
  49. package/dist/template/list.js +1 -46
  50. package/dist/template/preview.js +7 -318
  51. package/dist/template/proc.js +1 -43
  52. package/dist/template/project.js +6 -161
  53. package/dist/template/publish.js +5 -185
  54. package/dist/template/render-client.js +2 -79
  55. package/dist/template/upgrade.js +4 -123
  56. package/dist/text.js +1 -15
  57. package/package.json +2 -35
  58. package/skills/doc-parse/scripts/extract_via_render.py +32 -32
  59. package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
  60. package/skills/gen-script/SKILL.md +23 -1
  61. package/skills/gen-script/scripts/gen_script.py +644 -551
  62. package/skills/gen-script/skill.json +12 -0
  63. package/skills/gen-script/version.json +1 -1
  64. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  65. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  66. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  67. package/skills/render-video/scripts/_video_probe.py +33 -33
  68. package/skills/render-video/scripts/_vod_polling.py +13 -13
  69. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  70. package/skills/render-video/scripts/render_video.py +639 -639
  71. package/skills/render-video/scripts/required_props.py +37 -37
  72. package/skills/render-video/scripts/upload_video.py +35 -35
  73. package/skills/render-video/version.json +1 -1
  74. package/skills/template-registry/SKILL.md +16 -0
  75. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  76. package/skills/template-registry/scripts/doc_extract.py +40 -40
  77. package/skills/template-registry/scripts/list_templates.py +303 -191
  78. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  79. package/skills/template-registry/scripts/match_template.py +87 -87
  80. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  81. package/skills/template-registry/scripts/registry_loader.py +159 -159
  82. package/skills/template-registry/scripts/render_job_client.py +75 -75
  83. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  84. package/skills/template-registry/skill.json +4 -1
  85. package/skills/template-registry/version.json +1 -1
  86. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  87. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  88. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  89. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  90. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  91. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  92. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  93. package/skills/video-parser/scripts/analyze_video.py +173 -173
  94. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  95. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  96. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  97. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  98. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  100. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  101. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  102. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  103. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  104. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  105. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  119. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  120. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  122. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  123. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  124. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  125. package/skills/web-screenshot/scripts/read_page.py +15 -15
  126. package/skills/web-screenshot/scripts/record.py +26 -26
  127. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  128. package/dist/argv.d.ts +0 -15
  129. package/dist/auth/auth-lock.d.ts +0 -26
  130. package/dist/auth/auto-login.d.ts +0 -20
  131. package/dist/auth/commands.d.ts +0 -8
  132. package/dist/auth/credential-store.d.ts +0 -60
  133. package/dist/auth/device-flow-runner.d.ts +0 -58
  134. package/dist/auth/device-flow.d.ts +0 -52
  135. package/dist/auth/ensure.d.ts +0 -55
  136. package/dist/auth/environment.d.ts +0 -38
  137. package/dist/auth/pending-store.d.ts +0 -33
  138. package/dist/auth/resolve.d.ts +0 -30
  139. package/dist/billing.d.ts +0 -44
  140. package/dist/capabilities.d.ts +0 -63
  141. package/dist/cli.d.ts +0 -13
  142. package/dist/diagnostics.d.ts +0 -24
  143. package/dist/doctor.d.ts +0 -22
  144. package/dist/errors.d.ts +0 -29
  145. package/dist/exec.d.ts +0 -18
  146. package/dist/handlers/gen-digital-human.d.ts +0 -17
  147. package/dist/handlers/gen-image.d.ts +0 -20
  148. package/dist/handlers/gen-post.d.ts +0 -22
  149. package/dist/handlers/gen-video.d.ts +0 -19
  150. package/dist/handlers/gen-voice.d.ts +0 -26
  151. package/dist/handlers/index.d.ts +0 -20
  152. package/dist/handlers/shared.d.ts +0 -26
  153. package/dist/handlers/template-init.d.ts +0 -10
  154. package/dist/http.d.ts +0 -72
  155. package/dist/index.d.ts +0 -9
  156. package/dist/install.d.ts +0 -71
  157. package/dist/progress.d.ts +0 -23
  158. package/dist/project/commands.d.ts +0 -19
  159. package/dist/project/host.d.ts +0 -91
  160. package/dist/project/resolve.d.ts +0 -43
  161. package/dist/project/store.d.ts +0 -31
  162. package/dist/project/take.d.ts +0 -50
  163. package/dist/project/trace.d.ts +0 -98
  164. package/dist/registry.d.ts +0 -30
  165. package/dist/runner.d.ts +0 -39
  166. package/dist/skill-schema.d.ts +0 -138
  167. package/dist/template/args.d.ts +0 -25
  168. package/dist/template/check.d.ts +0 -23
  169. package/dist/template/commands.d.ts +0 -25
  170. package/dist/template/constants.d.ts +0 -39
  171. package/dist/template/init.d.ts +0 -28
  172. package/dist/template/list.d.ts +0 -23
  173. package/dist/template/preview.d.ts +0 -60
  174. package/dist/template/proc.d.ts +0 -26
  175. package/dist/template/project.d.ts +0 -47
  176. package/dist/template/publish.d.ts +0 -43
  177. package/dist/template/render-client.d.ts +0 -39
  178. package/dist/template/upgrade.d.ts +0 -44
  179. package/dist/text.d.ts +0 -8
@@ -1,17 +1,17 @@
1
- """
2
- 时间线编译器 - 将 DSL + TemplateBinding 编译为 Remotion 可消费的 RenderPlan。
3
1
 
4
- 负责: 帧号计算、字幕切片、转场注入、层级分配。
5
2
 
6
- ⚠️ 跨 skill 契约:
7
- `skills/render-video/scripts/render_video.py` 通过 sys.path 注入后直接
8
- `from video_dsl.runtime.timeline_compiler import (split_subtitle,
9
- split_subtitle_from_lines, segment_narration)`。
10
- 改动以下公共函数的签名 / 返回结构前,请同步核对 render_video.py 的调用点:
11
- - split_subtitle
12
- - split_subtitle_from_lines
13
- - segment_narration
14
- """
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
15
 
16
16
  import re
17
17
  from datetime import datetime, timezone
@@ -19,51 +19,51 @@ from typing import Optional
19
19
 
20
20
  CHARS_PER_SECOND = 5.0
21
21
 
22
- # 英文句末:`.!?`(可跟一个收尾引号/括号)且后面是空白。以前只认中文标点,英文旁白
23
- # 从来不在句号处断,只能落到逗号 / 30 字空格硬切,切出 `went home. A few days later,`
24
- # 这种跨句的行,所有段的 sentenceIndex 也全是 0。常见缩写后的句点不算句末。
22
+
23
+
24
+
25
25
  _EN_SENTENCE_END = (
26
26
  r"(?:(?<=[.!?])|(?<=[.!?][\"'\u201d\u2019)]))"
27
27
  r"(?<!\bMr\.)(?<!\bMs\.)(?<!\bDr\.)(?<!\bSt\.)(?<!\bMrs\.)(?<!\be\.g\.)(?<!\bi\.e\.)(?<!\bvs\.)"
28
28
  r"(?=\s)"
29
29
  )
30
30
  _PRIMARY_BREAK = re.compile(r"(?<=[。!?;\n])|" + _EN_SENTENCE_END)
31
- # 次级断点**不含顿号 `、`**。顿号是并列项之间的分隔,在它上面下刀就是把一串并列
32
- # 拦腰切开(`调研、策划、` / `开发、分析,…`)。这一层只数字符、分不出标点的语义
33
- # 强度,所以干脆不碰它 —— 真需要再切时交给渲染端,那边是分级的(句末 > 冒号 >
34
- # 逗号 > 顿号 > 空格),只有前面几级都切不动才会降到顿号。
31
+
32
+
33
+
34
+
35
35
  _SECONDARY_BREAK = re.compile(r"(?<=[,,::])")
36
36
  _PRIMARY_TAIL = re.compile(
37
37
  r"(?:[。!?;\n]|(?<!\bMr)(?<!\bMs)(?<!\bDr)(?<!\bSt)(?<!\bMrs)(?<!\bvs)[.!?][\"'\u201d\u2019)]?)\s*$"
38
38
  )
39
39
 
40
- # ─── 这两个上限管的是「喂给 TTS 的行」,不再是「一屏字幕」 ────────────────
41
- #
42
- # 曾经是 16 / 6,那是渲染端还不会分屏时的产物:谁都不会再切,所以必须在这里
43
- # 就把旁白预切成一屏放得下的大小。现在渲染端(core 的 `chunkSubtitleSegments`)
44
- # 会按**实际字号与实际可用宽度**算容量、按**标点语义强度分级**切分,这件事它
45
- # 做得比这里好得多:
46
- # · 这里只能数字符数,数不出 `HNSW` 比 4 个中文字窄一半;
47
- # · 这里的 `_SECONDARY_BREAK` 是平的,冒号和顿号同权,会把并列项拦腰切开;
48
- # · 这里一个全局 16 要同时服务 16:9(一行放 44 字)和 9:16(一行放 22 字)。
49
- #
50
- # 于是 16 的唯一效果变成了**抢在渲染端之前把句子剁碎**。2026-09-21 线上成片实测:
51
- # 121.5s 里 57 屏、中位 2.10s 一屏、33% 短于 1.5s,「AI Agent」独占一屏 —— 全都是
52
- # 这个 16 的产物,渲染端的分屏阈值(20)一次都没被触发。
53
- #
54
- # 现在这里只负责一件事:**在真实语音停顿处断行**,让 Minimax 按停顿返回时间戳。
55
- # 句末(`_PRIMARY_BREAK`)永远断;逗号那一级只在句子长到一行 TTS 太长时才用。
56
- # 30 是「一个中文长句」的量级 —— 越过它说明这句本来就该有个呼吸点。
57
- #
58
- # ⚠️ 别再把它当字幕宽度调:想改一屏显示多少字,改 core 的
59
- # `DEFAULT_SPLIT_MAX_UNITS` / `LINE_FILL_LIMIT`,那边才看得见字号和画幅。
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
60
  MAX_SEGMENT_CHARS = 30
61
- # 合并下限保持 6:TTS 行太短会让合成的语气断掉(和字幕闪烁是两回事)。
61
+
62
62
  MIN_SEGMENT_CHARS = 6
63
63
 
64
- # 字幕显示时需要去掉的末尾标点(中英文句末 + 逗号等)
64
+
65
65
  _TRAILING_PUNCT = re.compile(r"[。!?;,,、::..…]+$")
66
- # **...** 强调标记(用于计算纯文本长度)
66
+
67
67
  _EMPHASIS_MARKS = re.compile(r"\*\*")
68
68
 
69
69
  RESOLUTION_MAP = {
@@ -90,54 +90,54 @@ def _is_latin_word_char(ch: str) -> bool:
90
90
 
91
91
 
92
92
  def _breakable(prev_token: str, next_token: str) -> bool:
93
- """两个 token 之间断开是否安全 —— 拉丁↔拉丁即同一词组内部,不安全。
94
93
 
95
- 拆与并共用这一个判据:断的时候躲开的边界,并的时候就得把空格补回去。
96
- """
94
+
95
+
96
+
97
97
  if not prev_token or not next_token:
98
98
  return True
99
99
  return not (_is_latin_word_char(prev_token[-1]) and _is_latin_word_char(next_token[0]))
100
100
 
101
101
 
102
102
  def _concat_segments(left: str, right: str) -> str:
103
- """拼接两个字幕段,必要时补回被切分吃掉的那个空格。
104
103
 
105
- ``_split_at_spaces`` 在空格处断开,空格本身不属于任何一段;直接 ``+`` 拼回去
106
- 会把 ``capabilities and`` + ``more`` 粘成 ``capabilities andmore`` —— 这串同样会
107
- 原样进 TTS,读出来就是一个不存在的词。中文两侧不补空格(中文本来就不分词)。
108
- """
104
+
105
+
106
+
107
+
108
+
109
109
  if _breakable(left, right):
110
110
  return left + right
111
111
  return left + " " + right
112
112
 
113
113
 
114
114
  def _split_at_spaces(text: str, max_len: int) -> list[str]:
115
- """Split long text at space boundaries, keeping multi-word Latin terms whole.
116
-
117
- These boundaries are not only subtitle cuts: ``segment_narration`` joins the
118
- result with ``\\n`` and that is what goes to TTS, where each line is a hard
119
- segment boundary. Cutting between two Latin words therefore breaks the term
120
- apart in the *audio* — Minimax loses both the liaison and the context that
121
- told it this was English at all.
122
-
123
- 实测(2026-09-20): ``最近刷爆科技圈的 AI Agent,`` 是 18 字、超过 16 的上限,而这
124
- 段里仅有的两个空格都在 "AI Agent" 中间,于是旧实现只能从词组中间下刀,切成
125
- ``最近刷爆科技圈的 AI`` + ``Agent,`` —— 合成出来 "AI" 收在上一段末尾、"Agent"
126
- 孤零零起一段,那个词就读不准了。同句末尾的 "Agent Loop。" 因为没超上限反而完好,
127
- 用户听到的正是"中间那个 Agent 不对"。
128
-
129
- 所以断点优先选**中文↔拉丁**边界(回退到词组之前),找不到再退回"就近断开"。
130
- 纯英文旁白里根本不存在中文↔拉丁边界,每个断点都会走回退分支,行为与旧实现逐字
131
- 相同 —— picture-book-en 这类全英文模板不受影响。
132
- """
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
133
  if len(text) <= max_len:
134
134
  return [text]
135
135
 
136
136
  parts: list[str] = []
137
- buf: list[str] = [] # 当前段已收的 token
137
+ buf: list[str] = []
138
138
  for word in text.split(" "):
139
139
  if buf and len(" ".join(buf + [word])) > max_len:
140
- # 从最靠后的边界往回找第一个安全断点;整段都没有就按旧行为就近断开。
140
+
141
141
  cut = len(buf)
142
142
  for i in range(len(buf), 0, -1):
143
143
  following = word if i == len(buf) else buf[i]
@@ -153,28 +153,28 @@ def _split_at_spaces(text: str, max_len: int) -> list[str]:
153
153
 
154
154
 
155
155
  def _merge_short(segments: list[str], min_len: int) -> list[str]:
156
- """Merge fragments shorter than *min_len* into a neighbour.
157
156
 
158
- Never merge a short segment backward into a sentence-ending segment
159
- (ending with 。!?;\\n) to avoid cross-sentence subtitle lines like
160
- "可复用技能。第三,". Instead, keep it as a new entry so it merges
161
- forward with the next segment.
162
- """
157
+
158
+
159
+
160
+
161
+
162
+
163
163
  if len(segments) <= 1:
164
164
  return segments
165
165
  merged: list[str] = [segments[0]]
166
166
  for seg in segments[1:]:
167
167
  prev_is_sentence_end = bool(_PRIMARY_TAIL.search(merged[-1]))
168
168
  if len(merged[-1]) < min_len:
169
- # Previous fragment is short — always merge forward into it
169
+
170
170
  merged[-1] = _concat_segments(merged[-1], seg)
171
171
  elif len(seg) < min_len and not prev_is_sentence_end:
172
- # Current fragment is short and prev is NOT a sentence end — merge backward
172
+
173
173
  merged[-1] = _concat_segments(merged[-1], seg)
174
174
  else:
175
- # Either both are long enough, or seg is short but prev ends a sentence
175
+
176
176
  merged.append(seg)
177
- # Handle trailing short segment (avoid merging across sentence boundary)
177
+
178
178
  if len(merged) > 1 and len(merged[-1]) < min_len:
179
179
  if not _PRIMARY_TAIL.search(merged[-2]):
180
180
  merged[-2] = _concat_segments(merged[-2], merged.pop())
@@ -182,28 +182,28 @@ def _merge_short(segments: list[str], min_len: int) -> list[str]:
182
182
 
183
183
 
184
184
  def assign_sentence_indices(raw_texts: list[str]) -> list[int]:
185
- """给每个字幕/TTS 段标注它属于原旁白的第几**句**(0-based)。
186
185
 
187
- 为什么必须单独标:一段 **不等于** 一句。``segment_narration`` 在句末断行之后,
188
- 还会把超过 ``MAX_SEGMENT_CHARS`` 的长句在逗号处继续切 —— 那是为了让 TTS 按真实
189
- 停顿返回时间戳,跟"句"没关系。于是 35 字的一句话会变成 3 段。
190
186
 
191
- 靠下标把「第 i 个动作」对齐到「第 i 段字幕」的模板(math-principles-expl 的
192
- ``steps``、ppt-to-video 的 ``beats``)因此会整体错位,而且是**静默**的:
193
- 2026-09-23 线上一条鸡兔同笼成片里 10 句旁白被切成约 19 段,板面 10 步在音频
194
- 播到一半就走完,之后画面冻结、旁白继续,全程零报错、终态 completed。
195
187
 
196
- 判据:一段是某句的**末段**,当且仅当它以句末标点(``。!?;`` 或换行)结尾。
197
- 这与 ``_PRIMARY_BREAK`` 是同一个集合 —— 切的时候用它断,标的时候用它认,
198
- 所以两边不会漂移。
199
188
 
200
- ⚠️ 必须传**未去尾标点**的原文。``_strip_trailing_punct`` 之后句末标点就没了,
201
- 那时所有段都会被认成同一句。
202
189
 
203
- 极少数情况下一段会跨句:``_merge_short`` 为了不让 TTS 行短到丢语气,会把一个
204
- 过短的段向后并(比如整句只有「对吗?」三个字)。这时该段归**它起始的那一句**,
205
- 因为调用方要的是「这句话从哪一帧开始被说出来」。
206
- """
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
207
  indices: list[int] = []
208
208
  sentence = 0
209
209
  for text in raw_texts:
@@ -214,10 +214,10 @@ def assign_sentence_indices(raw_texts: list[str]) -> list[int]:
214
214
 
215
215
 
216
216
  def count_narration_sentences(text: str) -> int:
217
- """数一段旁白有多少句。与 ``assign_sentence_indices`` 同一判据。
218
217
 
219
- 给校验层用:模板声明「steps 与旁白一一对应」时,用它和 steps 条数比对。
220
- """
218
+
219
+
220
+
221
221
  if not text or not text.strip():
222
222
  return 0
223
223
  chunks = [s for s in _PRIMARY_BREAK.split(text.strip()) if s.strip()]
@@ -225,26 +225,26 @@ def count_narration_sentences(text: str) -> int:
225
225
 
226
226
 
227
227
  def _plain_len(text: str) -> int:
228
- """计算去掉 **...** 标记后的纯文本字符数,用于更准确的时间分配。"""
228
+
229
229
  return len(_EMPHASIS_MARKS.sub("", text))
230
230
 
231
231
 
232
232
  def _strip_trailing_punct(text: str) -> str:
233
- """去掉字幕段末尾的标点符号(中英文句末 + 逗号等),让画面更干净。"""
233
+
234
234
  return _TRAILING_PUNCT.sub("", text)
235
235
 
236
236
 
237
237
  def segment_narration(text: str) -> list[str]:
238
- """Split narration into **TTS lines**, cutting at speech pauses.
239
238
 
240
- Use before TTS: ``"\\n".join(segment_narration(text))`` sends to Minimax
241
- so it returns per-line timestamps.
242
239
 
243
- ⚠️ 这里切的**不是字幕屏**。字幕分屏归渲染端(core 的
244
- ``chunkSubtitleSegments``),它知道字号、画幅和标点的语义强度;这里只知道
245
- 字符数。这一层切得越细,留给渲染端的余地越小 —— 见 ``MAX_SEGMENT_CHARS``
246
- 的注释。
247
- """
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
248
  if not text:
249
249
  return []
250
250
  raw = _PRIMARY_BREAK.split(text.strip())
@@ -267,14 +267,14 @@ def segment_narration(text: str) -> list[str]:
267
267
  def split_subtitle_from_lines(
268
268
  lines: list[str], duration_frames: int, fps: int, padding_frames: int = 10
269
269
  ) -> list[dict]:
270
- """Fallback subtitle timing for pre-split narration lines.
271
270
 
272
- Preserves exactly ``len(lines)`` segments (no secondary comma-split,
273
- no short-fragment merging). Used when narration was authored as
274
- ``{intro, items, outro}`` and TTS timestamps are unavailable.
275
271
 
276
- 末尾标点会被去掉(画面更干净),时间分配按纯文本长度(去掉 ** 标记)。
277
- """
272
+
273
+
274
+
275
+
276
+
277
+
278
278
  segments = [s.strip() for s in lines if s and s.strip()]
279
279
  if not segments:
280
280
  return []
@@ -285,7 +285,7 @@ def split_subtitle_from_lines(
285
285
  max(duration_frames - 2 * padding_frames, fps),
286
286
  max(estimated_audio_frames, fps),
287
287
  )
288
- # 句归属必须在去尾标点**之前**算 —— 去掉「。」之后就认不出句末了。
288
+
289
289
  sentence_indices = assign_sentence_indices(segments)
290
290
  result: list[dict] = []
291
291
  current_frame = padding_frames
@@ -303,11 +303,11 @@ def split_subtitle_from_lines(
303
303
 
304
304
 
305
305
  def split_subtitle(text: str, duration_frames: int, fps: int, padding_frames: int = 10) -> list[dict]:
306
- """Split narration text into subtitle segments aligned to frames.
307
306
 
308
- Fallback path used when real TTS timestamps are not available.
309
- 末尾标点会被去掉(画面更干净),时间分配按纯文本长度(去掉 ** 标记)。
310
- """
307
+
308
+
309
+
310
+
311
311
  segments = segment_narration(text)
312
312
  if not segments:
313
313
  return []
@@ -318,7 +318,7 @@ def split_subtitle(text: str, duration_frames: int, fps: int, padding_frames: in
318
318
  max(duration_frames - 2 * padding_frames, fps),
319
319
  max(estimated_audio_frames, fps),
320
320
  )
321
- # 句归属必须在去尾标点**之前**算(见 assign_sentence_indices)。
321
+
322
322
  sentence_indices = assign_sentence_indices(segments)
323
323
  result: list[dict] = []
324
324
  current_frame = padding_frames
@@ -337,7 +337,7 @@ def split_subtitle(text: str, duration_frames: int, fps: int, padding_frames: in
337
337
 
338
338
 
339
339
  def _resolve_root_composition(binding: dict, ratio: str) -> str:
340
- """Pick Remotion root Composition ID from template's remotionEntry by aspect ratio."""
340
+
341
341
  entry_map = binding.get("remotionEntry", {})
342
342
  if entry_map:
343
343
  return entry_map.get(ratio, next(iter(entry_map.values()), "MainVideo"))
@@ -345,7 +345,7 @@ def _resolve_root_composition(binding: dict, ratio: str) -> str:
345
345
 
346
346
 
347
347
  def compile_timeline(dsl: dict, binding: dict) -> dict:
348
- """Compile DSL + TemplateBinding into a full RenderPlan."""
348
+
349
349
  fps = dsl.get("global", {}).get("fps", 30)
350
350
  ratio = dsl.get("global", {}).get("aspectRatio", "16:9")
351
351
  resolution = dsl.get("global", {}).get("resolution", "1080p")
@@ -389,9 +389,9 @@ def compile_timeline(dsl: dict, binding: dict) -> dict:
389
389
 
390
390
  scene_bind = binding_map.get(scene_id, {})
391
391
 
392
- # P2.3: 不再产出 entry.layers——背景视觉资产、旁白音频、文本图层全部
393
- # 通过 propExtractors 写入 binding.props(backgroundAssetId / narrationAssetId
394
- # / titleText 等),渲染端用 props 直接消费。
392
+
393
+
394
+
395
395
 
396
396
  narration = scene.get("audio", {}).get("narration", {})
397
397
 
@@ -54,7 +54,20 @@
54
54
  "description": "整体风格标签,如 科技感、清新、商务、卡通"
55
55
  },
56
56
  "audience": { "type": "string" },
57
- "description": { "type": "string" }
57
+ "description": { "type": "string" },
58
+ "templateSelection": {
59
+ "type": "object",
60
+ "description": "模板是怎么定下来的(gen_script --template-source / --template-candidates 写入)。user-explicit = 用户点名;user-confirmed = agent 推荐、用户确认。未经确认的 auto 选择不会出现在这里:gen_script 直接拒绝生成。",
61
+ "required": ["source"],
62
+ "properties": {
63
+ "source": { "type": "string", "enum": ["user-explicit", "user-confirmed"] },
64
+ "candidates": {
65
+ "type": "array",
66
+ "items": { "type": "string" },
67
+ "description": "给用户看过的候选 templateId,推荐的排第一"
68
+ }
69
+ }
70
+ }
58
71
  }
59
72
  },
60
73
  "GlobalConfig": {