@remixmate/cli 0.9.71 → 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.
- 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 -188
- 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 +53 -13
- 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-post/SKILL.md +23 -13
- package/skills/gen-post/skill.json +9 -10
- 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 -21
- 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,25 +1,25 @@
|
|
|
1
|
-
"""
|
|
2
|
-
旁白时长预算 —— 规划期(写脚本时)估算「这段旁白念完、这一场实际会有多长」。
|
|
3
1
|
|
|
4
|
-
为什么单独成模块:以前这个数有三份且互不一致 —— gen_script 的 ``CHARS_PER_SECOND = 5.0``
|
|
5
|
-
(从没参与计算,只出现在注释里)、gen-script SKILL.md 的「3–4 字/秒」、timeline_compiler
|
|
6
|
-
的 5.0(那份是没有 TTS 时间戳时估字幕时长的兜底,语义不同,不归这里管)。没有任何一环
|
|
7
|
-
真的拿旁白字数去核对场景时长,于是 2026-09-27 线上一条会话:确认稿写 75s、每场 8–9s,
|
|
8
|
-
每场却写了 45–58 字,成片 116.9s(+56%),用户确认的时长从一开始就不可能兑现。
|
|
9
2
|
|
|
10
|
-
模型 = 语速 + 行间停顿 + 场景尾巴,三项都按那条会话的 9 条 TTS 实测标定:
|
|
11
3
|
|
|
12
|
-
* 语速:纯整段旁白的场景 1.1× 下 ≈ 4.8–5.2 计数字/秒(汉字 + 英文词×2)→ 1.0× 取
|
|
13
|
-
``PLAN_CHARS_PER_SEC_1X = 4.5``(略偏保守);
|
|
14
|
-
* 行间停顿:分行旁白(``narration.items`` / intro / outro)每多一行,TTS 按段合成,
|
|
15
|
-
段间约 0.9s 的静音 —— 逐项高亮版式同样字数要慢出 30–60%,主要就是它;
|
|
16
|
-
* 场景尾巴:``render_video.adjust_timeline_to_audio`` 在每场音频后补的留白
|
|
17
|
-
(模板 ``capabilities.defaultTailPadSec``,缺省 1.5s)。9 场就是 13.5s,写脚本时
|
|
18
|
-
从没人算进去。
|
|
19
4
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
@@ -27,27 +27,27 @@ import math
|
|
|
27
27
|
import re
|
|
28
28
|
from typing import Optional
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
PLAN_CHARS_PER_SEC_1X = 4.5
|
|
32
|
-
PLAN_WORDS_PER_SEC_1X = 2.5
|
|
32
|
+
PLAN_WORDS_PER_SEC_1X = 2.5
|
|
33
|
+
|
|
33
34
|
|
|
34
|
-
# 分行旁白每一处换行带来的静音。
|
|
35
35
|
LINE_PAUSE_SEC = 0.9
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
DEFAULT_TAIL_PAD_SEC = 1.5
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
OVERRUN_WARN_RATIO = 0.3
|
|
42
42
|
|
|
43
43
|
_CJK = re.compile(r"[\u3400-\u9fff\uf900-\ufaff]")
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
|
|
45
|
+
|
|
46
46
|
_LATIN_WORD = re.compile(r"[A-Za-z0-9]+(?:['’.-][A-Za-z0-9]+)*")
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def count_speech_units(text: str) -> tuple[float, str]:
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
if not isinstance(text, str) or not text.strip():
|
|
52
52
|
return 0.0, "zh"
|
|
53
53
|
cjk = len(_CJK.findall(text))
|
|
@@ -58,10 +58,10 @@ def count_speech_units(text: str) -> tuple[float, str]:
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def narration_lines(narration: object) -> list[str]:
|
|
61
|
-
"""旁白按 TTS 实际合成的段拆开:分行形态 = intro + items + outro,否则整段 text。
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
65
|
if not isinstance(narration, dict):
|
|
66
66
|
return []
|
|
67
67
|
items = narration.get("items")
|
|
@@ -90,7 +90,7 @@ def speech_rate(language: str, speed: float) -> float:
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def estimate_speech_sec(narration: object, speed: float = 1.0) -> float:
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
lines = narration_lines(narration)
|
|
95
95
|
if not lines:
|
|
96
96
|
return 0.0
|
|
@@ -99,7 +99,7 @@ def estimate_speech_sec(narration: object, speed: float = 1.0) -> float:
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def template_tail_pad_sec(template_config: Optional[dict]) -> float:
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
caps = (template_config or {}).get("capabilities") or {}
|
|
104
104
|
v = caps.get("defaultTailPadSec") if isinstance(caps, dict) else None
|
|
105
105
|
if isinstance(v, bool) or not isinstance(v, (int, float)) or v < 0:
|
|
@@ -108,7 +108,7 @@ def template_tail_pad_sec(template_config: Optional[dict]) -> float:
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def estimate_scene_sec(narration: object, speed: float = 1.0, tail_pad_sec: float = DEFAULT_TAIL_PAD_SEC) -> float:
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
speech = estimate_speech_sec(narration, speed)
|
|
113
113
|
return speech + tail_pad_sec if speech > 0 else 0.0
|
|
114
114
|
|
|
@@ -120,10 +120,10 @@ def speech_budget(
|
|
|
120
120
|
lines: int = 1,
|
|
121
121
|
tail_pad_sec: float = DEFAULT_TAIL_PAD_SEC,
|
|
122
122
|
) -> int:
|
|
123
|
-
"""一场 ``duration_sec`` 秒的场景,旁白最多写多少字(中文)/ 词(英文)。
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
128
|
available = duration_sec - tail_pad_sec - LINE_PAUSE_SEC * max(lines - 1, 0)
|
|
129
129
|
return max(1, int(math.floor(available * speech_rate(language, speed))))
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"""Template-declared content requirements and recursive asset references.
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def payload_values(props: dict) -> dict:
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
layers, seen = [], set()
|
|
11
11
|
while isinstance(props, dict) and id(props) not in seen and len(layers) < 10:
|
|
12
12
|
layers.append(props)
|
|
@@ -19,7 +19,7 @@ def payload_values(props: dict) -> dict:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def requirement_errors(value, schema: dict, path: str = "customPayload") -> list[str]:
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
errors = []
|
|
24
24
|
kind = schema.get("type")
|
|
25
25
|
matches = {"object": isinstance(value, dict), "array": isinstance(value, list),
|
|
@@ -58,7 +58,7 @@ def required_payload_errors(template: dict, props: dict) -> list[str]:
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def asset_references(value, path: str = "", *, bound_props: bool = False):
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
if isinstance(value, list):
|
|
63
63
|
for index, item in enumerate(value):
|
|
64
64
|
yield from asset_references(item, f"{path}[{index}]", bound_props=bound_props)
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
"""
|
|
2
|
-
图片 Prompt 增强模块 — 利用旁白语义 + 模版视觉指南生成高质量图片提示词。
|
|
3
1
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
13
|
from typing import Optional
|
|
14
14
|
|
|
15
|
-
# ---------------------------------------------------------------------------
|
|
16
|
-
# 常量
|
|
17
|
-
# ---------------------------------------------------------------------------
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
20
|
_FILLER_PATTERNS = re.compile(
|
|
21
21
|
r"(那么|其实|所以说|然后呢|接下来|大家好|你好|对吧|对不对|是不是|"
|
|
22
22
|
r"嗯|啊|呢|吧|哦|哈|了|的话|来说|而言|就是说|换句话说|"
|
|
23
23
|
r"如果觉得有用就|点个关注|下期见|别忘了)"
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
_PUNCT = re.compile("[,。!?、;:\u201c\u201d\u2018\u2019《》【】()\\s]+")
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
DEFAULT_PURPOSE_PATTERNS: dict[str, str] = {
|
|
31
31
|
"opening": "{narration_summary},{topic}主题概念图,视觉冲击力强,{style_suffix},高清大图",
|
|
32
32
|
"intro": "{narration_summary},{topic}主题引入场景,{style_suffix},清晰直观",
|
|
@@ -39,30 +39,30 @@ DEFAULT_PURPOSE_PATTERNS: dict[str, str] = {
|
|
|
39
39
|
"ending": "感谢观看结尾画面,{style_suffix},温馨大气",
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
WELL_CRAFTED_THRESHOLD = 80
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
|
|
47
47
|
SKELETON_PREFIXES = ("[骨架待填充]", "[skeleton placeholder]")
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
COMPOSE_SUBJECT_PLACEHOLDER = (
|
|
51
51
|
f"{SKELETON_PREFIXES[0]} 本页画面主体:谁、在哪、在做什么、什么表情、什么光线"
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
COMPOSE_DEFAULT_TEMPLATE = "{subject}。{character_sheet}"
|
|
56
56
|
|
|
57
57
|
_CJK_CHAR = re.compile(r"[\u4e00-\u9fff\u3400-\u4dbf]")
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def _is_mostly_latin(text: str) -> bool:
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
chars = [c for c in text if not c.isspace()]
|
|
67
67
|
if not chars:
|
|
68
68
|
return False
|
|
@@ -70,16 +70,16 @@ def _is_mostly_latin(text: str) -> bool:
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def summarize_narration(text: str, max_chars: int = 60) -> str:
|
|
73
|
-
"""从旁白文本提取视觉关键信息,去除口语化填充词。
|
|
74
73
|
|
|
75
|
-
英文旁白不套中文填充词正则("了""的话"之类对英文无意义,反而可能误删),
|
|
76
|
-
按单词边界截断,上限放宽到 200 字符 —— 60 字符的英文常常只剩半句。
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
83
|
if not text:
|
|
84
84
|
return ""
|
|
85
85
|
if _is_mostly_latin(text):
|
|
@@ -90,25 +90,25 @@ def summarize_narration(text: str, max_chars: int = 60) -> str:
|
|
|
90
90
|
space = cut.rfind(" ")
|
|
91
91
|
cleaned = (cut[:space] if space > 0 else cut).rstrip(".!?;:,")
|
|
92
92
|
return cleaned or text[:limit]
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
cleaned = _FILLER_PATTERNS.sub("", text)
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
cleaned = _PUNCT.sub(" ", cleaned).strip()
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
if not cleaned:
|
|
99
99
|
return text[:max_chars]
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
if len(cleaned) > max_chars:
|
|
102
102
|
cleaned = cleaned[:max_chars]
|
|
103
103
|
return cleaned
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def extract_scene_context(scene: dict) -> dict:
|
|
107
|
-
"""从 DSL scene 提取视觉相关上下文。
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
112
|
purpose = scene.get("purpose", "default")
|
|
113
113
|
narration_text = ""
|
|
114
114
|
audio = scene.get("audio", {})
|
|
@@ -134,10 +134,10 @@ def extract_scene_context(scene: dict) -> dict:
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def resolve_style_guide(template: Optional[dict], variant_id: str = "") -> dict:
|
|
137
|
-
"""合并模版根级 + 变体级的 imageStyleGuide。
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
141
|
if not template:
|
|
142
142
|
return {}
|
|
143
143
|
|
|
@@ -150,7 +150,7 @@ def resolve_style_guide(template: Optional[dict], variant_id: str = "") -> dict:
|
|
|
150
150
|
if not variant_guide:
|
|
151
151
|
return base
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
base_overrides = dict(base.get("purposeOverrides", {}) or {})
|
|
155
155
|
variant_overrides = dict(variant_guide.get("purposeOverrides", {}) or {})
|
|
156
156
|
|
|
@@ -162,9 +162,9 @@ def resolve_style_guide(template: Optional[dict], variant_id: str = "") -> dict:
|
|
|
162
162
|
return merged
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
def build_enhanced_prompt(
|
|
@@ -175,21 +175,21 @@ def build_enhanced_prompt(
|
|
|
175
175
|
scene_title: str = "",
|
|
176
176
|
style_guide: Optional[dict] = None,
|
|
177
177
|
) -> dict:
|
|
178
|
-
"""构建增强 prompt,返回 {prompt, negative_prompt, guidance_scale}。
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
182
|
style_guide = style_guide or {}
|
|
183
183
|
style_suffix = f"{style}风格" if style else ""
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
narration_summary = summarize_narration(narration_text)
|
|
187
187
|
if not narration_summary and scene_title:
|
|
188
188
|
narration_summary = scene_title
|
|
189
189
|
if not narration_summary:
|
|
190
190
|
narration_summary = topic or ""
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
vars_ = {
|
|
194
194
|
"narration_summary": narration_summary,
|
|
195
195
|
"topic": topic or "",
|
|
@@ -197,7 +197,7 @@ def build_enhanced_prompt(
|
|
|
197
197
|
"scene_title": scene_title or "",
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
|
|
201
201
|
purpose_overrides = style_guide.get("purposeOverrides", {}) or {}
|
|
202
202
|
override = purpose_overrides.get(purpose, {})
|
|
203
203
|
|
|
@@ -207,28 +207,28 @@ def build_enhanced_prompt(
|
|
|
207
207
|
purpose, DEFAULT_PURPOSE_PATTERNS.get("point", "")
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
prompt = _tidy_punctuation(_safe_format(prompt_template, vars_))
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
style_modifier = override.get("styleModifier", "")
|
|
215
215
|
if style_modifier:
|
|
216
216
|
prompt = f"{prompt},{style_modifier}"
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
base_style = style_guide.get("baseStyle", "")
|
|
220
220
|
if base_style:
|
|
221
221
|
prompt = f"{prompt},{base_style}"
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
color_directive = style_guide.get("colorDirective", "")
|
|
225
225
|
if color_directive:
|
|
226
226
|
prompt = f"{prompt},{color_directive}"
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
negative_prompt = override.get("negativePrompt", "") or style_guide.get("negativePrompt", "")
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
guidance_scale = style_guide.get("guidanceScale")
|
|
233
233
|
|
|
234
234
|
result = {"prompt": prompt}
|
|
@@ -241,7 +241,7 @@ def build_enhanced_prompt(
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
def _safe_format(template: str, vars_: dict) -> str:
|
|
244
|
-
|
|
244
|
+
|
|
245
245
|
try:
|
|
246
246
|
return template.format(**vars_)
|
|
247
247
|
except (KeyError, IndexError):
|
|
@@ -253,7 +253,7 @@ def _safe_format(template: str, vars_: dict) -> str:
|
|
|
253
253
|
|
|
254
254
|
_EMPTY_BRACKETS = re.compile(r"[((]\s*[))]")
|
|
255
255
|
_PUNCT_BEFORE_CLOSE = re.compile(r"[,。、;,;.\s]+(?=[))])")
|
|
256
|
-
|
|
256
|
+
|
|
257
257
|
_PUNCT_RUN = re.compile(r"[,。、;,;.]\s*(?:[,。、;,;.]\s*)+")
|
|
258
258
|
_LEADING_PUNCT = re.compile(r"^[\s,。、;,;.]+")
|
|
259
259
|
_TRAILING_SOFT_PUNCT = re.compile(r"[\s,、;,;]+$")
|
|
@@ -266,12 +266,12 @@ def _collapse_punct_run(match: "re.Match[str]") -> str:
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
def _tidy_punctuation(text: str) -> str:
|
|
269
|
-
"""清理模板变量为空时留下的标点噪声。
|
|
270
269
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
275
|
if not text:
|
|
276
276
|
return ""
|
|
277
277
|
out = _PUNCT_BEFORE_CLOSE.sub("", text)
|
|
@@ -283,9 +283,9 @@ def _tidy_punctuation(text: str) -> str:
|
|
|
283
283
|
return out.strip()
|
|
284
284
|
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
289
|
|
|
290
290
|
|
|
291
291
|
def enrich_asset_payload(
|
|
@@ -295,14 +295,14 @@ def enrich_asset_payload(
|
|
|
295
295
|
dsl_meta: Optional[dict] = None,
|
|
296
296
|
variant_id: str = "",
|
|
297
297
|
) -> dict:
|
|
298
|
-
"""增强 asset payload 中的图片 prompt。
|
|
299
298
|
|
|
300
|
-
- prompt 已超过 WELL_CRAFTED_THRESHOLD 字符的,仅补充 negativePrompt(如缺失)
|
|
301
|
-
- 否则用 build_enhanced_prompt 重新构建
|
|
302
299
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
306
|
payload = dict(payload)
|
|
307
307
|
current_prompt = payload.get("prompt", "")
|
|
308
308
|
|
|
@@ -312,7 +312,7 @@ def enrich_asset_payload(
|
|
|
312
312
|
|
|
313
313
|
style_guide = resolve_style_guide(template, variant_id)
|
|
314
314
|
|
|
315
|
-
|
|
315
|
+
|
|
316
316
|
if len(current_prompt) > WELL_CRAFTED_THRESHOLD:
|
|
317
317
|
if "negativePrompt" not in payload and style_guide.get("negativePrompt"):
|
|
318
318
|
payload["negativePrompt"] = style_guide["negativePrompt"]
|
|
@@ -320,7 +320,7 @@ def enrich_asset_payload(
|
|
|
320
320
|
payload["guidanceScale"] = style_guide["guidanceScale"]
|
|
321
321
|
return payload
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
|
|
324
324
|
scene_ctx = extract_scene_context(scene) if scene else {}
|
|
325
325
|
purpose = scene_ctx.get("purpose", "point")
|
|
326
326
|
narration_text = scene_ctx.get("narration_text", "")
|
|
@@ -344,9 +344,9 @@ def enrich_asset_payload(
|
|
|
344
344
|
return payload
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
350
|
|
|
351
351
|
|
|
352
352
|
def is_compose_mode(style_guide: Optional[dict]) -> bool:
|
|
@@ -368,19 +368,19 @@ def compose_image_payload(
|
|
|
368
368
|
dsl_meta: Optional[dict] = None,
|
|
369
369
|
variant_id: str = "",
|
|
370
370
|
) -> dict:
|
|
371
|
-
"""把模板的 imageStyleGuide 套到一个 gen-image 素材的 payload 上(compose 模式专用)。
|
|
372
371
|
|
|
373
|
-
``payload.prompt`` 在 compose 模式下的语义是**画面主体**(这一页画什么),不含画风。
|
|
374
|
-
这里拼出最终 prompt = promptTemplate(主体 / 角色设定 / 文字层…) + styleModifier +
|
|
375
|
-
baseStyle + colorDirective,并补上 negativePrompt / ratio / guidanceScale / seed。
|
|
376
372
|
|
|
377
|
-
- 非 compose 模板:原样返回(不改变 legacy 模板的行为)。
|
|
378
|
-
- 幂等:``styleGuideApplied`` 等于当前模板版本时原样返回;版本变了则从
|
|
379
|
-
``promptSubject``(上次 compose 前的主体)重新拼,画风不会叠两遍。
|
|
380
|
-
- 主体为空或仍是骨架占位时退回本场景的旁白全文 / 文字层。
|
|
381
373
|
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
384
|
guide = resolve_style_guide(template, variant_id)
|
|
385
385
|
if not is_compose_mode(guide):
|
|
386
386
|
return payload
|
|
@@ -392,7 +392,7 @@ def compose_image_payload(
|
|
|
392
392
|
ctx = extract_scene_context(scene) if scene else {}
|
|
393
393
|
purpose = ctx.get("purpose") or "point"
|
|
394
394
|
|
|
395
|
-
|
|
395
|
+
|
|
396
396
|
subject = str(payload.get("promptSubject") or payload.get("prompt") or "").strip()
|
|
397
397
|
if _is_placeholder_subject(subject):
|
|
398
398
|
subject = (
|
|
@@ -413,15 +413,15 @@ def compose_image_payload(
|
|
|
413
413
|
"character_sheet": str(meta.get("characterSheet") or "").strip(),
|
|
414
414
|
"topic": str(meta.get("topic") or meta.get("title") or ""),
|
|
415
415
|
"style_suffix": f"{style}风格" if style else "",
|
|
416
|
-
|
|
416
|
+
|
|
417
417
|
"narration_summary": subject,
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
overrides = guide.get("purposeOverrides", {}) or {}
|
|
421
421
|
override = overrides.get(purpose) or {}
|
|
422
422
|
parts = [
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
|
|
424
|
+
|
|
425
425
|
_safe_format(override.get("promptTemplate") or COMPOSE_DEFAULT_TEMPLATE, vars_),
|
|
426
426
|
override.get("styleModifier", ""),
|
|
427
427
|
guide.get("baseStyle", ""),
|
|
@@ -456,11 +456,11 @@ def _scene_refs(node, asset_id: str) -> bool:
|
|
|
456
456
|
|
|
457
457
|
|
|
458
458
|
def compose_dsl_image_prompts(dsl: dict, template: Optional[dict]) -> list[str]:
|
|
459
|
-
"""对整份 DSL 里待生成的图片素材逐个 compose(就地改 dsl),返回日志行。
|
|
460
459
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
464
|
meta = dsl.get("meta") or {}
|
|
465
465
|
variant_id = str(meta.get("variantId") or "")
|
|
466
466
|
if not is_compose_mode(resolve_style_guide(template, variant_id)):
|