Compare commits

...

13 Commits

Author SHA1 Message Date
Matt McWilliams a57519adce Merge pull request 'Merge all work on server with filmout features' (#77) from server into main
Reviewed-on: #77
2023-02-25 21:16:28 -05:00
Matt McWilliams 02639466ee By doing a check for active clients during the sequencer start and stop actions, the local display is no longer used. Resolves first part of #20. 2023-02-25 17:00:11 -05:00
Matt McWilliams 6e2795d380 Remove ACK messages for ping, rely on 2023-02-25 16:33:55 -05:00
Matt McWilliams 8e35596088 Fixed the iOS issue. Also allow for fullscreen on all browsers (with no sleep). Can preview properly but normal mode does not work 2023-02-25 16:32:08 -05:00
Matt McWilliams 0162d012c5 Server can now display images and will completely preempt local display when done 2023-02-25 13:22:20 -05:00
Matt McWilliams 47fb673b78 Combined script and html into single request (problems with iOS safari).
Can get script loaded but still cannot connect to websocket server without SSL.

ALSO: Now can pre-empt opening new display if an active client is connected to server.
Otherwise will open a local link.
2023-02-25 13:11:40 -05:00
Matt McWilliams f296488bc2 Further progress on #20. TODO: Image display logic on the server side. Need to add image to proxy list and give it a properly-extensioned name and cmdAll.
ALSO: Server is not working on iOS.
2023-02-25 12:40:35 -05:00
Matt McWilliams f5392aea9f Resolves #74 2023-02-25 12:33:18 -05:00
Matt McWilliams 24b1301f9f Adding contextIsolation: false to the display module fixes the initial issue in #74, cannot use "require" but the escape button does not work due to "Cannot read properties of undefined (reading 'getCurrentWindow')" 2023-02-25 12:16:47 -05:00
Matt McWilliams 1290a8f324 Major progress on #20. Promised-based websocket command structure is completed. All functionality needs to be placed into individual classes and any new functions get added to the server.
Will create entire thin client using this method.
2023-02-25 11:59:56 -05:00
Matt McWilliams e64277e438 More work on #20. Fix typos in display code. Should re-work script into transpiled ts file. 2023-02-25 02:08:56 -05:00
Matt McWilliams c9bcb74a9f Progress on #20. Can start up a server and serve client.js 2023-02-25 02:08:05 -05:00
Matt McWilliams 42db1f81b8 Server work 2023-02-25 00:24:07 -05:00
33 changed files with 2451 additions and 164 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.7", "version": "1.7.18",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

View File

@ -13,6 +13,16 @@
body.meter { body.meter {
background: rgb(117, 117, 117); background: rgb(117, 117, 117);
} }
body.meter #img,
body.meter #can {
display: none;
}
body.image #can{
display: none;
}
body.image #img {
display: block;
}
#img { #img {
position: absolute; position: absolute;
/*background-image: url(".../img/background.jpg");*/ /*background-image: url(".../img/background.jpg");*/
@ -46,16 +56,16 @@
</canvas> </canvas>
<script> <script>
'use strict'; 'use strict';
const { remote, ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
const remote = require('@electron/remote')
let imgTmp; let imgTmp;
async function setImage (src) { async function setImage (src) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
imgTmp = new Image() imgTmp = new Image()
let img = document.getElementById('img') let img = document.getElementById('img')
let body = document.querySelector('body') let body = document.querySelector('body')
if (body.classList.contains('meter')) { body.className = ''
body.classList.remove('meter') body.classList.add('image')
}
imgTmp.onload = function () { imgTmp.onload = function () {
img.style.backgroundImage = `url('${src}')`; img.style.backgroundImage = `url('${src}')`;
return resolve(src); return resolve(src);
@ -66,15 +76,16 @@
async function onMeter () { async function onMeter () {
console.log('meter') console.log('meter')
const body = document.querySelector('body') const body = document.querySelector('body')
if (!body.classList.contains('meter')) { body.className = ''
body.classList.add('meter') body.classList.add('meter')
} }
}
async function onFocus () { async function onFocus () {
console.log('focus') console.log('focus')
const can = document.getElementById('can') const can = document.getElementById('can')
const dpr = window.devicePixelRatio || 1 const dpr = window.devicePixelRatio || 1
const body = document.querySelector('body')
let ctx; let ctx;
body.className = ''
if (!can.classList.contains('show')) { if (!can.classList.contains('show')) {
can.classList.add('show') can.classList.add('show')
} }
@ -122,7 +133,9 @@
const can = document.getElementById('can') const can = document.getElementById('can')
const dpr = window.devicePixelRatio || 1 const dpr = window.devicePixelRatio || 1
const screen = window.outerWidth / window.outerHeight const screen = window.outerWidth / window.outerHeight
const body = document.querySelector('body')
let ctx; let ctx;
body.className = ''
if (!can.classList.contains('show')) { if (!can.classList.contains('show')) {
can.classList.add('show') can.classList.add('show')
} }
@ -145,7 +158,7 @@
can.style.width = `${window.innerHeight * arg.ratio}px` can.style.width = `${window.innerHeight * arg.ratio}px`
can.style.height = `${window.innerHeight}px` can.style.height = `${window.innerHeight}px`
} else { } else {
can.style.width = `${window.inneWidth}px` can.style.width = `${window.innerWidth}px`
can.style.height = `${window.innerWidth / arg.ratio}px` can.style.height = `${window.innerWidth / arg.ratio}px`
} }
} else { } else {
@ -217,9 +230,11 @@
} }
return event.returnValue = true return event.returnValue = true
} }
async function onEscape (evt) { async function onEscape (evt) {
let isEscape = false let isEscape = false
let win let win
evt = evt || window.event evt = evt || window.event
if ('key' in evt) { if ('key' in evt) {
@ -227,6 +242,7 @@
} else { } else {
isEscape = (evt.keyCode == 27) isEscape = (evt.keyCode == 27)
} }
if (isEscape) { if (isEscape) {
win = remote.getCurrentWindow() win = remote.getCurrentWindow()
win.close() win.close()

287
app/lib/client/index.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/**
* Delay in an async/await function
*
* @param {integer} ms Milliseconds to delay for
*
* @returns {Promise} Promise to resolve after timeout
**/
declare function delay(ms: number): Promise<unknown>;

View File

@ -17,7 +17,8 @@ class WebView {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
allowRunningInsecureContent: false, allowRunningInsecureContent: false,
enableRemoteModule: true enableRemoteModule: true,
contextIsolation: false
}, },
width: 800, width: 800,
height: 600, height: 600,
@ -35,6 +36,7 @@ class WebView {
prefs.y = display.y + 50; prefs.y = display.y + 50;
} }
this.digitalWindow = new BrowserWindow(prefs); this.digitalWindow = new BrowserWindow(prefs);
require('@electron/remote/main').enable(this.digitalWindow.webContents);
this.digitalWindow.loadURL(pageUrl); this.digitalWindow.loadURL(pageUrl);
if (process.argv.indexOf('-d') !== -1 || process.argv.indexOf('--dev') !== -1) { if (process.argv.indexOf('-d') !== -1 || process.argv.indexOf('--dev') !== -1) {
this.digitalWindow.webContents.openDevTools(); this.digitalWindow.webContents.openDevTools();

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@ class FilmOut {
* @param {object} ui Electron ui object * @param {object} ui Electron ui object
* @param {object} light Light device object * @param {object} light Light device object
**/ **/
constructor(display, ffmpeg, ffprobe, ui, light) { constructor(display, server, ffmpeg, ffprobe, ui, light) {
this.id = 'filmout'; this.id = 'filmout';
this.videoExtensions = ['.mpg', '.mpeg', '.mov', '.mkv', '.avi', '.mp4']; this.videoExtensions = ['.mpg', '.mpeg', '.mov', '.mkv', '.avi', '.mp4'];
this.stillExtensions = ['.tif', '.tiff', '.png', '.jpg', '.jpeg', '.bmp']; this.stillExtensions = ['.tif', '.tiff', '.png', '.jpg', '.jpeg', '.bmp'];
@ -42,6 +42,7 @@ class FilmOut {
files: [] files: []
}; };
this.display = display; this.display = display;
this.server = server;
this.ffmpeg = ffmpeg; this.ffmpeg = ffmpeg;
this.ffprobe = ffprobe; this.ffprobe = ffprobe;
this.ui = ui; this.ui = ui;
@ -123,6 +124,10 @@ class FilmOut {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
throw err; throw err;
} }
if (this.server.displayImage(path)) {
await delay_1.delay(20);
return;
}
await this.display.show(path); await this.display.show(path);
await delay_1.delay(20); await delay_1.delay(20);
} }
@ -411,6 +416,9 @@ class FilmOut {
throw err; throw err;
} }
try { try {
if (await this.server.displayImage(path)) {
return;
}
await this.display.open(); await this.display.open();
await this.display.show(path); await this.display.show(path);
} }
@ -424,6 +432,9 @@ class FilmOut {
async focus(evt, arg) { async focus(evt, arg) {
this.log.info(`Showing focus screen`); this.log.info(`Showing focus screen`);
try { try {
if (await this.server.cmdAll('focus')) {
return;
}
await this.display.open(); await this.display.open();
await this.display.focus(); await this.display.focus();
} }
@ -438,6 +449,9 @@ class FilmOut {
const ratio = arg.ratio; const ratio = arg.ratio;
this.log.info(`Showing field guide screen`); this.log.info(`Showing field guide screen`);
try { try {
if (await this.server.cmdAll('field', { ratio })) {
return;
}
await this.display.open(); await this.display.open();
await this.display.field(ratio); await this.display.field(ratio);
} }
@ -451,6 +465,9 @@ class FilmOut {
async meter(evt, arg) { async meter(evt, arg) {
this.log.info(`Showing meter screen`); this.log.info(`Showing meter screen`);
try { try {
if (await this.server.cmdAll('meter')) {
return;
}
await this.display.open(); await this.display.open();
await this.display.meter(); await this.display.meter();
} }
@ -463,6 +480,9 @@ class FilmOut {
**/ **/
async close(evt, arg) { async close(evt, arg) {
try { try {
if (await this.server.cmdAll('blank')) {
return;
}
await this.display.hide(); await this.display.hide();
await this.display.close(); await this.display.close();
} }
@ -478,7 +498,7 @@ class FilmOut {
this.log.info(`Changing the display to ${arg.display}`); this.log.info(`Changing the display to ${arg.display}`);
} }
} }
module.exports = (display, ffmpeg, ffprobe, ui, light) => { module.exports = (display, server, ffmpeg, ffprobe, ui, light) => {
return new FilmOut(display, ffmpeg, ffprobe, ui, light); return new FilmOut(display, server, ffmpeg, ffprobe, ui, light);
}; };
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -50,7 +50,7 @@ async function logFile() {
* *
* @returns {object} Logger transport * @returns {object} Logger transport
**/ **/
module.exports = async function (arg) { module.exports = async function Log(arg) {
let consoleFormat = { let consoleFormat = {
colorize: true colorize: true
}; };

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/log/index.ts"],"names":[],"mappings":"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"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/log/index.ts"],"names":[],"mappings":"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"}

File diff suppressed because one or more lines are too long

View File

@ -146,7 +146,7 @@ class Sequencer {
//start sequence //start sequence
this.log.info(`Starting sequence...`); this.log.info(`Starting sequence...`);
this.ui.send(this.id, { start: true }); this.ui.send(this.id, { start: true });
if (this.cmd.proj.filmout.state.enabled === true) { if (this.cmd.proj.filmout.state.enabled === true && !this.cmd.proj.filmout.server.useServer()) {
await this.cmd.proj.filmout.display.open(); await this.cmd.proj.filmout.display.open();
} }
for (let x = 0; x < this.loops; x++) { for (let x = 0; x < this.loops; x++) {
@ -179,7 +179,7 @@ class Sequencer {
this.log.info(`Ended loop ${x + 1}`); this.log.info(`Ended loop ${x + 1}`);
this.ui.send(this.id, { loop: x, stop: true }); this.ui.send(this.id, { loop: x, stop: true });
} }
if (this.cmd.proj.filmout.state.enabled === true) { if (this.cmd.proj.filmout.state.enabled === true && !this.cmd.proj.filmout.server.useServer()) {
await this.cmd.proj.filmout.display.close(); await this.cmd.proj.filmout.display.close();
} }
electron_1.powerSaveBlocker.stop(psbId); electron_1.powerSaveBlocker.stop(psbId);

File diff suppressed because one or more lines are too long

1
app/lib/server/index.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

View File

@ -1,90 +1,197 @@
'use script' "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
let os return (mod && mod.__esModule) ? mod : { "default": mod };
let restify };
let app Object.defineProperty(exports, "__esModule", { value: true });
const ws_1 = require("ws");
let cam const express_1 = __importDefault(require("express"));
let proj const promises_1 = require("fs/promises");
let light const path_1 = require("path");
const uuid_1 = require("uuid");
const PACKAGE = require('../../package.json') const Log = require("log");
class Server { class Server {
constructor (camera, projector, light) { constructor() {
restify = require('restify') this.id = 'server';
os = require('os') this.isActive = false;
app = restify.createServer({ this.templates = [
name: 'mcopy-server', {
version: PACKAGE.version name: 'index',
}) path: 'server.html'
this.ip = this.getIp()
/*app.get('/', function (req, res) {
mcopy.mobile.log('Device connected');
res.send(fs.readFileSync('tmpl/mcopy_index.html', 'utf8'));
})
app.get('/js/mcopy_mobile.js', function (req, res) {
res.send(fs.readFileSync('js/mcopy_mobile.js', 'utf8'));
});
app.get('/js/jquery.js', function (req, res) {
res.send(fs.readFileSync('js/jquery.js', 'utf8'));
});
app.get('/cmd/:cmd', function (req, res) {
var cmd,
success = function (res) {
var obj = {
success: true,
cmd : cmd,
cam: {
dir : cam.dir,
pos : cam.pos
}, },
proj: { {
dir : proj.dir, name: 'script',
pos : proj.pos path: 'lib/client/index.js'
}
];
this.port = 9900;
this.wsPort = 9901;
this.proxy = {};
this.queue = {};
this.intervalPeriod = 10000; //10 sec
this.init();
}
async init() {
this.log = await Log({ label: this.id });
await this.load();
await this.start();
}
async load() {
this.http = express_1.default();
for (let tmpl of this.templates) {
tmpl.data = await promises_1.readFile(tmpl.path, 'utf8');
}
this.http.get('/', this.index.bind(this));
this.http.get('/image/:key', this.image.bind(this));
this.log.info("Server assets loaded");
}
template(name, data) {
let html = this.templates.find(el => el.name === name).data;
for (let key of Object.keys(data)) {
html = html.replace(`{{${key}}}`, data[key]);
}
return html;
}
async startWss() {
try {
this.wss = new ws_1.WebSocketServer({ port: this.wsPort });
}
catch (err) {
this.log.error(err);
return;
}
this.wss.on('connection', async function (ws) {
ws.on("message", function (data) {
let obj = JSON.parse(data);
//this.log.info(data)
if (obj.id && this.queue[obj.id]) {
this.queue[obj.id](obj);
delete this.queue[obj.id];
if (obj.action !== 'ping') {
this.log.info(`${obj.action} ACK`);
} }
} }
res.json(obj); }.bind(this));
ws.on('close', function () {
this.log.info('Client disconnected');
}.bind(this));
await this.cmd(ws, 'mcopy');
this.log.info('Client connected');
}.bind(this));
this.log.info(`Websocket server started!`);
this.log.info(`WSS [ ws://localhost:${this.wsPort} ]`);
}
async startHttp() {
return new Promise(function (resolve, reject) {
this.httpd = this.http.listen(this.port, function () {
this.log.info(`HTTP server started!`);
this.log.info(`URL [ http://localhost:${this.port} ]`);
return resolve(true);
}.bind(this));
}.bind(this));
}
async start() {
await this.startHttp();
await this.startWss();
this.interval = setInterval(async function () {
await this.cmdAll('ping');
}.bind(this), this.intervalPeriod);
this.isActive = true;
}
async stopHttp() {
return new Promise(function (resolve, reject) {
return this.httpd.close(function () {
this.log.info(`HTTP server stopped :(`);
return resolve(false);
}.bind(this));
}.bind(this));
}
async stop() {
await this.stopHttp();
clearInterval(this.interval);
this.isActive = false;
}
index(req, res, next) {
const SCRIPT = this.template('script', { PORT: `${this.wsPort}` });
const html = this.template('index', { SCRIPT });
this.log.info('GET /');
return res.send(html);
}
async image(req, res, next) {
let filePath;
if (req.params && req.params.key) {
if (this.proxy[req.params.key]) {
filePath = this.proxy[req.params.key].path;
}
else {
return false;
}
}
else {
return false;
}
return new Promise(function (resolve, reject) {
return res.sendFile(filePath, function (err) {
if (err) {
res.status(err.status).end();
return reject(err);
}
return resolve(true);
});
}.bind(this));
}
addProxy(key, filePath) {
//wipe every time
this.proxy = {};
this.proxy[key] = {
path: filePath
}; };
if (typeof req.params.cmd !== 'undefined') { this.log.info(`Added proxy image [${key}]`);
mcopy.log('Receiving command from mobile: ' + req.params.cmd);
cmd = req.params.cmd;
if (cmd === 'CF'){
mcopy.cmd.camera_forward(success);
} else if (cmd === 'CB') {
mcopy.cmd.camera_backward(success);
} else if (cmd === 'PF') {
mcopy.cmd.projector_forward(success);
} else if (cmd === 'PB') {
mcopy.cmd.projector_backward(success);
} else {
mcopy.mobile.fail(res, 'Command ' + cmd + ' not found');
} }
} else { async cmdAll(action, options = {}) {
mcopy.mobile.fail(res, 'No command provided'); const cmds = [];
if (this.useServer()) {
this.wss.clients.forEach(function (ws) {
cmds.push(this.cmd(ws, action, options));
}.bind(this));
await Promise.all(cmds);
return true;
} }
}); return false;
app.get('/state', function (req, res) {
res.json({
cam: {
dir : cam.dir,
pos : cam.pos
},
proj: {
dir : proj.dir,
pos : proj.pos
} }
}); async displayImage(src) {
});*/ let key;
if (this.useServer()) {
key = path_1.basename(src);
this.addProxy(key, src);
await this.cmdAll('image', { image: `/image/${key}` });
return true;
} }
end () { return false;
app.close() }
app = null useServer() {
return this.isActive && this.wss.clients.size > 0;
}
/**
* WSS
**/
async cmd(ws, action, options = {}) {
const id = uuid_1.v4();
let obj = {
id, action
};
let str;
obj = Object.assign(obj, options);
str = JSON.stringify(obj);
ws.send(str);
return new Promise(function (resolve, reject) {
this.queue[id] = function (obj) {
return resolve(obj);
};
//setTimeout() ?
}.bind(this));
} }
} }
module.exports = function () {
module.exports = Server return new Server();
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -39,6 +39,7 @@ let cmd;
let seq; let seq;
let capper; let capper;
let alert; let alert;
let server;
const cfg = require('./data/cfg.json') const cfg = require('./data/cfg.json')
@ -116,8 +117,9 @@ var init = async function () {
log.error('Error enumerating connected devices', err) log.error('Error enumerating connected devices', err)
} }
server = require('server')()
light = require('light')(arduino, cfg, mainWindow.webContents) light = require('light')(arduino, cfg, mainWindow.webContents)
filmout = require('filmout')(display, ffmpeg, ffprobe, mainWindow.webContents, light) filmout = require('filmout')(display, server, ffmpeg, ffprobe, mainWindow.webContents, light)
cam = require('cam')(arduino, cfg, mainWindow.webContents, filmout) cam = require('cam')(arduino, cfg, mainWindow.webContents, filmout)
proj = require('proj')(arduino, cfg, mainWindow.webContents, filmout) proj = require('proj')(arduino, cfg, mainWindow.webContents, filmout)
alert = require('alert')(mainWindow.webContents) alert = require('alert')(mainWindow.webContents)
@ -135,7 +137,6 @@ var init = async function () {
cmd = require('cmd')(cfg, proj, cam, light, alert, cam2, proj2, capper) cmd = require('cmd')(cfg, proj, cam, light, alert, cam2, proj2, capper)
seq = require('sequencer')(cfg, cmd, mainWindow.webContents) seq = require('sequencer')(cfg, cmd, mainWindow.webContents)
} }
app.on('ready', init) app.on('ready', init)

1167
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy-app", "name": "mcopy-app",
"version": "1.7.7", "version": "1.7.18",
"description": "GUI for the mcopy small gauge film optical printer platform", "description": "GUI for the mcopy small gauge film optical printer platform",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@ -64,6 +64,7 @@
"display": "file:lib/display", "display": "file:lib/display",
"exec": "file:lib/exec", "exec": "file:lib/exec",
"exit": "file:lib/exit", "exit": "file:lib/exit",
"express": "^4.18.2",
"ffmpeg": "file:lib/ffmpeg", "ffmpeg": "file:lib/ffmpeg",
"ffmpeg-static": "^5.0.0", "ffmpeg-static": "^5.0.0",
"ffprobe": "file:lib/ffprobe", "ffprobe": "file:lib/ffprobe",
@ -89,7 +90,8 @@
"spawn": "file:lib/spawn", "spawn": "file:lib/spawn",
"system": "file:lib/system", "system": "file:lib/system",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"winston": "^3.7.2" "winston": "^3.7.2",
"ws": "^8.12.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron-installer-debian": "^3.1.0" "electron-installer-debian": "^3.1.0"

39
app/server.html Normal file
View File

@ -0,0 +1,39 @@
<html>
<head>
<style>
html, body{ background: #000; padding: 0; margin: 0; width: 100vw; height: 100vh; overflow: hidden; }
body.meter { background: rgb(117, 117, 117); }
body.blank { background: #000; }
body.blank #img, body.blank #can, body.meter #img, body.meter #can, body.image #can { display: none; }
body.image #img, #can.show { display: block; }
#nosleep { color: #fff; margin: 50px auto; text-align: center; }
#img {
position: absolute;
background-repeat:no-repeat;
background-size: contain;
background-position: center;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#can{
margin: 0;
background: #fff;
display: none;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
</style>
<div id="nosleep">Click to full screen and prevent sleep</div>
<div id="img">
</div>
<canvas id="can">
</canvas>
<script>
{{SCRIPT}}
</script>
</body>
</html>

271
app/src/lib/client/index.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.7", "version": "1.7.18",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

164
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "mcopy", "name": "mcopy",
"version": "1.7.7", "version": "1.7.18",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mcopy", "name": "mcopy",
"version": "1.7.7", "version": "1.7.18",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"arduino": "file:app/lib/arduino", "arduino": "file:app/lib/arduino",
@ -28,12 +28,14 @@
}, },
"devDependencies": { "devDependencies": {
"@types/electron": "^1.6.10", "@types/electron": "^1.6.10",
"@types/express": "^4.17.17",
"@types/fs-extra": "^9.0.7", "@types/fs-extra": "^9.0.7",
"@types/jimp": "^0.2.28", "@types/jimp": "^0.2.28",
"@types/node": "^14.14.31", "@types/node": "^14.14.31",
"@types/request": "^2.48.5", "@types/request": "^2.48.5",
"@types/sharp": "^0.27.1", "@types/sharp": "^0.27.1",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/ws": "^8.5.4",
"jsdoc-to-markdown": "^6.0.1", "jsdoc-to-markdown": "^6.0.1",
"typescript": "^4.1.5" "typescript": "^4.1.5"
} }
@ -596,12 +598,31 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/caseless": { "node_modules/@types/caseless": {
"version": "0.12.2", "version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true "dev": true
}, },
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/electron": { "node_modules/@types/electron": {
"version": "1.6.10", "version": "1.6.10",
"resolved": "https://registry.npmjs.org/@types/electron/-/electron-1.6.10.tgz", "resolved": "https://registry.npmjs.org/@types/electron/-/electron-1.6.10.tgz",
@ -611,6 +632,29 @@
"electron": "*" "electron": "*"
} }
}, },
"node_modules/@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
"integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.33",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
"integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*"
}
},
"node_modules/@types/fs-extra": { "node_modules/@types/fs-extra": {
"version": "9.0.7", "version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.7.tgz",
@ -629,12 +673,30 @@
"jimp": "*" "jimp": "*"
} }
}, },
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
"dev": true
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "14.14.31", "version": "14.14.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
"integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==",
"dev": true "dev": true
}, },
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"node_modules/@types/request": { "node_modules/@types/request": {
"version": "2.48.5", "version": "2.48.5",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz",
@ -647,6 +709,16 @@
"form-data": "^2.5.0" "form-data": "^2.5.0"
} }
}, },
"node_modules/@types/serve-static": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
"integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
"dev": true,
"dependencies": {
"@types/mime": "*",
"@types/node": "*"
}
},
"node_modules/@types/sharp": { "node_modules/@types/sharp": {
"version": "0.27.1", "version": "0.27.1",
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.27.1.tgz",
@ -668,6 +740,15 @@
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
"dev": true "dev": true
}, },
"node_modules/@types/ws": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
"integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/ansi-escape-sequences": { "node_modules/ansi-escape-sequences": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz",
@ -3284,12 +3365,31 @@
"defer-to-connect": "^1.0.1" "defer-to-connect": "^1.0.1"
} }
}, },
"@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/caseless": { "@types/caseless": {
"version": "0.12.2", "version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true "dev": true
}, },
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/electron": { "@types/electron": {
"version": "1.6.10", "version": "1.6.10",
"resolved": "https://registry.npmjs.org/@types/electron/-/electron-1.6.10.tgz", "resolved": "https://registry.npmjs.org/@types/electron/-/electron-1.6.10.tgz",
@ -3299,6 +3399,29 @@
"electron": "*" "electron": "*"
} }
}, },
"@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
"integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.33",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
"integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*"
}
},
"@types/fs-extra": { "@types/fs-extra": {
"version": "9.0.7", "version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.7.tgz",
@ -3317,12 +3440,30 @@
"jimp": "*" "jimp": "*"
} }
}, },
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
"dev": true
},
"@types/node": { "@types/node": {
"version": "14.14.31", "version": "14.14.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
"integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==",
"dev": true "dev": true
}, },
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"@types/request": { "@types/request": {
"version": "2.48.5", "version": "2.48.5",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz",
@ -3335,6 +3476,16 @@
"form-data": "^2.5.0" "form-data": "^2.5.0"
} }
}, },
"@types/serve-static": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
"integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
"dev": true,
"requires": {
"@types/mime": "*",
"@types/node": "*"
}
},
"@types/sharp": { "@types/sharp": {
"version": "0.27.1", "version": "0.27.1",
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.27.1.tgz",
@ -3356,6 +3507,15 @@
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
"dev": true "dev": true
}, },
"@types/ws": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
"integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"ansi-escape-sequences": { "ansi-escape-sequences": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy", "name": "mcopy",
"version": "1.7.7", "version": "1.7.18",
"description": "Small gauge film optical printer platform", "description": "Small gauge film optical printer platform",
"main": "build.js", "main": "build.js",
"directories": { "directories": {
@ -26,12 +26,14 @@
"homepage": "https://github.com/sixteenmillimeter/mcopy#readme", "homepage": "https://github.com/sixteenmillimeter/mcopy#readme",
"devDependencies": { "devDependencies": {
"@types/electron": "^1.6.10", "@types/electron": "^1.6.10",
"@types/express": "^4.17.17",
"@types/fs-extra": "^9.0.7", "@types/fs-extra": "^9.0.7",
"@types/jimp": "^0.2.28", "@types/jimp": "^0.2.28",
"@types/node": "^14.14.31", "@types/node": "^14.14.31",
"@types/request": "^2.48.5", "@types/request": "^2.48.5",
"@types/sharp": "^0.27.1", "@types/sharp": "^0.27.1",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/ws": "^8.5.4",
"jsdoc-to-markdown": "^6.0.1", "jsdoc-to-markdown": "^6.0.1",
"typescript": "^4.1.5" "typescript": "^4.1.5"
}, },
@ -47,8 +49,8 @@
"light": "file:app/lib/light", "light": "file:app/lib/light",
"log": "file:app/lib/log", "log": "file:app/lib/log",
"mscript": "file:app/lib/mscript", "mscript": "file:app/lib/mscript",
"proj": "file:app/lib/proj",
"processing": "file:app/lib/processing", "processing": "file:app/lib/processing",
"proj": "file:app/lib/proj",
"sequencer": "file:app/lib/sequencer", "sequencer": "file:app/lib/sequencer",
"settings": "file:app/lib/settings", "settings": "file:app/lib/settings",
"system": "file:app/lib/system" "system": "file:app/lib/system"

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.7", "version": "1.7.18",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

View File

@ -8,7 +8,7 @@ echo "VERSION: $version"
git add ./package.json git add ./package.json
declare -a fileArr=("./package-lock.json" "./data/cfg.json" "./app/package.json" "./app/package-lock.json" "./processing/mcopy/cfg.json") declare -a fileArr=("./package-lock.json" "./data/cfg.json" "./app/package.json" "./app/package-lock.json" "./app/data/cfg.json" "./processing/mcopy/cfg.json")
for i in "${fileArr[@]}" for i in "${fileArr[@]}"
do do

View File

@ -25,7 +25,8 @@ class WebView {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
allowRunningInsecureContent: false, allowRunningInsecureContent: false,
enableRemoteModule: true enableRemoteModule: true,
contextIsolation: false
}, },
width: 800, width: 800,
height: 600, height: 600,
@ -43,6 +44,7 @@ class WebView {
prefs.y = display.y + 50; prefs.y = display.y + 50;
} }
this.digitalWindow = new BrowserWindow(prefs); this.digitalWindow = new BrowserWindow(prefs);
require('@electron/remote/main').enable(this.digitalWindow.webContents)
this.digitalWindow.loadURL(pageUrl); this.digitalWindow.loadURL(pageUrl);
if (process.argv.indexOf('-d') !== -1 || process.argv.indexOf('--dev') !== -1) { if (process.argv.indexOf('-d') !== -1 || process.argv.indexOf('--dev') !== -1) {
this.digitalWindow.webContents.openDevTools(); this.digitalWindow.webContents.openDevTools();

View File

@ -37,6 +37,7 @@ class FilmOut {
private ipc : any; private ipc : any;
private ui : any; private ui : any;
private log : any; private log : any;
private server : any;
/** /**
* @constructor * @constructor
* Builds FilmOut class with display, ffmpeg, ffprobe, ui and light as internal properties. * Builds FilmOut class with display, ffmpeg, ffprobe, ui and light as internal properties.
@ -47,8 +48,9 @@ class FilmOut {
* @param {object} ui Electron ui object * @param {object} ui Electron ui object
* @param {object} light Light device object * @param {object} light Light device object
**/ **/
constructor (display : any, ffmpeg : any, ffprobe : any, ui : any, light : any) { constructor (display : any, server : any, ffmpeg : any, ffprobe : any, ui : any, light : any) {
this.display = display; this.display = display;
this.server = server;
this.ffmpeg = ffmpeg; this.ffmpeg = ffmpeg;
this.ffprobe = ffprobe; this.ffprobe = ffprobe;
this.ui = ui; this.ui = ui;
@ -133,6 +135,11 @@ class FilmOut {
throw err; throw err;
} }
if (this.server.displayImage(path)) {
await delay(20)
return
}
await this.display.show(path); await this.display.show(path);
await delay(20); await delay(20);
} }
@ -430,6 +437,9 @@ class FilmOut {
} }
try { try {
if (await this.server.displayImage(path)) {
return
}
await this.display.open(); await this.display.open();
await this.display.show(path); await this.display.show(path);
} catch (err) { } catch (err) {
@ -442,6 +452,9 @@ class FilmOut {
async focus (evt : any, arg : any) { async focus (evt : any, arg : any) {
this.log.info(`Showing focus screen`); this.log.info(`Showing focus screen`);
try { try {
if (await this.server.cmdAll('focus')) {
return
}
await this.display.open(); await this.display.open();
await this.display.focus(); await this.display.focus();
} catch (err) { } catch (err) {
@ -455,8 +468,12 @@ class FilmOut {
const ratio : number = arg.ratio; const ratio : number = arg.ratio;
this.log.info(`Showing field guide screen`); this.log.info(`Showing field guide screen`);
try { try {
if (await this.server.cmdAll('field', { ratio })) {
return
}
await this.display.open(); await this.display.open();
await this.display.field(ratio); await this.display.field(ratio);
} catch (err) { } catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
} }
@ -467,6 +484,9 @@ class FilmOut {
async meter (evt : any, arg : any) { async meter (evt : any, arg : any) {
this.log.info(`Showing meter screen`); this.log.info(`Showing meter screen`);
try { try {
if (await this.server.cmdAll('meter')) {
return
}
await this.display.open(); await this.display.open();
await this.display.meter(); await this.display.meter();
} catch (err) { } catch (err) {
@ -478,8 +498,12 @@ class FilmOut {
**/ **/
async close (evt : any, arg : any) { async close (evt : any, arg : any) {
try { try {
if (await this.server.cmdAll('blank')) {
return
}
await this.display.hide(); await this.display.hide();
await this.display.close(); await this.display.close();
} catch (err) { } catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
} }
@ -493,6 +517,6 @@ class FilmOut {
} }
} }
module.exports = (display : any, ffmpeg : any, ffprobe : any, ui : any, light : any) => { module.exports = (display : any, server : any, ffmpeg : any, ffprobe : any, ui : any, light : any) => {
return new FilmOut(display, ffmpeg, ffprobe, ui, light); return new FilmOut(display, server, ffmpeg, ffprobe, ui, light);
} }

View File

@ -53,7 +53,7 @@ async function logFile () {
* *
* @returns {object} Logger transport * @returns {object} Logger transport
**/ **/
module.exports = async function (arg : any) { module.exports = async function Log (arg : any) {
let consoleFormat : any = { let consoleFormat : any = {
colorize : true colorize : true
} }

View File

@ -155,7 +155,7 @@ class Projector {
} }
message += ' 1 frame' message += ' 1 frame'
} else if (cmd === this.cfg.arduino.cmd.projectors) { } else if (cmd === this.cfg.arduino.cmd.projectors) {
message += 'Projectors both MOVED 1 frame each'; message += 'Projectors both MOVED 1 frame each'
} }
message += ` ${ms}ms` message += ` ${ms}ms`
this.log.info(message, 'PROJECTOR') this.log.info(message, 'PROJECTOR')
@ -164,5 +164,5 @@ class Projector {
} }
module.exports = function (arduino : Arduino, cfg : any, ui : any, filmout : any, second : boolean) { module.exports = function (arduino : Arduino, cfg : any, ui : any, filmout : any, second : boolean) {
return new Projector(arduino, cfg, ui, filmout, second); return new Projector(arduino, cfg, ui, filmout, second)
} }

View File

@ -168,7 +168,7 @@ class Sequencer {
this.log.info(`Starting sequence...`); this.log.info(`Starting sequence...`);
this.ui.send(this.id, { start : true }); this.ui.send(this.id, { start : true });
if (this.cmd.proj.filmout.state.enabled === true) { if (this.cmd.proj.filmout.state.enabled === true && !this.cmd.proj.filmout.server.useServer()) {
await this.cmd.proj.filmout.display.open(); await this.cmd.proj.filmout.display.open();
} }
@ -209,7 +209,7 @@ class Sequencer {
this.ui.send(this.id, { loop : x, stop : true }); this.ui.send(this.id, { loop : x, stop : true });
} }
if (this.cmd.proj.filmout.state.enabled === true) { if (this.cmd.proj.filmout.state.enabled === true && !this.cmd.proj.filmout.server.useServer()) {
await this.cmd.proj.filmout.display.close(); await this.cmd.proj.filmout.display.close();
} }

244
src/server/index.ts Normal file
View File

@ -0,0 +1,244 @@
import WebSocket, { WebSocketServer } from 'ws'
import express, { Express, Request, Response, Application } from 'express'
import { readFile } from 'fs/promises'
import { basename } from 'path'
import { v4 as uuidv4 } from 'uuid'
import Log = require('log')
import { delay } from 'delay'
interface ServerData {
[key: string]: string;
PORT? : string;
SCRIPT? : string;
}
interface ServerTemplate {
name : string;
path : string;
data? : string;
}
interface ServerProxy {
path : string;
}
interface ServerProxyList {
[key: string]: ServerProxy;
}
interface ServerQueue {
[key: string]: Function;
}
class Server {
private id : string = 'server'
public isActive : boolean = false
private log : any
private templates : ServerTemplate[] = [
{
name :'index',
path : 'server.html'
},
{
name : 'script',
path : 'lib/client/index.js'
}
]
private http : Application
private httpd : Server
private wss : WebSocketServer
private port : number = 9900
private wsPort : number = 9901
private proxy : ServerProxyList = {}
private queue : ServerQueue = {}
private interval : ReturnType<typeof setInterval>
private intervalPeriod : number = 10000 //10 sec
constructor () {
this.init()
}
async init () {
this.log = await Log({ label : this.id });
await this.load()
await this.start()
}
async load () {
this.http = express()
for (let tmpl of this.templates) {
tmpl.data = await readFile(tmpl.path, 'utf8')
}
this.http.get('/', this.index.bind(this))
this.http.get('/image/:key', this.image.bind(this))
this.log.info("Server assets loaded")
}
template (name: string, data : ServerData) {
let html : string = this.templates.find(el => el.name === name).data
for (let key of Object.keys(data)) {
html = html.replace(`{{${key}}}`, data[key])
}
return html
}
async startWss () {
try {
this.wss = new WebSocketServer({ port: this.wsPort })
} catch (err) {
this.log.error(err)
return
}
this.wss.on('connection', async function (ws : WebSocket) {
ws.on("message", function (data : string ) {
let obj : any = JSON.parse(data)
//this.log.info(data)
if (obj.id && this.queue[obj.id]) {
this.queue[obj.id](obj)
delete this.queue[obj.id]
if (obj.action !== 'ping') {
this.log.info(`${obj.action} ACK`)
}
}
}.bind(this))
ws.on('close', function () {
this.log.info('Client disconnected')
}.bind(this))
await this.cmd(ws, 'mcopy')
this.log.info('Client connected')
}.bind(this))
this.log.info(`Websocket server started!`)
this.log.info(`WSS [ ws://localhost:${this.wsPort} ]`)
}
async startHttp () {
return new Promise(function (resolve : Function, reject : Function) {
this.httpd = this.http.listen(this.port, function () {
this.log.info(`HTTP server started!`)
this.log.info(`URL [ http://localhost:${this.port} ]`)
return resolve(true)
}.bind(this))
}.bind(this))
}
async start () {
await this.startHttp()
await this.startWss()
this.interval = setInterval(async function () {
await this.cmdAll('ping');
}.bind(this), this.intervalPeriod)
this.isActive = true
}
async stopHttp() {
return new Promise(function (resolve : Function, reject : Function) {
return this.httpd.close(function () {
this.log.info(`HTTP server stopped :(`)
return resolve(false)
}.bind(this))
}.bind(this))
}
async stop () {
await this.stopHttp()
clearInterval(this.interval)
this.isActive = false
}
index (req : Request, res : Response, next : Function) {
const SCRIPT = this.template('script', { PORT : `${this.wsPort}` })
const html : string = this.template('index', { SCRIPT })
this.log.info('GET /')
return res.send(html)
}
async image (req : Request, res : Response, next : Function) {
let filePath : string
if (req.params && req.params.key) {
if (this.proxy[req.params.key]) {
filePath = this.proxy[req.params.key].path
} else {
return false
}
} else {
return false
}
return new Promise(function (resolve : Function, reject: Function) {
return res.sendFile(filePath, function (err : any) {
if (err) {
res.status(err.status).end()
return reject(err)
}
return resolve(true)
})
}.bind(this))
}
public addProxy (key : string, filePath : string) {
//wipe every time
this.proxy = {}
this.proxy[key] = {
path : filePath
}
this.log.info(`Added proxy image [${key}]`)
}
public async cmdAll (action : string, options : any = {}) {
const cmds : any[] = []
if (this.useServer()) {
this.wss.clients.forEach(function (ws : WebSocket) {
cmds.push(this.cmd(ws, action, options))
}.bind(this))
await Promise.all(cmds)
return true
}
return false
}
public async displayImage (src : string) {
let key
if (this.useServer()) {
key = basename(src)
this.addProxy(key, src)
await this.cmdAll('image', { image : `/image/${key}` })
return true
}
return false
}
public useServer () : boolean {
return this.isActive && this.wss.clients.size > 0
}
/**
* WSS
**/
public async cmd (ws : WebSocket, action : string, options : any = {}) {
const id : string = uuidv4()
let obj = {
id, action
}
let str : string
obj = Object.assign(obj, options)
str = JSON.stringify(obj)
ws.send(str)
return new Promise(function (resolve : Function, reject: Function) {
this.queue[id] = function (obj : any) {
return resolve(obj)
}
//setTimeout() ?
}.bind(this))
}
}
module.exports = function () {
return new Server()
}