如何用Node.js fluent-FFmpeg将S3中MP4转HLS并存储至目标S3?
解决FFmpeg转HLS后将.m3u8和.ts分片同步到S3的方案
嘿,我之前做视频流媒体应用时也踩过这个坑——本地转HLS顺风顺水,一碰到S3就卡壳,毕竟FFmpeg本身没法直接把多个输出文件(m3u8+ts)直接写到S3。下面给你两个亲测有效的方案,按需选就行:
方案1:本地临时目录中转(最稳妥,首推)
这个思路就是把S3上的MP4先拉到本地临时目录,转码生成HLS文件后,再把所有相关文件批量上传回S3。好处是完全兼容你本地已经跑通的FFmpeg命令,只需要加一层S3的下载/上传逻辑。
具体步骤:
- 创建本地临时目录:用
tmp这类npm包生成临时文件夹,避免污染本地文件系统,转码完成后自动清理。 - 从S3下载源MP4:用AWS SDK(比如
@aws-sdk/client-s3)把视频下载到临时目录。 - FFmpeg转HLS:指定输出的.m3u8路径为临时目录下的文件,同时用
hls_segment_filename参数把.ts分片也生成在同一个临时目录里。示例代码如下:
const ffmpeg = require('fluent-ffmpeg'); const tmp = require('tmp'); const fs = require('fs'); const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); const s3Client = new S3Client({ region: '你的AWS区域' }); // 创建自动清理的临时目录 const tmpDir = tmp.dirSync({ unsafeCleanup: true }); const inputPath = `${tmpDir.name}/source.mp4`; const outputM3u8Path = `${tmpDir.name}/output.m3u8`; // 1. 从S3下载源视频到本地临时目录 async function downloadFromS3() { const params = { Bucket: '你的S3桶名', Key: '源MP4在S3中的路径,比如videos/upload/sample.mp4' }; const response = await s3Client.send(new GetObjectCommand(params)); const fileStream = fs.createWriteStream(inputPath); await new Promise((resolve, reject) => { response.Body.pipe(fileStream); fileStream.on('finish', resolve); fileStream.on('error', reject); }); } // 2. 用FFmpeg转码生成HLS文件 function transcodeToHLS() { return new Promise((resolve, reject) => { ffmpeg(inputPath) .outputOptions([ '-hls_time 10', // 每个分片10秒 '-hls_list_size 0', // 保留所有分片(不自动删除旧分片) `-hls_segment_filename ${tmpDir.name}/segment_%03d.ts` // 指定分片文件名格式 ]) .save(outputM3u8Path) .on('end', resolve) .on('error', reject); }); } // 3. 批量上传所有HLS文件到S3指定路径 async function uploadToS3() { const targetS3Prefix = 'HLS文件存储的S3前缀,比如videos/hls/sample/'; const files = fs.readdirSync(tmpDir.name); for (const file of files) { const filePath = `${tmpDir.name}/${file}`; const fileContent = fs.readFileSync(filePath); // 正确设置文件的Content-Type,否则播放器可能无法识别 const contentType = file.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/MP2T'; const params = { Bucket: '你的S3桶名', Key: `${targetS3Prefix}${file}`, Body: fileContent, ContentType: contentType }; await s3Client.send(new PutObjectCommand(params)); } } // 执行完整流程 async function processVideo() { try { await downloadFromS3(); await transcodeToHLS(); await uploadToS3(); console.log('转码并上传S3完成!'); } catch (err) { console.error('处理失败:', err); } finally { // 不管成功失败,都清理临时目录 tmpDir.removeCallback(); } } processVideo();
- 注意事项:
- 如果是服务器less环境(比如Lambda),要注意临时目录的大小限制(Lambda默认512MB),大视频转码可能需要挂载EFS扩展空间。
- 上传时一定要设置正确的
ContentType,否则前端播放器可能加载失败。
方案2:用S3FS挂载S3为本地文件系统(适合持续转码场景)
如果你的服务器是长期运行的,可以用s3fs把S3桶挂载成本地目录,这样FFmpeg就像操作本地文件一样直接把.m3u8和.ts写到挂载目录,自动同步到S3。不过这个方案依赖系统环境,稳定性不如方案1,适合批量转码的后台服务。
简单配置示例(Linux环境):
- 安装s3fs:
sudo apt-get install s3fs - 配置AWS凭证:把你的AWS密钥写入
~/.passwd-s3fs,并设置权限为600:echo "AKIAXXX:secretXXX" > ~/.passwd-s3fs && chmod 600 ~/.passwd-s3fs - 挂载S3目录:
s3fs your-bucket-name /mnt/s3-hls -o use_cache=/tmp,allow_other - 之后FFmpeg的输出路径直接指向
/mnt/s3-hls/your/hls/path/,生成的所有文件会自动同步到S3。
关键提醒
转码后一定要检查.m3u8文件里的分片路径:
- 如果用相对路径,只要所有文件都在S3的同一个前缀下,播放器就能正确加载分片;
- 如果需要绝对URL,可以在FFmpeg的输出参数里加
-hls_base_url https://your-bucket.s3.region.amazonaws.com/videos/hls/sample/,这样.m3u8里的分片路径会自动加上这个前缀。
内容的提问来源于stack exchange,提问作者dexter2019
相关产品推荐
相关产品推荐

