PDF file: include meeting and room name
This commit is contained in:
parent
717c692468
commit
7851d54484
@ -35,6 +35,14 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati
|
|||||||
eventMap.put(JOB_TYPE, jobType)
|
eventMap.put(JOB_TYPE, jobType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def setMeetingName(meetingName: String) {
|
||||||
|
eventMap.put(MEETING_NAME, meetingName)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setPresName(presName: String) {
|
||||||
|
eventMap.put(PRES_NAME, presName)
|
||||||
|
}
|
||||||
|
|
||||||
def setPresId(presId: String) {
|
def setPresId(presId: String) {
|
||||||
eventMap.put(PRES_ID, presId)
|
eventMap.put(PRES_ID, presId)
|
||||||
}
|
}
|
||||||
@ -63,6 +71,8 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati
|
|||||||
object StoreExportJobInRedisPresAnnEvent {
|
object StoreExportJobInRedisPresAnnEvent {
|
||||||
protected final val JOB_ID = "jobId"
|
protected final val JOB_ID = "jobId"
|
||||||
protected final val JOB_TYPE = "jobType"
|
protected final val JOB_TYPE = "jobType"
|
||||||
|
protected final val MEETING_NAME = "meetingName"
|
||||||
|
protected final val PRES_NAME = "presName"
|
||||||
protected final val PRES_ID = "presId"
|
protected final val PRES_ID = "presId"
|
||||||
protected final val PRES_LOCATION = "presLocation"
|
protected final val PRES_LOCATION = "presLocation"
|
||||||
protected final val ALL_PAGES = "allPages"
|
protected final val ALL_PAGES = "allPages"
|
||||||
|
@ -753,6 +753,7 @@ class MeetingActor(
|
|||||||
def handleMakePresentationWithAnnotationDownloadReqMsg(m: MakePresentationWithAnnotationDownloadReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
def handleMakePresentationWithAnnotationDownloadReqMsg(m: MakePresentationWithAnnotationDownloadReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||||
|
|
||||||
val meetingId = liveMeeting.props.meetingProp.intId
|
val meetingId = liveMeeting.props.meetingProp.intId
|
||||||
|
val meetingName: String = liveMeeting.props.meetingProp.name
|
||||||
|
|
||||||
// Whiteboard ID
|
// Whiteboard ID
|
||||||
val presId: String = m.body.presId match {
|
val presId: String = m.body.presId match {
|
||||||
@ -765,18 +766,31 @@ class MeetingActor(
|
|||||||
|
|
||||||
// Determine page amount
|
// Determine page amount
|
||||||
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
|
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
|
||||||
val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).head
|
|
||||||
|
|
||||||
val pageCount = currentPres.pages.size
|
val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).headOption
|
||||||
|
|
||||||
|
currentPres match {
|
||||||
|
case None =>
|
||||||
|
log.error(s"No presentation set in meeting ${meetingId}")
|
||||||
|
return
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
val pageCount = currentPres.get.pages.size
|
||||||
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages
|
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages
|
||||||
|
|
||||||
var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size)
|
var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size)
|
||||||
var resultingPage = 0
|
var resultingPage = 0
|
||||||
|
|
||||||
for (pageNumber <- pagesRange) {
|
for (pageNumber <- pagesRange) {
|
||||||
|
if (pageNumber < 1 || pageNumber > pageCount) {
|
||||||
|
println(pagesRange.length)
|
||||||
|
log.error(s"Page ${pageNumber} requested for export out of range, aborting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var whiteboardId = s"${presId}/${pageNumber.toString}"
|
var whiteboardId = s"${presId}/${pageNumber.toString}"
|
||||||
val presentationPage: PresentationPage = currentPres.pages(whiteboardId)
|
val presentationPage: PresentationPage = currentPres.get.pages(whiteboardId)
|
||||||
val xOffset: Double = presentationPage.xOffset
|
val xOffset: Double = presentationPage.xOffset
|
||||||
val yOffset: Double = presentationPage.yOffset
|
val yOffset: Double = presentationPage.yOffset
|
||||||
val widthRatio: Double = presentationPage.widthRatio
|
val widthRatio: Double = presentationPage.widthRatio
|
||||||
@ -796,7 +810,7 @@ class MeetingActor(
|
|||||||
// 2) Insert Export Job in Redis
|
// 2) Insert Export Job in Redis
|
||||||
val jobType = "PresentationWithAnnotationDownloadJob"
|
val jobType = "PresentationWithAnnotationDownloadJob"
|
||||||
val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}"
|
val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}"
|
||||||
val exportJob = new ExportJob(jobId, jobType, presId, presLocation, allPages, pagesRange, meetingId, "")
|
val exportJob = new ExportJob(jobId, jobType, meetingName, currentPres.get.name, presId, presLocation, allPages, pagesRange, meetingId, "")
|
||||||
var job = buildStoreExportJobInRedisSysMsg(exportJob)
|
var job = buildStoreExportJobInRedisSysMsg(exportJob)
|
||||||
outGW.send(job)
|
outGW.send(job)
|
||||||
}
|
}
|
||||||
@ -804,25 +818,33 @@ class MeetingActor(
|
|||||||
def handleExportPresentationWithAnnotationReqMsg(m: ExportPresentationWithAnnotationReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
def handleExportPresentationWithAnnotationReqMsg(m: ExportPresentationWithAnnotationReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||||
|
|
||||||
val meetingId = liveMeeting.props.meetingProp.intId
|
val meetingId = liveMeeting.props.meetingProp.intId
|
||||||
|
val meetingName: String = liveMeeting.props.meetingProp.name
|
||||||
val userId = m.header.userId
|
val userId = m.header.userId
|
||||||
val presId: String = getMeetingInfoPresentationDetails.id
|
val presId: String = getMeetingInfoPresentationDetails.id
|
||||||
val parentMeetingId: String = m.body.parentMeetingId
|
val parentMeetingId: String = m.body.parentMeetingId
|
||||||
val allPages: Boolean = m.body.allPages
|
val allPages: Boolean = m.body.allPages
|
||||||
|
|
||||||
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
|
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
|
||||||
val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).head
|
val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).headOption
|
||||||
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres).get
|
|
||||||
|
|
||||||
val pageCount = currentPres.pages.size
|
currentPres match {
|
||||||
|
case None =>
|
||||||
|
log.error(s"No presentation set in meeting ${meetingId}")
|
||||||
|
return
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
|
||||||
|
|
||||||
|
val pageCount = currentPres.get.pages.size
|
||||||
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
|
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
|
||||||
|
|
||||||
var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size)
|
var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size)
|
||||||
var resultingPage = 0
|
var resultingPage = 0
|
||||||
|
|
||||||
for (pageNumber <- pagesRange) {
|
for (pageNumber <- pagesRange) {
|
||||||
|
|
||||||
var whiteboardId = s"${presId}/${pageNumber.toString}"
|
var whiteboardId = s"${presId}/${pageNumber.toString}"
|
||||||
val presentationPage: PresentationPage = currentPres.pages(whiteboardId)
|
val presentationPage: PresentationPage = currentPres.get.pages(whiteboardId)
|
||||||
val xOffset: Double = presentationPage.xOffset
|
val xOffset: Double = presentationPage.xOffset
|
||||||
val yOffset: Double = presentationPage.yOffset
|
val yOffset: Double = presentationPage.yOffset
|
||||||
val widthRatio: Double = presentationPage.widthRatio
|
val widthRatio: Double = presentationPage.widthRatio
|
||||||
@ -837,7 +859,7 @@ class MeetingActor(
|
|||||||
val jobId = RandomStringGenerator.randomAlphanumericString(16)
|
val jobId = RandomStringGenerator.randomAlphanumericString(16)
|
||||||
|
|
||||||
// Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens
|
// Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens
|
||||||
outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, currentPres.name))
|
outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, currentPres.get.name))
|
||||||
|
|
||||||
// 1) Send Annotations to Redis
|
// 1) Send Annotations to Redis
|
||||||
var annotations = new StoredAnnotations(jobId, presId, storeAnnotationPages)
|
var annotations = new StoredAnnotations(jobId, presId, storeAnnotationPages)
|
||||||
@ -846,7 +868,7 @@ class MeetingActor(
|
|||||||
// 2) Insert Export Job in Redis
|
// 2) Insert Export Job in Redis
|
||||||
val jobType: String = "PresentationWithAnnotationExportJob"
|
val jobType: String = "PresentationWithAnnotationExportJob"
|
||||||
val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}"
|
val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}"
|
||||||
val exportJob = new ExportJob(jobId, jobType, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
val exportJob = new ExportJob(jobId, jobType, meetingName, currentPres.get.name, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
||||||
var job = buildStoreExportJobInRedisSysMsg(exportJob)
|
var job = buildStoreExportJobInRedisSysMsg(exportJob)
|
||||||
outGW.send(job)
|
outGW.send(job)
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,8 @@ class ExportAnnotationsActor(
|
|||||||
|
|
||||||
ev.setJobId(msg.body.exportJob.jobId)
|
ev.setJobId(msg.body.exportJob.jobId)
|
||||||
ev.setJobType(msg.body.exportJob.jobType)
|
ev.setJobType(msg.body.exportJob.jobType)
|
||||||
|
ev.setMeetingName(msg.body.exportJob.meetingName)
|
||||||
|
ev.setPresName(msg.body.exportJob.presName)
|
||||||
ev.setPresId(msg.body.exportJob.presId)
|
ev.setPresId(msg.body.exportJob.presId)
|
||||||
ev.setPresLocation(msg.body.exportJob.presLocation)
|
ev.setPresLocation(msg.body.exportJob.presLocation)
|
||||||
ev.setAllPages(msg.body.exportJob.allPages.toString)
|
ev.setAllPages(msg.body.exportJob.allPages.toString)
|
||||||
|
@ -142,7 +142,7 @@ class RedisRecorderActor(
|
|||||||
ev.setSenderId(msg.body.msg.sender.id)
|
ev.setSenderId(msg.body.msg.sender.id)
|
||||||
ev.setMessage(msg.body.msg.message)
|
ev.setMessage(msg.body.msg.message)
|
||||||
ev.setSenderRole(msg.body.msg.sender.role)
|
ev.setSenderRole(msg.body.msg.sender.role)
|
||||||
|
|
||||||
val isModerator = msg.body.msg.sender.role == "MODERATOR"
|
val isModerator = msg.body.msg.sender.role == "MODERATOR"
|
||||||
ev.setChatEmphasizedText(msg.body.msg.chatEmphasizedText && isModerator)
|
ev.setChatEmphasizedText(msg.body.msg.chatEmphasizedText && isModerator)
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ case class StoredAnnotations(
|
|||||||
case class ExportJob(
|
case class ExportJob(
|
||||||
jobId: String,
|
jobId: String,
|
||||||
jobType: String,
|
jobType: String,
|
||||||
|
meetingName: String,
|
||||||
|
presName: String,
|
||||||
presId: String,
|
presId: String,
|
||||||
presLocation: String,
|
presLocation: String,
|
||||||
allPages: Boolean,
|
allPages: Boolean,
|
||||||
|
@ -27,9 +27,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~^\/bigbluebutton\/presentation\/(?<meeting_id_1>[A-Za-z0-9\-]+)\/(?<meeting_id_2>[A-Za-z0-9\-]+)\/(?<pres_id>[A-Za-z0-9\-]+)\/pdf\/(?<job_id>[A-Za-z0-9]+)$ {
|
location ~^\/bigbluebutton\/presentation\/(?<meeting_id_1>[A-Za-z0-9\-]+)\/(?<meeting_id_2>[A-Za-z0-9\-]+)\/(?<pres_id>[A-Za-z0-9\-]+)\/pdf\/(?<job_id>[A-Za-z0-9]+)/(?<filename>.*)$ {
|
||||||
default_type application/pdf;
|
default_type application/pdf;
|
||||||
alias /var/bigbluebutton/$meeting_id_2/$meeting_id_2/$pres_id/pdfs/annotated_slides_$job_id.pdf;
|
alias /var/bigbluebutton/$meeting_id_2/$meeting_id_2/$pres_id/pdfs/$job_id/annotated_$filename.pdf;
|
||||||
if ($bbb_loadbalancer_node) {
|
if ($bbb_loadbalancer_node) {
|
||||||
add_header 'Access-Control-Allow-Origin' $bbb_loadbalancer_node always;
|
add_header 'Access-Control-Allow-Origin' $bbb_loadbalancer_node always;
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ const fs = require('fs');
|
|||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const { commandOptions } = require('redis');
|
const { commandOptions } = require('redis');
|
||||||
const { Worker } = require('worker_threads');
|
const { Worker } = require('worker_threads');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const logger = new Logger('presAnn Master');
|
const logger = new Logger('presAnn Master');
|
||||||
logger.info("Running export-annotations");
|
logger.info("Running export-annotations");
|
||||||
|
|
||||||
const kickOffCollectorWorker = (jobId) => {
|
const kickOffCollectorWorker = (jobId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const worker = new Worker('./workers/collector.js', { workerData: jobId });
|
const worker = new Worker(path.join(__dirname, 'workers', 'collector.js'), { workerData: jobId });
|
||||||
worker.on('message', resolve);
|
worker.on('message', resolve);
|
||||||
worker.on('error', reject);
|
worker.on('error', reject);
|
||||||
worker.on('exit', (code) => {
|
worker.on('exit', (code) => {
|
||||||
@ -44,11 +45,11 @@ const kickOffCollectorWorker = (jobId) => {
|
|||||||
const exportJob = JSON.parse(job.element);
|
const exportJob = JSON.parse(job.element);
|
||||||
|
|
||||||
// Create folder in dropbox
|
// Create folder in dropbox
|
||||||
let dropbox = `${config.shared.presAnnDropboxDir}/${exportJob.jobId}`
|
let dropbox = path.join(config.shared.presAnnDropboxDir, exportJob.jobId);
|
||||||
fs.mkdirSync(dropbox, { recursive: true })
|
fs.mkdirSync(dropbox, { recursive: true })
|
||||||
|
|
||||||
// Drop job into dropbox as JSON
|
// Drop job into dropbox as JSON
|
||||||
fs.writeFile(`${dropbox}/job`, job.element, function(err) {
|
fs.writeFile(path.join(dropbox, 'job'), job.element, function(err) {
|
||||||
if(err) { return logger.error(err); }
|
if(err) { return logger.error(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
43
export-annotations/package-lock.json
generated
43
export-annotations/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"image-size": "^1.0.1",
|
"image-size": "^1.0.1",
|
||||||
"redis": "^4.0.3",
|
"redis": "^4.0.3",
|
||||||
|
"sanitize-filename": "^1.6.3",
|
||||||
"xmlbuilder2": "^3.0.2"
|
"xmlbuilder2": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -288,11 +289,32 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sanitize-filename": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
|
||||||
|
"dependencies": {
|
||||||
|
"truncate-utf8-bytes": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sprintf-js": {
|
"node_modules/sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
},
|
},
|
||||||
|
"node_modules/truncate-utf8-bytes": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
|
||||||
|
"dependencies": {
|
||||||
|
"utf8-byte-length": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utf8-byte-length": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
|
||||||
|
},
|
||||||
"node_modules/xmlbuilder2": {
|
"node_modules/xmlbuilder2": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz",
|
||||||
@ -529,11 +551,32 @@
|
|||||||
"redis-errors": "^1.0.0"
|
"redis-errors": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sanitize-filename": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
|
||||||
|
"requires": {
|
||||||
|
"truncate-utf8-bytes": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
},
|
},
|
||||||
|
"truncate-utf8-bytes": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
|
||||||
|
"requires": {
|
||||||
|
"utf8-byte-length": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utf8-byte-length": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
|
||||||
|
},
|
||||||
"xmlbuilder2": {
|
"xmlbuilder2": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"image-size": "^1.0.1",
|
"image-size": "^1.0.1",
|
||||||
"redis": "^4.0.3",
|
"redis": "^4.0.3",
|
||||||
|
"sanitize-filename": "^1.6.3",
|
||||||
"xmlbuilder2": "^3.0.2"
|
"xmlbuilder2": "^3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ const config = require('../config');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
const { Worker, workerData, parentPort } = require('worker_threads')
|
const { Worker, workerData, parentPort } = require('worker_threads')
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const jobId = workerData;
|
const jobId = workerData;
|
||||||
|
|
||||||
@ -23,10 +23,10 @@ const kickOffProcessWorker = (jobId) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let dropbox = `${config.shared.presAnnDropboxDir}/${jobId}`
|
let dropbox = path.join(config.shared.presAnnDropboxDir, jobId);
|
||||||
|
|
||||||
// Takes the Job from the dropbox
|
// Takes the Job from the dropbox
|
||||||
let job = fs.readFileSync(`${dropbox}/job`);
|
let job = fs.readFileSync(path.join(dropbox, 'job'));
|
||||||
let exportJob = JSON.parse(job);
|
let exportJob = JSON.parse(job);
|
||||||
|
|
||||||
// Collect the annotations from Redis
|
// Collect the annotations from Redis
|
||||||
@ -51,17 +51,17 @@ let exportJob = JSON.parse(job);
|
|||||||
let whiteboard = JSON.parse(annotations);
|
let whiteboard = JSON.parse(annotations);
|
||||||
let pages = JSON.parse(whiteboard.pages);
|
let pages = JSON.parse(whiteboard.pages);
|
||||||
|
|
||||||
fs.writeFile(`${dropbox}/whiteboard`, annotations, function(err) {
|
fs.writeFile(path.join(dropbox, 'whiteboard'), annotations, function(err) {
|
||||||
if(err) { return logger.error(err); }
|
if(err) { return logger.error(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collect the Presentation Page files from the presentation directory
|
// Collect the Presentation Page files from the presentation directory
|
||||||
let path = `${exportJob.presLocation}/${exportJob.presId}`;
|
let presentationFile = path.join(exportJob.presLocation, exportJob.presId);
|
||||||
let pdfFileExists = fs.existsSync(`${path}.pdf`);
|
let pdfFileExists = fs.existsSync(`${presentationFile}.pdf`);
|
||||||
|
|
||||||
for (let p of pages) {
|
for (let p of pages) {
|
||||||
let pageNumber = p.page;
|
let pageNumber = p.page;
|
||||||
let file = `${dropbox}/slide${pageNumber}`;
|
let outputFile = path.join(dropbox, `slide${pageNumber}`);
|
||||||
|
|
||||||
if(pdfFileExists) {
|
if(pdfFileExists) {
|
||||||
let extactSlideAsPDFCommands = [
|
let extactSlideAsPDFCommands = [
|
||||||
@ -70,8 +70,8 @@ let exportJob = JSON.parse(job);
|
|||||||
'-f', pageNumber,
|
'-f', pageNumber,
|
||||||
'-l', pageNumber,
|
'-l', pageNumber,
|
||||||
'-singlefile',
|
'-singlefile',
|
||||||
`${path}.pdf`,
|
`${presentationFile}.pdf`,
|
||||||
file
|
outputFile
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
execSync(extactSlideAsPDFCommands, (error, stderr) => {
|
execSync(extactSlideAsPDFCommands, (error, stderr) => {
|
||||||
@ -85,19 +85,19 @@ let exportJob = JSON.parse(job);
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (fs.existsSync(`${path}.png`)) {
|
else if (fs.existsSync(`${presentationFile}.png`)) {
|
||||||
fs.copyFileSync(`${path}.png`, `${file}.png`);
|
fs.copyFileSync(`${presentationFile}.png`, `${outputFile}.png`);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (fs.existsSync(`${path}.jpeg`)) {
|
else if (fs.existsSync(`${presentationFile}.jpeg`)) {
|
||||||
let convertImageToPngCommands = [
|
let convertImageToPngCommands = [
|
||||||
'convert',
|
'convert',
|
||||||
`${path}.jpeg`,
|
`${presentationFile}.jpeg`,
|
||||||
'-background', 'white',
|
'-background', 'white',
|
||||||
'-resize', '1600x1600',
|
'-resize', '1600x1600',
|
||||||
'-auto-orient',
|
'-auto-orient',
|
||||||
'-flatten',
|
'-flatten',
|
||||||
`${file}.png`
|
`${outputFile}.png`
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
execSync(convertImageToPngCommands, (error, stderr) => {
|
execSync(convertImageToPngCommands, (error, stderr) => {
|
||||||
|
@ -4,15 +4,16 @@ const fs = require('fs');
|
|||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const axios = require('axios').default;
|
const axios = require('axios').default;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const { workerData, parentPort } = require('worker_threads')
|
const { workerData, parentPort } = require('worker_threads')
|
||||||
|
|
||||||
const [jobType, jobId] = workerData;
|
const [jobType, jobId, filename] = workerData;
|
||||||
|
|
||||||
const logger = new Logger('presAnn Notifier Worker');
|
const logger = new Logger('presAnn Notifier Worker');
|
||||||
|
|
||||||
const dropbox = `${config.shared.presAnnDropboxDir}/${jobId}`
|
const dropbox = `${config.shared.presAnnDropboxDir}/${jobId}`
|
||||||
let job = fs.readFileSync(`${dropbox}/job`);
|
let job = fs.readFileSync(path.join(dropbox, 'job'));
|
||||||
let exportJob = JSON.parse(job);
|
let exportJob = JSON.parse(job);
|
||||||
|
|
||||||
async function notifyMeetingActor() {
|
async function notifyMeetingActor() {
|
||||||
@ -25,7 +26,7 @@ async function notifyMeetingActor() {
|
|||||||
await client.connect();
|
await client.connect();
|
||||||
client.on('error', (err) => logger.info('Redis Client Error', err));
|
client.on('error', (err) => logger.info('Redis Client Error', err));
|
||||||
|
|
||||||
let link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}`;
|
let link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}/${filename}`;
|
||||||
// Notify Meeting Actor of file availability by sending a message through Redis PubSub
|
// Notify Meeting Actor of file availability by sending a message through Redis PubSub
|
||||||
const notification = {
|
const notification = {
|
||||||
envelope: {
|
envelope: {
|
||||||
@ -56,13 +57,10 @@ async function upload(exportJob) {
|
|||||||
let callbackUrl = `http://${config.bbbWeb.host}:${config.bbbWeb.port}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`
|
let callbackUrl = `http://${config.bbbWeb.host}:${config.bbbWeb.port}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
|
|
||||||
formData.append('presentation_name', 'annotated_slides.pdf');
|
|
||||||
formData.append('Filename', 'annotated_slides');
|
|
||||||
formData.append('conference', exportJob.parentMeetingId);
|
formData.append('conference', exportJob.parentMeetingId);
|
||||||
formData.append('room', exportJob.parentMeetingId);
|
|
||||||
formData.append('pod_id', config.notifier.pod_id);
|
formData.append('pod_id', config.notifier.pod_id);
|
||||||
formData.append('is_downloadable', config.notifier.is_downloadable);
|
formData.append('is_downloadable', config.notifier.is_downloadable);
|
||||||
formData.append('fileUpload', fs.createReadStream(`${exportJob.presLocation}/pdfs/annotated_slides_${jobId}.pdf`));
|
formData.append('fileUpload', fs.createReadStream(`${exportJob.presLocation}/pdfs/${jobId}/${filename}.pdf`));
|
||||||
|
|
||||||
let res = await axios.post(callbackUrl, formData, { headers: formData.getHeaders() });
|
let res = await axios.post(callbackUrl, formData, { headers: formData.getHeaders() });
|
||||||
logger.info(`Upload of job ${exportJob.jobId} returned ${res.data}`);
|
logger.info(`Upload of job ${exportJob.jobId} returned ${res.data}`);
|
||||||
|
@ -5,6 +5,8 @@ const sizeOf = require('image-size');
|
|||||||
const { create } = require('xmlbuilder2', { encoding: 'utf-8' });
|
const { create } = require('xmlbuilder2', { encoding: 'utf-8' });
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
const { Worker, workerData, parentPort } = require('worker_threads');
|
const { Worker, workerData, parentPort } = require('worker_threads');
|
||||||
|
const path = require('path');
|
||||||
|
const sanitize = require("sanitize-filename");
|
||||||
|
|
||||||
const jobId = workerData;
|
const jobId = workerData;
|
||||||
const MAGIC_MYSTERY_NUMBER = 2;
|
const MAGIC_MYSTERY_NUMBER = 2;
|
||||||
@ -12,9 +14,9 @@ const MAGIC_MYSTERY_NUMBER = 2;
|
|||||||
const logger = new Logger('presAnn Process Worker');
|
const logger = new Logger('presAnn Process Worker');
|
||||||
logger.info("Processing PDF for job " + jobId);
|
logger.info("Processing PDF for job " + jobId);
|
||||||
|
|
||||||
const kickOffNotifierWorker = (jobType) => {
|
const kickOffNotifierWorker = (jobType, sanitizedFilename) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId] });
|
const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId, sanitizedFilename] });
|
||||||
worker.on('message', resolve);
|
worker.on('message', resolve);
|
||||||
worker.on('error', reject);
|
worker.on('error', reject);
|
||||||
worker.on('exit', (code) => {
|
worker.on('exit', (code) => {
|
||||||
@ -43,7 +45,7 @@ function render_HTMLTextBox(htmlFilePath, id, width, height) {
|
|||||||
'--crop-h', height,
|
'--crop-h', height,
|
||||||
'--log-level', 'none',
|
'--log-level', 'none',
|
||||||
'--quality', '100',
|
'--quality', '100',
|
||||||
htmlFilePath, `${dropbox}/text${id}.png`
|
htmlFilePath, path.join(dropbox, `text${id}.png`)
|
||||||
]
|
]
|
||||||
|
|
||||||
execSync(commands.join(' '), (error, stderr) => {
|
execSync(commands.join(' '), (error, stderr) => {
|
||||||
@ -176,8 +178,8 @@ function overlay_poll(svg, annotation, w, h) {
|
|||||||
let poll_width = Math.round(scale_shape(w, annotation.points[2]));
|
let poll_width = Math.round(scale_shape(w, annotation.points[2]));
|
||||||
let poll_height = Math.round(scale_shape(h, annotation.points[3]));
|
let poll_height = Math.round(scale_shape(h, annotation.points[3]));
|
||||||
let pollId = annotation.id.replace(/\//g, '');
|
let pollId = annotation.id.replace(/\//g, '');
|
||||||
let pollSVG = `${dropbox}/poll-${pollId}.svg`
|
let pollSVG = path.join(dropbox, `poll-${pollId}.svg`);
|
||||||
let pollJSON = `${dropbox}/poll-${pollId}.json`
|
let pollJSON = path.join(dropbox, `poll-${pollId}.json`);
|
||||||
|
|
||||||
// Rename 'numVotes' key to 'num_votes'
|
// Rename 'numVotes' key to 'num_votes'
|
||||||
let pollJSONContent = annotation.result.map(result => {
|
let pollJSONContent = annotation.result.map(result => {
|
||||||
@ -281,7 +283,7 @@ function overlay_text(svg, annotation, w, h) {
|
|||||||
</p>
|
</p>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
var htmlFilePath = `${dropbox}/text${annotation.id}.html`
|
var htmlFilePath = path.join(dropbox, `text${annotation.id}.html`)
|
||||||
|
|
||||||
fs.writeFileSync(htmlFilePath, html, function (err) {
|
fs.writeFileSync(htmlFilePath, html, function (err) {
|
||||||
if (err) logger.error(err)
|
if (err) logger.error(err)
|
||||||
@ -331,19 +333,19 @@ function overlay_annotations(svg, currentSlideAnnotations, w, h) {
|
|||||||
// Process the presentation pages and annotations into a PDF file
|
// Process the presentation pages and annotations into a PDF file
|
||||||
|
|
||||||
// 1. Get the job
|
// 1. Get the job
|
||||||
const dropbox = `${config.shared.presAnnDropboxDir}/${jobId}`
|
const dropbox = path.join(config.shared.presAnnDropboxDir, jobId);
|
||||||
let job = fs.readFileSync(`${dropbox}/job`);
|
let job = fs.readFileSync(path.join(dropbox, 'job'));
|
||||||
let exportJob = JSON.parse(job);
|
let exportJob = JSON.parse(job);
|
||||||
|
|
||||||
// 2. Get the annotations
|
// 2. Get the annotations
|
||||||
let annotations = fs.readFileSync(`${dropbox}/whiteboard`);
|
let annotations = fs.readFileSync(path.join(dropbox, 'whiteboard'));
|
||||||
let whiteboard = JSON.parse(annotations);
|
let whiteboard = JSON.parse(annotations);
|
||||||
let pages = JSON.parse(whiteboard.pages);
|
let pages = JSON.parse(whiteboard.pages);
|
||||||
let ghostScriptInput = ""
|
let ghostScriptInput = ""
|
||||||
|
|
||||||
// 3. Convert annotations to SVG
|
// 3. Convert annotations to SVG
|
||||||
for (let currentSlide of pages) {
|
for (let currentSlide of pages) {
|
||||||
var dimensions = sizeOf(`${dropbox}/slide${currentSlide.page}.png`);
|
var dimensions = sizeOf(path.join(dropbox, `slide${currentSlide.page}.png`));
|
||||||
var slideWidth = dimensions.width;
|
var slideWidth = dimensions.width;
|
||||||
var slideHeight = dimensions.height;
|
var slideHeight = dimensions.height;
|
||||||
|
|
||||||
@ -381,8 +383,8 @@ for (let currentSlide of pages) {
|
|||||||
|
|
||||||
svg = svg.end({ prettyPrint: true });
|
svg = svg.end({ prettyPrint: true });
|
||||||
// Write annotated SVG file
|
// Write annotated SVG file
|
||||||
let SVGfile = `${dropbox}/annotated-slide${currentSlide.page}.svg`
|
let SVGfile = path.join(dropbox, `annotated-slide${currentSlide.page}.svg`)
|
||||||
let PDFfile = `${dropbox}/annotated-slide${currentSlide.page}.pdf`
|
let PDFfile = path.join(dropbox, `annotated-slide${currentSlide.page}.pdf`)
|
||||||
|
|
||||||
fs.writeFileSync(SVGfile, svg, function(err) {
|
fs.writeFileSync(SVGfile, svg, function(err) {
|
||||||
if(err) { return logger.error(err); }
|
if(err) { return logger.error(err); }
|
||||||
@ -408,14 +410,16 @@ for (let currentSlide of pages) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create PDF output directory if it doesn't exist
|
// Create PDF output directory if it doesn't exist
|
||||||
let output_dir = `${exportJob.presLocation}/pdfs`;
|
let output_dir = path.join(exportJob.presLocation, 'pdfs', jobId);
|
||||||
if (!fs.existsSync(output_dir)) { fs.mkdirSync(output_dir); }
|
let filename = sanitize(`annotated_${exportJob.meetingName}_${path.parse(exportJob.presName).name}`).replace(/\s/g, '_');
|
||||||
|
|
||||||
|
if (!fs.existsSync(output_dir)) { fs.mkdirSync(output_dir, { recursive: true }); }
|
||||||
|
|
||||||
let mergePDFs = [
|
let mergePDFs = [
|
||||||
'gs',
|
'gs',
|
||||||
'-dNOPAUSE',
|
'-dNOPAUSE',
|
||||||
'-sDEVICE=pdfwrite',
|
'-sDEVICE=pdfwrite',
|
||||||
`-sOUTPUTFILE=${output_dir}/annotated_slides_${jobId}.pdf`,
|
`-sOUTPUTFILE="${path.join(output_dir, `${filename}.pdf`)}"`,
|
||||||
`-dBATCH`,
|
`-dBATCH`,
|
||||||
ghostScriptInput,
|
ghostScriptInput,
|
||||||
].join(' ');
|
].join(' ');
|
||||||
@ -432,8 +436,8 @@ execSync(mergePDFs, (error, stderr) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Launch Notifier Worker depending on job type
|
// Launch Notifier Worker depending on job type
|
||||||
logger.info(`Saved PDF at ${output_dir}/annotated_slides_${jobId}.pdf`);
|
logger.info(`Saved PDF at ${output_dir}/${jobId}/${filename}.pdf`);
|
||||||
|
|
||||||
kickOffNotifierWorker(exportJob.jobType);
|
kickOffNotifierWorker(exportJob.jobType, filename);
|
||||||
|
|
||||||
parentPort.postMessage({ message: workerData })
|
parentPort.postMessage({ message: workerData })
|
||||||
|
Loading…
Reference in New Issue
Block a user