- 用户 2026-10-12 令「可以加入」。
- 依据:product-planning 第③段的交接说明已把它当**依赖**指向 ⇒ 仓里有引用、仓里就该有实体,
⛔ 否则从本仓还原到别的机器会拿到一个「指向装不出来的技能」的悬空引用。
- 收录 5 个:SKILL.md、scripts/extract_frames.py、scripts/fetch_video.py、
references/视频抽帧与字幕规范.md、ATTRIBUTION.md。
- 目录内 env/ 与 logs/ 仍按本机配置规则挡在仓外(各机器自建)。
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
video-capture · 取视频(最小、合规集)
|
||
|
||
只做两件事,用于把「一个视频」落到本地待抽帧:
|
||
1) 本地文件:直接登记(--file)
|
||
2) 直链媒体 URL:下载(--url,需 .mp4/.mov/.webm/.m4v 等直链)
|
||
⛔ 不做:平台页内抓取(抖音/B站/YouTube 等页面解析)、⛔ 不调任何付费接口。
|
||
—— 如需平台视频,请先用浏览器工具把视频下到本地,再用 --file 传入(本工具不代抓)。
|
||
|
||
依赖:ffprobe(校验)+ Python 标准库。可选 curl(更快)。
|
||
用法:
|
||
python fetch_video.py --file D:/a.mp4 --out ./video
|
||
python fetch_video.py --url https://host/x.mp4 --out ./video
|
||
"""
|
||
import argparse
|
||
import json
|
||
import os
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
import urllib.request
|
||
|
||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0 Safari/537.36"
|
||
|
||
|
||
def probe(path):
|
||
try:
|
||
r = subprocess.run(
|
||
["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||
"-show_entries", "stream=codec_type,width,height", "-of", "json", path],
|
||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||
j = json.loads(r.stdout or "{}")
|
||
dur = float((j.get("format") or {}).get("duration") or 0)
|
||
w = h = 0
|
||
for s in (j.get("streams") or []):
|
||
if s.get("codec_type") == "video":
|
||
w, h = int(s.get("width") or 0), int(s.get("height") or 0)
|
||
break
|
||
return {"duration": round(dur, 3), "resolution": f"{w}x{h}"}
|
||
except Exception:
|
||
return {}
|
||
|
||
|
||
def download(url, dest):
|
||
req = urllib.request.Request(url, headers={"User-Agent": UA, "Referer": url})
|
||
with urllib.request.urlopen(req, timeout=90) as r, open(dest, "wb") as f:
|
||
shutil.copyfileobj(r, f)
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
g = ap.add_mutually_exclusive_group(required=True)
|
||
g.add_argument("--file", help="本地视频路径")
|
||
g.add_argument("--url", help="直链媒体 URL(.mp4/.mov/.webm/.m4v)")
|
||
ap.add_argument("--out", default="video", help="输出目录")
|
||
args = ap.parse_args()
|
||
|
||
os.makedirs(args.out, exist_ok=True)
|
||
|
||
if args.file:
|
||
if not os.path.exists(args.file):
|
||
print(json.dumps({"ok": False, "error": f"找不到文件:{args.file}"}), file=sys.stderr)
|
||
sys.exit(2)
|
||
dest = os.path.join(args.out, os.path.basename(args.file))
|
||
if os.path.abspath(dest) != os.path.abspath(args.file):
|
||
shutil.copy2(args.file, dest)
|
||
else:
|
||
if not os.path.splitext(args.url.split("?")[0])[1].lower() in (".mp4", ".mov", ".webm", ".m4v", ".mkv"):
|
||
print(json.dumps({"ok": False, "error": "仅支持直链媒体 URL(.mp4/.mov/.webm/.m4v/.mkv);平台页内抓取不在本工具范围"}), file=sys.stderr)
|
||
sys.exit(2)
|
||
dest = os.path.join(args.out, "video" + os.path.splitext(args.url.split("?")[0])[1].lower())
|
||
try:
|
||
download(args.url, dest)
|
||
except Exception as e:
|
||
print(json.dumps({"ok": False, "error": f"下载失败:{e}"}), file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
meta = probe(dest)
|
||
print(json.dumps({"ok": True, "video": os.path.abspath(dest), **meta}, ensure_ascii=False, indent=2))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|