视频 9 宫格预览
均匀截取 N 帧拼接为预览图 · Canvas 本地渲染
contact sheet/等距抽帧合成预览图
均匀截取 N 帧拼接为预览图 · Canvas 本地渲染
了解工具定位 · 使用场景 · 对比优势
视频剪辑师或自媒体创作者面对大量长视频素材,逐一拖入播放器查看费时费力。本工具自动等距抽帧合成 9 宫格预览图,一眼看清每个视频的关键画面、构图和内容转折点,快速定位需要的片段,将素材筛选时间从半小时缩短到几分钟。
在线教育平台运营者需要为数十节录播课生成封面或内容索引。本工具对每节课视频等距提取 9 帧,合成一张预览图,直观展示课程从引入到总结的完整知识脉络。学生可凭预览图判断课程难度和节奏,运营者也能批量生成标准化的课程缩略图。
安防管理员需从 24 小时监控录像中查找特定事件,逐秒回放效率极低。本工具将长视频压缩为 9 帧时间轴预览,异常画面(如人员走动、车辆进出)在抽帧中一目了然。管理员仅需浏览几张预览图就能锁定可疑时间点,大幅提升检索效率。
电商运营需要为产品视频制作详情页的“视频看点”模块,手动截图拼接耗时且不统一。本工具自动从产品演示视频中等距抽取 9 帧,合成一张展示产品外观、功能、使用场景的完整预览图,可直接用于商品主图或详情页,提升用户浏览体验。
短视频创作者发布作品前,常需从视频中挑选一张最具吸引力的帧作为封面。本工具提供 9 张等距抽帧结果,创作者无需手动拖动进度条寻找精彩瞬间,直接从中选择构图最优、信息最丰富的画面作为封面,提高视频点击率。
| 维度 | 本工具 | 竞品 A | 传统方法 |
|---|---|---|---|
| 数据隐私 | 纯浏览器/WASM 处理,视频不上传服务器 | 上传到服务器处理 | 依赖工作人员操作,存在数据泄露风险 |
| 处理速度 | 秒级,取决于本地设备性能 | 取决于服务器负载和网络传输速度 | 数小时,需人工截取和排版 |
| 离线可用 | 支持,完全离线运行 | 不支持,必须联网 | 支持,但需要专业软件和操作技能 |
| 费用 | 免费,无使用次数限制 | 免费/付费,通常有次数或功能限制 | 需购买专业软件(如 Adobe Premiere)或付费外包 |
| 操作门槛 | 上传视频即生成,无学习成本 | 需注册账号,上传后等待 | 需掌握视频剪辑和图像处理软件 |
| 输出格式 | PNG/JPG 图片,可自定义网格行列数 | PNG/JPG 图片,部分支持自定义 | 取决于软件,通常为图片格式 |
| 视频格式支持 | 主流格式(MP4, MOV, AVI, WebM 等) | 主流格式,部分限制文件大小 | 支持所有格式,但需解码器 |
| 批量处理 | 单次单视频 | 部分支持批量上传 | 可批量处理,但操作繁琐 |
上手步骤 · 输入输出 · 避坑提示
| 输入 | 输出 | 说明 |
|---|---|---|
| https://example.com/video.mp4 | 生成一张 3x3 的预览图,包含 9 个等距抽帧画面,每个画面带时间戳水印 | 典型场景:在线视频 URL 直接输入 |
| 本地文件上传(10 秒短视频) | 生成一张 3x3 的预览图,9 帧均匀分布在 0-10 秒区间,每帧间隔约 1.1 秒 | 典型场景:短时长视频,抽帧间隔自动适配 |
| 本地文件上传(2 小时长视频) | 生成一张 3x3 的预览图,9 帧均匀分布在 0-7200 秒区间,每帧间隔约 800 秒 | 边界 case:长视频仍只抽 9 帧,帧间距大 |
| 视频分辨率 1920x1080 | 预览图总尺寸 1920x1080(原分辨率),每个小格 640x360,排列为 3x3 | 典型场景:高清视频,预览图保持原比例 |
| 视频分辨率 320x240 | 预览图总尺寸 320x240(原分辨率),每个小格约 106x80,排列为 3x3 | 边界 case:低分辨率视频,小格可能模糊 |
| 视频时长不足 1 秒(0.5 秒) | 生成一张 3x3 的预览图,9 帧全部取自视频唯一可用的关键帧(重复帧) | 易错 case:超短视频,9 帧可能完全相同 |
| 视频仅 1 帧(静态图片格式伪装为视频) | 生成一张 3x3 的预览图,9 格全部为同一帧画面 | 易错 case:用户误传静态图片,工具仍输出 9 宫格 |
上传一个 .jpg 或 .mp3 文件上传 .mp4 / .mov / .avi / .mkv 等视频文件工具底层用 FFmpeg 解码视频流,图片和音频文件没有视频帧序列,抽帧会失败或输出空白
上传一段 0.5 秒的视频视频时长至少 1 秒以上(推荐 ≥ 3 秒)等距抽帧算法将时长除以 9 得到帧间隔;若时长 < 1 秒,帧间隔 < 0.11 秒,部分编码器可能输出重复帧或黑帧
frames=8.5 或 frames=-3frames=9(整数,1~99)抽帧数量必须是正整数;小数会被 FFmpeg 截断取整,负数直接导致参数解析错误
width=1920, height=1080,但原始视频是 640×480保持宽高比,宽度不超过原始宽度(如 640×480 视频设 width=640)FFmpeg 可以放大但会严重模糊;工具未做超分辨率,放大后画质劣化明显
上传一个 2GB 的 4K 视频先压缩或裁剪视频,或使用浏览器端 WASM 模式(受浏览器内存限制)后端 Go 处理有内存上限;超大文件会导致 OOM 或超时。浏览器 WASM 模式受 WebAssembly 内存限制(默认 256MB~2GB)
上传一个 .webm 文件(VP9 编码)使用 H.264 / H.265 编码的 .mp4 文件FFmpeg 支持解码 VP9/AV1,但浏览器原生解码器可能不支持;后端处理无问题,但浏览器预览可能黑屏
以为工具会生成一段 9 帧的 GIF 或短视频工具输出一张拼接了 9 帧缩略图的静态 PNG/JPG 图片contact sheet 是静态拼图,不是动画;若需要动态预览,请使用 GIF 或视频截取工具
视频内容变化极快(如 1 秒内场景切换 10 次),只抽 9 帧增加抽帧数量(如 18 帧)或分段处理等距抽帧固定间隔,快速变化的内容可能漏掉关键场景;增加帧数可提高采样密度
公式推导 · 流程图解 · 依据出处
W = ceil(T / (R × 9))
W — 合成预览图的总宽度(像素)T — 视频总帧数(总时长 × 帧率)R — 抽帧间隔(每 R 帧取一帧)9 — 固定值,9 宫格每行 3 列共 9 格一段 30 秒视频,帧率 25fps,总帧数 T=750。设定抽帧间隔 R=10(每 10 帧取一帧),则抽得帧数 = 750/10=75 帧。9 宫格每格宽 100px,则 W = ceil(75 / 9) × 100 = 9 × 100 = 900px。最终输出 900×300px 的 3 行 3 列预览图。
适用于等距抽帧合成 contact sheet 场景。当视频时长极短(<1秒)或抽帧间隔过大导致不足 9 帧时,工具会自动补黑帧或调整间隔,公式仅作基础布局计算。
3 种主流语言 · 复制即用
import subprocess
import json
# 使用 FFmpeg 生成 3x3 等距抽帧预览图(contact sheet)
# 输入:本地视频文件路径
# 输出:合成后的预览图文件
def generate_contact_sheet(video_path: str, output_path: str = "preview.jpg"):
# 先获取视频总时长(秒)
probe = subprocess.run([
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", video_path
], capture_output=True, text=True)
info = json.loads(probe.stdout)
duration = float(info["format"]["duration"])
# 等距取 9 帧的时间点(0 到 duration 之间均匀分布)
interval = duration / 10 # 留出首尾缓冲
timestamps = [interval * (i + 1) for i in range(9)]
# 用 select 滤镜按时间点抽帧,再 tile 合成 3x3
filter_str = "+".join([f"eq(n,{int(t*25)})" for t in timestamps])
filter_str = f"select='{filter_str}',setpts=N/FRAME_RATE/TB,tile=3x3"
cmd = [
"ffmpeg", "-i", video_path,
"-vf", filter_str,
"-frames:v", "1",
"-q:v", "2",
output_path
]
subprocess.run(cmd, check=True)
print(f"预览图已生成:{output_path}")
# 示例用法
generate_contact_sheet("input.mp4", "contact_sheet.jpg")
package main
import (
"fmt"
"os/exec"
"strconv"
"strings"
)
// 使用 FFmpeg 生成 3x3 视频预览图
// 输入:视频文件路径
// 输出:合成后的 JPG 文件
func generateContactSheet(videoPath, outputPath string) error {
// 1. 用 ffprobe 获取视频时长
probe := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", videoPath)
out, err := probe.Output()
if err != nil {
return fmt.Errorf("ffprobe 失败: %w", err)
}
// 简单解析 JSON 获取 duration(生产环境建议用 encoding/json)
durationStr := extractDuration(string(out))
duration, _ := strconv.ParseFloat(durationStr, 64)
// 2. 等距取 9 帧的时间点
interval := duration / 10
var timestamps []string
for i := 1; i <= 9; i++ {
t := interval * float64(i)
timestamps = append(timestamps, fmt.Sprintf("%.2f", t))
}
// 3. 用 select 滤镜 + tile 合成
selectExpr := "select='" + strings.Join(timestamps, "+") + "'"
filter := fmt.Sprintf("%s,setpts=N/FRAME_RATE/TB,tile=3x3", selectExpr)
cmd := exec.Command("ffmpeg", "-i", videoPath, "-vf", filter, "-frames:v", "1", "-q:v", "2", outputPath)
return cmd.Run()
}
// 辅助:从 JSON 中提取 duration 字段(简化版)
func extractDuration(jsonStr string) string {
start := strings.Index(jsonStr, `"duration":"`)
if start == -1 {
return "0"
}
start += len(`"duration":"`)
end := strings.Index(jsonStr[start:], `"`)
return jsonStr[start : start+end]
}
func main() {
err := generateContactSheet("input.mp4", "preview.jpg")
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("预览图已生成: preview.jpg")
}
// 浏览器端纯 JS 实现:用 Canvas 对视频等距抽帧合成 3x3 预览图
// 注意:需用户上传视频文件或提供 video 元素
async function generateContactSheet(videoFile) {
// 1. 加载视频到 video 元素
const video = document.createElement('video');
video.src = URL.createObjectURL(videoFile);
await video.play();
// 2. 获取视频时长,计算 9 个等距时间点
const duration = video.duration;
const interval = duration / 10;
const timestamps = Array.from({ length: 9 }, (_, i) => interval * (i + 1));
// 3. 创建 Canvas(3x3 网格,每格 160x90)
const cellW = 160, cellH = 90;
const canvas = document.createElement('canvas');
canvas.width = cellW * 3;
canvas.height = cellH * 3;
const ctx = canvas.getContext('2d');
// 4. 逐帧 seek 并绘制到对应位置
for (let i = 0; i < timestamps.length; i++) {
video.currentTime = timestamps[i];
await new Promise(resolve => video.onseeked = resolve);
const col = i % 3;
const row = Math.floor(i / 3);
ctx.drawImage(video, col * cellW, row * cellH, cellW, cellH);
}
// 5. 导出为图片
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
video.pause();
URL.revokeObjectURL(video.src);
return dataUrl; // 可直接用于 <img src="..."> 或下载
}
// 示例:在文件上传 change 事件中使用
// document.querySelector('input[type="file"]').onchange = async (e) => {
// const url = await generateContactSheet(e.target.files[0]);
// document.querySelector('img').src = url;
// };
7 个高频疑问
「9 宫格 / 拼接」下的其他工具