Files
ai-agent-book/chapter5/video-edit/README.md
T
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

458 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Experiment 5-6: API-Driven Smart Video Editing / 实验 5-6:基于 API 的智能视频剪辑
> Companion lab for *AI Agents in Depth*, Chapter 5 — NL request + multi-scene video → two-step Vision locate → Blender bpy script / ffmpeg cut → ProposerReviewer.
> 《深入理解 AI Agent》配套:自然语言需求 + 多场景视频 → 两步 Vision 定位 → 生成 Blender bpy / ffmpeg 剪辑 → 提议者-审核者。
← [Chapter 5 index / 返回第 5 章目录](../README.md)
---
## English
### Purpose
Given a multi-scene video and one NL request (e.g. “cut out the surfing part”), the Agent locates the target scene, **generates a Blender Python API script**, cuts the clip, and self-reviews.
Three mechanisms in multimedia processing:
1. **Two-step Vision locate**: Proposer cannot “watch” video directly; a **video-analysis sub-agent** uses ffmpeg frame extraction + Vision LLM to find time bounds.
2. **Code generation (Blender Python API)**: Proposer turns the edit plan into a **bpy** script—import / trim / subtitle / speed / render as API calls, run with `blender --background --python edit.py`. Without Blender, scripts still generate; render falls back to ffmpeg (see “edit backends”).
3. **Proposer / Reviewer**: after cut, Reviewer samples key frames with Vision; fail → feedback → iterate.
### Two-step locate
Scanning every frame is slow/expensive → coarse then fine:
- **Coarse**: one frame every **10s**; Vision gets a rough interval (e.g. surfing 2030s).
- **Fine**: expand the coarse window by one coarse step; one frame every **1s**; Vision returns precise bounds (e.g. 1529s).
Encapsulated as a **sub-agent**: tens of screenshots live only in the sub-agents one-shot context and do not pollute Proposer/Reviewer history. Demo prints token comparison at the end.
### ProposerReviewer flow
```
NL request ──► Proposer parses intent (scene + effects)
Video-analysis sub-agent two-step locate ──► [start, end]
Proposer emits Blender bpy script (edit.py) ──► render cut (+ subtitle/slow-mo)
│ Blender if installed else ffmpeg
Reviewer samples start/mid/end frames ──► Vision pass/fail + feedback
│pass? no → Proposer adjusts bounds, re-cut (max 3 rounds)
▼yes
final.mp4
```
### Run
```bash
# From the repository root: use the shared Chapter 5 environment
uv sync --locked --python 3.12 --extra ch5
# Activate it before changing directories:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
# Windows cmd: .venv\Scripts\activate.bat
# pip fallback when uv is not installed:
# python -m pip install -e ".[ch5]"
cd chapter5/video-edit
# Single-project compatibility path, still supported during migration:
# python -m pip install -r requirements.txt
cp env.example .env # OPENAI_API_KEY (or OPENROUTER_API_KEY fallback)
python demo.py # default: "把冲浪的部分剪出来" (full pipeline)
python demo.py "把滑雪部分剪出来,并加上字幕 Winter" # custom request
python demo.py -i my.mp4 -o out.mp4 "把演讲开场剪出来" # own video + output
python demo.py --backend blender # force Blender headless (Blender required)
python demo.py --vision-model gpt-5.6-luna # also --text-model
python demo.py --quick # coarse sampling + single review round (cheapest Vision)
python demo.py --smoke # smoke: edit path + bpy script only; no API
python demo.py --help
```
Common flags (see `--help`): `--input/-i`, `--output/-o`, `--backend {auto,blender,ffmpeg}`, `--text-model` / `--vision-model`.
One command runs: generate/read video → two-step locate → bpy cut → review → final. Each run clears `output/` (idempotent). Full path calls Vision many times; use `--smoke` (zero API) or `--quick` first.
### Sample outputs
#### `--smoke` (zero API, reproducible)
Real output of `python demo.py --smoke` (no OpenAI key; needs ffmpeg):
```text
==========================================================================
冒烟自检 | 剪辑链路 + bpy 脚本生成,不调用任何 API
==========================================================================
[1/3] 生成测试视频 OKoutput/source.mp4(场景真值={'hiking': (0, 15), 'surfing': (15, 30), 'skiing': (30, 42), 'cycling': (42, 54)}
[2/3] 抽帧 OKoutput/frames/smoke.png
[3/3] 剪辑+字幕 OK(后端=ffmpeg(未装 Blender,回退)):
文件: smoke_cut.mp4
时长: 5.03s
容器: mov,mp4,m4a,3gp,3g2,mj2
大小: 121.4 KB
视频流: h264 1280x720 @ 30/1 fps
音频流: aac 44100Hz 1ch
已生成 Proposer 的 Blender 脚本:output/edit.py
(这正是书中'生成 Blender Python API 代码'的产物;装好 Blender 后可直接
`blender --background --python output/edit.py` 无头渲染。)
✓ 冒烟自检通过:剪辑链路正常 + bpy 脚本已生成(未调用 OpenAI)。
```
`output/edit.py` is an executable Blender bpy script (`new_movie`, `frame_offset_start` / `frame_final_duration`, `new_effect(type='TEXT')`, `bpy.ops.render.render`); syntax-checked with `py_compile`.
#### `--quick` (full path, needs API)
Excerpt from `python demo.py --quick` (default surfing request); locate/error/token parts independent of bpy/ffmpeg:
```text
步骤 1 | Proposer 解析自然语言需求
解析结果:目标场景='surfing scene' 特效=[]
步骤 2 | 视频分析子 Agent:两步 Vision 定位(--quick 快速采样)
[粗粒度] 每 15s 采样 5 帧 → Vision 得区间 [15, 30]s(依据:The word 'SURFING' appears at t=15s and changes at t=30s.
[细粒度] 窗口 [0.0, 45.0] 内每 2s 采样 23 帧 → 精确边界 [16.0, 28.0]s
>>> 最终定位:起 16.0s 止 28.0s
真值 [15, 30]s → 起点误差 1.0s,终点误差 2.0s(验收要求 ≤ 3s)
步骤 3-4 | Proposer 剪辑 + Reviewer 审查(迭代)
Proposer 剪出片段 [16.0, 28.0]s,成片时长 12.0s
Reviewerpass=... score=... 检查帧=['0.5', '6.0', '11.5']
Token 统计(子 Agent 隔离截图,主上下文不被污染)
主 AgentProposer+Reviewer):573 tokens
子 Agent(两步定位截图) 2934 tokens
```
Artifacts under `output/`:
| File | Duration | Note |
| --- | --- | --- |
| `source.mp4` | 54.0s | Procedural 4-scene test source |
| `edit_round1.py` | — | Proposer bpy script (portable) |
| `cut_round1.mp4` | 12.0s | Round-1 candidate |
| `final.mp4` | 12.0s | Chosen final (H.264 + AAC, 1280x720@30fps) |
Token stats: tens of screenshots (2934 tok) only in **sub-agent**; main history (~573 tok) almost unpolluted. (Synthetic test shows “SURFING” text, not real surfing—Reviewer may fail on content; real video avoids that.)
### Dependencies
- **ffmpeg / ffprobe** for cuts and frames. `brew install ffmpeg` (macOS) / `apt install ffmpeg` (Ubuntu). Validated on ffmpeg 8.0.
- **OPENAI_API_KEY**: `gpt-5.6-luna` for vision locate/review and text planning (vision model must accept images); or `OPENROUTER_API_KEY` fallback via OpenRouter.
### Adapt / extend
#### Model / provider
All via env (`env.example`); no code change:
- `TEXT_MODEL` (default `gpt-5.6-luna`).
- `VISION_MODEL` must support images (default `gpt-5.6-luna`).
- `OPENAI_BASE_URL` + matching `OPENAI_API_KEY` for compatible endpoints.
```bash
export OPENAI_BASE_URL=https://your-gateway.example.com/v1
export VISION_MODEL=gpt-5.6-luna
export TEXT_MODEL=gpt-5.6-luna
```
`agents.py` `OpenAI()` client reads these lazily via `client()`.
#### Own input video
`make_test_video.py` builds a 54s video with 4 distinct scenes (HIKING green / SURFING blue / SKIING white / CYCLING orange) plus large scene-name and timecode watermarks for Vision reproducibility.
Own video: `python demo.py -i your.mp4 -o out.mp4 "edit request"` (skips test generation; no ground-truth error print).
#### Blender vs ffmpeg (edit backends)
Book uses **Blender Python API (bpy)** on the VSE. First-class here: `blender_editor.generate_bpy_script()` emits real bpy (`new_movie`, frame offsets, `TEXT`/`SPEED` effects, render); `render_with_blender()` runs `blender --background --python edit.py`.
- `--backend blender`: force Blender (`blender --version` required);
- `--backend ffmpeg`: force ffmpeg;
- `--backend auto` (default): bpy if Blender installed, else ffmpeg.
**Always**: Proposers bpy script is written to `output/edit_round*.py` (`output/edit.py` under `--smoke`)—core “generate Blender Python API code” artifact. This environment may not have Blender, so render is validated with ffmpeg; bpy is `py_compile`-checked but **not rendered in real Blender** unless you install it and use `--backend blender`.
| | ffmpeg | Blender (bpy) |
| --- | --- | --- |
| Fit | 2D cut/join/subtitle/speed pipelines | 3D scenes, compositing, keyframe anim, particles/camera |
| Ops | single binary, no GUI, CI-friendly | full Blender install; larger/slower |
| When | most “cut a clip + simple effects” | only when 3D/compositing/complex transitions needed |
Two-step Vision + ProposerReviewer is decoupled from the execution layer; same edit plan for both backends.
### Files
| File | Role |
| --- | --- |
| `demo.py` | CLI orchestration, self-check, iteration, token stats |
| `agents.py` | `VideoAnalyzerAgent` / `ProposerAgent` / `ReviewerAgent` |
| `blender_editor.py` | **bpy script gen + headless render** (book path) |
| `video_editor.py` | `apply_edit()`; Blender/ffmpeg backends |
| `make_test_video.py` | Procedural 4-scene test video |
| `ffmpeg_utils.py` | Thin ffmpeg/ffprobe wrappers |
`output/` is gitignored.
### Limitations
- Locate accuracy depends on visual distinctness; gradual scene transitions increase boundary error vs solid-color test film.
- Fine step fixed at 1s → boundary precision about ±1s (book acceptance ±3s).
- Slow-mo audio uses `atempo`; extreme ratios hurt quality; complex transitions/multi-track not covered.
- Reviewer samples only start/mid/end; mid-clip glitches can be missed (raise sample density if needed).
---
## 中文
### 目的
用户给一段含多个场景的视频 + 一句自然语言需求(如"把冲浪部分剪出来"),Agent 自动定位目标场景、**生成 Blender Python API 脚本**剪出片段并自我审查。
验证三个核心机制在多媒体处理中的作用:
1. **两步 Vision 定位**Proposer 无法直接"看懂"视频,于是委托一个**视频分析子 Agent**,
用 ffmpeg 抽帧 + Vision LLM 读图来定位目标场景的时间边界。
2. **代码生成(Blender Python API**:Proposer 把剪辑计划翻译成一段调用 **Blender
Python APIbpy** 的脚本——导入 / 裁剪 / 字幕 / 变速 / 渲染各对应一个 API 调用,
`blender --background --python edit.py` 无头执行。这正是书中"把视频编辑重构为
API 调用和代码生成问题"的落地。未装 Blender 时脚本照常生成(代码生成产物),
实际渲染回退到 ffmpeg(见下文"剪辑后端")。
3. **提议者-审核者(Proposer / Reviewer**:Proposer 剪辑后无法自证效果,
由 Reviewer 抽取成片关键帧、用 Vision LLM 检查是否剪对,不合格则反馈、迭代。
### 两步定位原理
Vision LLM 逐帧扫全片既慢又贵,因此采用"先粗后细":
- **第一步(粗粒度)**:每 **10 秒**抽一帧,把全片的稀疏截图连同"要找哪个场景"一起
交给 Vision,得到大致区间(如"冲浪在 2030s")。
- **第二步(细粒度)**:在粗区间上下各外扩一个粗间隔,每 **1 秒**抽一帧,
再问 Vision 精确边界(如"1529s")。
把这套抽帧-读图封装成**独立子 Agent**:几十张截图只进入子 Agent 的一次性上下文,
不会污染主 AgentProposer/Reviewer)的对话历史。demo 末尾会打印两者的 token 对比。
### 提议者-审核者
```
NL 需求 ──► Proposer 解析意图(目标场景 + 特效)
视频分析子 Agent 两步定位 ──► [start, end]
Proposer 生成 Blender bpy 脚本(edit.py)──► 渲染剪辑(可加字幕/慢动作)
│ 装了 Blender 用 bpy,否则回退 ffmpeg
Reviewer 抽首/中/尾关键帧 ──► Vision 检查 pass/fail + 反馈
│pass? 否 → Proposer 据反馈修正边界,重剪(最多 3 轮)
▼是
输出成片 final.mp4
```
### 运行
```bash
# 在仓库根目录使用统一的第 5 章环境
uv sync --locked --python 3.12 --extra ch5
# 切换目录前先激活环境:
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell.\.venv\Scripts\Activate.ps1
# Windows cmd.venv\Scripts\activate.bat
# 未安装 uv 时可用 pip 兜底:
# python -m pip install -e ".[ch5]"
cd chapter5/video-edit
# 迁移期间仍支持单项目兼容路径:
# python -m pip install -r requirements.txt
cp env.example .env # 填入 OPENAI_API_KEY(未配置时设 OPENROUTER_API_KEY 自动改走 OpenRouter
python demo.py # 默认需求"把冲浪的部分剪出来"(完整流程)
python demo.py "把滑雪部分剪出来,并加上字幕 Winter" # 自定义需求
python demo.py -i my.mp4 -o out.mp4 "把演讲开场剪出来" # 用自己的视频 + 自定义输出
python demo.py --backend blender # 强制用 Blender Python API 无头渲染(需装 Blender
python demo.py --vision-model gpt-5.6-luna # 覆盖模型(也可用 --text-model
python demo.py --quick # 快速模式:粗采样 + 单轮审查,Vision 调用最少(省时省钱)
python demo.py --smoke # 冒烟自检:仅剪辑链路 + 生成 bpy 脚本,不调用任何 API
python demo.py --help # 查看全部参数
```
常用参数(完整见 `--help`):`--input/-i` 输入视频、`--output/-o` 成片路径、
`--backend {auto,blender,ffmpeg}` 剪辑后端、`--text-model`/`--vision-model` 覆盖模型。
一条命令即可跑通:生成/读取视频 → 两步定位 → 生成 bpy 脚本剪辑 → 审查 → 输出成片。
每次运行都会清空 `output/`,从干净状态开始(幂等可重复)。完整流程会多次调用
Vision 模型(较慢/耗费额度);只想验证链路时先跑 `--smoke`(零 API),或用 `--quick`
### 预期输出示例
#### `--smoke`(零 API,可复现)
以下为 `python demo.py --smoke` 的**真实输出**(无需 OpenAI Key,仅需 ffmpeg):
```text
==========================================================================
冒烟自检 | 剪辑链路 + bpy 脚本生成,不调用任何 API
==========================================================================
[1/3] 生成测试视频 OKoutput/source.mp4(场景真值={'hiking': (0, 15), 'surfing': (15, 30), 'skiing': (30, 42), 'cycling': (42, 54)}
[2/3] 抽帧 OKoutput/frames/smoke.png
[3/3] 剪辑+字幕 OK(后端=ffmpeg(未装 Blender,回退)):
文件: smoke_cut.mp4
时长: 5.03s
容器: mov,mp4,m4a,3gp,3g2,mj2
大小: 121.4 KB
视频流: h264 1280x720 @ 30/1 fps
音频流: aac 44100Hz 1ch
已生成 Proposer 的 Blender 脚本:output/edit.py
(这正是书中'生成 Blender Python API 代码'的产物;装好 Blender 后可直接
`blender --background --python output/edit.py` 无头渲染。)
✓ 冒烟自检通过:剪辑链路正常 + bpy 脚本已生成(未调用 OpenAI)。
```
生成的 `output/edit.py` 是一段**可执行的 Blender bpy 脚本**`new_movie` 导入、
`frame_offset_start`/`frame_final_duration` 裁剪、`new_effect(type='TEXT')` 字幕、
`bpy.ops.render.render` 渲染),本机对其做过 `py_compile` 语法校验。
#### `--quick`(完整链路,需 API
以下为 `python demo.py --quick`(默认需求"把冲浪的部分剪出来")的真实节选(定位/误差/
token 部分与剪辑后端无关,故不受 bpy/ffmpeg 后端切换影响):
```text
步骤 1 | Proposer 解析自然语言需求
解析结果:目标场景='surfing scene' 特效=[]
步骤 2 | 视频分析子 Agent:两步 Vision 定位(--quick 快速采样)
[粗粒度] 每 15s 采样 5 帧 → Vision 得区间 [15, 30]s(依据:The word 'SURFING' appears at t=15s and changes at t=30s.
[细粒度] 窗口 [0.0, 45.0] 内每 2s 采样 23 帧 → 精确边界 [16.0, 28.0]s
>>> 最终定位:起 16.0s 止 28.0s
真值 [15, 30]s → 起点误差 1.0s,终点误差 2.0s(验收要求 ≤ 3s)
步骤 3-4 | Proposer 剪辑 + Reviewer 审查(迭代)
Proposer 剪出片段 [16.0, 28.0]s,成片时长 12.0s
Reviewerpass=... score=... 检查帧=['0.5', '6.0', '11.5']
Token 统计(子 Agent 隔离截图,主上下文不被污染)
主 AgentProposer+Reviewer):573 tokens
子 Agent(两步定位截图) 2934 tokens
```
产物(`output/` 目录,真实文件):
| 文件 | 时长 | 说明 |
| --- | --- | --- |
| `source.mp4` | 54.0s | 程序化生成的 4 场景测试原片 |
| `edit_round1.py` | — | Proposer 生成的 Blender bpy 脚本(代码生成产物,可换机执行) |
| `cut_round1.mp4` | 12.0s | 第 1 轮剪出的候选片段 |
| `final.mp4` | 12.0s | 采用的成片(H.264 + AAC1280x720@30fps |
Token 统计印证了核心结论:几十张截图(2934 tokens)只进入**子 Agent**的一次性
上下文,主 Agent 的对话历史(573 tokens)几乎不受截图污染。
(注:合成测试片仅显示"SURFING"字样而非真实冲浪画面,Reviewer 有时会据此判为
不通过——这正是审核者按画面内容如实反馈的体现;换真实视频即无此现象。)
### 依赖
- **ffmpeg / ffprobe**:本机实际剪辑与抽帧。`brew install ffmpeg`macOS/
`apt install ffmpeg`Ubuntu)。本项目在 ffmpeg 8.0 上验证通过。
- **OPENAI_API_KEY**:用 `gpt-5.6-luna` 做视觉定位/审查与文本规划(视觉模型须支持图像输入);未配置时用 `OPENROUTER_API_KEY` 兜底,自动改走 OpenRouter。
### 如何适配 / 扩展
#### 换模型 / 供应商
模型与端点全部通过**环境变量**注入(见 `env.example`),无需改代码:
- `TEXT_MODEL`:规划/边界修正的文本模型(默认 `gpt-5.6-luna`)。
- `VISION_MODEL`:定位/审查的视觉模型,**必须支持图像输入**(默认 `gpt-5.6-luna`)。
- `OPENAI_BASE_URL`:换成任何兼容 OpenAI 协议的端点(自建代理、Azure OpenAI、
或其他厂商网关),配合对应的 `OPENAI_API_KEY` 即可。
```bash
export OPENAI_BASE_URL=https://your-gateway.example.com/v1
export VISION_MODEL=gpt-5.6-luna # 例:用当前廉价旗舰视觉模型
export TEXT_MODEL=gpt-5.6-luna
```
`agents.py` 里的 `OpenAI()` 客户端会自动读取上述变量(`client()` 惰性初始化)。
#### 换输入视频
`make_test_video.py` 用 ffmpeg **程序化生成**一段 54s 的视频,含 4 个明显不同的场景
HIKING 绿 / SURFING 蓝 / SKIING 白 / CYCLING 橙),每段都叠加大号场景名与时间码水印,
让 Vision 仅凭画面就能准确定位——便于复现验收。
换成**你自己的真实视频**:直接 `python demo.py -i 你的.mp4 -o 输出.mp4 "剪辑需求"`
即可(无需改代码)。此时跳过测试片生成,也不再打印定位误差(外部视频无真值)。
#### Blender vs. ffmpeg(剪辑后端)
书中原方案用 **Blender Python APIbpy** 驱动视频序列编辑器(VSE)完成剪辑。
本项目把它实现为**一等后端**`blender_editor.generate_bpy_script()` 把剪辑计划翻译成
一段真实可执行的 bpy 脚本(`new_movie` 导入、`frame_offset_start`/`frame_final_duration`
裁剪、`new_effect(type='TEXT'/'SPEED')` 字幕/变速、`bpy.ops.render.render` 渲染),
`render_with_blender()` 再用 `blender --background --python edit.py` 无头执行。
- `--backend blender`:强制走 Blender(需 `blender --version` 可用);
- `--backend ffmpeg`:强制走 ffmpeg
- `--backend auto`(默认):装了 Blender 用 bpy,否则回退 ffmpeg。
**关键点:无论哪个后端,Proposer 生成的 bpy 脚本都会落盘到 `output/edit_round*.py`**
`--smoke` 下为 `output/edit.py`)——即"生成 Blender Python API 代码"这一核心产物,
可人工核对、也可拷到装了 Blender 的机器上执行。本机未安装 Blender,故本仓库的实际
渲染由 ffmpeg 完成并验证;bpy 脚本已通过 `py_compile` 语法校验,但**未在真实 Blender
上跑过渲染**(装好 Blender 后即可用 `--backend blender` 端到端执行)。两种后端的取舍:
| | ffmpeg | Blenderbpy |
| --- | --- | --- |
| 定位 | 裁剪/拼接/字幕/变速等 2D 流水线 | 3D 场景、合成、关键帧动画、粒子/摄像机 |
| 上手 | 单二进制、无 GUI、CI 友好 | 需装完整 Blender,体积大、渲染慢 |
| 适用 | 绝大多数"剪一段 + 简单特效"需求 | 需要 3D 合成/复杂转场/图层混合时才值得 |
核心的"两步 Vision 定位 + 提议者-审核者"与执行层解耦,两个后端共用同一份剪辑计划,
`agents.py`/`demo.py` 无需为切换后端改动逻辑。
### 文件
| 文件 | 作用 |
| --- | --- |
| `demo.py` | 一条命令跑通的编排入口(CLI、启动自检、迭代循环、token 统计) |
| `agents.py` | `VideoAnalyzerAgent`(两步定位)/ `ProposerAgent` / `ReviewerAgent` |
| `blender_editor.py` | **Blender bpy 脚本生成 + 无头渲染**(书中原方案,核心实验点) |
| `video_editor.py` | 剪辑执行层:`apply_edit()` 统一入口,调度 Blender/ffmpeg 双后端 |
| `make_test_video.py` | 程序化生成含 4 个场景的测试视频 |
| `ffmpeg_utils.py` | ffmpeg/ffprobe 薄封装(统一错误检查、抽帧、探测时长/流) |
`output/`(生成的视频、截图、成片)已被 `.gitignore` 忽略,避免仓库膨胀。
### 局限
- 定位精度取决于场景在画面上的可辨识度;真实视频若场景过渡渐变,边界误差会大于纯色测试片。
- 细粒度步长固定 1s,边界精度上限即 ±1s 量级(满足书中 ±3s 验收)。
- 慢动作音频用 `atempo` 变速,倍率过大时音质下降;转场/多轨混音等复杂特效未覆盖。
- Reviewer 仅抽首/中/尾三帧,长片段中段的偶发错误可能漏检(可调高抽帧密度)。
---
## Notes / 说明
- Prefer `--smoke` then `--quick` before a full Vision run. / 完整 Vision 前先 `--smoke`,再 `--quick`
- Commands/code/paths/env vars are identical in both language sections. / 命令、代码、路径与环境变量在中英文两侧保持一致。