filmout_manager/dist/index.js

507 lines
16 KiB
JavaScript
Raw Permalink Normal View History

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv/config");
const express_1 = __importDefault(require("express"));
const promises_1 = __importDefault(require("fs/promises"));
const body_parser_1 = __importDefault(require("body-parser"));
const uuid_1 = require("uuid");
const Handlebars = __importStar(require("handlebars"));
const ws_1 = require("ws");
const log_1 = require("./log");
const env_1 = require("./env");
const files_1 = require("./files");
const testimage_1 = require("./testimage");
const fd_1 = require("./fd");
2024-08-05 02:34:03 +00:00
const display_1 = require("./display");
const ffmpeg_1 = require("./ffmpeg");
const ffprobe_1 = require("./ffprobe");
2024-07-11 21:26:14 +00:00
const camera_1 = require("./camera");
const sequence_1 = require("./sequence");
const image_1 = require("./image");
let mock = false;
const log = (0, log_1.createLog)('fm');
const app = (0, express_1.default)();
let wss;
let fd;
2024-08-05 02:34:03 +00:00
let display;
let ffmpeg;
let ffprobe;
let image;
2024-07-11 21:26:14 +00:00
let camera;
let sequence;
let index;
let focusImage = null;
let framingImage = null;
let port;
let wsPort;
let sequences;
let videos;
let width;
let height;
log.info('Starting filmout_manager...');
app.use(body_parser_1.default.json());
app.use(body_parser_1.default.urlencoded({ extended: true }));
app.use('/static', express_1.default.static('./static'));
async function createTemplate(filePath) {
let tmpl;
try {
tmpl = await promises_1.default.readFile(filePath, 'utf8');
}
catch (err) {
log.error(err);
return null;
}
return Handlebars.compile(tmpl);
}
async function settings() {
let sequencesExists = false;
let videosExists = false;
if ((0, env_1.envString)('FD', null) === null) {
log.error('Please include an FD value containing the path to your fd binary in .env');
process.exit(1);
}
else {
log.info(`FD=${process.env['FD']}`);
}
if ((0, env_1.envString)('FFMPEG', null) === null) {
log.error('Please include an FFMPEG value containing the path to your ffmpeg binary in .env');
process.exit(1);
}
else {
log.info(`FFMPEG=${process.env['FFMPEG']}`);
}
if ((0, env_1.envInt)('WIDTH', null) === null) {
log.error('Please include a WIDTH value containing the width of the screen you are using in .env');
process.exit(2);
}
else {
width = (0, env_1.envInt)('WIDTH', 0);
log.info(`WIDTH=${width}`);
}
if ((0, env_1.envInt)('HEIGHT', null) === null) {
log.error('Please include a HEIGHT value containing the height of the screen you are using in .env');
process.exit(3);
}
else {
height = (0, env_1.envInt)('HEIGHT', 0);
log.info(`HEIGHT=${height}`);
}
if ((0, env_1.envString)('FD_HOST', null) === null) {
log.error('Please include a FD_HOST value with the host that the fd socket server is hosted on in .env');
process.exit(4);
}
else {
log.info(`FD_HOST=${process.env['FD_HOST']}`);
}
if ((0, env_1.envInt)('FD_PORT', null) === null) {
log.error('Please include a FD_PORT value with the port that the fd socket server is hosted on in .env');
process.exit(5);
}
else {
log.info(`FD_PORT=${process.env['FD_PORT']}`);
}
if ((0, env_1.envInt)('PORT', null) === null) {
log.error('Please include a PORT value with the port that the HTTP web process is hosted on in .env');
process.exit(6);
}
else {
port = (0, env_1.envInt)('PORT', 8080);
log.info(`PORT=${port}`);
}
if ((0, env_1.envInt)('WS_PORT', null) === null) {
log.error('Please include a WSPORT value with the port that the WebSocket web process is hosted on in .env');
process.exit(6);
}
else {
wsPort = (0, env_1.envInt)('WS_PORT', 8081);
log.info(`WS_PORT=${wsPort}`);
}
if (wsPort === port) {
log.error(`Websocket port (${wsPort}) should not be the same as HTTP port (${port})`);
process.exit(7);
}
if ((0, env_1.envString)('SEQUENCES', null) === null) {
log.error('Please include a SEQUENCES directory where the image sequences will be located in .env');
process.exit(7);
}
else {
sequences = (0, env_1.envString)('SEQUENCES', null);
2024-10-26 21:11:25 +00:00
sequencesExists = await files_1.Files.init(sequences);
if (!sequencesExists) {
log.error(`The SEQUENCES directory in .env, ${sequences}, does not exist`);
process.exit(8);
}
log.info(`SEQUENCES=${sequences}`);
}
if ((0, env_1.envString)('VIDEOS', null) === null) {
log.error('Please include a VIDEOS directory where the videos will be located in .env');
process.exit(7);
}
else {
videos = (0, env_1.envString)('VIDEOS', null);
videosExists = await files_1.Files.exists(videos);
if (!sequencesExists) {
log.error(`The VIDEOS directory in .env, ${videos}, does not exist`);
process.exit(8);
}
log.info(`VIDEOS=${videos}`);
}
if ((0, env_1.envString)('MOCK', null) !== null) {
if ((0, env_1.envString)('MOCK', '').trim().toLowerCase() === "true" || (0, env_1.envString)('MOCK', '').trim() === '1') {
mock = true;
log.info(`MOCK=true`);
}
else {
mock = false;
}
}
}
function onWssConnection(ws, req) {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
if (ip.substr(0, 7) === "::ffff:")
ip = ip.substr(7);
log.info(`Client ${ip} connected to WebSocket server`);
ws.ip = ip;
ws.session = (0, uuid_1.v4)();
2024-07-13 02:00:24 +00:00
ws.on('message', function (data) { onClientMessage(data, ws); });
sequence.updateClientsOnLoad();
2024-07-13 02:00:24 +00:00
}
async function onClientMessage(data, ws) {
let msg = null;
try {
msg = JSON.parse(data);
}
catch (err) {
log.error('Error parsing message', err);
}
if (msg !== null && typeof msg.cmd !== 'undefined') {
await cmd(msg);
2024-07-13 02:00:24 +00:00
}
}
async function cmd(msg) {
let success = false;
switch (msg.cmd) {
case 'pong':
//received keepalive
break;
2024-07-13 02:00:24 +00:00
case 'open':
await cameraOpen();
break;
case 'close':
await cameraClose();
break;
case 'select':
await select(msg.state.sequence.hash);
break;
case 'start':
await start();
break;
case 'stop':
stop();
break;
case 'advance':
frameAdvance();
break;
case 'rewind':
frameRewind();
break;
case 'set':
frameSet(msg.state.sequence.current);
break;
case 'exposure':
exposureSet(msg.state.exposure);
break;
case 'focus':
await focus();
break;
case 'framing':
await framing();
break;
case 'offset':
offset(msg);
break;
case 'size':
size(msg);
break;
case 'scale':
scale(msg);
break;
2024-07-13 02:00:24 +00:00
default:
log.warn(`No matching command: ${msg.cmd}`);
2024-07-13 02:00:24 +00:00
}
}
async function cameraOpen() {
await camera.open();
send({ cmd: 'open' });
}
async function cameraClose() {
await camera.close();
send({ cmd: 'close' });
}
function frameAdvance() {
focusImage = null;
sequence.frameAdvance();
}
function frameRewind() {
focusImage = null;
sequence.frameRewind();
}
function frameSet(frame) {
focusImage = null;
sequence.frameSet(frame);
}
function exposureSet(exposure) {
if (exposure < 1) {
exposure = 1;
}
sequence.setExposure(exposure);
}
async function select(id) {
2024-10-26 21:11:25 +00:00
const sequencesArr = await files_1.Files.enumerateSequences();
const seq = sequencesArr.find(el => el.hash === id);
if (typeof seq == 'undefined' || seq == null) {
log.error('Sequence not found, maybe deleted?', new Error(`Cannot find sequence ${id}`));
return false;
}
await sequence.load(seq);
return true;
}
async function start() {
if (focusImage !== null) {
await stopFocus();
}
if (framingImage !== null) {
await stopFraming();
}
sequence.start();
}
function stop() {
sequence.stop();
}
async function send(msg) {
const msgStr = JSON.stringify(msg);
wss.clients.forEach((client) => {
client.send(msgStr);
});
}
async function keepAlive() {
await send({ cmd: 'ping' });
}
async function focus() {
let pos;
let dims;
let state;
let filePath;
if (focusImage !== null) {
await stopFocus();
return;
}
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
w: state.display.width,
h: state.display.height,
x: state.offset.x,
y: state.offset.y
};
}
else {
dims = display.getScreen();
pos = {
w: dims.width,
h: dims.height,
x: 0,
y: 0
};
}
focusImage = await testimage_1.TestImage.Focus(pos.w, pos.h);
await fd.load(focusImage, pos.x, pos.y, pos.w, pos.h);
await fd.display(focusImage);
send({ cmd: 'focus' });
}
async function stopFocus() {
focusImage = null;
await fd.stop(focusImage);
send({ cmd: 'unfocus' });
}
async function framing() {
let pos;
let dims;
let state;
let filePath;
if (framingImage !== null) {
await stopFraming();
return;
}
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
w: state.display.width,
h: state.display.height,
x: state.offset.x,
y: state.offset.y
};
}
else {
dims = display.getScreen();
pos = {
w: dims.width,
h: dims.height,
x: 0,
y: 0
};
}
framingImage = await testimage_1.TestImage.Frame(pos.w, pos.h);
await fd.load(framingImage, pos.x, pos.y, pos.w, pos.h);
await fd.display(framingImage);
send({ cmd: 'framing' });
}
async function stopFraming() {
framingImage = null;
await fd.stop(framingImage);
send({ cmd: 'unframing' });
}
function offset(msg) {
let current = sequence.getCurrent();
if (current !== null) {
sequence.updateOffset(msg.x, msg.y);
}
}
function size(msg) {
let current = sequence.getCurrent();
if (current !== null) {
sequence.updateSize(msg.width, msg.height);
}
}
function scale(msg) {
let current = sequence.getCurrent();
if (current !== null) {
sequence.updateScale(msg.scale);
}
}
app.get('/', async (req, res, next) => {
2024-10-26 21:11:25 +00:00
const sequencesArr = await files_1.Files.enumerateSequences();
//const videosArr : VideoObject[] = await Files.enumerateVideos(videos);
const html = index({ sequences: sequencesArr, width, height, wsPort });
res.send(html);
});
app.get('/:width/:height/image.jpg', async (req, res, next) => {
let data;
let current = sequence.getCurrent();
let width = parseInt(req.params.width);
let height = parseInt(req.params.height);
if (focusImage !== null) {
try {
data = await image.thumbnail(focusImage, width, height);
log.info(`Image: ${current.path} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
return next(new Error('Error getting thumbnail'));
}
}
else if (framingImage !== null) {
try {
data = await image.thumbnail(framingImage, width, height);
log.info(`Image: ${current.path} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
return next(new Error('Error getting thumbnail'));
}
}
else if (current !== null) {
try {
data = await image.thumbnail(current.path, width, height);
log.info(`Image: ${current.path} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
return next(new Error('Error getting thumbnail'));
}
}
else {
try {
data = await image.blank(width, height);
log.info(`Blank - ${width},${height}`);
}
catch (err) {
log.error('Error generating blank image', err);
return next(new Error('Error generating blank image'));
}
}
res.contentType('image/jpeg');
res.send(data);
});
async function main() {
await settings();
index = await createTemplate('./views/index.hbs');
ffmpeg = new ffmpeg_1.FFMPEG((0, env_1.envString)('FFMPEG', 'ffmpeg'));
ffprobe = new ffprobe_1.FFPROBE();
image = new image_1.Image();
camera = new camera_1.Camera(mock);
2024-08-05 02:34:03 +00:00
display = new display_1.Display(width, height);
fd = new fd_1.FD((0, env_1.envString)('FD', 'fd'), width, height, (0, env_1.envString)('FD_HOST', 'localhost'), (0, env_1.envInt)('FD_PORT', 8082), (0, env_1.envString)('FD_DISPLAY', null), mock);
app.listen(port, async () => {
log.info(`filmout_manager HTTP server running on port ${port}`);
});
wss = new ws_1.Server({ port: wsPort, clientTracking: true });
wss.on('connection', onWssConnection);
log.info(`filmout_manager WebSocket server running on port ${wsPort}`);
2024-08-05 02:34:03 +00:00
//ffmpeg.listFormats();
//log.info(await TestImage.Focus(640, 480));
sequence = new sequence_1.Sequence(camera, fd, display, ffprobe, send);
setInterval(keepAlive, 30000);
}
main();
process.stdin.resume(); // so the program will not close instantly
async function exitHandler(options, exitCode) {
if (options.cleanup) {
log.info(`Cleaning up...`);
try {
await fd.exit();
}
catch (err) {
log.error('Error cleanly exiting filmout_display (fd) executable', err);
}
}
exitCode == 'SIGINT' ? log.info(`exit: ${exitCode}`) : log.error(`exit: ${exitCode}`, new Error(`Exited with non-zero code: "${exitCode}"`));
if (options.exit)
process.exit();
}
// do something when app is closing
process.on('exit', exitHandler.bind(null, { cleanup: true }));
// catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, { exit: true }));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, { exit: true }));
process.on('SIGUSR2', exitHandler.bind(null, { exit: true }));
// catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, { exit: true }));
//# sourceMappingURL=index.js.map