Skip to main content
Glama
README.md
# vidlens

[![ci](https://github.com/cctv2333/vidlens/actions/workflows/ci.yml/badge.svg)](https://github.com/cctv2333/vidlens/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/vidlens-mcp.svg?cacheSeconds=600)](https://pypi.org/project/vidlens-mcp/)
[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

**中文** · [English](README.en.md)

把一个**公开可看**的视频,降解成「模型能真正读懂的证据表」。
零云 API、零视觉模型、零显卡依赖。核心原则:**只下最小必要字节**。

## 一分钟理解

我有眼睛(能看图片),但没有耳朵。所以我需要两样东西:
1. 声音 → 带时间戳的文字(这是唯一必须借工具的部分)
2. 任意时刻的画面 → 我自己看

工具只负责"取样 + 量化",理解留在模型脑子里。

## 三级预算(实测数据,7 分钟 1080p 视频)

| 取法 | 体积 | 拿到什么 |
|---|---|---|
| 全片 1080p | 105.5 MB | 全部 |
| **只下音频** | **7.3 MB** | **100% 语言信息(139 段 / 2184 字,与 1080p 逐字一致)** |
| 只取 20 秒片段 | 0.9 MB | 那 20 秒的画面 |
| 平台字幕(若有) | 0 MB | 平台自带的字幕轨,连音频都不用下 |

**分辨率对语言信息零贡献。** 只有要读小字(代码、表格、UI 参数)时才需要原分辨率单帧。

## 安装

需要 Python 3.10+。两种装法:

**A. 从 PyPI(推荐给使用者,不用 clone)**

```bash
pipx install "vidlens-mcp[all]"      # 或 pip install "vidlens-mcp[all]"
vidlens doctor                        # CLI 自检
# MCP 配置只需要:{"command": "vidlens-mcp"}
```

产物与缓存在 `%LOCALAPPDATA%\vidlens\`(POSIX:`~/.local/share/vidlens/`),
可用 `VIDLENS_OUT` / `VIDLENS_BIN` 覆盖;模型仍然放在你自己的模型目录。

**B. 从源码(开发用)**

```bat
install.cmd
```

工程在哪就用哪,下面三个位置都能用环境变量覆盖:
- 产物:`VIDLENS_OUT`,默认工程内 `out/`(源码布局);装成 wheel 后默认 `%LOCALAPPDATA%\vidlens\out`
- 模型:`VIDLENS_MODEL_ROOT`,默认 `%LOCALAPPDATA%\vidlens\models`(POSIX 走 XDG);旧的 `D:\dev\models` **仅在它已存在时**沿用
- ffmpeg 软链:`VIDLENS_BIN`,默认工程内 `bin/`(装成 wheel 后默认用户数据目录下的 `bin/`)

**DSH 插件**(DeepSeek Harness 的原生 21 个工具)在另一个仓库:
<https://github.com/cctv2333/dsh-vidlens>


## 命令

### 一、网上视频(不需要先下载)

```bash
vl meta  <url>                      # 只读网页,0 字节媒体
vl grab  <url> --lang zh            # 字幕优先;无字幕则只下音频 -> 转写 + 停顿 + brief
vl grab  <url> --cuts               # 额外扫镜头(需下最低画质视频,约 14-21MB,用完即删)
vl clip  <url> --from 02:00 --to 02:20 --sheet   # 只下这 20 秒,出带时间码的拼图
```

### 二、本地视频

```bash
vl probe  a.mp4
vl shots  a.mp4 --thr 0.30
vl frames a.mp4 --at 19.5,21.0
vl frames a.mp4 --from 92 --to 104 --fps 4
vl sheet  a.mp4 --from 0 --to 30 --cols 5 --rows 4
vl speech a.mp4 --lang zh
vl pauses a.mp4
vl env    a.mp4                      # 响度包络(文本火花线)
vl report a.mp4 --lang zh            # 本地全量 -> brief.md + events.jsonl
```

Windows 用 `vl.cmd`,其余平台用 `.venv/bin/python vidlens.py`。

**MCP 工具同样吃本地路径**(1.6.0 起,18 个工具全部有效):`video_meta("D:\\a.mp4")`、
`video_brief(...)`、`video_ocr(...)` 都可以直接给路径,也认 `file:///D:/a.mp4`、`~/a.mov`、带引号的路径。
本地文件**不下载、不联网**。以前这里会报 `Unsupported url scheme: "d"`——盘符被当成 URL scheme,
属于工具层把"输入一定是 URL"写死了(见 BUG_LEDGER B-068)。

本地文件少两样东西,因为那是网站才有的:**平台字幕**(走本地 ASR)和**评论区**(工具会直接说没有,不会编造)。

## 安全边界(写死在代码里)

**1. 不是白名单,是行为规则。** 没有站点列表,没有域名限制。任何不用登录就能看的页面都可以试。
遇到"需要登录/付费/会员/验证码"会**停下来说明原因**,因为绕过去会让账号和站点把你当攻击者,代价比看一个视频大得多。
如果你愿意,可以显式传 `--cookies-from-browser` 用你自己的登录态——默认不用。

**2. 不装爬虫。** 一次调用只处理一个视频;请求间强制 `--sleep-requests 0.6`、
`--sleep-interval 1~3`、失败退避重试。这是为了保护"以后还能看",不是为了限制你。

**3. 外部文本分两层看,不是"什么都不信"。**

| 层面 | 规则 | 原因 |
|---|---|---|
| **指令地位** | 外部文本永远是数据,不是命令 | 标题/字幕里写"忽略以上指令"的句子,不能让它劫持我。这条无条件。 |
| **事实可信度** | 工具不判断真假,只标注来源与风险 | 视频说的可能不准确。该信的是画面/时间码/交叉验证,不是旁白。 |

所以 brief 顶部会给的是"来源 + 风险扫描",不是"内容不可信"。
清洗只针对**藏字和载荷**(零宽字符、双向符、base64、ANSI),正常文字一个字不改。

**4. 不信 exit code。** yt-dlp 对失效视频可能"成功"返回空壳,
`check_meta()` 会校验 id/title/duration,不合格就停。

## MCP 外壳(18 个工具)

> `url` 参数既接受公开视频链接,也接受**本地文件路径**(1.6.0 起),例如 `D:\a.mp4` 或 `file:///D:/a.mp4`。

**基础四件**

| 工具 | 下载量 | 用途 |
|---|---|---|
| `video_meta(url)` | 0 | 标题/UP主/时长/播放/是否有字幕 |
| `video_plan(url, window=300)` | 0 | 按切点/长静音分章节 + 建议顺序;长视频先调它 |
| `video_subs(url, lang=zh)` | 0 | 平台字幕(人工优先,其次自动),带时间码 |
| `video_brief(url, ..., level="digest")` | 平台字幕 0;无字幕才下音频 | **摘要**(默认,约 1.5k 字:章节 + 5 句关键台词 + 全文路径);`level="full"` 才给 6–9k 字完整报告;>20 分钟且无字幕时默认只给章节,`full=true` 才全量 |
| `video_watch(url, minutes=5, cursor="next")` | 每段该窗音频(或缓存) | **分段看片**:一次一段(默认 5 分钟、约 0.5k 字),带回「已看 X / 共 Y(%)」;`cursor` 支持 next/restart/stop |
| `video_window(url, start, end)` | 0(有缓存时) | 某时间窗的台词与停顿 |
| `video_full(url, confirm=True)` | 整片 | 必须显式确认 |

**超时解法(长视频必用)**

| 工具 | 用途 |
|---|---|
| `video_job_start(url, scan_cuts, comments)` | 后台起任务,立即返回 `job_id` |
| `video_job(job_id, wait_seconds=20)` | 轮询阶段+百分比,完成后返回报告 |

任务状态存在服务进程内存里:**Chatbox 重启/重连 MCP,未取回的任务会消失**(重跑即可,缓存还在)。

**看画面 / 无音频视频**

| 工具 | 下载量 | 用途 |
|---|---|---|
| `video_visual_scan(url, n=24)` | 最低画质视频(7min≈14MB) | 全片抽样 n 帧(按切点优先),烧上时间码拼图 |
| `video_sheet(url, start, end, cols, rows)` | 该段(20s≈0.9MB) | 指定区间拼图 |
| `video_frames(url, at=[...])` | 该段 | 最多 6 张精确帧 |
| `video_ocr(url, start, end, n=12)` | 该段最低画质 | **把画面上的字读成文本**(本地 OCR,离线),带时间码;无人声/无字幕视频的主力 |

**无声视频(字幕型)怎么处理**

中文知识区有大量视频**没有人声、也没有平台字幕,文字全烧在画面上**。`video_brief` 会先告诉你
「几乎无人声(有效台词 0 字)」,接着用 `video_ocr`,它把 n 帧里的文字识别出来、合并同一句跨帧的重复、
剔除水印与图表标签,输出可直接引用的带时间码文本(12 帧约 1.3k tokens,比逐帧看图省一个数量级)。

OCR 是可选依赖,引擎是 [RapidOCR](https://github.com/RapidAI/RapidOCR)(Apache-2.0),
PP-OCRv6 模型随包分发、约 30MB,**装好即离线,运行时不下载任何东西**。

**装在哪、怎么换(不装也不会坏)**:第一次调用 `video_ocr` 不会偷偷下载,而是告诉你
「下载约 80MB、会装到 <路径>、怎么换目录」;再调一次并传 `install=true` 才开始下,
下载前后都会把路径写进结果。

| 变量 | 作用 | 默认 |
|---|---|---|
| `VIDLENS_OCR_TARGET` | 装到这个目录(自动加入 import 路径,不写系统目录) | 本解释器的 `site-packages`(Windows 上通常就是 **C 盘**) |
| `VIDLENS_OCR_AUTO_INSTALL` | `ask` 先问(默认)/ `auto` 自动装 / `off` 只给命令 | `ask` |

```
# 想放 D 盘、并且以后自动装:
set VIDLENS_OCR_TARGET=D:\your\ocr
set VIDLENS_OCR_AUTO_INSTALL=auto

# 或者自己装一次(推荐给 CI/多环境):
pip install "vidlens-mcp[ocr]"
vidlens_doctor          # 会多一行 ocr: rapidocr 可用
```

实测:下载约 80MB、装完占盘 150MB(已有 numpy)/ 最多 220MB(自选目录要自带 numpy),
首次安装 18s(有 pip 缓存时)。

署名与许可证见 [NOTICE.md](NOTICE.md);与 [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR)(MIT)
的关系只是"借了离线+可换引擎的做法",没有复制它的代码。

**听声音事件(台词之外)**

| 工具 | 下载量 | 用途 |
|---|---|---|
| `video_soundscape(url, start, end)` | 该段音频(60s≈1MB) | AudioSet 527 类声音事件(Chewing / Knock / Wood block / Water / Chink / Scrape …)+ 概率 + 时间码;**喝水/吞咽没有对应类**,只能靠水声+画面推断 |

**判断内容性质**

| 工具 | 下载量 | 用途 |
|---|---|---|
| `video_comments(url, limit=40)` | 0 | 评论互动比、正/负面词频、争议度启发式 |
| `vidlens_models(action=list)` | 0 | 本地模型目录 + 硬件建议 + **本机真实路径**;要下用 `action=download`(confirm 才真下) |
| `vidlens_doctor()` | 0 | 自检:python/ffmpeg/yt-dlp/依赖/模型/磁盘/网络 |

Chatbox / Claude / Cursor 里加 stdio server 的三项:

```
command : <你的 python 路径>
args    : <工程目录>\vl_mcp.py       # 或直接 vidlens-mcp
env     : PYTHONIOENCODING=utf-8 ; HF_HUB_DISABLE_XET=1
```

通用 JSON 见 `mcp.example.json`(`<PROJECT>` 换成工程目录);本机 `mcp.json` 是实际路径。
**注意:MCP 走 stdio,服务进程绝对不能 print**;
库函数用 `verbose=False`,下载/ASR 用 `_MEDIA_LOCK` 串行。禁止用
`contextlib.redirect_stdout` 长时间劫持 `sys.stdout`,那会吞掉协议响应。

**需要 cookie 的站点**(抖音等)默认不碰;用户可显式开启:

```
VIDLENS_COOKIES=D:\path\cookies.txt
# 或
VIDLENS_COOKIES_FROM_BROWSER=chrome   # edge / firefox / chromium
```

**浏览器正在运行时读不到 cookie 库**(文件被锁),先完全退出浏览器(含托盘/后台进程)再试;
至于"这个浏览器里到底有没有该站的 cookie"是另一回事——没登录过就是没有,工具不会替你凭空造一个。
新版 Chrome/Edge 用 App-Bound 加密时,导出的 `cookies.txt` + `VIDLENS_COOKIES` 更稳。

**图片内联策略**(MCP 的 image block 线上就是 base64,一张 340KB 拼图 ≈ 45 万字符):

```
VIDLENS_INLINE_IMAGES=auto    # 默认:只内联 ≤200KB 的图,超出留在磁盘并给路径
VIDLENS_INLINE_IMAGES=always  # 全贴图(能读文件的宿主没必要)
VIDLENS_INLINE_IMAGES=never   # 只给路径,一张都不贴
```

每个较重的工具结果末尾都有一行 `[成本] 本次回上下文 ≈N tok`,方便在调用前决定值不值。

## 命令行新增命令

```bash
vl clean --days 7 --dry-run      # 清理旧缓存(默认只删媒体,保留 brief.md)
vl clean --days 7                # 真删
vl clean --days 30 --all         # 整个目录删
vl doctor                        # 自检 11 项
vl comments <url>                # 评论区信号 -> out/<id>/comments.md
vl scan <url> --n 24             # 全片抽样帧拼图(无音频视频用)
vl plan <url> --window 300       # 长视频分步建议
vl models                        # 本地模型目录(只读,不下载)
vl models --recommend            # 按内存/CPU/磁盘给建议
```

## 无音频 / 长静音视频怎么办

1. `video_brief` 会先算**语音覆盖率**;低于 15% 直接提醒"信息在画面里"
2. 用 `video_visual_scan` 全片抽样(有切点按切点取,没有就均匀取),一次返回多张带时间码的图
3. 发现可疑段落,再用 `video_sheet` 或 `video_frames` 局部放大

## 长视频怎么办:默认抽样,按需全量

人类看长视频会加速/跳过,工具也按这个顺序:

1. `video_meta` → 值不值得看
2. `video_plan` → 0 下载拿章节
3. 挑 2-3 段:`video_window` 读台词、`video_sheet` 看画面
4. 只有用户明确要「完整/逐字/全片」时:`video_brief(full=true)`,超过 60 秒用
   `video_job_start(full=true)` → `video_job` 轮询

超过 20 分钟、且没有平台字幕的视频,`video_brief` 默认**只返回章节建议**,
不会偷偷下完整音频。平台字幕优先(`video_subs`,0 下载);没有字幕才走本地 ASR。
`video_job(comments=True)` 会顺带附上评论区分析。

## 本地 ASR:SenseVoice(推荐)/ Whisper

默认 `VIDLENS_ASR=auto`:只要 `sherpa-onnx`、SenseVoice 模型和词表都在,就用 SenseVoice;
否则自动回退 faster-whisper。**不会重复下模型**。

- 用户已有模型(FlashVoice 自带):
  `%APPDATA%\com.flashvoices\models\sense-voice-large.onnx`
  (937MB,等于官方 float32 `model.onnx`,元数据 `iic/SenseVoiceSmall`)
- 词表:`tokens.txt`(308KB,一次下载,25,055 行),放在模型根目录的 `sensevoice/` 下
- 覆盖路径:
  ```
  VIDLENS_SENSEVOICE_MODEL=D:\path\sense-voice.onnx
  VIDLENS_SENSEVOICE_TOKENS=D:\path\tokens.txt
  ```
- 实测(本机 CPU):加载 1.5s;28.4s 音频解码 0.53s;20s 音乐段解码 0.35s。
- 一次输出:`text`、`lang`(zh/en/yue/ja/ko)、`emotion`(NEUTRAL/HAPPY/SAD/…)、
  `event`(Speech/BGM/Laughter/Applause/Cry/…)、`timestamps`。
- `video_audio` 用这些标签 + 一个轻量节拍估计(合成 120 BPM 实测 121.0;真实 BGM 约 125)。
- 强制旧后端:`VIDLENS_ASR=whisper`。Whisper 加载时先试 `local_files_only=True`,
  缓存命中就不联网(实测 1.68s 加载);只有缓存缺失才下载。
- **注意**:新对话会启动新的 MCP 进程,模型需要重新加载进内存(SenseVoice 约 1.5s),
  但不会重新下载权重。模型加载 ≠ 下载。

## 音乐信息(优先看平台元数据)

- **B 站**:`player/wbi/v2` 的 `bgm_info` 直接给 `music_title` 和音乐链接。
  `video_meta` 会显示「平台音乐」,`video_brief` 有「音乐信息」章节,
  `video_audio` 也会带上。
- **其他站**:扫描标题/简介里的 `BGM:`、`配乐:`、`音乐:`、`歌曲:`、`《…》`
  等线索;这是零下载的文本信息,不是听音识曲。
- **注意**:平台标注是**全片**信息,未必当前片段在放;也可能缺失/写错。
- 实测 BV1PvHX61EgV:`bgm_info` = 《Macarena (River Re-Mix 103 BPM)》,
  `video_meta` 已正确显示。

## 模型目录 / 下载 / CED-mini / AcoustID

`vidlens_models`(或 `vl models`)列出本机模型、体积、用途、硬件建议,**以及本机解析出的
真实路径**(`MODEL_ROOT` / whisper 目录 / 声音事件目录)。下载走同一条链路,规则和 OCR 一致:

```
vidlens_models(action="list")                                  # 目录 + 真实路径
vidlens_models(action="download", name="ced-mini")             # 只报大小与落点
vidlens_models(action="download", name="ced-mini", confirm=true)  # 真下,回报装到哪
```

| 变量 | 作用 | 默认 |
|---|---|---|
| `VIDLENS_MODEL_ROOT` | 整棵模型目录(whisper / sensevoice / audio-tagging 都在下面) | `%LOCALAPPDATA%\vidlens\models`(POSIX: XDG);旧的 `D:\dev\models` 仅在它已存在时沿用 |
| `VIDLENS_MODEL_DIR` / `VIDLENS_TAGGING_DIR` | 单独覆盖 whisper / 声音事件 | 上面目录的子目录 |
| `VIDLENS_MODEL_AUTO_DOWNLOAD` | `ask` 先问 / `auto` 自动下(**只对 ≤20MB**)/ `off` 只给命令 | `ask` |

可下载:`ced-mini` 10MB、`whisper-tiny` 75MB、`whisper-base` 145MB、`whisper-small` 484MB。
**whisper 不再静默下载**:以前第一次转写会直接 `local_files_only=False` 拉几百 MB,
现在先给大小与落点,确认后才下。SenseVoice(937MB)只给说明,不代下。

- **CED-mini**(已接入,V-007):sherpa-onnx 的 `AudioTagging` + `k2-fsa` int8 模型
  (10.4MB)+ `class_labels_indices.csv`(14.7KB),AudioSet 527 类声音事件分类,
  **不是 ASR**。入口:`video_soundscape` / `vidlens_soundscape`。
  能识别吃饭/咀嚼(Chewing, mastication)、敲/拍(Knock / Tap / Wood block / Thump)、
  水声(Water / Water tap)、杯子碰撞(Chink, clink)、拖拽(Scrape)、塑料袋
  (Crumpling, crinkling)、掌声/笑声等。**喝水/吞咽没有对应 AudioSet 类**,只能靠
  水声 + 画面推断;要准需 CLAP 零样本或自训小分类器。
  实测:加载 0.14s、28s 音频推理 43ms;下载链路实测 10.0MB / 52s(hf-mirror 回退),
  下完立刻被 `available()` 认出来(同一个解析函数,装和读不会各说各话)。
- **AcoustID**:音频指纹检索,不是 AI 模型。需要 Chromaprint `fpcalc` + 客户端
  API key,官方限 3 请求/秒、仅非商业;返回 MusicBrainz 记录,不保证歌名。
  优先级低于平台元数据:B 站 `player/wbi/v2` 的 `bgm_info` 直接给歌名和链接,
  已接入 `video_meta` / `video_brief` / `video_audio`。
- 平台元数据也可能缺失/写错;需要时再用听音识曲交叉核对。

## 方言 / 口音 / 字幕

- **人工字幕**:UP 主上传的字幕最可靠,直接 `video_subs` 或 `video_brief`(0 下载)。
- **自动字幕**:平台 ASR 生成的,免下载但可能错;仍比没有强。
- **B 站 AI 字幕**:`video_meta` 的 `has_auto_subs` 会显示 `ai-zh` 等;
  但匿名 `player/v2` 多数返回空,通常需要登录态。设置
  `VIDLENS_COOKIES_FROM_BROWSER=edge`(或 chrome/firefox)后再试。
  字幕匹配已包含 `ai-zh,ai-en,ai-.*`。
- **无字幕**:SenseVoice 优先;`lang` 传 `yue`(粤语)等可改善;重口音仍可能错。
  要旧 Whisper 加方言提示词:`VIDLENS_ASR=whisper` + `prompt=...`。
- **画面硬字幕**:用 `video_sheet` / `video_frames` 取帧,AI 自己看字。
- 低置信词会列在 brief 的「低置信词」章节,必须回画面/原音核对。
## 产物结构

```
out/<视频id>/
  meta.json          元数据 + 风险扫描结果
  transcript.jsonl   词级时间戳 + 每词置信度
  transcript.srt     字幕
  pauses.json        话中停顿
  brief.md           来源/风险扫描 / 结构 / 停顿语境 / 低置信词 / 下一步看哪里
  clip_<a>_<b>/      按需片段与拼图(媒体文件默认删除)
```

## 广告自动跳过

赞助口播、带货、优惠码——**零价值,不进摘要**。

- `vidlens_ads.py` 按“赞助/优惠/下单/链接/积分/保温杯…”等词加权,按**时间间隔**聚类成段,
  再要求“时长够长 + 命中密度够高”才判定(不靠单个关键词就下手)
- brief 里单独列出「广告段落(已跳过)」及时间码,**转写正文直接剔除**
- 原始文字仍保留在 `transcript.jsonl`(可追溯),只是不进摘要
- 实测:某 40 分钟视频命中 `20:02.780 → 21:39.060`(96 秒,占 4%);无广告视频 0 命中
- 阈值可调:`vidlens_ads.detect(rows, min_score=, min_dur=, density=, join_sec=)`,
  误判可直接看 brief 里的段落推翻

## 交接与分发

- 交接入口:`HANDOVER.md` → `handover/README.md`
- 架构/模型/分发/归档/复现:`handover/ARCHITECTURE.md`、`MODELS.md`、
  `DISTRIBUTION.md`、`ARCHIVE.md`、`REPRODUCE.md`
- 可分发包:`dist/vidlens-src-YYYYMMDD.zip`(不含模型、`out/`、`.venv/`、`bin/`、
  `mcp.json`)。本次包解压后 94 项测试通过。
- 许可证:**MIT**(见 `LICENSE`);模型权重不随仓库/分发包分发。

## 测试(209 项,默认不依赖网络)

```bash
.venv\Scripts\python.exe -m unittest discover -s tests -v
```

`core` 9 + `web` 23 + `extra` 12 + `ads` 9 + `mcp` 7(含真实 stdio 握手)+
`plan` 10 + `features` 24(字幕/抽样/cookie/缓存/边界/声音工具/模型目录)+
`audioevents` 7(声音事件,模型缺失时自动跳过)+ `lock` 3(跨进程锁)。
网络相关用例用 `VIDLENS_NET_TESTS=1` 打开。

覆盖:探测、切点、抽帧命名、拼图偏移(历史 bug 回归)、静音检测、时间码解析、
SRT 生成、模型目录、零宽字符清洗、中英文注入检测、shell/外泄检测、
滚动字幕去重、VTT 解析、片段参数边界、登录墙识别、空壳元数据拒绝、
AudioSet 窗口切分与时间码不漂移、跨进程文件锁。

## 发布(维护者)

- CI:`.github/workflows/ci.yml`(ubuntu + windows × python 3.10/3.12,离线测试 +
  wheel 构建 + 「安装形态写用户数据目录」检查)。
- 发版:打 tag 即发布。

```bash
git tag v1.4.0 && git push origin v1.4.0
```

`release.yml` 构建 sdist + wheel、通过 **Trusted Publishing(OIDC)** 上传 PyPI(不需要任何长期 token;一次性 publisher 配置见 `RELEASING.md`);
也可改用 PyPI Trusted Publishing)、并附到 GitHub Release。

## 路线图

已做 / 未做 / 验收标准 / 不做清单:[ROADMAP.md](ROADMAP.md)。

## 已知坑(都是实测踩过的)

1. **`av` 版本**:faster-whisper 1.2.1 用 `av.open(metadata_errors=...)`,av 19 已删该参数。
   本工具绕开 PyAV,用 ffmpeg 自解码 16k 波形喂模型。
2. **`HF_HUB_DISABLE_XET=1` 必须设**,否则 hf-mirror 走 Xet 会 401。
3. **`--download-sections` 要求可执行文件名就是 `ffmpeg`**,而 imageio 给的是
   `ffmpeg-win-x86_64-v7.1.exe` → 用 `ffmpeg_dir()` 建一个软链接/副本。
4. **`-ss` 会让 pts 归零**,拼图时间码会全错 → 必须 `setpts=PTS+offset/TB`。
5. **静音段分两种**:话中犹豫,和片尾无词。混在一起会给出 8 条一样的"犹豫"结论。
6. **PowerShell 读无 BOM 脚本,中文全烂;`.cmd` 必须 CRLF;中文别走命令行参数。**
7. **Linux 上 imageio 的 ffmpeg 没有 `drawtext`**(没编 libfreetype):拼图/抽样帧不能烧时间码。
   现在会先用 `has_drawtext()` 探一次,没有就**跳过烧字**、照样出图(文件名与时间码仍然准确),
   并在回执里注明 —— 以前是直接整张拼图失败。

## 许可证与归属

本项目以 **MIT** 许可证发布,见 [`LICENSE`](LICENSE)。本项目未复制任何外部代码,
只借鉴设计思路,已逐项在 `NOTICE.md` 登记。
运行时依赖:yt-dlp(Unlicense)、faster-whisper(MIT)、imageio-ffmpeg(BSD-2)、
sherpa-onnx(Apache-2.0)、ffmpeg 二进制(LGPL/GPL,以子进程方式调用,不链接)。
模型权重(SenseVoice / Whisper / CED-mini)不随仓库分发,各自遵循上游许可。

TDQS

A3.8/5.0

Scored across 18 tools

Disambiguation4/5

Descriptions actively steer the agent away from the wrong tool (e.g. video_subs before ASR, video_sheet/frames instead of video_full, video_window for a single span vs video_watch for incremental progress), which is unusually strong guidance. Still, there are clusters of near-neighbours: video_brief / video_watch / video_window all can yield transcripts of the same footage, and video_sheet / video_frames / video_visual_scan / video_ocr all sample frames, so a hurried agent could easily pick the wrong one.

Naming Consistency4/5

Nearly everything follows a single `video_<noun>` scheme with short, readable suffixes. The two infrastructure tools break the prefix (vidlens_models, vidlens_doctor), and a few names mix styles (video_job_start is noun_verb, video_full is an adjective), but the overall set is predictable.

Tool Count4/5

18 tools is on the heavy side, but they form a deliberate cost tier (meta → plan → window → sheet → OCR → job) rather than a flat pile. Each tool has a stated cheaper alternative it exists to replace, though a few of the visual/transcript tools could plausibly be merged.

Completeness5/5

The surface covers the full video-analysis lifecycle: metadata, subtitles, planning, incremental watching, time windows, audio events, soundscape, frame/sheet/scan visuals, OCR, comments, plus model management, diagnostics and async job polling for long runs. No obvious dead ends remain for the stated domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues