2021-12-06 20:58:04 +08:00
# frozen_string_literal: false
2012-07-18 03:39:44 +08:00
# Set encoding to utf-8
# encoding: UTF-8
#
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
#
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
2021-12-06 02:04:21 +08:00
_performance_start = Time . now
2012-07-18 03:39:44 +08:00
2021-12-05 22:53:06 +08:00
# For DEVELOPMENT
# Allows us to run the script manually
require File . expand_path ( '../../../../core/lib/recordandplayback' , __FILE__ )
# For PRODUCTION
# require File.expand_path('../../../lib/recordandplayback', __FILE__)
2012-07-18 03:39:44 +08:00
require 'rubygems'
require 'trollop'
require 'yaml'
require 'builder'
require 'fastimage' # require fastimage to get the image size of the slides (gem install fastimage)
2021-06-18 05:36:45 +08:00
require 'json'
2012-07-18 03:39:44 +08:00
2017-08-19 03:24:54 +08:00
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
2017-10-10 03:37:28 +08:00
bbb_props = BigBlueButton . read_props
2021-12-06 02:35:22 +08:00
@presentation_props = YAML . safe_load ( File . read ( 'presentation.yml' ) )
2017-02-24 02:21:10 +08:00
2017-08-18 01:27:09 +08:00
# There's a couple of places where stuff is mysteriously divided or multiplied
# by 2. This is just here to call out how spooky that is.
2021-12-06 02:35:22 +08:00
@magic_mystery_number = 2
2017-02-24 02:21:10 +08:00
2021-12-06 03:09:46 +08:00
def scale_to_deskshare_video ( width , height )
2021-12-06 02:35:22 +08:00
deskshare_video_height = @presentation_props [ 'deskshare_output_height' ] . to_f
deskshare_video_width = @presentation_props [ 'deskshare_output_height' ] . to_f
2016-12-16 01:19:10 +08:00
2021-12-05 22:53:06 +08:00
scale = [ deskshare_video_width / width , deskshare_video_height / height ]
2016-12-16 01:19:10 +08:00
video_width = width * scale . min
video_height = height * scale . min
2021-12-05 22:53:06 +08:00
[ video_width . floor , video_height . floor ]
2016-12-16 01:19:10 +08:00
end
2021-12-06 03:09:46 +08:00
def get_deskshare_video_dimension ( deskshare_stream_name )
2021-12-06 02:35:22 +08:00
video_width = @presentation_props [ 'deskshare_output_height' ] . to_f
video_height = @presentation_props [ 'deskshare_output_height' ] . to_f
deskshare_video_filename = " #{ @deskshare_dir } / #{ deskshare_stream_name } "
2017-02-24 04:17:16 +08:00
if File . exist? ( deskshare_video_filename )
2017-08-19 03:24:54 +08:00
video_info = BigBlueButton :: EDL :: Video . video_info ( deskshare_video_filename )
2021-12-06 03:09:46 +08:00
video_width , video_height = scale_to_deskshare_video ( video_info [ :width ] , video_info [ :height ] )
2016-12-13 02:29:06 +08:00
else
2017-02-24 04:17:16 +08:00
BigBlueButton . logger . error ( " Could not find deskshare video: #{ deskshare_video_filename } " )
2016-12-13 02:29:06 +08:00
end
2017-02-24 04:17:16 +08:00
2021-12-05 22:53:06 +08:00
[ video_width , video_height ]
2016-11-30 01:08:28 +08:00
end
2017-08-18 01:27:09 +08:00
#
# Calculate the offsets based on the start and stop recording events, so it's easier
# to translate the timestamps later based on these offsets
#
2021-12-06 03:09:46 +08:00
def calculate_record_events_offset
2017-08-18 01:27:09 +08:00
accumulated_duration = 0
2021-12-06 02:35:22 +08:00
previous_stop_recording = @meeting_start . to_f
@rec_events . each do | event |
2017-08-18 01:27:09 +08:00
event [ :offset ] = event [ :start_timestamp ] - accumulated_duration
event [ :duration ] = event [ :stop_timestamp ] - event [ :start_timestamp ]
event [ :accumulated_duration ] = accumulated_duration
previous_stop_recording = event [ :stop_timestamp ]
accumulated_duration += event [ :duration ]
2016-01-20 03:52:28 +08:00
end
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
#
# Translated an arbitrary Unix timestamp to the recording timestamp. This is the
# function that others will call
#
2021-12-06 03:09:46 +08:00
def translate_timestamp ( timestamp )
new_timestamp = translate_timestamp_helper ( timestamp . to_f ) . to_f
2021-12-06 02:35:22 +08:00
# BigBlueButton.logger.info("Translating #{timestamp}, old value=#{timestamp.to_f-@meeting_start.to_f}, new value=#{new_timestamp}")
2017-08-18 01:27:09 +08:00
new_timestamp
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
#
# Translated an arbitrary Unix timestamp to the recording timestamp
#
2021-12-06 03:09:46 +08:00
def translate_timestamp_helper ( timestamp )
2021-12-06 02:35:22 +08:00
@rec_events . each do | event |
2017-08-18 01:27:09 +08:00
# if the timestamp comes before the start recording event, then the timestamp is translated to the moment it starts recording
2021-12-06 02:04:21 +08:00
return event [ :start_timestamp ] - event [ :offset ] if timestamp < = event [ :start_timestamp ]
2017-08-18 01:27:09 +08:00
# if the timestamp is during the recording period, it is just translated to the new one using the offset
2021-12-06 02:04:21 +08:00
return timestamp - event [ :offset ] if ( timestamp > event [ :start_timestamp ] ) && ( timestamp < = event [ :stop_timestamp ] )
2016-01-20 03:52:28 +08:00
end
2021-12-06 02:04:21 +08:00
2017-08-18 01:27:09 +08:00
# if the timestamp comes after the last stop recording event, then the timestamp is translated to the last stop recording event timestamp
2021-12-06 02:35:22 +08:00
timestamp - @rec_events . last [ :offset ] + @rec_events . last [ :duration ]
2017-08-18 01:27:09 +08:00
end
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
def color_to_hex ( color )
color = color . to_i . to_s ( 16 )
2021-12-05 22:53:06 +08:00
'0' * ( 6 - color . length ) + color
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
def shape_scale_width ( slide , x )
2021-12-05 22:53:06 +08:00
( x / 100 . 0 * slide [ :width ] ) . round ( 5 )
2017-08-18 01:27:09 +08:00
end
2021-12-05 22:53:06 +08:00
2017-08-18 01:27:09 +08:00
def shape_scale_height ( slide , y )
2021-12-05 22:53:06 +08:00
( y / 100 . 0 * slide [ :height ] ) . round ( 5 )
2017-08-18 01:27:09 +08:00
end
2021-12-05 22:53:06 +08:00
2017-08-18 01:27:09 +08:00
def shape_thickness ( slide , shape )
if ! shape [ :thickness_percent ] . nil?
2021-12-05 22:53:06 +08:00
shape_scale_width ( slide , shape [ :thickness_percent ] )
2017-08-18 01:27:09 +08:00
else
2021-12-05 22:53:06 +08:00
shape [ :thickness ]
2016-01-20 03:52:28 +08:00
end
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
def svg_render_shape_pencil ( g , slide , shape )
g [ 'shape' ] = " pencil #{ shape [ :shape_unique_id ] } "
doc = g . document
2018-06-23 03:33:30 +08:00
if shape [ :data_points ] . length < 2
BigBlueButton . logger . warn ( " Pencil #{ shape [ :shape_unique_id ] } doesn't have enough points " )
return
end
2017-08-18 01:27:09 +08:00
if shape [ :data_points ] . length == 2
BigBlueButton . logger . info ( " Pencil #{ shape [ :shape_unique_id ] } : Drawing single point " )
g [ 'style' ] = " stroke:none;fill: # #{ shape [ :color ] } ;visibility:hidden "
circle = doc . create_element ( 'circle' ,
2021-12-05 22:53:06 +08:00
cx : shape_scale_width ( slide , shape [ :data_points ] [ 0 ] ) ,
cy : shape_scale_height ( slide , shape [ :data_points ] [ 1 ] ) ,
r : ( shape_thickness ( slide , shape ) / 2 . 0 ) . round ( 5 ) )
2017-08-18 01:27:09 +08:00
g << circle
2017-08-04 00:17:48 +08:00
else
path = [ ]
2017-08-18 01:27:09 +08:00
data_points = shape [ :data_points ] . each
2017-08-04 00:17:48 +08:00
2017-08-18 01:27:09 +08:00
if ! shape [ :commands ] . nil?
BigBlueButton . logger . info ( " Pencil #{ shape [ :shape_unique_id ] } : Drawing from command string ( #{ shape [ :commands ] . length } commands) " )
shape [ :commands ] . each do | command |
2017-08-04 00:17:48 +08:00
case command
2017-08-18 01:27:09 +08:00
when 1 # MOVE_TO
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
path . push ( " M #{ x } #{ y } " )
when 2 # LINE_TO
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
path . push ( " L #{ x } #{ y } " )
when 3 # Q_CURVE_TO
cx1 = shape_scale_width ( slide , data_points . next )
cy1 = shape_scale_height ( slide , data_points . next )
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
2021-12-05 23:09:26 +08:00
path . push ( " Q #{ cx1 } #{ cy1 } , #{ x } #{ y } " )
2017-08-18 01:27:09 +08:00
when 4 # C_CURVE_TO
cx1 = shape_scale_width ( slide , data_points . next )
cy1 = shape_scale_height ( slide , data_points . next )
cx2 = shape_scale_width ( slide , data_points . next )
cy2 = shape_scale_height ( slide , data_points . next )
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
path . push ( " C #{ cx1 } #{ cy1 } , #{ cx2 } #{ cy2 } , #{ x } #{ y } " )
2017-08-04 00:17:48 +08:00
else
raise " Unknown pencil command: #{ command } "
end
end
else
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Pencil #{ shape [ :shape_unique_id ] } : Drawing simple line ( #{ shape [ :data_points ] . length / 2 } points) " )
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
path << " M #{ x } #{ y } "
begin
2021-12-05 22:53:06 +08:00
loop do
2017-08-18 01:27:09 +08:00
x = shape_scale_width ( slide , data_points . next )
y = shape_scale_height ( slide , data_points . next )
path << " L #{ x } #{ y } "
end
rescue StopIteration
2017-08-04 00:17:48 +08:00
end
end
2017-08-18 01:27:09 +08:00
path = path . join ( '' )
2021-12-05 22:53:06 +08:00
g [ 'style' ] = " stroke: # #{ shape [ :color ] } ;stroke-linecap:round;stroke-linejoin:round;stroke-width: #{ shape_thickness ( slide , shape ) } ;visibility:hidden;fill:none "
2017-08-18 01:27:09 +08:00
svg_path = doc . create_element ( 'path' , d : path )
g << svg_path
2016-01-20 03:52:28 +08:00
end
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
def svg_render_shape_line ( g , slide , shape )
g [ 'shape' ] = " line #{ shape [ :shape_unique_id ] } "
2021-12-06 20:58:04 +08:00
g [ 'style' ] = " stroke: # #{ shape [ :color ] } ;stroke-width: #{ shape_thickness ( slide , shape ) } ;visibility:hidden;fill:none #{ @version_atleast_2_0_0 ? " ;stroke-linecap:butt " : " ;stroke-linecap:round " } "
2017-08-18 01:27:09 +08:00
doc = g . document
data_points = shape [ :data_points ]
line = doc . create_element ( 'line' ,
2021-12-05 22:53:06 +08:00
x1 : shape_scale_width ( slide , data_points [ 0 ] ) ,
y1 : shape_scale_height ( slide , data_points [ 1 ] ) ,
x2 : shape_scale_width ( slide , data_points [ 2 ] ) ,
y2 : shape_scale_height ( slide , data_points [ 3 ] ) )
2017-08-18 01:27:09 +08:00
g << line
end
2016-01-20 03:52:28 +08:00
2021-12-06 19:40:11 +08:00
def stroke_attributes ( slide , shape )
" stroke: # #{ shape [ :color ] } ;stroke-width: #{ shape_thickness ( slide , shape ) } ;visibility:hidden;fill: #{ shape [ :fill ] ? '#' + shape [ :color ] : 'none' } "
end
2017-08-18 01:27:09 +08:00
def svg_render_shape_rect ( g , slide , shape )
g [ 'shape' ] = " rect #{ shape [ :shape_unique_id ] } "
2021-12-06 20:58:04 +08:00
g [ 'style' ] = " #{ stroke_attributes ( slide , shape ) } #{ @version_atleast_2_0_0 ? " ;stroke-linejoin:miter " : " ;stroke-linejoin:round " } "
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
doc = g . document
data_points = shape [ :data_points ]
x1 = shape_scale_width ( slide , data_points [ 0 ] )
y1 = shape_scale_height ( slide , data_points [ 1 ] )
x2 = shape_scale_width ( slide , data_points [ 2 ] )
y2 = shape_scale_height ( slide , data_points [ 3 ] )
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
width = ( x2 - x1 ) . abs
2021-12-06 02:04:21 +08:00
# height = (y2 - y1).abs
2012-09-07 03:21:55 +08:00
2017-08-18 01:27:09 +08:00
if shape [ :square ]
# Convert to a square, keeping aligned with the start point.
if x2 > x1
y2 = y1 + width
else
# This replicates a bug in the BigBlueButton flash client
BigBlueButton . logger . info ( " Rect #{ shape [ :shape_unique_id ] } reversed square bug " )
y2 = y1 - width
2016-01-20 03:52:28 +08:00
end
end
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
path = doc . create_element ( 'path' ,
2021-12-05 22:53:06 +08:00
d : " M #{ x1 } #{ y1 } L #{ x2 } #{ y1 } L #{ x2 } #{ y2 } L #{ x1 } #{ y2 } Z " )
2017-08-18 01:27:09 +08:00
g << path
end
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
def svg_render_shape_triangle ( g , slide , shape )
g [ 'shape' ] = " triangle #{ shape [ :shape_unique_id ] } "
2021-12-06 20:58:04 +08:00
g [ 'style' ] = " #{ stroke_attributes ( slide , shape ) } #{ @version_atleast_2_0_0 ? " ;stroke-linejoin:miter;stroke-miterlimit:8 " : " ;stroke-linejoin:round " } "
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
doc = g . document
data_points = shape [ :data_points ]
x1 = shape_scale_width ( slide , data_points [ 0 ] )
y1 = shape_scale_height ( slide , data_points [ 1 ] )
x2 = shape_scale_width ( slide , data_points [ 2 ] )
y2 = shape_scale_height ( slide , data_points [ 3 ] )
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
px = ( ( x1 + x2 ) / 2 . 0 ) . round ( 5 )
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
path = doc . create_element ( 'path' ,
2021-12-05 22:53:06 +08:00
d : " M #{ px } #{ y1 } L #{ x2 } #{ y2 } L #{ x1 } #{ y2 } Z " )
2017-08-18 01:27:09 +08:00
g << path
end
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
def svg_render_shape_ellipse ( g , slide , shape )
g [ 'shape' ] = " ellipse #{ shape [ :shape_unique_id ] } "
2021-12-06 19:40:11 +08:00
g [ 'style' ] = stroke_attributes ( slide , shape )
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
doc = g . document
data_points = shape [ :data_points ]
x1 = shape_scale_width ( slide , data_points [ 0 ] )
y1 = shape_scale_height ( slide , data_points [ 1 ] )
x2 = shape_scale_width ( slide , data_points [ 2 ] )
y2 = shape_scale_height ( slide , data_points [ 3 ] )
2012-09-04 09:14:29 +08:00
2017-08-18 01:27:09 +08:00
width_r = ( ( x2 - x1 ) . abs / 2 . 0 ) . round ( 5 )
height_r = ( ( y2 - y1 ) . abs / 2 . 0 ) . round ( 5 )
hx = ( ( x1 + x2 ) / 2 . 0 ) . round ( 5 )
hy = ( ( y1 + y2 ) / 2 . 0 ) . round ( 5 )
2016-08-12 23:53:01 +08:00
2017-08-18 01:27:09 +08:00
if shape [ :circle ]
2016-08-12 23:53:01 +08:00
# Convert to a circle, keeping aligned with the start point
2017-08-18 01:27:09 +08:00
height_r = width_r
if x2 > x1
y2 = y1 + height_r + height_r
else
# This replicates a bug in the BigBlueButton flash client
BigBlueButton . logger . info ( " Ellipse #{ shape [ :shape_unique_id ] } reversed circle bug " )
y2 = y1 - height_r - height_r
2016-01-20 03:52:28 +08:00
end
2017-08-18 01:27:09 +08:00
end
2016-08-12 23:53:01 +08:00
2017-08-18 01:27:09 +08:00
# Normalize the x,y coordinates
x1 , x2 = x2 , x1 if x1 > x2
y1 , y2 = y2 , y1 if y1 > y2
# SVG's ellipse element doesn't render if r_x or r_y is 0, but
# we want to display a line segment in that case. But the SVG
# path element's elliptical arc code renders r_x or r_y
# degenerate cases as line segments, so we can use that.
path = " M #{ x1 } #{ hy } "
2021-12-06 20:58:04 +08:00
path << " A #{ width_r } #{ height_r } 0 0 1 #{ hx } #{ y1 } "
path << " A #{ width_r } #{ height_r } 0 0 1 #{ x2 } #{ hy } "
path << " A #{ width_r } #{ height_r } 0 0 1 #{ hx } #{ y2 } "
path << " A #{ width_r } #{ height_r } 0 0 1 #{ x1 } #{ hy } "
path << 'Z'
2017-08-18 01:27:09 +08:00
svg_path = doc . create_element ( 'path' , d : path )
g << svg_path
2013-09-26 21:32:55 +08:00
end
2017-08-18 01:27:09 +08:00
def svg_render_shape_text ( g , slide , shape )
g [ 'shape' ] = " text #{ shape [ :shape_unique_id ] } "
doc = g . document
data_points = shape [ :data_points ]
x = shape_scale_width ( slide , data_points [ 0 ] )
y = shape_scale_height ( slide , data_points [ 1 ] )
width = shape_scale_width ( slide , shape [ :text_box_width ] )
height = shape_scale_height ( slide , shape [ :text_box_height ] )
font_size = shape_scale_height ( slide , shape [ :calced_font_size ] )
BigBlueButton . logger . info ( " Text #{ shape [ :shape_unique_id ] } width #{ width } height #{ height } font size #{ font_size } " )
g [ 'style' ] = " color: # #{ shape [ :font_color ] } ;word-wrap:break-word;visibility:hidden;font-family:Arial;font-size: #{ font_size } px "
switch = doc . create_element ( 'switch' )
fo = doc . create_element ( 'foreignObject' ,
2021-12-05 22:53:06 +08:00
width : width , height : height , x : x , y : y )
2017-08-18 01:27:09 +08:00
p = doc . create_element ( 'p' ,
2021-12-05 22:53:06 +08:00
xmlns : 'http://www.w3.org/1999/xhtml' , style : 'margin:0;padding:0' )
2017-08-18 01:27:09 +08:00
shape [ :text ] . each_line . with_index do | line , index |
2021-12-06 02:04:21 +08:00
p << doc . create_element ( 'br' ) if index . positive?
2017-08-18 01:27:09 +08:00
p << doc . create_text_node ( line . chomp )
end
fo << p
switch << fo
g << switch
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
def svg_render_shape_poll ( g , slide , shape )
poll_id = shape [ :shape_unique_id ]
g [ 'shape' ] = " poll #{ poll_id } "
g [ 'style' ] = 'visibility:hidden'
doc = g . document
data_points = shape [ :data_points ]
x = shape_scale_width ( slide , data_points [ 0 ] )
y = shape_scale_height ( slide , data_points [ 1 ] )
width = shape_scale_width ( slide , data_points [ 2 ] )
height = shape_scale_height ( slide , data_points [ 3 ] )
2020-01-14 03:32:10 +08:00
result = shape [ :result ]
2017-08-18 01:27:09 +08:00
num_responders = shape [ :num_responders ]
2018-09-22 02:28:24 +08:00
presentation = slide [ :presentation ]
2015-08-12 02:55:59 +08:00
2021-12-06 02:35:22 +08:00
json_file = " #{ @process_dir } /poll_result #{ poll_id } .json "
svg_file = " #{ @process_dir } /presentation/ #{ presentation } /poll_result #{ poll_id } .svg "
2015-08-12 02:55:59 +08:00
2020-01-14 03:32:10 +08:00
# Save the poll json to a temp file
IO . write ( json_file , result )
# Render the poll svg
2021-12-05 22:53:06 +08:00
ret = BigBlueButton . exec_ret ( 'utils/gen_poll_svg' , '-i' , json_file , '-w' , width . round . to_s , '-h' , height . round . to_s , '-n' , num_responders . to_s , '-o' , svg_file )
raise 'Failed to generate poll svg' if ret != 0
2020-01-14 03:32:10 +08:00
# Poll image
2017-08-18 01:27:09 +08:00
g << doc . create_element ( 'image' ,
2021-12-05 22:53:06 +08:00
'xlink:href' = > " presentation/ #{ presentation } /poll_result #{ poll_id } .svg " ,
width : width , height : height , x : x , y : y )
2017-08-18 01:27:09 +08:00
end
2015-08-12 02:55:59 +08:00
2017-08-22 04:40:53 +08:00
def svg_render_shape ( canvas , slide , shape , image_id )
2017-08-18 01:27:09 +08:00
if shape [ :in ] == shape [ :out ]
BigBlueButton . logger . info ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } is never shown (duration rounds to 0) " )
return
end
2021-12-05 22:53:06 +08:00
if ( shape [ :in ] > = slide [ :out ] ) ||
( ! shape [ :out ] . nil? && ( shape [ :out ] < = slide [ :in ] ) )
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } is not visible during image time span " )
return
end
2015-08-12 02:55:59 +08:00
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } Type #{ shape [ :type ] } from #{ shape [ :in ] } to #{ shape [ :out ] } undo #{ shape [ :undo ] } " )
doc = canvas . document
g = doc . create_element ( 'g' ,
2021-12-05 22:53:06 +08:00
id : " image #{ image_id } -draw #{ shape [ :shape_id ] } " , class : 'shape' ,
timestamp : shape [ :in ] , undo : ( shape [ :undo ] . nil? ? - 1 : shape [ :undo ] ) )
2017-08-18 01:27:09 +08:00
case shape [ :type ]
when 'pencil'
svg_render_shape_pencil ( g , slide , shape )
when 'line'
svg_render_shape_line ( g , slide , shape )
when 'rectangle'
svg_render_shape_rect ( g , slide , shape )
when 'triangle'
svg_render_shape_triangle ( g , slide , shape )
when 'ellipse'
svg_render_shape_ellipse ( g , slide , shape )
when 'text'
svg_render_shape_text ( g , slide , shape )
2017-08-31 02:42:29 +08:00
when 'poll_result'
2017-08-18 01:27:09 +08:00
svg_render_shape_poll ( g , slide , shape )
else
BigBlueButton . logger . warn ( " Ignoring unhandled shape type #{ shape [ :type ] } " )
end
2015-08-12 02:55:59 +08:00
2017-08-22 04:40:53 +08:00
g [ :shape ] = " image #{ image_id } - #{ g [ :shape ] } "
2021-12-05 22:53:06 +08:00
canvas << g unless g . element_children . empty?
2015-08-12 02:55:59 +08:00
end
2021-12-06 02:35:22 +08:00
@svg_image_id = 1
2017-08-18 01:27:09 +08:00
def svg_render_image ( svg , slide , shapes )
if slide [ :in ] == slide [ :out ]
BigBlueButton . logger . info ( " Presentation #{ slide [ :presentation ] } Slide #{ slide [ :slide ] } is never shown (duration rounds to 0) " )
return
end
2016-01-20 03:52:28 +08:00
2021-12-06 02:35:22 +08:00
image_id = @svg_image_id
@svg_image_id += 1
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Image #{ image_id } : Presentation #{ slide [ :presentation ] } Slide #{ slide [ :slide ] } Deskshare #{ slide [ :deskshare ] } from #{ slide [ :in ] } to #{ slide [ :out ] } " )
doc = svg . document
image = doc . create_element ( 'image' ,
2021-12-05 22:53:06 +08:00
id : " image #{ image_id } " , class : 'slide' ,
in : slide [ :in ] , out : slide [ :out ] ,
'xlink:href' = > slide [ :src ] ,
width : slide [ :width ] , height : slide [ :height ] , x : 0 , y : 0 ,
style : 'visibility:hidden' )
image [ 'text' ] = slide [ :text ] unless slide [ :text ] . nil?
2017-08-18 01:27:09 +08:00
svg << image
2021-12-05 22:53:06 +08:00
if slide [ :deskshare ] ||
shapes [ slide [ :presentation ] ] . nil? ||
shapes [ slide [ :presentation ] ] [ slide [ :slide ] ] . nil?
2017-08-18 01:27:09 +08:00
return
2016-01-20 03:52:28 +08:00
end
2017-08-18 01:27:09 +08:00
shapes = shapes [ slide [ :presentation ] ] [ slide [ :slide ] ]
2013-09-19 04:54:51 +08:00
2017-08-18 01:27:09 +08:00
canvas = doc . create_element ( 'g' ,
2021-12-05 22:53:06 +08:00
class : 'canvas' , id : " canvas #{ image_id } " ,
image : " image #{ image_id } " , display : 'none' )
2013-09-19 04:54:51 +08:00
2017-08-18 01:27:09 +08:00
shapes . each do | shape |
2017-08-22 04:40:53 +08:00
svg_render_shape ( canvas , slide , shape , image_id )
2016-01-20 03:52:28 +08:00
end
2017-08-18 01:27:09 +08:00
2021-12-05 22:53:06 +08:00
svg << canvas unless canvas . element_children . empty?
2013-09-26 21:32:55 +08:00
end
2013-09-19 04:54:51 +08:00
2017-08-22 02:44:41 +08:00
def panzoom_viewbox ( panzoom )
2017-08-18 01:27:09 +08:00
if panzoom [ :deskshare ]
panzoom [ :x_offset ] = 0 . 0
panzoom [ :y_offset ] = 0 . 0
panzoom [ :width_ratio ] = 100 . 0
panzoom [ :height_ratio ] = 100 . 0
2017-02-24 02:21:10 +08:00
end
2017-08-18 01:27:09 +08:00
2021-12-06 02:35:22 +08:00
x = ( - panzoom [ :x_offset ] * @magic_mystery_number / 100 . 0 * panzoom [ :width ] ) . round ( 5 )
y = ( - panzoom [ :y_offset ] * @magic_mystery_number / 100 . 0 * panzoom [ :height ] ) . round ( 5 )
2017-08-18 01:27:09 +08:00
w = shape_scale_width ( panzoom , panzoom [ :width_ratio ] )
h = shape_scale_height ( panzoom , panzoom [ :height_ratio ] )
2021-12-05 22:53:06 +08:00
[ x , y , w , h ]
2017-08-22 02:44:41 +08:00
end
def panzooms_emit_event ( rec , panzoom )
if panzoom [ :in ] == panzoom [ :out ]
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Panzoom: not emitting, duration rounds to 0' )
2017-08-22 02:44:41 +08:00
return
end
doc = rec . document
event = doc . create_element ( 'event' , timestamp : panzoom [ :in ] )
x , y , w , h = panzoom_viewbox ( panzoom )
2017-08-18 01:27:09 +08:00
viewbox = doc . create_element ( 'viewBox' )
viewbox . content = " #{ x } #{ y } #{ w } #{ h } "
BigBlueButton . logger . info ( " Panzoom viewbox #{ viewbox . content } at #{ panzoom [ :in ] } " )
event << viewbox
rec << event
2017-02-24 02:21:10 +08:00
end
2017-08-18 01:27:09 +08:00
def cursors_emit_event ( rec , cursor )
if cursor [ :in ] == cursor [ :out ]
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Cursor: not emitting, duration rounds to 0' )
2017-08-18 01:27:09 +08:00
return
end
2016-12-03 03:40:50 +08:00
2017-08-18 01:27:09 +08:00
doc = rec . document
event = doc . create_element ( 'event' , timestamp : cursor [ :in ] )
2017-08-03 04:50:24 +08:00
2017-08-22 02:44:41 +08:00
panzoom = cursor [ :panzoom ]
2017-08-18 01:27:09 +08:00
if cursor [ :visible ]
2021-12-06 02:35:22 +08:00
if @version_atleast_2_0_0
2017-08-22 02:44:41 +08:00
# In BBB 2.0, the cursor now uses the same coordinate system as annotations
# Use the panzoom information to convert it to be relative to viewbox
2021-12-06 02:35:22 +08:00
x = ( ( ( cursor [ :x ] / 100 . 0 ) + ( panzoom [ :x_offset ] * @magic_mystery_number / 100 . 0 ) ) /
2017-08-22 02:44:41 +08:00
( panzoom [ :width_ratio ] / 100 . 0 ) ) . round ( 5 )
2021-12-06 02:35:22 +08:00
y = ( ( ( cursor [ :y ] / 100 . 0 ) + ( panzoom [ :y_offset ] * @magic_mystery_number / 100 . 0 ) ) /
2017-08-22 02:44:41 +08:00
( panzoom [ :height_ratio ] / 100 . 0 ) ) . round ( 5 )
2021-12-06 02:04:21 +08:00
if x . negative? || ( x > 1 ) || y . negative? || ( y > 1 )
2017-08-22 02:44:41 +08:00
x = - 1 . 0
y = - 1 . 0
end
else
# Cursor position is relative to the visible area
x = cursor [ :x ] . round ( 5 )
y = cursor [ :y ] . round ( 5 )
end
2017-08-18 01:27:09 +08:00
else
x = - 1 . 0
y = - 1 . 0
end
2017-08-03 04:50:24 +08:00
2017-08-18 01:27:09 +08:00
cursor_e = doc . create_element ( 'cursor' )
cursor_e . content = " #{ x } #{ y } "
2016-11-30 01:08:28 +08:00
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Cursor #{ cursor_e . content } at #{ cursor [ :in ] } " )
2017-02-24 02:21:10 +08:00
2017-08-18 01:27:09 +08:00
event << cursor_e
rec << event
end
2017-08-03 04:50:24 +08:00
2021-12-06 02:35:22 +08:00
@svg_shape_id = 1
@svg_shape_unique_id = 1
2021-12-06 19:40:11 +08:00
def determine_presentation ( presentation , current_presentation )
return current_presentation if presentation . nil?
presentation . text
end
def determine_slide_number ( slide , current_slide )
return current_slide if slide . nil?
slide = slide . text . to_i
slide -= 1 unless @version_atleast_0_9_0
slide
end
2017-08-18 01:27:09 +08:00
def events_parse_shape ( shapes , event , current_presentation , current_slide , timestamp )
# Figure out what presentation+slide this shape is for, with fallbacks
# for old BBB where this info isn't in the shape messages
presentation = event . at_xpath ( 'presentation' )
slide = event . at_xpath ( 'pageNumber' )
2021-12-06 19:40:11 +08:00
presentation = determine_presentation ( presentation , current_presentation )
slide = determine_slide_number ( slide , current_slide )
2017-08-03 04:50:24 +08:00
2017-08-18 01:27:09 +08:00
# Set up the shapes data structures if needed
shapes [ presentation ] = { } if shapes [ presentation ] . nil?
shapes [ presentation ] [ slide ] = [ ] if shapes [ presentation ] [ slide ] . nil?
# We only need to deal with shapes for this slide
shapes = shapes [ presentation ] [ slide ]
# Set up the structure for this shape
shape = { }
# Common properties
shape [ :in ] = timestamp
shape [ :type ] = event . at_xpath ( 'type' ) . text
2021-12-05 22:53:06 +08:00
shape [ :data_points ] = event . at_xpath ( 'dataPoints' ) . text . split ( ',' ) . map ( & :to_f )
2017-08-18 01:27:09 +08:00
# These can be missing in old BBB versions, there are fallbacks
user_id = event . at_xpath ( 'userId' )
2021-12-05 22:53:06 +08:00
shape [ :user_id ] = user_id . text unless user_id . nil?
2017-08-18 01:27:09 +08:00
shape_id = event . at_xpath ( 'id' )
2021-12-05 22:53:06 +08:00
shape [ :id ] = shape_id . text unless shape_id . nil?
2017-08-18 01:27:09 +08:00
status = event . at_xpath ( 'status' )
2021-12-05 22:53:06 +08:00
shape [ :status ] = status . text unless status . nil?
2021-12-06 02:35:22 +08:00
shape [ :shape_id ] = @svg_shape_id
@svg_shape_id += 1
2017-08-18 01:27:09 +08:00
# Some shape-specific properties
2021-12-06 19:40:11 +08:00
if %w[ ellipse line pencil rectangle triangle ] . include? ( shape [ :type ] )
2017-08-18 01:27:09 +08:00
shape [ :color ] = color_to_hex ( event . at_xpath ( 'color' ) . text )
2020-09-26 04:13:29 +08:00
thickness = event . at_xpath ( 'thickness' )
unless thickness
BigBlueButton . logger . warn ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } ID #{ shape [ :id ] } is missing thickness " )
return
end
2021-12-06 02:35:22 +08:00
if @version_atleast_2_0_0
2020-09-26 04:13:29 +08:00
shape [ :thickness_percent ] = thickness . text . to_f
2017-08-03 04:50:24 +08:00
else
2020-09-26 04:13:29 +08:00
shape [ :thickness ] = thickness . text . to_i
2017-08-03 04:50:24 +08:00
end
2017-08-18 01:27:09 +08:00
end
2021-12-06 19:40:11 +08:00
if %w[ ellipse rectangle triangle ] . include? ( shape [ :type ] )
2021-12-05 22:53:06 +08:00
# TODO: uncomment this
# fill = event.at_xpath('fill').text
# shape[:fill] = fill =~ /true/ ? true : false
shape [ :fill ] = false
2020-10-28 14:03:40 +08:00
end
2021-12-06 19:40:11 +08:00
case shape [ :type ]
when 'rectangle'
2017-08-18 01:27:09 +08:00
square = event . at_xpath ( 'square' )
2021-12-05 22:53:06 +08:00
shape [ :square ] = ( square . text == 'true' ) unless square . nil?
2021-12-06 19:40:11 +08:00
when 'ellipse'
2017-08-18 01:27:09 +08:00
circle = event . at_xpath ( 'circle' )
2021-12-05 22:53:06 +08:00
shape [ :circle ] = ( circle . text == 'true' ) unless circle . nil?
2021-12-06 19:40:11 +08:00
when 'pencil'
2017-08-18 01:27:09 +08:00
commands = event . at_xpath ( 'commands' )
2021-12-05 22:53:06 +08:00
shape [ :commands ] = commands . text . split ( ',' ) . map ( & :to_i ) unless commands . nil?
2021-12-06 19:40:11 +08:00
when 'poll_result'
2017-08-31 02:51:52 +08:00
shape [ :num_responders ] = event . at_xpath ( 'num_responders' ) . text . to_i
shape [ :num_respondents ] = event . at_xpath ( 'num_respondents' ) . text . to_i
2017-08-18 01:27:09 +08:00
shape [ :result ] = event . at_xpath ( 'result' ) . text
2021-12-06 19:40:11 +08:00
when 'text'
2017-08-18 01:27:09 +08:00
shape [ :text_box_width ] = event . at_xpath ( 'textBoxWidth' ) . text . to_f
shape [ :text_box_height ] = event . at_xpath ( 'textBoxHeight' ) . text . to_f
2020-04-07 00:46:57 +08:00
calced_font_size = event . at_xpath ( 'calcedFontSize' )
unless calced_font_size
BigBlueButton . logger . warn ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } ID #{ shape [ :id ] } is missing calcedFontSize " )
return
end
shape [ :calced_font_size ] = calced_font_size . text . to_f
2017-08-18 01:27:09 +08:00
shape [ :font_color ] = color_to_hex ( event . at_xpath ( 'fontColor' ) . text )
text = event . at_xpath ( 'text' )
2021-12-06 19:40:11 +08:00
shape [ :text ] = ! text . nil? ? text . text : ''
2017-08-18 01:27:09 +08:00
end
2017-08-03 04:50:24 +08:00
2017-08-18 01:27:09 +08:00
# Find the previous shape, for updates
prev_shape = nil
if ! shape [ :id ] . nil?
# If we have a shape ID, look up the previous shape by ID
2021-12-05 22:53:06 +08:00
prev_shape = shapes . find_all { | s | s [ :id ] == shape [ :id ] } . last
2017-08-18 01:27:09 +08:00
else
# No shape ID, so do heuristic matching. If the previous shape had the
# same type and same first two data points, update it.
last_shape = shapes . last
2021-12-05 22:53:06 +08:00
if ( last_shape [ :type ] == shape [ :type ] ) &&
( last_shape [ :data_points ] [ 0 ] == shape [ :data_points ] [ 0 ] ) &&
( last_shape [ :data_points ] [ 1 ] == shape [ :data_points ] [ 1 ] )
2017-08-18 01:27:09 +08:00
prev_shape = last_shape
2017-08-03 04:50:24 +08:00
end
2017-08-18 01:27:09 +08:00
end
if ! prev_shape . nil?
prev_shape [ :out ] = timestamp
shape [ :shape_unique_id ] = prev_shape [ :shape_unique_id ]
2021-12-05 22:53:06 +08:00
if ( shape [ :type ] == 'pencil' ) && ( shape [ :status ] == 'DRAW_UPDATE' )
2017-08-18 01:27:09 +08:00
# BigBlueButton 2.0 quirk - 'DRAW_UPDATE' events on pencil tool only
# include newly added points, rather than the full list.
shape [ :data_points ] = prev_shape [ :data_points ] + shape [ :data_points ]
end
else
2021-12-06 02:35:22 +08:00
shape [ :shape_unique_id ] = @svg_shape_unique_id
@svg_shape_unique_id += 1
2017-08-18 01:27:09 +08:00
end
BigBlueButton . logger . info ( " Draw #{ shape [ :shape_id ] } Shape #{ shape [ :shape_unique_id ] } ID #{ shape [ :id ] } Type #{ shape [ :type ] } " )
shapes << shape
end
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
def events_parse_undo ( shapes , event , current_presentation , current_slide , timestamp )
# Figure out what presentation+slide this undo is for, with fallbacks
# for old BBB where this info isn't in the undo messages
presentation = event . at_xpath ( 'presentation' )
slide = event . at_xpath ( 'pageNumber' )
2021-12-06 19:40:11 +08:00
presentation = determine_presentation ( presentation , current_presentation )
slide = determine_slide_number ( slide , current_slide )
2017-08-18 01:27:09 +08:00
# Newer undo messages have the shape id, making this a lot easier
shape_id = event . at_xpath ( 'shapeId' )
2021-12-05 22:53:06 +08:00
shape_id = shape_id . text unless shape_id . nil?
2017-08-18 01:27:09 +08:00
# Set up the shapes data structures if needed
shapes [ presentation ] = { } if shapes [ presentation ] . nil?
shapes [ presentation ] [ slide ] = [ ] if shapes [ presentation ] [ slide ] . nil?
# We only need to deal with shapes for this slide
shapes = shapes [ presentation ] [ slide ]
if ! shape_id . nil?
# If we have the shape id, we simply have to update the undo time on
# all the shapes with that id.
BigBlueButton . logger . info ( " Undo: removing shape with ID #{ shape_id } at #{ timestamp } " )
shapes . each do | shape |
2021-12-05 22:53:06 +08:00
next unless shape [ :id ] == shape_id
shape [ :undo ] = timestamp if shape [ :undo ] . nil? || ( shape [ :undo ] > timestamp )
2017-08-18 01:27:09 +08:00
end
else
# The undo command removes the most recently added shape that has not
# already been removed by another undo or clear. Find that shape.
undo_shape = shapes . select { | s | s [ :undo ] . nil? } . last
if ! undo_shape . nil?
BigBlueButton . logger . info ( " Undo: removing Shape #{ undo_shape [ :shape_unique_id ] } at #{ timestamp } " )
# We have an id number assigned to associate all the updated versions
# of the same shape. Use that to determine which shapes to apply undo
# times to.
shapes . each do | shape |
2021-12-05 22:53:06 +08:00
next unless shape [ :shape_unique_id ] == undo_shape [ :shape_unique_id ]
shape [ :undo ] = timestamp if shape [ :undo ] . nil? || ( shape [ :undo ] > timestamp )
2017-08-18 01:27:09 +08:00
end
2017-08-03 22:40:17 +08:00
else
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Undo: no applicable shapes found' )
2016-01-20 03:52:28 +08:00
end
2017-08-03 22:50:14 +08:00
end
2012-07-18 03:39:44 +08:00
end
2017-08-18 01:27:09 +08:00
def events_parse_clear ( shapes , event , current_presentation , current_slide , timestamp )
# Figure out what presentation+slide this clear is for, with fallbacks
# for old BBB where this info isn't in the clear messages
presentation = event . at_xpath ( 'presentation' )
slide = event . at_xpath ( 'pageNumber' )
2021-12-06 19:40:11 +08:00
presentation = determine_presentation ( presentation , current_presentation )
slide = determine_slide_number ( slide , current_slide )
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
# BigBlueButton 2.0 per-user clear features
full_clear = event . at_xpath ( 'fullClear' )
2021-12-05 22:53:06 +08:00
full_clear = if ! full_clear . nil?
( full_clear . text == 'true' )
else
# Default to full clear on older versions
true
end
2017-08-18 01:27:09 +08:00
user_id = event . at_xpath ( 'userId' )
2021-12-05 22:53:06 +08:00
user_id = user_id . text unless user_id . nil?
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
# Set up the shapes data structures if needed
shapes [ presentation ] = { } if shapes [ presentation ] . nil?
shapes [ presentation ] [ slide ] = [ ] if shapes [ presentation ] [ slide ] . nil?
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
# We only need to deal with shapes for this slide
shapes = shapes [ presentation ] [ slide ]
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
if full_clear
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Clear: removing all shapes' )
2017-08-18 01:27:09 +08:00
else
BigBlueButton . logger . info ( " Clear: removing shapes for User #{ user_id } " )
end
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
shapes . each do | shape |
2021-12-05 22:53:06 +08:00
if full_clear || ( user_id == shape [ :user_id ] )
shape [ :undo ] = timestamp if shape [ :undo ] . nil? || ( shape [ :undo ] > timestamp )
2017-08-18 01:27:09 +08:00
end
end
end
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
def events_get_image_info ( slide )
if slide [ :deskshare ]
slide [ :src ] = 'presentation/deskshare.png'
elsif slide [ :presentation ] == ''
2017-08-19 04:39:18 +08:00
slide [ :src ] = 'presentation/logo.png'
2017-08-19 04:56:45 +08:00
else
2017-08-19 05:07:14 +08:00
slide [ :src ] = " presentation/ #{ slide [ :presentation ] } /slide- #{ slide [ :slide ] + 1 } .png "
slide [ :text ] = " presentation/ #{ slide [ :presentation ] } /textfiles/slide- #{ slide [ :slide ] + 1 } .txt "
2017-08-18 01:27:09 +08:00
end
2021-12-06 02:35:22 +08:00
image_path = " #{ @process_dir } / #{ slide [ :src ] } "
2020-06-25 00:12:19 +08:00
unless File . exist? ( image_path )
2017-08-19 04:39:18 +08:00
BigBlueButton . logger . warn ( " Missing image file #{ image_path } ! " )
2017-08-18 01:27:09 +08:00
# Emergency last-ditch blank image creation
2017-08-19 04:39:18 +08:00
FileUtils . mkdir_p ( File . dirname ( image_path ) )
2020-06-25 00:12:19 +08:00
command = \
if slide [ :deskshare ]
2021-12-06 02:35:22 +08:00
[ 'convert' , '-size' , " #{ @presentation_props [ 'deskshare_output_width' ] } x #{ @presentation_props [ 'deskshare_output_height' ] } " , 'xc:transparent' , '-background' , 'transparent' , image_path ]
2020-06-25 00:12:19 +08:00
else
[ 'convert' , '-size' , '1600x1200' , 'xc:transparent' , '-background' , 'transparent' , '-quality' , '90' , '+dither' , '-depth' , '8' , '-colors' , '256' , image_path ]
end
BigBlueButton . exec_ret ( * command ) || raise ( " Unable to generate blank image for #{ image_path } " )
2017-08-18 01:27:09 +08:00
end
2020-06-25 00:12:19 +08:00
2017-08-19 04:39:18 +08:00
slide [ :width ] , slide [ :height ] = FastImage . size ( image_path )
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( " Image size is #{ slide [ :width ] } x #{ slide [ :height ] } " )
end
2016-01-20 03:52:28 +08:00
2017-08-18 01:27:09 +08:00
# Create the shapes.svg, cursors.xml, and panzooms.xml files used for
# rendering the presentation area
2021-12-06 03:09:46 +08:00
def process_presentation ( package_dir )
2021-12-05 22:53:06 +08:00
shapes_doc = Nokogiri :: XML :: Document . new
2017-08-18 01:27:09 +08:00
shapes_doc . create_internal_subset ( 'svg' , '-//W3C//DTD SVG 1.1//EN' ,
2021-12-05 22:53:06 +08:00
'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' )
2017-08-18 01:27:09 +08:00
svg = shapes_doc . root = shapes_doc . create_element ( 'svg' ,
2021-12-05 22:53:06 +08:00
id : 'svgfile' ,
style : 'position:absolute;height:600px;width:800px' ,
xmlns : 'http://www.w3.org/2000/svg' ,
'xmlns:xlink' = > 'http://www.w3.org/1999/xlink' ,
version : '1.1' ,
viewBox : '0 0 800 600' )
panzooms_doc = Nokogiri :: XML :: Document . new
2017-08-18 01:27:09 +08:00
panzooms_rec = panzooms_doc . root = panzooms_doc . create_element ( 'recording' ,
2021-12-05 22:53:06 +08:00
id : 'panzoom_events' )
2017-08-18 01:27:09 +08:00
2021-12-05 22:53:06 +08:00
cursors_doc = Nokogiri :: XML :: Document . new
2017-08-18 01:27:09 +08:00
cursors_rec = cursors_doc . root = cursors_doc . create_element ( 'recording' ,
2021-12-05 22:53:06 +08:00
id : 'cursor_events' )
2017-08-18 01:27:09 +08:00
# Current presentation/slide state
current_presentation_slide = { }
current_presentation = ''
current_slide = 0
# Current pan/zoom state
current_x_offset = 0 . 0
current_y_offset = 0 . 0
current_width_ratio = 100 . 0
current_height_ratio = 100 . 0
# Current cursor status
cursor_x = - 1 . 0
cursor_y = - 1 . 0
cursor_visible = false
presenter = nil
# Current deskshare state (affects presentation and pan/zoom)
deskshare = false
slides = [ ]
panzooms = [ ]
cursors = [ ]
shapes = { }
# Iterate through the events.xml and store the events, building the
# xml files as we go
last_timestamp = 0 . 0
2021-12-06 02:35:22 +08:00
events_xml = Nokogiri :: XML ( File . read ( " #{ @process_dir } /events.xml " ) )
2017-08-18 01:27:09 +08:00
events_xml . xpath ( '/recording/event' ) . each do | event |
eventname = event [ 'eventname' ]
last_timestamp = timestamp =
2021-12-06 03:09:46 +08:00
( translate_timestamp ( event [ 'timestamp' ] ) / 1000 . 0 ) . round ( 1 )
2017-08-18 01:27:09 +08:00
# Make sure to add initial entries to the slide & panzoom lists
slide_changed = slides . empty?
panzoom_changed = panzooms . empty?
cursor_changed = cursors . empty?
# Do event specific processing
2021-12-06 19:40:11 +08:00
case eventname
when 'SharePresentationEvent'
2017-08-18 01:27:09 +08:00
current_presentation = event . at_xpath ( 'presentationName' ) . text
current_slide = current_presentation_slide [ current_presentation ] . to_i
slide_changed = true
panzoom_changed = true
2016-01-20 03:52:28 +08:00
2021-12-06 19:40:11 +08:00
when 'GotoSlideEvent'
2017-08-18 01:27:09 +08:00
current_slide = event . at_xpath ( 'slide' ) . text . to_i
current_presentation_slide [ current_presentation ] = current_slide
slide_changed = true
panzoom_changed = true
2021-12-06 19:40:11 +08:00
when 'ResizeAndMoveSlideEvent'
2017-08-18 01:27:09 +08:00
current_x_offset = event . at_xpath ( 'xOffset' ) . text . to_f
current_y_offset = event . at_xpath ( 'yOffset' ) . text . to_f
current_width_ratio = event . at_xpath ( 'widthRatio' ) . text . to_f
current_height_ratio = event . at_xpath ( 'heightRatio' ) . text . to_f
panzoom_changed = true
2021-12-06 19:40:11 +08:00
when 'DeskshareStartedEvent' , 'StartWebRTCDesktopShareEvent'
if @presentation_props [ 'include_deskshare' ]
deskshare = true
slide_changed = true
end
2017-08-18 01:27:09 +08:00
2021-12-06 19:40:11 +08:00
when 'DeskshareStoppedEvent' , 'StopWebRTCDesktopShareEvent'
if @presentation_props [ 'include_deskshare' ]
deskshare = false
slide_changed = true
end
2017-08-18 01:27:09 +08:00
2021-12-06 19:40:11 +08:00
when 'AddShapeEvent' , 'ModifyTextEvent'
2017-08-18 01:27:09 +08:00
events_parse_shape ( shapes , event , current_presentation , current_slide , timestamp )
2021-12-06 19:40:11 +08:00
when 'UndoShapeEvent' , 'UndoAnnotationEvent'
2017-08-18 01:27:09 +08:00
events_parse_undo ( shapes , event , current_presentation , current_slide , timestamp )
2021-12-06 19:40:11 +08:00
when 'ClearPageEvent' , 'ClearWhiteboardEvent'
2017-08-18 01:27:09 +08:00
events_parse_clear ( shapes , event , current_presentation , current_slide , timestamp )
2021-12-06 19:40:11 +08:00
when 'AssignPresenterEvent'
2017-08-18 01:27:09 +08:00
# Move cursor offscreen on presenter switch, it'll reappear if the new
# presenter moves it
2017-08-18 02:29:54 +08:00
presenter = event . at_xpath ( 'userid' ) . text
2017-08-18 01:27:09 +08:00
cursor_visible = false
cursor_changed = true
2021-12-06 19:40:11 +08:00
when 'CursorMoveEvent'
2017-08-22 02:44:41 +08:00
cursor_x = event . at_xpath ( 'xOffset' ) . text . to_f
cursor_y = event . at_xpath ( 'yOffset' ) . text . to_f
2017-08-18 01:27:09 +08:00
cursor_visible = true
cursor_changed = true
2021-12-06 19:40:11 +08:00
when 'WhiteboardCursorMoveEvent'
2017-08-18 01:27:09 +08:00
user_id = event . at_xpath ( 'userId' )
# Only draw cursor for current presentor. TODO multi-cursor support
2021-12-05 22:53:06 +08:00
if user_id . nil? || ( user_id . text == presenter )
2017-08-18 01:27:09 +08:00
cursor_x = event . at_xpath ( 'xOffset' ) . text . to_f
cursor_y = event . at_xpath ( 'yOffset' ) . text . to_f
cursor_visible = true
cursor_changed = true
end
end
# Perform slide finalization
if slide_changed
slide = slides . last
2021-12-05 22:53:06 +08:00
if ! slide . nil? &&
( slide [ :presentation ] == current_presentation ) &&
( slide [ :slide ] == current_slide ) &&
( slide [ :deskshare ] == deskshare )
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( 'Presentation/Slide: skipping, no changes' )
2021-12-06 02:04:21 +08:00
# slide_changed = false
2017-08-18 01:27:09 +08:00
else
2021-12-05 22:53:06 +08:00
unless slide . nil?
2017-08-18 01:27:09 +08:00
slide [ :out ] = timestamp
svg_render_image ( svg , slide , shapes )
end
BigBlueButton . logger . info ( " Presentation #{ current_presentation } Slide #{ current_slide } Deskshare #{ deskshare } " )
slide = {
presentation : current_presentation ,
slide : current_slide ,
in : timestamp ,
deskshare : deskshare
}
events_get_image_info ( slide )
slides << slide
end
end
# Perform panzoom finalization
if panzoom_changed
slide = slides . last
panzoom = panzooms . last
2021-12-05 22:53:06 +08:00
if ! panzoom . nil? &&
( panzoom [ :x_offset ] == current_x_offset ) &&
( panzoom [ :y_offset ] == current_y_offset ) &&
( panzoom [ :width_ratio ] == current_width_ratio ) &&
( panzoom [ :height_ratio ] == current_height_ratio ) &&
( panzoom [ :width ] == slide [ :width ] ) &&
( panzoom [ :height ] == slide [ :height ] ) &&
( panzoom [ :deskshare ] == deskshare )
2017-08-18 01:27:09 +08:00
BigBlueButton . logger . info ( 'Panzoom: skipping, no changes' )
2017-08-22 02:44:41 +08:00
panzoom_changed = false
2017-08-18 01:27:09 +08:00
else
2021-12-05 22:53:06 +08:00
unless panzoom . nil?
2017-08-18 01:27:09 +08:00
panzoom [ :out ] = timestamp
panzooms_emit_event ( panzooms_rec , panzoom )
end
BigBlueButton . logger . info ( " Panzoom: #{ current_x_offset } #{ current_y_offset } #{ current_width_ratio } #{ current_height_ratio } ( #{ slide [ :width ] } x #{ slide [ :height ] } ) " )
panzoom = {
x_offset : current_x_offset ,
y_offset : current_y_offset ,
width_ratio : current_width_ratio ,
height_ratio : current_height_ratio ,
width : slide [ :width ] ,
height : slide [ :height ] ,
in : timestamp ,
2021-12-05 22:53:06 +08:00
deskshare : deskshare
2017-08-18 01:27:09 +08:00
}
panzooms << panzoom
end
end
# Perform cursor finalization
2021-12-05 22:53:06 +08:00
next unless cursor_changed || panzoom_changed
unless ( cursor_x > = 0 ) && ( cursor_x < = 100 ) &&
cursor_y > = 0 && ( cursor_y < = 100 )
cursor_visible = false
end
2017-08-18 01:27:09 +08:00
2021-12-05 22:53:06 +08:00
panzoom = panzooms . last
cursor = cursors . last
if ! cursor . nil? &&
( ( ! cursor [ :visible ] && ! cursor_visible ) ||
( ( cursor [ :x ] == cursor_x ) && ( cursor [ :y ] == cursor_y ) ) ) &&
! panzoom_changed
BigBlueButton . logger . info ( 'Cursor: skipping, no changes' )
else
unless cursor . nil?
cursor [ :out ] = timestamp
cursors_emit_event ( cursors_rec , cursor )
2016-01-20 03:52:28 +08:00
end
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( " Cursor: visible #{ cursor_visible } , #{ cursor_x } #{ cursor_y } ( #{ panzoom [ :width ] } x #{ panzoom [ :height ] } ) " )
cursor = {
visible : cursor_visible ,
x : cursor_x ,
y : cursor_y ,
panzoom : panzoom ,
in : timestamp
}
cursors << cursor
2016-01-20 03:52:28 +08:00
end
end
2017-08-18 01:27:09 +08:00
# Add the last slide, panzoom, and cursor
slide = slides . last
slide [ :out ] = last_timestamp
svg_render_image ( svg , slide , shapes )
panzoom = panzooms . last
panzoom [ :out ] = last_timestamp
panzooms_emit_event ( panzooms_rec , panzoom )
cursor = cursors . last
cursor [ :out ] = last_timestamp
cursors_emit_event ( cursors_rec , cursor )
# And save the result
2021-12-06 02:35:22 +08:00
File . write ( " #{ package_dir } / #{ @shapes_svg_filename } " , shapes_doc . to_xml )
File . write ( " #{ package_dir } / #{ @panzooms_xml_filename } " , panzooms_doc . to_xml )
File . write ( " #{ package_dir } / #{ @cursor_xml_filename } " , cursors_doc . to_xml )
2012-07-18 03:39:44 +08:00
end
2021-12-06 03:09:46 +08:00
def process_chat_messages ( events , bbb_props )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Processing chat events' )
2016-01-20 03:52:28 +08:00
# Create slides.xml and chat.
2021-08-31 05:46:52 +08:00
Nokogiri :: XML :: Builder . new do | xml |
2021-12-05 22:53:06 +08:00
xml . popcorn do
2021-12-06 02:35:22 +08:00
BigBlueButton :: Events . get_chat_events ( events , @meeting_start . to_i , @meeting_end . to_i , bbb_props ) . each do | chat |
2021-08-31 05:46:52 +08:00
chattimeline = {
in : ( chat [ :in ] / 1000 . 0 ) . round ( 1 ) ,
direction : 'down' ,
name : chat [ :sender ] ,
message : chat [ :message ] ,
target : 'chat'
}
chattimeline [ :out ] = ( chat [ :out ] / 1000 . 0 ) . round ( 1 ) unless chat [ :out ] . nil?
xml . chattimeline ( ** chattimeline )
2016-01-20 03:52:28 +08:00
end
2021-12-05 22:53:06 +08:00
end
2016-01-20 03:52:28 +08:00
end
2012-07-18 03:39:44 +08:00
end
2021-12-06 03:09:46 +08:00
def process_deskshare_events ( events )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Processing deskshare events' )
2017-11-08 23:12:25 +08:00
deskshare_matched_events = BigBlueButton :: Events . get_matched_start_and_stop_deskshare_events ( events )
2016-09-10 05:31:27 +08:00
2021-12-06 02:35:22 +08:00
@deskshare_xml = Nokogiri :: XML :: Builder . new do | xml |
@xml = xml
@xml . recording ( 'id' = > 'deskshare_events' ) do
2017-02-24 02:21:10 +08:00
deskshare_matched_events . each do | event |
2021-12-06 03:09:46 +08:00
start_timestamp = ( translate_timestamp ( event [ :start_timestamp ] . to_f ) / 1000 ) . round ( 1 )
stop_timestamp = ( translate_timestamp ( event [ :stop_timestamp ] . to_f ) / 1000 ) . round ( 1 )
2021-12-05 22:53:06 +08:00
next unless start_timestamp != stop_timestamp
2021-12-06 02:35:22 +08:00
video_info = BigBlueButton :: EDL :: Video . video_info ( " #{ @deskshare_dir } / #{ event [ :stream ] } " )
2021-12-05 22:53:06 +08:00
unless video_info [ :video ]
BigBlueButton . logger . warn ( " #{ event [ :stream ] } is not a valid video file, skipping... " )
next
2016-09-10 05:31:27 +08:00
end
2021-12-06 03:09:46 +08:00
video_width , video_height = get_deskshare_video_dimension ( event [ :stream ] )
2021-12-06 02:35:22 +08:00
@xml . event ( start_timestamp : start_timestamp ,
2021-12-05 22:53:06 +08:00
stop_timestamp : stop_timestamp ,
video_width : video_width ,
video_height : video_height )
2016-09-10 05:31:27 +08:00
end
end
end
end
2021-12-06 03:09:46 +08:00
def get_poll_question ( event )
2021-12-05 22:53:06 +08:00
question = ''
question = event . at_xpath ( 'question' ) . text unless event . at_xpath ( 'question' ) . nil?
2021-06-18 05:36:45 +08:00
question
end
2021-12-06 03:09:46 +08:00
def get_poll_answers ( event )
2021-06-18 05:36:45 +08:00
answers = [ ]
2021-12-06 02:04:21 +08:00
answers = JSON . parse ( event . at_xpath ( 'answers' ) . content ) unless event . at_xpath ( 'answers' ) . nil?
2021-06-18 05:36:45 +08:00
answers
end
2021-12-06 03:09:46 +08:00
def get_poll_respondents ( event )
2021-06-18 05:36:45 +08:00
respondents = 0
2021-12-05 22:53:06 +08:00
respondents = event . at_xpath ( 'numRespondents' ) . text . to_i unless event . at_xpath ( 'numRespondents' ) . nil?
2021-06-18 05:36:45 +08:00
respondents
end
2021-12-06 03:09:46 +08:00
def get_poll_responders ( event )
2021-06-18 05:36:45 +08:00
responders = 0
2021-12-05 22:53:06 +08:00
responders = event . at_xpath ( 'numResponders' ) . text . to_i unless event . at_xpath ( 'numResponders' ) . nil?
2021-06-18 05:36:45 +08:00
responders
end
2021-12-06 03:09:46 +08:00
def get_poll_id ( event )
2021-12-05 22:53:06 +08:00
id = ''
id = event . at_xpath ( 'pollId' ) . text unless event . at_xpath ( 'pollId' ) . nil?
2021-06-18 05:36:45 +08:00
id
end
2021-12-06 03:09:46 +08:00
def get_poll_type ( events , published_poll_event )
published_poll_id = get_poll_id ( published_poll_event )
2021-06-18 05:36:45 +08:00
2021-12-05 22:53:06 +08:00
type = ''
2021-06-18 05:36:45 +08:00
events . xpath ( " //event[@eventname='PollStartedRecordEvent'] " ) . each do | event |
2021-12-06 03:09:46 +08:00
poll_id = get_poll_id ( event )
2021-06-18 05:36:45 +08:00
if poll_id . eql? ( published_poll_id )
2021-12-05 22:53:06 +08:00
type = event . at_xpath ( 'type' ) . text
2021-06-18 05:36:45 +08:00
break
end
end
type
end
2021-12-06 03:09:46 +08:00
def process_poll_events ( events , package_dir )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Processing poll events' )
2021-06-18 05:36:45 +08:00
published_polls = [ ]
2021-12-06 02:35:22 +08:00
@rec_events . each do | re |
2021-06-18 05:36:45 +08:00
events . xpath ( " //event[@eventname='PollPublishedRecordEvent'] " ) . each do | event |
2021-12-05 22:53:06 +08:00
next unless ( event [ :timestamp ] . to_i > = re [ :start_timestamp ] ) && ( event [ :timestamp ] . to_i < = re [ :stop_timestamp ] )
published_polls << {
2021-12-06 03:09:46 +08:00
timestamp : ( translate_timestamp ( event [ :timestamp ] ) / 1000 ) . to_i ,
type : get_poll_type ( events , event ) ,
question : get_poll_question ( event ) ,
answers : get_poll_answers ( event ) ,
respondents : get_poll_respondents ( event ) ,
responders : get_poll_responders ( event )
2021-12-05 22:53:06 +08:00
}
2021-06-18 05:36:45 +08:00
end
end
2021-12-06 02:04:21 +08:00
File . open ( " #{ package_dir } /polls.json " , 'w' ) { | f | f . puts ( published_polls . to_json ) } unless published_polls . empty?
2021-06-18 05:36:45 +08:00
end
2021-12-06 03:09:46 +08:00
def process_external_video_events ( _events , package_dir )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Processing external video events' )
2021-07-07 21:26:24 +08:00
# Retrieve external video events
external_video_events = BigBlueButton :: Events . match_start_and_stop_external_video_events (
2021-12-05 22:53:06 +08:00
BigBlueButton :: Events . get_start_and_stop_external_video_events ( @doc )
)
2021-07-07 21:26:24 +08:00
external_videos = [ ]
2021-12-06 02:35:22 +08:00
@rec_events . each do | re |
2021-07-07 21:26:24 +08:00
external_video_events . each do | event |
2021-12-05 22:53:06 +08:00
# BigBlueButton.logger.info("Processing rec event #{re} and external video event #{event}")
2021-12-06 03:09:46 +08:00
timestamp = ( translate_timestamp ( event [ :start_timestamp ] ) / 1000 ) . to_i
2021-07-07 21:26:24 +08:00
# do not add same external_video twice
2021-12-05 22:53:06 +08:00
next unless external_videos . find { | ev | ev [ :timestamp ] == timestamp } . nil?
if ( ( event [ :start_timestamp ] > = re [ :start_timestamp ] ) && ( event [ :start_timestamp ] < = re [ :stop_timestamp ] ) ) ||
( ( event [ :start_timestamp ] < re [ :start_timestamp ] ) && ( event [ :stop_timestamp ] > = re [ :start_timestamp ] ) )
external_videos << {
timestamp : timestamp ,
external_video_url : event [ :external_video_url ]
}
2021-07-07 21:26:24 +08:00
end
end
end
2021-12-06 03:09:46 +08:00
File . open ( " #{ package_dir } /external_videos.json " , 'w' ) { | f | f . puts ( external_videos . to_json ) } unless external_videos . empty?
2021-07-07 21:26:24 +08:00
end
2021-12-06 02:35:22 +08:00
@shapes_svg_filename = 'shapes.svg'
@panzooms_xml_filename = 'panzooms.xml'
@cursor_xml_filename = 'cursor.xml'
@deskshare_xml_filename = 'deskshare.xml'
2012-07-18 03:39:44 +08:00
2021-12-05 22:53:06 +08:00
opts = Trollop . options do
opt :meeting_id , 'Meeting id to archive' , default : '58f4a6b3-cd07-444d-8564-59116cb53974' , type : String
2012-07-18 03:39:44 +08:00
end
2021-12-06 02:35:22 +08:00
@meeting_id = opts [ :meeting_id ]
puts @meeting_id
match = / (.*)-(.*) / . match @meeting_id
@meeting_id = match [ 1 ]
@playback = match [ 2 ]
2012-07-18 03:39:44 +08:00
2021-12-06 02:35:22 +08:00
puts @meeting_id
puts @playback
2014-04-15 06:14:14 +08:00
begin
2021-12-06 02:35:22 +08:00
if @playback == 'presentation'
2014-04-04 04:23:49 +08:00
log_dir = bbb_props [ 'log_dir' ]
2021-12-06 02:35:22 +08:00
logger = Logger . new ( " #{ log_dir } /presentation/publish- #{ @meeting_id } .log " , 'daily' )
2016-01-27 03:25:02 +08:00
BigBlueButton . logger = logger
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Setting recording dir' )
2016-01-27 03:25:02 +08:00
recording_dir = bbb_props [ 'recording_dir' ]
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Setting process dir' )
2021-12-06 02:35:22 +08:00
@process_dir = " #{ recording_dir } /process/presentation/ #{ @meeting_id } "
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'setting publish dir' )
2021-12-06 02:35:22 +08:00
publish_dir = @presentation_props [ 'publish_dir' ]
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'setting playback url info' )
2016-01-27 03:25:02 +08:00
playback_protocol = bbb_props [ 'playback_protocol' ]
playback_host = bbb_props [ 'playback_host' ]
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'setting target dir' )
2021-12-06 02:35:22 +08:00
target_dir = " #{ recording_dir } /publish/presentation/ #{ @meeting_id } "
@deskshare_dir = " #{ recording_dir } /raw/ #{ @meeting_id } /deskshare "
2016-12-01 02:31:19 +08:00
2021-12-05 22:53:06 +08:00
if ! FileTest . directory? ( target_dir )
BigBlueButton . logger . info ( 'Making dir target_dir' )
2016-01-27 03:25:02 +08:00
FileUtils . mkdir_p target_dir
2021-12-06 02:35:22 +08:00
package_dir = " #{ target_dir } / #{ @meeting_id } "
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Making dir package_dir' )
2016-01-27 03:25:02 +08:00
FileUtils . mkdir_p package_dir
begin
2021-12-06 02:35:22 +08:00
video_formats = @presentation_props [ 'video_formats' ]
2018-07-12 11:05:17 +08:00
2021-12-06 02:35:22 +08:00
video_files = Dir . glob ( " #{ @process_dir } /webcams.{ #{ video_formats . join ( ',' ) } } " )
2021-12-05 22:53:06 +08:00
if ! video_files . empty?
BigBlueButton . logger . info ( 'Making video dir' )
2016-01-27 03:25:02 +08:00
video_dir = " #{ package_dir } /video "
FileUtils . mkdir_p video_dir
2018-07-12 11:05:17 +08:00
video_files . each do | video_file |
BigBlueButton . logger . info ( " Made video dir - copying: #{ video_file } to -> #{ video_dir } " )
FileUtils . cp ( video_file , video_dir )
BigBlueButton . logger . info ( " Copied #{ File . extname ( video_file ) } file " )
end
2016-01-27 03:25:02 +08:00
else
audio_dir = " #{ package_dir } /audio "
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Making audio dir' )
2016-01-27 03:25:02 +08:00
FileUtils . mkdir_p audio_dir
2021-12-06 02:35:22 +08:00
BigBlueButton . logger . info ( " Made audio dir - copying: #{ @process_dir } /audio.webm to -> #{ audio_dir } " )
FileUtils . cp ( " #{ @process_dir } /audio.webm " , audio_dir )
BigBlueButton . logger . info ( " Copied audio.webm file - copying: #{ @process_dir } /audio.ogg to -> #{ audio_dir } " )
FileUtils . cp ( " #{ @process_dir } /audio.ogg " , audio_dir )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Copied audio.ogg file' )
2016-01-27 03:25:02 +08:00
end
2016-01-20 03:52:28 +08:00
2021-12-06 02:35:22 +08:00
if File . exist? ( " #{ @process_dir } /captions.json " )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Copying caption files' )
2021-12-06 02:35:22 +08:00
FileUtils . cp ( " #{ @process_dir } /captions.json " , package_dir )
Dir . glob ( " #{ @process_dir } /caption_*.vtt " ) . each do | caption |
2016-02-02 01:23:39 +08:00
BigBlueButton . logger . debug ( caption )
2019-05-01 02:54:02 +08:00
FileUtils . cp ( caption , package_dir )
2016-02-02 01:23:39 +08:00
end
end
2021-12-06 02:35:22 +08:00
video_files = Dir . glob ( " #{ @process_dir } /deskshare.{ #{ video_formats . join ( ',' ) } } " )
2021-12-05 22:53:06 +08:00
if ! video_files . empty?
BigBlueButton . logger . info ( 'Making deskshare dir' )
2016-10-21 03:39:35 +08:00
deskshare_dir = " #{ package_dir } /deskshare "
FileUtils . mkdir_p deskshare_dir
2018-07-12 11:05:17 +08:00
video_files . each do | video_file |
BigBlueButton . logger . info ( " Made deskshare dir - copying: #{ video_file } to -> #{ deskshare_dir } " )
FileUtils . cp ( video_file , deskshare_dir )
BigBlueButton . logger . info ( " Copied #{ File . extname ( video_file ) } file " )
end
2016-10-21 03:39:35 +08:00
else
BigBlueButton . logger . info ( " Could not copy deskshares.webm: file doesn't exist " )
end
2021-12-06 02:35:22 +08:00
if File . exist? ( " #{ @process_dir } /presentation_text.json " )
FileUtils . cp ( " #{ @process_dir } /presentation_text.json " , package_dir )
2016-11-29 22:00:19 +08:00
end
2016-01-20 03:52:28 +08:00
2021-12-06 02:35:22 +08:00
FileUtils . cp ( " #{ @process_dir } /notes/notes.html " , package_dir ) if File . exist? ( " #{ @process_dir } /notes/notes.html " )
2020-12-02 03:04:26 +08:00
2021-12-06 02:35:22 +08:00
processing_time = File . read ( " #{ @process_dir } /processing_time " )
2016-01-27 03:25:02 +08:00
2021-12-06 02:35:22 +08:00
@doc = Nokogiri :: XML ( File . read ( " #{ @process_dir } /events.xml " ) )
2017-11-04 03:17:04 +08:00
2016-01-27 03:25:02 +08:00
# Retrieve record events and calculate total recording duration.
2021-12-06 02:35:22 +08:00
@rec_events = BigBlueButton :: Events . match_start_and_stop_rec_events (
2021-12-05 22:53:06 +08:00
BigBlueButton :: Events . get_start_and_stop_rec_events ( @doc )
)
2016-01-27 03:25:02 +08:00
2017-11-08 23:12:25 +08:00
recording_time = BigBlueButton :: Events . get_recording_length ( @doc )
2016-01-27 03:25:02 +08:00
2021-12-06 02:35:22 +08:00
# presentation_url = "/slides/" + @meeting_id + "/presentation"
2016-01-27 03:25:02 +08:00
2021-12-06 02:35:22 +08:00
@meeting_start = @doc . xpath ( '//event' ) [ 0 ] [ :timestamp ]
@meeting_end = @doc . xpath ( '//event' ) . last [ :timestamp ]
2016-01-27 03:25:02 +08:00
2021-12-06 02:35:22 +08:00
@version_atleast_0_9_0 = BigBlueButton :: Events . bbb_version_compare (
2021-12-05 22:53:06 +08:00
@doc , 0 , 9 , 0
)
2021-12-06 02:35:22 +08:00
@version_atleast_2_0_0 = BigBlueButton :: Events . bbb_version_compare (
2021-12-05 22:53:06 +08:00
@doc , 2 , 0 , 0
)
BigBlueButton . logger . info ( 'Creating metadata.xml' )
2016-01-27 03:25:02 +08:00
# Get the real-time start and end timestamp
2021-12-06 02:35:22 +08:00
# match = /.*-(\d+)@/.match(@meeting_id)
2021-12-06 02:04:21 +08:00
# real_start_time = match[1]
2021-12-06 02:35:22 +08:00
# real_end_time = (real_start_time.to_i + (@meeting_end.to_i - @meeting_start.to_i)).to_s
2016-01-27 03:25:02 +08:00
2016-01-28 23:13:45 +08:00
#### INSTEAD OF CREATING THE WHOLE metadata.xml FILE AGAIN, ONLY ADD <playback>
2016-01-27 06:16:28 +08:00
# Copy metadata.xml from process_dir
2021-12-06 02:35:22 +08:00
FileUtils . cp ( " #{ @process_dir } /metadata.xml " , package_dir )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Copied metadata.xml file' )
2016-01-27 06:16:28 +08:00
2016-02-13 01:08:33 +08:00
# Update state and add playback to metadata.xml
2016-01-27 06:16:28 +08:00
## Load metadata.xml
2020-08-12 17:07:19 +08:00
metadata = Nokogiri :: XML ( File . read ( " #{ package_dir } /metadata.xml " ) )
2016-02-13 01:08:33 +08:00
## Update state
2016-01-27 06:16:28 +08:00
recording = metadata . root
2021-12-05 22:53:06 +08:00
state = recording . at_xpath ( 'state' )
state . content = 'published'
published = recording . at_xpath ( 'published' )
published . content = 'true'
2016-01-28 23:13:45 +08:00
## Remove empty playback
2021-12-05 22:53:06 +08:00
metadata . search ( '//recording/playback' ) . each ( & :remove )
2016-02-10 06:26:29 +08:00
## Add the actual playback
2021-12-06 02:35:22 +08:00
presentation = BigBlueButton :: Presentation . get_presentation_for_preview ( @process_dir . to_s )
2021-12-06 02:04:21 +08:00
Nokogiri :: XML :: Builder . with ( metadata . at ( 'recording' ) ) do | xml |
2021-12-05 22:53:06 +08:00
xml . playback do
xml . format ( 'presentation' )
2021-12-06 02:35:22 +08:00
xml . link ( " #{ playback_protocol } :// #{ playback_host } /playback/presentation/2.3/ #{ @meeting_id } " )
2021-12-05 22:53:06 +08:00
xml . processing_time ( processing_time . to_s )
xml . duration ( recording_time . to_s )
unless presentation . empty?
xml . extensions do
xml . preview do
xml . images do
presentation [ :slides ] . each do | key , val |
attributes = { width : '176' , height : '136' , alt : ! val [ :alt ] . nil? ? ( val [ :alt ] ) . to_s : '' }
2021-12-06 02:35:22 +08:00
xml . image ( attributes ) { xml . text ( " #{ playback_protocol } :// #{ playback_host } /presentation/ #{ @meeting_id } /presentation/ #{ presentation [ :id ] } /thumbnails/thumb- #{ key } .png " ) }
2021-12-05 22:53:06 +08:00
end
end
end
2016-09-20 06:09:31 +08:00
end
2021-12-05 22:53:06 +08:00
end
end
2016-01-27 06:16:28 +08:00
end
## Write the new metadata.xml
2021-12-05 22:53:06 +08:00
metadata_file = File . new ( " #{ package_dir } /metadata.xml " , 'w' )
metadata = Nokogiri :: XML ( metadata . to_xml , & :noblanks )
2016-02-13 01:08:33 +08:00
metadata_file . write ( metadata . root )
metadata_file . close
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Added playback to metadata.xml' )
2016-01-27 06:16:28 +08:00
2021-12-05 22:53:06 +08:00
# Create slides.xml
BigBlueButton . logger . info ( 'Generating xml for slides and chat' )
2016-01-27 03:25:02 +08:00
2021-12-06 03:09:46 +08:00
calculate_record_events_offset
2016-01-27 03:25:02 +08:00
2021-08-31 05:46:52 +08:00
# Write slides.xml to file
2021-12-06 03:09:46 +08:00
slides_doc = process_chat_messages ( @doc , bbb_props )
2021-08-31 05:46:52 +08:00
File . open ( " #{ package_dir } /slides_new.xml " , 'w' ) { | f | f . puts slides_doc . to_xml }
2016-01-20 03:52:28 +08:00
2021-12-06 03:09:46 +08:00
process_presentation ( package_dir )
2016-01-20 03:52:28 +08:00
2021-12-06 03:09:46 +08:00
process_deskshare_events ( @doc )
2016-09-10 05:31:27 +08:00
2021-12-06 03:09:46 +08:00
process_poll_events ( @doc , package_dir )
2021-06-18 05:36:45 +08:00
2021-12-06 03:09:46 +08:00
process_external_video_events ( @doc , package_dir )
2021-07-07 21:26:24 +08:00
2016-09-10 05:31:27 +08:00
# Write deskshare.xml to file
2021-12-06 02:35:22 +08:00
File . open ( " #{ package_dir } / #{ @deskshare_xml_filename } " , 'w' ) { | f | f . puts @deskshare_xml . to_xml }
2016-09-10 05:31:27 +08:00
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Copying files to package dir' )
2021-12-06 02:35:22 +08:00
FileUtils . cp_r ( " #{ @process_dir } /presentation " , package_dir )
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Copied files to package dir' )
2016-01-20 03:52:28 +08:00
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Publishing slides' )
2016-01-27 03:25:02 +08:00
# Now publish this recording files by copying them into the publish folder.
2021-12-05 22:53:06 +08:00
FileUtils . mkdir_p publish_dir unless FileTest . directory? ( publish_dir )
2016-03-02 12:03:15 +08:00
# Get raw size of presentation files
2021-12-06 02:35:22 +08:00
raw_dir = " #{ recording_dir } /raw/ #{ @meeting_id } "
2016-03-02 12:03:15 +08:00
# After all the processing we'll add the published format and raw sizes to the metadata file
BigBlueButton . add_raw_size_to_metadata ( package_dir , raw_dir )
BigBlueButton . add_playback_size_to_metadata ( package_dir )
2016-01-27 03:25:02 +08:00
FileUtils . cp_r ( package_dir , publish_dir ) # Copy all the files.
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Finished publishing script presentation.rb successfully.' )
2016-01-27 03:25:02 +08:00
2021-12-05 22:53:06 +08:00
# TODO: remove comments
# BigBlueButton.logger.info("Removing processed files.")
2021-12-06 02:35:22 +08:00
# FileUtils.rm_r(Dir.glob("#{@process_dir}/*"))
2016-01-27 03:25:02 +08:00
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . info ( 'Removing published files.' )
2016-01-27 03:25:02 +08:00
FileUtils . rm_r ( Dir . glob ( " #{ target_dir } /* " ) )
2021-12-06 02:04:21 +08:00
rescue StandardError = > e
2021-12-05 22:53:06 +08:00
BigBlueButton . logger . error ( e . message )
e . backtrace . each do | traceline |
2016-01-27 03:25:02 +08:00
BigBlueButton . logger . error ( traceline )
end
exit 1
2016-01-20 03:52:28 +08:00
end
2021-12-06 02:35:22 +08:00
publish_done = File . new ( " #{ recording_dir } /status/published/ #{ @meeting_id } -presentation.done " , 'w' )
publish_done . write ( " Published #{ @meeting_id } " )
2016-01-27 03:25:02 +08:00
publish_done . close
2014-04-15 06:14:14 +08:00
2016-01-27 03:25:02 +08:00
else
BigBlueButton . logger . info ( " #{ target_dir } is already there " )
end
2016-01-20 03:52:28 +08:00
end
2021-12-06 02:04:21 +08:00
rescue StandardError = > e
2016-08-12 23:53:01 +08:00
BigBlueButton . logger . error ( e . message )
e . backtrace . each do | traceline |
BigBlueButton . logger . error ( traceline )
2016-01-27 03:25:02 +08:00
end
2021-12-06 02:35:22 +08:00
publish_done = File . new ( " #{ recording_dir } /status/published/ #{ @meeting_id } -presentation.fail " , 'w' )
publish_done . write ( " Failed Publishing #{ @meeting_id } " )
2016-01-27 03:25:02 +08:00
publish_done . close
2012-09-07 03:21:55 +08:00
2016-01-27 03:25:02 +08:00
exit 1
2014-04-15 06:14:14 +08:00
end