// ================== functions alphabetical order ================================================== const logger = require('./logger.js'); const fs = require('fs'); const path = require('path') const moment = require('moment'); module.exports = { isDisabled: function (serverName) { let outcome = false; config.casparcg.servers.forEach((item,index) => { // console.log('Iterating server ' + item.name + ', disabled = ' + item.disabled); if (item.name == serverName && item.disabled == true) { outcome = true } }); return outcome; }, clearChannelsFromGCServer: function (serverName='') { //TODO: add 2nd parameter an Array of layer numbers... let ClearAMCPCommand = 'CLEAR 1\r\n CLEAR 2\r\n CLEAR 3\r\n CLEAR 4\r\n CLEAR 5\r\n CLEAR 6\r\n CLEAR 7\r\n CLEAR 8\r\n'; if (serverName) { // clear the given server global.CCGSockets[this.getSockIndex(serverName)].write(ClearAMCPCommand); } else { // clear all configured servers if ( config.casparcg && config.casparcg.servers ) { config.casparcg.servers.forEach((item,index) => { global.CCGSockets[this.getSockIndex(item.name)].write(ClearAMCPCommand); }); } } logger.verbose('Clearing CasparCG [server: ' + serverName + ']'); }, CGComponentFactory: function (fieldID, value) { // Generate template data entry XML for CasparCG. // decodeURIComponent and UnSwapCharacters() added here to workaround issue #5. // require ..... fieldID, such as 'f0' and value such as 'Tuomo' // returns ..... component element as xml string, example: /* */ logger.debug('CGComponentFactory (for XML data only) - fieldID: ' + fieldID + ', value: ' + value); let decodedValue = decodeURIComponent(value) || ""; // changed again. Was += " " and then "null" <:-/ return ``; }, playoutController: function (data){ // We get data object which has // - data.command (ADD | STOP | UPDATE) // let GFX_Teml = data.relpathCCG; // before 1.0.7 // let GFX_Teml = 'http://' + ip.address() + ':' + config.general.port + '/templates/' + data.relpathCCG + '.html'; // changed to http in 1.0.7 // let GFX_Teml = 'http://localhost:' + config.general.port + '/templates/' + data.relpathCCG + '.html'; // accidentally left to "localhost" in 1.0.8. Oops <:-O // 1.0.12 - Check if we do have CCG servers configured let CCGSERVER = global.CCGSockets[this.getSockIndex(data.playserver)]; if (!CCGSERVER || typeof CCGSERVER === 'undefined') { logger.verbose('No CasparCG servers configured (or server [' + data.playserver + '] not found) in SPX-GC. Skipping CCG command ' + data.command); return } if (!data.playserver || data.playserver=='' || typeof data.playserver === 'undefined') { logger.verbose('No CasparCG server configured in the template. Skipping CCG command ' + data.command); return } if (this.isDisabled(data.playserver)===true) { logger.verbose('Server ' + data.playserver + ' temporarily disabled, canceling playout commands.' ); return; } let GFX_Teml = getCCGTemplateFilepath(data.relpathCCG); // v.1.0.9 = Supports both FILE or HTTP template paths, see config>general.casparcg-template-folder let GFX_Serv = data.playserver; let GFX_Chan = data.playchannel; let GFX_Laye = data.playlayer; let DataType = data.dataformat || 'json'; // added in 1.2.0 let InvFunct = data.invoke; data.command = data.command.toUpperCase(); // Notify user if ( !global.CCGSockets[this.getSockIndex(data.playserver)] ) { logger.warn('Server [' + GFX_Serv + '] not found in your config! Make sure configuration and project settings match.' ) } logger.verbose('CasparCG playoutController - command ' + data.command + ', Template: ' + GFX_Teml, ', CasparCG: ' + GFX_Serv + ', ' + GFX_Chan + ', ' + GFX_Laye); logger.debug('CasparCG playoutController ' + JSON.stringify(data,null,4)); logger.debug('Generating DATA for CasparCG [command ' + data.command + '], source: ' + JSON.stringify(data.fields,null,4)); let TEMPLATEDATA = ""; var DataStr = ""; if (data.command == "ADD" || data.command == "UPDATE") { if (data.fields) { data.fields.forEach((item,index) => { logger.debug(' DATA --> ' + item.field + ' : ' + item.value); // Fixes "undefined" issue in AMCP playout let value = ""; // default value if null or undefined if (item.value!=null && typeof item.value !== 'undefined') { value = item.value.toString(); } if (DataType == 'xml'){ // generate data in XML format TEMPLATEDATA += this.CGComponentFactory(item.field, value); } else { logger.debug('Generating JSON data for CasparCG, field: ' + item.field + ', value: ' + value); TEMPLATEDATA += '\\"' + item.field + '\\":\\"' + value + '\\",'; } }); } if (DataType == 'xml'){ // finalize XML format DataStr = "" + TEMPLATEDATA + ""; } else { // finalize JSON formatby removing trailing comma if (TEMPLATEDATA.slice(-1)==','){ TEMPLATEDATA = TEMPLATEDATA.slice(0, -1); } DataStr = "{" + TEMPLATEDATA + "}"; } } logger.debug('[' + DataType + '] ' + DataStr); try { switch (data.command) { case 'ADD': global.CCGSockets[this.getSockIndex(data.playserver)].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' ADD 1 "' + GFX_Teml + '" 1 "' + DataStr + '"\r\n'); break; case 'UPDATE': global.CCGSockets[this.getSockIndex(data.playserver)].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' UPDATE 1 "' + DataStr + '"\r\n'); break; case 'STOP': global.CCGSockets[this.getSockIndex(data.playserver)].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' STOP 1\r\n'); break; case 'NEXT': global.CCGSockets[this.getSockIndex(data.playserver)].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' NEXT 0\r\n'); break; case 'INVOKE': InvFunct = InvFunct.replace(/"/g, '\\"'); // replace " with \" globally. Added in 1.1.0. global.CCGSockets[this.getSockIndex(data.playserver)].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' INVOKE 1 \"' + InvFunct + '\"\r\n'); break; default: logger.warn('CCG/Control (util) - Unknown command: ' + data.command); } } catch (error) { logger.error('ERROR in playoutController: ' + error) } }, // end add, VideoPlayerController: function (data) { // We get data object which has // - data.command (ADD | STOP | UPDATE) // handle video commands console.log('Handling VIDEO PLAYOUT', data); let GFX_Serv = data.playserver; let GFX_Chan = data.playchannel; let GFX_Laye = data.playlayer; let GFX_File = data.relpath; let GFX_CCGf = data.relpathCCG; let GFX_OPTS = data.playoptions; logger.info("VideoPlayerController / CasparCG: " + GFX_Serv + ", " + GFX_Chan + ", " + GFX_Laye + ", Videofile " + GFX_CCGf); logger.debug('CasparCG VideoPlayerController ' + JSON.stringify(data,null,4)); try { switch (data.command) { case 'play': logger.verbose('Playing VIDEO / ' + GFX_CCGf); global.CCGSockets[this.getSockIndex(GFX_Serv)].write('PLAY ' + GFX_Chan + '-' + GFX_Laye + ' ' + GFX_CCGf + ' ' + GFX_OPTS + ' \r\n'); break; case 'stop': logger.verbose('Stopping VIDEO / ' + GFX_CCGf); global.CCGSockets[this.getSockIndex(GFX_Serv)].write('STOP ' + GFX_Chan + '-' + GFX_Laye + '\r\n'); break; case 'fadeout': logger.verbose('Fadeout VIDEO / ' + GFX_CCGf); // PLAY 1-10 CLEAR MIX 50 global.CCGSockets[this.getSockIndex(GFX_Serv)].write('PLAY ' + GFX_Chan + '-' + GFX_Laye + ' EMPTY MIX 13\r\n'); break; default: logger.warn('CCG/controlvideo - Unknown command: ' + data.command); } // res.sendStatus(200); } catch (error) { logger.error('ERROR in VideoPlayerController: ' + error) // res.sendStatus(500); } }, // end video player getSockIndex: function (SERVERNAME) { // Get an index of a CasparCG socket connection reference, not the object directly. // require .... SERVERNAME (example "TG") // returns .... CCG Server object INDEX (such as 0) logger.debug('getSockIndex / Searching for Socket reference for connection "' + SERVERNAME + '"...'); let serverIndex = ""; CCGSockets.forEach(function (item, index) { if (global.CCGSockets[index].spxname.toLowerCase() == SERVERNAME.toLowerCase()) { serverIndex = index; logger.debug('getSockIndex found [' + global.CCGSockets[index].spxname + '] so index is [' + serverIndex + '].'); } else { logger.debug('getSockIndex skipping ' + global.CCGSockets[index].spxname) + '...'; } }); return serverIndex; } } // end of exports PlayoutCCG. function getCCGTemplateFilepath(fileRef) { // Added in 1.0.9. // Return either the "simple filepath" or "full URL" for the CasparCG command. const spx = require('./spx_server_functions.js'); let TemplateSource = spx.getTemplateSourcePath() let TemplatePathForCasparCGServer if ( TemplateSource.substring(0, 4)=='http' ) // HTTP TemplatePathForCasparCGServer = TemplateSource + ':' + config.general.port + '/templates/' + fileRef + '.html'; // else { // FILE TemplatePathForCasparCGServer = fileRef; // as-is, a filepath in CasparCG server's own template-path directory } return TemplatePathForCasparCGServer }