Merge branch 'text-tracks' of https://github.com/riadvice/bigbluebutton into riadvice-text-tracks
This commit is contained in:
commit
9d416ee473
@ -48,6 +48,7 @@ public class ApiParams {
|
||||
public static final String PARENT_MEETING_ID = "parentMeetingID";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String RECORD = "record";
|
||||
public static final String RECORD_ID = "recordID";
|
||||
public static final String REDIRECT = "redirect";
|
||||
public static final String SEQUENCE = "sequence";
|
||||
public static final String VOICE_BRIDGE = "voiceBridge";
|
||||
|
@ -417,7 +417,11 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
public String getCaptionTrackInboxDir() {
|
||||
return recordingService.getCaptionTrackInboxDir();
|
||||
}
|
||||
}
|
||||
|
||||
public String getCaptionsDir() {
|
||||
return recordingService.getCaptionsDir();
|
||||
}
|
||||
|
||||
public String getRecordings2x(List<String> idList, List<String> states, Map<String, String> metadataFilters) {
|
||||
return recordingService.getRecordings2x(idList, states, metadataFilters);
|
||||
|
@ -54,6 +54,7 @@ public class RecordingService {
|
||||
private String recordStatusDir;
|
||||
private String captionsDir;
|
||||
private String presentationBaseDir;
|
||||
private String defaultServerUrl;
|
||||
|
||||
private void copyPresentationFile(File presFile, File dlownloadableFile) {
|
||||
try {
|
||||
@ -169,7 +170,7 @@ public class RecordingService {
|
||||
}
|
||||
|
||||
public String getRecordingTextTracks(String recordId) {
|
||||
return recordingServiceHelper.getRecordingTextTracks(recordId, captionsDir);
|
||||
return recordingServiceHelper.getRecordingTextTracks(recordId, captionsDir, getCaptionFileUrlDirectory(recordId));
|
||||
}
|
||||
|
||||
public String putRecordingTextTrack(UploadedTrack track) {
|
||||
@ -374,6 +375,10 @@ public class RecordingService {
|
||||
presentationBaseDir = dir;
|
||||
}
|
||||
|
||||
public void setDefaultServerUrl(String url) {
|
||||
defaultServerUrl = url;
|
||||
}
|
||||
|
||||
public void setPublishedDir(String dir) {
|
||||
publishedDir = dir;
|
||||
}
|
||||
@ -662,7 +667,16 @@ public class RecordingService {
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
public String getCaptionTrackInboxDir() {
|
||||
return captionsDir + File.separatorChar + "inbox";
|
||||
}
|
||||
public String getCaptionTrackInboxDir() {
|
||||
return captionsDir + File.separatorChar + "inbox";
|
||||
}
|
||||
|
||||
public String getCaptionsDir() {
|
||||
return captionsDir;
|
||||
}
|
||||
|
||||
public String getCaptionFileUrlDirectory(String recordId) {
|
||||
return defaultServerUrl + "/captions/" + recordId + "/";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ public class RecordingMetadataReaderHelper {
|
||||
|
||||
private RecordingServiceGW recordingServiceGW;
|
||||
|
||||
public String getRecordingTextTracks(String recordId, String captionsDir) {
|
||||
return recordingServiceGW.getRecordingTextTracks(recordId, captionsDir);
|
||||
public String getRecordingTextTracks(String recordId, String captionsDir, String captionsBaseUrl) {
|
||||
return recordingServiceGW.getRecordingTextTracks(recordId, captionsDir, captionsBaseUrl);
|
||||
}
|
||||
|
||||
public String putRecordingTextTrack(UploadedTrack track) {
|
||||
|
@ -13,6 +13,6 @@ public interface RecordingServiceGW {
|
||||
String getRecordings2x(ArrayList<RecordingMetadata> recs);
|
||||
Option<RecordingMetadata> getRecordingMetadata(File xml);
|
||||
boolean saveRecordingMetadata(File xml, RecordingMetadata metadata);
|
||||
String getRecordingTextTracks(String recordId, String captionsDir);
|
||||
String getRecordingTextTracks(String recordId, String captionsDir, String captionBasUrl);
|
||||
String putRecordingTextTrack(UploadedTrack track);
|
||||
}
|
||||
|
@ -22,11 +22,11 @@ case class UploadedTrackInfo(
|
||||
origFilename: String
|
||||
)
|
||||
case class Track(
|
||||
href: String,
|
||||
kind: String,
|
||||
lang: String,
|
||||
label: String,
|
||||
source: String,
|
||||
href: String
|
||||
lang: String,
|
||||
source: String
|
||||
)
|
||||
case class GetRecTextTracksResult(
|
||||
returncode: String,
|
||||
|
@ -20,6 +20,8 @@ import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
import com.google.gson.internal.LinkedTreeMap
|
||||
|
||||
class RecMetaXmlHelper extends RecordingServiceGW with LogHelper {
|
||||
|
||||
val SUCCESS = "SUCCESS"
|
||||
@ -188,19 +190,36 @@ class RecMetaXmlHelper extends RecordingServiceGW with LogHelper {
|
||||
}
|
||||
}
|
||||
|
||||
def getRecordingTextTracks(recordId: String, captionsDir: String): String = {
|
||||
def getRecordingTextTracks(recordId: String, captionsDir: String, captionBaseUrl: String): String = {
|
||||
val gson = new Gson()
|
||||
var returnResponse: String = ""
|
||||
val captionsFilePath = captionsDir + File.separatorChar + recordId + File.separatorChar + CAPTIONS_FILE
|
||||
|
||||
readCaptionJsonFile(captionsFilePath, StandardCharsets.UTF_8) match {
|
||||
case Some(captions) =>
|
||||
val ctracks = gson.fromJson(captions, classOf[util.ArrayList[Track]])
|
||||
val result1 = GetRecTextTracksResult(SUCCESS, ctracks)
|
||||
val response1 = GetRecTextTracksResp(result1)
|
||||
val respText1 = gson.toJson(response1)
|
||||
val ctracks = gson.fromJson(captions, classOf[java.util.List[LinkedTreeMap[String, String]]])
|
||||
|
||||
returnResponse = respText1
|
||||
val list = new util.ArrayList[Track]()
|
||||
val it = ctracks.iterator()
|
||||
|
||||
while (it.hasNext()) {
|
||||
val mapTrack = it.next()
|
||||
list.add(new Track(
|
||||
// TODO : change this later and provide authenticated/signed URLs to fetch the caption files
|
||||
href = captionBaseUrl + mapTrack.get("lang") + ".vtt",
|
||||
kind = mapTrack.get("kind"),
|
||||
label = mapTrack.get("label"),
|
||||
lang = mapTrack.get("lang"),
|
||||
source = mapTrack.get("source")
|
||||
))
|
||||
}
|
||||
val textTracksResult = GetRecTextTracksResult(SUCCESS, list)
|
||||
|
||||
val textTracksResponse = GetRecTextTracksResp(textTracksResult)
|
||||
val textTracksJson = gson.toJson(textTracksResponse)
|
||||
// parse(textTracksJson).transformField{case JField(x, v) if x == "value" && v == JString("Company")=> JField("value1",JString("Company1"))}
|
||||
|
||||
returnResponse = textTracksJson
|
||||
case None =>
|
||||
val resFailed = GetRecTextTracksResultFailed(FAILED, "noCaptionsFound", "No captions found for " + recordId)
|
||||
val respFailed = GetRecTextTracksRespFailed(resFailed)
|
@ -35,6 +35,7 @@
|
||||
# 2016-07-02 FFD Updates for 1.1-beta
|
||||
# 2016-10-17 GTR Stricter rule for detection of recording directories names
|
||||
# 2017-04-28 FFD Updated references to systemd processing units
|
||||
# 2019-05-13 GTR Delete caption files
|
||||
|
||||
#set -e
|
||||
#set -x
|
||||
@ -372,6 +373,10 @@ if [ $DELETE ]; then
|
||||
rm -rf /var/log/bigbluebutton/$type/*$MEETING_ID*
|
||||
done
|
||||
|
||||
rm -rf /var/bigbluebutton/captions/$MEETING_ID*
|
||||
rm -f /var/bigbluebutton/inbox/$MEETING_ID*.json
|
||||
rm -f /var/bigbluebutton/inbox/$MEETING_ID*.txt
|
||||
|
||||
rm -rf /var/bigbluebutton/recording/raw/$MEETING_ID*
|
||||
|
||||
rm -rf /usr/share/red5/webapps/video/streams/$MEETING_ID
|
||||
@ -402,7 +407,9 @@ if [ $DELETEALL ]; then
|
||||
done
|
||||
|
||||
rm -rf /var/bigbluebutton/recording/raw/*
|
||||
|
||||
|
||||
rm -f /var/bigbluebutton/captions/inbox/*
|
||||
|
||||
find /usr/share/red5/webapps/video/streams -name "*.flv" -exec rm '{}' \;
|
||||
find /usr/share/red5/webapps/video-broadcast/streams -name "*.flv" -exec rm '{}' \;
|
||||
rm -f /var/bigbluebutton/screenshare/*.flv
|
||||
@ -411,6 +418,7 @@ if [ $DELETEALL ]; then
|
||||
for meeting in $(ls /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$"); do
|
||||
echo "deleting: $meeting"
|
||||
rm -rf /var/bigbluebutton/$meeting
|
||||
rm -rf /var/bigbluebutton/captions/$meeting
|
||||
done
|
||||
fi
|
||||
|
||||
|
@ -89,6 +89,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="captionsDir" value="${captionsDir}"/>
|
||||
<property name="recordingServiceHelper" ref="recordingServiceHelper"/>
|
||||
<property name="presentationBaseDir" value="${presentationDir}"/>
|
||||
<property name="defaultServerUrl" value="${bigbluebutton.web.serverURL}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="configServiceHelper" class="org.bigbluebutton.api.ClientConfigServiceHelperImp"/>
|
||||
|
@ -84,7 +84,7 @@ class UrlMappings {
|
||||
}
|
||||
|
||||
"/bigbluebutton/api/getRecordingTextTracks"(controller: "recording") {
|
||||
action = [GET: 'getRecordingTextTracks']
|
||||
action = [GET: 'getRecordingTextTracksHandler', POST: 'getRecordingTextTracksHandler']
|
||||
}
|
||||
|
||||
"/bigbluebutton/api/putRecordingTextTrack"(controller: "recording") {
|
||||
|
@ -1,33 +1,71 @@
|
||||
package org.bigbluebutton.web.controllers
|
||||
|
||||
import org.bigbluebutton.api.MeetingService;
|
||||
import org.bigbluebutton.api.ParamsProcessorUtil;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bigbluebutton.api.ApiErrors;
|
||||
import grails.web.context.ServletContextHolder
|
||||
import groovy.json.JsonBuilder
|
||||
import org.bigbluebutton.api.MeetingService
|
||||
import org.bigbluebutton.api.ParamsProcessorUtil
|
||||
import org.bigbluebutton.api.util.ResponseBuilder
|
||||
import org.bigbluebutton.api.ApiErrors
|
||||
import org.bigbluebutton.api.ApiParams
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.json.JSONArray
|
||||
|
||||
|
||||
class RecordingController {
|
||||
private static final String CONTROLLER_NAME = 'RecordingController'
|
||||
protected static final String RESP_CODE_SUCCESS = 'SUCCESS'
|
||||
protected static final String RESP_CODE_FAILED = 'FAILED'
|
||||
protected static Boolean REDIRECT_RESPONSE = true
|
||||
|
||||
MeetingService meetingService;
|
||||
MeetingService meetingService
|
||||
ParamsProcessorUtil paramsProcessorUtil
|
||||
ResponseBuilder responseBuilder = initResponseBuilder()
|
||||
|
||||
def getRecordingTextTracks = {
|
||||
def initResponseBuilder = {
|
||||
String protocol = this.getClass().getResource("").getProtocol()
|
||||
if (Objects.equals(protocol, "jar")) {
|
||||
// Application running inside a JAR file
|
||||
responseBuilder = new ResponseBuilder(getClass().getClassLoader(), "/WEB-INF/freemarker")
|
||||
} else if (Objects.equals(protocol, "file")) {
|
||||
// Application unzipped and running outside a JAR file
|
||||
String templateLoc = ServletContextHolder.servletContext.getRealPath("/WEB-INF/freemarker")
|
||||
// We should never have a null `templateLoc`
|
||||
responseBuilder = new ResponseBuilder(new File(templateLoc))
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* GET RECORDING TEXT TRACKS API
|
||||
******************************************************/
|
||||
def getRecordingTextTracksHandler = {
|
||||
String API_CALL = "getRecordingTextTracks"
|
||||
log.debug CONTROLLER_NAME + "#${API_CALL}"
|
||||
|
||||
// BEGIN - backward compatibility
|
||||
if (StringUtils.isEmpty(params.checksum)) {
|
||||
respondWithError("paramError", "Missing param checksum.")
|
||||
invalid("checksumError", "You did not pass the checksum security check")
|
||||
return
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(params.recordID)) {
|
||||
respondWithError("paramError", "Missing param recordID.");
|
||||
invalid("missingParamRecordID", "You must specify a recordID.")
|
||||
return
|
||||
}
|
||||
|
||||
String recordId = StringUtils.strip(params.recordID)
|
||||
if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
|
||||
invalid("checksumError", "You did not pass the checksum security check")
|
||||
return
|
||||
}
|
||||
// END - backward compatibility
|
||||
|
||||
// Do we agree on the checksum? If not, complain.
|
||||
if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
|
||||
errors.checksumError()
|
||||
respondWithErrors(errors)
|
||||
return
|
||||
}
|
||||
|
||||
String recId = StringUtils.strip(params.recordID)
|
||||
|
||||
// Do we agree on the checksum? If not, complain.
|
||||
//if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
|
||||
@ -35,7 +73,7 @@ class RecordingController {
|
||||
// return
|
||||
//}
|
||||
|
||||
String result = meetingService.getRecordingTextTracks(recordId)
|
||||
String result = meetingService.getRecordingTextTracks(recId)
|
||||
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
@ -49,19 +87,21 @@ class RecordingController {
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
json {
|
||||
render(contentType: "application/json") {
|
||||
response() {
|
||||
returncode = "FAILED"
|
||||
messageKey = errorKey
|
||||
messsage = errorMessage
|
||||
}
|
||||
log.debug "Rendering as json"
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_FAILED
|
||||
messageKey errorKey
|
||||
message errorMessage
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def putRecordingTextTrack = {
|
||||
log.debug CONTROLLER_NAME + "#putRecordingTextTrack"
|
||||
String API_CALL = "putRecordingTextTrack"
|
||||
log.debug CONTROLLER_NAME + "#${API_CALL}"
|
||||
|
||||
// BEGIN - backward compatibility
|
||||
if (StringUtils.isEmpty(params.checksum)) {
|
||||
@ -70,25 +110,53 @@ class RecordingController {
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(params.recordID)) {
|
||||
respondWithError("paramError", "Missing param recordID.");
|
||||
respondWithError("paramError", "Missing param recordID.")
|
||||
return
|
||||
} else {
|
||||
String captionsDirPath = meetingService.getCaptionsDir() + File.separatorChar + StringUtils.isEmpty(params.recordID)
|
||||
File captionsDir = new File(captionsDirPath);
|
||||
if (!captionsDir.exists() || !captionsDir.isDirectory()) {
|
||||
respondWithError("noRecordings", "No recording was found matching the provided recording ID.")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String recordId = StringUtils.strip(params.recordID)
|
||||
|
||||
if (StringUtils.isEmpty(params.kind)) {
|
||||
respondWithError("paramError", "Missing param kind.");
|
||||
respondWithError("paramError", "Missing param kind.")
|
||||
return
|
||||
} else {
|
||||
def isAllowedKind = StringUtils.strip(params.kind) in ['subtitles', 'captions']
|
||||
if (!isAllowedKind) {
|
||||
respondWithError("invalidKind", "The kind parameter is not set to a permitted value.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
String captionsKind = StringUtils.strip(params.kind)
|
||||
|
||||
Locale locale;
|
||||
if (StringUtils.isEmpty(params.lang)) {
|
||||
respondWithError("paramError", "Missing param lang.");
|
||||
respondWithError("paramError", "Missing param lang.")
|
||||
return
|
||||
} else {
|
||||
Collection<Locale> locales = new ArrayList<>();
|
||||
locales.add(Locale.forLanguageTag(params.lang));
|
||||
try {
|
||||
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(params.lang);
|
||||
locale = Locale.lookup(languageRanges, locales);
|
||||
if (locale == null) {
|
||||
respondWithError("invalidLang", "The lang parameter is not a valid language tag.")
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
respondWithError("invalidLang", "The lang parameter is not a well-formed language tag.")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String captionsLang = StringUtils.strip(params.lang)
|
||||
String captionsLang = locale.toString()
|
||||
String captionsLabel = captionsLang
|
||||
|
||||
if (!StringUtils.isEmpty(params.label)) {
|
||||
@ -118,16 +186,78 @@ class RecordingController {
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
json {
|
||||
render(contentType: "application/json") {
|
||||
response = {
|
||||
returncode = "FAILED"
|
||||
messageKey = "empty_uploaded_text_track"
|
||||
message = "Empty uploaded text track."
|
||||
}
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_FAILED
|
||||
messageKey = "empty_uploaded_text_track"
|
||||
message = "Empty uploaded text track."
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void invalid(key, msg, redirectResponse = false) {
|
||||
// Note: This xml scheme will be DEPRECATED.
|
||||
log.debug CONTROLLER_NAME + "#invalid " + msg
|
||||
if (redirectResponse) {
|
||||
ArrayList<Object> errors = new ArrayList<Object>()
|
||||
Map<String, String> errorMap = new LinkedHashMap<String, String>()
|
||||
errorMap.put("key", key)
|
||||
errorMap.put("message", msg)
|
||||
errors.add(errorMap)
|
||||
|
||||
JSONArray errorsJSONArray = new JSONArray(errors)
|
||||
log.debug "JSON Errors {}", errorsJSONArray.toString()
|
||||
|
||||
respondWithRedirect(errorsJSONArray)
|
||||
} else {
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
xml {
|
||||
render(text: responseBuilder.buildError(key, msg, RESP_CODE_FAILED), contentType: "text/xml")
|
||||
}
|
||||
json {
|
||||
log.debug "Rendering as json"
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_FAILED
|
||||
messageKey key
|
||||
message msg
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void respondWithRedirect(errorsJSONArray) {
|
||||
String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
|
||||
URI oldUri = URI.create(logoutUrl)
|
||||
|
||||
if (!StringUtils.isEmpty(params.logoutURL)) {
|
||||
try {
|
||||
oldUri = URI.create(params.logoutURL)
|
||||
} catch (Exception e) {
|
||||
// Do nothing, the variable oldUri was already initialized
|
||||
}
|
||||
}
|
||||
|
||||
String newQuery = oldUri.getQuery()
|
||||
|
||||
if (newQuery == null) {
|
||||
newQuery = "errors="
|
||||
} else {
|
||||
newQuery += "&" + "errors="
|
||||
}
|
||||
newQuery += errorsJSONArray
|
||||
|
||||
URI newUri = new URI(oldUri.getScheme(), oldUri.getAuthority(), oldUri.getPath(), newQuery, oldUri.getFragment())
|
||||
|
||||
log.debug "Constructed logout URL {}", newUri.toString()
|
||||
redirect(url: newUri)
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ log_dir: /var/log/bigbluebutton
|
||||
events_dir: /var/bigbluebutton/events
|
||||
recording_dir: /var/bigbluebutton/recording
|
||||
published_dir: /var/bigbluebutton/published
|
||||
captions_dir: /var/bigbluebutton/captions
|
||||
playback_host: 127.0.0.1
|
||||
playback_protocol: http
|
||||
|
||||
|
@ -45,6 +45,12 @@ def process_archived_meetings(recording_dir)
|
||||
|
||||
step_succeeded = true
|
||||
|
||||
# Generate captions
|
||||
ret = BigBlueButton.exec_ret('ruby', 'utils/captions.rb', '-m', meeting_id)
|
||||
if ret != 0
|
||||
BigBlueButton.logger.warn("Failed to generate caption files #{ret}")
|
||||
end
|
||||
|
||||
# Iterate over the list of recording processing scripts to find available
|
||||
# types. For now, we look for the ".rb" extension - TODO other scripting
|
||||
# languages?
|
||||
|
103
record-and-playback/core/scripts/utils/captions.rb
Normal file
103
record-and-playback/core/scripts/utils/captions.rb
Normal file
@ -0,0 +1,103 @@
|
||||
# Set encoding to utf-8
|
||||
# encoding: UTF-8
|
||||
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2019 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/>.
|
||||
#
|
||||
|
||||
# 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__)
|
||||
|
||||
require 'rubygems'
|
||||
require 'trollop'
|
||||
require 'yaml'
|
||||
require 'json'
|
||||
|
||||
opts = Trollop::options do
|
||||
opt :meeting_id, "Meeting id to archive", :type => String
|
||||
end
|
||||
|
||||
meeting_id = opts[:meeting_id]
|
||||
|
||||
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
|
||||
props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
|
||||
|
||||
recording_dir = props['recording_dir']
|
||||
raw_archive_dir = "#{recording_dir}/raw/#{meeting_id}"
|
||||
BigBlueButton.logger.info("Setting process dir")
|
||||
BigBlueButton.logger.info("setting captions dir")
|
||||
captions_dir = props['captions_dir']
|
||||
|
||||
log_dir = props['log_dir']
|
||||
|
||||
target_dir = "#{recording_dir}/process/presentation/#{meeting_id}"
|
||||
|
||||
# Generate captions.json for API
|
||||
def create_api_captions_file(captions_meeting_dir)
|
||||
BigBlueButton.logger.info("Generating closed captions for API")
|
||||
|
||||
captions = JSON.load(File.new("#{captions_meeting_dir}/captions_playback.json"))
|
||||
captions_json = []
|
||||
captions.each do |track|
|
||||
caption = {}
|
||||
caption[:kind] = :captions
|
||||
caption[:label] = track['localeName']
|
||||
caption[:lang] = track['locale']
|
||||
caption[:source] = :live
|
||||
captions_json << caption
|
||||
end
|
||||
|
||||
File.open("#{captions_meeting_dir}/captions.json", "w") do |f|
|
||||
f.write(captions_json.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
if not FileTest.directory?(target_dir)
|
||||
|
||||
captions_meeting_dir = "#{captions_dir}/#{meeting_id}"
|
||||
|
||||
FileUtils.mkdir_p "#{log_dir}/presentation"
|
||||
logger = Logger.new("#{log_dir}/presentation/process-#{meeting_id}.log", 'daily')
|
||||
BigBlueButton.logger = logger
|
||||
BigBlueButton.logger.info("Processing script captions.rb")
|
||||
FileUtils.mkdir_p target_dir
|
||||
|
||||
begin
|
||||
BigBlueButton.logger.info("Generating closed captions")
|
||||
FileUtils.mkdir_p captions_meeting_dir
|
||||
ret = BigBlueButton.exec_ret('utils/gen_webvtt', '-i', raw_archive_dir, '-o', captions_meeting_dir)
|
||||
if ret != 0
|
||||
raise "Generating closed caption files failed"
|
||||
end
|
||||
|
||||
FileUtils.cp("#{captions_meeting_dir}/captions.json", "#{captions_meeting_dir}/captions_playback.json")
|
||||
create_api_captions_file(captions_meeting_dir)
|
||||
FileUtils.rm "#{captions_meeting_dir}/captions_playback.json"
|
||||
|
||||
rescue Exception => e
|
||||
BigBlueButton.logger.error(e.message)
|
||||
e.backtrace.each do |traceline|
|
||||
BigBlueButton.logger.error(traceline)
|
||||
end
|
||||
exit 1
|
||||
end
|
||||
|
||||
end
|
@ -40,6 +40,7 @@ function deploy_format() {
|
||||
|
||||
deploy_format "presentation"
|
||||
|
||||
sudo mkdir -p /var/bigbluebutton/captions/
|
||||
sudo mkdir -p /var/bigbluebutton/events/
|
||||
sudo mkdir -p /var/bigbluebutton/playback/
|
||||
sudo mkdir -p /var/bigbluebutton/recording/raw/
|
||||
|
@ -47,6 +47,10 @@ recording_dir = props['recording_dir']
|
||||
raw_archive_dir = "#{recording_dir}/raw/#{meeting_id}"
|
||||
log_dir = props['log_dir']
|
||||
|
||||
BigBlueButton.logger.info("setting captions dir")
|
||||
captions_dir = props['captions_dir']
|
||||
captions_meeting_dir = "#{captions_dir}/#{meeting_id}"
|
||||
|
||||
target_dir = "#{recording_dir}/process/presentation/#{meeting_id}"
|
||||
if not FileTest.directory?(target_dir)
|
||||
FileUtils.mkdir_p "#{log_dir}/presentation"
|
||||
@ -198,11 +202,22 @@ if not FileTest.directory?(target_dir)
|
||||
FileUtils.cp_r("#{pres_dir}/thumbnails", "#{target_pres_dir}/thumbnails")
|
||||
end
|
||||
|
||||
BigBlueButton.logger.info("Generating closed captions")
|
||||
ret = BigBlueButton.exec_ret('utils/gen_webvtt', '-i', raw_archive_dir, '-o', target_dir)
|
||||
if ret != 0
|
||||
raise "Generating closed caption files failed"
|
||||
BigBlueButton.logger.info("Copying closed captions")
|
||||
|
||||
captions = JSON.load(File.new("#{captions_meeting_dir}/captions.json"))
|
||||
captions_json = []
|
||||
captions.each do |track|
|
||||
caption = {}
|
||||
caption[:localeName] = track['label']
|
||||
caption[:locale] = track['lang']
|
||||
captions_json << caption
|
||||
FileUtils.cp("#{captions_meeting_dir}/caption_" + track['lang'] + ".vtt", target_dir)
|
||||
end
|
||||
|
||||
File.open("#{target_dir}/captions.json", "w") do |f|
|
||||
f.write(captions_json.to_json)
|
||||
end
|
||||
|
||||
captions = JSON.load(File.new("#{target_dir}/captions.json", 'r'))
|
||||
|
||||
if not presentation_text.empty?
|
||||
|
@ -1185,6 +1185,9 @@ begin
|
||||
$process_dir = "#{recording_dir}/process/presentation/#{$meeting_id}"
|
||||
BigBlueButton.logger.info("setting publish dir")
|
||||
publish_dir = $presentation_props['publish_dir']
|
||||
BigBlueButton.logger.info("setting captions dir")
|
||||
captions_dir = bbb_props['captions_dir']
|
||||
captions_meeting_dir = "#{captions_dir}/#{$meeting_id}"
|
||||
BigBlueButton.logger.info("setting playback url info")
|
||||
playback_protocol = bbb_props['playback_protocol']
|
||||
playback_host = bbb_props['playback_host']
|
||||
@ -1225,6 +1228,21 @@ begin
|
||||
BigBlueButton.logger.info("Copied audio.ogg file")
|
||||
end
|
||||
|
||||
BigBlueButton.logger.info("Copying caption files to #{target_dir}")
|
||||
captions = JSON.load(File.new("#{captions_meeting_dir}/captions.json"))
|
||||
captions_json = []
|
||||
captions.each do |track|
|
||||
caption = {}
|
||||
caption[:localeName] = track['label']
|
||||
caption[:locale] = track['lang']
|
||||
captions_json << caption
|
||||
FileUtils.cp("#{captions_meeting_dir}/caption_" + track['lang'] + ".vtt", target_dir)
|
||||
end
|
||||
|
||||
File.open("#{target_dir}/captions.json", "w") do |f|
|
||||
f.write(captions_json.to_json)
|
||||
end
|
||||
|
||||
if File.exist?("#{$process_dir}/captions.json")
|
||||
BigBlueButton.logger.info("Copying caption files")
|
||||
FileUtils.cp("#{$process_dir}/captions.json", package_dir)
|
||||
|
Loading…
Reference in New Issue
Block a user