Day 08 · AI 视频摘要:10 分钟视频 30 秒看完
作者:梅雅达编程笔记
收藏了一个 1 小时的 Python 教学视频,想着"周末好好看"。结果周末到了,打开视频,看了 5 分钟觉得太慢,拖了一下进度条,又觉得跳太多了怕漏掉关键内容……反反复复折腾了 20 分钟,啥也没学到。最后安慰自己"收藏了就是学了",然后继续刷短视频。
这个视频其实就讲了 3 个知识点。如果有个 30 秒就能看完的图文摘要就好了。
今天这篇就写这么一套工具:给它一个视频文件,它自动帮你截取画面变化的关键帧、把语音全部转成文字、让 AI"看"关键帧理解画面里展示了什么,最后综合文字和画面,生成一份带时间戳的图文笔记。全程自动,你只需要一行命令。
视频为什么这么"难啃"
文字有摘要,图片有缩略图,但视频没有。传统方案无非两种:
方案 A:人工看完全片,手写笔记。 1 小时视频至少花 1.5 小时(还得暂停记笔记)。慢、累、不可持续。
方案 B:自动截取等间隔截图 + 字幕。 很多播放器有这个功能——每隔 N 秒截一张图。但你仔细看就知道,大多数截图是"废话画面":讲师在喝水、PPT 停在同一页、画面没有变化。
问题的核心是:视频的信息密度不均匀。一个 1 小时的教学视频,真正有新信息的内容可能只占 20%——剩下的全是过渡、重复、闲聊。你需要的不是"均匀截图",而是"智能抓重点"。
AI 视频摘要的思路就是四件事:
- 关键帧提取:不是等间隔截,而是在"画面发生变化"的时候才截——切 PPT 了、换场景了、出现新代码了,这些才是重点时刻
- 语音转文字:把讲师说的每句话都记下来(Day05 学的 Whisper,今天直接复用)
- 视觉模型看关键帧:光有文字不够,视频里很多信息是"写在画面上"的——代码截图、图表、公式、板书。视觉模型能"看懂"这些
- LLM 综合整理:把文字 + 画面信息合在一起,生成结构化的摘要笔记
原理:三步拼出一个视频摘要
整个流程画成一张图:
视频文件
│
├──→ FFmpeg 提取音轨 ──→ Whisper 语音转文字 ──→ 带时间戳的全文
│
├──→ FFmpeg 场景检测 ──→ 关键帧图片序列
│ │
│ GLM-4V-Flash 视觉分析
│ │
│ 每帧的文字描述
│
└──────────────┬──────────────────┘
│
glm-4.5-air 综合总结
│
结构化图文摘要
FFmpeg 场景检测(Scene Detection)
FFmpeg 内置了一个 select 滤镜,配合 scene 检测器,可以自动识别"画面什么时候发生了显著变化"。原理很简单:计算相邻两帧的像素差异(直方图差值),差异超过阈值就认为"场景切换了",把这一帧截下来。
ffmpeg -i video.mp4 -vf "select='gt(scene,0.3)'" -fps_mode vfr frame_%04d.jpg
这一行命令就能把视频里所有"画面变化"的瞬间截取出来。阈值 0.3 是个经验值——越小越敏感(截的帧越多),越大越迟钝(只截大变化)。
Whisper 语音转文字
Day05 详细讲过,这里直接复用。核心就一句话:
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
segments, info = model.transcribe("audio.wav", language="zh")
GLM-4V-Flash 视觉分析 + glm-4.5-air 总结
关键帧截下来之后,用 GLM-4V-Flash(永久免费的视觉模型)逐张分析"画面里有什么",然后把画面描述 + 语音转文字的结果一起交给 glm-4.5-air,让它生成结构化摘要。
为什么需要视觉模型?因为视频里很多东西是"写在画面上"的——PPT 标题、代码片段、图表数据。纯靠语音转文字,这些信息会丢失。
动手实战:完整 Pipeline
环境准备
# Python 依赖
pip install faster-whisper requests
# FFmpeg(系统级工具)
# Windows: 去 https://ffmpeg.org/download.html 下载,加入 PATH
# macOS:
brew install ffmpeg
# Ubuntu:
sudo apt install ffmpeg
验证安装:
ffmpeg -version # 能看到版本号就行
ffprobe -version # ffprobe 用来获取视频信息
第一步:提取音轨
视频文件里的音频不是独立文件,而是和视频流封装在一起的。我们先用 FFmpeg 把音频"抽"出来:
import subprocess
def extract_audio(video_path, output_dir):
audio_path = f"{output_dir}/audio_track.wav"
cmd = [
"ffmpeg", "-y",
"-i", video_path,
"-vn", # 不要视频,只要音频
"-acodec", "pcm_s16le", # 16bit PCM 编码
"-ar", "16000", # 16kHz 采样率(Whisper 推荐)
"-ac", "1", # 单声道
audio_path
]
subprocess.run(cmd, capture_output=True, text=True, check=True)
return audio_path
16kHz 单声道 WAV 是 Whisper 的"标准输入格式",这个采样率对人声足够用了。
第二步:关键帧提取
这是整个 Pipeline 最有意思的一步。
def extract_keyframes(video_path, output_dir, threshold=0.3, max_frames=30):
frames_dir = f"{output_dir}/keyframes"
os.makedirs(frames_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y",
"-i", video_path,
"-vf", f"select='gt(scene\\,{threshold})',scale=1280:-1",
"-fps_mode", "vfr", # 可变帧率,只输出选中的帧(ffmpeg 9 已移除 -vsync vfr)
"-pix_fmt", "yuvj420p", # 关键帧 JPEG 输出,避免 mjpeg 编码器 limited-range YUV 报错
"-q:v", "2", # JPEG 高质量
"-frames:v", str(max_frames), # 最多截 max_frames 帧
f"{frames_dir}/frame_%04d.jpg"
]
subprocess.run(cmd, capture_output=True, text=True, check=True)
# 收集截到的帧
frame_files = sorted([
f"{frames_dir}/{f}" for f in os.listdir(frames_dir)
if f.endswith(".jpg")
])
return frame_files
几个参数解释:
select='gt(scene,0.3)':场景差异 > 0.3 才截取。0.3 是个不错的起点scale=1280:-1:截出来的帧缩放到宽度 1280px,高度自动。这样发给视觉模型不会太大-fps_mode vfr:只输出被选中的帧,不输出"空白帧"(ffmpeg 9 起-vsync vfr已移除,用这个替代)-frames:v 30:最多 30 帧,防止长视频截几百张图
如果场景检测没截到帧怎么办? 比如视频全程画面几乎没变(纯音频播客配一张静态图),这时候需要退化为等间隔截取:
def extract_frames_interval(video_path, frames_dir, max_frames=30):
"""退化方案:等间隔截取"""
# 用 ffprobe 获取视频时长
result = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", video_path],
capture_output=True, text=True
)
duration = float(result.stdout.strip())
interval = duration / (max_frames + 1)
frame_files = []
for i in range(max_frames):
timestamp = interval * (i + 1)
output = f"{frames_dir}/frame_{i+1:04d}.jpg"
subprocess.run([
"ffmpeg", "-y", "-ss", str(timestamp),
"-i", video_path, "-vframes", "1",
"-q:v", "2", "-vf", "scale=1280:-1",
"-pix_fmt", "yuvj420p", # 兼容 mjpeg 编码器
output
], capture_output=True)
if os.path.exists(output):
frame_files.append(output)
return frame_files
第三步:语音转文字
直接复用 Day05 的代码:
from faster_whisper import WhisperModel
def transcribe_audio(audio_path):
model = WhisperModel("small", device="cpu", compute_type="int8")
segments, info = model.transcribe(
audio_path,
language="zh",
vad_filter=True, # 自动跳过静音段
)
full_text = ""
transcript_segments = []
for seg in segments:
text = seg.text.strip()
if text:
transcript_segments.append({
"start": round(seg.start, 1),
"end": round(seg.end, 1),
"text": text,
})
full_text += text
return {"full_text": full_text, "segments": transcript_segments}
第四步:视觉模型分析关键帧
把每张关键帧发给 GLM-4V-Flash,让它描述画面内容:
import base64
import requests
def analyze_keyframes(frame_files, frame_timestamps):
"""逐帧调用 GLM-4V-Flash,返回 [{time_str, description}, ...]"""
descriptions = []
for frame_path, timestamp in zip(frame_files, frame_timestamps):
with open(frame_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
mm, ss = int(timestamp // 60), int(timestamp % 60)
time_str = f"{mm:02d}:{ss:02d}"
resp = requests.post(
"https://open.bigmodel.cn/api/paas/v4/chat/completions",
headers={"Authorization": f"Bearer {os.environ['GLM_API_KEY']}"},
json={"model": "glm-4v-flash",
"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
{"type": "text", "text": f"这是视频在 {time_str} 时刻的画面,用一句话描述画面内容。"}]}],
"max_tokens": 100},
timeout=30,
)
desc = resp.json()["choices"][0]["message"]["content"].strip()
descriptions.append({"time_str": time_str, "description": desc})
return descriptions
GLM-4V-Flash 是永久免费的视觉模型,支持图片理解。这里我们只让它"一句话描述",控制 token 消耗。
第五步:LLM 生成结构化摘要
最后,把语音转文字 + 关键帧描述一起交给 glm-4.5-air,让它生成结构化摘要:
def generate_summary(transcript, frame_descriptions):
"""语音转文字 + 关键帧描述 → glm-4.5-air 生成结构化摘要"""
frame_text = "\n".join(
f"[{fd['time_str']}] {fd['description']}" for fd in frame_descriptions)
prompt = f"""根据以下视频的语音转文字内容和关键帧画面描述,生成结构化图文摘要笔记。
## 语音转文字内容
---
{transcript["full_text"][:8000]}
---
## 关键帧画面描述
---
{frame_text}
---
请输出 JSON 格式:{{"title": "标题(15字内)", "one_line_summary": "一句话总结",
"timeline": [{{"time": "00:00", "topic": "主题", "detail": "要点"}}],
"key_points": ["要点1","要点2"], "conclusion": "核心结论", "tags": ["标签1"]}}"""
resp = requests.post(
"https://open.bigmodel.cn/api/paas/v4/chat/completions",
headers={"Authorization": f"Bearer {os.environ['GLM_API_KEY']}"},
json={"model": "glm-4.5-air", "messages": [{"role": "user", "content": prompt}],
"temperature": 0.3, "max_tokens": 2000},
timeout=60,
)
content = resp.json()["choices"][0]["message"]["content"]
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
return json.loads(content.strip())
把 Pipeline 串起来
把所有步骤串成一行命令可调用的 Pipeline:
def video_summary_pipeline(video_path, output_dir=None):
"""一行命令跑完:视频 → 图文摘要"""
video_path = os.path.abspath(video_path)
if output_dir is None:
video_name = Path(video_path).stem
output_dir = f"{os.path.dirname(video_path)}/{video_name}_summary"
os.makedirs(output_dir, exist_ok=True)
# Step 1: 提取音轨
audio_path = extract_audio(video_path, output_dir)
# Step 2: 关键帧提取
frame_files = extract_keyframes(video_path, output_dir)
if not frame_files:
frame_files = extract_frames_interval(video_path, f"{output_dir}/keyframes")
# Step 3: 语音转文字
transcript = transcribe_audio(audio_path)
# Step 4: 视觉模型分析关键帧
frame_descriptions = analyze_keyframes(frame_files, frame_timestamps)
# Step 5: LLM 生成摘要
summary = generate_summary(transcript, frame_descriptions)
# Step 6: 导出 Markdown
export_markdown(summary, transcript, frame_descriptions, frame_files, output_dir)
return summary
运行:
python day08_video_summary.py lecture.mp4
输出目录结构:
lecture_summary/
├── video_summary.md ← 图文笔记(直接能看)
├── summary_result.json ← 结构化数据(程序能用)
├── audio_track.wav ← 提取的音轨
└── keyframes/ ← 关键帧图片
├── frame_0001.jpg
├── frame_0002.jpg
└── ...
完整的可运行代码见配套文件 day08_video_summary.py。
实测运行结果
用一段 15 秒的测试视频(sample_lecture.mp4,testsrc 画面 + 正弦音轨)跑完整条流水线,控制台输出如下:

