跳到主内容
快讯直播
AI智模界
教程

用 Pegasus 1.6 做长视频理解与可引用摘要

这篇能做出什么

跑完这套流程,你会得到一个可查询的长视频知识库,输入一句自然语言问题,输出这样的结果:

```

问题:预算调整是在哪一段讨论的?

预算在 Q3 被下调,主要削减来自市场推广费用。 [00:41:12–00:41:48]

下季度不再新增外包名额,现有外包合同到期不续。 [01:02:30–01:02:55]

该调整需要在下一次经营会上复核。 [01:03:10–01:03:26]

```

每条结论后面都挂着时间区间,点一下就能跳回原片核对。这比"整段视频总结成 200 字"有用得多——因为总结里的每一句话都能被验证,出错的句子能立刻发现并剔除。

整套流程分四件事:把长视频切成带绝对时间戳的片段、对片段做帧采样和索引、按时间戳提问拿到结构化证据、把证据归并成带引用的摘要并落成检索索引。Pegasus 系列是视频理解模型,1.6 具体支持的片段长度上限、鉴权方式、字段名与计费规则以官方文档当前版本为准,本文把所有厂商差异收敛在一个函数里,其余代码可直接照抄。

前置条件清单

  • Python 3.10 及以上,能装 requests、numpy
  • ffmpeg 与 ffprobe 可执行文件在 PATH 中(ffmpeg -version 有输出即可)
  • 一个视频理解模型的调用凭据:API Key、服务地址。环境变量里放 PEGASUS_API_KEY 和 PEGASUS_ENDPOINT
  • 一个用于向量化的 embedding 来源,本地模型或在线接口都行
  • 一段待处理的长视频,建议先用 5 分钟片子打通,再上 90 分钟
  • 磁盘留出视频体积 1.5 倍以上的空间给切片和抽帧

分步骤

步骤 1:先定产物结构,再动手

不要一上来就调模型。先把输出结构定死,后面每一步都往这个结构里填:

```python

schema.py

Claim = {

"video": str, # 视频标识

"text": str, # 一句结论

"t_start": float, # 绝对起始秒

"t_end": float, # 绝对结束秒

"chunk_id": str, # 来自哪个片段,便于回溯

"confidence": str, # high / medium / low

}

```

没有 t_start/t_end 的结论一律丢弃。这条规则是整个流程可验证性的地基。

步骤 2:规划切片,保留重叠

长视频必须切开,但切点落在句子中间会丢语义,所以相邻片段留一段重叠。

```python

plan.py

def plan_chunks(duration, chunk=180, overlap=15):

step = chunk - overlap

out, start, i = [], 0.0, 0

while start < duration:

end = min(start + chunk, duration)

out.append({"id": f"c{i:04d}", "start": start, "end": end})

if end >= duration:

break

start += step

i += 1

return out

```

先拿到真实时长:

```bash

ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4

```

步骤 3:用 ffmpeg 切片,保证时间戳准确

关键点是切片要重编码。用 -c copy 会吸附到最近的关键帧,实际起点和规划值可能差好几秒,后续所有时间戳都会偏。

```bash

cut.sh —— 逐片执行,$start / $dur / $id 由 Python 传入

ffmpeg -hide_banner -y \

-ss "$start" -i input.mp4 -t "$dur" \

-c:v libx264 -preset veryfast -crf 20 \

-c:a aac -ar 16000 -ac 1 \

-force_key_frames 0 \

"chunks/${id}.mp4"

```

切完立刻校验实际时长,写回清单:

```python

import subprocess, json

def probe_duration(path):

out = subprocess.check_output([

"ffprobe", "-v", "error", "-show_entries", "format=duration",

"-of", "csv=p=0", path,

])

return float(out.strip())

```

步骤 4:帧采样与时间戳映射

模型看视频通常是抽帧或抽帧加音轨。固定间隔采样最省事,也最好回溯:

```bash

每 2 秒取一帧,宽度压到 640,便于快速上传和比对

ffmpeg -hide_banner -y -i "chunks/${id}.mp4" \

-vf "fps=0.5,scale=640:-2" -q:v 3 \

"frames/${id}_%05d.jpg"

```

