Skip to content

Instantly share code, notes, and snippets.

@galanin
Last active October 4, 2020 14:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save galanin/6616965426eaf5a6221f0fff7ac1db8f to your computer and use it in GitHub Desktop.
Save galanin/6616965426eaf5a6221f0fff7ac1db8f to your computer and use it in GitHub Desktop.
def self.composite_cut(output, cut, layout, videoinfo)
duration = cut[:next_timestamp] - cut[:timestamp]
BigBlueButton.logger.info " Cut start time #{cut[:timestamp]}, duration #{duration}"
ffmpeg_filter = "color=c=white:s=#{layout[:width]}x#{layout[:height]}:r=24"
layout[:areas].each do |layout_area|
area = cut[:areas][layout_area[:name]]
video_count = area.length
BigBlueButton.logger.debug " Laying out #{video_count} videos in #{layout_area[:name]}"
next if video_count == 0
tile_offset_x = layout_area[:x]
tile_offset_y = layout_area[:y]
tiles_h = 0
tiles_v = 0
tile_width = 0
tile_height = 0
total_area = 0
# Do an exhaustive search to maximize video areas
for tmp_tiles_v in 1..video_count
tmp_tiles_h = (video_count / tmp_tiles_v.to_f).ceil
tmp_tile_width = (2 * (layout_area[:width].to_f / tmp_tiles_h / 2).floor).to_i
tmp_tile_height = (2 * (layout_area[:height].to_f / tmp_tiles_v / 2).floor).to_i
next if tmp_tile_width <= 0 or tmp_tile_height <= 0
tmp_total_area = 0
area.each do |video|
video_width = videoinfo[video[:filename]][:width]
video_height = videoinfo[video[:filename]][:height]
scale_width, scale_height = aspect_scale(video_width, video_height, tmp_tile_width, tmp_tile_height)
tmp_total_area += scale_width * scale_height
end
if tmp_total_area > total_area
tiles_h = tmp_tiles_h
tiles_v = tmp_tiles_v
tile_width = tmp_tile_width
tile_height = tmp_tile_height
total_area = tmp_total_area
end
end
tile_x = 0
tile_y = 0
BigBlueButton.logger.debug " Tiling in a #{tiles_h}x#{tiles_v} grid"
ffmpeg_filter << "[#{layout_area[:name]}_in];"
area.each do |video|
BigBlueButton.logger.debug " tile location (#{tile_x}, #{tile_y})"
video_width = videoinfo[video[:filename]][:width]
video_height = videoinfo[video[:filename]][:height]
BigBlueButton.logger.debug " original size: #{video_width}x#{video_height}"
scale_width, scale_height = aspect_scale(video_width, video_height, tile_width, tile_height)
BigBlueButton.logger.debug " scaled size: #{scale_width}x#{scale_height}"
offset_x, offset_y = pad_offset(scale_width, scale_height, tile_width, tile_height)
BigBlueButton.logger.debug " offset: left: #{offset_x}, top: #{offset_y}"
BigBlueButton.logger.debug " start timestamp: #{video[:timestamp]}"
BigBlueButton.logger.debug(" codec: #{videoinfo[video[:filename]][:video][:codec_name].inspect}")
BigBlueButton.logger.debug(" duration: #{videoinfo[video[:filename]][:duration]}, original duration: #{video[:original_duration]}")
if videoinfo[video[:filename]][:video][:codec_name] == "flashsv2"
# Desktop sharing videos in flashsv2 do not have regular
# keyframes, so seeking in them doesn't really work.
# To make processing more reliable, always decode them from the
# start in each cut. (Slow!)
seek = 0
else
# Webcam videos are variable, low fps; it might be that there's
# no frame until some time after the seek point. Start decoding
# 30s before the desired point to avoid this issue.
seek = video[:timestamp] - 30000
seek = 0 if seek < 0
end
# Workaround early 1.1 deskshare timestamp bug
# It resulted in video files that were too short. To workaround, we
# assume that the framerate was constant throughout (it might not
# actually be...) and scale the video length.
scale = nil
if !video[:original_duration].nil? and
videoinfo[video[:filename]][:video][:deskshare_timestamp_bug]
scale = video[:original_duration].to_f / videoinfo[video[:filename]][:duration]
# Rather than attempt to recalculate seek...
seek = 0
BigBlueButton.logger.debug(" Early 1.1 deskshare timestamp bug: scaling video length by #{scale}")
end
pad_name = "#{layout_area[:name]}_x#{tile_x}_y#{tile_y}"
ffmpeg_filter << "movie=#{video[:filename]}:sp=#{ms_to_s(seek)}"
if !scale.nil?
ffmpeg_filter << ",setpts=PTS*#{scale}"
end
ffmpeg_filter << ",fps=#{FFMPEG_WF_FRAMERATE}:start_time=#{ms_to_s(video[:timestamp])}"
ffmpeg_filter << ",setpts=PTS-STARTPTS,scale=#{scale_width}:#{scale_height}"
ffmpeg_filter << ",pad=w=#{tile_width}:h=#{tile_height}:x=#{offset_x}:y=#{offset_y}:color=white"
ffmpeg_filter << "[#{pad_name}];"
tile_x += 1
if tile_x >= tiles_h
tile_x = 0
tile_y += 1
end
end
remaining = video_count
(0...tiles_v).each do |tile_y|
this_tiles_h = [tiles_h, remaining].min
remaining -= this_tiles_h
(0...this_tiles_h).each do |tile_x|
ffmpeg_filter << "[#{layout_area[:name]}_x#{tile_x}_y#{tile_y}]"
end
if this_tiles_h > 1
ffmpeg_filter << "hstack=inputs=#{this_tiles_h},"
end
ffmpeg_filter << "pad=w=#{layout_area[:width]}:h=#{tile_height}:color=white"
ffmpeg_filter << "[#{layout_area[:name]}_y#{tile_y}];"
end
(0...tiles_v).each do |tile_y|
ffmpeg_filter << "[#{layout_area[:name]}_y#{tile_y}]"
end
if tiles_v > 1
ffmpeg_filter << "vstack=inputs=#{tiles_v},"
end
ffmpeg_filter << "pad=w=#{layout_area[:width]}:h=#{layout_area[:height]}:color=white"
ffmpeg_filter << "[#{layout_area[:name]}];"
ffmpeg_filter << "[#{layout_area[:name]}_in][#{layout_area[:name]}]overlay=x=#{layout_area[:x]}:y=#{layout_area[:y]}"
end
ffmpeg_filter << ",trim=end=#{ms_to_s(duration)}"
ffmpeg_cmd = [*FFMPEG]
ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-']
File.open(output, 'a') do |outio|
exitstatus = BigBlueButton.exec_redirect_ret(outio, *ffmpeg_cmd)
raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0
end
return output
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment