Added any number of untold features. Saving.

This commit is contained in:
mmcwilliams 2024-08-24 10:23:15 -04:00
parent 0b280ff623
commit c33aaa2bec
17 changed files with 450 additions and 73 deletions

View File

@ -84,7 +84,7 @@ class Display {
this.ctx.stroke(); this.ctx.stroke();
} }
private updateDisplay () { public updateDisplay () {
if (!this.sequence) { if (!this.sequence) {
return; return;
} }
@ -96,13 +96,13 @@ class Display {
this.displayOffsetX = this.screenOffsetX + Math.round(this.offsetX * screenScaleX); this.displayOffsetX = this.screenOffsetX + Math.round(this.offsetX * screenScaleX);
this.displayOffsetY = this.screenOffsetY + Math.round(this.offsetY * screenScaleY); this.displayOffsetY = this.screenOffsetY + Math.round(this.offsetY * screenScaleY);
this.ctx.fillStyle = 'rgb(125, 125, 125)'; this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight); this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
console.log(`${this.displayOffsetX}, ${this.displayOffsetY}, ${this.displayWidth}, ${this.displayHeight}`); console.log(`${this.displayOffsetX}, ${this.displayOffsetY}, ${this.displayWidth}, ${this.displayHeight}`);
this.updateImage(); this.updateImage();
} }
private updateImage() { public updateImage() {
const img : any = new Image; const img : any = new Image;
img.onload = function () { img.onload = function () {
this.ctx.drawImage(img, this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight); this.ctx.drawImage(img, this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
@ -136,16 +136,18 @@ class Client {
private client : WebSocket; private client : WebSocket;
private connected : boolean = false; private connected : boolean = false;
private progress : HTMLProgressElement; private progress : HTMLProgressElement;
private progressText : HTMLElement;
constructor () { constructor () {
let uri : string = 'ws://localhost:8082'; let uri : string = 'ws://localhost:8082';
this.progress = document.getElementById('progress') as HTMLProgressElement; this.progress = document.getElementById('progress') as HTMLProgressElement;
this.progressText = document.getElementById('progressText');
this.client = new WebSocket(uri); this.client = new WebSocket(uri);
this.display = new Display(); this.display = new Display();
this.client.onopen = this.onOpen.bind(this); this.client.onopen = this.onOpen.bind(this);
this.client.onclose = this.onClose.bind(this); this.client.onclose = this.onClose.bind(this);
this.client.onmessage = this.onMessage.bind(this); this.client.onmessage = this.onMessage.bind(this);
(document.getElementById('sequenceForm') as HTMLFormElement ).reset(); (document.getElementById('sequenceSelectForm') as HTMLFormElement ).reset();
(document.getElementById('sequenceCtrlForm') as HTMLFormElement ).reset(); (document.getElementById('sequenceCtrlForm') as HTMLFormElement ).reset();
(document.getElementById('manualCtrlForm') as HTMLFormElement ).reset(); (document.getElementById('manualCtrlForm') as HTMLFormElement ).reset();
this.disableClass('sequenceCtrl'); this.disableClass('sequenceCtrl');
@ -174,11 +176,19 @@ class Client {
private setSequence(state : State) { private setSequence(state : State) {
this.setProgress(state.sequence); this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence); this.setStatus(state.sequence);
this.setDisplay(state); this.setDisplay(state);
(document.getElementById('sequence') as HTMLSelectElement ).value = state.sequence.hash; (document.getElementById('sequence') as HTMLSelectElement ).value = state.sequence.hash;
} }
private setUpdate(state : State) {
this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence);
this.display.updateImage();
}
private setStatus (sequence : SequenceState) { private setStatus (sequence : SequenceState) {
let status : string; let status : string;
switch (sequence.status) { switch (sequence.status) {
@ -202,10 +212,15 @@ class Client {
private setProgress (sequence : SequenceState) { private setProgress (sequence : SequenceState) {
const percent : number = sequence.progress * 100.0; const percent : number = sequence.progress * 100.0;
if (this.progress !== null) { if (this.progress !== null) {
this.progress.value = percent; this.progress.value = sequence.progress;
this.progress.innerText = `${Math.floor(percent)}%`; this.progressText.innerText = `Progress: ${Math.floor(percent)}%`;
}
}
private setFrame (sequence : SequenceState) {
if (typeof sequence.current !== 'undefined') {
(document.getElementById('frame') as HTMLInputElement).value = `${sequence.current}`.padStart(5, '0');
} }
document.getElementById('sequenceProgress').innerText = `Progress: ${Math.round(sequence.progress)}%`;
} }
private setDisplay (state : State) { private setDisplay (state : State) {
@ -214,6 +229,7 @@ class Client {
const srcWidthEl : HTMLInputElement = document.getElementById('sourceWidth') as HTMLInputElement; const srcWidthEl : HTMLInputElement = document.getElementById('sourceWidth') as HTMLInputElement;
const srcHeightEl : HTMLInputElement = document.getElementById('sourceHeight') as HTMLInputElement; const srcHeightEl : HTMLInputElement = document.getElementById('sourceHeight') as HTMLInputElement;
if (typeof state.display !== 'undefined') {
widthEl.value = state.display.width as any; widthEl.value = state.display.width as any;
heightEl.value = state.display.height as any; heightEl.value = state.display.height as any;
@ -224,7 +240,7 @@ class Client {
heightEl.readOnly = false; heightEl.readOnly = false;
//console.dir(state); //console.dir(state);
this.display.set(state); this.display.set(state);
}
} }
private cmd (msg : Message) { private cmd (msg : Message) {
@ -238,6 +254,9 @@ class Client {
case 'select' : case 'select' :
this.receiveSelect(msg); this.receiveSelect(msg);
break; break;
case 'update' :
this.receiveUpdate(msg);
break;
default: default:
console.warn(`No command "${msg.cmd}"`); console.warn(`No command "${msg.cmd}"`);
break; break;
@ -280,6 +299,14 @@ class Client {
this.enableClass('manualCtrl'); this.enableClass('manualCtrl');
} }
public sendAdvance () {
this.client.send(JSON.stringify({ cmd : 'advance' }));
}
public sendRewind () {
this.client.send(JSON.stringify({ cmd : 'rewind' }));
}
public sendSelect () { public sendSelect () {
const hash : string = (document.getElementById('sequence') as HTMLSelectElement ).value; const hash : string = (document.getElementById('sequence') as HTMLSelectElement ).value;
let msg : Message; let msg : Message;
@ -294,13 +321,21 @@ class Client {
private receiveSelect (msg : Message) { private receiveSelect (msg : Message) {
console.log('got select'); console.log('got select');
console.dir(msg); //console.dir(msg);
this.enableClass('sequenceCtrl'); this.enableClass('sequenceCtrl');
this.setSequence(msg.state); this.setSequence(msg.state);
} }
private receiveUpdate (msg : Message) { public sendStart () {
this.client.send(JSON.stringify({ cmd : 'start' }));
}
public sendStop () {
this.client.send(JSON.stringify({ cmd : 'stop' }));
}
private receiveUpdate (msg : Message) {
this.setUpdate(msg.state);
} }
public fullscreen () { public fullscreen () {

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

@ -47,7 +47,8 @@ export declare class FD {
private socketAvailable; private socketAvailable;
private socketConnected; private socketConnected;
private waiting; private waiting;
constructor(bin: string, width: number, height: number, host: string, port: number); private mock;
constructor(bin: string, width: number, height: number, host: string, port: number, mock?: boolean);
private startDisplay; private startDisplay;
private startClient; private startClient;
private logstd; private logstd;

38
dist/fd/index.js vendored
View File

@ -25,26 +25,33 @@ var Mode;
Mode[Mode["INVERT_CHANNELS"] = 5] = "INVERT_CHANNELS"; Mode[Mode["INVERT_CHANNELS"] = 5] = "INVERT_CHANNELS";
})(Mode || (Mode = {})); })(Mode || (Mode = {}));
class FD { class FD {
constructor(bin, width, height, host, port) { constructor(bin, width, height, host, port, mock = false) {
this.socketAvailable = false; this.socketAvailable = false;
this.socketConnected = false; this.socketConnected = false;
this.waiting = null; this.waiting = null;
this.mock = false;
this.bin = bin; this.bin = bin;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.host = host; this.host = host;
this.port = port; this.port = port;
this.mock = mock;
this.log = (0, log_1.createLog)('fd'); this.log = (0, log_1.createLog)('fd');
if (!this.mock)
this.shell = new shell_1.Shell([this.bin, `${this.width}`, `${this.height}`, `${this.port}`], this.logstd.bind(this), this.logsterr.bind(this), null, true); this.shell = new shell_1.Shell([this.bin, `${this.width}`, `${this.height}`, `${this.port}`], this.logstd.bind(this), this.logsterr.bind(this), null, true);
this.startDisplay(); this.startDisplay();
this.startClient(); this.startClient();
this.test(); //this.test();
} }
async startDisplay() { async startDisplay() {
this.log.info(`Launching fd binary ${this.bin}`); this.log.info(`Launching fd binary ${this.bin}`);
if (!this.mock)
this.shell.execute(); this.shell.execute();
} }
async startClient() { async startClient() {
if (this.mock) {
return false;
}
this.client = new net_1.default.Socket(); this.client = new net_1.default.Socket();
this.log.info(`Waiting for TCP socket server on ${this.host}:${this.port}...`); this.log.info(`Waiting for TCP socket server on ${this.host}:${this.port}...`);
while (!this.socketAvailable) { while (!this.socketAvailable) {
@ -65,6 +72,7 @@ class FD {
this.client.on('error', (err) => { this.client.on('error', (err) => {
this.log.error('Error in socket client', err); this.log.error('Error in socket client', err);
this.socketConnected = false; this.socketConnected = false;
if (!this.mock)
this.shell.kill(); this.shell.kill();
}); });
} }
@ -90,6 +98,7 @@ class FD {
send(msg) { send(msg) {
const json = JSON.stringify(msg); const json = JSON.stringify(msg);
this.log.info(json); this.log.info(json);
if (!this.mock)
this.client.write(json); this.client.write(json);
} }
receive(json) { receive(json) {
@ -112,6 +121,13 @@ class FD {
} }
}; };
const startTime = +new Date(); const startTime = +new Date();
if (this.mock) {
return {
action: Action.LOAD,
image,
time: (+new Date()) - startTime
};
}
const promise = new Promise(function (resolve, reject) { const promise = new Promise(function (resolve, reject) {
this.waiting = function (msg) { this.waiting = function (msg) {
if (msg.action == Action.LOAD && msg.success) { if (msg.action == Action.LOAD && msg.success) {
@ -137,6 +153,16 @@ class FD {
exposure exposure
}; };
const startTime = +new Date(); const startTime = +new Date();
if (this.mock) {
for (let exp of exposure) {
await (0, delay_1.delay)(exp);
}
return {
action: Action.DISPLAY,
image,
time: (+new Date()) - startTime
};
}
const promise = new Promise(function (resolve, reject) { const promise = new Promise(function (resolve, reject) {
this.waiting = function (msg) { this.waiting = function (msg) {
if (msg.action == Action.DISPLAY && msg.success) { if (msg.action == Action.DISPLAY && msg.success) {
@ -161,6 +187,13 @@ class FD {
image image
}; };
const startTime = +new Date(); const startTime = +new Date();
if (this.mock) {
return {
action: Action.STOP,
image,
time: (+new Date()) - startTime
};
}
const promise = new Promise(function (resolve, reject) { const promise = new Promise(function (resolve, reject) {
this.waiting = function (msg) { this.waiting = function (msg) {
if (msg.action == Action.STOP && msg.success) { if (msg.action == Action.STOP && msg.success) {
@ -194,6 +227,7 @@ class FD {
await this.display(img); await this.display(img);
await (0, delay_1.delay)(2000); await (0, delay_1.delay)(2000);
await this.stop(img); await this.stop(img);
this.log.warn('QUITTING!!!!');
process.exit(); process.exit();
} }
} }

File diff suppressed because one or more lines are too long

29
dist/index.js vendored
View File

@ -35,6 +35,7 @@ const Handlebars = __importStar(require("handlebars"));
const ws_1 = require("ws"); const ws_1 = require("ws");
const log_1 = require("./log"); const log_1 = require("./log");
const files_1 = require("./files"); const files_1 = require("./files");
const fd_1 = require("./fd");
const display_1 = require("./display"); const display_1 = require("./display");
const ffmpeg_1 = require("./ffmpeg"); const ffmpeg_1 = require("./ffmpeg");
const ffprobe_1 = require("./ffprobe"); const ffprobe_1 = require("./ffprobe");
@ -175,7 +176,7 @@ function onWssConnection(ws, req) {
ws.ip = ip; ws.ip = ip;
ws.session = (0, uuid_1.v4)(); ws.session = (0, uuid_1.v4)();
ws.on('message', function (data) { onClientMessage(data, ws); }); ws.on('message', function (data) { onClientMessage(data, ws); });
sequence.updateClients(); sequence.updateClientsOnLoad();
} }
async function onClientMessage(data, ws) { async function onClientMessage(data, ws) {
let msg = null; let msg = null;
@ -201,6 +202,18 @@ async function cmd(msg) {
case 'select': case 'select':
await select(msg.state.sequence.hash); await select(msg.state.sequence.hash);
break; break;
case 'start':
start();
break;
case 'stop':
stop();
break;
case 'advance':
frameAdvance();
break;
case 'rewind':
frameRewind();
break;
default: default:
log.warn(`No matching command: ${msg.cmd}`); log.warn(`No matching command: ${msg.cmd}`);
} }
@ -213,6 +226,12 @@ async function cameraClose() {
await camera.close(); await camera.close();
send({ cmd: 'close' }); send({ cmd: 'close' });
} }
function frameAdvance() {
sequence.frameAdvance();
}
function frameRewind() {
sequence.frameRewind();
}
async function select(id) { async function select(id) {
const sequencesArr = await files_1.Files.enumerateSequences(sequences); const sequencesArr = await files_1.Files.enumerateSequences(sequences);
const seq = sequencesArr.find(el => el.hash === id); const seq = sequencesArr.find(el => el.hash === id);
@ -223,6 +242,12 @@ async function select(id) {
await sequence.load(seq); await sequence.load(seq);
return true; return true;
} }
function start() {
sequence.start();
}
function stop() {
sequence.stop();
}
async function send(msg) { async function send(msg) {
const msgStr = JSON.stringify(msg); const msgStr = JSON.stringify(msg);
wss.clients.forEach((client) => { wss.clients.forEach((client) => {
@ -271,7 +296,7 @@ async function main() {
image = new image_1.Image(); image = new image_1.Image();
camera = new camera_1.Camera(); camera = new camera_1.Camera();
display = new display_1.Display(width, height); display = new display_1.Display(width, height);
//fd = new FD(process.env['FD'], width, height, process.env['FD_HOST'], parseInt(process.env['FD_PORT'])); fd = new fd_1.FD(process.env['FD'], width, height, process.env['FD_HOST'], parseInt(process.env['FD_PORT']), true);
app.listen(port, async () => { app.listen(port, async () => {
log.info(`filmout_manager HTTP server running on port ${port}`); log.info(`filmout_manager HTTP server running on port ${port}`);
}); });

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -28,14 +28,21 @@ export declare class Sequence {
start(): void; start(): void;
stop(): void; stop(): void;
isRunning(): boolean; isRunning(): boolean;
private run;
load(seq: SequenceObject): Promise<void>; load(seq: SequenceObject): Promise<void>;
updateClients(): void; updateClientsOnLoad(): void;
updateClientsOnState(): void;
private enumerate; private enumerate;
unload(): void; unload(): void;
getState(): State; getState(): State;
getUpdateState(): State;
getSequenceState(): SequenceState; getSequenceState(): SequenceState;
setExposure(ms: number): void; setExposure(ms: number): void;
getStatus(): SequenceStatus; getStatus(): SequenceStatus;
getCurrent(): ImageObject; getCurrent(): ImageObject;
frameAdvance(frames?: number): void;
frameRewind(frames?: number): void;
frameSet(frame: number): void;
private frameRecord;
} }
export {}; export {};

View File

@ -28,26 +28,54 @@ class Sequence {
this.send = send; this.send = send;
} }
start() { start() {
if (this.current !== null) {
this.running = true; this.running = true;
this.log.info(`Started sequence: ${this.current.name}`);
this.run();
}
} }
stop() { stop() {
if (this.running && this.current !== null) {
this.log.info(`Stopped sequence: ${this.current.name}`);
this.running = false; this.running = false;
} }
}
isRunning() { isRunning() {
return this.running; return this.running;
} }
async run() {
//update running
for (let i = this.frame; i < this.images.length; i++) {
if (!this.running) {
break;
}
try {
await this.frameRecord();
}
catch (err) {
this.log.error(`Error recording frame`, err);
}
this.frameAdvance();
}
//complete running
}
async load(seq) { async load(seq) {
this.current = seq; this.current = seq;
this.frame = 0; this.frame = 0;
this.progress = 0; this.progress = 0;
await this.enumerate(); await this.enumerate();
this.updateClients(); this.updateClientsOnLoad();
} }
updateClients() { updateClientsOnLoad() {
if (this.current !== null) { if (this.current !== null) {
this.send({ cmd: 'select', state: this.getState() }); this.send({ cmd: 'select', state: this.getState() });
} }
} }
updateClientsOnState() {
if (this.current !== null) {
this.send({ cmd: 'update', state: this.getState() });
}
}
async enumerate() { async enumerate() {
let screen; let screen;
if (this.current === null) { if (this.current === null) {
@ -105,6 +133,19 @@ class Sequence {
exposure: this.exposure exposure: this.exposure
}; };
} }
getUpdateState() {
return {
sequence: {
hash: this.current.hash,
name: this.current.name,
progress: this.progress,
current: this.frame,
frames: this.frames,
status: this.getStatus()
},
exposure: this.exposure
};
}
getSequenceState() { getSequenceState() {
if (this.current === null) { if (this.current === null) {
return null; return null;
@ -133,6 +174,45 @@ class Sequence {
} }
return null; return null;
} }
frameAdvance(frames = 1) {
if (this.frame + frames >= this.images.length) {
this.frame = this.images.length - 1;
}
else {
this.frame += frames;
}
this.progress = this.frame / (this.images.length - 1);
this.updateClientsOnState();
}
frameRewind(frames = 1) {
if (this.frame + frames < 0) {
this.frame = 0;
}
else {
this.frame -= frames;
}
this.progress = this.frame > 0 ? this.frame / (this.images.length - 1) : 0;
this.updateClientsOnState();
}
frameSet(frame) {
if (frame < 0) {
frame = 0;
}
else if (frame > this.images.length - 1) {
frame = this.images.length - 1;
}
this.progress = this.frame > 0 ? this.frame / (this.images.length - 1) : 0;
this.updateClientsOnState();
}
async frameRecord() {
const img = this.images[this.frame];
const dimensions = this.display.getDimensions();
this.log.info(`Frame: ${this.frame} / ${this.images.length}`);
await this.fd.load(img.path, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
await this.camera.open();
await this.fd.display(img.path, [this.exposure]);
await this.camera.close();
}
} }
exports.Sequence = Sequence; exports.Sequence = Sequence;
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -61,26 +61,31 @@ export class FD {
private socketConnected : boolean = false; private socketConnected : boolean = false;
private waiting : Function = null; private waiting : Function = null;
private mock : boolean = false;
constructor (bin: string, width : number, height : number, host : string, port : number) { constructor (bin: string, width : number, height : number, host : string, port : number, mock : boolean = false) {
this.bin = bin; this.bin = bin;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.host = host; this.host = host;
this.port = port; this.port = port;
this.mock = mock;
this.log = createLog('fd'); this.log = createLog('fd');
this.shell = new Shell([ this.bin, `${this.width}`, `${this.height}`, `${this.port}` ], this.logstd.bind(this), this.logsterr.bind(this), null, true); if (!this.mock) this.shell = new Shell([ this.bin, `${this.width}`, `${this.height}`, `${this.port}` ], this.logstd.bind(this), this.logsterr.bind(this), null, true);
this.startDisplay(); this.startDisplay();
this.startClient(); this.startClient();
this.test(); //this.test();
} }
private async startDisplay () { private async startDisplay () {
this.log.info(`Launching fd binary ${this.bin}`); this.log.info(`Launching fd binary ${this.bin}`);
this.shell.execute(); if (!this.mock) this.shell.execute();
} }
private async startClient () { private async startClient () {
if (this.mock) {
return false;
}
this.client = new net.Socket(); this.client = new net.Socket();
this.log.info(`Waiting for TCP socket server on ${this.host}:${this.port}...`); this.log.info(`Waiting for TCP socket server on ${this.host}:${this.port}...`);
while (!this.socketAvailable) { while (!this.socketAvailable) {
@ -104,7 +109,7 @@ export class FD {
this.client.on('error', (err : Error) => { this.client.on('error', (err : Error) => {
this.log.error('Error in socket client', err); this.log.error('Error in socket client', err);
this.socketConnected = false; this.socketConnected = false;
this.shell.kill(); if (!this.mock) this.shell.kill();
}); });
} }
@ -131,7 +136,7 @@ export class FD {
private send (msg : fdOutgoingMessage) { private send (msg : fdOutgoingMessage) {
const json : string = JSON.stringify(msg); const json : string = JSON.stringify(msg);
this.log.info(json); this.log.info(json);
this.client.write(json); if (!this.mock) this.client.write(json);
} }
private receive (json : string) { private receive (json : string) {
@ -155,6 +160,13 @@ export class FD {
} }
}; };
const startTime : number = +new Date(); const startTime : number = +new Date();
if (this.mock) {
return {
action : Action.LOAD,
image,
time : (+new Date()) - startTime
};
}
const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) { const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) {
this.waiting = function (msg : fdIncomingMessage) { this.waiting = function (msg : fdIncomingMessage) {
if (msg.action == Action.LOAD && msg.success) { if (msg.action == Action.LOAD && msg.success) {
@ -180,6 +192,16 @@ export class FD {
exposure exposure
}; };
const startTime : number = +new Date(); const startTime : number = +new Date();
if (this.mock) {
for (let exp of exposure) {
await delay(exp);
}
return {
action : Action.DISPLAY,
image,
time : (+new Date()) - startTime
};
}
const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) { const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) {
this.waiting = function (msg : fdIncomingMessage) { this.waiting = function (msg : fdIncomingMessage) {
if (msg.action == Action.DISPLAY && msg.success) { if (msg.action == Action.DISPLAY && msg.success) {
@ -204,6 +226,13 @@ export class FD {
image image
}; };
const startTime : number = +new Date(); const startTime : number = +new Date();
if (this.mock) {
return {
action : Action.STOP,
image,
time : (+new Date()) - startTime
};
}
const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) { const promise : Promise<fdResult> = new Promise(function (resolve : Function, reject : Function) {
this.waiting = function (msg : fdIncomingMessage) { this.waiting = function (msg : fdIncomingMessage) {
if (msg.action == Action.STOP && msg.success) { if (msg.action == Action.STOP && msg.success) {
@ -244,6 +273,7 @@ export class FD {
await delay(2000); await delay(2000);
await this.stop(img); await this.stop(img);
this.log.warn('QUITTING!!!!');
process.exit(); process.exit();
} }
} }

View File

@ -164,7 +164,7 @@ function onWssConnection (ws : WebSocketExtended, req : Request) {
ws.ip = ip; ws.ip = ip;
ws.session = uuid(); ws.session = uuid();
ws.on('message', function (data) { onClientMessage(data, ws) }); ws.on('message', function (data) { onClientMessage(data, ws) });
sequence.updateClients(); sequence.updateClientsOnLoad();
} }
async function onClientMessage (data : any, ws : WebSocket) { async function onClientMessage (data : any, ws : WebSocket) {
@ -191,6 +191,18 @@ async function cmd (msg : Message) {
case 'select' : case 'select' :
await select(msg.state.sequence.hash); await select(msg.state.sequence.hash);
break; break;
case 'start' :
start();
break;
case 'stop' :
stop();
break;
case 'advance' :
frameAdvance();
break;
case 'rewind' :
frameRewind();
break;
default : default :
log.warn(`No matching command: ${msg.cmd}`); log.warn(`No matching command: ${msg.cmd}`);
} }
@ -206,6 +218,14 @@ async function cameraClose () {
send({ cmd : 'close' }); send({ cmd : 'close' });
} }
function frameAdvance () {
sequence.frameAdvance();
}
function frameRewind () {
sequence.frameRewind();
}
async function select (id : string) : Promise<boolean> { async function select (id : string) : Promise<boolean> {
const sequencesArr : SequenceObject[] = await Files.enumerateSequences(sequences); const sequencesArr : SequenceObject[] = await Files.enumerateSequences(sequences);
const seq : SequenceObject = sequencesArr.find(el => el.hash === id); const seq : SequenceObject = sequencesArr.find(el => el.hash === id);
@ -217,6 +237,14 @@ async function select (id : string) : Promise<boolean> {
return true; return true;
} }
function start () {
sequence.start();
}
function stop () {
sequence.stop();
}
async function send (msg : Message) { async function send (msg : Message) {
const msgStr : string = JSON.stringify(msg); const msgStr : string = JSON.stringify(msg);
wss.clients.forEach((client : WebSocket ) => { wss.clients.forEach((client : WebSocket ) => {
@ -265,8 +293,7 @@ async function main () {
image = new Image(); image = new Image();
camera = new Camera(); camera = new Camera();
display = new Display(width, height); display = new Display(width, height);
//fd = new FD(process.env['FD'], width, height, process.env['FD_HOST'], parseInt(process.env['FD_PORT'])); fd = new FD(process.env['FD'], width, height, process.env['FD_HOST'], parseInt(process.env['FD_PORT']), true);
app.listen(port, async () => { app.listen(port, async () => {
log.info(`filmout_manager HTTP server running on port ${port}`); log.info(`filmout_manager HTTP server running on port ${port}`);

View File

@ -1,5 +1,6 @@
import { Files } from '../files'; import { Files } from '../files';
import { createLog } from '../log' import { createLog } from '../log';
import { delay } from '../delay';
import type { Logger } from 'winston'; import type { Logger } from 'winston';
import type { SequenceObject, ImageObject } from '../files'; import type { SequenceObject, ImageObject } from '../files';
import type { FD, fdOutgoingPosition } from '../fd'; import type { FD, fdOutgoingPosition } from '../fd';
@ -42,31 +43,60 @@ export class Sequence {
} }
public start () { public start () {
if (this.current !== null) {
this.running = true; this.running = true;
this.log.info(`Started sequence: ${this.current.name}`);
this.run();
}
} }
public stop () { public stop () {
if (this.running && this.current !== null) {
this.log.info(`Stopped sequence: ${this.current.name}`);
this.running = false; this.running = false;
} }
}
public isRunning () { public isRunning () {
return this.running; return this.running;
} }
private async run () {
//update running
for (let i = this.frame; i < this.images.length; i++) {
if (!this.running) {
break;
}
try {
await this.frameRecord();
} catch (err) {
this.log.error(`Error recording frame`, err);
}
this.frameAdvance();
}
//complete running
}
public async load (seq : SequenceObject) { public async load (seq : SequenceObject) {
this.current = seq; this.current = seq;
this.frame = 0; this.frame = 0;
this.progress = 0; this.progress = 0;
await this.enumerate(); await this.enumerate();
this.updateClients(); this.updateClientsOnLoad();
} }
public updateClients () { public updateClientsOnLoad () {
if (this.current !== null) { if (this.current !== null) {
this.send({ cmd : 'select', state : this.getState() }); this.send({ cmd : 'select', state : this.getState() });
} }
} }
public updateClientsOnState () {
if (this.current !== null) {
this.send({ cmd : 'update', state : this.getState() });
}
}
private async enumerate () { private async enumerate () {
let screen : Dimensions; let screen : Dimensions;
@ -131,6 +161,20 @@ export class Sequence {
} }
} }
public getUpdateState () : State {
return {
sequence : {
hash : this.current.hash,
name : this.current.name,
progress : this.progress,
current : this.frame,
frames : this.frames,
status : this.getStatus() as SequenceStatus
},
exposure : this.exposure
}
}
public getSequenceState () : SequenceState { public getSequenceState () : SequenceState {
if (this.current === null) { if (this.current === null) {
return null; return null;
@ -161,4 +205,46 @@ export class Sequence {
} }
return null; return null;
} }
public frameAdvance (frames : number = 1) {
if (this.frame + frames >= this.images.length) {
this.frame = this.images.length - 1;
} else {
this.frame += frames;
}
this.progress = this.frame / (this.images.length - 1);
this.updateClientsOnState();
}
public frameRewind (frames : number = 1) {
if (this.frame + frames < 0) {
this.frame = 0;
} else {
this.frame -= frames;
}
this.progress = this.frame > 0 ? this.frame / (this.images.length - 1) : 0;
this.updateClientsOnState();
}
public frameSet (frame : number) {
if (frame < 0) {
frame = 0;
} else if (frame > this.images.length - 1) {
frame = this.images.length - 1;
}
this.progress = this.frame > 0 ? this.frame / (this.images.length - 1) : 0;
this.updateClientsOnState();
}
private async frameRecord () {
const img : ImageObject = this.images[this.frame];
const dimensions : fdOutgoingPosition = this.display.getDimensions();
this.log.info(`Frame: ${this.frame} / ${this.images.length}`);
await this.fd.load(img.path, dimensions.x, dimensions.y, dimensions.w, dimensions.h);
await this.camera.open();
await this.fd.display(img.path, [ this.exposure ] );
await this.camera.close();
}
} }

View File

@ -64,3 +64,10 @@ fieldset.inline .field-row {
fieldset.inline .field-row input { fieldset.inline .field-row input {
max-width: 50px; max-width: 50px;
} }
progress {
width: 97vw;
position: absolute;
bottom: 28px;
left: 0.5vw;
}

11
static/js/index.d.ts vendored
View File

@ -31,8 +31,8 @@ declare class Display {
private updateSize; private updateSize;
private clear; private clear;
private updateScreen; private updateScreen;
private updateDisplay; updateDisplay(): void;
private updateImage; updateImage(): void;
update(msg: Message): void; update(msg: Message): void;
set(state: State): void; set(state: State): void;
private onResize; private onResize;
@ -42,13 +42,16 @@ declare class Client {
private client; private client;
private connected; private connected;
private progress; private progress;
private progressText;
constructor(); constructor();
private onMessage; private onMessage;
private onOpen; private onOpen;
private onClose; private onClose;
private setSequence; private setSequence;
private setUpdate;
private setStatus; private setStatus;
private setProgress; private setProgress;
private setFrame;
private setDisplay; private setDisplay;
private cmd; private cmd;
disableClass(className: string): void; disableClass(className: string): void;
@ -57,8 +60,12 @@ declare class Client {
private receiveCameraOpen; private receiveCameraOpen;
sendCameraClose(): void; sendCameraClose(): void;
private receiveCameraClose; private receiveCameraClose;
sendAdvance(): void;
sendRewind(): void;
sendSelect(): void; sendSelect(): void;
private receiveSelect; private receiveSelect;
sendStart(): void;
sendStop(): void;
private receiveUpdate; private receiveUpdate;
fullscreen(): void; fullscreen(): void;
exitFullscreen(): void; exitFullscreen(): void;

View File

@ -82,7 +82,7 @@ class Display {
this.displayHeight = Math.round(this.height * screenScaleY); this.displayHeight = Math.round(this.height * screenScaleY);
this.displayOffsetX = this.screenOffsetX + Math.round(this.offsetX * screenScaleX); this.displayOffsetX = this.screenOffsetX + Math.round(this.offsetX * screenScaleX);
this.displayOffsetY = this.screenOffsetY + Math.round(this.offsetY * screenScaleY); this.displayOffsetY = this.screenOffsetY + Math.round(this.offsetY * screenScaleY);
this.ctx.fillStyle = 'rgb(125, 125, 125)'; this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight); this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
console.log(`${this.displayOffsetX}, ${this.displayOffsetY}, ${this.displayWidth}, ${this.displayHeight}`); console.log(`${this.displayOffsetX}, ${this.displayOffsetY}, ${this.displayWidth}, ${this.displayHeight}`);
this.updateImage(); this.updateImage();
@ -116,12 +116,13 @@ class Client {
this.connected = false; this.connected = false;
let uri = 'ws://localhost:8082'; let uri = 'ws://localhost:8082';
this.progress = document.getElementById('progress'); this.progress = document.getElementById('progress');
this.progressText = document.getElementById('progressText');
this.client = new WebSocket(uri); this.client = new WebSocket(uri);
this.display = new Display(); this.display = new Display();
this.client.onopen = this.onOpen.bind(this); this.client.onopen = this.onOpen.bind(this);
this.client.onclose = this.onClose.bind(this); this.client.onclose = this.onClose.bind(this);
this.client.onmessage = this.onMessage.bind(this); this.client.onmessage = this.onMessage.bind(this);
document.getElementById('sequenceForm').reset(); document.getElementById('sequenceSelectForm').reset();
document.getElementById('sequenceCtrlForm').reset(); document.getElementById('sequenceCtrlForm').reset();
document.getElementById('manualCtrlForm').reset(); document.getElementById('manualCtrlForm').reset();
this.disableClass('sequenceCtrl'); this.disableClass('sequenceCtrl');
@ -146,10 +147,17 @@ class Client {
} }
setSequence(state) { setSequence(state) {
this.setProgress(state.sequence); this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence); this.setStatus(state.sequence);
this.setDisplay(state); this.setDisplay(state);
document.getElementById('sequence').value = state.sequence.hash; document.getElementById('sequence').value = state.sequence.hash;
} }
setUpdate(state) {
this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence);
this.display.updateImage();
}
setStatus(sequence) { setStatus(sequence) {
let status; let status;
switch (sequence.status) { switch (sequence.status) {
@ -172,16 +180,21 @@ class Client {
setProgress(sequence) { setProgress(sequence) {
const percent = sequence.progress * 100.0; const percent = sequence.progress * 100.0;
if (this.progress !== null) { if (this.progress !== null) {
this.progress.value = percent; this.progress.value = sequence.progress;
this.progress.innerText = `${Math.floor(percent)}%`; this.progressText.innerText = `Progress: ${Math.floor(percent)}%`;
}
}
setFrame(sequence) {
if (typeof sequence.current !== 'undefined') {
document.getElementById('frame').value = `${sequence.current}`.padStart(5, '0');
} }
document.getElementById('sequenceProgress').innerText = `Progress: ${Math.round(sequence.progress)}%`;
} }
setDisplay(state) { setDisplay(state) {
const widthEl = document.getElementById('displayWidth'); const widthEl = document.getElementById('displayWidth');
const heightEl = document.getElementById('displayHeight'); const heightEl = document.getElementById('displayHeight');
const srcWidthEl = document.getElementById('sourceWidth'); const srcWidthEl = document.getElementById('sourceWidth');
const srcHeightEl = document.getElementById('sourceHeight'); const srcHeightEl = document.getElementById('sourceHeight');
if (typeof state.display !== 'undefined') {
widthEl.value = state.display.width; widthEl.value = state.display.width;
heightEl.value = state.display.height; heightEl.value = state.display.height;
srcWidthEl.value = state.source.width; srcWidthEl.value = state.source.width;
@ -190,6 +203,7 @@ class Client {
heightEl.readOnly = false; heightEl.readOnly = false;
this.display.set(state); this.display.set(state);
} }
}
cmd(msg) { cmd(msg) {
switch (msg.cmd) { switch (msg.cmd) {
case 'open': case 'open':
@ -201,6 +215,9 @@ class Client {
case 'select': case 'select':
this.receiveSelect(msg); this.receiveSelect(msg);
break; break;
case 'update':
this.receiveUpdate(msg);
break;
default: default:
console.warn(`No command "${msg.cmd}"`); console.warn(`No command "${msg.cmd}"`);
break; break;
@ -236,6 +253,12 @@ class Client {
console.log('got camera close'); console.log('got camera close');
this.enableClass('manualCtrl'); this.enableClass('manualCtrl');
} }
sendAdvance() {
this.client.send(JSON.stringify({ cmd: 'advance' }));
}
sendRewind() {
this.client.send(JSON.stringify({ cmd: 'rewind' }));
}
sendSelect() { sendSelect() {
const hash = document.getElementById('sequence').value; const hash = document.getElementById('sequence').value;
let msg; let msg;
@ -249,11 +272,17 @@ class Client {
} }
receiveSelect(msg) { receiveSelect(msg) {
console.log('got select'); console.log('got select');
console.dir(msg);
this.enableClass('sequenceCtrl'); this.enableClass('sequenceCtrl');
this.setSequence(msg.state); this.setSequence(msg.state);
} }
sendStart() {
this.client.send(JSON.stringify({ cmd: 'start' }));
}
sendStop() {
this.client.send(JSON.stringify({ cmd: 'stop' }));
}
receiveUpdate(msg) { receiveUpdate(msg) {
this.setUpdate(msg.state);
} }
fullscreen() { fullscreen() {
if (!document.fullscreenElement) { if (!document.fullscreenElement) {

File diff suppressed because one or more lines are too long

View File

@ -64,10 +64,10 @@
{{/each}} {{/each}}
</select> </select>
--> -->
<div> <div class="flex">
<fieldset id="sequenceSelect"> <fieldset id="sequenceSelect" class="inline half">
<legend>Sequence</legend> <legend>Sequence</legend>
<form id="sequenceForm" onsubmit="return false;"> <form id="sequenceSelectForm" onsubmit="return false;">
<select name="sequence" id="sequence"> <select name="sequence" id="sequence">
<option> - Select Image Sequence - </option> <option> - Select Image Sequence - </option>
{{#each sequences}} {{#each sequences}}
@ -77,17 +77,26 @@
<button id="select" onclick="client.sendSelect();">Select</button> <button id="select" onclick="client.sendSelect();">Select</button>
</form> </form>
</fieldset> </fieldset>
<fieldset id="displayAdjust" class="inline half">
<legend>Display Adjust</legend>
<form id="displayAdjustForm" onsubmit="return false;">
<button id="offsetXPlus">X +</button>
<button id="offsetXMinus">X -</button>
<button id="offsetYPlus">Y +</button>
<button id="offsetYMinus">Y -</button>
</form>
</fieldset>
</div> </div>
<div> <div>
<fieldset id="sequenceCtrl"> <fieldset id="sequenceCtrl">
<legend>Sequence Controls</legend> <legend>Sequence Controls</legend>
<form id="sequenceCtrlForm" onsubmit="return false;"> <form id="sequenceCtrlForm" onsubmit="return false;">
<button id="start" class="sequenceCtrl" disabled>Start</button> <button id="start" class="sequenceCtrl" onclick="client.sendStart();" disabled>Start</button>
<button id="stop" class="sequenceCtrl" disabled>Stop</button> <button id="stop" class="sequenceCtrl" onclick="client.sendStop();" disabled>Stop</button>
<button id="pause" class="sequenceCtrl" disabled>Pause</button> <button id="pause" class="sequenceCtrl" disabled>Pause</button>
<button id="rewind" class="sequenceCtrl" disabled><<</button> <button id="rewind" class="sequenceCtrl" onclick="client.sendRewind();" disabled><<</button>
<input id="frame" value="00000" class="sequenceCtrl" disabled /> <input id="frame" value="00000" class="sequenceCtrl" disabled />
<button id="forward" class="sequenceCtrl" disabled>>></button> <button id="advance" class="sequenceCtrl" onclick="client.sendAdvance()" disabled>>></button>
</form> </form>
</fieldset> </fieldset>
</div> </div>
@ -108,7 +117,7 @@
<div class="status-bar" id="status"> <div class="status-bar" id="status">
<p class="status-bar-field" id="sequenceStatus">Idle</p> <p class="status-bar-field" id="sequenceStatus">Idle</p>
<p class="status-bar-field" id="sequenceName">Not Set</p> <p class="status-bar-field" id="sequenceName">Not Set</p>
<p class="status-bar-field" id="sequenceProgress">Progress: 0%</p> <p class="status-bar-field" id="progressText">Progress: 0%</p>
<p class="status-bar-field" id="sequenceLength">Sequence Length: 0</p> <p class="status-bar-field" id="sequenceLength">Sequence Length: 0</p>
</div> </div>
</div> </div>