@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,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
剪映草稿生成脚本 — 调用 ab-api POST /file/generateJianYing
|
|
4
|
-
通过 PrivToken(X-Priv-Token)认证。
|
|
5
2
|
|
|
6
|
-
将素材 URL(视频/图片/音频)+ 时长描述打包为剪映可导入的草稿 ZIP。
|
|
7
3
|
|
|
8
|
-
默认行为(非 --download):
|
|
9
|
-
生成完成后直接输出下载 URL,供调用方直接展示,无需落盘。
|
|
10
4
|
|
|
11
|
-
用法:
|
|
12
|
-
python gen_jianying_draft.py --title "我的视频" --scenes '[{"videoUrl":"...","duration":5}]'
|
|
13
|
-
python gen_jianying_draft.py --title "测试" --scenes scenes.json --download -o draft.zip
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
@@ -33,17 +33,17 @@ import uuid
|
|
|
33
33
|
import zipfile
|
|
34
34
|
|
|
35
35
|
API_BASE_URL = os.environ.get("MM_API_BASE_URL", "https://api-agent.remixmate.ai/api")
|
|
36
|
-
PRIVATE_TOKEN = ""
|
|
36
|
+
PRIVATE_TOKEN = ""
|
|
37
37
|
SKILL_NAME = "export-jianying"
|
|
38
38
|
AGENT_NAME = os.environ.get("AGENT_NAME", "")
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
STATUS_PENDING = "pending"
|
|
42
42
|
STATUS_PROCESSING = "processing"
|
|
43
43
|
STATUS_COMPLETED = "completed"
|
|
44
44
|
STATUS_FAILED = "failed"
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
ROLE_PRESETS = {
|
|
48
48
|
"headline": {"fontSize": 15, "color": "#FFFFFF", "bold": True, "position": {"x": 0, "y": -0.35}, "shadow": True},
|
|
49
49
|
"subheadline": {"fontSize": 8, "color": "#F0C040", "bold": False, "position": {"x": 0, "y": 0.38}, "shadow": False},
|
|
@@ -52,7 +52,7 @@ ROLE_PRESETS = {
|
|
|
52
52
|
"caption": {"fontSize": 6, "color": "#CCCCCC", "bold": False, "position": {"x": 0, "y": 0.44}},
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
PILL_SUBTITLE_STYLE = {
|
|
57
57
|
"font_size": 8.0,
|
|
58
58
|
"background_style": 1,
|
|
@@ -73,19 +73,19 @@ PILL_SUBTITLE_STYLE = {
|
|
|
73
73
|
|
|
74
74
|
PILL_SUBTITLE_POSITION_Y = -0.458
|
|
75
75
|
|
|
76
|
-
# ---------------------------------------------------------------------------
|
|
77
|
-
# 剪映草稿根目录解析
|
|
78
|
-
# ---------------------------------------------------------------------------
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
86
|
DRAFT_ROOT_SUFFIX = ("JianyingPro", "User Data", "Projects", "com.lveditor.draft")
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
DRAFT_ROOT_TEMPLATES = {
|
|
90
90
|
"mac": "/Users/<username>/Movies/JianyingPro/User Data/Projects/com.lveditor.draft",
|
|
91
91
|
"windows": "C:/Users/<username>/AppData/Local/JianyingPro/User Data/Projects/com.lveditor.draft",
|
|
@@ -95,7 +95,7 @@ _WINDOWS_ABS_PATH = re.compile(r"^[A-Za-z]:[\\/]")
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def draft_root_path_error(path: str) -> str:
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
if "<" in path or ">" in path:
|
|
100
100
|
return "still contains a placeholder such as <username>; replace it with the real value"
|
|
101
101
|
if path.startswith("~"):
|
|
@@ -108,7 +108,7 @@ def draft_root_path_error(path: str) -> str:
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _local_draft_root(system: str) -> str:
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
if system == "mac" and sys.platform == "darwin":
|
|
113
113
|
return os.path.join(os.path.expanduser("~/Movies"), *DRAFT_ROOT_SUFFIX)
|
|
114
114
|
if system == "windows" and os.name == "nt":
|
|
@@ -119,13 +119,13 @@ def _local_draft_root(system: str) -> str:
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def resolve_draft_root_path(explicit: str, system: str) -> str:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
129
|
if system not in DRAFT_ROOT_TEMPLATES:
|
|
130
130
|
print(f"❌ unsupported --system value: {system} (supported: mac / windows)", file=sys.stderr)
|
|
131
131
|
sys.exit(1)
|
|
@@ -133,7 +133,7 @@ def resolve_draft_root_path(explicit: str, system: str) -> str:
|
|
|
133
133
|
|
|
134
134
|
if explicit:
|
|
135
135
|
path = explicit.strip()
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
while len(path) > 3 and path[-1] in "/\\":
|
|
138
138
|
path = path[:-1]
|
|
139
139
|
reason = draft_root_path_error(path)
|
|
@@ -161,7 +161,7 @@ def resolve_draft_root_path(explicit: str, system: str) -> str:
|
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
def resolve_token() -> str:
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
token = os.environ.get("PRIV_TOKEN", "")
|
|
166
166
|
if not token:
|
|
167
167
|
if sys.stdin.isatty():
|
|
@@ -173,12 +173,12 @@ def resolve_token() -> str:
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def gen_jianying_uuid() -> str:
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
return str(uuid.uuid4()).upper()
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def hex_to_rgb_float(hex_color: str) -> list:
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
hex_color = hex_color.lstrip("#")
|
|
183
183
|
r = int(hex_color[0:2], 16) / 255.0
|
|
184
184
|
g = int(hex_color[2:4], 16) / 255.0
|
|
@@ -187,7 +187,7 @@ def hex_to_rgb_float(hex_color: str) -> list:
|
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
def build_text_content_json(text: str, style: dict) -> str:
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
rgb = hex_to_rgb_float(style.get("color", "#FFFFFF"))
|
|
192
192
|
font_size = style.get("fontSize", 8)
|
|
193
193
|
char_count = len(text)
|
|
@@ -212,7 +212,7 @@ def build_text_content_json(text: str, style: dict) -> str:
|
|
|
212
212
|
|
|
213
213
|
|
|
214
214
|
def _build_headers(content_type: str = "application/json") -> dict:
|
|
215
|
-
|
|
215
|
+
|
|
216
216
|
headers = {
|
|
217
217
|
"X-Priv-Token": PRIVATE_TOKEN,
|
|
218
218
|
"x-invoke-skill": SKILL_NAME,
|
|
@@ -225,10 +225,10 @@ def _build_headers(content_type: str = "application/json") -> dict:
|
|
|
225
225
|
|
|
226
226
|
|
|
227
227
|
def api_request(path: str, payload: dict, *, exit_on_error: bool = True) -> dict:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
232
|
if not PRIVATE_TOKEN:
|
|
233
233
|
print("❌ PrivToken not configured: set the PRIV_TOKEN env var", file=sys.stderr)
|
|
234
234
|
sys.exit(1)
|
|
@@ -254,10 +254,10 @@ def api_request(path: str, payload: dict, *, exit_on_error: bool = True) -> dict
|
|
|
254
254
|
|
|
255
255
|
|
|
256
256
|
def build_text_material(material_id: str, text: str, style: dict) -> dict:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
261
|
content_json = build_text_content_json(text, style)
|
|
262
262
|
font_size = style.get("fontSize", 8)
|
|
263
263
|
color_hex = style.get("color", "#FFFFFF")
|
|
@@ -374,10 +374,10 @@ def build_text_segment(
|
|
|
374
374
|
duration_us: int,
|
|
375
375
|
position: dict,
|
|
376
376
|
) -> dict:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
381
|
return {
|
|
382
382
|
"caption_info": None,
|
|
383
383
|
"check_flag": 7,
|
|
@@ -432,13 +432,13 @@ def build_text_segment(
|
|
|
432
432
|
|
|
433
433
|
|
|
434
434
|
def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
435
|
-
"""
|
|
436
|
-
将 RenderPlan JSON 转换为剪映草稿的 scenes 列表。
|
|
437
|
-
返回 (scenes, width, height)。
|
|
438
435
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
|
|
442
442
|
timeline = render_plan.get("timeline", [])
|
|
443
443
|
resolved_assets = {a["assetId"]: a for a in render_plan.get("resolvedAssets", [])}
|
|
444
444
|
render_config = render_plan.get("renderConfig", {})
|
|
@@ -453,7 +453,7 @@ def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
|
453
453
|
duration_sec = round(duration_frames / fps, 2) if fps else 5.0
|
|
454
454
|
scene["duration"] = duration_sec
|
|
455
455
|
|
|
456
|
-
|
|
456
|
+
|
|
457
457
|
for layer in entry.get("layers", []):
|
|
458
458
|
if layer.get("type") == "visual":
|
|
459
459
|
asset = resolved_assets.get(layer.get("assetId", ""))
|
|
@@ -465,7 +465,7 @@ def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
|
465
465
|
scene["imageUrl"] = asset["url"]
|
|
466
466
|
break
|
|
467
467
|
|
|
468
|
-
|
|
468
|
+
|
|
469
469
|
for layer in entry.get("layers", []):
|
|
470
470
|
if layer.get("type") == "audio":
|
|
471
471
|
asset = resolved_assets.get(layer.get("assetId", ""))
|
|
@@ -475,14 +475,14 @@ def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
|
475
475
|
scene["audioDuration"] = round(asset["duration"] / 1000.0, 2)
|
|
476
476
|
break
|
|
477
477
|
|
|
478
|
-
|
|
478
|
+
|
|
479
479
|
subtitle_segments = entry.get("subtitleSegments", [])
|
|
480
480
|
if subtitle_segments:
|
|
481
481
|
subtitle_text = " ".join(seg.get("text", "") for seg in subtitle_segments if seg.get("text"))
|
|
482
482
|
if subtitle_text.strip():
|
|
483
483
|
scene["subtitleText"] = subtitle_text.strip()
|
|
484
484
|
|
|
485
|
-
|
|
485
|
+
|
|
486
486
|
props = entry.get("props", {})
|
|
487
487
|
title_text = props.get("titleText", "")
|
|
488
488
|
if title_text:
|
|
@@ -491,18 +491,18 @@ def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
|
491
491
|
"content": title_text,
|
|
492
492
|
"role": "headline",
|
|
493
493
|
})
|
|
494
|
-
|
|
494
|
+
|
|
495
495
|
if scene.get("subtitleText"):
|
|
496
496
|
text_layers.append({
|
|
497
497
|
"content": scene["subtitleText"],
|
|
498
498
|
"role": "subheadline",
|
|
499
499
|
})
|
|
500
|
-
del scene["subtitleText"]
|
|
500
|
+
del scene["subtitleText"]
|
|
501
501
|
scene["textLayers"] = text_layers
|
|
502
502
|
|
|
503
|
-
|
|
503
|
+
|
|
504
504
|
if not scene.get("videoUrl") and not scene.get("imageUrl"):
|
|
505
|
-
|
|
505
|
+
|
|
506
506
|
print(f" ⚠️ scene {entry.get('sceneId', '?')} has no visual asset, skipping", file=sys.stderr)
|
|
507
507
|
continue
|
|
508
508
|
|
|
@@ -515,18 +515,18 @@ def convert_render_plan_to_scenes(render_plan: dict) -> tuple:
|
|
|
515
515
|
return scenes, width, height
|
|
516
516
|
|
|
517
517
|
|
|
518
|
-
|
|
518
|
+
|
|
519
519
|
SCENE_FIELDS = (
|
|
520
520
|
"videoUrl", "imageUrl", "duration", "width", "height",
|
|
521
521
|
"audioUrl", "audioDuration", "subtitleText", "textLayers",
|
|
522
522
|
)
|
|
523
|
-
|
|
523
|
+
|
|
524
524
|
SCENE_IGNORED_FIELDS = ("id", "sceneId")
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
530
|
SCENE_FIELD_ALIASES = {
|
|
531
531
|
"image": "imageUrl", "img": "imageUrl", "imageURL": "imageUrl", "image_url": "imageUrl",
|
|
532
532
|
"video": "videoUrl", "videoURL": "videoUrl", "video_url": "videoUrl",
|
|
@@ -538,7 +538,7 @@ SCENE_FIELD_ALIASES = {
|
|
|
538
538
|
|
|
539
539
|
|
|
540
540
|
def unknown_field_error(index: int, scene: dict) -> None:
|
|
541
|
-
|
|
541
|
+
|
|
542
542
|
unknown = [k for k in scene if k not in SCENE_FIELDS and k not in SCENE_IGNORED_FIELDS]
|
|
543
543
|
if not unknown:
|
|
544
544
|
return
|
|
@@ -552,8 +552,8 @@ def unknown_field_error(index: int, scene: dict) -> None:
|
|
|
552
552
|
|
|
553
553
|
|
|
554
554
|
def parse_scenes(scenes_arg: str) -> list:
|
|
555
|
-
|
|
556
|
-
|
|
555
|
+
|
|
556
|
+
|
|
557
557
|
if os.path.isfile(scenes_arg):
|
|
558
558
|
with open(scenes_arg, "r", encoding="utf-8") as f:
|
|
559
559
|
content = f.read()
|
|
@@ -563,7 +563,7 @@ def parse_scenes(scenes_arg: str) -> list:
|
|
|
563
563
|
print(f"❌ failed to parse scenes JSON file: {e}", file=sys.stderr)
|
|
564
564
|
sys.exit(1)
|
|
565
565
|
else:
|
|
566
|
-
|
|
566
|
+
|
|
567
567
|
try:
|
|
568
568
|
scenes = json.loads(scenes_arg)
|
|
569
569
|
except json.JSONDecodeError as e:
|
|
@@ -579,7 +579,7 @@ def parse_scenes(scenes_arg: str) -> list:
|
|
|
579
579
|
print("❌ scenes list cannot be empty", file=sys.stderr)
|
|
580
580
|
sys.exit(1)
|
|
581
581
|
|
|
582
|
-
|
|
582
|
+
|
|
583
583
|
for i, scene in enumerate(scenes):
|
|
584
584
|
if not isinstance(scene, dict):
|
|
585
585
|
print(f"❌ scene {i + 1} is malformed: must be an object", file=sys.stderr)
|
|
@@ -592,7 +592,7 @@ def parse_scenes(scenes_arg: str) -> list:
|
|
|
592
592
|
print(f"❌ scene {i + 1} is missing a valid duration (must be > 0)", file=sys.stderr)
|
|
593
593
|
sys.exit(1)
|
|
594
594
|
|
|
595
|
-
|
|
595
|
+
|
|
596
596
|
text_layers = scene.get("textLayers")
|
|
597
597
|
if text_layers is not None:
|
|
598
598
|
if not isinstance(text_layers, list):
|
|
@@ -617,7 +617,7 @@ def create_jianying_task(
|
|
|
617
617
|
height: int = 1920,
|
|
618
618
|
draft_name: str = "",
|
|
619
619
|
) -> str:
|
|
620
|
-
|
|
620
|
+
|
|
621
621
|
print("📦 Submitting Jianying draft generation task...")
|
|
622
622
|
print(f" title: {title}")
|
|
623
623
|
print(f" scenes: {len(scenes)}")
|
|
@@ -638,7 +638,7 @@ def create_jianying_task(
|
|
|
638
638
|
|
|
639
639
|
result = api_request("/file/generateJianYing", payload)
|
|
640
640
|
|
|
641
|
-
|
|
641
|
+
|
|
642
642
|
if result.get("code") != 0:
|
|
643
643
|
msg = result.get("msg", "未知错误")
|
|
644
644
|
print(f"❌ API returned an error: {msg}", file=sys.stderr)
|
|
@@ -655,10 +655,10 @@ def create_jianying_task(
|
|
|
655
655
|
|
|
656
656
|
|
|
657
657
|
def poll_task_status(task_id: str, poll_interval: int = 5, max_wait: int = 300) -> dict:
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
|
|
662
662
|
print(f"\n⏳ Waiting for draft generation (up to {max_wait} s)...")
|
|
663
663
|
|
|
664
664
|
start_time = time.time()
|
|
@@ -684,10 +684,10 @@ def poll_task_status(task_id: str, poll_interval: int = 5, max_wait: int = 300)
|
|
|
684
684
|
time.sleep(poll_interval)
|
|
685
685
|
continue
|
|
686
686
|
|
|
687
|
-
|
|
687
|
+
|
|
688
688
|
consecutive_errors = 0
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
|
|
691
691
|
if result.get("code") != 0:
|
|
692
692
|
msg = result.get("msg", "未知错误")
|
|
693
693
|
print(f"\n❌ status query failed: {msg}", file=sys.stderr)
|
|
@@ -696,7 +696,7 @@ def poll_task_status(task_id: str, poll_interval: int = 5, max_wait: int = 300)
|
|
|
696
696
|
result_data = result.get("data") or {}
|
|
697
697
|
status = result_data.get("status", "unknown")
|
|
698
698
|
|
|
699
|
-
|
|
699
|
+
|
|
700
700
|
status_icons = {
|
|
701
701
|
STATUS_PENDING: "⏳",
|
|
702
702
|
STATUS_PROCESSING: "🔄",
|
|
@@ -707,7 +707,7 @@ def poll_task_status(task_id: str, poll_interval: int = 5, max_wait: int = 300)
|
|
|
707
707
|
print(f"\r {icon} status: {status} | elapsed: {elapsed:.0f}s", end="", flush=True)
|
|
708
708
|
|
|
709
709
|
if status == STATUS_COMPLETED:
|
|
710
|
-
print()
|
|
710
|
+
print()
|
|
711
711
|
return result_data
|
|
712
712
|
|
|
713
713
|
if status == STATUS_FAILED:
|
|
@@ -720,32 +720,32 @@ def poll_task_status(task_id: str, poll_interval: int = 5, max_wait: int = 300)
|
|
|
720
720
|
|
|
721
721
|
|
|
722
722
|
def _encode_url(raw_url: str) -> str:
|
|
723
|
-
|
|
723
|
+
|
|
724
724
|
parsed = urllib.parse.urlparse(raw_url)
|
|
725
|
-
|
|
726
|
-
|
|
725
|
+
|
|
726
|
+
|
|
727
727
|
encoded_path = urllib.parse.quote(parsed.path, safe="/:@!$&'()*+,;=-._~%")
|
|
728
728
|
return urllib.parse.urlunparse(parsed._replace(path=encoded_path))
|
|
729
729
|
|
|
730
730
|
|
|
731
731
|
class DraftUploadError(RuntimeError):
|
|
732
|
-
|
|
732
|
+
pass
|
|
733
733
|
|
|
734
734
|
|
|
735
735
|
def upload_draft_zip(zip_path: str, file_name: str) -> str:
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
|
|
744
744
|
boundary = "----RemixmateJianYing" + uuid.uuid4().hex
|
|
745
745
|
with open(zip_path, "rb") as f:
|
|
746
746
|
zip_data = f.read()
|
|
747
|
-
|
|
748
|
-
|
|
747
|
+
|
|
748
|
+
|
|
749
749
|
parts = [
|
|
750
750
|
f"--{boundary}".encode(),
|
|
751
751
|
b'Content-Disposition: form-data; name="fileName"',
|
|
@@ -772,7 +772,7 @@ def upload_draft_zip(zip_path: str, file_name: str) -> str:
|
|
|
772
772
|
with urllib.request.urlopen(req, timeout=600) as resp:
|
|
773
773
|
raw = resp.read().decode("utf-8", errors="replace")
|
|
774
774
|
except urllib.error.HTTPError as e:
|
|
775
|
-
|
|
775
|
+
|
|
776
776
|
snippet = e.read().decode("utf-8", errors="replace")[:200].replace("\n", " ")
|
|
777
777
|
raise DraftUploadError(f"HTTP {e.code} {e.reason}: {snippet}") from e
|
|
778
778
|
except (urllib.error.URLError, OSError, ConnectionError) as e:
|
|
@@ -792,9 +792,9 @@ def upload_draft_zip(zip_path: str, file_name: str) -> str:
|
|
|
792
792
|
|
|
793
793
|
|
|
794
794
|
def download_zip(download_url: str, output_path: str) -> None:
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
798
798
|
print("\n⬇️ Downloading draft ZIP for post-processing...")
|
|
799
799
|
safe_url = _encode_url(download_url)
|
|
800
800
|
try:
|
|
@@ -812,11 +812,11 @@ def download_zip(download_url: str, output_path: str) -> None:
|
|
|
812
812
|
|
|
813
813
|
|
|
814
814
|
def _resolve_layer_style(layer: dict) -> dict:
|
|
815
|
-
|
|
815
|
+
|
|
816
816
|
role = layer.get("role", "subheadline")
|
|
817
817
|
preset = ROLE_PRESETS.get(role, ROLE_PRESETS["subheadline"])
|
|
818
|
-
style = dict(preset)
|
|
819
|
-
|
|
818
|
+
style = dict(preset)
|
|
819
|
+
|
|
820
820
|
for key in ("fontSize", "color", "bold", "shadow", "backgroundColor"):
|
|
821
821
|
if key in layer:
|
|
822
822
|
style[key] = layer[key]
|
|
@@ -826,11 +826,11 @@ def _resolve_layer_style(layer: dict) -> dict:
|
|
|
826
826
|
|
|
827
827
|
|
|
828
828
|
def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
834
|
video_track = None
|
|
835
835
|
for track in draft.get("tracks", []):
|
|
836
836
|
if track.get("type") == "video":
|
|
@@ -848,7 +848,7 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
848
848
|
file=sys.stderr,
|
|
849
849
|
)
|
|
850
850
|
|
|
851
|
-
|
|
851
|
+
|
|
852
852
|
text_track = None
|
|
853
853
|
for track in draft.get("tracks", []):
|
|
854
854
|
if track.get("type") == "text":
|
|
@@ -869,21 +869,21 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
869
869
|
text_segments = text_track.get("segments", [])
|
|
870
870
|
texts_materials = draft.get("materials", {}).get("texts", [])
|
|
871
871
|
|
|
872
|
-
|
|
872
|
+
|
|
873
873
|
seg_by_start: dict[int, int] = {}
|
|
874
874
|
for idx, seg in enumerate(text_segments):
|
|
875
875
|
start = seg.get("target_timerange", {}).get("start", -1)
|
|
876
876
|
seg_by_start[start] = idx
|
|
877
877
|
|
|
878
|
-
|
|
878
|
+
|
|
879
879
|
mat_by_id: dict[str, int] = {}
|
|
880
880
|
for idx, mat in enumerate(texts_materials):
|
|
881
881
|
mat_by_id[mat["id"]] = idx
|
|
882
882
|
|
|
883
|
-
|
|
883
|
+
|
|
884
884
|
extra_text_tracks: list[dict] = []
|
|
885
885
|
|
|
886
|
-
|
|
886
|
+
|
|
887
887
|
match_count = min(len(video_segments), len(scenes))
|
|
888
888
|
for i in range(match_count):
|
|
889
889
|
scene = scenes[i]
|
|
@@ -903,12 +903,12 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
903
903
|
position = style.get("position", {"x": 0, "y": 0})
|
|
904
904
|
|
|
905
905
|
if layer_idx == 0 and start_us in seg_by_start:
|
|
906
|
-
|
|
906
|
+
|
|
907
907
|
seg_idx = seg_by_start[start_us]
|
|
908
908
|
existing_seg = text_segments[seg_idx]
|
|
909
909
|
mat_id = existing_seg.get("material_id", "")
|
|
910
910
|
|
|
911
|
-
|
|
911
|
+
|
|
912
912
|
if mat_id in mat_by_id:
|
|
913
913
|
mat = texts_materials[mat_by_id[mat_id]]
|
|
914
914
|
mat["content"] = build_text_content_json(content, style)
|
|
@@ -920,11 +920,11 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
920
920
|
mat["background_color"] = bg_color
|
|
921
921
|
mat["background_style"] = 1 if bg_color else 0
|
|
922
922
|
|
|
923
|
-
|
|
923
|
+
|
|
924
924
|
existing_seg["clip"]["transform"]["x"] = position.get("x", 0)
|
|
925
925
|
existing_seg["clip"]["transform"]["y"] = position.get("y", 0)
|
|
926
926
|
else:
|
|
927
|
-
|
|
927
|
+
|
|
928
928
|
new_mat_id = gen_jianying_uuid()
|
|
929
929
|
new_seg_id = gen_jianying_uuid()
|
|
930
930
|
|
|
@@ -936,7 +936,7 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
936
936
|
new_seg_id, new_mat_id, start_us, duration_us, position
|
|
937
937
|
)
|
|
938
938
|
|
|
939
|
-
|
|
939
|
+
|
|
940
940
|
extra_idx = layer_idx - 1 if (layer_idx > 0 and start_us in seg_by_start) else layer_idx
|
|
941
941
|
while extra_idx >= len(extra_text_tracks):
|
|
942
942
|
new_track = {
|
|
@@ -951,30 +951,30 @@ def apply_text_layers(draft: dict, scenes: list) -> None:
|
|
|
951
951
|
extra_text_tracks.append(new_track)
|
|
952
952
|
extra_text_tracks[extra_idx]["segments"].append(new_segment)
|
|
953
953
|
|
|
954
|
-
|
|
954
|
+
|
|
955
955
|
for et in extra_text_tracks:
|
|
956
956
|
if et["segments"]:
|
|
957
957
|
draft["tracks"].append(et)
|
|
958
958
|
|
|
959
|
-
|
|
959
|
+
|
|
960
960
|
draft["materials"]["texts"] = texts_materials
|
|
961
961
|
|
|
962
962
|
print(f" ✅ textLayers post-processing finished")
|
|
963
963
|
|
|
964
964
|
|
|
965
965
|
def apply_subtitle_pill_style(draft: dict) -> None:
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
|
|
970
970
|
texts_materials = draft.get("materials", {}).get("texts", []) or []
|
|
971
971
|
|
|
972
|
-
|
|
972
|
+
|
|
973
973
|
for mat in texts_materials:
|
|
974
974
|
for key, value in PILL_SUBTITLE_STYLE.items():
|
|
975
975
|
mat[key] = value
|
|
976
976
|
|
|
977
|
-
|
|
977
|
+
|
|
978
978
|
content_raw = mat.get("content", "")
|
|
979
979
|
if content_raw:
|
|
980
980
|
try:
|
|
@@ -984,10 +984,10 @@ def apply_subtitle_pill_style(draft: dict) -> None:
|
|
|
984
984
|
styles[0]["size"] = PILL_SUBTITLE_STYLE["font_size"]
|
|
985
985
|
mat["content"] = json.dumps(content_obj, ensure_ascii=False)
|
|
986
986
|
except (json.JSONDecodeError, TypeError):
|
|
987
|
-
|
|
987
|
+
|
|
988
988
|
pass
|
|
989
989
|
|
|
990
|
-
|
|
990
|
+
|
|
991
991
|
for track in draft.get("tracks", []):
|
|
992
992
|
if track.get("type") != "text":
|
|
993
993
|
continue
|
|
@@ -1000,16 +1000,16 @@ def apply_subtitle_pill_style(draft: dict) -> None:
|
|
|
1000
1000
|
|
|
1001
1001
|
|
|
1002
1002
|
def postprocess_draft_zip(zip_path: str, scenes: list) -> None:
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
1007
|
print(f"\n🔧 Post-processing draft ZIP (applying textLayers)...")
|
|
1008
1008
|
|
|
1009
1009
|
tmp_path = zip_path + ".tmp"
|
|
1010
1010
|
|
|
1011
1011
|
with zipfile.ZipFile(zip_path, "r") as zin:
|
|
1012
|
-
|
|
1012
|
+
|
|
1013
1013
|
draft_json_name = None
|
|
1014
1014
|
draft_info_name = None
|
|
1015
1015
|
for name in zin.namelist():
|
|
@@ -1024,71 +1024,71 @@ def postprocess_draft_zip(zip_path: str, scenes: list) -> None:
|
|
|
1024
1024
|
|
|
1025
1025
|
draft_data = json.loads(zin.read(draft_json_name).decode("utf-8"))
|
|
1026
1026
|
|
|
1027
|
-
|
|
1027
|
+
|
|
1028
1028
|
apply_text_layers(draft_data, scenes)
|
|
1029
1029
|
|
|
1030
|
-
|
|
1030
|
+
|
|
1031
1031
|
apply_subtitle_pill_style(draft_data)
|
|
1032
1032
|
|
|
1033
1033
|
modified_json = json.dumps(draft_data, ensure_ascii=False, indent=4).encode("utf-8")
|
|
1034
1034
|
|
|
1035
|
-
|
|
1035
|
+
|
|
1036
1036
|
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
1037
1037
|
for item in zin.infolist():
|
|
1038
1038
|
if item.filename == draft_json_name:
|
|
1039
1039
|
zout.writestr(item, modified_json)
|
|
1040
1040
|
elif item.filename == draft_info_name:
|
|
1041
|
-
|
|
1041
|
+
|
|
1042
1042
|
zout.writestr(item, modified_json)
|
|
1043
1043
|
else:
|
|
1044
1044
|
zout.writestr(item, zin.read(item.filename))
|
|
1045
1045
|
|
|
1046
|
-
|
|
1046
|
+
|
|
1047
1047
|
os.replace(tmp_path, zip_path)
|
|
1048
1048
|
print(f" ✅ draft ZIP post-processing finished: {zip_path}")
|
|
1049
1049
|
|
|
1050
1050
|
|
|
1051
1051
|
def prepare_api_scenes(scenes: list) -> list:
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
1056
|
api_scenes = []
|
|
1057
1057
|
for scene in scenes:
|
|
1058
1058
|
api_scene = {k: v for k, v in scene.items() if k != "textLayers"}
|
|
1059
1059
|
text_layers = scene.get("textLayers")
|
|
1060
1060
|
if text_layers and not api_scene.get("subtitleText"):
|
|
1061
|
-
|
|
1061
|
+
|
|
1062
1062
|
api_scene["subtitleText"] = text_layers[0].get("content", "")
|
|
1063
1063
|
api_scenes.append(api_scene)
|
|
1064
1064
|
return api_scenes
|
|
1065
1065
|
|
|
1066
1066
|
|
|
1067
1067
|
def has_text_layers(scenes: list) -> bool:
|
|
1068
|
-
|
|
1068
|
+
|
|
1069
1069
|
return any(scene.get("textLayers") for scene in scenes)
|
|
1070
1070
|
|
|
1071
1071
|
|
|
1072
1072
|
def _render_job_client():
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
1077
|
shared = os.path.join(os.path.dirname(__file__), "..", "..", "template-registry", "scripts")
|
|
1078
1078
|
if shared not in sys.path:
|
|
1079
1079
|
sys.path.insert(0, shared)
|
|
1080
|
-
import render_job_client
|
|
1080
|
+
import render_job_client
|
|
1081
1081
|
|
|
1082
1082
|
return render_job_client
|
|
1083
1083
|
|
|
1084
1084
|
|
|
1085
1085
|
def print_draft_summary(scenes: list, width: int, height: int, draft_root_path: str) -> None:
|
|
1086
|
-
"""
|
|
1087
|
-
打印草稿里**实际**装了什么。
|
|
1088
1086
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
1092
|
total = len(scenes)
|
|
1093
1093
|
video = sum(1 for s in scenes if s.get("videoUrl"))
|
|
1094
1094
|
image = sum(1 for s in scenes if s.get("imageUrl") and not s.get("videoUrl"))
|
|
@@ -1175,7 +1175,7 @@ Scene fields:
|
|
|
1175
1175
|
|
|
1176
1176
|
args = parser.parse_args()
|
|
1177
1177
|
|
|
1178
|
-
|
|
1178
|
+
|
|
1179
1179
|
global PRIVATE_TOKEN
|
|
1180
1180
|
if args.priv_token:
|
|
1181
1181
|
PRIVATE_TOKEN = args.priv_token
|
|
@@ -1188,8 +1188,8 @@ Scene fields:
|
|
|
1188
1188
|
|
|
1189
1189
|
conversation_id = os.environ.get("CONVERSATION_ID", "").strip()
|
|
1190
1190
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
1193
|
if not (args.from_job_id or args.from_render_plan or args.scenes) and conversation_id:
|
|
1194
1194
|
rjc = _render_job_client()
|
|
1195
1195
|
try:
|
|
@@ -1204,11 +1204,11 @@ Scene fields:
|
|
|
1204
1204
|
f"jobId={args.from_job_id} (attempt {latest.get('attempt', '?')}, updated {latest.get('updateTime', '?')})"
|
|
1205
1205
|
)
|
|
1206
1206
|
elif args.scenes and conversation_id:
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
1212
|
try:
|
|
1213
1213
|
latest = _render_job_client().latest_job(conversation_id, PRIVATE_TOKEN)
|
|
1214
1214
|
except RuntimeError:
|
|
@@ -1220,7 +1220,7 @@ Scene fields:
|
|
|
1220
1220
|
file=sys.stderr,
|
|
1221
1221
|
)
|
|
1222
1222
|
|
|
1223
|
-
|
|
1223
|
+
|
|
1224
1224
|
if args.from_job_id:
|
|
1225
1225
|
print(f"📋 loading RenderPlan from database (jobId={args.from_job_id})...")
|
|
1226
1226
|
rjc = _render_job_client()
|
|
@@ -1238,7 +1238,7 @@ Scene fields:
|
|
|
1238
1238
|
dsl_meta = render_plan.get("dslMeta", {})
|
|
1239
1239
|
args.title = dsl_meta.get("title", "") or "RenderPlan 草稿"
|
|
1240
1240
|
print(f" ✅ converted {len(scenes)} scene(s), canvas {args.width}x{args.height}")
|
|
1241
|
-
|
|
1241
|
+
|
|
1242
1242
|
elif args.from_render_plan:
|
|
1243
1243
|
if not os.path.isfile(args.from_render_plan):
|
|
1244
1244
|
print(f"❌ RenderPlan file does not exist: {args.from_render_plan}", file=sys.stderr)
|
|
@@ -1247,17 +1247,17 @@ Scene fields:
|
|
|
1247
1247
|
with open(args.from_render_plan, "r", encoding="utf-8") as f:
|
|
1248
1248
|
render_plan = json.load(f)
|
|
1249
1249
|
scenes, rp_width, rp_height = convert_render_plan_to_scenes(render_plan)
|
|
1250
|
-
|
|
1250
|
+
|
|
1251
1251
|
if args.width == 1080 and args.height == 1920:
|
|
1252
1252
|
args.width = rp_width
|
|
1253
1253
|
args.height = rp_height
|
|
1254
|
-
|
|
1254
|
+
|
|
1255
1255
|
if not args.title:
|
|
1256
1256
|
dsl_meta = render_plan.get("dslMeta", {})
|
|
1257
1257
|
args.title = dsl_meta.get("title", "") or "RenderPlan 草稿"
|
|
1258
1258
|
print(f" ✅ converted {len(scenes)} scene(s), canvas {args.width}x{args.height}")
|
|
1259
1259
|
else:
|
|
1260
|
-
|
|
1260
|
+
|
|
1261
1261
|
if not args.scenes:
|
|
1262
1262
|
print("❌ provide --from-job-id, --from-render-plan or --scenes", file=sys.stderr)
|
|
1263
1263
|
parser.print_help()
|
|
@@ -1268,18 +1268,18 @@ Scene fields:
|
|
|
1268
1268
|
sys.exit(1)
|
|
1269
1269
|
scenes = parse_scenes(args.scenes)
|
|
1270
1270
|
|
|
1271
|
-
|
|
1271
|
+
|
|
1272
1272
|
draft_root_path = resolve_draft_root_path(args.draft_root_path, args.system)
|
|
1273
1273
|
|
|
1274
|
-
|
|
1274
|
+
|
|
1275
1275
|
needs_postprocess = has_text_layers(scenes) or any(s.get("subtitleText") for s in scenes)
|
|
1276
1276
|
if needs_postprocess:
|
|
1277
1277
|
print("📝 textLayers or subtitleText detected; post-processing after generation (incl. Pill subtitle styling)")
|
|
1278
1278
|
|
|
1279
|
-
|
|
1279
|
+
|
|
1280
1280
|
api_scenes = prepare_api_scenes(scenes)
|
|
1281
1281
|
|
|
1282
|
-
|
|
1282
|
+
|
|
1283
1283
|
task_id = create_jianying_task(
|
|
1284
1284
|
title=args.title,
|
|
1285
1285
|
scenes=api_scenes,
|
|
@@ -1289,7 +1289,7 @@ Scene fields:
|
|
|
1289
1289
|
draft_name=args.draft_name,
|
|
1290
1290
|
)
|
|
1291
1291
|
|
|
1292
|
-
|
|
1292
|
+
|
|
1293
1293
|
result_data = poll_task_status(task_id, args.poll_interval, args.max_wait)
|
|
1294
1294
|
|
|
1295
1295
|
download_url = result_data.get("downloadUrl", "")
|
|
@@ -1302,13 +1302,13 @@ Scene fields:
|
|
|
1302
1302
|
print(f"\n🎉 Jianying draft generated successfully!")
|
|
1303
1303
|
print(f" filename: {file_name}")
|
|
1304
1304
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
1307
|
keep_local = bool(args.output) or (not args.no_download and not conversation_id)
|
|
1308
1308
|
styled = True
|
|
1309
1309
|
output_path = ""
|
|
1310
1310
|
if needs_postprocess:
|
|
1311
|
-
|
|
1311
|
+
|
|
1312
1312
|
if keep_local:
|
|
1313
1313
|
output_path = args.output or f"jianying_draft_{int(time.time())}.zip"
|
|
1314
1314
|
else:
|
|
@@ -1317,12 +1317,12 @@ Scene fields:
|
|
|
1317
1317
|
try:
|
|
1318
1318
|
download_zip(download_url, output_path)
|
|
1319
1319
|
postprocess_draft_zip(output_path, scenes)
|
|
1320
|
-
|
|
1320
|
+
|
|
1321
1321
|
download_url = upload_draft_zip(output_path, file_name)
|
|
1322
1322
|
except DraftUploadError as e:
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
1326
|
styled = False
|
|
1327
1327
|
print(f"⚠️ could not upload the styled draft ({e}); delivering the backend draft instead.", file=sys.stderr)
|
|
1328
1328
|
print(" subtitle text is included, but the pill subtitle style / textLayers were NOT applied. "
|