基于Node.js和Express如何将动态生成字幕永久嵌入视频供用户下载
Node.js + Express 环境下视频内嵌字幕实现方案
核心实现思路
你可以通过FFmpeg实现字幕和视频的合并,支持硬烧录字幕(内嵌到画面无法移除)和软封装字幕(作为独立轨道可开关)两种模式,Node.js侧通过fluent-ffmpeg库调用FFmpeg能力完成处理,最终返回给用户下载。
前置准备
- 首先在你的服务端部署环境中安装FFmpeg,Windows、Linux、macOS均有对应发行版,安装完成后确保FFmpeg已加入系统环境变量
- 安装Node.js依赖包:
npm install fluent-ffmpeg
具体实现步骤
1. 生成标准字幕文件
你动态生成的字幕需要先转换为SRT/ASS格式,这是FFmpeg兼容性最好的字幕格式:
- 可以将内存中的字幕数据临时写入本地
.srt文件,建议用uuid作为文件名避免不同请求的文件冲突 - 高并发场景下也可以直接使用流传输字幕,无需写入本地磁盘
2. 视频合并字幕逻辑
硬烧录字幕方案(所有播放器默认显示字幕)
直接将字幕渲染到视频画面中,兼容性最强:
const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs'); const path = require('path'); // 入参:原视频路径、字幕文件路径、输出文件路径 function burnSubtitlesToVideo(videoPath, subtitlePath, outputPath) { return new Promise((resolve, reject) => { ffmpeg(videoPath) // 配置字幕滤镜,可额外调整字幕字体、大小、位置等参数 .videoFilter(`subtitles=${subtitlePath}:force_style='FontSize=24,FontName=微软雅黑'`) .output(outputPath) .on('end', () => { // 处理完成后删除临时字幕文件 fs.unlinkSync(subtitlePath); resolve(outputPath); }) .on('error', (err) => { reject(err); }) .run(); }); }
软封装字幕方案(字幕作为独立轨道可开关)
字幕不会修改原视频画面,用户可以在播放器中手动开启/关闭字幕,处理速度更快,服务器资源消耗更低:
function encapsulateSubtitleTrack(videoPath, subtitlePath, outputPath) { return new Promise((resolve, reject) => { ffmpeg(videoPath) .addInput(subtitlePath) // 字幕编码适配MP4容器,若输出MKV格式可改为`srt`/`ass` .outputOptions('-c:s mov_text') .output(outputPath) .on('end', () => { fs.unlinkSync(subtitlePath); resolve(outputPath); }) .on('error', (err) => { reject(err); }) .run(); }); }
3. Express接口对接
在Express路由中处理用户的下载请求:
const { v4: uuidv4 } = require('uuid'); app.get('/download/video-with-subtitle', async (req, res) => { const videoId = req.query.videoId; // 1. 获取对应原视频路径 const originVideoPath = path.join(__dirname, `./videos/${videoId}.mp4`); // 2. 生成临时字幕文件 const subtitleContent = await generateYourDynamicSubtitle(videoId); // 替换为你自己的动态字幕生成逻辑 const subtitleTempPath = path.join(__dirname, `./temp/${uuidv4()}.srt`); fs.writeFileSync(subtitleTempPath, subtitleContent); // 3. 生成输出临时文件路径 const outputTempPath = path.join(__dirname, `./temp/${uuidv4()}.mp4`); try { // 调用上面的合并方法,可按需选硬烧录/软封装 await burnSubtitlesToVideo(originVideoPath, subtitleTempPath, outputTempPath); // 返回文件给用户下载 res.download(outputTempPath, `video_with_subtitle.mp4`, (err) => { // 下载完成后删除临时输出文件 fs.unlinkSync(outputTempPath); }); } catch (err) { res.status(500).send('视频处理失败'); // 出错时清理临时文件 if (fs.existsSync(subtitleTempPath)) fs.unlinkSync(subtitleTempPath); if (fs.existsSync(outputTempPath)) fs.unlinkSync(outputTempPath); } })
注意事项
- FFmpeg视频处理属于CPU密集型操作,并发请求较多时建议引入任务队列做流量削峰,避免服务器资源耗尽
- 大视频处理时可以监听FFmpeg的
progress事件,将处理进度同步给前端,提升用户体验 - 临时文件目录需要提前创建,同时做好权限配置,避免读写失败
内容的提问来源于stack exchange,提问作者Andres Eduardo Bercowsky
相关产品推荐
相关产品推荐

