如何在Rails ActionJob中动态生成稳定的FFmpeg视频合并命令?
Rails ActionJob 动态生成FFmpeg filter_complex 合并多视频方案
核心思路
用filter_complex的concat滤镜替代文件列表方式,能自动处理不同编码、分辨率的视频,从根源解决音画不同步、截断问题。下面直接给适配Project关联多视频场景的Ruby实现代码。
实现代码(ActionJob类)
class VideoMergeJob < ApplicationJob queue_as :default def perform(project_id) project = Project.find(project_id) videos = project.videos output_path = Rails.root.join("tmp", "merged_#{project.id}.mp4").to_s # 过滤掉不存在的视频文件,避免命令执行失败 valid_videos = videos.select { |v| File.exist?(v.file.path) } return if valid_videos.empty? # 1. 生成FFmpeg输入参数:每个视频对应一个 -i 路径(自动转义特殊字符) input_args = valid_videos.map { |video| "-i #{Shellwords.escape(video.file.path)}" }.join(" ") # 2. 生成filter_complex的输入链:格式为 [0:v][0:a],[1:v][1:a],... input_links = valid_videos.each_index.map { |i| "[#{i}:v][#{i}:a]" }.join(",") # 3. 拼接concat滤镜:n=视频数量,v=1/a=1指定输出单视频/音频流 concat_filter = "#{input_links}concat=n=#{valid_videos.count}:v=1:a=1[outv][outa]" # 4. 组装完整FFmpeg命令,强制用H.264/AAC编码保证兼容性 ffmpeg_cmd = <<~CMD ffmpeg #{input_args} -filter_complex "#{concat_filter}" -map "[outv]" -map "[outa]" -c:v libx264 -c:a aac #{Shellwords.escape(output_path)} CMD # 执行命令并捕获输出,方便调试排错 stdout, stderr, status = Open3.capture3(ffmpeg_cmd) if status.success? # 合并成功后的业务逻辑:比如关联到Project project.update!(merged_video: File.open(output_path)) # 清理临时文件(可选,根据需求保留) File.delete(output_path) if File.exist?(output_path) else # 记录错误日志,便于排查 Rails.logger.error "FFmpeg合并失败: #{stderr}" raise "视频合并失败: #{stderr}" end end end
关键细节说明
- Shellwords.escape:自动转义路径中的空格、特殊字符,避免命令解析错误
- Open3.capture3:替代
system方法,能捕获命令的标准输出和错误信息,方便排查执行失败原因 - filter_complex concat参数:
n指定视频数量,v=1/a=1保证输出单一视频/音频流,彻底解决音画不同步问题 - 强制编码:指定
libx264和aac编码,避免不同源视频编码不一致导致的播放异常
注意事项
- 确保服务器已安装FFmpeg,若不在系统PATH中,需写绝对路径(比如
/usr/bin/ffmpeg) - 大视频合并时,建议将任务放到专用队列,避免阻塞其他任务
- 可以在生成输入参数前,先校验视频文件的可读取权限,进一步提升稳定性
内容的提问来源于stack exchange,提问作者Seto
相关产品推荐
相关产品推荐