第 n 张帧(从 1 开始计数)在片段内的相对时间是 (n-1) / fps,加上片段起点就是绝对时间。有幻灯片、白板、代码演示的视频,再用一次镜头切换检测补充关键帧:

```bash

ffmpeg -hide_banner -y -i "chunks/${id}.mp4" \

-vf "select='gt(scene,0.35)',showinfo" -vsync vfr \

"frames_scene/${id}_%05d.jpg"

```

把两批帧合并去重后,连同绝对时间戳写进一个 manifest 文件,后面每一步都从 manifest 读时间,不靠推断。

步骤 5:把模型调用收敛成一个函数

不同服务的上传方式、鉴权头、返回字段都不一样。只写一个适配函数,其余代码就不受厂商变动影响:

```python

vlm.py

import os, time, requests

def analyze(clip_path, prompt, retries=4):

"""调用视频理解模型。

请求路径、鉴权头、表单字段名与响应结构,

以你所用服务官方文档当前版本为准,只需改这一个函数。

返回统一结构 {"text": str, "raw": dict}

"""

endpoint = os.environ["PEGASUS_ENDPOINT"]

api_key = os.environ["PEGASUS_API_KEY"]

delay = 2.0

for _ in range(retries):

with open(clip_path, "rb") as f:

resp = requests.post(

endpoint,

headers={"Authorization": f"Bearer {api_key}"},

files={"video": f},

data={"prompt": prompt, "temperature": "0"},

timeout=600,

)

if resp.status_code in (429, 500, 502, 503, 504):

time.sleep(delay)

delay *= 2

continue

resp.raise_for_status()

payload = resp.json()

return {"text": payload.get("text", ""), "raw": payload}

raise RuntimeError("重试次数用尽,检查限流或片段体积")

```

长任务建议改成"提交作业 + 轮询结果",避免连接被中间层掐断。

步骤 6:按时间戳提问

提问模板里必须写清片段的起点和时长,并且强制模型输出时间戳。否则你拿到的只是漂亮句子,无法回溯。

```python

PROMPT = """你在分析一段视频片段。

片段时长 {dur:.1f} 秒,在完整视频中的起点是 {start:.1f} 秒。

只依据画面与声音中确实出现的信息作答,不要用常识补全。

只输出 JSON,不要输出其它文字:

{{

"answer": "字符串;没有相关信息时填 未提及",

"evidence": [

{{"t_start": 相对片段起点的秒数, "t_end": 秒数, "quote": "画面或台词要点"}}

],

"confidence": "high | medium | low"

}}

问题:{question}

"""

```

拿到结果后立刻把相对时间换成绝对时间,并做基本校验:

```python

def normalize(chunk, result):

claims = []

for ev in result.get("evidence", []):

ts, te = float(ev["t_start"]), float(ev["t_end"])

if not (0 <= ts <= te <= (chunk["end"] - chunk["start"]) + 1):

continue # 时间戳越界,丢弃

claims.append({

"text": ev.get("quote", ""),

"t_start": chunk["start"] + ts,

"t_end": chunk["start"] + te,

"chunk_id": chunk["id"],

"confidence": result.get("confidence", "medium"),

})

return claims

```

步骤 7:生成可引用摘要

分两轮。第一轮逐片段产出要点,第二轮做归并。归并时不要把所有片段原文塞进一次请求,只传"时间区间 + 要点",上下文压力小很多。

```python

MERGE_PROMPT = """以下是一段长视频各片段的要点,已按时间排序。

请合并成一份结构化摘要,每条结论必须携带原样的时间区间,不要改写时间。

输出 JSON:{"sections":[{"title":"小标题","claims":[{"text":"...","t_start":秒,"t_end":秒}]}]}

要点列表:

{bullets}

"""

```

归并完做一次引用校验:每条结论的时间区间必须落在某个已知片段范围内,否则标记为"未证实"并从正式摘要里剔除。

```python

def verify(claims, clip_ranges, tol=2.0):

kept, dropped = [], []

for c in claims:

ok = any(

r["start"] - tol <= c["t_start"] and c["t_end"] <= r["end"] + tol

for r in clip_ranges

)

(kept if ok else dropped).append(c)

return kept, dropped

```

