返回博客

视频作为可查询数据库:VideoDB 的工程思路

我们习惯把视频当作文件来处理:下载、存储、播放。但 VideoDB 提供了另一种看待方式——把视频当作一个可查询的数据库:向里面打索引,用自然语言搜特定片段,然后把搜索结果直接剪成片段流返回。

这个范式转换有点意思,值得写下来。

「找到 → 剪出 → 播放」三步合一

传统的视频处理链路很重:下载文件、本地转码、用 ffmpeg 截取时间段、生成新文件、再部署。每一步都是运维成本。

VideoDB 把这三步压缩成了一个 API 调用链:

video.index_spoken_words(force=True)
results = video.search("产品演示环节")
stream_url = results.compile()

compile() 返回的不是本地文件路径,而是一个即时可播放的 HLS 流地址。你不需要在本地生成任何中间文件——「找到片段」和「播放片段」之间没有编码等待。

这对 Agent 场景尤其有价值:AI 在分析视频内容时,可以把「找到了什么」直接作为可验证的链接证据,而不是一个抽象的时间戳区间。

索引策略:语义、视觉、关键词三层分离

VideoDB 有三种独立的索引类型,刚上手时容易混淆。我的理解是:

  • spoken word 索引:对音轨做转录,按语义搜索说了什么。适合演讲、访谈、教程类视频。
  • scene 索引:对画面做视觉理解,搜索「白板上有人在写字」「有人在演示操作界面」这类视觉事件。
  • keyword 索引:精确匹配,适合需要找到特定词汇出现位置的场景。

实践中最需要注意的是:index_scenes() 没有 force 参数,重复调用会抛出异常,错误信息里藏着已有索引的 ID。所以正确的写法是先尝试建索引,失败了就从错误里解析 ID:

try:
    scene_index_id = video.index_scenes(...)
except Exception as e:
    match = re.search(r"id\s+([a-f0-9]+)", str(e))
    if match:
        scene_index_id = match.group(1)
    else:
        raise

这是个典型的「把异常当信号量用」的设计,不算优雅,但了解之后就不会踩坑。

时间轴编辑的心法:先验证再组合

VideoDB 的 Timeline API 允许用编程方式组合多个片段、叠加字幕和音轨,生成一条复合流。但它有一个静默失败的陷阱:负数时间戳会被接受但产出损坏的流,不会报错。

# 危险写法——start 若来自外部计算,可能为负
timeline.add_inline(VideoAsset(asset_id=video.id, start=start, end=end))
 
# 安全写法——建议在组装前做边界检查
assert start >= 0, f"start 不能为负: {start}"
assert start < end, f"start 必须小于 end"
assert end <= video.length, f"end 超出视频长度: {video.length}"

这个规则和数据库写入前的参数校验本质相同:不要依赖下游系统来捕捉非法输入,在数据入口就把它挡住。Timeline 是一个声明式的配置对象,一旦生成了损坏的流,调试成本远高于入口校验。

搜索空结果的处理

VideoDB 的 video.search() 在没有命中时会抛 InvalidRequestError,而不是返回空列表。这是个反直觉的设计,需要显式捕获并判断消息内容再决定是「空结果」还是「真正的错误」。处理时要把「No results found」当作正常分支,而不是异常路径。

本地工具与云端处理的边界

最后一个我认为重要的工程判断:什么时候用 VideoDB,什么时候用 ffmpeg

VideoDB 在服务器端处理转码、截图、字幕烧录、音轨叠加、画面重构——这些操作发一个 API 请求就可以异步完成,适合不需要实时结果的批量处理场景。但它不支持速度变更、视频过渡效果、颜色调整、局部裁切缩放。这些操作需要本地工具来做。

边界清晰之后,工程决策就变得简单:凡是 VideoDB 覆盖的操作,就不要在本地跑 ffmpeg 转码,否则既浪费算力,又引入了本地环境依赖。只有 VideoDB 明确不支持的操作才回落到本地工具。

这和选择任何云服务的逻辑是一样的:先弄清楚边界,再做工具选型,而不是用熟悉的工具解决所有问题。

一句话心法

把视频当数据库来建模,搜索即剪辑,剪辑即播放——减少的不只是代码量,而是「从找到内容到验证内容」之间的认知摩擦。