这次是真实调用智谱 API:音轨提取 ✓ → 场景检测未检出硬切(测试视频画面连续变化),自动退化为等间隔截取 30 帧 ✓ → Whisper 转录运行正常(该测试音轨是正弦波、无人声,所以转录 0 字;换成真人语音视频就能出完整字幕)→ glm-4v-flash 逐帧分析 30 个关键帧 ✓ → glm-4.5-air 生成结构化摘要 ✓ → 导出 video_summary.md。总耗时 75 秒。
环境提示:FFmpeg 需要 5.1 以上版本。ffmpeg 9 起
-vsync vfr已移除,代码里已改用-fps_mode vfr;关键帧 JPEG 输出加-pix_fmt yuvj420p避免 mjpeg 编码器对 limited-range YUV 报错。文本模型用.env里的GLM_REPORTER_MODEL=glm-4.5-air,视觉模型用免费的 glm-4v-flash。
这套流水线花多少钱
| 项目 | 方案 | 成本 |
|---|---|---|
| 关键帧提取 | FFmpeg 场景检测(开源) | ¥0 |
| 语音转文字 | faster-whisper 本地运行 | ¥0 |
| 关键帧视觉分析 | GLM-4V-Flash(永久免费) | ¥0 |
| 结构化摘要生成 | glm-4.5-air(永久免费) | ¥0 |
| 图片去重(可选) | Pillow + imagehash(开源) | ¥0 |
| 本篇总计 | ¥0 |
整条 Pipeline 跑一个 1 小时视频的成本是零。API 消耗估算:约 30 次视觉模型调用(每次约 100 token)+ 1 次文本模型调用(约 2000-3000 token),全在免费额度内。时间成本:1 小时视频,CPU 环境下大约需要 3-5 分钟(主要耗时在 Whisper 转录),有 GPU 的话 1-2 分钟。
从"能用"到"好用"
关键帧智能去重
场景检测有时候会截到非常相似的两帧(比如 PPT 上只改了一个字)。加一步视觉去重:
def deduplicate_frames(frame_files, similarity_threshold=0.9):
"""用图片哈希去重,删除相似的关键帧"""
from PIL import Image
import imagehash
unique_frames = []
last_hash = None
for fp in frame_files:
img = Image.open(fp)
h = imagehash.phash(img)
if last_hash is None or h - last_hash > 5: # 汉明距离 > 5 算不同
unique_frames.append(fp)
last_hash = h
return unique_frames
长视频分段处理
1 小时以上的视频,Whisper 处理会比较慢。可以先切分再并行处理:
def split_audio(audio_path, output_dir, chunk_seconds=600):
"""把长音频切成 10 分钟一段"""
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", audio_path,
"-f", "segment",
"-segment_time", str(chunk_seconds),
"-c", "copy",
f"{output_dir}/chunk_%03d.wav"
]
subprocess.run(cmd, capture_output=True)
chunks = sorted([
f"{output_dir}/{f}" for f in os.listdir(output_dir)
if f.endswith(".wav")
])
return chunks
批量处理视频文件夹
工作室场景:甲方发来 20 个教学视频,要全部生成摘要。
def batch_video_summary(video_dir, output_base_dir):
"""批量处理文件夹中的所有视频"""
video_exts = {".mp4", ".avi", ".mkv", ".mov", ".flv", ".wmv"}
videos = [
f for f in os.listdir(video_dir)
if os.path.splitext(f)[1].lower() in video_exts
]
print(f"找到 {len(videos)} 个视频文件")
for i, video in enumerate(videos, 1):
print(f"\n{'='*60}")
print(f"[{i}/{len(videos)}] {video}")
print(f"{'='*60}")
video_path = os.path.join(video_dir, video)
output_dir = os.path.join(output_base_dir,
os.path.splitext(video)[0])
try:
video_summary_pipeline(video_path, output_dir)
except Exception as e:
print(f"❌ 处理失败:{e}")
顺带看看:2026 的 AI 视频生成
这篇做的是"视频→摘要"(理解视频),反过来"文字/图片→视频"(生成视频)在 2026 年也在爆发式增长,简单聊几个:
Runway Gen-3 / Gen-4.5: 目前专业级 AI 视频生成的标杆。Gen-3 Alpha 的核心优势是"导演级控制"——Motion Brush 2.0 可以精确控制画面中每个区域的运动方向和速度,Act One 能做面部表情捕捉和迁移,摄像机控制支持推拉摇移等专业术语。2025 年底推出的 Gen-4.5 进一步支持原生 4K 输出。在影视广告领域,Runway 已经是很多工作室的标配工具。
可灵 AI(Kling): 快手旗下的 AI 视频生成平台,2026 年用户突破 6000 万,年化收入超过 3 亿美元。最新的 Kling VIDEO 3.0 Omni 实现了"文本+图片+音频"统一生成——一段视频自带同步对话和环境音效,支持 5 种语言的精准口型同步。AI Director 功能可以自动把一段长 prompt 拆分成多机位镜头,直接生成完整场景而不是单个片段。$6.99/月起步,性价比很高。
行业趋势: Netflix 2026 年二季度财报披露,今年已有约 300 部作品在制作中使用了生成式 AI,主要集中在后期制作环节。国内爱奇艺也上线了首部获发行许可证的 AIGC 网络故事片。AI 视频生成正在从"好玩的玩具"变成"真正的生产力工具"。
不过这两件事方向是互补的——"理解视频"和"生成视频"迟早会合流。下一篇(Day09)就把图文生成和视频摘要的能力结合起来,做一套完整的小红书内容生产流水线。
想练手可以改这些
按难度分三档:
入门级:
- 调整场景检测阈值(0.1 / 0.3 / 0.5),对比同一个视频截出来的关键帧数量和质量
- 把摘要的 Markdown 输出改成 HTML 格式,加上图片预览,做得更漂亮
进阶级:
- 给 Pipeline 加一个"精华片段剪辑"功能:根据摘要中的 key_points 时间戳,用 FFmpeg 自动剪出每段要点的前后 10 秒
- 实现"多视频对比摘要":喂进去同一主题的 3 个视频,生成一份综合笔记
挑战级:
- 把关键帧分析改成"多图批量发送"(GLM-4V-Flash 支持单条消息多张图片),减少 API 调用次数
- 搭一个本地 Web 界面:上传视频 → 预览关键帧 → 编辑摘要 → 导出 PDF
参考链接
- FFmpeg 场景检测文档:FFmpeg 官方文档(select 滤镜)
- faster-whisper:GitHub 仓库 SYSTRAN/faster-whisper
- 智谱 BigModel(GLM-4V-Flash 免费):智谱开放平台
- GLM-4V-Flash 视觉模型文档:智谱开放平台
- Runway Gen-3:Runway 官网
- 可灵 AI(Kling):可灵 AI 官网
- 本篇配套代码:
day08_video_summary.py
把这套代码里的视频路径换成你自己的文件跑一遍,10 分钟的视频 30 秒就能看完。遇到问题,评论区见。
更多推荐

所有评论(0)