diff --git a/record-and-playback/core/.rubocop.yml b/record-and-playback/core/.rubocop.yml new file mode 100644 index 0000000000000000000000000000000000000000..6f070f61536b2c2a191fd9ee3f0e313e1f95a03f --- /dev/null +++ b/record-and-playback/core/.rubocop.yml @@ -0,0 +1,16 @@ +AllCops: + TargetRubyVersion: 2.5 +Layout/AlignHash: + EnforcedHashRocketStyle: [ key, table ] +Layout/EndAlignment: + EnforcedStyleAlignWith: variable +Layout/RescueEnsureAlignment: + Enabled: false # https://github.com/rubocop-hq/rubocop/issues/6918 +Metrics: + Enabled: false +Style/AsciiComments: + AllowedChars: [ © ] +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: consistent_comma +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma diff --git a/record-and-playback/core/scripts/rap-caption-inbox.rb b/record-and-playback/core/scripts/rap-caption-inbox.rb index b9569efe3f03b15de4262ae3ff0bf820bf286892..29890dff6701e533259b65d164aa8e321c50be08 100755 --- a/record-and-playback/core/scripts/rap-caption-inbox.rb +++ b/record-and-playback/core/scripts/rap-caption-inbox.rb @@ -1,8 +1,9 @@ #!/usr/bin/ruby +# frozen_string_literal: true # Copyright © 2019 BigBlueButton Inc. and by respective authors. # -# This file is part of BigBlueButton open source conferencing system. +# 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 @@ -15,32 +16,33 @@ # 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/>. +# along with BigBlueButton. If not, see <https://www.gnu.org/licenses/>. -require "rubygems" -require "bundler/setup" +require 'rubygems' +require 'bundler/setup' -require File.expand_path("../../lib/recordandplayback", __FILE__) +require File.expand_path('../lib/recordandplayback', __dir__) -require "journald-logger" -require "locale" -require "rb-inotify" -require "yaml" +require 'journald-logger' +require 'locale' +require 'rb-inotify' +require 'yaml' # Read configuration and set up logger -props = YAML::load(File.open(File.expand_path("../bigbluebutton.yml", __FILE__))) +props = File.open(File.expand_path('bigbluebutton.yml', __dir__)) do |bbb_yml| + YAML.safe_load(bbb_yml) +end -log_dir = props["log_dir"] -logger = Journald::Logger.new("bbb-rap-caption-inbox") +logger = Journald::Logger.new('bbb-rap-caption-inbox') BigBlueButton.logger = logger -captions_dir = props["captions_dir"] +captions_dir = props['captions_dir'] unless captions_dir - logger.error("captions_dir was not defined in bigbluebutton.yml") + logger.error('captions_dir was not defined in bigbluebutton.yml') exit(1) end -captions_inbox_dir = File.join(captions_dir, "inbox") +captions_inbox_dir = File.join(captions_dir, 'inbox') # Internal error classes @@ -64,54 +66,81 @@ def caption_file_notify(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 - + 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( - File.join(captions_dir, new_caption_info["record_id"], "captions.json") - ) do |file| - JSON.parse(file) - end - rescue + 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 + 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"] + 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", + 'kind' => new_caption_info['kind'], + 'label' => new_caption_info['label'], + 'lang' => langtag.to_s, + 'source' => 'upload', } - dest_filename = "#{captions_info["kind"]}_#{captions_info["lang"]}.vtt" + 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") + 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| +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...") +logger.info('Waiting for new caption files...') notifier.run