@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.
Files changed (181) 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 -188
  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 +53 -13
  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-post/SKILL.md +23 -13
  61. package/skills/gen-post/skill.json +9 -10
  62. package/skills/gen-script/SKILL.md +23 -1
  63. package/skills/gen-script/scripts/gen_script.py +644 -551
  64. package/skills/gen-script/skill.json +12 -0
  65. package/skills/gen-script/version.json +1 -1
  66. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  67. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  68. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  69. package/skills/render-video/scripts/_video_probe.py +33 -33
  70. package/skills/render-video/scripts/_vod_polling.py +13 -13
  71. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  72. package/skills/render-video/scripts/render_video.py +639 -639
  73. package/skills/render-video/scripts/required_props.py +37 -37
  74. package/skills/render-video/scripts/upload_video.py +35 -35
  75. package/skills/render-video/version.json +1 -1
  76. package/skills/template-registry/SKILL.md +16 -0
  77. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  78. package/skills/template-registry/scripts/doc_extract.py +40 -40
  79. package/skills/template-registry/scripts/list_templates.py +303 -191
  80. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  81. package/skills/template-registry/scripts/match_template.py +87 -87
  82. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  83. package/skills/template-registry/scripts/registry_loader.py +159 -159
  84. package/skills/template-registry/scripts/render_job_client.py +75 -75
  85. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  86. package/skills/template-registry/skill.json +4 -1
  87. package/skills/template-registry/version.json +1 -1
  88. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  89. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  90. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  91. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  92. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  93. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  94. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  95. package/skills/video-parser/scripts/analyze_video.py +173 -173
  96. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  97. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  98. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  100. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  101. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  102. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  103. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  104. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  105. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  119. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  120. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  122. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  123. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  124. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  125. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  126. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  127. package/skills/web-screenshot/scripts/read_page.py +15 -15
  128. package/skills/web-screenshot/scripts/record.py +26 -26
  129. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  130. package/dist/argv.d.ts +0 -15
  131. package/dist/auth/auth-lock.d.ts +0 -26
  132. package/dist/auth/auto-login.d.ts +0 -20
  133. package/dist/auth/commands.d.ts +0 -8
  134. package/dist/auth/credential-store.d.ts +0 -60
  135. package/dist/auth/device-flow-runner.d.ts +0 -58
  136. package/dist/auth/device-flow.d.ts +0 -52
  137. package/dist/auth/ensure.d.ts +0 -55
  138. package/dist/auth/environment.d.ts +0 -38
  139. package/dist/auth/pending-store.d.ts +0 -33
  140. package/dist/auth/resolve.d.ts +0 -30
  141. package/dist/billing.d.ts +0 -44
  142. package/dist/capabilities.d.ts +0 -63
  143. package/dist/cli.d.ts +0 -13
  144. package/dist/diagnostics.d.ts +0 -24
  145. package/dist/doctor.d.ts +0 -22
  146. package/dist/errors.d.ts +0 -29
  147. package/dist/exec.d.ts +0 -18
  148. package/dist/handlers/gen-digital-human.d.ts +0 -17
  149. package/dist/handlers/gen-image.d.ts +0 -20
  150. package/dist/handlers/gen-post.d.ts +0 -21
  151. package/dist/handlers/gen-video.d.ts +0 -19
  152. package/dist/handlers/gen-voice.d.ts +0 -26
  153. package/dist/handlers/index.d.ts +0 -20
  154. package/dist/handlers/shared.d.ts +0 -26
  155. package/dist/handlers/template-init.d.ts +0 -10
  156. package/dist/http.d.ts +0 -72
  157. package/dist/index.d.ts +0 -9
  158. package/dist/install.d.ts +0 -71
  159. package/dist/progress.d.ts +0 -23
  160. package/dist/project/commands.d.ts +0 -19
  161. package/dist/project/host.d.ts +0 -91
  162. package/dist/project/resolve.d.ts +0 -43
  163. package/dist/project/store.d.ts +0 -31
  164. package/dist/project/take.d.ts +0 -50
  165. package/dist/project/trace.d.ts +0 -98
  166. package/dist/registry.d.ts +0 -30
  167. package/dist/runner.d.ts +0 -39
  168. package/dist/skill-schema.d.ts +0 -138
  169. package/dist/template/args.d.ts +0 -25
  170. package/dist/template/check.d.ts +0 -23
  171. package/dist/template/commands.d.ts +0 -25
  172. package/dist/template/constants.d.ts +0 -39
  173. package/dist/template/init.d.ts +0 -28
  174. package/dist/template/list.d.ts +0 -23
  175. package/dist/template/preview.d.ts +0 -60
  176. package/dist/template/proc.d.ts +0 -26
  177. package/dist/template/project.d.ts +0 -47
  178. package/dist/template/publish.d.ts +0 -43
  179. package/dist/template/render-client.d.ts +0 -39
  180. package/dist/template/upgrade.d.ts +0 -44
  181. package/dist/text.d.ts +0 -8
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- 视频解构脚本 - 将视频拆解为可复用的内容资产
4
- 自动提取:音频、ASR 文本(含逐字时间戳)、场景切分、关键帧图片。
5
- 输出结构化资产清单 JSON。
6
2
 
7
- 依赖:本地须安装 ffmpeg(brew install ffmpeg)
8
3
 
9
- 用法:
10
- python deconstruct_video.py --url "https://example.com/video.mp4"
11
- python deconstruct_video.py --local /path/to/video.mp4
12
- python deconstruct_video.py --url "https://example.com/video.mp4" --skip-asr
13
- python deconstruct_video.py --url "https://example.com/video.mp4" --json-output
14
4
 
15
- 环境变量:
16
- MM_API_BASE_URL - 后端 API 地址(默认: https://api-agent.remixmate.ai/api)
17
- PRIV_TOKEN - PrivToken 认证令牌
18
- """
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
19
 
20
20
  import argparse
21
21
  import json
@@ -29,27 +29,27 @@ import urllib.request
29
29
 
30
30
  API_BASE_URL = os.environ.get("MM_API_BASE_URL", "https://api-agent.remixmate.ai/api")
31
31
 
32
- PRIVATE_TOKEN = "" # 在 main() 中通过 resolve_token() 初始化
32
+ PRIVATE_TOKEN = ""
33
33
  SKILL_NAME = "video-parser"
34
34
  AGENT_NAME = os.environ.get("AGENT_NAME", "")
35
35
 
36
- # 是否静默模式(--json-output 时抑制进度输出)
36
+
37
37
  QUIET = False
38
38
 
39
39
 
40
40
  def log(msg: str) -> None:
41
- """打印进度信息(静默模式下不输出)"""
41
+
42
42
  if not QUIET:
43
43
  print(msg, flush=True)
44
44
 
45
45
 
46
46
  def log_err(msg: str) -> None:
47
- """打印错误信息到 stderr(始终输出)"""
47
+
48
48
  print(msg, file=sys.stderr, flush=True)
49
49
 
50
50
 
51
51
  def resolve_token(cli_token: str = None) -> str:
52
- """优先从 CLI 参数读取 token,其次环境变量,最后报错退出"""
52
+
53
53
  if cli_token:
54
54
  return cli_token
55
55
  token = os.environ.get("PRIV_TOKEN", "")
@@ -63,7 +63,7 @@ def resolve_token(cli_token: str = None) -> str:
63
63
 
64
64
 
65
65
  def _build_headers() -> dict:
66
- """构建统一请求头(X-Priv-Token 认证头)"""
66
+
67
67
  headers = {
68
68
  "Content-Type": "application/json",
69
69
  "X-Priv-Token": PRIVATE_TOKEN,
@@ -74,12 +74,12 @@ def _build_headers() -> dict:
74
74
  return headers
75
75
 
76
76
 
77
- # ---------------------------------------------------------------------------
78
- # 前置检查
79
- # ---------------------------------------------------------------------------
77
+
78
+
79
+
80
80
 
81
81
  def check_ffmpeg() -> None:
82
- """检查 ffmpeg 是否已安装"""
82
+
83
83
  try:
84
84
  subprocess.run(
85
85
  ["ffmpeg", "-version"],
@@ -92,13 +92,13 @@ def check_ffmpeg() -> None:
92
92
  sys.exit(1)
93
93
 
94
94
 
95
- # ---------------------------------------------------------------------------
96
- # 步骤 1:获取视频到本地
97
- # ---------------------------------------------------------------------------
95
+
96
+
97
+
98
98
 
99
99
  def download_video(url: str, output_dir: str) -> str:
100
- """下载远程视频到本地,返回本地文件路径"""
101
- # 从 URL 推断文件扩展名
100
+
101
+
102
102
  url_path = url.split("?")[0]
103
103
  ext = os.path.splitext(url_path)[1] or ".mp4"
104
104
  local_path = os.path.join(output_dir, f"source_video{ext}")
@@ -109,7 +109,7 @@ def download_video(url: str, output_dir: str) -> str:
109
109
  with urllib.request.urlopen(req, timeout=300) as resp:
110
110
  with open(local_path, "wb") as f:
111
111
  while True:
112
- chunk = resp.read(1024 * 1024) # 1MB chunks
112
+ chunk = resp.read(1024 * 1024)
113
113
  if not chunk:
114
114
  break
115
115
  f.write(chunk)
@@ -123,7 +123,7 @@ def download_video(url: str, output_dir: str) -> str:
123
123
 
124
124
 
125
125
  def get_video_duration_ms(video_path: str) -> int:
126
- """使用 ffprobe 获取视频时长(毫秒)"""
126
+
127
127
  try:
128
128
  result = subprocess.run(
129
129
  [
@@ -140,12 +140,12 @@ def get_video_duration_ms(video_path: str) -> int:
140
140
  return 0
141
141
 
142
142
 
143
- # ---------------------------------------------------------------------------
144
- # 步骤 2:提取音频
145
- # ---------------------------------------------------------------------------
143
+
144
+
145
+
146
146
 
147
147
  def extract_audio(video_path: str, output_dir: str) -> str:
148
- """从视频中提取音频为 MP3,返回音频文件路径"""
148
+
149
149
  audio_path = os.path.join(output_dir, "audio.mp3")
150
150
  log("[2/6] 正在提取音频...")
151
151
 
@@ -169,12 +169,12 @@ def extract_audio(video_path: str, output_dir: str) -> str:
169
169
  return audio_path
170
170
 
171
171
 
172
- # ---------------------------------------------------------------------------
173
- # 步骤 3:上传音频到 OSS
174
- # ---------------------------------------------------------------------------
172
+
173
+
174
+
175
175
 
176
176
  def upload_audio(audio_path: str) -> str:
177
- """上传音频文件到 OSS(POST /file/uploadMediaFile,multipart),返回文件 URL"""
177
+
178
178
  log("[3/6] 正在上传音频到 OSS...")
179
179
 
180
180
  url = f"{API_BASE_URL}/file/uploadMediaFile"
@@ -184,27 +184,27 @@ def upload_audio(audio_path: str) -> str:
184
184
  with open(audio_path, "rb") as f:
185
185
  file_data = f.read()
186
186
 
187
- # 构建 multipart/form-data body
187
+
188
188
  body = b""
189
- # file 字段
189
+
190
190
  body += f"--{boundary}\r\n".encode()
191
191
  body += f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode()
192
192
  body += b"Content-Type: audio/mpeg\r\n\r\n"
193
193
  body += file_data
194
194
  body += b"\r\n"
195
- # title 字段
195
+
196
196
  body += f"--{boundary}\r\n".encode()
197
197
  body += b'Content-Disposition: form-data; name="title"\r\n\r\n'
198
198
  body += filename.encode()
199
199
  body += b"\r\n"
200
- # role / retention:ab-api 落素材表时必填角色。抽离音频只为 ASR,属中间产物;
201
- # 源视频是第三方的,检索侧据 role 默认排除(license=third_party_unknown)。
200
+
201
+
202
202
  for name, value in (("role", "source_audio"), ("retention", "intermediate")):
203
203
  body += f"--{boundary}\r\n".encode()
204
204
  body += f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode()
205
205
  body += value.encode()
206
206
  body += b"\r\n"
207
- # 结束
207
+
208
208
  body += f"--{boundary}--\r\n".encode()
209
209
 
210
210
  headers = {
@@ -244,12 +244,12 @@ def upload_audio(audio_path: str) -> str:
244
244
  return file_url
245
245
 
246
246
 
247
- # ---------------------------------------------------------------------------
248
- # 步骤 4:ASR 语音识别
249
- # ---------------------------------------------------------------------------
247
+
248
+
249
+
250
250
 
251
251
  def run_asr(audio_url: str) -> dict:
252
- """调用 ASR 接口(POST /tool/asr),返回识别结果"""
252
+
253
253
  log("[4/6] 正在进行语音识别 (ASR)...")
254
254
 
255
255
  url = f"{API_BASE_URL}/tool/asr"
@@ -289,15 +289,15 @@ def run_asr(audio_url: str) -> dict:
289
289
  }
290
290
 
291
291
 
292
- # ---------------------------------------------------------------------------
293
- # 步骤 5:场景切变检测 & 关键帧提取
294
- # ---------------------------------------------------------------------------
292
+
293
+
294
+
295
295
 
296
296
  def detect_scenes(video_path: str, threshold: float) -> list:
297
- """
298
- 使用 ffmpeg select 滤镜检测场景切变,返回切点时间戳列表(秒)。
299
- 始终包含 0.0 作为第一个切点。
300
- """
297
+
298
+
299
+
300
+
301
301
  log(f"[5/6] 正在检测场景切变 (阈值={threshold})...")
302
302
 
303
303
  try:
@@ -310,7 +310,7 @@ def detect_scenes(video_path: str, threshold: float) -> list:
310
310
  ],
311
311
  capture_output=True, text=True,
312
312
  )
313
- # 从 stderr 解析 pts_time
313
+
314
314
  output = result.stderr
315
315
  except Exception as e:
316
316
  log_err(f"场景检测失败: {e}")
@@ -321,7 +321,7 @@ def detect_scenes(video_path: str, threshold: float) -> list:
321
321
  ts = float(match.group(1))
322
322
  timestamps.append(ts)
323
323
 
324
- # 去重并排序,始终包含 0.0
324
+
325
325
  timestamps = sorted(set(timestamps))
326
326
  if not timestamps or timestamps[0] != 0.0:
327
327
  timestamps.insert(0, 0.0)
@@ -331,7 +331,7 @@ def detect_scenes(video_path: str, threshold: float) -> list:
331
331
 
332
332
 
333
333
  def extract_keyframes(video_path: str, timestamps: list, output_dir: str) -> list:
334
- """按时间戳列表从视频截取关键帧,返回关键帧信息列表"""
334
+
335
335
  keyframes_dir = os.path.join(output_dir, "keyframes")
336
336
  os.makedirs(keyframes_dir, exist_ok=True)
337
337
 
@@ -368,12 +368,12 @@ def extract_keyframes(video_path: str, timestamps: list, output_dir: str) -> lis
368
368
  return keyframes
369
369
 
370
370
 
371
- # ---------------------------------------------------------------------------
372
- # 步骤 6:汇总输出
373
- # ---------------------------------------------------------------------------
371
+
372
+
373
+
374
374
 
375
375
  def build_scenes(timestamps: list, video_duration_sec: float, keyframes: list) -> list:
376
- """根据切点时间戳构建场景分段列表"""
376
+
377
377
  scenes = []
378
378
  keyframe_map = {kf["index"]: kf["localPath"] for kf in keyframes}
379
379
 
@@ -399,7 +399,7 @@ def build_result(
399
399
  scenes: list,
400
400
  keyframes: list,
401
401
  ) -> dict:
402
- """汇总所有数据为最终资产清单"""
402
+
403
403
  result = {
404
404
  "source": {
405
405
  "url": source_url or None,
@@ -417,9 +417,9 @@ def build_result(
417
417
  return result
418
418
 
419
419
 
420
- # ---------------------------------------------------------------------------
421
- # Main
422
- # ---------------------------------------------------------------------------
420
+
421
+
422
+
423
423
 
424
424
  def main():
425
425
  parser = argparse.ArgumentParser(
@@ -492,7 +492,7 @@ Examples:
492
492
 
493
493
  args = parser.parse_args()
494
494
 
495
- # 校验输入
495
+
496
496
  if not args.url and not args.local:
497
497
  log_err("请提供 --url 或 --local 参数指定视频来源")
498
498
  parser.print_help()
@@ -507,29 +507,29 @@ Examples:
507
507
  log_err("--scene-threshold 范围为 0.0-1.0")
508
508
  sys.exit(1)
509
509
 
510
- # 静默模式
510
+
511
511
  global QUIET
512
512
  QUIET = args.json_output
513
513
 
514
- # 初始化 token(如果需要 ASR,则必须有 token)
514
+
515
515
  global PRIVATE_TOKEN
516
516
  if not args.skip_asr:
517
517
  PRIVATE_TOKEN = resolve_token(args.priv_token)
518
518
  else:
519
- # 即使跳过 ASR,如果提供了 token 也保存(可能用于其他扩展)
519
+
520
520
  PRIVATE_TOKEN = args.priv_token or os.environ.get("PRIV_TOKEN", "")
521
521
 
522
- # 前置检查
522
+
523
523
  check_ffmpeg()
524
524
 
525
- # 输出目录
525
+
526
526
  output_dir = args.output_dir or f"./deconstructed_{int(time.time())}"
527
527
  os.makedirs(output_dir, exist_ok=True)
528
528
  log(f"输出目录: {os.path.abspath(output_dir)}")
529
529
 
530
- # -----------------------------------------------------------------------
531
- # [1] 获取视频到本地
532
- # -----------------------------------------------------------------------
530
+
531
+
532
+
533
533
  source_url = args.url or ""
534
534
  if args.url:
535
535
  video_path = download_video(args.url, output_dir)
@@ -537,19 +537,19 @@ Examples:
537
537
  video_path = os.path.abspath(args.local)
538
538
  log(f"[1/6] 使用本地视频: {video_path}")
539
539
 
540
- # 获取视频时长
540
+
541
541
  video_duration_ms = get_video_duration_ms(video_path)
542
542
  video_duration_sec = video_duration_ms / 1000.0 if video_duration_ms > 0 else 0.0
543
543
  log(f" 视频时长: {video_duration_sec:.1f}s ({video_duration_ms}ms)")
544
544
 
545
- # -----------------------------------------------------------------------
546
- # [2] 提取音频
547
- # -----------------------------------------------------------------------
545
+
546
+
547
+
548
548
  audio_path = extract_audio(video_path, output_dir)
549
549
 
550
- # -----------------------------------------------------------------------
551
- # [3-4] 上传音频 + ASR
552
- # -----------------------------------------------------------------------
550
+
551
+
552
+
553
553
  audio_oss_url = ""
554
554
  asr_result = {"text": "", "duration": 0, "utterances": []}
555
555
 
@@ -560,9 +560,9 @@ Examples:
560
560
  log("[3/6] 跳过音频上传(--skip-asr)")
561
561
  log("[4/6] 跳过 ASR(--skip-asr)")
562
562
 
563
- # -----------------------------------------------------------------------
564
- # [5-6] 场景检测 + 关键帧提取
565
- # -----------------------------------------------------------------------
563
+
564
+
565
+
566
566
  scenes = []
567
567
  keyframes = []
568
568
 
@@ -574,9 +574,9 @@ Examples:
574
574
  log("[5/6] 跳过场景检测(--skip-keyframes)")
575
575
  log("[6/6] 跳过关键帧提取(--skip-keyframes)")
576
576
 
577
- # -----------------------------------------------------------------------
578
- # 汇总输出
579
- # -----------------------------------------------------------------------
577
+
578
+
579
+
580
580
  final = build_result(
581
581
  source_url=source_url,
582
582
  local_video_path=video_path,
@@ -588,13 +588,13 @@ Examples:
588
588
  keyframes=keyframes,
589
589
  )
590
590
 
591
- # 写入 JSON
591
+
592
592
  json_path = os.path.join(output_dir, "deconstruction.json")
593
593
  with open(json_path, "w", encoding="utf-8") as f:
594
594
  json.dump(final, f, ensure_ascii=False, indent=2)
595
595
 
596
596
  if args.json_output:
597
- # 管道模式:仅输出 JSON 到 stdout
597
+
598
598
  print(json.dumps(final, ensure_ascii=False, indent=2))
599
599
  else:
600
600
  log(f"\n资产清单已保存: {json_path}")
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- 视频解构脚本 — 通过 ab-render HTTP 接口实现,无需本地 ffmpeg。
4
2
 
5
- 调用链:
6
- POST $RENDER_API_URL/parse → { taskId }
7
- POST $RENDER_API_URL/parseStatus → { status, progress, result?, error? }
8
3
 
9
- 必要环境变量(由 ab-agent 自动注入):
10
- RENDER_API_URL ab-render 服务地址(默认 https://api-render.remixmate.ai;指向本地/staging 时覆盖)
11
- PRIV_TOKEN ab-api 私有 token
12
4
 
13
- 可选环境变量:
14
- CONVERSATION_ID 对话 ID,用于文件关联
15
- """
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
16
 
17
17
  import argparse
18
18
  import json
@@ -22,7 +22,7 @@ import time
22
22
  import urllib.error
23
23
  import urllib.request
24
24
 
25
- # ─── 环境变量 ─────────────────────────────────────────────────────────────────
25
+
26
26
 
27
27
  RENDER_API_URL = (
28
28
  os.environ.get("RENDER_API_URL")
@@ -32,10 +32,10 @@ RENDER_API_URL = (
32
32
  PRIV_TOKEN = os.environ.get("PRIV_TOKEN", "")
33
33
  CONVERSATION_ID = os.environ.get("CONVERSATION_ID", "")
34
34
 
35
- # ─── HTTP 工具 ────────────────────────────────────────────────────────────────
35
+
36
36
 
37
37
  def api_post(path: str, body: dict, timeout: int = 30) -> dict:
38
- """向 ab-render 发送 POST 请求,返回解析后的 JSON。"""
38
+
39
39
  url = f"{RENDER_API_URL}{path}"
40
40
  data = json.dumps(body).encode()
41
41
 
@@ -60,7 +60,7 @@ def log(msg: str, silent: bool = False) -> None:
60
60
  print(msg, flush=True)
61
61
 
62
62
 
63
- # ─── 主逻辑 ───────────────────────────────────────────────────────────────────
63
+
64
64
 
65
65
  def main() -> None:
66
66
  parser = argparse.ArgumentParser(
@@ -79,7 +79,7 @@ def main() -> None:
79
79
  )
80
80
  args = parser.parse_args()
81
81
 
82
- # ── Pre-flight check ─────────────────────────────────────────────────────
82
+
83
83
  if not RENDER_API_URL:
84
84
  print(
85
85
  "❌ RENDER_API_URL env var is not configured.\n"
@@ -88,7 +88,7 @@ def main() -> None:
88
88
  )
89
89
  sys.exit(1)
90
90
 
91
- # ── 1. Submit the deconstruction task ───────────────────────────────────
91
+
92
92
  log(f"[video-parser] Submitting deconstruction task: {args.url[:80]}", args.json_output)
93
93
 
94
94
  resp = api_post("/parse", {
@@ -105,9 +105,9 @@ def main() -> None:
105
105
  task_id: str = resp["data"]["taskId"]
106
106
  log(f"[video-parser] task accepted, taskId={task_id}", args.json_output)
107
107
 
108
- # ── 2. Poll status until completion ─────────────────────────────────────
109
- max_wait_sec = 600 # Wait up to 10 minutes
110
- poll_interval = 3 # Poll every 3 seconds
108
+
109
+ max_wait_sec = 600
110
+ poll_interval = 3
111
111
  elapsed = 0
112
112
 
113
113
  while elapsed < max_wait_sec:
@@ -116,7 +116,7 @@ def main() -> None:
116
116
 
117
117
  try:
118
118
  status_resp = api_post("/parseStatus", {"taskId": task_id})
119
- except Exception as e: # noqa: BLE001
119
+ except Exception as e:
120
120
  log(f"[video-parser] polling error (will retry): {e}", args.json_output)
121
121
  continue
122
122
 
@@ -1,8 +1,8 @@
1
- """media-screenshot internal package.
2
1
 
3
- Public surface used by the entry-point scripts. Anything else under this
4
- package is implementation detail.
5
- """
2
+
3
+
4
+
5
+
6
6
  from __future__ import annotations
7
7
 
8
8
  from . import cli_args, scenes, template, trim
@@ -1,4 +1,4 @@
1
- """Auto-install playwright + chromium on first run."""
1
+
2
2
  from __future__ import annotations
3
3
 
4
4
  import subprocess
@@ -14,7 +14,7 @@ def _pip_install(pkg: str) -> None:
14
14
 
15
15
  def _ensure_playwright_pkg() -> None:
16
16
  try:
17
- import playwright.sync_api # noqa: F401
17
+ import playwright.sync_api
18
18
  return
19
19
  except ImportError:
20
20
  pass
@@ -22,11 +22,11 @@ def _ensure_playwright_pkg() -> None:
22
22
 
23
23
 
24
24
  def ensure_browser(name: str = "chromium") -> None:
25
- """Install the given browser engine via `playwright install <name>`.
26
25
 
27
- Must install the engine that is about to be launched: installing chromium
28
- and then relaunching firefox fails the same way a second time.
29
- """
26
+
27
+
28
+
29
+
30
30
  print(f"[bootstrap] playwright install {name} ...", file=sys.stderr)
31
31
  subprocess.check_call(
32
32
  [sys.executable, "-m", "playwright", "install", name]
@@ -34,5 +34,5 @@ def ensure_browser(name: str = "chromium") -> None:
34
34
 
35
35
 
36
36
  def ensure_runtime() -> None:
37
- """Make sure the playwright pip package is importable."""
37
+
38
38
  _ensure_playwright_pkg()
@@ -1,4 +1,4 @@
1
- """Browser context / locator / wait helpers."""
1
+
2
2
  from __future__ import annotations
3
3
 
4
4
  from typing import Any
@@ -49,14 +49,14 @@ def _is_missing_executable(e: Exception) -> bool:
49
49
 
50
50
  def launch_with_browser_install(p, browser_name: str | None, launch_kwargs: dict | None = None):
51
51
  btype = pick_browser_type(p, browser_name)
52
- engine = btype.name # "chromium" | "firefox" | "webkit"
52
+ engine = btype.name
53
53
  try:
54
54
  return btype.launch(**(launch_kwargs or {}))
55
55
  except Exception as e:
56
56
  if not _is_missing_executable(e):
57
57
  raise
58
- # 引擎没装:装「这次要启动的那个」引擎再试一次。装不上 / 装了仍起不来时给一句
59
- # 能据以行动的话,而不是 30 行 Playwright traceback。
58
+
59
+
60
60
  hint = (
61
61
  f"浏览器引擎 {engine} 在当前环境不可用。"
62
62
  + ("去掉 browser 参数改用默认的 chromium 即可。" if engine != "chromium"
@@ -1,4 +1,4 @@
1
- """Argparse helpers shared by ``screenshot.py`` and ``record.py``."""
1
+
2
2
  from __future__ import annotations
3
3
 
4
4
  import argparse
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
 
10
10
 
11
11
  def add_common_args(ap: argparse.ArgumentParser) -> None:
12
- """Add the args common to both screenshot and record entry points."""
12
+
13
13
  ap.add_argument(
14
14
  "-b", "--browser", choices=["chromium", "firefox", "webkit"],
15
15
  default="chromium", help="浏览器内核(默认 chromium)",
@@ -59,7 +59,7 @@ def parse_cookies(raw: str) -> list[dict]:
59
59
 
60
60
 
61
61
  def build_storage(args) -> tuple[str | None, bool]:
62
- """Returns (storage_state_path, is_temp). Caller must unlink if is_temp."""
62
+
63
63
  if getattr(args, "storage_state", None):
64
64
  return args.storage_state, False
65
65
  if getattr(args, "cookies", None):
@@ -92,7 +92,7 @@ def parse_clip(raw: str) -> list[float]:
92
92
 
93
93
 
94
94
  def common_args_to_cfg(args, cfg: dict) -> None:
95
- """Merge the common-args values into a cfg dict (shared by entry points)."""
95
+
96
96
  if args.device:
97
97
  cfg["device"] = args.device
98
98
  if args.viewport:
@@ -1,4 +1,4 @@
1
- """Load .js files from `js/` as strings, cached at import time."""
1
+
2
2
  from __future__ import annotations
3
3
 
4
4
  from functools import lru_cache
@@ -9,6 +9,6 @@ _JS_DIR = Path(__file__).resolve().parent / "js"
9
9
 
10
10
  @lru_cache(maxsize=None)
11
11
  def load(name: str) -> str:
12
- """Return the contents of js/<name>.js. Raises FileNotFoundError if missing."""
12
+
13
13
  path = _JS_DIR / f"{name}.js"
14
14
  return path.read_text(encoding="utf-8")
@@ -1,4 +1,4 @@
1
- """Small wrappers around the overlay-management JS snippets."""
1
+
2
2
  from __future__ import annotations
3
3
 
4
4
  from . import js_loader