前言

传统在线视频压缩工具需要把文件上传到服务器,用户等待上传 → 服务器处理 → 再下载,流程长、服务器成本高,还存在隐私泄露风险。

本文介绍如何利用 FFmpeg 的 WebAssembly 版本,在浏览器本地实现视频压缩,**文件全程不上传服务器**。

技术选型

- **@ffmpeg/ffmpeg**:FFmpeg 的 WASM 编译版本,在浏览器中运行完整的 FFmpeg 命令行
- **@ffmpeg/util**:工具库,提供 `fetchFile` 等便捷方法
- **SharedArrayBuffer**:WASM 多线程支持,提升编解码性能

核心实现

1. 初始化 FFmpeg 引擎

import { FFmpeg } from '@ffmpeg/ffmpeg'
import { fetchFile } from '@ffmpeg/util'

const ffmpeg = new FFmpeg()
await ffmpeg.load()

FFmpeg WASM 核心文件约 31MB,首次需要从 CDN 加载,之后会被浏览器缓存。

2. 视频信息读取

压缩前需要获取视频的宽高、时长等信息,用于计算压缩参数:

async function getVideoInfo(file: File) {
  const inputName = 'input' + getExtension(file.name)
  await ffmpeg.writeFile(inputName, await fetchFile(file))
  await ffmpeg.exec(['-i', inputName])
  // 从 stderr 中解析视频元数据
}

3. 自适应压缩算法

核心思路:根据目标文件大小反推需要的编码参数。

async function compressToTargetSize(file: File, targetSizeMB: number) {
  /* 1. 读取视频信息 */
  const info = await getVideoInfo(file)
  
  /* 2. 根据目标大小计算目标比特率 */
  const targetBitrate = Math.floor((targetSizeMB * 8192) / info.duration)
  
  /* 3. 根据压缩率动态决定是否缩小分辨率 */
  const ratio = targetSizeMB / (file.size / 1024 / 1024)
  let scaleFilter = ''
  if (ratio < 0.1) scaleFilter = 'scale=640:360'
  else if (ratio < 0.4) scaleFilter = 'scale=1280:720'
  
  /* 4. 构建 FFmpeg 命令 */
  const args = ['-i', inputName, '-c:v', 'libx264']
  if (scaleFilter) args.push('-vf', scaleFilter)
  args.push('-b:v', `${targetBitrate}k`, '-y', outputName)
  
  /* 5. 执行压缩 */
  await ffmpeg.exec(args)
  
  /* 6. 读取结果 */
  const data = await ffmpeg.readFile(outputName)
  return new Blob([data])
}

4. 同等画质优先复制(关键优化)

如果源文件的目标格式与源编码一致,可以直接**复制视频流**,实现秒级处理:

// MP4 → MP4 转换时,尝试直接复制视频流
if (isSameContainer) {
  await ffmpeg.exec(['-i', inputName, '-c:v', 'copy', '-c:a', 'copy', '-y', outputName])
}

5. 进度追踪

通过监听 FFmpeg 的日志输出,解析处理进度:

ffmpeg.on('progress', ({ progress, time }) => {
  console.log(`压缩进度: ${(progress * 100).toFixed(1)}%`)
})

完整 Composables 封装

将上述逻辑封装为 Vue Composables,方便在组件中复用:

export function useVideoCompress() {
  const isProcessing = ref(false)
  const progress = ref({ percent: 0, time: 0 })
  const error = ref<string | null>(null)

  async function compressAuto(file: File): Promise<Blob> {
    isProcessing.value = true
    error.value = null
    // ... 上述核心逻辑
  }

  return { isProcessing, progress, error, compressAuto }
}

组件中使用

<script setup>
import { useVideoCompress } from './composables/useVideoCompress'
const { isProcessing, progress, error, compressAuto } = useVideoCompress()

async function handleCompress(file) {
  const blob = await compressAuto(file)
  const url = URL.createObjectURL(blob)
  // 提供给用户下载
}
</script>

部署注意

FFmpeg WASM 需要 `SharedArrayBuffer` 支持,服务器响应头需配置:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Vite 开发环境可在 `vite.config.ts` 中配置:

export default defineConfig({
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp'
    }
  }
})

总结

纯前端 WASM 方案相比传统服务端方案有几个明显优势:

对比项 纯前端 WASM 传统服务端
文件上传 不上传 必须上传
隐私安全 文件不离设备 存储在第三方
服务器成本
文件大小限制 通常有限制

这套方案已在 91AI工具 生产环境中运行,支持 MP4/WebM/MOV/AVI/MKV 等格式的在线压缩,完全免费使用。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