如何在 ruby 中实时解析 ffmpeg 进度

How to parse ffmpeg progress real time in ruby

所以昨晚我在这个问题上苦苦挣扎了很长时间,终于弄明白了所以我想post在这里以防有人运行遇到同样的问题。

目标是解析 FFmpeg 的输出,以便它在 Sidekiq worker 中 运行 并将进度和持续时间保存到 ActiveRecord 模型中,这样我就可以在 UI 中获得进度条通过轮询数据库。

如何在不等待进程完成的情况下实时解析 ffmpeg durationtime

有两个问题:

  1. 如何在ruby中实时解析输出?
  2. 既然 FFmpeg 将进度输出写在一行上,那么如何获得该输出?

对于第一个,我查看了 ruby Open3 模块,this article 特别有用。要点是这样的:

require 'open3'
cmd = "bash my_long_running_command.sh"
Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
  stdout.each do |line|
    # this will print as stdout is being written to the stream real-time
    puts line
  end
end

现在这适用于许多命令,但不适用于 FFmpeg。 FFmpeg 很奇怪,因为它将进度和输出写入 STDERR 而不是 STDOUT

不仅如此,我终于注意到 FFmpeg 的行定界符不是大多数 bash 命令中的 \n,而是 \r,这就是它能够内联编辑进度。现在我们知道唯一的更新是流和定界符,它们可以在 each 方法中作为第一个参数被覆盖。

require 'open3'
cmd = "ffmpeg -i input.mp4 ... output.mp4"
Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
  duration = 0
  progress = 0
  stderr.each("\r") do |line|
    next unless (line.include?("time=") || line.include?("Duration:"))
    if duration == 0
      if line =~ /Duration:\s+(\d{2}):(\d{2}):(\d{2}).(\d{1,2})/
        duration = .to_i * 3600 + .to_i * 60 + 
      end
    end 

    percentage = if line =~ /time(\d{2}):(\d{2}):(\d{2})/
      progress = .to_i * 3600 + .to_i * 60 + 
      (progress.to_f/duration.to_f) * 100
    end

    puts "#{percentage}%"
  end
end

您也可以使用我发现的 gem 做的事情非常相似,称为 stremio-ffmpeg,我在实施我的解决方案后遗憾地发现了它。如果你想做和上面一样的事情,只需做:

require 'streamio-ffmpeg'
movie = FFMPEG::Movie.new('input.mp4')
duration = movie.duration
movie.transcode('output.mp4', options_as_a_string) do |progress|
  percentage = (progress.to_f/duration.to_f) * 100
  puts "#{percentage}%"
end