Pre-export all frames in video (with confirmation dialog). Greatly improves sequence times and reliablity in conjunction with last commit. Resolves #36 and resolves #39.

This commit is contained in:
sixteenmillimeter 2020-02-21 13:34:22 -05:00
parent aec3e29476
commit d70de98256
27 changed files with 412 additions and 301 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

View File

@ -4,18 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
* @module display * @module display
* Provides features for displaying a full screen display of images for the digital module. * Provides features for displaying a full screen display of images for the digital module.
**/ **/
const spawn = require("spawn");
const path_1 = require("path"); const path_1 = require("path");
const delay_1 = require("delay"); const delay_1 = require("delay");
const { BrowserWindow } = require('electron'); const { BrowserWindow } = require('electron');
function padded_frame(i) {
let len = (i + '').length;
let str = i + '';
for (let x = 0; x < 8 - len; x++) {
str = '0' + str;
}
return str;
}
class WebView { class WebView {
constructor(platform, display) { constructor(platform, display) {
this.opened = false; this.opened = false;
@ -78,9 +69,10 @@ class WebView {
}.bind(this)); }.bind(this));
} }
onLoad(evt, arg) { onLoad(evt, arg) {
console.dir(arg); if (this.loadWait[arg.src]) {
this.loadWait[arg.src](); this.loadWait[arg.src]();
delete this.loadWait[arg.src]; delete this.loadWait[arg.src];
}
} }
async focus() { async focus() {
if (!this.digitalWindow) { if (!this.digitalWindow) {
@ -139,28 +131,6 @@ class WebView {
return true; return true;
} }
} }
class EOG {
constructor() {
}
open() {
this.hide();
}
async show(src) {
//timeout 3 eog --fullscreen ${src}
this.cp = spawn('eog', ['--fullscreen', src]);
await delay_1.delay(200);
return true;
}
hide() {
if (this.cp) {
this.cp.kill();
this.cp = null;
}
}
close() {
this.hide();
}
}
class Display { class Display {
constructor(sys) { constructor(sys) {
this.platform = sys.platform; this.platform = sys.platform;
@ -180,12 +150,8 @@ class Display {
await this.wv.open(); await this.wv.open();
} }
} }
async show(frame) { async show(src) {
let padded = padded_frame(frame); await this.wv.show(src);
let ext = 'png';
let tmppath;
tmppath = path_1.join(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
} }
async showPath(pathStr) { async showPath(pathStr) {
return await this.wv.show(pathStr); return await this.wv.show(pathStr);

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
'use strict'; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
/** @module ffmpeg **/
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec"); const exec_1 = require("exec");
@ -55,28 +56,35 @@ class FFMPEG {
const w = state.info.width; const w = state.info.width;
const h = state.info.height; const h = state.info.height;
const padded = this.padded_frame(frameNum); const padded = this.padded_frame(frameNum);
let ext = 'tif'; let ext = 'png';
let rgb = light.color; //let rgb : any[] = light.color;
let tmpoutput; let tmpoutput;
let cmd; let cmd;
let output; let output;
let cmd2; //let cmd2 : string;
let output2; //let output2 : any;
let fileExists = false;
let scale = ''; let scale = '';
if (w && h) { if (w && h) {
scale = `,scale=${w}:${h}`; scale = `,scale=${w}:${h}`;
} }
//console.dir(state) tmpoutput = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
//if (system.platform !== 'nix') { try {
ext = 'png'; fileExists = await fs_extra_1.exists(tmpoutput);
//} }
tmpoutput = path_1.join(this.TMPDIR, `export-${padded}.${ext}`); catch (err) {
rgb = rgb.map((e) => { //
return parseInt(e); }
}); if (fileExists) {
this.log.info(`File ${tmpoutput} exists`);
return tmpoutput;
}
//rgb = rgb.map((e : string) => {
// return parseInt(e);
//});
// //
cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"`; cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`; //cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`;
//ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg" //ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg"
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff"
//-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png" //-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png"
@ -89,17 +97,16 @@ class FFMPEG {
} }
if (output && output.stdout) if (output && output.stdout)
this.log.info(`"${output.stdout}"`); this.log.info(`"${output.stdout}"`);
if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) { /*if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
try { try {
this.log.info(cmd2); this.log.info(cmd2);
output2 = await exec_1.exec(cmd2); output2 = await exec(cmd2);
} } catch (err) {
catch (err) {
this.log.error(err); this.log.error(err);
} }
} }
if (output2 && output2.stdout)
this.log.info(`"${output2.stdout}"`); if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);*/
return tmpoutput; return tmpoutput;
} }
/** /**
@ -111,14 +118,20 @@ class FFMPEG {
* *
* @returns {?} * @returns {?}
**/ **/
async frames(video, obj) { async frames(state) {
const video = state.path;
const w = state.info.width;
const h = state.info.height;
const tmppath = this.TMPDIR; const tmppath = this.TMPDIR;
let ext = 'tif'; let ext = 'png';
let tmpoutput; let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
//if (system.platform !== 'nix') { let cmd;
ext = 'png'; let output;
//} let scale = '';
tmpoutput = path_1.join(tmppath, `export-%08d.${ext}`); if (w && h) {
scale = `scale=${w}:${h}`;
}
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
try { try {
await fs_extra_1.mkdir(tmppath); await fs_extra_1.mkdir(tmppath);
} }
@ -126,6 +139,15 @@ class FFMPEG {
this.log.error(err); this.log.error(err);
} }
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try {
this.log.info(cmd);
output = await exec_1.exec(cmd);
}
catch (err) {
this.log.error(err);
throw err;
}
return true;
} }
/** /**
* Clears a specific frame from the tmp directory * Clears a specific frame from the tmp directory
@ -134,24 +156,19 @@ class FFMPEG {
* *
* @returns {boolean} True if successful, false if not * @returns {boolean} True if successful, false if not
**/ **/
async clear(frame) { async clear(state) {
const padded = this.padded_frame(frame); const padded = this.padded_frame(state.frame);
let ext = 'tif'; let ext = 'png';
let tmppath; let tmppath;
let tmpoutput;
let cmd;
let fileExists; let fileExists;
//if (system.platform !== 'nix') { tmppath = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
ext = 'png';
//}
tmppath = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
try { try {
fileExists = await fs_extra_1.exists(tmppath); fileExists = await fs_extra_1.exists(tmppath);
} }
catch (err) { catch (err) {
this.log.error(err); this.log.error(err);
} }
if (!fs_extra_1.exists) if (!fileExists)
return false; return false;
try { try {
await fs_extra_1.unlink(tmppath); await fs_extra_1.unlink(tmppath);
@ -175,6 +192,12 @@ class FFMPEG {
catch (err) { catch (err) {
this.log.error(err); this.log.error(err);
} }
files = files.filter((file) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) { if (files) {
files.forEach(async (file, index) => { files.forEach(async (file, index) => {
try { try {

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@ const animated_gif_detector_1 = __importDefault(require("animated-gif-detector")
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const delay_1 = require("delay"); const delay_1 = require("delay");
const crypto_1 = require("crypto");
/** /**
* @module FilmOut * @module FilmOut
**/ **/
@ -65,6 +66,15 @@ class FilmOut {
this.ipc.on('preview', this.preview.bind(this)); this.ipc.on('preview', this.preview.bind(this));
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this));
}
/**
* Create a hash of a string.
*
* @param {string} data Data to produce hash of
*/
hash(data) {
return crypto_1.createHash('sha1').update(data).digest('hex');
} }
/** /**
* Sets filmout direction. * Sets filmout direction.
@ -97,21 +107,15 @@ class FilmOut {
* Begin the process of exporting single frames from the video for display. * Begin the process of exporting single frames from the video for display.
**/ **/
async start() { async start() {
let path;
try { try {
await this.ffmpeg.clearAll(); path = await this.ffmpeg.frame(this.state, this.light.state);
} }
catch (err) { catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
throw err; throw err;
} }
try { await this.display.show(path);
await this.ffmpeg.frame(this.state, this.light.state);
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
await this.display.show(this.state.frame);
await delay_1.delay(20); await delay_1.delay(20);
} }
/** /**
@ -156,6 +160,13 @@ class FilmOut {
this.log.error(`File is not of a valid file type`, 'FILMOUT', true, true); this.log.error(`File is not of a valid file type`, 'FILMOUT', true, true);
return false; return false;
} }
try {
await this.ffmpeg.clearAll();
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) { if (this.state.still) {
try { try {
info = await this.stillInfo(arg.path); info = await this.stillInfo(arg.path);
@ -194,11 +205,30 @@ class FilmOut {
this.state.fileName = arg.fileName; this.state.fileName = arg.fileName;
this.state.frames = frames; this.state.frames = frames;
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true); this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true; this.state.enabled = true;
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) }); return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
} }
/**
* Pre-export all frames from video for display.
*
* @param {object} evt IPC event
* @param {object} arg IPC args
*/
async onPreExport(evt, arg) {
if (!this.state.path) {
return await this.ui.send('pre_export', { complete: false, err: 'No file to pre export.' });
}
try {
await this.ffmpeg.frames(this.state);
}
catch (err) {
return await this.ui.send('pre_export', { complete: false, err });
}
return await this.ui.send('pre_export', { complete: true });
}
/** /**
* Return true if gif is animated, false if it is a still * Return true if gif is animated, false if it is a still
* *
@ -248,7 +278,7 @@ class FilmOut {
this.ui.send('preview_frame', { path, frame: arg.frame }); this.ui.send('preview_frame', { path, frame: arg.frame });
} }
/** /**
* * Open a single frame in a display window to preview filmout.
* *
* @param {object} evt Original event * @param {object} evt Original event
* @param {object} arg Arguments from message * @param {object} arg Arguments from message
@ -267,7 +297,7 @@ class FilmOut {
} }
try { try {
await this.display.open(); await this.display.open();
await this.display.show(arg.frame); await this.display.show(path);
} }
catch (err) { catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);

File diff suppressed because one or more lines are too long

View File

@ -62,6 +62,7 @@ class FilmOut {
ipcRenderer.on(this.id, this.onFilmout.bind(this)); ipcRenderer.on(this.id, this.onFilmout.bind(this));
ipcRenderer.on('system', this.onSystem.bind(this)); ipcRenderer.on('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this)); ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
} }
onSystem(evt, args) { onSystem(evt, args) {
let option; let option;
@ -153,7 +154,7 @@ class FilmOut {
if (!valid) { if (!valid) {
return false; return false;
} }
log.info(`Selected video ${pathStr.split('/').pop()}`, 'DIGITAL', true); log.info(`Selected video ${pathStr.split('/').pop()}`, 'FILMOUT', true);
elem.attr('data-file', pathStr); elem.attr('data-file', pathStr);
displayName = pathStr.split('/').pop(); displayName = pathStr.split('/').pop();
elem.val(displayName); elem.val(displayName);
@ -198,7 +199,7 @@ class FilmOut {
state = JSON.parse(args.state); state = JSON.parse(args.state);
$('#digital').addClass('active'); $('#digital').addClass('active');
$('#projector_type_digital').prop('checked', 'checked'); $('#projector_type_digital').prop('checked', 'checked');
gui.notify('DEVICES', `Using video ${state.fileName}`); gui.notify('FILMOUT', `Using video ${state.fileName}`);
seq.set(0, 'PF'); seq.set(0, 'PF');
grid.state(0); grid.state(0);
seq.set(1, 'CF'); seq.set(1, 'CF');
@ -207,17 +208,20 @@ class FilmOut {
if (light.disabled) { if (light.disabled) {
//light.enable(); //light.enable();
} }
console.dir(state);
this.state.frame = 0; this.state.frame = 0;
this.state.frames = state.frames; this.state.frames = state.frames;
this.state.width = state.info.width; this.state.width = state.info.width;
this.state.height = state.info.height; this.state.height = state.info.height;
this.state.name = state.fileName; this.state.name = state.fileName;
this.state.path = state.path;
$('#seq_loop').val(`${state.frames - 1}`).trigger('change'); $('#seq_loop').val(`${state.frames - 1}`).trigger('change');
$('#filmout_stats_video_name').text(state.fileName); $('#filmout_stats_video_name').text(state.fileName);
$('#filmout_stats_video_size').text(`${state.info.width} x ${state.info.height}`); $('#filmout_stats_video_size').text(`${state.info.width} x ${state.info.height}`);
$('#filmout_stats_video_frames').text(`${state.frames} frames`); $('#filmout_stats_video_frames').text(`${state.frames} frames`);
gui.updateState(); gui.updateState();
this.previewFrame(); this.previewFrame();
this.preExport();
} }
else { else {
$('#projector_type_digital').prop('checked', 'checked'); $('#projector_type_digital').prop('checked', 'checked');
@ -235,6 +239,29 @@ class FilmOut {
elem[0].style.backgroundImage = `url('${args.path}')`; elem[0].style.backgroundImage = `url('${args.path}')`;
elem.addClass('on'); elem.addClass('on');
} }
preExport() {
let proceed = false;
if (this.state.path && this.state.path !== '') {
proceed = confirm(`Export all frames for ${this.state.name}? This may take a while, but will allow filmout sequences to run faster.`);
}
if (proceed) {
gui.overlay(true);
gui.spinner(true, `Exporting frames for ${this.state.name}`);
ipcRenderer.send('pre_export', {});
}
}
onPreExport(evt, args) {
log.info('onPreExport');
if (args.completed) {
gui.notify('FILMOUT', `Exported frames for ${this.state.name}`);
log.info(`Exported frames for ${this.state.name}`, 'FILMOUT', true);
}
else {
log.error(args.err);
}
gui.overlay(false);
gui.spinner(false);
}
advance() { advance() {
this.state.frame++; this.state.frame++;
if (this.state.frame >= this.state.frames) { if (this.state.frame >= this.state.frames) {

File diff suppressed because one or more lines are too long

2
app/package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy-app", "name": "mcopy-app",
"version": "1.6.0", "version": "1.6.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy-app", "name": "mcopy-app",
"version": "1.6.0", "version": "1.6.1",
"description": "GUI for the mcopy small gauge film optical printer platform", "description": "GUI for the mcopy small gauge film optical printer platform",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@ -61,6 +61,7 @@ class FilmOut {
ipcRenderer.on(this.id, this.onFilmout.bind(this)); ipcRenderer.on(this.id, this.onFilmout.bind(this));
ipcRenderer.on('system', this.onSystem.bind(this)); ipcRenderer.on('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this)); ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
} }
onSystem (evt : Event, args : any) { onSystem (evt : Event, args : any) {
let option : any; let option : any;
@ -157,7 +158,7 @@ class FilmOut {
if (!valid) { if (!valid) {
return false; return false;
} }
log.info(`Selected video ${pathStr.split('/').pop()}`, 'DIGITAL', true); log.info(`Selected video ${pathStr.split('/').pop()}`, 'FILMOUT', true);
elem.attr('data-file', pathStr); elem.attr('data-file', pathStr);
displayName = pathStr.split('/').pop(); displayName = pathStr.split('/').pop();
elem.val(displayName); elem.val(displayName);
@ -204,7 +205,7 @@ class FilmOut {
state = JSON.parse(args.state); state = JSON.parse(args.state);
$('#digital').addClass('active'); $('#digital').addClass('active');
$('#projector_type_digital').prop('checked', 'checked'); $('#projector_type_digital').prop('checked', 'checked');
gui.notify('DEVICES', `Using video ${state.fileName}`); gui.notify('FILMOUT', `Using video ${state.fileName}`);
seq.set(0, 'PF'); seq.set(0, 'PF');
grid.state(0); grid.state(0);
@ -216,12 +217,13 @@ class FilmOut {
if (light.disabled) { if (light.disabled) {
//light.enable(); //light.enable();
} }
console.dir(state);
this.state.frame = 0; this.state.frame = 0;
this.state.frames = state.frames; this.state.frames = state.frames;
this.state.width = state.info.width; this.state.width = state.info.width;
this.state.height = state.info.height; this.state.height = state.info.height;
this.state.name = state.fileName; this.state.name = state.fileName;
this.state.path = state.path;
$('#seq_loop').val(`${state.frames - 1}`).trigger('change'); $('#seq_loop').val(`${state.frames - 1}`).trigger('change');
$('#filmout_stats_video_name').text(state.fileName); $('#filmout_stats_video_name').text(state.fileName);
@ -230,6 +232,7 @@ class FilmOut {
gui.updateState(); gui.updateState();
this.previewFrame(); this.previewFrame();
this.preExport();
} else { } else {
$('#projector_type_digital').prop('checked', 'checked'); $('#projector_type_digital').prop('checked', 'checked');
$('#digital').removeClass('active'); $('#digital').removeClass('active');
@ -246,6 +249,30 @@ class FilmOut {
elem[0].style.backgroundImage = `url('${args.path}')`; elem[0].style.backgroundImage = `url('${args.path}')`;
elem.addClass('on'); elem.addClass('on');
} }
preExport () {
let proceed = false;
if (this.state.path && this.state.path !== '') {
proceed = confirm(`Export all frames for ${this.state.name}? This may take a while, but will allow filmout sequences to run faster.`);
}
if (proceed) {
gui.overlay(true);
gui.spinner(true, `Exporting frames for ${this.state.name}`);
ipcRenderer.send('pre_export', { });
}
}
onPreExport (evt : Event, args : any) {
log.info('onPreExport');
if (args.completed) {
gui.notify('FILMOUT', `Exported frames for ${this.state.name}`);
log.info(`Exported frames for ${this.state.name}`, 'FILMOUT', true);
} else {
log.error(args.err);
}
gui.overlay(false);
gui.spinner(false);
}
advance () { advance () {
this.state.frame++; this.state.frame++;
if (this.state.frame >= this.state.frames) { if (this.state.frame >= this.state.frames) {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

View File

@ -4,18 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
* @module display * @module display
* Provides features for displaying a full screen display of images for the digital module. * Provides features for displaying a full screen display of images for the digital module.
**/ **/
const spawn = require("spawn");
const path_1 = require("path"); const path_1 = require("path");
const delay_1 = require("delay"); const delay_1 = require("delay");
const { BrowserWindow } = require('electron'); const { BrowserWindow } = require('electron');
function padded_frame(i) {
let len = (i + '').length;
let str = i + '';
for (let x = 0; x < 8 - len; x++) {
str = '0' + str;
}
return str;
}
class WebView { class WebView {
constructor(platform, display) { constructor(platform, display) {
this.opened = false; this.opened = false;
@ -78,9 +69,10 @@ class WebView {
}.bind(this)); }.bind(this));
} }
onLoad(evt, arg) { onLoad(evt, arg) {
console.dir(arg); if (this.loadWait[arg.src]) {
this.loadWait[arg.src](); this.loadWait[arg.src]();
delete this.loadWait[arg.src]; delete this.loadWait[arg.src];
}
} }
async focus() { async focus() {
if (!this.digitalWindow) { if (!this.digitalWindow) {
@ -139,28 +131,6 @@ class WebView {
return true; return true;
} }
} }
class EOG {
constructor() {
}
open() {
this.hide();
}
async show(src) {
//timeout 3 eog --fullscreen ${src}
this.cp = spawn('eog', ['--fullscreen', src]);
await delay_1.delay(200);
return true;
}
hide() {
if (this.cp) {
this.cp.kill();
this.cp = null;
}
}
close() {
this.hide();
}
}
class Display { class Display {
constructor(sys) { constructor(sys) {
this.platform = sys.platform; this.platform = sys.platform;
@ -180,12 +150,8 @@ class Display {
await this.wv.open(); await this.wv.open();
} }
} }
async show(frame) { async show(src) {
let padded = padded_frame(frame); await this.wv.show(src);
let ext = 'png';
let tmppath;
tmppath = path_1.join(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
} }
async showPath(pathStr) { async showPath(pathStr) {
return await this.wv.show(pathStr); return await this.wv.show(pathStr);

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
'use strict'; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
/** @module ffmpeg **/
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec"); const exec_1 = require("exec");
@ -55,28 +56,35 @@ class FFMPEG {
const w = state.info.width; const w = state.info.width;
const h = state.info.height; const h = state.info.height;
const padded = this.padded_frame(frameNum); const padded = this.padded_frame(frameNum);
let ext = 'tif'; let ext = 'png';
let rgb = light.color; //let rgb : any[] = light.color;
let tmpoutput; let tmpoutput;
let cmd; let cmd;
let output; let output;
let cmd2; //let cmd2 : string;
let output2; //let output2 : any;
let fileExists = false;
let scale = ''; let scale = '';
if (w && h) { if (w && h) {
scale = `,scale=${w}:${h}`; scale = `,scale=${w}:${h}`;
} }
//console.dir(state) tmpoutput = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
//if (system.platform !== 'nix') { try {
ext = 'png'; fileExists = await fs_extra_1.exists(tmpoutput);
//} }
tmpoutput = path_1.join(this.TMPDIR, `export-${padded}.${ext}`); catch (err) {
rgb = rgb.map((e) => { //
return parseInt(e); }
}); if (fileExists) {
this.log.info(`File ${tmpoutput} exists`);
return tmpoutput;
}
//rgb = rgb.map((e : string) => {
// return parseInt(e);
//});
// //
cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"`; cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`; //cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`;
//ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg" //ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg"
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff"
//-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png" //-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png"
@ -89,17 +97,16 @@ class FFMPEG {
} }
if (output && output.stdout) if (output && output.stdout)
this.log.info(`"${output.stdout}"`); this.log.info(`"${output.stdout}"`);
if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) { /*if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
try { try {
this.log.info(cmd2); this.log.info(cmd2);
output2 = await exec_1.exec(cmd2); output2 = await exec(cmd2);
} } catch (err) {
catch (err) {
this.log.error(err); this.log.error(err);
} }
} }
if (output2 && output2.stdout)
this.log.info(`"${output2.stdout}"`); if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);*/
return tmpoutput; return tmpoutput;
} }
/** /**
@ -111,14 +118,20 @@ class FFMPEG {
* *
* @returns {?} * @returns {?}
**/ **/
async frames(video, obj) { async frames(state) {
const video = state.path;
const w = state.info.width;
const h = state.info.height;
const tmppath = this.TMPDIR; const tmppath = this.TMPDIR;
let ext = 'tif'; let ext = 'png';
let tmpoutput; let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
//if (system.platform !== 'nix') { let cmd;
ext = 'png'; let output;
//} let scale = '';
tmpoutput = path_1.join(tmppath, `export-%08d.${ext}`); if (w && h) {
scale = `scale=${w}:${h}`;
}
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
try { try {
await fs_extra_1.mkdir(tmppath); await fs_extra_1.mkdir(tmppath);
} }
@ -126,6 +139,15 @@ class FFMPEG {
this.log.error(err); this.log.error(err);
} }
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try {
this.log.info(cmd);
output = await exec_1.exec(cmd);
}
catch (err) {
this.log.error(err);
throw err;
}
return true;
} }
/** /**
* Clears a specific frame from the tmp directory * Clears a specific frame from the tmp directory
@ -134,24 +156,19 @@ class FFMPEG {
* *
* @returns {boolean} True if successful, false if not * @returns {boolean} True if successful, false if not
**/ **/
async clear(frame) { async clear(state) {
const padded = this.padded_frame(frame); const padded = this.padded_frame(state.frame);
let ext = 'tif'; let ext = 'png';
let tmppath; let tmppath;
let tmpoutput;
let cmd;
let fileExists; let fileExists;
//if (system.platform !== 'nix') { tmppath = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
ext = 'png';
//}
tmppath = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
try { try {
fileExists = await fs_extra_1.exists(tmppath); fileExists = await fs_extra_1.exists(tmppath);
} }
catch (err) { catch (err) {
this.log.error(err); this.log.error(err);
} }
if (!fs_extra_1.exists) if (!fileExists)
return false; return false;
try { try {
await fs_extra_1.unlink(tmppath); await fs_extra_1.unlink(tmppath);
@ -175,6 +192,12 @@ class FFMPEG {
catch (err) { catch (err) {
this.log.error(err); this.log.error(err);
} }
files = files.filter((file) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) { if (files) {
files.forEach(async (file, index) => { files.forEach(async (file, index) => {
try { try {

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@ const animated_gif_detector_1 = __importDefault(require("animated-gif-detector")
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const delay_1 = require("delay"); const delay_1 = require("delay");
const crypto_1 = require("crypto");
/** /**
* @module FilmOut * @module FilmOut
**/ **/
@ -65,6 +66,15 @@ class FilmOut {
this.ipc.on('preview', this.preview.bind(this)); this.ipc.on('preview', this.preview.bind(this));
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this));
}
/**
* Create a hash of a string.
*
* @param {string} data Data to produce hash of
*/
hash(data) {
return crypto_1.createHash('sha1').update(data).digest('hex');
} }
/** /**
* Sets filmout direction. * Sets filmout direction.
@ -97,21 +107,15 @@ class FilmOut {
* Begin the process of exporting single frames from the video for display. * Begin the process of exporting single frames from the video for display.
**/ **/
async start() { async start() {
let path;
try { try {
await this.ffmpeg.clearAll(); path = await this.ffmpeg.frame(this.state, this.light.state);
} }
catch (err) { catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
throw err; throw err;
} }
try { await this.display.show(path);
await this.ffmpeg.frame(this.state, this.light.state);
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
await this.display.show(this.state.frame);
await delay_1.delay(20); await delay_1.delay(20);
} }
/** /**
@ -156,6 +160,13 @@ class FilmOut {
this.log.error(`File is not of a valid file type`, 'FILMOUT', true, true); this.log.error(`File is not of a valid file type`, 'FILMOUT', true, true);
return false; return false;
} }
try {
await this.ffmpeg.clearAll();
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) { if (this.state.still) {
try { try {
info = await this.stillInfo(arg.path); info = await this.stillInfo(arg.path);
@ -194,11 +205,30 @@ class FilmOut {
this.state.fileName = arg.fileName; this.state.fileName = arg.fileName;
this.state.frames = frames; this.state.frames = frames;
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true); this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true; this.state.enabled = true;
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) }); return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
} }
/**
* Pre-export all frames from video for display.
*
* @param {object} evt IPC event
* @param {object} arg IPC args
*/
async onPreExport(evt, arg) {
if (!this.state.path) {
return await this.ui.send('pre_export', { complete: false, err: 'No file to pre export.' });
}
try {
await this.ffmpeg.frames(this.state);
}
catch (err) {
return await this.ui.send('pre_export', { complete: false, err });
}
return await this.ui.send('pre_export', { complete: true });
}
/** /**
* Return true if gif is animated, false if it is a still * Return true if gif is animated, false if it is a still
* *
@ -248,7 +278,7 @@ class FilmOut {
this.ui.send('preview_frame', { path, frame: arg.frame }); this.ui.send('preview_frame', { path, frame: arg.frame });
} }
/** /**
* * Open a single frame in a display window to preview filmout.
* *
* @param {object} evt Original event * @param {object} evt Original event
* @param {object} arg Arguments from message * @param {object} arg Arguments from message
@ -267,7 +297,7 @@ class FilmOut {
} }
try { try {
await this.display.open(); await this.display.open();
await this.display.show(arg.frame); await this.display.show(path);
} }
catch (err) { catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy-cli", "name": "mcopy-cli",
"version": "1.6.0", "version": "1.6.1",
"description": "CLI for controlling the mcopy optical printer platform", "description": "CLI for controlling the mcopy optical printer platform",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy", "name": "mcopy",
"version": "1.6.0", "version": "1.6.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "mcopy", "name": "mcopy",
"version": "1.6.0", "version": "1.6.1",
"description": "Small gauge film optical printer platform", "description": "Small gauge film optical printer platform",
"main": "build.js", "main": "build.js",
"directories": { "directories": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.0", "version": "1.6.1",
"ext_port": 1111, "ext_port": 1111,
"profiles": { "profiles": {
"mcopy": { "mcopy": {

View File

@ -5,22 +5,11 @@
* Provides features for displaying a full screen display of images for the digital module. * Provides features for displaying a full screen display of images for the digital module.
**/ **/
import spawn = require('spawn');
import { join as pathJoin } from 'path'; import { join as pathJoin } from 'path';
import { delay } from 'delay'; import { delay } from 'delay';
import { IpcMain } from 'electron';
const { BrowserWindow } = require('electron'); const { BrowserWindow } = require('electron');
function padded_frame (i : number) {
let len = (i + '').length;
let str = i + '';
for (let x = 0; x < 8 - len; x++) {
str = '0' + str;
}
return str;
}
class WebView { class WebView {
private digitalWindow : any; private digitalWindow : any;
public opened : boolean = false; public opened : boolean = false;
@ -91,8 +80,10 @@ class WebView {
} }
onLoad (evt : Event, arg : any) { onLoad (evt : Event, arg : any) {
this.loadWait[arg.src](); if (this.loadWait[arg.src]) {
delete this.loadWait[arg.src]; this.loadWait[arg.src]();
delete this.loadWait[arg.src];
}
} }
async focus () { async focus () {
if (!this.digitalWindow) { if (!this.digitalWindow) {
@ -149,41 +140,12 @@ class WebView {
} }
} }
class EOG {
private cp : any;
constructor () {
}
public open () {
this.hide();
}
public async show (src : string) {
//timeout 3 eog --fullscreen ${src}
this.cp = spawn('eog', ['--fullscreen', src]);
await delay(200)
return true
}
public hide () {
if (this.cp) {
this.cp.kill();
this.cp = null;
}
}
public close () {
this.hide();
}
}
class Display { class Display {
private platform : string; private platform : string;
private displays : any[]; private displays : any[];
private display : any; private display : any;
private tmpdir : string; private tmpdir : string;
private wv : WebView; private wv : WebView;
private eog : EOG;
constructor (sys : any) { constructor (sys : any) {
this.platform = sys.platform; this.platform = sys.platform;
@ -202,14 +164,8 @@ class Display {
await this.wv.open(); await this.wv.open();
} }
} }
public async show (frame : number) { public async show (src : string) {
let padded : string = padded_frame(frame); await this.wv.show(src);
let ext : string = 'png';
let tmppath : string;
tmppath = pathJoin(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
} }
public async showPath (pathStr : string) { public async showPath (pathStr : string) {
return await this.wv.show(pathStr); return await this.wv.show(pathStr);

View File

@ -2,12 +2,16 @@
/** @module ffmpeg **/ /** @module ffmpeg **/
import uuid from 'uuid/v4';
import { join } from 'path'; import { join } from 'path';
import { exists, mkdir, readdir, unlink } from 'fs-extra'; import { exists, mkdir, readdir, unlink } from 'fs-extra';
import { exec } from 'exec'; import { exec } from 'exec';
//const spawn = require('spawn');
import { exit } from 'exit'; interface FilmoutState {
frame : number;
path : string;
hash : string;
info : any;
}
/** @class FFMPEG **/ /** @class FFMPEG **/
@ -63,39 +67,46 @@ class FFMPEG {
* *
* @returns {string} Path of frame * @returns {string} Path of frame
**/ **/
public async frame (state : any, light : any) { public async frame (state : FilmoutState, light : any) {
const frameNum : number = state.frame; const frameNum : number = state.frame;
const video : string = state.path; const video : string = state.path;
const w : number = state.info.width; const w : number = state.info.width;
const h : number = state.info.height; const h : number = state.info.height;
const padded : string = this.padded_frame(frameNum); const padded : string = this.padded_frame(frameNum);
let ext : string = 'tif'; let ext : string = 'png';
let rgb : any[] = light.color; //let rgb : any[] = light.color;
let tmpoutput : string; let tmpoutput : string;
let cmd : string; let cmd : string;
let output : any; let output : any;
let cmd2 : string; //let cmd2 : string;
let output2 : any; //let output2 : any;
let fileExists = false;
let scale : string = ''; let scale : string = '';
if (w && h) { if (w && h) {
scale = `,scale=${w}:${h}`; scale = `,scale=${w}:${h}`;
} }
//console.dir(state) tmpoutput = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
//if (system.platform !== 'nix') { try {
ext = 'png'; fileExists = await exists(tmpoutput);
//} } catch (err) {
//
}
tmpoutput = join(this.TMPDIR, `export-${padded}.${ext}`); if (fileExists) {
this.log.info(`File ${tmpoutput} exists`);
return tmpoutput;
}
rgb = rgb.map((e : string) => { //rgb = rgb.map((e : string) => {
return parseInt(e); // return parseInt(e);
}); //});
//
cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"`; //
cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`; cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
//cmd2 = `${this.convert} "${tmpoutput}" -resize ${w}x${h} -size ${w}x${h} xc:"rgb(${rgb[0]},${rgb[1]},${rgb[2]})" +swap -compose Darken -composite "${tmpoutput}"`;
//ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg" //ffmpeg -i "${video}" -ss 00:00:07.000 -vframes 1 "export-${time}.jpg"
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff"
@ -109,7 +120,7 @@ class FFMPEG {
} }
if (output && output.stdout) this.log.info(`"${output.stdout}"`); if (output && output.stdout) this.log.info(`"${output.stdout}"`);
if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) { /*if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
try { try {
this.log.info(cmd2); this.log.info(cmd2);
output2 = await exec(cmd2); output2 = await exec(cmd2);
@ -118,8 +129,8 @@ class FFMPEG {
} }
} }
if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`); if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);*/
return tmpoutput return tmpoutput;
} }
/** /**
@ -131,17 +142,23 @@ class FFMPEG {
* *
* @returns {?} * @returns {?}
**/ **/
public async frames (video : string, obj : any) { public async frames (state : FilmoutState) {
const video : string = state.path;
const w : number = state.info.width;
const h : number = state.info.height;
const tmppath : string = this.TMPDIR; const tmppath : string = this.TMPDIR;
let ext : string = 'tif'; let ext : string = 'png';
let tmpoutput : string; let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd : string;
let output : any;
let scale : string = '';
//if (system.platform !== 'nix') { if (w && h) {
ext = 'png'; scale = `scale=${w}:${h}`;
//} }
tmpoutput = join(tmppath, `export-%08d.${ext}`);
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
try { try {
await mkdir(tmppath); await mkdir(tmppath);
} catch (err) { } catch (err) {
@ -149,6 +166,16 @@ class FFMPEG {
} }
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try {
this.log.info(cmd);
output = await exec(cmd);
} catch (err) {
this.log.error(err);
throw err;
}
return true;
} }
/** /**
@ -158,19 +185,13 @@ class FFMPEG {
* *
* @returns {boolean} True if successful, false if not * @returns {boolean} True if successful, false if not
**/ **/
public async clear (frame : number) { public async clear (state : any) {
const padded : string = this.padded_frame(frame); const padded : string = this.padded_frame(state.frame);
let ext : string = 'tif'; let ext : string = 'png';
let tmppath : string; let tmppath : string;
let tmpoutput : string;
let cmd : string;
let fileExists : boolean; let fileExists : boolean;
//if (system.platform !== 'nix') { tmppath = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
ext = 'png';
//}
tmppath = join(this.TMPDIR, `export-${padded}.${ext}`);
try { try {
fileExists = await exists(tmppath); fileExists = await exists(tmppath);
@ -178,7 +199,7 @@ class FFMPEG {
this.log.error(err); this.log.error(err);
} }
if (!exists) return false; if (!fileExists) return false;
try { try {
await unlink(tmppath); await unlink(tmppath);
@ -202,6 +223,12 @@ class FFMPEG {
} catch (err) { } catch (err) {
this.log.error(err); this.log.error(err);
} }
files = files.filter((file : string) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) { if (files) {
files.forEach(async (file : string, index : any) => { files.forEach(async (file : string, index : any) => {
try { try {

View File

@ -5,6 +5,7 @@ import { default as animated } from 'animated-gif-detector';
import { extname } from 'path'; import { extname } from 'path';
import { readFile } from 'fs-extra'; import { readFile } from 'fs-extra';
import { delay } from 'delay'; import { delay } from 'delay';
import { createHash } from 'crypto';
/** /**
* @module FilmOut * @module FilmOut
@ -71,7 +72,16 @@ class FilmOut {
this.ipc.on('filmout_close', this.close.bind(this)); this.ipc.on('filmout_close', this.close.bind(this));
this.ipc.on('preview', this.preview.bind(this)); this.ipc.on('preview', this.preview.bind(this));
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this));
}
/**
* Create a hash of a string.
*
* @param {string} data Data to produce hash of
*/
private hash (data : string) {
return createHash('sha1').update(data).digest('hex');
} }
/** /**
* Sets filmout direction. * Sets filmout direction.
@ -102,21 +112,17 @@ class FilmOut {
/** /**
* Begin the process of exporting single frames from the video for display. * Begin the process of exporting single frames from the video for display.
**/ **/
async start () { async start () {
let path;
try { try {
await this.ffmpeg.clearAll(); path = await this.ffmpeg.frame(this.state, this.light.state);
} catch (err) { } catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
throw err; throw err;
} }
try { await this.display.show(path);
await this.ffmpeg.frame(this.state, this.light.state);
} catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
await this.display.show(this.state.frame);
await delay(20); await delay(20);
} }
/** /**
@ -161,6 +167,13 @@ class FilmOut {
return false; return false;
} }
try {
await this.ffmpeg.clearAll();
} catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) { if (this.state.still) {
try { try {
info = await this.stillInfo(arg.path); info = await this.stillInfo(arg.path);
@ -196,6 +209,7 @@ class FilmOut {
this.state.fileName = arg.fileName; this.state.fileName = arg.fileName;
this.state.frames = frames; this.state.frames = frames;
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true); this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
@ -203,6 +217,26 @@ class FilmOut {
return await this.ui.send(this.id, { valid : true, state : JSON.stringify(this.state) }); return await this.ui.send(this.id, { valid : true, state : JSON.stringify(this.state) });
} }
/**
* Pre-export all frames from video for display.
*
* @param {object} evt IPC event
* @param {object} arg IPC args
*/
async onPreExport (evt : Event, arg : any) {
if (!this.state.path) {
return await this.ui.send('pre_export', { complete : false, err : 'No file to pre export.' });
}
try {
await this.ffmpeg.frames(this.state);
} catch (err) {
return await this.ui.send('pre_export', { complete : false, err });
}
return await this.ui.send('pre_export', { complete : true });
}
/** /**
* Return true if gif is animated, false if it is a still * Return true if gif is animated, false if it is a still
* *
@ -248,10 +282,11 @@ class FilmOut {
this.log.error(err, 'FILMOUT', true, true);; this.log.error(err, 'FILMOUT', true, true);;
throw err; throw err;
} }
this.ui.send('preview_frame', { path, frame : arg.frame }) this.ui.send('preview_frame', { path, frame : arg.frame })
} }
/** /**
* * Open a single frame in a display window to preview filmout.
* *
* @param {object} evt Original event * @param {object} evt Original event
* @param {object} arg Arguments from message * @param {object} arg Arguments from message
@ -263,6 +298,7 @@ class FilmOut {
state.frame = arg.frame; state.frame = arg.frame;
this.log.info(`Previewing frame ${state.frame} of ${state.fileName}`); this.log.info(`Previewing frame ${state.frame} of ${state.fileName}`);
try { try {
path = await this.ffmpeg.frame(state, { color : [255, 255, 255] }); path = await this.ffmpeg.frame(state, { color : [255, 255, 255] });
} catch (err) { } catch (err) {
@ -272,7 +308,7 @@ class FilmOut {
try { try {
await this.display.open(); await this.display.open();
await this.display.show(arg.frame); await this.display.show(path);
} catch (err) { } catch (err) {
this.log.error(err, 'FILMOUT', true, true); this.log.error(err, 'FILMOUT', true, true);
} }