文章

视频上传、FFmpeg 转码和 MinIO 存储如何串起来

语之溪

·

Java

·

⚠️ 本文最后更新于2026年02月14日,已经过了191天没有更新,若内容或图片失效,请留言反馈

视频上传不是把一个文件接收下来就结束了。平台还要校验文件、保存原始对象、提取媒体信息、转码、生成封面、更新业务状态,并在任何一步失败时留下可恢复的记录。

在 HAOVP 的内容链路中,我把业务记录、MinIO 对象存储和 FFmpeg 媒体处理串在一起。三者职责不同:数据库记录业务状态,MinIO 保存文件对象,FFmpeg 负责读取和转换媒体内容。

一条完整的上传链路

客户端选择文件
    -> 创建上传任务
    -> 校验文件信息
    -> 上传到临时对象
    -> 保存业务记录
    -> FFmpeg 探测媒体
    -> 转码与封面提取
    -> 保存处理后对象
    -> 更新视频状态
    -> 前端查询处理结果

这条链路既可以同步实现,也可以拆成异步任务。视频文件较大、转码耗时不可控时,异步处理更容易避免接口长时间占用连接。

先创建业务记录还是先上传文件

两种顺序都有需要处理的问题。

先创建业务记录:

  • 可以立即获得任务 ID;
  • 方便记录上传进度;
  • 上传失败时会留下待清理记录。

先上传文件:

  • 数据库不会提前产生业务记录;
  • 文件上传后创建记录失败,会留下孤立对象。

我更关注每一步都能关联到同一个任务标识,并设计清理机制,而不是认为某种顺序天然不会产生垃圾数据。

文件校验不能只看扩展名

客户端传来的文件名不可信。后端至少需要检查:

  • 文件是否为空;
  • 大小是否超过限制;
  • 扩展名是否允许;
  • MIME 类型是否合理;
  • 实际文件头是否符合格式;
  • 文件名是否需要重新生成;
  • 用户是否有上传权限;
  • 当前配额是否允许继续上传。

扩展名和 MIME 类型都可能被伪造。真正进入媒体处理前,还需要让 FFmpeg 或探测工具确认文件能否解析。

MinIO 中怎样组织对象

对象存储更适合按 Bucket 和对象键管理文件,而不是让业务代码依赖服务器本地路径。

一个脱敏的对象键结构可以是:

videos/{date}/{taskId}/source.mp4
videos/{date}/{taskId}/output.mp4
covers/{date}/{taskId}/cover.jpg

这是通用示例,不对应 HAOVP 的真实 Bucket、目录和地址。

对象键应由服务端生成,避免直接使用用户文件名。数据库中可以保存对象键和必要元数据,不应把存储凭据、内部端点和临时签名地址作为永久业务字段。

FFprobe 先读取媒体信息

在转码前,可以先使用 FFprobe 获取:

  • 容器格式;
  • 视频和音频编码;
  • 时长;
  • 分辨率;
  • 帧率;
  • 码率;
  • 流数量。
ffprobe -v error -show_streams -show_format input.mp4

命令是通用示例。实际执行时应通过受控参数调用,不把用户输入直接拼接成 Shell 命令。

探测失败时,任务应进入明确的失败状态,并记录脱敏后的错误摘要,而不是继续尝试播放一个无法识别的对象。

FFmpeg 转码的目标

转码不只是换扩展名。它可能涉及视频编码、音频编码、分辨率、码率和容器格式。

一个简化示例:

ffmpeg -i input.mp4 \
  -c:v libx264 \
  -c:a aac \
  -movflags +faststart \
  output.mp4

+faststart 可以把 MP4 播放需要的元数据移动到文件前部,便于渐进播放。具体编码参数需要根据输入文件、资源和质量目标调整,不能直接把示例作为所有视频的固定方案。

封面怎样生成

封面可以由用户上传,也可以从视频中截取。

ffmpeg -ss 00:00:03 -i input.mp4 -frames:v 1 cover.jpg

固定截取第几秒不一定合适:短视频可能没有该时间点,开头也可能是黑屏。后续可以根据视频时长选择位置,或者允许用户从候选帧中选择。

生成封面后,需要把图片保存到对象存储,并更新业务记录中的封面对象键。

处理状态要明确

前端不能只看到“上传成功”,因为文件进入 MinIO 后,转码可能仍在进行。

可以设置类似状态:

CREATED
UPLOADING
UPLOADED
PROCESSING
READY
FAILED

具体名称可以调整,重点是区分上传和媒体处理。只有 READY 状态的视频才进入正常播放和公开展示。

失败状态还应该保存失败阶段,例如上传、探测、转码、封面或对象写入,方便判断能否重试。

异步任务与消息

上传接口完成后,可以提交异步处理任务:

保存上传完成状态
    -> 提交视频处理事件
    -> 消费者执行 FFmpeg
    -> 上传输出文件
    -> 更新最终状态

消息成功发送不代表媒体处理成功。需要考虑:

  • 消息重复消费;
  • 消费者中途退出;
  • 同一任务被并发处理;
  • 转码成功但状态更新失败;
  • 状态成功但对象上传失败;
  • 重试是否覆盖已有对象。

任务处理要具备幂等性,并允许根据任务状态重新执行安全步骤。

临时文件与磁盘空间

FFmpeg 通常需要本地输入或输出文件。即使最终对象存储在 MinIO,处理节点仍可能使用临时目录。

需要关注:

  • 临时文件名是否唯一;
  • 任务完成或失败后是否清理;
  • 磁盘空间是否足够;
  • 多任务并发是否造成磁盘耗尽;
  • 服务重启后怎样清理遗留文件;
  • 文件权限是否限制其他进程访问。

不能只清理成功任务,异常路径更容易留下大文件。

删除和失败补偿

一个视频可能对应原始文件、转码文件、封面和数据库记录。删除时需要明确顺序和失败处理。

标记删除中
    -> 禁止继续展示
    -> 删除或安排清理对象
    -> 处理互动与关联数据
    -> 更新删除结果

若对象删除失败,可以记录待清理任务,而不是让业务接口无限等待。反过来,也不能先删除数据库记录后失去对象键,导致存储中的文件难以追踪。

下载和播放地址

对象存储内部地址不应直接暴露给所有客户端。可以根据部署方式选择:

  • 由网关或文件服务代理;
  • 生成短期签名地址;
  • 通过受控公开 Bucket;
  • 配合 CDN 使用受限源站。

无论哪种方式,都不应在日志和文章中公开真实端点、Access Key、Secret Key 和永久签名链接。

如何验证链路

我会分别检查:

  • 合法视频能够上传并进入处理;
  • 非法格式和超大文件被拒绝;
  • FFprobe 失败时状态正确;
  • 转码和封面对象可读取;
  • 失败任务可以安全重试;
  • 重复消息不会产生多份业务记录;
  • 临时文件能够清理;
  • 未完成视频不会被正常展示;
  • 删除后对象与业务状态一致。

视频上传、FFmpeg 转码和 MinIO 存储串起来以后,真正需要维护的是一条有状态、可重试、可清理的处理链路。数据库说明任务处于哪里,MinIO 保存对象,FFmpeg 完成媒体处理;每一步都要能够成功确认,也要能够在失败后找到恢复入口。

现在已有 9 次阅读,0 条评论,0 人点赞
评论:共0条
发表
搜索 消息 足迹 排行
你还不曾留言过..
你还不曾留下足迹..
博主 不再显示
博主
未知作品 歌曲封面
立即安装