如何在 ruby 中实时解析 ffmpeg 进度
How to parse ffmpeg progress real time in ruby
所以昨晚我在这个问题上苦苦挣扎了很长时间,终于弄明白了所以我想post在这里以防有人运行遇到同样的问题。
目标是解析 FFmpeg 的输出,以便它在 Sidekiq worker 中 运行 并将进度和持续时间保存到 ActiveRecord 模型中,这样我就可以在 UI 中获得进度条通过轮询数据库。
如何在不等待进程完成的情况下实时解析 ffmpeg duration
和 time
?
有两个问题:
- 如何在ruby中实时解析输出?
- 既然 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
所以昨晚我在这个问题上苦苦挣扎了很长时间,终于弄明白了所以我想post在这里以防有人运行遇到同样的问题。
目标是解析 FFmpeg 的输出,以便它在 Sidekiq worker 中 运行 并将进度和持续时间保存到 ActiveRecord 模型中,这样我就可以在 UI 中获得进度条通过轮询数据库。
如何在不等待进程完成的情况下实时解析 ffmpeg duration
和 time
?
有两个问题:
- 如何在ruby中实时解析输出?
- 既然 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