Skip to content

Instantly share code, notes, and snippets.

@trestrantham
Last active August 29, 2015 13:56
Show Gist options
  • Save trestrantham/8804888 to your computer and use it in GitHub Desktop.
Save trestrantham/8804888 to your computer and use it in GitHub Desktop.
Parse video by scene change
require "csv"
require "streamio-ffmpeg"
require "interactor"
require "command"
require "bigdecimal"
require "bigdecimal/util"
module Video
class DetectScenes
include Interactor
def before
fail!(error: "Invalid file path") unless context[:file_path] && File.exists?(file_path)
context[:video] = FFMPEG::Movie.new(file_path)
fail!(error: "Invalid video file") unless video.valid?
end
def run
result = Command.run(%Q(ffprobe -show_frames -of compact=p=0 -f lavfi "movie=#{file_path},select=gt(scene\\,.2)"))
context[:ffprobe_output] = result.stdout
end
end
end
module Video
class RollupScenes
include Interactor
def before
fail!(error: "Invalid ffprobe output") unless context[:ffprobe_output]
fail!(error: "Invalid file path") unless context[:file_path] && File.exists?(file_path)
context[:video] ||= FFMPEG::Movie.new(file_path)
end
def run
ffprobe_info = CSV.parse(ffprobe_output, { col_sep: "|" })
times = ffprobe_info.map do |row|
Hash[row.map do |item|
[item.split("=").first, item.split("=").last]
end].fetch("pkt_pts_time", nil)
end.compact
context[:scene_times] = rollup_times(times)
end
private
def rollup_times(times)
last_time = 0
rollup_times = []
times.each do |time|
if time.to_f - last_time.to_f > 0.5
rollup_times << [last_time.to_d + 0.1, time.to_d - 0.1]
last_time = time
end
end
rollup_times << [last_time.to_d + 0.1, video.duration]
end
end
end
module Video
class SplitVideoByScenes
include Interactor
def before
fail!(error: "Invalid file path") unless context[:file_path] && File.exists?(file_path)
fail!(error: "Invalid video file") unless FFMPEG::Movie.new(file_path).valid?
fail!(error: "Invalid scene times") unless context[:scene_times] && scene_times.is_a?(Array)
context[:directory] = File.dirname(file_path)
context[:video_filename] = File.basename(file_path, ".*")
context[:video_extension] = File.extname(file_path)
end
def run
scene_times.each_with_index do |time, index|
fast_seek_time = time[0] > 20 ? (((time[0] - 10).to_i / 10) * 10) : 0
slow_seek_time = time[0] - fast_seek_time
Command.run(%Q(ffmpeg -ss #{format_seconds(fast_seek_time)} -i #{file_path} -ss #{format_seconds(slow_seek_time)} -t #{format_seconds(time[1] - time[0])} -c:v libx264 -c:a libfaac #{directory}/#{video_filename}_#{index}#{video_extension}))
end
end
private
def format_seconds(seconds)
Time.at(seconds.to_d).utc.strftime("%H:%M:%S.%6N")
end
end
end
module Video
class SplitVideo
include Interactor::Organizer
organize Video::DetectScenes, Video::RollupScenes, Video::SplitVideoByScenes
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment