You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Amazon S3中合并多个MediaRecorder视频分片为单个WebM文件?

合并S3中WebM视频分片的方案

核心思路

MediaRecorder每秒生成的WebM分片都带有独立文件头,直接二进制拼接会导致播放异常。推荐用FFmpeg工具完成合并,步骤如下:

  • 按录制顺序下载S3目标文件夹内的所有分片到服务器临时目录
  • 用FFmpeg将分片合并为标准单文件WebM
  • 将合并后的文件上传回S3(可选:清理原分片文件)
  • 清除服务器临时文件

具体实现(Node.js)

1. 安装依赖

先安装所需工具包:

npm install @aws-sdk/client-s3 fluent-ffmpeg fs-extra

2. 代码实现

const { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectsCommand } = require('@aws-sdk/client-s3');
const ffmpeg = require('fluent-ffmpeg');
const fs = require('fs-extra');
const path = require('path');

// 初始化S3客户端
const s3Client = new S3Client({
  region: '你的S3区域',
  credentials: {
    accessKeyId: '你的AccessKey',
    secretAccessKey: '你的SecretKey'
  }
});

async function mergeWebMChunks(bucketName, folderPrefix, outputS3Key) {
  // 1. 拉取目标文件夹下的所有分片文件
  const listCmd = new ListObjectsV2Command({
    Bucket: bucketName,
    Prefix: folderPrefix // 示例:"recordings/user_123/"
  });
  const listResult = await s3Client.send(listCmd);
  const chunkObjects = listResult.Contents.filter(obj => !obj.Key.endsWith('/'));

  // 2. 按分片序号排序(需匹配你的分片命名规则,示例为提取part后的数字)
  chunkObjects.sort((a, b) => {
    const partA = parseInt(a.Key.match(/part(\d+)/)[1]);
    const partB = parseInt(b.Key.match(/part(\d+)/)[1]);
    return partA - partB;
  });

  // 3. 创建临时目录
  const tempDir = path.join(__dirname, `temp_merge_${Date.now()}`);
  await fs.ensureDir(tempDir);

  try {
    // 4. 批量下载分片到本地
    const downloadTasks = chunkObjects.map(async (obj, idx) => {
      const tempPath = path.join(tempDir, `chunk_${idx}.webm`);
      const getCmd = new GetObjectCommand({ Bucket: bucketName, Key: obj.Key });
      const s3Res = await s3Client.send(getCmd);
      await fs.writeFile(tempPath, s3Res.Body);
      return tempPath;
    });
    const localChunkPaths = await Promise.all(downloadTasks);

    // 5. 用FFmpeg合并分片(用文件列表避免命令行参数过长)
    const outputTempPath = path.join(tempDir, 'merged_final.webm');
    const listFilePath = path.join(tempDir, 'chunk_list.txt');
    const listContent = localChunkPaths.map(p => `file '${p}'`).join('\n');
    fs.writeFileSync(listFilePath, listContent);

    await new Promise((resolve, reject) => {
      ffmpeg()
        .input(listFilePath)
        .inputOptions('-f', 'concat')
        .output(outputTempPath)
        .outputOptions('-c', 'copy') // 直接复制流,不重新编码,速度快
        .on('end', resolve)
        .on('error', reject)
        .run();
    });

    // 6. 上传合并后的文件到S3
    const mergedFile = await fs.readFile(outputTempPath);
    const putCmd = new PutObjectCommand({
      Bucket: bucketName,
      Key: outputS3Key, // 示例:"recordings/user_123/final.webm"
      Body: mergedFile,
      ContentType: 'video/webm'
    });
    await s3Client.send(putCmd);

    // 7. 可选:删除原分片文件
    const deleteCmd = new DeleteObjectsCommand({
      Bucket: bucketName,
      Delete: {
        Objects: chunkObjects.map(obj => ({ Key: obj.Key }))
      }
    });
    await s3Client.send(deleteCmd);

    console.log('合并完成,文件已上传至S3');
  } catch (err) {
    console.error('合并失败:', err);
    throw err;
  } finally {
    // 8. 清理临时文件
    await fs.remove(tempDir);
  }
}

// 调用示例
mergeWebMChunks(
  'your_bucket_name',
  'recordings/user_123/',
  'recordings/user_123/final.webm'
);

关键注意事项

  • 分片排序:必须严格按录制顺序合并,否则视频会错乱,需根据你的分片命名规则调整排序逻辑
  • FFmpeg参数:-c copy参数直接复用原有编码,合并速度极快且无画质损失
  • 临时文件清理:无论合并成功或失败,都要清理临时目录,避免服务器磁盘被占用
  • 无FFmpeg替代方案:若无法安装FFmpeg,需手动解析WebM的Matroska结构,移除除第一个分片外的所有文件头再拼接,但兼容性差,不推荐

内容的提问来源于stack exchange,提问作者KaZaT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 00:37:45