重叠窗口会让同一件事出现两次。去重规则:同一主题且时间区间重叠超过一半时,保留起点更早的那条。

步骤 8:落成检索索引

用 SQLite 做一层轻量索引:全文检索负责关键词命中,向量负责语义命中,时间字段负责范围过滤。中文全文检索建议用 trigram 分词器,unicode61 对中文几乎不起作用。

```python

import sqlite3, numpy as np

conn = sqlite3.connect("video_index.db")

conn.executescript("""

CREATE TABLE IF NOT EXISTS clips(

video TEXT, chunk_id TEXT, start REAL, end REAL, path TEXT,

summary TEXT, PRIMARY KEY(video, chunk_id));

CREATE VIRTUAL TABLE IF NOT EXISTS clips_fts

USING fts5(chunk_id UNINDEXED, text, tokenize='trigram');

CREATE TABLE IF NOT EXISTS vecs(

chunk_id TEXT PRIMARY KEY, dim INTEGER, vec BLOB);

""")

def to_blob(v):

return np.asarray(v, dtype="float32").tobytes()

def from_blob(b):

return np.frombuffer(b, dtype="float32")

```

检索时三路合并打分:关键词命中、向量AI 词典:余弦相似度">余弦相似度、如果问题里出现"第 40 分钟"这类线索就加时间范围过滤。取前 3 到 5 个片段,再把原始片段喂给模型做一次精读,得到最终带时间戳的回答。

步骤 9:端到端跑一遍

```python

run.py(示意,省略 import)

video = "meeting_2024.mp4"

duration = probe_duration(video)

chunks = plan_chunks(duration, chunk=180, overlap=15)

1) 切片 + 抽帧(调用前面写好的 cut.sh / ffmpeg 命令)

2) 逐片段提问并归一化

for ch in chunks:

res = analyze(f"chunks/{ch['id']}.mp4",

PROMPT.format(dur=ch["end"] - ch["start"],

start=ch["start"],

question="这段里有没有讨论预算调整?"))

ch["claims"] = normalize(ch, parse_json(res["text"]))

3) 归并 + 校验 + 写索引

```

对 90 分钟会议录像,按 180 秒切片、15 秒重叠,得到约 33 个片段。串行跑完大致在一杯咖啡的时间内,并发上限设 2 到 3,别把限流打满。

常见坑与排错

时间戳整体偏移几秒。 九成是切片时用了 -c copy。改成重编码,并且在切片后用 ffprobe 回读实际时长写回清单,不要相信规划值。

模型说"未提及",被当成否定结论写进摘要。 "未提及"只代表这个片段里没找到,不代表事情没发生。归并阶段要把"未提及"的片段和"有结论"的片段分开处理。

结论重复出现两三次。 重叠窗口导致。按主题加时间重叠度去重,别指望模型自己发现。

返回的 JSON 解析失败。 提示词里明确"只输出 JSON",解析时先用正则抠出第一个完整花括号块,再做 json.loads;仍然失败就重试一次,别直接抛异常中断整批任务。

429 与 5xx。 指数退避是基础,并发数控制在 2 到 3。批量任务一定要做断点续跑:把每个片段的处理结果落盘,重跑时跳过已完成的。

片段超出模型可处理时长。 把 MAX_CLIP_SECONDS 设得比你以为的安全值再小一点,并且每次调用前用 ffprobe 校验实际时长。超长片段常常表现为静默截断,不报错,但后半段的内容全丢。

声音和画面不同步。 切片时音频单独重编码容易引入偏移,尽量让音视频用同一条命令输出,并检查 -ar、-ac 参数一致。

上传前的合规问题。 会议录像常含个人信息。上传前确认数据保留策略,必要时先做画面遮挡与音频静音处理。

下一步建议

先拿一段 5 分钟的视频把整条链路跑通,确认时间戳能对上原片,再扩到长视频。之后可以往三个方向走:一是做增量索引,新视频进来只处理新增片段;二是把引用做成可点击的时间轴跳转,接进播放器;三是建一个小评测集,人工标注 20 个问题和标准时间区间,每次调整切片长度、帧率或提示词后重跑一遍,用命中率来判断改动是否真的有效。切片粒度和帧采样率这两个参数对结果影响较大,值得单独做几组对照实验。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。