CLI now supports rgb mode. CLI works mostly. FI needs more features but is working

This commit is contained in:
mattmcw 2025-12-31 23:59:08 -05:00
parent 238b18beb1
commit 1179e80288
28 changed files with 783 additions and 210 deletions

View File

@ -53,6 +53,7 @@ class Display {
this.updateSize();
this.clear();
this.updateScreen();
this.startDisplay();
this.updateDisplay();
}
@ -108,11 +109,31 @@ class Display {
this.updateImage();
}
public updateImage() {
private startDisplay () {
this.displayOffsetX = 0;
this.displayOffsetY = 0;
this.set({
offset : {
x : 0,
y : 0
},
display : {
width : this.screen.width,
height : this.screen.height
}
});
}
public updateImage () {
//console.log(`display.updateImage()`);
//console.trace();
this.img = new Image;
this.img.onload = function () {
//console.log('display.updateImage.loaded()');
this.ctx.drawImage(this.img, this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
}.bind(this);
this.img.src = `/${this.displayWidth}/${this.displayHeight}/image.jpg?cacheBreaker=${+new Date()}`;
}
@ -152,6 +173,7 @@ class Display {
this.updateSize();
this.clear();
this.updateScreen();
this.updateDisplay();
}
private onResize (event : any) {
@ -496,11 +518,13 @@ class Client {
}
private receiveFocus (msg : Message) {
console.log('receive focus');
this.display.setFocus();
this.display.updateImage();
}
private receiveUnfocus (msg : Message) {
console.log('receive unfocus');
this.display.unsetFocus();
this.display.updateImage();
}
@ -512,11 +536,13 @@ class Client {
}
private receiveFraming (msg : Message) {
console.log('receive framing');
this.display.setFraming();
this.display.updateImage();
}
private receiveUnframing (msg : Message) {
console.log('receive unframing');
this.display.unsetFraming();
this.display.updateImage();
}

View File

@ -5,6 +5,7 @@ WS_PORT=8081
WIDTH=2560
HEIGHT=1600
FD=../filmout_display/build/bin/fd
FI=../filmout_image/build/bin/fi
#FD_DISPLAY=:0
FFMPEG=ffmpeg
FD_HOST=localhost

8
dist/cli/index.d.ts vendored
View File

@ -7,7 +7,10 @@ export declare class CLI {
fd: FD;
private camera;
private display;
private bin;
private ffprobe;
private image;
private fdBin;
private fiBin;
private width;
private height;
private host;
@ -17,10 +20,11 @@ export declare class CLI {
private lastImage;
constructor();
private main;
private settings;
private getSettings;
private getArgs;
private parse;
private parseLine;
private execute;
private expose;
private rgb;
}

188
dist/cli/index.js vendored
View File

@ -13,23 +13,30 @@ const display_1 = require("../display");
const files_1 = require("../files");
const delay_1 = require("../delay");
const camera_1 = require("../camera");
const ffprobe_1 = require("../ffprobe");
const image_1 = require("../image");
let cli;
var Actions;
(function (Actions) {
Actions[Actions["EXPOSE"] = 0] = "EXPOSE";
Actions[Actions["DELAY"] = 1] = "DELAY";
Actions[Actions["FORWARD"] = 2] = "FORWARD";
Actions[Actions["BACKWARD"] = 3] = "BACKWARD";
Actions[Actions["GOTO"] = 4] = "GOTO";
Actions[Actions["RGB"] = 1] = "RGB";
Actions[Actions["INVERT"] = 2] = "INVERT";
Actions[Actions["IRGB"] = 3] = "IRGB";
Actions[Actions["DELAY"] = 4] = "DELAY";
Actions[Actions["FORWARD"] = 5] = "FORWARD";
Actions[Actions["BACKWARD"] = 6] = "BACKWARD";
Actions[Actions["GOTO"] = 7] = "GOTO";
})(Actions || (Actions = {}));
class CLI {
constructor() {
this.mock = false;
this.log = (0, log_1.createLog)('fm');
this.settings();
this.args = this.getArgs();
this.ffprobe = new ffprobe_1.FFPROBE();
this.getSettings();
this.getArgs();
this.display = new display_1.Display(this.width, this.height);
this.fd = new fd_1.FD(this.bin, this.width, this.height, this.host, this.port, this.displayStr, this.mock);
this.image = new image_1.Image(this.fiBin);
this.fd = new fd_1.FD(this.fdBin, this.width, this.height, this.host, this.port, this.displayStr, this.mock);
this.camera = new camera_1.Camera(this.mock);
this.main();
}
@ -44,9 +51,10 @@ class CLI {
this.fd.exit();
process.exit(0);
}
settings() {
getSettings() {
//parse args
this.bin = (0, env_1.envString)('FD', 'fd');
this.fdBin = (0, env_1.envString)('FD', 'fd');
this.fiBin = (0, env_1.envString)('FI', 'fi');
this.width = (0, env_1.envInt)('WIDTH', 0);
this.height = (0, env_1.envInt)('HEIGHT', 0);
this.host = (0, env_1.envString)('FD_HOST', 'localhost');
@ -54,28 +62,31 @@ class CLI {
}
getArgs() {
const parser = new argparse_1.ArgumentParser({
description: 'Filmout manager CLI application',
description: 'Filmout manager CLI application'
});
/*
parser.addArgument(['-f', '--file'], {
help: 'Path to a file',
type: String, // Explicitly define type
required: true,
parser.add_argument('-i', '--input', {
help: 'Path to a script to execute',
type: String,
required: false
});
parser.addArgument(['-v', '--verbose'], {
parser.add_argument('-c', '--command', {
help: 'Command to execute',
type: String,
required: false
});
parser.add_argument('-v', '--verbose', {
help: 'Enable verbose output',
action: 'storeTrue', // Boolean flag
action: 'store_true'
});
*/
return parser.parse_args();
this.args = parser.parse_args();
//overwrite defaults
}
async parse(filePath) {
let lines = [];
let file;
let cmd = null;
try {
file = await (0, promises_1.readFile)(file, 'utf8');
file = await (0, promises_1.readFile)(filePath, 'utf8');
}
catch (err) {
this.log.error(`Error reading input file`, err);
@ -83,7 +94,10 @@ class CLI {
}
lines = file.split(os_1.EOL);
for (let line of lines) {
if (line.trim() === '')
continue;
cmd = this.parseLine(line);
this.log.info(cmd);
try {
await this.execute(cmd);
}
@ -102,18 +116,81 @@ class CLI {
this.log.info(`LINE: ${line.trim()}`);
parts = line.split(',');
if (parts.length > 0) {
cmdChar = parts[0].trim().toUpperCase()[0];
cmdChar = parts[0].trim().toUpperCase();
switch (cmdChar) {
case 'E':
cmd.action = Actions.EXPOSE;
cmd.time = 1000;
break;
case 'RGB':
cmd.action = Actions.RGB;
break;
case 'I':
cmd.action = Actions.INVERT;
break;
case 'IRGB':
cmd.action = Actions.IRGB;
break;
case 'D':
cmd.action = Actions.DELAY;
cmd.time = 1000;
break;
case 'F':
cmd.action = Actions.FORWARD;
cmd.count = 1;
break;
case 'B':
cmd.action = Actions.BACKWARD;
cmd.count = 1;
break;
default:
return null;
}
switch (cmd.action) {
case Actions.EXPOSE:
if (parts.length > 1)
cmd.file = parts[1].trim();
if (parts.length > 2)
cmd.time = parseInt(parts[2]);
break;
case Actions.INVERT:
if (parts.length > 1)
cmd.file = parts[1].trim();
if (parts.length > 2)
cmd.time = parseInt(parts[2]);
break;
case Actions.RGB:
if (parts.length > 1)
cmd.file = parts[1].trim();
if (parts.length > 4) {
cmd.times = [
parseInt(parts[2]),
parseInt(parts[3]),
parseInt(parts[4])
];
}
break;
case Actions.IRGB:
if (parts.length > 1)
cmd.file = parts[1].trim();
if (parts.length > 4) {
cmd.times = [
parseInt(parts[2]),
parseInt(parts[3]),
parseInt(parts[4])
];
}
break;
case Actions.FORWARD:
if (parts.length > 1)
cmd.count = parseInt(parts[1]);
break;
case Actions.BACKWARD:
if (parts.length > 1)
cmd.count = parseInt(parts[1]);
break;
}
return cmd;
}
return null;
}
@ -127,38 +204,65 @@ class CLI {
case Actions.EXPOSE:
await this.expose(cmd);
break;
case Actions.INVERT:
await this.expose(cmd, true);
break;
case Actions.RGB:
await this.rgb(cmd);
break;
case Actions.IRGB:
await this.rgb(cmd, true);
break;
default:
break;
}
//await this.load(img, 100, 200, 640, 640);
//await delay(2000);
//await this.display(img, [ 4000 ] );
//await delay(8000);
}
async expose(cmd) {
async expose(cmd, invert = false) {
const start = Date.now();
let load;
let open;
let exposureElapsed;
let exposureReported;
let close;
let total;
let result;
const stats = await (0, promises_1.lstat)(cmd.file);
const img = await files_1.Files.getImageObject(cmd.file, stats);
const dimensions = this.display.getOutgoingPosition();
this.log.info(`Frame: ${img.name}`);
const info = await this.ffprobe.info(cmd.file);
let dimensions;
//console.dir(img);
//console.dir(info);
this.display.setSource(info.width, info.height);
dimensions = this.display.getOutgoingPosition();
this.log.info(`Expose: ${img.name}`);
await this.fd.load(img.path, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
load = Date.now() - start;
await this.camera.open();
open = Date.now() - start - load;
result = await this.fd.display(img.path, [cmd.time]);
exposureReported = result.reported;
exposureElapsed = Date.now() - start - load - open;
await this.camera.close();
close = Date.now() - start - load - open - exposureElapsed;
total = Date.now() - start;
//this.stats.add(load, open, exposureElapsed, exposureReported, close, total);
}
async rgb(cmd, invert = false) {
const start = Date.now();
let result;
let channels;
const stats = await (0, promises_1.lstat)(cmd.file);
const img = await files_1.Files.getImageObject(cmd.file, stats);
const info = await this.ffprobe.info(cmd.file);
let dimensions;
this.display.setSource(info.width, info.height);
dimensions = this.display.getOutgoingPosition();
this.log.info(`RGB: ${img.name}`);
channels = await this.image.rgb(img.path);
//console.dir(channels)
await this.fd.load(channels.red, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
await this.camera.open();
result = await this.fd.display(channels.red, [cmd.times[0]]);
await this.fd.load(channels.green, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
result = await this.fd.display(channels.green, [cmd.times[1]]);
await this.fd.load(channels.blue, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
result = await this.fd.display(channels.blue, [cmd.times[2]]);
try {
await (0, promises_1.unlink)(channels.red);
await (0, promises_1.unlink)(channels.green);
await (0, promises_1.unlink)(channels.blue);
}
catch (err) {
this.log.error(`Error cleaning up temp channel files`, err);
}
await this.camera.close();
}
}
exports.CLI = CLI;

File diff suppressed because one or more lines are too long

15
dist/exec/index.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
interface ExecOutput {
code: number;
stdout: string;
stderr: string;
}
export declare class Exec {
private _process;
private _bin;
private _args;
private _options;
private _log;
constructor(cmdInput: string[], cwd?: string, log?: Function);
exec(): Promise<ExecOutput>;
}
export type { ExecOutput };

49
dist/exec/index.js vendored Normal file
View File

@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Exec = void 0;
const child_process_1 = require("child_process");
class Exec {
constructor(cmdInput, cwd = null, log = null) {
this._options = {};
this._log = null;
this._bin = cmdInput[0];
if (cmdInput.length > 1) {
cmdInput.shift();
this._args = cmdInput;
}
else {
this._args = [];
}
if (cwd !== null) {
this._options.cwd = cwd;
}
if (log !== null) {
this._log = log;
}
}
async exec() {
return new Promise((resolve, reject) => {
let stdout = '';
let stderr = '';
this._process = (0, child_process_1.spawn)(this._bin, this._args, this._options);
this._process.stdout.on('data', (data) => {
stdout += data.toString();
if (this._log !== null) {
this._log(data.toString(), null);
}
});
this._process.stderr.on('data', (data) => {
stderr += data.toString();
if (this._log !== null) {
this._log(null, data.toString());
}
});
this._process.on('exit', (code) => {
return resolve({ code, stdout, stderr });
});
});
}
}
exports.Exec = Exec;
module.exports = { Exec };
//# sourceMappingURL=index.js.map

1
dist/exec/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exec/index.ts"],"names":[],"mappings":";;;AAAA,iDAAoD;AASpD,MAAa,IAAI;IAOhB,YAAa,QAAmB,EAAE,MAAe,IAAI,EAAE,MAAiB,IAAI;QAHpE,aAAQ,GAAkB,EAAE,CAAC;QAC7B,SAAI,GAAc,IAAI,CAAC;QAG9B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;QACvB,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC;QACzB,CAAC;QACD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QACjB,CAAC;IACF,CAAC;IAED,KAAK,CAAC,IAAI;QACT,OAAO,IAAI,OAAO,CAAE,CAAC,OAAkB,EAAE,MAAiB,EAAE,EAAE;YAC7D,IAAI,MAAM,GAAY,EAAE,CAAC;YACzB,IAAI,MAAM,GAAY,EAAE,CAAC;YAEzB,IAAI,CAAC,QAAQ,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,EAAE;gBACjD,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAC;gBAClC,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,EAAE;gBACjD,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAClC,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,EAAE;gBACzC,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YAC1C,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAA;IACH,CAAC;CACD;AAhDD,oBAgDC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,CAAC"}

7
dist/fd/index.d.ts vendored
View File

@ -5,12 +5,7 @@ export declare enum Action {
STOP = 3
}
export declare enum Mode {
RGB = 0,
BW = 1,
INVERT = 2,
BW_INVERT = 3,
RGB_CHANNELS = 4,
INVERT_CHANNELS = 5
RGB = 0
}
interface fdOutgoingPosition {
x: number;

5
dist/fd/index.js vendored
View File

@ -18,11 +18,6 @@ var Action;
var Mode;
(function (Mode) {
Mode[Mode["RGB"] = 0] = "RGB";
Mode[Mode["BW"] = 1] = "BW";
Mode[Mode["INVERT"] = 2] = "INVERT";
Mode[Mode["BW_INVERT"] = 3] = "BW_INVERT";
Mode[Mode["RGB_CHANNELS"] = 4] = "RGB_CHANNELS";
Mode[Mode["INVERT_CHANNELS"] = 5] = "INVERT_CHANNELS";
})(Mode || (exports.Mode = Mode = {}));
class FD {
constructor(bin, width, height, host, port, display = null, mock = false) {

File diff suppressed because one or more lines are too long

10
dist/fi/index.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
export declare enum fiMode {
RGB = 0,
IRGB = 1
}
export declare class FilmoutImage {
private log;
private bin;
constructor(bin: string);
rgb(image: string, red: string, green: string, blue: string): Promise<void>;
}

37
dist/fi/index.js vendored Normal file
View File

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FilmoutImage = exports.fiMode = void 0;
const log_1 = require("../log");
const shell_1 = require("../shell");
var fiMode;
(function (fiMode) {
fiMode[fiMode["RGB"] = 0] = "RGB";
fiMode[fiMode["IRGB"] = 1] = "IRGB";
})(fiMode || (exports.fiMode = fiMode = {}));
class FilmoutImage {
constructor(bin) {
this.log = (0, log_1.createLog)('fi');
this.bin = bin;
}
async rgb(image, red, green, blue) {
const cmd = [
this.bin,
image,
'-m', fiMode.RGB,
'-r', red,
'-g', green,
'-b', blue
];
this.log.info(`Executing: ${cmd.join(' ')}`);
const shell = new shell_1.Shell(cmd, null, function (stdio) { console.log(stdio); }, null, null, true);
try {
await shell.execute();
}
catch (err) {
this.log.error(`Error executing ${cmd.join(' ')}`, err);
}
}
}
exports.FilmoutImage = FilmoutImage;
module.exports = { FilmoutImage };
//# sourceMappingURL=index.js.map

1
dist/fi/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fi/index.ts"],"names":[],"mappings":";;;AACA,gCAAkC;AAClC,oCAAiC;AAEjC,IAAY,MAGX;AAHD,WAAY,MAAM;IACjB,iCAAO,CAAA;IACP,mCAAQ,CAAA;AACT,CAAC,EAHW,MAAM,sBAAN,MAAM,QAGjB;AAED,MAAa,YAAY;IAIxB,YAAa,GAAY;QACxB,IAAI,CAAC,GAAG,GAAG,IAAA,eAAS,EAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC;IAEM,KAAK,CAAC,GAAG,CAAE,KAAc,EAAE,GAAY,EAAE,KAAc,EAAE,IAAa;QAC5E,MAAM,GAAG,GAAW;YACnB,IAAI,CAAC,GAAG;YACR,KAAK;YACL,IAAI,EAAE,MAAM,CAAC,GAAG;YAChB,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,IAAI;SACV,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAW,IAAI,aAAK,CAAC,GAAG,EAAE,IAAI,EAAE,UAAS,KAAc,IAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAE9G,IAAI,CAAC;YACJ,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,mBAAmB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC;IACF,CAAC;CACD;AA3BD,oCA2BC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,YAAY,EAAE,CAAC"}

10
dist/image/index.d.ts vendored
View File

@ -3,12 +3,16 @@ export declare class Image {
private prefix;
private thumbnailCache;
private thumbnailHash;
private blankCache;
private blankHash;
private colorCache;
private colorHash;
private tmp;
constructor();
private fi;
constructor(fiBin: string);
private mktemp;
private dpx2png;
thumbnail(path: string, width: number, height: number): Promise<Buffer>;
private color;
blank(width: number, height: number): Promise<Buffer>;
gray(width: number, height: number): Promise<Buffer>;
rgb(image: string): Promise<any>;
}

40
dist/image/index.js vendored
View File

@ -10,17 +10,19 @@ const path_1 = require("path");
const os_1 = require("os");
const promises_1 = require("fs/promises");
const shell_1 = require("../shell");
const fi_1 = require("../fi");
class Image {
constructor() {
constructor(fiBin) {
this.prefix = 'fm_thumbs';
this.thumbnailCache = null;
this.thumbnailHash = null;
this.blankCache = null;
this.blankHash = null;
this.colorCache = null;
this.colorHash = null;
this.tmp = null;
if (this.tmp === null) {
this.tmp = (0, os_1.tmpdir)();
}
this.fi = new fi_1.FilmoutImage(fiBin);
}
async mktemp(ext = '.png') {
const randomString = Math.random().toString(36).slice(2);
@ -82,21 +84,39 @@ class Image {
}
return this.thumbnailCache;
}
async blank(width, height) {
const hash = hash_1.Hashes.stringHash(`${width},${height}`);
if (hash !== this.blankHash) {
async color(width, height, r, g, b) {
const hash = hash_1.Hashes.stringHash(`${width},${height},${r},${g},${b}`);
if (hash !== this.colorHash) {
const options = {
create: {
width,
height,
channels: 3,
background: { r: 125, g: 125, b: 125 }
background: { r, g, b }
}
};
this.blankCache = await (0, sharp_1.default)(options).jpeg().toBuffer();
this.blankHash = hash;
this.colorCache = await (0, sharp_1.default)(options).jpeg().toBuffer();
this.colorHash = hash;
}
return this.blankCache;
return this.colorCache;
}
async blank(width, height) {
return this.color(width, height, 0, 0, 0);
}
async gray(width, height) {
return this.color(width, height, 125, 125, 125);
}
async rgb(image) {
const red = await this.mktemp('tif');
const green = await this.mktemp('tif');
const blue = await this.mktemp('tif');
try {
await this.fi.rgb(image, red, green, blue);
}
catch (err) {
throw err;
}
return { red, green, blue };
}
}
exports.Image = Image;

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAE1B,kCAAiC;AACjC,+BAAqC;AACrC,2BAA4B;AAC5B,0CAA4C;AAC5C,oCAAiC;AACjC,8BAAqC;AAGrC,MAAa,KAAK;IASjB,YAAa,KAAc;QARnB,WAAM,GAAY,WAAW,CAAC;QAC9B,mBAAc,GAAY,IAAI,CAAC;QAC/B,kBAAa,GAAY,IAAI,CAAC;QAC9B,eAAU,GAAY,IAAI,CAAC;QAC3B,cAAS,GAAY,IAAI,CAAC;QAC1B,QAAG,GAAY,IAAI,CAAC;QAI3B,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,GAAG,GAAG,IAAA,WAAM,GAAE,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,iBAAY,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,MAAM,CAAE,MAAe,MAAM;QAC1C,MAAM,YAAY,GAAY,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAY,IAAA,WAAI,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;QAEhF,IAAI,CAAC;YACJ,MAAM,IAAA,gBAAK,EAAC,IAAA,WAAI,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,EAAE;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAE,KAAc,EAAE,MAAe;QACrD,IAAI,GAAG,GAAS,IAAI,CAAC;QACrB,MAAM,IAAI,GAAc;YACvB,SAAS;YACT,KAAK;YACL,aAAa,EAAG,KAAK;YACrB,QAAQ,EAAE,GAAG;YACb,MAAM;SACN,CAAC;QACF,MAAM,KAAK,GAAW,IAAI,aAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpE,IAAI,CAAC;YACJ,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,EAAE;QACH,CAAC;IACF,CAAC;IAEM,KAAK,CAAC,SAAS,CAAE,IAAa,EAAE,KAAc,EAAE,MAAc;QACpE,MAAM,IAAI,GAAY,aAAM,CAAC,UAAU,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;QACtE,IAAI,OAAO,GAAY,IAAI,CAAC;QAC5B,IAAI,IAAI,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;YACjC,IAAI,IAAA,cAAO,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;gBAC5C,IAAI,CAAC;oBACJ,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACnC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBAClC,IAAI,GAAG,OAAO,CAAC;gBAChB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,EAAE;gBACH,CAAC;YACF,CAAC;YACD,MAAM,OAAO,GAAmB;gBAC/B,KAAK;gBACL,MAAM;gBACN,GAAG,EAAE,eAAK,CAAC,GAAG,CAAC,IAAI;aACnB,CAAA;YACD,IAAI,CAAC,cAAc,GAAG,MAAM,IAAA,eAAK,EAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC1E,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACJ,MAAM,IAAA,iBAAM,EAAC,OAAO,CAAC,CAAC;gBACvB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,EAAE;gBACH,CAAC;YACF,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,KAAK,CAAE,KAAc,EAAE,MAAe,EAAE,CAAU,EAAE,CAAU,EAAE,CAAU;QACvF,MAAM,IAAI,GAAY,aAAM,CAAC,UAAU,CAAC,GAAG,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,IAAI,IAAI,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAkB;gBAC9B,MAAM,EAAE;oBACP,KAAK;oBACL,MAAM;oBACN,QAAQ,EAAE,CAAC;oBACX,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;iBACvB;aACD,CAAC;YACF,IAAI,CAAC,UAAU,GAAG,MAAM,IAAA,eAAK,EAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;YACzD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAEM,KAAK,CAAC,KAAK,CAAE,KAAc,EAAE,MAAe;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;IAEM,KAAK,CAAC,IAAI,CAAE,KAAc,EAAE,MAAe;QACjD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IAEM,KAAK,CAAC,GAAG,CAAE,KAAc;QAC/B,MAAM,GAAG,GAAY,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAY,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,IAAI,GAAY,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAE/C,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;QACX,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;CACD;AAlHD,sBAkHC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,CAAC"}

103
dist/index.js vendored
View File

@ -58,6 +58,8 @@ let sequence;
let index;
let focusImage = null;
let framingImage = null;
let blankImage = null;
let grayImage = null;
let port;
let wsPort;
let sequences;
@ -113,6 +115,13 @@ async function settings() {
else {
log.info(`FD_PORT=${process.env['FD_PORT']}`);
}
if ((0, env_1.envString)('FI', null) === null) {
log.error('Please include an FI value containing the path to your fi binary in .env');
process.exit(1);
}
else {
log.info(`FI=${process.env['FI']}`);
}
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);
@ -229,13 +238,13 @@ async function cmd(msg) {
stop();
break;
case 'advance':
frameAdvance();
await frameAdvance();
break;
case 'rewind':
frameRewind();
await frameRewind();
break;
case 'set':
frameSet(msg.state.sequence.current);
await frameSet(msg.state.sequence.current);
break;
case 'exposure':
exposureSet(msg.state.exposure);
@ -270,16 +279,16 @@ async function cameraClose() {
await camera.close();
send({ cmd: 'close' });
}
function frameAdvance() {
focusImage = null;
async function frameAdvance() {
await reset('advance');
sequence.frameAdvance();
}
function frameRewind() {
focusImage = null;
async function frameRewind() {
await reset('rewind');
sequence.frameRewind();
}
function frameSet(frame) {
focusImage = null;
async function frameSet(frame) {
await reset('set');
sequence.frameSet(frame);
}
function exposureSet(exposure) {
@ -298,25 +307,30 @@ async function select(id) {
await sequence.load(seq);
return true;
}
async function start() {
if (focusImage !== null) {
async function reset(target = 'none') {
if (target !== 'focus' && focusImage !== null) {
await stopFocus();
}
if (framingImage !== null) {
if (target !== 'framing' && framingImage !== null) {
await stopFraming();
}
if (target !== 'blank' && blank !== null) {
blankImage = null;
}
if (target !== 'gray' && blank !== null) {
grayImage = null;
}
}
async function start() {
await reset();
sequence.start();
}
function stop() {
sequence.stop();
}
async function blank() {
if (focusImage !== null) {
await stopFocus();
}
if (framingImage !== null) {
await stopFraming();
}
await reset('blank');
blankImage = "SET";
await send({ cmd: 'blank' });
}
async function send(msg) {
@ -333,10 +347,7 @@ async function focus() {
let dims;
let state;
let filePath;
if (focusImage !== null) {
await stopFocus();
return;
}
await reset('focus');
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
@ -371,10 +382,7 @@ async function framing() {
let dims;
let state;
let filePath;
if (framingImage !== null) {
await stopFraming();
return;
}
await reset('framing');
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
@ -433,37 +441,28 @@ app.get('/:width/:height/image.jpg', async (req, res, next) => {
let current = sequence.getCurrent();
let width = parseInt(req.params.width);
let height = parseInt(req.params.height);
//log.info(`${width}x${height}`)
if (focusImage !== null) {
try {
data = await image.thumbnail(focusImage, width, height);
log.info(`Image: ${current.path} - ${width},${height}`);
log.info(`Focus Image: ${focusImage} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
log.error(`Error getting thumbnail of focusImage`, 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}`);
log.info(`Framing Image: ${framingImage} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
log.error(`Error getting thumbnail of framingImage`, 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 {
else if (blankImage !== null) {
try {
data = await image.blank(width, height);
log.info(`Blank - ${width},${height}`);
@ -473,6 +472,26 @@ app.get('/:width/:height/image.jpg', async (req, res, next) => {
return next(new Error('Error generating blank image'));
}
}
else if (grayImage !== null) {
try {
data = await image.gray(width, height);
log.info(`Gray - ${width},${height}`);
}
catch (err) {
log.error('Error generating gray image', err);
return next(new Error('Error generating gray image'));
}
}
else if (current !== null) {
try {
data = await image.thumbnail(current.path, width, height);
log.info(`Frame Image: ${current.path} - ${width},${height}`);
}
catch (err) {
log.error(`Error getting thumbnail of frame ${current}`, err);
return next(new Error('Error getting thumbnail'));
}
}
res.contentType('image/jpeg');
res.send(data);
});
@ -481,7 +500,7 @@ async function main() {
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();
image = new image_1.Image((0, env_1.envString)('FI', 'fi'));
camera = new camera_1.Camera(mock);
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);

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

50
src/cli/README.md Normal file
View File

@ -0,0 +1,50 @@
# filmout_manager CLI
## Sytax
```
<Command>,<Image>,<Time(s)>
```
## Commands
* E - Exposure normal
* RGB - Per channel RGB exposure (for balancing)
* I - Inverted exposure
* IRGB - Per channel inverted RGB exposure
## Examples
Exposure normal
```
E,images/test.jpg,1000
```
Will open the camera and create a single 1000ms (1 second) exposure and close the camera.
RGB
```
RGB,images/test.jpg,100,300,500
```
Will open the camera, expose the red channel for 100ms, green for 300ms and blue for 500ms.
Inverted
```
I,images/test.jpg,500
```
Will open the camera and create a single 500ms exposure and close the camera.
Inverted RGB
```
IRGB,images/test.jpg,500,300,100
```
TODO: Work on this feature

View File

@ -1,5 +1,5 @@
import 'dotenv/config';
import { readFile, lstat} from 'fs/promises';
import { readFile, lstat, unlink } from 'fs/promises';
import type { Stats } from 'fs';
import { tmpdir, EOL } from 'os';
import { join, basename } from 'path';
@ -16,11 +16,17 @@ import type { ImageObject } from '../files';
import { delay } from '../delay';
import { Camera } from '../camera';
import { TestImage } from '../testimage';
import { FFPROBE } from '../ffprobe';
import type { VideoInfo } from '../ffprobe';
import { Image } from '../image';
let cli : CLI;
enum Actions {
EXPOSE,
RGB,
INVERT,
IRGB,
DELAY,
FORWARD,
BACKWARD,
@ -37,7 +43,9 @@ interface Args {
interface CMD {
action : Actions;
file : string;
count?: number;
time?: number;
times? : number[];
}
export class CLI {
@ -45,9 +53,12 @@ export class CLI {
private args : Args;
public fd : FD;
private camera : Camera;
private display: Display;
private display : Display;
private ffprobe : FFPROBE;
private image : Image;
private bin : string;
private fdBin : string;
private fiBin : string;
private width : number;
private height : number;
private host : string;
@ -59,29 +70,34 @@ export class CLI {
constructor () {
this.log = createLog('fm');
this.settings();
this.args = this.getArgs();
this.ffprobe = new FFPROBE();
this.getSettings();
this.getArgs();
this.display = new Display(this.width, this.height);
this.fd = new FD(this.bin, this.width, this.height, this.host, this.port, this.displayStr, this.mock);
this.image = new Image(this.fiBin);
this.fd = new FD(this.fdBin, this.width, this.height, this.host, this.port, this.displayStr, this.mock);
this.camera = new Camera(this.mock);
this.main();
}
private async main () {
if (typeof this.args.input !== 'undefined') {
this.log.info(`Loading ${basename(this.args.input)}`);
await delay(2000);
await this.parse(this.args.input);
await delay(2000)
await delay(2000);
}
await delay(5000);
this.fd.exit();
process.exit(0);
}
private settings () {
private getSettings () {
//parse args
this.bin = envString('FD', 'fd')
this.fdBin = envString('FD', 'fd');
this.fiBin = envString('FI', 'fi');
this.width = envInt('WIDTH', 0);
this.height = envInt('HEIGHT', 0);
this.host = envString('FD_HOST', 'localhost');
@ -91,21 +107,29 @@ export class CLI {
private getArgs () {
const parser = new ArgumentParser({
description: 'Filmout manager CLI application',
description: 'Filmout manager CLI application'
});
/*
parser.addArgument(['-f', '--file'], {
help: 'Path to a file',
type: String, // Explicitly define type
required: true,
parser.add_argument('-i', '--input', {
help: 'Path to a script to execute',
type: String,
required: false
});
parser.addArgument(['-v', '--verbose'], {
help: 'Enable verbose output',
action: 'storeTrue', // Boolean flag
parser.add_argument('-c', '--command', {
help: 'Command to execute',
type: String,
required: false
});
*/
return parser.parse_args();
parser.add_argument('-v', '--verbose', {
help: 'Enable verbose output',
action: 'store_true'
});
this.args = parser.parse_args();
//overwrite defaults
}
private async parse (filePath : string) {
@ -114,7 +138,7 @@ export class CLI {
let cmd : CMD = null;
try {
file = await readFile(file, 'utf8');
file = await readFile(filePath, 'utf8');
} catch (err) {
this.log.error(`Error reading input file`, err);
process.exit(1);
@ -123,7 +147,9 @@ export class CLI {
lines = file.split(EOL);
for (let line of lines) {
if (line.trim() === '') continue;
cmd = this.parseLine(line);
this.log.info(cmd);
try {
await this.execute(cmd);
} catch (err) {
@ -146,18 +172,76 @@ export class CLI {
parts = line.split(',');
if (parts.length > 0) {
cmdChar = parts[0].trim().toUpperCase()[0];
cmdChar = parts[0].trim().toUpperCase();
switch (cmdChar) {
case 'E' :
cmd.action = Actions.EXPOSE;
cmd.time = 1000;
break;
case 'RGB' :
cmd.action = Actions.RGB;
break;
case 'I' :
cmd.action = Actions.INVERT;
break;
case 'IRGB' :
cmd.action = Actions.IRGB;
break;
case 'D':
cmd.action = Actions.DELAY;
cmd.time = 1000;
break;
case 'F' :
cmd.action = Actions.FORWARD;
cmd.count = 1;
break;
case 'B' :
cmd.action = Actions.BACKWARD;
cmd.count = 1;
break;
default :
return null;
}
switch(cmd.action) {
case Actions.EXPOSE :
if (parts.length > 1) cmd.file = parts[1].trim();
if (parts.length > 2) cmd.time = parseInt(parts[2]);
break;
case Actions.INVERT :
if (parts.length > 1) cmd.file = parts[1].trim();
if (parts.length > 2) cmd.time = parseInt(parts[2]);
break;
case Actions.RGB :
if (parts.length > 1) cmd.file = parts[1].trim();
if (parts.length > 4) {
cmd.times = [
parseInt(parts[2]),
parseInt(parts[3]),
parseInt(parts[4])
];
}
break;
case Actions.IRGB :
if (parts.length > 1) cmd.file = parts[1].trim();
if (parts.length > 4) {
cmd.times = [
parseInt(parts[2]),
parseInt(parts[3]),
parseInt(parts[4])
];
}
break;
case Actions.FORWARD :
if (parts.length > 1) cmd.count = parseInt(parts[1]);
break;
case Actions.BACKWARD :
if (parts.length > 1) cmd.count = parseInt(parts[1]);
break;
}
return cmd;
}
return null;
}
@ -171,41 +255,84 @@ export class CLI {
case Actions.EXPOSE :
await this.expose(cmd);
break;
case Actions.INVERT :
await this.expose(cmd, true);
break;
case Actions.RGB :
await this.rgb(cmd);
break;
case Actions.IRGB :
await this.rgb(cmd, true);
break;
default :
break;
}
//await this.load(img, 100, 200, 640, 640);
//await delay(2000);
//await this.display(img, [ 4000 ] );
//await delay(8000);
}
private async expose (cmd : CMD) {
private async expose (cmd : CMD, invert : boolean = false) {
const start : number = Date.now();
let load : number;
let open : number;
let exposureElapsed : number;
let exposureReported : number;
let close : number;
let total : number;
let result : fdResult;
const stats : Stats = await lstat(cmd.file);
const img : ImageObject = await Files.getImageObject(cmd.file, stats);
const dimensions : fdOutgoingPosition = this.display.getOutgoingPosition();
this.log.info(`Frame: ${img.name}`);
const info : VideoInfo = await this.ffprobe.info(cmd.file);
let dimensions : fdOutgoingPosition;
//console.dir(img);
//console.dir(info);
this.display.setSource(info.width, info.height);
dimensions = this.display.getOutgoingPosition();
this.log.info(`Expose: ${img.name}`);
await this.fd.load(img.path, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
load = Date.now() - start;
await this.camera.open();
open = Date.now() - start - load;
result = await this.fd.display(img.path, [ cmd.time ] );
exposureReported = result.reported;
exposureElapsed = Date.now() - start - load - open;
await this.camera.close();
}
private async rgb (cmd : CMD, invert : boolean = false) {
const start : number = Date.now();
let result : fdResult;
let channels : any;
const stats : Stats = await lstat(cmd.file);
const img : ImageObject = await Files.getImageObject(cmd.file, stats);
const info : VideoInfo = await this.ffprobe.info(cmd.file);
let dimensions : fdOutgoingPosition;
this.display.setSource(info.width, info.height);
dimensions = this.display.getOutgoingPosition();
this.log.info(`RGB: ${img.name}`);
channels = await this.image.rgb(img.path);
await this.fd.load(channels.red, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
await this.camera.open();
result = await this.fd.display(channels.red, [ cmd.times[0] ] );
await this.fd.load(channels.green, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
result = await this.fd.display(channels.green, [ cmd.times[1] ] );
await this.fd.load(channels.blue, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
result = await this.fd.display(channels.blue, [ cmd.times[2] ] );
try {
await unlink(channels.red);
await unlink(channels.green);
await unlink(channels.blue);
} catch (err) {
this.log.error(`Error cleaning up temp channel files`, err);
}
await this.camera.close();
close = Date.now() - start - load - open - exposureElapsed;
total = Date.now() - start;
//this.stats.add(load, open, exposureElapsed, exposureReported, close, total);
}
}

39
src/fi/index.ts Normal file
View File

@ -0,0 +1,39 @@
import type { Logger } from 'winston';
import { createLog } from '../log'
import { Shell } from '../shell';
export enum fiMode {
RGB = 0,
IRGB = 1
}
export class FilmoutImage {
private log : Logger;
private bin : string;
constructor (bin : string) {
this.log = createLog('fi');
this.bin = bin;
}
public async rgb (image : string, red : string, green : string, blue : string) {
const cmd : any[] = [
this.bin,
image,
'-m', fiMode.RGB,
'-r', red,
'-g', green,
'-b', blue
];
this.log.info(`Executing: ${cmd.join(' ')}`);
const shell : Shell = new Shell(cmd, null, function(stdio : string){ console.log(stdio); }, null, null, true);
try {
await shell.execute();
} catch (err) {
this.log.error(`Error executing ${cmd.join(' ')}`, err);
}
}
}
module.exports = { FilmoutImage };

View File

@ -5,19 +5,23 @@ import { join, extname } from 'path';
import { tmpdir } from 'os';
import { mkdir, unlink } from 'fs/promises';
import { Shell } from '../shell';
import { FilmoutImage } from '../fi';
import type { fiMode } from '../fi';
export class Image {
private prefix : string = 'fm_thumbs';
private thumbnailCache : Buffer = null;
private thumbnailHash : string = null;
private blankCache : Buffer = null;
private blankHash : string = null;
private colorCache : Buffer = null;
private colorHash : string = null;
private tmp : string = null;
private fi : FilmoutImage;
constructor () {
constructor (fiBin : string) {
if (this.tmp === null) {
this.tmp = tmpdir();
}
this.fi = new FilmoutImage(fiBin);
}
private async mktemp (ext : string = '.png') : Promise<string> {
@ -80,23 +84,44 @@ export class Image {
return this.thumbnailCache;
}
public async blank (width : number, height : number) {
const hash : string = Hashes.stringHash(`${width},${height}`);
if (hash !== this.blankHash) {
private async color (width : number, height : number, r : number, g : number, b : number) {
const hash : string = Hashes.stringHash(`${width},${height},${r},${g},${b}`);
if (hash !== this.colorHash) {
const options : SharpOptions = {
create: {
width,
height,
channels: 3,
background: { r: 125, g: 125, b: 125 }
background: { r, g, b }
}
};
this.blankCache = await sharp(options).jpeg().toBuffer();
this.blankHash = hash;
this.colorCache = await sharp(options).jpeg().toBuffer();
this.colorHash = hash;
}
return this.blankCache;
return this.colorCache;
}
public async blank (width : number, height : number) {
return this.color(width, height, 0, 0, 0);
}
public async gray (width : number, height : number) {
return this.color(width, height, 125, 125, 125);
}
public async rgb (image : string) : Promise<any> {
const red : string = await this.mktemp('tif');
const green : string = await this.mktemp('tif');
const blue : string = await this.mktemp('tif');
try {
await this.fi.rgb(image, red, green, blue);
} catch (err) {
throw err;
}
return { red, green, blue };
}
}
module.exports = { Image };

View File

@ -44,6 +44,8 @@ let sequence : Sequence;
let index : HandlebarsTemplateDelegate<any>;
let focusImage : string = null;
let framingImage : string = null;
let blankImage : string = null;
let grayImage : string = null;
let port : number;
let wsPort : number;
@ -105,6 +107,12 @@ async function settings () {
} else {
log.info(`FD_PORT=${process.env['FD_PORT']}`);
}
if (envString('FI', null) === null) {
log.error('Please include an FI value containing the path to your fi binary in .env');
process.exit(1);
} else {
log.info(`FI=${process.env['FI']}`);
}
if (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);
@ -217,13 +225,13 @@ async function cmd (msg : Message) {
stop();
break;
case 'advance' :
frameAdvance();
await frameAdvance();
break;
case 'rewind' :
frameRewind();
await frameRewind();
break;
case 'set' :
frameSet(msg.state.sequence.current);
await frameSet(msg.state.sequence.current);
break;
case 'exposure' :
exposureSet(msg.state.exposure);
@ -261,18 +269,18 @@ async function cameraClose () {
send({ cmd : 'close' });
}
function frameAdvance () {
focusImage = null;
async function frameAdvance () {
await reset('advance');
sequence.frameAdvance();
}
function frameRewind () {
focusImage = null;
async function frameRewind () {
await reset('rewind');
sequence.frameRewind();
}
function frameSet (frame : number) {
focusImage = null;
async function frameSet (frame : number) {
await reset('set');
sequence.frameSet(frame);
}
@ -294,13 +302,26 @@ async function select (id : string) : Promise<boolean> {
return true;
}
async function start () {
if (focusImage !== null) {
async function reset (target : string = 'none') {
if (target !== 'focus' && focusImage !== null) {
await stopFocus();
}
if (framingImage !== null) {
if (target !== 'framing' && framingImage !== null) {
await stopFraming();
}
if (target !== 'blank' && blank !== null) {
blankImage = null;
}
if (target !== 'gray' && blank !== null) {
grayImage = null;
}
}
async function start () {
await reset()
sequence.start();
}
@ -309,12 +330,8 @@ function stop () {
}
async function blank () {
if (focusImage !== null) {
await stopFocus();
}
if (framingImage !== null) {
await stopFraming();
}
await reset('blank');
blankImage = "SET";
await send({ cmd : 'blank' });
}
@ -334,10 +351,9 @@ async function focus () {
let dims : Dimensions;
let state : State;
let filePath : string;
if (focusImage !== null) {
await stopFocus();
return;
}
await reset('focus');
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
@ -355,7 +371,9 @@ async function focus () {
y : 0
}
}
focusImage = await TestImage.Focus(pos.w, pos.h);
fd.setMode(Mode.RGB);
await fd.load (focusImage, pos.x, pos.y, pos.w, pos.h);
await fd.display(focusImage);
@ -373,10 +391,9 @@ async function framing () {
let dims : Dimensions;
let state : State;
let filePath : string;
if (framingImage !== null) {
await stopFraming();
return;
}
await reset('framing');
if (sequence.isLoaded()) {
state = sequence.getState();
pos = {
@ -395,7 +412,9 @@ async function framing () {
}
}
framingImage = await TestImage.Frame(pos.w, pos.h);
fd.setMode(Mode.RGB);
await fd.load (framingImage, pos.x, pos.y, pos.w, pos.h);
await fd.display(framingImage);
send({ cmd : 'framing' });
@ -440,31 +459,24 @@ app.get('/:width/:height/image.jpg', async (req : Request, res : Response, next
let current : ImageObject = sequence.getCurrent();
let width : number = parseInt(req.params.width);
let height : number = parseInt(req.params.height);
//log.info(`${width}x${height}`)
if (focusImage !== null) {
try {
data = await image.thumbnail(focusImage, width, height);
log.info(`Image: ${current.path} - ${width},${height}`);
log.info(`Focus Image: ${focusImage} - ${width},${height}`);
} catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
log.error(`Error getting thumbnail of focusImage`, 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}`);
log.info(`Framing Image: ${framingImage} - ${width},${height}`);
} catch (err) {
log.error(`Error getting thumbnail of ${current}`, err);
log.error(`Error getting thumbnail of framingImage`, 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 {
} else if (blankImage !== null) {
try {
data = await image.blank(width, height);
log.info(`Blank - ${width},${height}`);
@ -472,6 +484,22 @@ app.get('/:width/:height/image.jpg', async (req : Request, res : Response, next
log.error('Error generating blank image', err);
return next(new Error('Error generating blank image'));
}
} else if (grayImage !== null) {
try {
data = await image.gray(width, height);
log.info(`Gray - ${width},${height}`);
} catch (err) {
log.error('Error generating gray image', err);
return next(new Error('Error generating gray image'));
}
}else if (current !== null) {
try {
data = await image.thumbnail(current.path, width, height);
log.info(`Frame Image: ${current.path} - ${width},${height}`);
} catch (err) {
log.error(`Error getting thumbnail of frame ${current}`, err);
return next(new Error('Error getting thumbnail'));
}
}
res.contentType('image/jpeg');
res.send(data);
@ -482,7 +510,7 @@ async function main () {
index = await createTemplate('./views/index.hbs');
ffmpeg = new FFMPEG(envString('FFMPEG', 'ffmpeg'));
ffprobe = new FFPROBE();
image = new Image();
image = new Image(envString('FI', 'fi'));
camera = new Camera(mock);
display = new Display(width, height);
fd = new FD(envString('FD', 'fd'), width, height, envString('FD_HOST', 'localhost'), envInt('FD_PORT', 8082), envString('FD_DISPLAY', null), mock);

View File

@ -36,6 +36,7 @@ declare class Display {
clear(): void;
updateScreen(): void;
updateDisplay(): void;
private startDisplay;
updateImage(): void;
update(msg: Message): void;
set(state: State): void;

View File

@ -41,6 +41,7 @@ class Display {
this.updateSize();
this.clear();
this.updateScreen();
this.startDisplay();
this.updateDisplay();
}
updateSize() {
@ -87,6 +88,20 @@ class Display {
this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
this.updateImage();
}
startDisplay() {
this.displayOffsetX = 0;
this.displayOffsetY = 0;
this.set({
offset: {
x: 0,
y: 0
},
display: {
width: this.screen.width,
height: this.screen.height
}
});
}
updateImage() {
this.img = new Image;
this.img.onload = function () {
@ -123,6 +138,7 @@ class Display {
this.updateSize();
this.clear();
this.updateScreen();
this.updateDisplay();
}
onResize(event) {
this.updateSize();
@ -409,10 +425,12 @@ class Client {
this.client.send(JSON.stringify({ cmd: 'focus' }));
}
receiveFocus(msg) {
console.log('receive focus');
this.display.setFocus();
this.display.updateImage();
}
receiveUnfocus(msg) {
console.log('receive unfocus');
this.display.unsetFocus();
this.display.updateImage();
}
@ -421,10 +439,12 @@ class Client {
this.client.send(JSON.stringify({ cmd: 'framing' }));
}
receiveFraming(msg) {
console.log('receive framing');
this.display.setFraming();
this.display.updateImage();
}
receiveUnframing(msg) {
console.log('receive unframing');
this.display.unsetFraming();
this.display.updateImage();
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1,3 @@
E,test/grayscale_43.jpg,500
E,test/grayscale_43.jpg,2500
E,test/grayscale_43.jpg,1000
RGB,test/grayscale_43.jpg,200,400,600