bigbluebutton-Github/record-and-playback/core/scripts/rap-caption-inbox.rb
Calvin Walton b22c905194 rap-caption-inbox: improve formatting, initial pass at conversion
It now attempts to convert the uploaded caption file to WebVTT and
updates the index file.
2019-06-04 11:03:22 -04:00

147 lines
4.9 KiB
Ruby
Executable File

#!/usr/bin/ruby
# frozen_string_literal: true
# Copyright © 2019 BigBlueButton Inc. and by respective authors.
#
# This file is part of the BigBlueButton open source conferencing system.
#
# 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 <https://www.gnu.org/licenses/>.
require 'rubygems'
require 'bundler/setup'
require File.expand_path('../lib/recordandplayback', __dir__)
require 'journald-logger'
require 'locale'
require 'rb-inotify'
require 'yaml'
# Read configuration and set up logger
props = File.open(File.expand_path('bigbluebutton.yml', __dir__)) do |bbb_yml|
YAML.safe_load(bbb_yml)
end
logger = Journald::Logger.new('bbb-rap-caption-inbox')
BigBlueButton.logger = logger
captions_dir = props['captions_dir']
unless captions_dir
logger.error('captions_dir was not defined in bigbluebutton.yml')
exit(1)
end
captions_inbox_dir = File.join(captions_dir, 'inbox')
# Internal error classes
# Base class for internal errors
class CaptionError < StandardError
end
# Indicates that uploaded caption files are invalid (unrecoverable)
class InvalidCaptionError < CaptionError
end
# Implementation
def caption_file_notify(json_filename)
# There's a possible race condition where we can be notified twice for a new
# file. That's fine, just do nothing the second time.
return unless File.exist?(json_filename)
logger.info("Found new caption index file #{json_filename}")
# TODO: Rather than do anything directly in this script, it should create a
# queue job (resque?) that does the actual work.
captions_work_base = File.join(props['recording_dir'], 'caption', 'inbox')
new_caption_info = File.open(json_filename) { |file| JSON.parse(file) }
logger.tag(record_id: new_caption_info['record_id']) do
# TODO: This is racy if multiple tools are editing the captions.json file
index_filename = File.join(captions_dir, new_caption_info['record_id'], 'captions.json')
captions_info = begin
File.open(index_filename) { |file| JSON.parse(file) }
rescue StandardError
# No captions file or cannot be read, assume none present
[]
end
langtag = Locale::Tag::Rfc.parse(new_caption_info['lang'])
raise InvalidCaptionError, 'Language tag is not well-formed' unless langtag
# Remove the info for an existing matching track
captions_info.delete_if do |caption_info|
caption_info['lang'] == new_caption_info['lang'] &&
caption_info['kind'] == new_caption_info['kind']
end
captions_info << {
'kind' => new_caption_info['kind'],
'label' => new_caption_info['label'],
'lang' => langtag.to_s,
'source' => 'upload',
}
src_filename = File.join(captions_inbox_dir, new_caption_info['temp_filename'])
dest_filename = "#{captions_info['kind']}_#{captions_info['lang']}.vtt"
tmp_dest = File.join(captions_work_base, new_caption_info['record_id'], dest_filename)
final_dest = File.join(captions_dir, new_caption_info['record_id'], dest_filename)
# Try to use ffmpeg to convert the received caption file to WebVTT
ffmpeg_cmd = [
'ffmpeg',
'-i', src_filename, '-map', '0:s',
'-f', 'webvtt', tmp_dest,
]
ret = BigBlueButton.exec_ret(*ffmpeg_cmd)
raise InvalidCaptionError, 'FFmpeg could not read input' unless ret.zero?
FileUtils.mv(tmp_dest, final_dest)
File.open(index_filename, 'w') do |file|
result = JSON.pretty_generate(captions_info)
file.write(result)
end
# TODO: trigger incorporating caption file into existing published recordings
logger.info('Removing files from inbox directory')
FileUtils.rm_f(src_filename) if src_filename
FileUtils.rm_f(json_filename)
rescue InvalidCaptionError => e
logger.exception(e)
logger.info('Deleting invalid files from inbox directory')
FileUtils.rm_f(src_filename) if src_filename
FileUtils.rm_f(json_filename)
end
end
logger.info("Setting up inotify watch on #{captions_inbox_dir}")
notifier = INotify::Notifier.new
notifier.watch(captions_inbox_dir, :moved_to, :create) do |event|
next unless event.name.end_with?('-track.json')
handle_caption_file(event.absolute_name)
end
logger.info('Checking for missed/skipped caption files')
Dir.glob(File.join(captions_inbox_dir, '*-track.json')).each do |filename|
caption_file_notify(filename)
end
logger.info('Waiting for new caption files...')
notifier.run