video-capture:入仓(加入 .gitignore 白名单)—— 取视频 + 抽帧的全局技能
- 用户 2026-10-12 令「可以加入」。
- 依据:product-planning 第③段的交接说明已把它当**依赖**指向 ⇒ 仓里有引用、仓里就该有实体,
⛔ 否则从本仓还原到别的机器会拿到一个「指向装不出来的技能」的悬空引用。
- 收录 5 个:SKILL.md、scripts/extract_frames.py、scripts/fetch_video.py、
references/视频抽帧与字幕规范.md、ATTRIBUTION.md。
- 目录内 env/ 与 logs/ 仍按本机配置规则挡在仓外(各机器自建)。
This commit is contained in:
1 parent
2666a611b1
commit
051482168f
6 files changed
+567
No files matched your search
@@ -0,0 +1,85 @@
|
||||
#!/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()
|
||||
Reference in new issue
Block a user