From c76dc4c16ebbeb379c39650e0a414a47678ce4c6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:15:30 +0900 Subject: [PATCH 01/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 8101ba5bca4a..096309e9eadc 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -11,6 +11,9 @@ deskshare_output_framerate: 5 audio_offset: 0 include_deskshare: true +# generate thumbnails from most viewed slides +heuristic_thumbnails : true + # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation video_formats: From 63941d78484326cbb274d9988ee082fabe35e3f8 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:24:36 +0900 Subject: [PATCH 02/11] Update presentation.rb --- .../generators/presentation.rb | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb index 66494b825160..21b7f23a00c9 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb @@ -110,35 +110,49 @@ def self.get_text_from_slide(textfiles_dir, slide_num) end # Get from events the presentation that will be used for preview. - def self.get_presentation_for_preview(process_dir) + def self.get_presentation_for_preview(process_dir, heuristic_thumbnails) events_xml = "#{process_dir}/events.xml" BigBlueButton.logger.info("Task: Getting from events the presentation to be used for preview") - presentation = {} + presentation = [] doc = Nokogiri::XML(File.open(events_xml)) presentation_filenames = {} doc.xpath("//event[@eventname='ConversionCompletedEvent']").each do |conversion_event| presentation_filenames[conversion_event.xpath("presentationName").text] = conversion_event.xpath("originalFilename").text end - doc.xpath("//event[@eventname='SharePresentationEvent']").each do |presentation_event| - # Extract presentation data from events - presentation_id = presentation_event.xpath("presentationName").text - presentation_filename = presentation_filenames[presentation_id] - # Set textfile directory - textfiles_dir = "#{process_dir}/presentation/#{presentation_id}/textfiles" - # Set presentation hashmap to be returned - unless presentation_filename == "default.pdf" - presentation[:id] = presentation_id - presentation[:filename] = presentation_filename - presentation[:slides] = {} - for i in 1..3 - if File.file?("#{textfiles_dir}/slide-#{i}.txt") - text_from_slide = self.get_text_from_slide(textfiles_dir, i) - presentation[:slides][i] = { :alt => text_from_slide == nil ? '' : text_from_slide } - end + slide_events = doc.xpath("//event[@eventname='GotoSlideEvent']").sort{|x,y| x.attributes["timestamp"].value.to_i <=> y.attributes["timestamp"].value.to_i} + slide_time = {} + current_slide = nil + current_ts = nil + slide_events.each_with_index do |e, i| + if i > 0 + time_shown = e.attributes["timestamp"].value.to_i - current_ts + if slide_time[current_slide] + slide_time[current_slide] += time_shown + else + slide_time[current_slide] = time_shown end - # Break because something else than default.pdf was found - break end + current_slide = e.at('presentationName').text + ":" + e.at('slide').text + current_ts = e.attributes["timestamp"].value.to_i + end + if current_ts + time_shown = doc.xpath('//event[last()]')[0].attributes['timestamp'].value.to_i - current_ts + if slide_time[current_slide] + slide_time[current_slide] += time_shown + else + slide_time[current_slide] = time_shown + end + end + + slide_time = slide_time.sort_by{|_, v| -v} if heuristic_thumbnails + slide_time.each do |st| + break if presentation.size > 2 + p,s = st[0].split(':') + presentation_filename = presentation_filenames[p] + next if presentation_filename == "default.pdf" + textfiles_dir = "#{process_dir}/presentation/#{p}/textfiles" + text_from_slide = self.get_text_from_slide(textfiles_dir, s) if File.file?("#{textfiles_dir}/slide-#{s}.txt") + presentation.push({:id => p, :filename => presentation_filename, :i => s, :alt => text_from_slide == nil ? '' : text_from_slide}) end presentation end From ac26151fd594a8f05b77fd1c529c1a39ef4832b0 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:28:36 +0900 Subject: [PATCH 03/11] Update presentation.rb --- .../presentation/scripts/publish/presentation.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 1767ddaadc41..3112488e8415 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -1388,7 +1388,7 @@ def copy_media_files_helper(media, media_files, package_dir) ## Remove empty playback metadata.search('recording/playback').each(&:remove) ## Add the actual playback - presentation = BigBlueButton::Presentation.get_presentation_for_preview(@process_dir.to_s) + presentation = BigBlueButton::Presentation.get_presentation_for_preview(@process_dir.to_s, @presentation_props['heuristic_thumbnails']) Nokogiri::XML::Builder.with(metadata.at('recording')) do |xml| xml.playback do xml.format('presentation') @@ -1399,10 +1399,10 @@ def copy_media_files_helper(media, media_files, package_dir) xml.extensions do xml.preview do xml.images do - presentation[:slides].each do |key, val| - attributes = { width: '176', height: '136', alt: val[:alt]&.to_s || '' } + presentation.each do |p| + attributes = { width: '176', height: '136', alt: p[:alt]&.to_s || '' } xml.image(attributes) do - xml.text("#{playback_protocol}://#{playback_host}/presentation/#{@meeting_id}/presentation/#{presentation[:id]}/thumbnails/thumb-#{key}.png") + xml.text("#{playback_protocol}://#{playback_host}/presentation/#{@meeting_id}/presentation/#{p[:id]}/thumbnails/thumb-#{p[:i]}.png") end end end From f54a8b8c47657a11ea549f9f6fd5f102992cde92 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:36:41 +0900 Subject: [PATCH 04/11] Update presentation.rb --- .../core/lib/recordandplayback/generators/presentation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb index 21b7f23a00c9..ef9b1ae9426d 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb @@ -147,7 +147,7 @@ def self.get_presentation_for_preview(process_dir, heuristic_thumbnails) slide_time = slide_time.sort_by{|_, v| -v} if heuristic_thumbnails slide_time.each do |st| break if presentation.size > 2 - p,s = st[0].split(':') + p, s = st[0].split(':') presentation_filename = presentation_filenames[p] next if presentation_filename == "default.pdf" textfiles_dir = "#{process_dir}/presentation/#{p}/textfiles" From 70a82ebe9b56651339f99491fe957f3c19660ccc Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:37:28 +0900 Subject: [PATCH 05/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 096309e9eadc..e66ee1170e5d 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -12,7 +12,7 @@ audio_offset: 0 include_deskshare: true # generate thumbnails from most viewed slides -heuristic_thumbnails : true +heuristic_thumbnails : false # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation From 86205fcae07c9e5cfc62100c3628ab1529392e15 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:40:28 +0900 Subject: [PATCH 06/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index e66ee1170e5d..ff5c48b553ab 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -12,7 +12,7 @@ audio_offset: 0 include_deskshare: true # generate thumbnails from most viewed slides -heuristic_thumbnails : false +heuristic_thumbnails: false # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation From 2d22bde5850858cf20dc14a398a214938452a9e9 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 19:44:16 +0900 Subject: [PATCH 07/11] number of thumbnails --- record-and-playback/presentation/scripts/presentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index ff5c48b553ab..000241a0000f 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -13,6 +13,7 @@ include_deskshare: true # generate thumbnails from most viewed slides heuristic_thumbnails: false +number_thumbnails: 3 # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation From 2590bbb8cf90c1ff172e266e5dac54ae8bb05f62 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 19:47:53 +0900 Subject: [PATCH 08/11] duration, #thumbnail, log --- .../core/lib/recordandplayback/generators/presentation.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb index ef9b1ae9426d..9b4812a423e6 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb @@ -110,7 +110,7 @@ def self.get_text_from_slide(textfiles_dir, slide_num) end # Get from events the presentation that will be used for preview. - def self.get_presentation_for_preview(process_dir, heuristic_thumbnails) + def self.get_presentation_for_preview(process_dir, heuristic_thumbnails, number_thumbnails) events_xml = "#{process_dir}/events.xml" BigBlueButton.logger.info("Task: Getting from events the presentation to be used for preview") presentation = [] @@ -145,14 +145,15 @@ def self.get_presentation_for_preview(process_dir, heuristic_thumbnails) end slide_time = slide_time.sort_by{|_, v| -v} if heuristic_thumbnails + BigBlueButton.logger.info("Thumbnail candidates: #{slide_time}") slide_time.each do |st| - break if presentation.size > 2 + break if presentation.size >= number_thumbnails p, s = st[0].split(':') presentation_filename = presentation_filenames[p] next if presentation_filename == "default.pdf" textfiles_dir = "#{process_dir}/presentation/#{p}/textfiles" text_from_slide = self.get_text_from_slide(textfiles_dir, s) if File.file?("#{textfiles_dir}/slide-#{s}.txt") - presentation.push({:id => p, :filename => presentation_filename, :i => s, :alt => text_from_slide == nil ? '' : text_from_slide}) + presentation.push({ :id => p, :filename => presentation_filename, :i => s, :alt => text_from_slide == nil ? '' : text_from_slide, :duration => st[1] }) end presentation end From 27a2c23d0d415d6b1338d005ee98385951e03874 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 25 Jun 2023 19:49:54 +0900 Subject: [PATCH 09/11] #thumbnails, duration in sec --- .../presentation/scripts/publish/presentation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 3112488e8415..6349485dea91 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -1388,7 +1388,7 @@ def copy_media_files_helper(media, media_files, package_dir) ## Remove empty playback metadata.search('recording/playback').each(&:remove) ## Add the actual playback - presentation = BigBlueButton::Presentation.get_presentation_for_preview(@process_dir.to_s, @presentation_props['heuristic_thumbnails']) + presentation = BigBlueButton::Presentation.get_presentation_for_preview(@process_dir.to_s, @presentation_props['heuristic_thumbnails'], @presentation_props['number_thumbnails']) Nokogiri::XML::Builder.with(metadata.at('recording')) do |xml| xml.playback do xml.format('presentation') @@ -1400,7 +1400,7 @@ def copy_media_files_helper(media, media_files, package_dir) xml.preview do xml.images do presentation.each do |p| - attributes = { width: '176', height: '136', alt: p[:alt]&.to_s || '' } + attributes = { width: '176', height: '136', alt: p[:alt]&.to_s || '', duration: p[:duration]/1000 } xml.image(attributes) do xml.text("#{playback_protocol}://#{playback_host}/presentation/#{@meeting_id}/presentation/#{p[:id]}/thumbnails/thumb-#{p[:i]}.png") end From bdf294f7f5616c222ee7761c95171c4e855d0448 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:26:57 +0900 Subject: [PATCH 10/11] Intersection with recorded periods --- .../generators/presentation.rb | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb index 9b4812a423e6..cfccaa80a3da 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/presentation.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/presentation.rb @@ -23,6 +23,16 @@ require 'rubygems' require 'nokogiri' +class Range + def intersection(other) + raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range) + new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil + new_max = self.cover?(other.max) ? other.max : other.cover?(max) ? max : nil + new_min && new_max ? new_min..new_max : nil + end + alias_method :&, :intersection +end + module BigBlueButton class Presentation # Get the presentations. @@ -119,36 +129,67 @@ def self.get_presentation_for_preview(process_dir, heuristic_thumbnails, number_ doc.xpath("//event[@eventname='ConversionCompletedEvent']").each do |conversion_event| presentation_filenames[conversion_event.xpath("presentationName").text] = conversion_event.xpath("originalFilename").text end - slide_events = doc.xpath("//event[@eventname='GotoSlideEvent']").sort{|x,y| x.attributes["timestamp"].value.to_i <=> y.attributes["timestamp"].value.to_i} + sesseion_end = doc.xpath('//event[last()]')[0].attributes['timestamp'].value.to_i + slide_events = doc.xpath("//event[@eventname='GotoSlideEvent']") slide_time = {} current_slide = nil current_ts = nil slide_events.each_with_index do |e, i| if i > 0 - time_shown = e.attributes["timestamp"].value.to_i - current_ts - if slide_time[current_slide] - slide_time[current_slide] += time_shown + time_shown = current_ts..e.attributes["timestamp"].value.to_i + if slide_time[current_slide] + slide_time[current_slide].push(time_shown) else - slide_time[current_slide] = time_shown + slide_time[current_slide] = [time_shown] end end - current_slide = e.at('presentationName').text + ":" + e.at('slide').text + current_slide = e.at('id').text current_ts = e.attributes["timestamp"].value.to_i end if current_ts - time_shown = doc.xpath('//event[last()]')[0].attributes['timestamp'].value.to_i - current_ts + time_shown = current_ts..sesseion_end if slide_time[current_slide] - slide_time[current_slide] += time_shown + slide_time[current_slide].push(time_shown) else - slide_time[current_slide] = time_shown + slide_time[current_slide] = [time_shown] + end + end + #BigBlueButton.logger.info("slide_time: #{slide_time}") + + record_time = [] + record_events = doc.xpath("//event[@eventname='RecordStatusEvent']") + record_events.each_with_index do |e, i| + if i.odd? + record_time.push(record_events[i-1].attributes["timestamp"].value.to_i..e.attributes["timestamp"].value.to_i) + end + end + if record_events.size.odd? + record_time.push(record_events[-1].attributes["timestamp"].value.to_i..sesseion_end) + end + #BigBlueButton.logger.info("record_time: #{record_time}") + + # Intersect with the recorded periods + slide_time_recorded = {} + slide_time.each do |id, periods| + periods.each do |period| + record_time.each do |record| + intersected_period = period.intersection(record) + if slide_time_recorded[id] + slide_time_recorded[id].push(intersected_period) + else + slide_time_recorded[id] = [intersected_period] + end + end end + slide_time_recorded[id].compact! if slide_time_recorded[id] end + #BigBlueButton.logger.info("slide_time_recorded: #{slide_time_recorded}") - slide_time = slide_time.sort_by{|_, v| -v} if heuristic_thumbnails - BigBlueButton.logger.info("Thumbnail candidates: #{slide_time}") - slide_time.each do |st| + slide_time_sort = slide_time_recorded.map{|k, v| [k, v.inject(0){|s, n| s += n.size}] }.sort_by{|_, v| -v} if heuristic_thumbnails + BigBlueButton.logger.info("Thumbnail candidates: #{slide_time_sort}") + slide_time_sort.each do |st| break if presentation.size >= number_thumbnails - p, s = st[0].split(':') + p, s = st[0].split('/') presentation_filename = presentation_filenames[p] next if presentation_filename == "default.pdf" textfiles_dir = "#{process_dir}/presentation/#{p}/textfiles" From 1c2def02e0f5d6f77cb84bdefc2cea319eadbf07 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 26 Jun 2023 21:21:29 +0900 Subject: [PATCH 11/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 000241a0000f..f7aa684ec8ca 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -11,7 +11,7 @@ deskshare_output_framerate: 5 audio_offset: 0 include_deskshare: true -# generate thumbnails from most viewed slides +# Generate thumbnails from most viewed slides heuristic_thumbnails: false number_thumbnails: 3