Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -110,35 +120,81 @@ 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, number_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 }
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 = 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]
end
end
current_slide = e.at('id').text
current_ts = e.attributes["timestamp"].value.to_i
end
if current_ts
time_shown = current_ts..sesseion_end
if slide_time[current_slide]
slide_time[current_slide].push(time_shown)
else
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
# Break because something else than default.pdf was found
break
end
slide_time_recorded[id].compact! if slide_time_recorded[id]
end
#BigBlueButton.logger.info("slide_time_recorded: #{slide_time_recorded}")

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('/')
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, :duration => st[1] })
end
presentation
end
Expand Down
4 changes: 4 additions & 0 deletions record-and-playback/presentation/scripts/presentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ deskshare_output_framerate: 5
audio_offset: 0
include_deskshare: true

# Generate thumbnails from most viewed slides
heuristic_thumbnails: false
number_thumbnails: 3

# For PRODUCTION
publish_dir: /var/bigbluebutton/published/presentation
video_formats:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'], @presentation_props['number_thumbnails'])
Nokogiri::XML::Builder.with(metadata.at('recording')) do |xml|
xml.playback do
xml.format('presentation')
Expand All @@ -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 || '', duration: p[:duration]/1000 }
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
Expand Down