node-red-contrib-mog
Version:
mog
602 lines (515 loc) • 26.2 kB
JavaScript
const express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var fs = require('fs');
const fetch = require("node-fetch");
const homedir = require('os').homedir();
var spawn = require("child_process").spawn,child;
const app = express();
const port = 3000;
if(process.argv.length != 3) {
console.log("Missing connection port or too many arguments. Use 'node server.js <connection_port>");
console.log("Aborting...");
console.log("Server shut down");
return(1);
}
const connection_port = process.argv[2];
var children = [];
var last_event = "";
var filename;
fs.writeFile("video-switcher.bat", "", 'utf8', function (err) {
if (err) throw err;
console.log('Saved!');
});
fs.writeFile("audio-switcher.bat", "", 'utf8', function (err) {
if (err) throw err;
console.log('Saved!');
});
var change = 0;
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(function(req, res, next) {
res.setHeader("Content-Type", "application/json");
next();
});
app.get('/database', function (req, res) {
var obj;
fs.readFile('db.json', 'utf8', function(err, data){
if (err){
console.log(err);
} else {
obj = JSON.parse(data);
console.log(obj);
res.end(JSON.stringify(obj));
}});
})
app.post('/database', function (req, res) {
var dir = homedir + '\\.node-red\\flows_'+ process.env.COMPUTERNAME +'.json';
fs.watch(dir, function (event) {
if(event == 'change' && last_event != 'change') {
console.log("Sending database");
var data = fs.readFileSync(dir);
var node_red_nodes = JSON.parse(data);
var nodes = req.body.nodes;
var toDelete = [];
for(var i = 0; i<children.length; i++) {
spawn("taskkill", ["/pid", children[i].pid, '/f', '/t']);
}
children = [];
var cmd = "Get-Process | Where-Object { $_.ProcessName -eq 'ffplay' } | ForEach-Object {taskkill /F /PID $_.Id }";
child = spawn("powershell",[cmd]);
children.push(child);
console.log("PowerShell: " + cmd);
child.stdout.on("data",function(data){
console.log("PowerShell: " + data);
});
child.stderr.on("data",function(data){
console.log("PowerShell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
//call batchfile
command = "\"OutputDistributor\" " + filename;
child = spawn("start",[command], {shell: true, detached: true});
children.push(child);
console.log("PowerShell: " + command);
child.stdout.on("data",function(data){
console.log("Powershell: " + data);
});
child.stderr.on("data",function(data){
console.log("Powershell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
});
child.stdin.end();
});
child.stdin.end();
cmd = "Get-Process | Where-Object { $_.mainWindowTitle -eq 'OutputDistributor - "+filename+"' } | ForEach-Object {taskkill /F /PID $_.Id }";
child = spawn("powershell",[cmd]);
children.push(child);
console.log("PowerShell: " + cmd);
child.stdout.on("data",function(data){
console.log("PowerShell: " + data);
});
child.stderr.on("data",function(data){
console.log("PowerShell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
});
child.stdin.end();
cmd = "Get-Process | Where-Object { $_.mainWindowTitle -eq 'VideoSwitcher - video-switcher.bat' } | ForEach-Object {taskkill /F /PID $_.Id }";
child = spawn("powershell",[cmd]);
children.push(child);
console.log("PowerShell: " + cmd);
child.stdout.on("data",function(data){
console.log("PowerShell: " + data);
});
child.stderr.on("data",function(data){
console.log("PowerShell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
//call batchfile
command = "\"VideoSwitcher\" " + 'video-switcher.bat';
child = spawn("start",[command], {shell: true, detached: true});
children.push(child);
console.log("PowerShell: " + command);
child.stdout.on("data",function(data){
console.log("Powershell: " + data);
});
child.stderr.on("data",function(data){
console.log("Powershell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
});
child.stdin.end();
});
child.stdin.end();
cmd = "Get-Process | Where-Object { $_.mainWindowTitle -eq 'AudioSwitcher - audio-switcher.bat' } | ForEach-Object {taskkill /F /PID $_.Id }";
child = spawn("powershell",[cmd]);
children.push(child);
console.log("PowerShell: " + cmd);
child.stdout.on("data",function(data){
console.log("PowerShell: " + data);
});
child.stderr.on("data",function(data){
console.log("PowerShell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
//call batchfile
command = "\"AudioSwitcher\" " + 'audio-switcher.bat';
child = spawn("start",[command], {shell: true, detached: true});
children.push(child);
console.log("PowerShell: " + command);
child.stdout.on("data",function(data){
console.log("Powershell: " + data);
});
child.stderr.on("data",function(data){
console.log("Powershell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
});
child.stdin.end();
});
child.stdin.end();
for(var i=0; i<nodes.length; i++) {
var match = 0;
for(var m=0; m<node_red_nodes.length; m++) {
if(nodes[i].id == node_red_nodes[m].id) {
match = 1;
break;
}
}
if(match == 0) {
toDelete.push(i);
} else {
if(nodes[i].type == "input" || nodes[i].type == "input-file"){
var command = 'ffmpeg -loglevel quiet -re -f lavfi -i testsrc=size=qcif:rate=25 -f lavfi -i "sine=frequency=1000" ';
if(change == 1) {
command = 'ffmpeg -loglevel quiet -re -f lavfi -i smptebars=size=qcif:rate=25 -f lavfi -i "sine=frequency=2000" ';
change = 0;
} else if(change == 0) {
change = 1;
}
for(var n = 0; n<nodes[i].video_ips.length; n++){
command += '-pix_fmt yuv420p -an -f rawvideo udp://' + nodes[i].video_ips[n] + ':' + nodes[i].video_ports[n] + ' ';
}
for(var n = 0; n<nodes[i].audio_ips.length; n++){
command += '-vn -f s16le udp://' + nodes[i].audio_ips[n] + ':' + nodes[i].audio_ports[n] + ' ';
}
command += ' -loglevel quiet';
child = spawn("powershell.exe",[command], {shell: true, detached: true});
children.push(child);
console.log("PowerShell: " + command);
child.stdout.on("data",function(data){
console.log("Powershell: " + data);
});
child.stderr.on("data",function(data){
console.log("Powershell: " + data);
});
child.on("exit",function(){
console.log("Powershell Script finished");
});
child.stdin.end();
} else if(nodes[i].type == "output") {
nodes[i].audio_ips = [];
nodes[i].video_ips = [];
nodes[i].audio_ports = [];
nodes[i].video_ports = [];
for(var j=0; j<node_red_nodes.length; j++) {
if(node_red_nodes[j].wires != undefined){
for(var k=0; k<node_red_nodes[j].wires.length; k++) {
for(var z=0; z<node_red_nodes[j].wires[k].length; z++) {
if(node_red_nodes[j].wires[k][z] == nodes[i].id){
var aux;
if((k==1 || node_red_nodes[j].type == 'audio-switcher') && node_red_nodes[j].audio_ips != undefined) {
aux = nodes[i].audio_ips.concat(node_red_nodes[j].audio_ips);
for(var z = 0; z<aux.length; z++){
if (nodes[i].audio_ips.indexOf(aux[z]) < 0 ) nodes[i].audio_ips.push(aux[z]);
}
aux = nodes[i].audio_ports.concat(node_red_nodes[j].audio_ports);
for(var z = 0; z<aux.length; z++){
if (nodes[i].audio_ports.indexOf(aux[z]) < 0 ) nodes[i].audio_ports.push(aux[z]);
}
//nodes[i].n_audio_inputs = nodes[i].audio_ips.length;
}
if(k==0 && node_red_nodes[j].video_ips != undefined) {
aux = nodes[i].video_ips.concat(node_red_nodes[j].video_ips);
for(var z = 0; z<aux.length; z++){
if (nodes[i].video_ips.indexOf(aux[z]) < 0 ) nodes[i].video_ips.push(aux[z]);
}
aux = nodes[i].video_ports.concat(node_red_nodes[j].video_ports);
for(var z = 0; z<aux.length; z++){
if (nodes[i].video_ports.indexOf(aux[z]) < 0 ) nodes[i].video_ports.push(aux[z]);
}
//nodes[i].n_video_inputs = nodes[i].video_ips.length;
}
}
}
}
}
}
// Write to a batch file
var command = '@echo off\r\nffmpeg -loglevel quiet ';
for(var n = 0; n<nodes[i].video_ips.length; n++){
command += '-f rawvideo -pixel_format yuv420p -framerate 25 -video_size 176x144 -i "udp://' + nodes[i].video_ips[n] + ':' + nodes[i].video_ports[n] + '?fifo_size=100000&buffer_size=10000000" ';
}
for(var n = 0; n<nodes[i].audio_ips.length; n++){
command += '-f s16le -i "udp://' + nodes[i].audio_ips[n] + ':' + nodes[i].audio_ports[n] + '?fifo_size=100000&buffer_size=10000000" ';
}
command += '-c copy -f avi - | ffplay -loglevel quiet -';
filename = 'output'+i+'.bat';
fs.writeFile(filename, command, 'utf8', function (err) {
if (err) throw err;
console.log('Saved!');
});
}
else if(nodes[i].type == "video-switcher") {
nodes[i].input_video_ips = [];
nodes[i].input_video_ports = [];
for(var j=0; j<node_red_nodes.length; j++) {
if(node_red_nodes[j].wires != undefined){
for(var k=0; k<node_red_nodes[j].wires.length; k++) {
for(var z=0; z<node_red_nodes[j].wires[k].length; z++) {
if(node_red_nodes[j].wires[k][z] == nodes[i].id){
var aux = nodes[i].input_video_ips.concat(node_red_nodes[j].video_ips);
nodes[i].input_video_ips = aux;
aux = nodes[i].input_video_ports.concat(node_red_nodes[j].video_ports);
nodes[i].input_video_ports = aux;
//nodes[i].n_input_video_inputs = nodes[i].input_video_ips.length;
}
}
}
}
}
var command = '@echo off\r\nffmpeg -loglevel quiet ';
for(var n = 0; n<nodes[i].input_video_ips.length; n++){
command += '-f rawvideo -pixel_format yuv420p -framerate 25 -video_size 176x144 -i "udp://' + nodes[i].input_video_ips[n] + ':' + nodes[i].input_video_ports[n] + '?fifo_size=100000&buffer_size=10000000" ';
}
command += '-lavfi "null, zmq [bg];[bg]';
for(var n = 0; n<nodes[i].input_video_ips.length; n++){
command += '['+ n + ']';
}
var number = nodes[i].input_video_ips.length + 1;
command += 'streamselect=inputs=' + number + ':map=' + nodes[i].video_ips.length + '[out]" -map [out] ';
for(var n = 0; n<nodes[i].video_ips.length; n++){
command += '-f rawvideo -pixel_format yuv420p -framerate 25 -video_size 176x144 "udp://' + nodes[i].video_ips[n] + ':' + nodes[i].video_ports[n] + '" ';
}
command += ' -loglevel quiet';
var video_file = 'video-switcher.bat';
fs.writeFile(video_file, command, 'utf8', function (err) {
if (err) throw err;
console.log('Saved!video');
});
}
else if(nodes[i].type == "audio-switcher") {
nodes[i].input_audio_ips = [];
nodes[i].input_audio_ports = [];
for(var j=0; j<node_red_nodes.length; j++) {
if(node_red_nodes[j].wires != undefined){
for(var k=0; k<node_red_nodes[j].wires.length; k++) {
for(var z=0; z<node_red_nodes[j].wires[k].length; z++) {
if(node_red_nodes[j].wires[k][z] == nodes[i].id){
var aux = nodes[i].input_audio_ips.concat(node_red_nodes[j].audio_ips);
nodes[i].input_audio_ips = aux;
aux = nodes[i].input_audio_ports.concat(node_red_nodes[j].audio_ports);
nodes[i].input_audio_ports = aux;
//nodes[i].n_input_audio_inputs = nodes[i].input_audio_ips.length;
}
}
}
}
}
var command = '@echo off\r\nffmpeg -loglevel quiet ';
for(var n = 0; n<nodes[i].input_audio_ips.length; n++){
command += '-f s16le -i "udp://' + nodes[i].input_audio_ips[n] + ':' + nodes[i].input_audio_ports[n] + '?fifo_size=100000&buffer_size=10000000" ';
}
command += '-lavfi "anull, azmq=bind_address=\'tcp\\://127.0.0.1\\:1235\' [bg];[bg]';
for(var n = 0; n<nodes[i].input_audio_ips.length; n++){
command += '['+ n + ']';
}
var number = nodes[i].input_audio_ips.length + 1;
command += 'astreamselect=inputs=' + number + ':map=' + nodes[i].audio_ips.length + '[out]" -map [out] ';
for(var n = 0; n<nodes[i].audio_ips.length; n++){
command += '-f s16le "udp://' + nodes[i].audio_ips[n] + ':' + nodes[i].audio_ports[n] + '" ';
}
command += '-loglevel quiet';
var audio_file = 'audio-switcher.bat';
fs.writeFile(audio_file, command, 'utf8', function (err) {
if (err) throw err;
console.log('Saved!audio');
});
}
}
}
for(var q = 0; q<toDelete.length; q++) {
nodes.splice(toDelete[q],1);
}
console.log(nodes);
console.log(req.body);
var json = JSON.stringify(req.body);
fs.writeFile('db.json', json, 'utf8', function (err) {
if (err) throw err;
console.log('Saved!');
});
res.end(json);
}
last_event = event;
});
})
app.get('/devices', function (req, res) {
var obj;
fs.readFile('available-devices.json', 'utf8', function(err, data){
if (err){
console.log(err);
} else {
obj = JSON.parse(data);
var availableDevices = {
devices: []
};
for(var i = 0; i < obj.services.length; i++) {
if(obj.services[i].protocol == req.query.type) {
availableDevices.devices.push(obj.services[i].fullname);
}
}
res.end(JSON.stringify(availableDevices));
}});
})
app.get('/services', function (req, res) {
//get request ao node
var deviceChosen;
var data = fs.readFileSync('available-devices.json');
obj = JSON.parse(data);
for(var i = 0; i < obj.services.length; i++) {
if(obj.services[i].fullname == req.query.deviceID) {
deviceChosen = obj.services[i];
break;
}
}
var serviceType;
if(req.query.type == 'input'){
serviceType = "senders";
} else if(req.query.type == 'output') {
serviceType = "receivers";
} else {
serviceType = "senders";
}
const url = 'http://' + deviceChosen.addresses[0] + ':' + deviceChosen.port + '/x-nmos/node/v1.3/' + serviceType;
console.log(url);
fetch(url)
.then(function(response) {
console.log('Status code:', response.status);
return response.json();
})
.then(function(myJson) {
var data = JSON.parse(JSON.stringify(myJson));
console.log(data);
var availableServices = {
services: [],
available: []
};
var exec = async() => {
for(var i = 0; i < data.length; i++) {
const url = "http://" + deviceChosen.addresses[0] + ":" + connection_port + "/x-nmos/connection/v1.1/single/" + serviceType + "/" + data[i].id + "/active";
console.log(url);
var d = data[i].id;
const request = async(d) => {
const response = await fetch(url)
.then(function(response) {
console.log('Status code:', response.status);
return response.json();
})
.catch(error => console.error(error));
return response.activation.activation_time;
}
var response = await request(d);
if(response == null){
availableServices.services.push(d);
availableServices.available.push(1);
} else {
availableServices.services.push(d);
availableServices.available.push(0);
}
}
console.log(availableServices);
return availableServices;
}
async function finish() {
await exec();
res.end(JSON.stringify(availableServices));
};
finish();
})
.catch(error => console.error(error));
})
app.post('/', function (req, res) {
var deviceChosen;
var data = fs.readFileSync('available-devices.json');
obj = JSON.parse(data);
for(var i = 0; i < obj.services.length; i++) {
if(obj.services[i].fullname == req.body.device) {
deviceChosen = obj.services[i];
break;
}
}
var serviceType1;
var serviceType2;
if(req.body.type == 'input'){
serviceType1 = "senders";
serviceType2 = "senders";
} else if(req.body.type == 'output') {
serviceType1 = "receivers";
serviceType2 = "receivers";
} else if(req.body.type == "audio-switcher" || req.body.type == "video-switcher") {
serviceType1 = "senders";
serviceType2 = "receivers";
} else {
serviceType = "senders";
}
if(deviceChosen != "" && deviceChosen != undefined) {
var services1 = [];
var services2 = [];
if(req.body.type == "audio-switcher" || req.body.type == "video-switcher"){
services1 = req.body.output_services;
services2 = req.body.input_services;
} else {
services1 = req.body.audio_services;
services2 = req.body.video_services;
}
for(var i = 0; i < services1.length; i++){
var url = "http://" + deviceChosen.addresses[0] + ":" + connection_port + "/x-nmos/connection/v1.1/single/" + serviceType1 + "/" + services1[i] + "/staged";
console.log("POST" + url);
fetch(url, {
method : "PATCH",
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body : JSON.stringify({
activation: {
mode: "activate_immediate",
requested_time: null
}
})
})
.then(function(response) {
console.log('Status code:', response.status);
if(response.status == 200){
res.send({ status: 'SUCCESS' });
}
})
.catch(error => console.error('Error:', error));
}
for(var i = 0; i < services2.length; i++){
var url = "http://" + deviceChosen.addresses[0] + ":" + connection_port + "/x-nmos/connection/v1.1/single/" + serviceType2 + "/" + services2[i] + "/staged";
console.log("POST" + url);
fetch(url, {
method : "PATCH",
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body : JSON.stringify({
activation: {
mode: "activate_immediate",
requested_time: null
}
})
})
.then(function(response) {
console.log('Status code:', response.status);
if(response.status == 200){
res.send({ status: 'SUCCESS' });
}
})
.catch(error => console.error('Error:', error));
}
}
})
app.listen(port, () => console.log(`Server listening on port ${port}!`))