diff --git a/record-and-playback/core/lib/recordandplayback/edl.rb b/record-and-playback/core/lib/recordandplayback/edl.rb index e74c03d0cb150971fbef10377608827c1b623dbe..8588c90694468a07113d60cd608e9066f732d5ba 100644 --- a/record-and-playback/core/lib/recordandplayback/edl.rb +++ b/record-and-playback/core/lib/recordandplayback/edl.rb @@ -70,6 +70,7 @@ module BigBlueButton exitstatus = BigBlueButton.exec_ret(*cmd) raise "postprocess failed, exit code #{exitstatus}" if exitstatus != 0 end + FileUtils.rm(lastoutput) lastoutput = ppoutput end end diff --git a/record-and-playback/core/lib/recordandplayback/edl/audio.rb b/record-and-playback/core/lib/recordandplayback/edl/audio.rb index 32e4ed373b62cbb3f29eb3ba77b2e383d074bca7..e6c500c0d87764da1697eb6ab46b9af77d95c1b8 100644 --- a/record-and-playback/core/lib/recordandplayback/edl/audio.rb +++ b/record-and-playback/core/lib/recordandplayback/edl/audio.rb @@ -22,9 +22,9 @@ module BigBlueButton module Audio FFMPEG_AEVALSRC = "aevalsrc=s=48000:c=stereo:exprs=0|0" FFMPEG_AFORMAT = "aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo" - FFMPEG_WF_CODEC = 'flac' - FFMPEG_WF_ARGS = ['-c:a', FFMPEG_WF_CODEC, '-f', 'flac'] - WF_EXT = 'flac' + FFMPEG_WF_CODEC = 'libvorbis' + FFMPEG_WF_ARGS = ['-c:a', FFMPEG_WF_CODEC, '-q:a', '2', '-f', 'ogg'] + WF_EXT = 'ogg' def self.dump(edl) BigBlueButton.logger.debug "EDL Dump:" diff --git a/record-and-playback/core/lib/recordandplayback/edl/video.rb b/record-and-playback/core/lib/recordandplayback/edl/video.rb index 269be99a6a887ac3a775857d6508c26bcd40bae6..7bb879d6b24b1adfbe361834e9f87728586947fb 100644 --- a/record-and-playback/core/lib/recordandplayback/edl/video.rb +++ b/record-and-playback/core/lib/recordandplayback/edl/video.rb @@ -23,10 +23,9 @@ require 'set' module BigBlueButton module EDL module Video - FFMPEG_WF_CODEC = 'mpeg2video' - FFMPEG_WF_FRAMERATE = 24 - FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC.to_s, '-q:v', '2', '-g', (FFMPEG_WF_FRAMERATE * 10).to_s, '-pix_fmt', 'yuv420p', '-r', FFMPEG_WF_FRAMERATE.to_s, '-f', 'mpegts'] - WF_EXT = 'ts' + FFMPEG_WF_CODEC = 'libx264' + FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC.to_s, '-preset', 'veryfast', '-crf', '30', '-force_key_frames', 'expr:gte(t,n_forced*10)', '-pix_fmt', 'yuv420p', '-bsf:v', 'h264_mp4toannexb'] + WF_EXT = 'mp4' def self.dump(edl) BigBlueButton.logger.debug "EDL Dump:" @@ -254,12 +253,35 @@ module BigBlueButton BigBlueButton.logger.info "Compositing cuts" render = "#{output_basename}.#{WF_EXT}" + concat = [] for i in 0...(edl.length - 1) if edl[i][:timestamp] == edl[i][:next_timestamp] warn 'Skipping 0-length edl entry' next end - composite_cut(render, edl[i], layout, videoinfo) + if video_info(segment).empty? + warn 'Skipping edl entry with no video stream' + next + end + segment = "#{output_basename}_#{i}.#{WF_EXT}" + composite_cut(segment, edl[i], layout, videoinfo) + concat += [segment] + end + + concat_file = "#{output_basename}.txt" + File.open(concat_file, 'w') do |outio| + concat.each do |segment| + outio.write("file #{segment}\n") + end + end + + ffmpeg_cmd = [*FFMPEG] + ffmpeg_cmd += ['-safe', '0', '-f', 'concat', '-i', concat_file , '-c', 'copy', render] + exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd) + raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0 + + concat.each do |segment| + File.delete(segment) end return render @@ -375,7 +397,7 @@ module BigBlueButton 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" + ffmpeg_filter = "color=c=white:s=#{layout[:width]}x#{layout[:height]}:r=#{layout[:framerate]}" layout[:areas].each do |layout_area| area = cut[:areas][layout_area[:name]] @@ -488,7 +510,7 @@ module BigBlueButton ffmpeg_filter << ",setpts=PTS-#{ms_to_s(seek_offset)}/TB" # fps filter fills in frames up to the desired start point, and # cuts the video there - ffmpeg_filter << ",fps=#{FFMPEG_WF_FRAMERATE}:start_time=#{ms_to_s(video[:timestamp])}" + ffmpeg_filter << ",fps=#{layout[:framerate]}:start_time=#{ms_to_s(video[:timestamp])}" # Reset the timestamps to start at 0 so that everything is synced # for the video tiling, and scale to the desired size. ffmpeg_filter << ",setpts=PTS-STARTPTS,scale=#{scale_width}:#{scale_height},setsar=1" @@ -500,7 +522,7 @@ module BigBlueButton # it to length. do that by concatenating a video generated by the # color filter. (It would be nice to repeat the last frame instead, # but there's no easy way to do that.) - ffmpeg_filter << "color=c=white:s=#{tile_width}x#{tile_height}:r=#{FFMPEG_WF_FRAMERATE}" + ffmpeg_filter << "color=c=white:s=#{tile_width}x#{tile_height}:r=#{layout[:framerate]}" ffmpeg_filter << "[#{pad_name}_pad];" ffmpeg_filter << "[#{pad_name}_movie][#{pad_name}_pad]concat=n=2:v=1:a=0[#{pad_name}];" @@ -542,12 +564,10 @@ module BigBlueButton ffmpeg_filter << ",trim=end=#{ms_to_s(duration)}" ffmpeg_cmd = [*FFMPEG] - ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-'] + ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-r', layout[:framerate].to_s, output] - File.open(output, 'a') do |outio| - exitstatus = BigBlueButton.exec_redirect_ret(outio, *ffmpeg_cmd) - raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0 - end + exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd) + raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0 return output end diff --git a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb index ba03a34e367442b47b9a72a545e95b3d44cfe209..9f5129c14487cb1a93944a3d1774aac451a3f31b 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb @@ -60,13 +60,13 @@ module BigBlueButton ogg_format = { :extension => 'ogg', - :parameters => [ [ '-c:a', 'libvorbis', '-q:a', '2', '-f', 'ogg' ] ] + :parameters => [ [ '-c:a', 'copy', '-f', 'ogg' ] ] } BigBlueButton::EDL.encode(@audio_file, nil, ogg_format, file_basename) webm_format = { :extension => 'webm', - :parameters => [ [ '-c:a', 'libvorbis', '-q:a', '2', '-f', 'webm' ] ], + :parameters => [ [ '-c:a', 'copy', '-f', 'webm' ] ], :postprocess => [ [ 'mkclean', '--quiet', ':input', ':output' ] ] } BigBlueButton::EDL.encode(@audio_file, nil, webm_format, file_basename) diff --git a/record-and-playback/core/lib/recordandplayback/generators/video.rb b/record-and-playback/core/lib/recordandplayback/generators/video.rb index d2ae8846d39de7dd61f7dc25a2f8ba1e6a607988..83b8a7381ed7c744344d8bf16164e507f068c65c 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/video.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/video.rb @@ -1,3 +1,4 @@ + # Set encoding to utf-8 # encoding: UTF-8 @@ -27,7 +28,7 @@ require File.expand_path('../../edl', __FILE__) module BigBlueButton - def BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, output_width, output_height, audio_offset, processed_audio_file, video_formats=['webm']) + def BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, output_width, output_height, output_framerate, audio_offset, processed_audio_file, video_formats=['webm']) BigBlueButton.logger.info("Processing webcam videos") events = Nokogiri::XML(File.open("#{temp_dir}/#{meeting_id}/events.xml")) @@ -42,7 +43,7 @@ module BigBlueButton BigBlueButton::EDL::Video.dump(user_video_edl) user_video_layout = { - width: output_width, height: output_height, + width: output_width, height: output_height, framerate: output_framerate, areas: [ { name: :webcam, x: 0, y: 0, width: output_width, height: output_height } ] } user_video_file = BigBlueButton::EDL::Video.render( @@ -55,8 +56,8 @@ module BigBlueButton # These settings are appropriate for 640x480 medium quality, and should be tweaked for other resolutions # See https://developers.google.com/media/vp9/settings/vod/ # Increase -threads to max of 4 or increase -speed to max of 4 to speed up processing - %w[-c:v libvpx-vp9 -b:v 750K -minrate 375K -maxrate 1088K -crf 33 -quality good -speed 1 -g 240 -tile-columns 1 -threads 2 - -c:a libopus -b:a 48K + %w[-c:v libvpx-vp9 -crf 32 -deadline realtime -cpu-used 8 -force_key_frames expr:gte(t,n_forced*10) -tile-columns 2 -tile-rows 2 -threads 4 + -c:a copy -f webm] # Google recommends doing a 2-pass encode for better quality, but it's a lot slower. If you want to do this, # comment the lines above, and uncomment the lines below. @@ -76,7 +77,7 @@ module BigBlueButton # Increase -threads (or remove it, to use all cpu cores) to speed up processing # You can also change the preset: try 'fast' or 'faster' # To change quality, adjust the -crf value. Lower numbers are higher quality. - %w[-c:v libx264 -crf 23 -threads 2 -preset medium -g 240 + %w[-c:v copy -c:a aac -b:a 64K -f mp4 -movflags faststart] ], @@ -90,7 +91,7 @@ module BigBlueButton end end - def BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, output_width, output_height, video_formats=['webm']) + def BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, output_width, output_height, output_framerate, video_formats=['webm']) BigBlueButton.logger.info("Processing deskshare videos") events = Nokogiri::XML(File.open("#{temp_dir}/#{meeting_id}/events.xml")) @@ -107,7 +108,7 @@ module BigBlueButton BigBlueButton::EDL::Video.dump(deskshare_video_edl) deskshare_layout = { - width: output_width, height: output_height, + width: output_width, height: output_height, framerate: output_framerate, areas: [ { name: :deskshare, x: 0, y: 0, width: output_width, height: output_height } ] } @@ -121,8 +122,8 @@ module BigBlueButton # These settings are appropriate for 1280x720 medium quality, and should be tweaked for other resolutions # See https://developers.google.com/media/vp9/settings/vod/ # Increase -threads to max of 8 or increase -speed to max of 4 to speed up processing - %w[-c:v libvpx-vp9 -b:v 1024K -minrate 512K -maxrate 1485K -crf 32 -quality good -speed 2 -g 240 -tile-columns 2 -threads 2 - -c:a libopus -b:a 48K + %w[-c:v libvpx-vp9 -crf 32 -deadline realtime -cpu-used 8 -force_key_frames expr:gte(t,n_forced*10) -tile-columns 2 -tile-rows 2 -threads 4 + -c:a copy -f webm] # Google recommends doing a 2-pass encode for better quality, but it's a lot slower. If you want to do this, # comment the lines above, and uncomment the lines below. @@ -142,7 +143,7 @@ module BigBlueButton # Increase -threads (or remove it, to use all cpu cores) to speed up processing # You can also change the preset: try 'fast' or 'faster' # To change quality, adjust the -crf value. Lower numbers are higher quality. - %w[-c:v libx264 -crf 23 -threads 2 -preset medium -g 240 + %w[-c:v copy -c:a aac -b:a 64K -f mp4 -movflags faststart] ], diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 19973ce28c5d26b16802eced3aafc81202e4c783..8101ba5bca4abe1cfb88d4afe9bfdda9479ef41a 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -1,9 +1,11 @@ video_output_width: 640 video_output_height: 480 +video_output_framerate: 15 # Alternate output size to use when deskshare videos are present # Set higher so that deskshare output is higher quality, but uses more space. deskshare_output_width: 1280 deskshare_output_height: 720 +deskshare_output_framerate: 5 # offset applied to audio in the output video file # audio_offset = 1200 means that the audio will be delayed by 1200ms audio_offset: 0 diff --git a/record-and-playback/presentation/scripts/process/presentation.rb b/record-and-playback/presentation/scripts/process/presentation.rb index 55154fc7d87ee0e499f9179a76f086f8275f23d1..5c3a7ab0adac4a515d9611ff2d626862b7bfd54b 100755 --- a/record-and-playback/presentation/scripts/process/presentation.rb +++ b/record-and-playback/presentation/scripts/process/presentation.rb @@ -220,21 +220,27 @@ if not FileTest.directory?(target_dir) captions.length > 0 webcam_width = presentation_props['video_output_width'] webcam_height = presentation_props['video_output_height'] + webcam_framerate = presentation_props['video_output_framerate'] # Use a higher resolution video canvas if there's broadcast video streams if !Dir["#{raw_archive_dir}/video-broadcast/*"].empty? webcam_width = presentation_props['deskshare_output_width'] webcam_height = presentation_props['deskshare_output_height'] + webcam_framerate = presentation_props['deskshare_output_framerate'] end + webcam_framerate = 15 if webcam_framerate.nil? processed_audio_file = BigBlueButton::AudioProcessor.get_processed_audio_file("#{temp_dir}/#{meeting_id}", "#{target_dir}/audio") - BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, webcam_width, webcam_height, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats']) + BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, webcam_width, webcam_height, webcam_framerate, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats']) end if !Dir["#{raw_archive_dir}/deskshare/*"].empty? and presentation_props['include_deskshare'] deskshare_width = presentation_props['deskshare_output_width'] deskshare_height = presentation_props['deskshare_output_height'] - BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, deskshare_width, deskshare_height, presentation_props['video_formats']) + deskshare_framerate = presentation_props['deskshare_output_framerate'] + deskshare_framerate = 5 if deskshare_framerate.nil? + + BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, deskshare_width, deskshare_height, deskshare_framerate, presentation_props['video_formats']) end # Copy shared notes from raw files