@remixmate/cli 0.9.72 → 0.9.74
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 -192
- 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 +44 -3
- 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-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 -22
- 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
|
-
视频解构脚本 - 将视频拆解为可复用的内容资产
|
|
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
|
-
|
|
17
|
-
|
|
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 = ""
|
|
32
|
+
PRIVATE_TOKEN = ""
|
|
33
33
|
SKILL_NAME = "video-parser"
|
|
34
34
|
AGENT_NAME = os.environ.get("AGENT_NAME", "")
|
|
35
35
|
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
98
|
|
|
99
99
|
def download_video(url: str, output_dir: str) -> str:
|
|
100
|
-
|
|
101
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
146
|
|
|
147
147
|
def extract_audio(video_path: str, output_dir: str) -> str:
|
|
148
|
-
|
|
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
|
-
|
|
174
|
-
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
175
|
|
|
176
176
|
def upload_audio(audio_path: str) -> str:
|
|
177
|
-
|
|
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
|
-
|
|
187
|
+
|
|
188
188
|
body = b""
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
249
|
-
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
250
|
|
|
251
251
|
def run_asr(audio_url: str) -> dict:
|
|
252
|
-
|
|
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
|
-
|
|
294
|
-
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
295
|
|
|
296
296
|
def detect_scenes(video_path: str, threshold: float) -> list:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
|
|
548
548
|
audio_path = extract_audio(video_path, output_dir)
|
|
549
549
|
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
|
|
36
36
|
|
|
37
37
|
def api_post(path: str, body: dict, timeout: int = 30) -> dict:
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
max_wait_sec = 600
|
|
110
|
-
poll_interval = 3
|
|
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:
|
|
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,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
37
|
+
|
|
38
38
|
_ensure_playwright_pkg()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
58
|
+
|
|
59
|
+
|
|
60
60
|
hint = (
|
|
61
61
|
f"浏览器引擎 {engine} 在当前环境不可用。"
|
|
62
62
|
+ ("去掉 browser 参数改用默认的 chromium 即可。" if engine != "chromium"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
+
|
|
96
96
|
if args.device:
|
|
97
97
|
cfg["device"] = args.device
|
|
98
98
|
if args.viewport:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
12
|
+
|
|
13
13
|
path = _JS_DIR / f"{name}.js"
|
|
14
14
|
return path.read_text(encoding="utf-8")
|