2018-04-27 05:00:30 +08:00
|
|
|
#!/usr/bin/ruby
|
|
|
|
# encoding: UTF-8
|
|
|
|
|
|
|
|
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
|
|
|
#
|
|
|
|
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors.
|
|
|
|
#
|
|
|
|
# BigBlueButton 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 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/>.
|
|
|
|
|
|
|
|
require File.expand_path('../../../lib/recordandplayback', __FILE__)
|
|
|
|
require File.expand_path('../../../lib/recordandplayback/edl', __FILE__)
|
|
|
|
|
2021-11-24 10:59:59 +08:00
|
|
|
require 'optimist'
|
2018-04-27 05:00:30 +08:00
|
|
|
require 'yaml'
|
|
|
|
require 'nokogiri'
|
2018-04-28 03:25:36 +08:00
|
|
|
require 'erb'
|
2018-04-27 05:00:30 +08:00
|
|
|
|
2021-11-24 10:59:59 +08:00
|
|
|
opts = Optimist::options do
|
2018-04-27 05:00:30 +08:00
|
|
|
opt :meeting_id, "Meeting id to process", :type => String
|
|
|
|
opt :stderr, "Log output to stderr"
|
|
|
|
end
|
2021-11-24 10:59:59 +08:00
|
|
|
Optimist::die :meeting_id, "must be provided" unless opts[:meeting_id]
|
2018-04-27 05:00:30 +08:00
|
|
|
meeting_id = opts[:meeting_id]
|
|
|
|
|
|
|
|
start_real_time = nil
|
|
|
|
begin
|
|
|
|
m = /-(\d+)$/.match(meeting_id)
|
|
|
|
start_real_time = m[1].to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
# Load parameters and set up paths
|
|
|
|
props = YAML::load(File.open(File.expand_path('../../bigbluebutton.yml', __FILE__)))
|
|
|
|
screenshare_props = YAML::load(File.open(File.expand_path('../../screenshare.yml', __FILE__)))
|
|
|
|
|
|
|
|
recording_dir = props['recording_dir']
|
|
|
|
playback_dir = screenshare_props['playback_dir']
|
|
|
|
process_dir = "#{recording_dir}/process/screenshare/#{meeting_id}"
|
|
|
|
raw_archive_dir = "#{recording_dir}/raw/#{meeting_id}"
|
|
|
|
donefile = "#{recording_dir}/status/processed/#{meeting_id}-screenshare.done"
|
|
|
|
log_file = "#{props['log_dir']}/screenshare/process-#{meeting_id}.log"
|
|
|
|
|
|
|
|
if opts[:stderr]
|
|
|
|
BigBlueButton.logger = Logger.new(STDERR)
|
|
|
|
else
|
|
|
|
BigBlueButton.logger = Logger.new(log_file)
|
|
|
|
end
|
|
|
|
logger = BigBlueButton.logger
|
|
|
|
|
|
|
|
if File.exists?(donefile)
|
|
|
|
logger.warn "This processing script has already been run"
|
|
|
|
exit 0
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
|
|
|
FileUtils.mkdir_p(process_dir)
|
|
|
|
|
|
|
|
logger.info "Reading basic recording information"
|
|
|
|
events = Nokogiri::XML(File.open("#{raw_archive_dir}/events.xml"))
|
|
|
|
initial_timestamp = nil
|
|
|
|
final_timestamp = nil
|
|
|
|
metadata = events.at_xpath('/recording/metadata')
|
|
|
|
meetingName = metadata['meetingName']
|
|
|
|
begin
|
|
|
|
event = events.at_xpath('/recording/event[position()=1]')
|
|
|
|
initial_timestamp = event['timestamp'].to_i
|
|
|
|
event = events.at_xpath('/recording/event[position()=last()]')
|
|
|
|
final_timestamp = event['timestamp'].to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
video_edl = []
|
|
|
|
begin
|
|
|
|
logger.info "Generating video events list"
|
|
|
|
|
|
|
|
# Webcams
|
2022-05-02 20:42:00 +08:00
|
|
|
webcam_edl = BigBlueButton::Events.create_webcam_edl(events, raw_archive_dir, props['show_moderator_viewpoint'])
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.debug "Webcam EDL:"
|
|
|
|
BigBlueButton::EDL::Video.dump(webcam_edl)
|
|
|
|
|
|
|
|
# Deskshare
|
2019-09-24 05:08:14 +08:00
|
|
|
deskshare_edl = BigBlueButton::Events.create_deskshare_edl(events, raw_archive_dir)
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.debug "Deskshare EDL:"
|
|
|
|
BigBlueButton::EDL::Video.dump(deskshare_edl)
|
|
|
|
|
|
|
|
video_edl = BigBlueButton::EDL::Video.merge(webcam_edl, deskshare_edl)
|
|
|
|
end
|
|
|
|
|
|
|
|
logger.debug "Merged Video EDL:"
|
|
|
|
BigBlueButton::EDL::Video.dump(video_edl)
|
|
|
|
|
2019-09-24 05:08:14 +08:00
|
|
|
start_time = BigBlueButton::Events.first_event_timestamp(events)
|
|
|
|
end_time = BigBlueButton::Events.last_event_timestamp(events)
|
|
|
|
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.info "Applying recording start/stop events to video"
|
2019-09-24 05:08:14 +08:00
|
|
|
video_edl = BigBlueButton::Events.edl_match_recording_marks_video(video_edl, events, start_time, end_time)
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.debug "Trimmed Video EDL:"
|
|
|
|
BigBlueButton::EDL::Video.dump(video_edl)
|
|
|
|
|
|
|
|
audio_edl = []
|
|
|
|
logger.info "Generating audio events list"
|
2019-09-24 05:08:14 +08:00
|
|
|
audio_edl = BigBlueButton::AudioEvents.create_audio_edl(events, raw_archive_dir)
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.debug "Audio EDL:"
|
|
|
|
BigBlueButton::EDL::Audio.dump(audio_edl)
|
|
|
|
|
|
|
|
logger.info "Applying recording start/stop events to audio"
|
2019-09-24 05:08:14 +08:00
|
|
|
audio_edl = BigBlueButton::Events.edl_match_recording_marks_audio(audio_edl, events, start_time, end_time)
|
2018-04-27 05:00:30 +08:00
|
|
|
logger.debug "Trimmed Audio EDL:"
|
|
|
|
BigBlueButton::EDL::Audio.dump(audio_edl)
|
|
|
|
|
|
|
|
logger.info "Rendering audio"
|
|
|
|
audio = "#{process_dir}/audio.#{BigBlueButton::EDL::Audio::WF_EXT}"
|
|
|
|
if File.exist?(audio)
|
|
|
|
logger.warn " Skipping rendering audio ... File already exists"
|
|
|
|
else
|
|
|
|
audio = BigBlueButton::EDL::Audio.render(audio_edl, "#{process_dir}/audio")
|
|
|
|
end
|
|
|
|
|
|
|
|
layout = screenshare_props['layout']
|
|
|
|
|
|
|
|
logger.info "Rendering video"
|
|
|
|
video = "#{process_dir}/video.#{BigBlueButton::EDL::Video::WF_EXT}"
|
|
|
|
if File.exist?(video)
|
|
|
|
logger.warn " Skipping rendering video ... File already exists"
|
|
|
|
else
|
|
|
|
video = BigBlueButton::EDL::Video.render(video_edl, layout, "#{process_dir}/video")
|
|
|
|
end
|
|
|
|
|
|
|
|
logger.info "Encoding output files to #{screenshare_props['formats'].length} formats"
|
|
|
|
screenshare_props['formats'].each_with_index do |format, i|
|
|
|
|
logger.info " #{format[:mimetype]}"
|
|
|
|
filename = "#{process_dir}/screenshare-#{i}.#{format[:extension]}"
|
|
|
|
if File.exist?(filename)
|
|
|
|
logger.warn " Skipping encode ... File already exists"
|
|
|
|
else
|
2018-04-28 03:25:36 +08:00
|
|
|
filename = BigBlueButton::EDL.encode(audio, video, format, "#{process_dir}/screenshare-#{i}", 0)
|
2018-04-27 05:00:30 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
logger.info("Generating closed captions")
|
|
|
|
ret = BigBlueButton.exec_ret('utils/gen_webvtt', '-i', raw_archive_dir, '-o', process_dir)
|
|
|
|
if ret != 0
|
|
|
|
raise "Generating closed caption files failed"
|
|
|
|
end
|
|
|
|
captions = JSON.load(File.new("#{process_dir}/captions.json", 'r'))
|
|
|
|
|
|
|
|
# Publishing support files
|
|
|
|
|
|
|
|
logger.info "Generating index page"
|
|
|
|
index_template = "#{playback_dir}/index.html.erb"
|
|
|
|
index_erb = ERB.new(File.read(index_template))
|
|
|
|
index_erb.filename = index_template
|
|
|
|
File.open("#{process_dir}/index.html", 'w') do |index_html|
|
|
|
|
index_html.write(index_erb.result)
|
|
|
|
end
|
|
|
|
|
|
|
|
logger.info "Generating metadata xml"
|
2019-09-24 05:08:14 +08:00
|
|
|
duration = BigBlueButton::Events.get_recording_length(events)
|
2019-11-21 02:59:48 +08:00
|
|
|
meeting_xml = events.at_xpath('/recording/meeting')
|
|
|
|
breakout_xml = events.at_xpath('/recording/breakout')
|
|
|
|
breakout_rooms_xml = events.at_xpath('/recording/breakoutRooms')
|
2018-04-27 05:00:30 +08:00
|
|
|
metadata_xml = Nokogiri::XML::Builder.new do |xml|
|
|
|
|
xml.recording {
|
|
|
|
xml.id(meeting_id)
|
|
|
|
xml.state('available')
|
|
|
|
xml.published('true')
|
|
|
|
xml.start_time(start_real_time)
|
|
|
|
xml.end_time(start_real_time + final_timestamp - initial_timestamp)
|
2019-11-21 02:59:48 +08:00
|
|
|
xml << meeting_xml.to_xml unless meeting_xml.nil?
|
|
|
|
xml << breakout_xml.to_xml unless breakout_xml.nil?
|
|
|
|
xml << breakout_rooms_xml.to_xml unless breakout_rooms_xml.nil?
|
2018-04-27 05:00:30 +08:00
|
|
|
xml.playback {
|
|
|
|
xml.format('screenshare')
|
2018-04-28 03:25:36 +08:00
|
|
|
xml.link("#{props['playback_protocol']}://#{props['playback_host']}/recording/screenshare/#{meeting_id}/")
|
2018-04-27 05:00:30 +08:00
|
|
|
xml.duration(duration)
|
|
|
|
}
|
|
|
|
xml.meta {
|
|
|
|
metadata.attributes.each do |k, v|
|
|
|
|
xml.method_missing(k, v)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
File.open("#{process_dir}/metadata.xml", 'w') do |metadata_file|
|
|
|
|
metadata_file.write(metadata_xml.to_xml)
|
|
|
|
end
|
|
|
|
|
|
|
|
logger.info "Copying css and js support files"
|
|
|
|
FileUtils.cp_r("#{playback_dir}/css", process_dir)
|
|
|
|
FileUtils.cp_r("#{playback_dir}/js", process_dir)
|
|
|
|
FileUtils.cp_r("#{playback_dir}/video-js", process_dir)
|
|
|
|
|
|
|
|
logger.info "Processing successfully completed, writing done file"
|
|
|
|
|
|
|
|
File.open(donefile, 'w') do |done|
|
|
|
|
done.write("Processed #{meeting_id}")
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue Exception => e
|
|
|
|
warn e.message
|
|
|
|
logger.error e.message
|
|
|
|
e.backtrace.each do |traceline|
|
|
|
|
logger.error traceline
|
|
|
|
end
|
|
|
|
exit 1
|
|
|
|
end
|