前言:
最近接了个需求:用户传一个两小时的会议视频,系统得自动生成带时间戳的大纲。最骚的是,用户点击大纲标题,视频得立马跳到对应位置播放。
乍一看是“调包侠”的活儿,但真做起来,从GB级大文件上传到精准的时间戳对齐,全是细节。今天把这套全链路的技术方案开源出来,架构基于 FastAPI + Vue3,核心模型用了 vLLM (Qwen3) 和 WhisperX。
🏗️ 第一关:搞定“巨型”视频上传(分片+断点续传)
视频不同于图片,动不动就 1GB 起步。直接一个 POST 请求甩过去,Nginx 肯定报错,用户网络稍微抖一下就得重来。
核心方案:前端切片 + 并行上传 + 后端合并
1. 前端切片 (The Slice)
利用 HTML5 File.prototype.slice 方法。这不是什么高深技术,就是把大文件切成小二进制块(Chunk)。
// 前端伪代码 (Vue3/React)
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 一片
const chunks = [];
let cur = 0;
while (cur < file.size) {
chunks.push(file.slice(cur, cur + CHUNK_SIZE));
cur += CHUNK_SIZE;
}
2. 关键点:文件指纹 (MD5)
怎么知道这个分片属于哪个文件?怎么做秒传?
不要算全量 MD5! 几个 G 的文件在浏览器算 MD5 会把页面卡死。
技巧:取文件头 2MB + 文件尾 2MB + 中间取几段,算一个“抽样 MD5”。虽然理论上有碰撞风险,但在业务场景下足够唯一且飞快。
3. 后端合并 (The Merge)
后端(FastAPI)接收到所有切片后,需要进行合并。
- 坑点:不要在内存里合并!内存会爆。
-
做法:使用
append模式写入文件流,或者利用 Linux 的cat命令(如果是在 OS 层面处理)。
👂 第二关:听懂视频(ASR 与 强制对齐)
视频传上来了,下一步是提取字幕。
大家第一反应是 OpenAI 的 Whisper。没错,但原版 Whisper 有个致命弱点:时间戳经常漂移,或者只给出一大段话的模糊时间,没法做到“精准跳转”。
技术选型:WhisperX
我们需要引入 WhisperX。它在 Whisper 之后加了一个 Forced Alignment(强制对齐) 模型(基于 Wav2Vec2.0),能把时间戳精确到“单词级”或“音素级”。
处理流程:
-
音频提取:用
ffmpeg把视频里的音频抽出来(mp3/wav)。 - VAD(语音活动检测):WhisperX 会先切除静音片段,提升识别效率。
- 转录 + 对齐:
import whisperx
# 1. 加载模型 (显存够直接上 large-v3)
model = whisperx.load_model("large-v3", device, compute_type="float16")
audio = whisperx.load_audio(audio_file)
# 2. 转录 (Batch inference,速度飞快)
result = model.transcribe(audio, batch_size=16)
# 3. 关键步骤:强制对齐 (解决时间戳不准的问题)
model_a, metadata = whisperx.load_align_model(language_code=result["language"], device=device)
result = whisperx.align(result["segments"], model_a, metadata, audio, device, return_char_alignments=False)
# result['segments'] 现在包含了极其精准的 start 和 end 时间戳
🧠 第三关:理解与大纲生成 (LLM + Dify)
拿到了带有时间戳的几十页逐字稿,直接扔给用户看是没用的。我们需要 LLM(Qwen3-32B)来做带时间戳的语义摘要。
这里我用 Dify 做编排,或者直接代码调用 vLLM 接口。
Prompt Engineering (提示词工程) 是核心:
你不能只说“总结全文”,你必须要求它“保留锚点”。
System Prompt:
你是一个专业的视频内容导读助手。你的任务是阅读带有时间戳的字幕文本,并生成一份结构化的大纲。
要求:
- 按逻辑段落划分章节。
- 必须从原文中提取该章节第一句话的起始时间戳作为该章节的跳转点。
- 输出严格的 JSON 格式:
[{"title": "...", "summary": "...", "timestamp": 12.5}, ...]输入文本示例:
[00:10] 今天我们讲微服务... [00:15] 首先看架构图...
难点处理(Context Window):
如果视频长达 2 小时,字幕 token 可能会撑爆上下文。
- 方案 A:利用 Qwen2.5/3 的长窗口(32k/128k),通常够用。
- 方案 B:Map-Reduce。先按 10 分钟切片总结,最后再把总结汇聚。
🖱️ 第四关:全链路打通 (点击跳转)
终于到了前端展示环节。左边播放器,右边大纲。
核心逻辑:
用户点击大纲 -> 获取 timestamp -> 修改 <video> 标签的 currentTime。
代码实现 (Vue3 + TypeScript 示例):
<template>
<div class="container">
<video
ref="videoPlayer"
controls
src="/api/videos/stream/v123.mp4"
class="w-full"
></video>
<div class="outline-list">
<div
v-for="item in outlineData"
:key="item.timestamp"
@click="seekTo(item.timestamp)"
class="outline-item"
>
<span class="time">{{ formatTime(item.timestamp) }}</span>
<span class="title">{{ item.title }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const videoPlayer = ref<HTMLVideoElement | null>(null);
// 核心跳转逻辑
const seekTo = (seconds: number) => {
if (videoPlayer.value) {
// 1. 设置时间
videoPlayer.value.currentTime = seconds;
// 2. 自动播放 (提升体验)
videoPlayer.value.play();
}
};
// 辅助函数:把秒数转成 00:12:30 格式
const formatTime = (s: number) => { /* ... */ }
</script>
🚀 总结与架构图
要实现这个功能,千万别把所有逻辑都写在一个接口里。建议引入消息队列(如 RabbitMQ/Redis + Celery/Dramatiq)。
最终的异步处理流:
-
API: 接收文件切片 -> 合并完成 -> 写入数据库状态
status: UPLOADED-> 发送消息到 MQ。 -
Worker A (GPU): 消费消息 -> 运行 WhisperX -> 生成
json_transcript-> 存库 -> 发送消息到 MQ。 -
Worker B (CPU/LLM): 消费消息 -> 组装 Prompt -> 调用 Qwen/Dify -> 生成
json_outline-> 存库status: DONE。 - Frontend: 轮询或 WebSocket 监听状态 -> 渲染页面。
这就是从“传视频”到“点大纲”的全貌。技术不难,难的是把 WhisperX 的环境配好(依赖坑很多),以及 Prompt 的调优。
祝大家开发愉快,早日把那些又臭又长的会议视频变成清爽的思维导图!









网友评论