@remixmate/cli 0.1.0

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/dist/argv.d.ts +15 -0
  4. package/dist/argv.js +51 -0
  5. package/dist/cli.d.ts +13 -0
  6. package/dist/cli.js +83 -0
  7. package/dist/handlers/gen-image.d.ts +13 -0
  8. package/dist/handlers/gen-image.js +161 -0
  9. package/dist/handlers/gen-voice.d.ts +16 -0
  10. package/dist/handlers/gen-voice.js +110 -0
  11. package/dist/handlers/index.d.ts +13 -0
  12. package/dist/handlers/index.js +14 -0
  13. package/dist/handlers/template-bind.d.ts +15 -0
  14. package/dist/handlers/template-bind.js +149 -0
  15. package/dist/http.d.ts +52 -0
  16. package/dist/http.js +110 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +8 -0
  19. package/dist/manifest.json +260 -0
  20. package/dist/progress.d.ts +23 -0
  21. package/dist/progress.js +15 -0
  22. package/dist/registry.d.ts +33 -0
  23. package/dist/registry.js +72 -0
  24. package/dist/runner.d.ts +17 -0
  25. package/dist/runner.js +63 -0
  26. package/package.json +54 -0
  27. package/skills/export-jianying/SKILL.md +251 -0
  28. package/skills/export-jianying/scripts/gen_jianying_draft.py +1099 -0
  29. package/skills/export-jianying/skill.json +24 -0
  30. package/skills/export-jianying/version.json +7 -0
  31. package/skills/gen-digital-human/SKILL.md +211 -0
  32. package/skills/gen-digital-human/scripts/gen_digital_human_video.py +574 -0
  33. package/skills/gen-digital-human/skill.json +23 -0
  34. package/skills/gen-digital-human/version.json +7 -0
  35. package/skills/gen-image/SKILL.md +130 -0
  36. package/skills/gen-image/skill.json +22 -0
  37. package/skills/gen-image/version.json +7 -0
  38. package/skills/gen-script/SKILL.md +283 -0
  39. package/skills/gen-script/scripts/gen_script.py +1208 -0
  40. package/skills/gen-script/skill.json +41 -0
  41. package/skills/gen-script/version.json +7 -0
  42. package/skills/gen-video/SKILL.md +170 -0
  43. package/skills/gen-video/scripts/gen_video.py +591 -0
  44. package/skills/gen-video/skill.json +24 -0
  45. package/skills/gen-video/version.json +7 -0
  46. package/skills/gen-voice/SKILL.md +104 -0
  47. package/skills/gen-voice/skill.json +21 -0
  48. package/skills/gen-voice/version.json +7 -0
  49. package/skills/prepare-video-assets/SKILL.md +192 -0
  50. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +57 -0
  51. package/skills/prepare-video-assets/skill.json +23 -0
  52. package/skills/prepare-video-assets/version.json +7 -0
  53. package/skills/render-video/SKILL.md +187 -0
  54. package/skills/render-video/scripts/_chrome_vendor.py +305 -0
  55. package/skills/render-video/scripts/_video_probe.py +174 -0
  56. package/skills/render-video/scripts/_vod_polling.py +86 -0
  57. package/skills/render-video/scripts/remote_renderer_client.py +236 -0
  58. package/skills/render-video/scripts/render_video.py +2288 -0
  59. package/skills/render-video/scripts/upload_video.py +193 -0
  60. package/skills/render-video/skill.json +21 -0
  61. package/skills/render-video/version.json +7 -0
  62. package/skills/template-bind/README.md +63 -0
  63. package/skills/template-bind/SKILL.md +234 -0
  64. package/skills/template-bind/scripts/check_contracts.py +196 -0
  65. package/skills/template-bind/scripts/match_template.py +215 -0
  66. package/skills/template-bind/scripts/registry_loader.py +379 -0
  67. package/skills/template-bind/scripts/render_job_client.py +162 -0
  68. package/skills/template-bind/scripts/sync_registry.py +44 -0
  69. package/skills/template-bind/scripts/template_paths.py +72 -0
  70. package/skills/template-bind/skill.json +20 -0
  71. package/skills/template-bind/version.json +7 -0
  72. package/skills/template-bind/video_dsl/README.md +97 -0
  73. package/skills/template-bind/video_dsl/runtime/__init__.py +11 -0
  74. package/skills/template-bind/video_dsl/runtime/dsl_validator.py +495 -0
  75. package/skills/template-bind/video_dsl/runtime/prompt_enhancer.py +274 -0
  76. package/skills/template-bind/video_dsl/runtime/template_binder.py +87 -0
  77. package/skills/template-bind/video_dsl/runtime/timeline_compiler.py +312 -0
  78. package/skills/template-bind/video_dsl/schema/render-plan-v1alpha1.json +193 -0
  79. package/skills/template-bind/video_dsl/schema/template-binding-v1alpha1.json +122 -0
  80. package/skills/template-bind/video_dsl/schema/template-definition-v1alpha1.json +247 -0
  81. package/skills/template-bind/video_dsl/schema/video-dsl-v1alpha1.json +378 -0
  82. package/skills/video-parser/SKILL.md +238 -0
  83. package/skills/video-parser/scripts/analyze_video.py +1038 -0
  84. package/skills/video-parser/scripts/deconstruct_video.py +602 -0
  85. package/skills/video-parser/scripts/parse_via_render.py +148 -0
  86. package/skills/video-parser/skill.json +21 -0
  87. package/skills/video-parser/version.json +7 -0
  88. package/skills/web-capture/SKILL.md +183 -0
  89. package/skills/web-capture/scripts/web_capture.py +461 -0
  90. package/skills/web-capture/skill.json +29 -0
  91. package/skills/web-capture/version.json +7 -0
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 远程渲染 HTTP 客户端 — 对接独立部署的 remotion-renderer 服务。
4
+
5
+ 接口(位于 /remotion-renderer/server/index.ts):
6
+ - POST /render → { code, data: { taskId } }
7
+ body 可选 coverCompositionId(如 "SpotlightCardCover");传了的话视频
8
+ 渲染完成后服务端会再串行渲一帧静态封面、一并上传。
9
+ - POST /renderStatus → { code, data: { status, progress, fileUrl?,
10
+ coverUrl?, coverError?, error? } }
11
+ coverUrl 视频成功且封面成功时存在
12
+ coverError 视频成功但封面失败时存在(视频本身仍然 succeeded)
13
+
14
+ 认证头(复用 X-Priv-Token 规范,服务端通过后端 API 校验):
15
+ X-Priv-Token: <PRIV_TOKEN>
16
+ x-invoke-skill: render-video
17
+ x-invoke-agent: <AGENT_NAME> # optional
18
+
19
+ Base URL 优先级:
20
+ 1. 调用方显式传入 base_url
21
+ 2. 环境变量 REMOTION_RENDER_API_URL
22
+ 3. 默认 http://localhost:3000(与 server/config.ts 的 PORT 默认值一致,方便本地调试)
23
+
24
+ 本模块只负责 HTTP 层;调用方(render_video.py)负责把结果写回 render_plan。
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import sys
32
+ import time
33
+ import urllib.error
34
+ import urllib.parse
35
+ import urllib.request
36
+ from typing import Callable, Optional
37
+
38
+ DEFAULT_API_BASE_URL = "http://localhost:3000"
39
+ API_BASE_URL = (os.environ.get("REMOTION_RENDER_API_URL") or DEFAULT_API_BASE_URL).strip()
40
+ SKILL_NAME = "render-video"
41
+ AGENT_NAME = os.environ.get("AGENT_NAME", "")
42
+
43
+ STATUS_PENDING = "pending"
44
+ STATUS_PROCESSING = "processing"
45
+ STATUS_RUNNING = "running"
46
+ STATUS_SUCCEEDED = "succeeded"
47
+ STATUS_SUCCEED = "succeed"
48
+ STATUS_COMPLETED = "completed"
49
+ STATUS_FAILED = "failed"
50
+ STATUS_CANCELLED = "cancelled"
51
+
52
+ TERMINAL_SUCCESS = {STATUS_SUCCEEDED, STATUS_SUCCEED, STATUS_COMPLETED}
53
+ TERMINAL_FAILURE = {STATUS_FAILED, STATUS_CANCELLED}
54
+
55
+
56
+ class RemoteRenderError(RuntimeError):
57
+ """抛出至调用方,由上层写入 render_plan.errors。"""
58
+
59
+
60
+ def _build_headers(private_token: str, content_type: str = "application/json", conversation_id: Optional[str] = None) -> dict:
61
+ if not private_token:
62
+ raise RemoteRenderError("PrivToken is not set (PRIV_TOKEN or --priv-token)")
63
+ headers = {
64
+ "X-Priv-Token": private_token,
65
+ "x-invoke-skill": SKILL_NAME,
66
+ }
67
+ if content_type:
68
+ headers["Content-Type"] = content_type
69
+ if AGENT_NAME:
70
+ headers["x-invoke-agent"] = AGENT_NAME
71
+ if conversation_id:
72
+ headers["x-conversation-id"] = conversation_id
73
+ return headers
74
+
75
+
76
+ def _request(
77
+ method: str,
78
+ path: str,
79
+ *,
80
+ private_token: str,
81
+ payload: Optional[dict] = None,
82
+ query: Optional[dict] = None,
83
+ base_url: Optional[str] = None,
84
+ timeout: float = 120.0,
85
+ conversation_id: Optional[str] = None,
86
+ ) -> dict:
87
+ base = (base_url or API_BASE_URL).rstrip("/")
88
+ if not base:
89
+ raise RemoteRenderError("renderer service base URL is not set")
90
+ url = f"{base}{path}"
91
+ if query:
92
+ url = f"{url}?{urllib.parse.urlencode(query)}"
93
+
94
+ data = None
95
+ if payload is not None:
96
+ data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
97
+
98
+ headers = _build_headers(private_token, conversation_id=conversation_id)
99
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
100
+
101
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
102
+ body = resp.read().decode("utf-8")
103
+ try:
104
+ return json.loads(body)
105
+ except json.JSONDecodeError as exc:
106
+ raise RemoteRenderError(f"remote response is not JSON: {body[:200]}") from exc
107
+
108
+
109
+ def _check_code(result: dict) -> dict:
110
+ code = result.get("code")
111
+ if code != 0:
112
+ msg = result.get("msg") or result.get("message") or "unknown error"
113
+ raise RemoteRenderError(f"API code={code}: {msg}")
114
+ return result.get("data") or {}
115
+
116
+
117
+ def start_render(
118
+ payload: dict,
119
+ *,
120
+ private_token: str,
121
+ base_url: Optional[str] = None,
122
+ timeout: float = 120.0,
123
+ conversation_id: Optional[str] = None,
124
+ ) -> str:
125
+ """POST /render;返回 taskId。"""
126
+ result = _request(
127
+ "POST",
128
+ "/render",
129
+ private_token=private_token,
130
+ payload=payload,
131
+ base_url=base_url,
132
+ timeout=timeout,
133
+ conversation_id=conversation_id,
134
+ )
135
+ data = _check_code(result)
136
+ task_id = data.get("taskId")
137
+ if not task_id:
138
+ raise RemoteRenderError(f"/render did not return a taskId: {result}")
139
+ return str(task_id)
140
+
141
+
142
+ def poll_render(
143
+ task_id: str,
144
+ *,
145
+ private_token: str,
146
+ base_url: Optional[str] = None,
147
+ timeout: float = 1800.0,
148
+ interval: float = 5.0,
149
+ request_timeout: float = 60.0,
150
+ max_consecutive_errors: int = 5,
151
+ on_progress: Optional[Callable[[dict], None]] = None,
152
+ adaptive_interval: bool = False,
153
+ ) -> dict:
154
+ """POST /renderStatus 轮询直到完成/失败/超时。
155
+
156
+ 成功时返回 data dict(至少包含 fileUrl),否则抛 RemoteRenderError。
157
+ on_progress 回调在每次成功请求后触发,参数为完整 data dict。
158
+
159
+ adaptive_interval=True 时根据渲染进度自动调整轮询间隔:
160
+ 0-20%: interval * 2(初始化阶段,变化慢)
161
+ 20-80%: interval(正常渲染阶段)
162
+ 80-100%: interval * 0.6(编码收尾阶段,更频繁检查)
163
+ """
164
+ start = time.monotonic()
165
+ consecutive_errors = 0
166
+ current_progress = 0.0
167
+
168
+ while True:
169
+ elapsed = time.monotonic() - start
170
+ if elapsed > timeout:
171
+ raise RemoteRenderError(
172
+ f"remote render polling timed out (waited {elapsed:.0f}s, taskId={task_id}); "
173
+ f"the job may still be running on the backend — query /renderStatus later"
174
+ )
175
+
176
+ try:
177
+ result = _request(
178
+ "POST",
179
+ "/renderStatus",
180
+ private_token=private_token,
181
+ payload={"taskId": task_id},
182
+ base_url=base_url,
183
+ timeout=request_timeout,
184
+ )
185
+ except (urllib.error.URLError, OSError, ConnectionError) as exc:
186
+ consecutive_errors += 1
187
+ print(
188
+ f" ⚠️ polling network error ({consecutive_errors}/{max_consecutive_errors}): {exc}",
189
+ file=sys.stderr,
190
+ flush=True,
191
+ )
192
+ if consecutive_errors >= max_consecutive_errors:
193
+ raise RemoteRenderError(
194
+ f"{max_consecutive_errors} consecutive network errors; giving up on polling (taskId={task_id})"
195
+ ) from exc
196
+ time.sleep(interval)
197
+ continue
198
+ except urllib.error.HTTPError as exc:
199
+ body = ""
200
+ try:
201
+ body = exc.read().decode("utf-8")
202
+ except Exception:
203
+ pass
204
+ raise RemoteRenderError(f"HTTP {exc.code} /renderStatus: {body[:200]}") from exc
205
+
206
+ consecutive_errors = 0
207
+
208
+ data = _check_code(result)
209
+ if on_progress is not None:
210
+ try:
211
+ on_progress(data)
212
+ except Exception: # noqa: BLE001 — 进度回调异常不应中断轮询
213
+ pass
214
+
215
+ status = str(data.get("status") or "").lower()
216
+ if status in TERMINAL_SUCCESS:
217
+ return data
218
+ if status in TERMINAL_FAILURE:
219
+ err = data.get("error") or data.get("errorMsg") or data.get("message") or "unknown"
220
+ raise RemoteRenderError(f"remote render failed ({status}): {err}")
221
+
222
+ # Adaptive interval: adjust sleep based on current progress
223
+ current_progress = float(data.get("progress") or 0.0)
224
+ if adaptive_interval:
225
+ if current_progress < 0.2:
226
+ sleep_time = interval * 2
227
+ elif current_progress >= 0.8:
228
+ sleep_time = max(interval * 0.6, 2.0)
229
+ else:
230
+ sleep_time = interval
231
+ else:
232
+ sleep_time = interval
233
+ time.sleep(sleep_time)
234
+
235
+
236
+ __all__ = ["start_render", "poll_render", "RemoteRenderError"]