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,
"profiles": {
"mcopy": {

View File

@ -4,18 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
* @module display
* Provides features for displaying a full screen display of images for the digital module.
**/
const spawn = require("spawn");
const path_1 = require("path");
const delay_1 = require("delay");
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 {
constructor(platform, display) {
this.opened = false;
@ -78,9 +69,10 @@ class WebView {
}.bind(this));
}
onLoad(evt, arg) {
console.dir(arg);
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
if (this.loadWait[arg.src]) {
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
}
}
async focus() {
if (!this.digitalWindow) {
@ -139,28 +131,6 @@ class WebView {
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 {
constructor(sys) {
this.platform = sys.platform;
@ -180,12 +150,8 @@ class Display {
await this.wv.open();
}
}
async show(frame) {
let padded = padded_frame(frame);
let ext = 'png';
let tmppath;
tmppath = path_1.join(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
async show(src) {
await this.wv.show(src);
}
async showPath(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';
Object.defineProperty(exports, "__esModule", { value: true });
/** @module ffmpeg **/
const path_1 = require("path");
const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec");
@ -55,28 +56,35 @@ class FFMPEG {
const w = state.info.width;
const h = state.info.height;
const padded = this.padded_frame(frameNum);
let ext = 'tif';
let rgb = light.color;
let ext = 'png';
//let rgb : any[] = light.color;
let tmpoutput;
let cmd;
let output;
let cmd2;
let output2;
//let cmd2 : string;
//let output2 : any;
let fileExists = false;
let scale = '';
if (w && h) {
scale = `,scale=${w}:${h}`;
}
//console.dir(state)
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmpoutput = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
rgb = rgb.map((e) => {
return parseInt(e);
});
tmpoutput = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await fs_extra_1.exists(tmpoutput);
}
catch (err) {
//
}
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}"`;
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}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff"
//-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png"
@ -89,17 +97,16 @@ class FFMPEG {
}
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 {
this.log.info(cmd2);
output2 = await exec_1.exec(cmd2);
}
catch (err) {
output2 = await exec(cmd2);
} catch (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;
}
/**
@ -111,14 +118,20 @@ class FFMPEG {
*
* @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;
let ext = 'tif';
let tmpoutput;
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmpoutput = path_1.join(tmppath, `export-%08d.${ext}`);
let ext = 'png';
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd;
let output;
let scale = '';
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 {
await fs_extra_1.mkdir(tmppath);
}
@ -126,6 +139,15 @@ class FFMPEG {
this.log.error(err);
}
//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
@ -134,24 +156,19 @@ class FFMPEG {
*
* @returns {boolean} True if successful, false if not
**/
async clear(frame) {
const padded = this.padded_frame(frame);
let ext = 'tif';
async clear(state) {
const padded = this.padded_frame(state.frame);
let ext = 'png';
let tmppath;
let tmpoutput;
let cmd;
let fileExists;
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmppath = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
tmppath = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await fs_extra_1.exists(tmppath);
}
catch (err) {
this.log.error(err);
}
if (!fs_extra_1.exists)
if (!fileExists)
return false;
try {
await fs_extra_1.unlink(tmppath);
@ -175,6 +192,12 @@ class FFMPEG {
catch (err) {
this.log.error(err);
}
files = files.filter((file) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) {
files.forEach(async (file, index) => {
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 fs_extra_1 = require("fs-extra");
const delay_1 = require("delay");
const crypto_1 = require("crypto");
/**
* @module FilmOut
**/
@ -65,6 +66,15 @@ class FilmOut {
this.ipc.on('preview', this.preview.bind(this));
this.ipc.on('preview_frame', this.previewFrame.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.
@ -97,21 +107,15 @@ class FilmOut {
* Begin the process of exporting single frames from the video for display.
**/
async start() {
let path;
try {
await this.ffmpeg.clearAll();
path = await this.ffmpeg.frame(this.state, this.light.state);
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
try {
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 this.display.show(path);
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);
return false;
}
try {
await this.ffmpeg.clearAll();
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) {
try {
info = await this.stillInfo(arg.path);
@ -194,11 +205,30 @@ class FilmOut {
this.state.fileName = arg.fileName;
this.state.frames = frames;
this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true;
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
*
@ -248,7 +278,7 @@ class FilmOut {
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} arg Arguments from message
@ -267,7 +297,7 @@ class FilmOut {
}
try {
await this.display.open();
await this.display.show(arg.frame);
await this.display.show(path);
}
catch (err) {
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('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
}
onSystem(evt, args) {
let option;
@ -153,7 +154,7 @@ class FilmOut {
if (!valid) {
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);
displayName = pathStr.split('/').pop();
elem.val(displayName);
@ -198,7 +199,7 @@ class FilmOut {
state = JSON.parse(args.state);
$('#digital').addClass('active');
$('#projector_type_digital').prop('checked', 'checked');
gui.notify('DEVICES', `Using video ${state.fileName}`);
gui.notify('FILMOUT', `Using video ${state.fileName}`);
seq.set(0, 'PF');
grid.state(0);
seq.set(1, 'CF');
@ -207,17 +208,20 @@ class FilmOut {
if (light.disabled) {
//light.enable();
}
console.dir(state);
this.state.frame = 0;
this.state.frames = state.frames;
this.state.width = state.info.width;
this.state.height = state.info.height;
this.state.name = state.fileName;
this.state.path = state.path;
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
$('#filmout_stats_video_name').text(state.fileName);
$('#filmout_stats_video_size').text(`${state.info.width} x ${state.info.height}`);
$('#filmout_stats_video_frames').text(`${state.frames} frames`);
gui.updateState();
this.previewFrame();
this.preExport();
}
else {
$('#projector_type_digital').prop('checked', 'checked');
@ -235,6 +239,29 @@ class FilmOut {
elem[0].style.backgroundImage = `url('${args.path}')`;
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() {
this.state.frame++;
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",
"version": "1.6.0",
"version": "1.6.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

@ -61,6 +61,7 @@ class FilmOut {
ipcRenderer.on(this.id, this.onFilmout.bind(this));
ipcRenderer.on('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
}
onSystem (evt : Event, args : any) {
let option : any;
@ -157,7 +158,7 @@ class FilmOut {
if (!valid) {
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);
displayName = pathStr.split('/').pop();
elem.val(displayName);
@ -204,7 +205,7 @@ class FilmOut {
state = JSON.parse(args.state);
$('#digital').addClass('active');
$('#projector_type_digital').prop('checked', 'checked');
gui.notify('DEVICES', `Using video ${state.fileName}`);
gui.notify('FILMOUT', `Using video ${state.fileName}`);
seq.set(0, 'PF');
grid.state(0);
@ -216,12 +217,13 @@ class FilmOut {
if (light.disabled) {
//light.enable();
}
console.dir(state);
this.state.frame = 0;
this.state.frames = state.frames;
this.state.width = state.info.width;
this.state.height = state.info.height;
this.state.name = state.fileName;
this.state.path = state.path;
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
$('#filmout_stats_video_name').text(state.fileName);
@ -230,6 +232,7 @@ class FilmOut {
gui.updateState();
this.previewFrame();
this.preExport();
} else {
$('#projector_type_digital').prop('checked', 'checked');
$('#digital').removeClass('active');
@ -246,6 +249,30 @@ class FilmOut {
elem[0].style.backgroundImage = `url('${args.path}')`;
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 () {
this.state.frame++;
if (this.state.frame >= this.state.frames) {

View File

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

View File

@ -4,18 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
* @module display
* Provides features for displaying a full screen display of images for the digital module.
**/
const spawn = require("spawn");
const path_1 = require("path");
const delay_1 = require("delay");
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 {
constructor(platform, display) {
this.opened = false;
@ -78,9 +69,10 @@ class WebView {
}.bind(this));
}
onLoad(evt, arg) {
console.dir(arg);
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
if (this.loadWait[arg.src]) {
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
}
}
async focus() {
if (!this.digitalWindow) {
@ -139,28 +131,6 @@ class WebView {
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 {
constructor(sys) {
this.platform = sys.platform;
@ -180,12 +150,8 @@ class Display {
await this.wv.open();
}
}
async show(frame) {
let padded = padded_frame(frame);
let ext = 'png';
let tmppath;
tmppath = path_1.join(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
async show(src) {
await this.wv.show(src);
}
async showPath(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';
Object.defineProperty(exports, "__esModule", { value: true });
/** @module ffmpeg **/
const path_1 = require("path");
const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec");
@ -55,28 +56,35 @@ class FFMPEG {
const w = state.info.width;
const h = state.info.height;
const padded = this.padded_frame(frameNum);
let ext = 'tif';
let rgb = light.color;
let ext = 'png';
//let rgb : any[] = light.color;
let tmpoutput;
let cmd;
let output;
let cmd2;
let output2;
//let cmd2 : string;
//let output2 : any;
let fileExists = false;
let scale = '';
if (w && h) {
scale = `,scale=${w}:${h}`;
}
//console.dir(state)
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmpoutput = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
rgb = rgb.map((e) => {
return parseInt(e);
});
tmpoutput = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await fs_extra_1.exists(tmpoutput);
}
catch (err) {
//
}
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}"`;
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}" -compression_algo raw -pix_fmt rgb24 "export-%05d.tiff"
//-vf "select=gte(n\,${frame})" -compression_algo raw -pix_fmt rgb24 "export-${padded}.png"
@ -89,17 +97,16 @@ class FFMPEG {
}
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 {
this.log.info(cmd2);
output2 = await exec_1.exec(cmd2);
}
catch (err) {
output2 = await exec(cmd2);
} catch (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;
}
/**
@ -111,14 +118,20 @@ class FFMPEG {
*
* @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;
let ext = 'tif';
let tmpoutput;
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmpoutput = path_1.join(tmppath, `export-%08d.${ext}`);
let ext = 'png';
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd;
let output;
let scale = '';
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 {
await fs_extra_1.mkdir(tmppath);
}
@ -126,6 +139,15 @@ class FFMPEG {
this.log.error(err);
}
//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
@ -134,24 +156,19 @@ class FFMPEG {
*
* @returns {boolean} True if successful, false if not
**/
async clear(frame) {
const padded = this.padded_frame(frame);
let ext = 'tif';
async clear(state) {
const padded = this.padded_frame(state.frame);
let ext = 'png';
let tmppath;
let tmpoutput;
let cmd;
let fileExists;
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmppath = path_1.join(this.TMPDIR, `export-${padded}.${ext}`);
tmppath = path_1.join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await fs_extra_1.exists(tmppath);
}
catch (err) {
this.log.error(err);
}
if (!fs_extra_1.exists)
if (!fileExists)
return false;
try {
await fs_extra_1.unlink(tmppath);
@ -175,6 +192,12 @@ class FFMPEG {
catch (err) {
this.log.error(err);
}
files = files.filter((file) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) {
files.forEach(async (file, index) => {
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 fs_extra_1 = require("fs-extra");
const delay_1 = require("delay");
const crypto_1 = require("crypto");
/**
* @module FilmOut
**/
@ -65,6 +66,15 @@ class FilmOut {
this.ipc.on('preview', this.preview.bind(this));
this.ipc.on('preview_frame', this.previewFrame.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.
@ -97,21 +107,15 @@ class FilmOut {
* Begin the process of exporting single frames from the video for display.
**/
async start() {
let path;
try {
await this.ffmpeg.clearAll();
path = await this.ffmpeg.frame(this.state, this.light.state);
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
try {
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 this.display.show(path);
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);
return false;
}
try {
await this.ffmpeg.clearAll();
}
catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) {
try {
info = await this.stillInfo(arg.path);
@ -194,11 +205,30 @@ class FilmOut {
this.state.fileName = arg.fileName;
this.state.frames = frames;
this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true;
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
*
@ -248,7 +278,7 @@ class FilmOut {
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} arg Arguments from message
@ -267,7 +297,7 @@ class FilmOut {
}
try {
await this.display.open();
await this.display.show(arg.frame);
await this.display.show(path);
}
catch (err) {
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",
"version": "1.6.0",
"version": "1.6.1",
"description": "CLI for controlling the mcopy optical printer platform",
"main": "index.js",
"scripts": {

View File

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

2
package-lock.json generated
View File

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

View File

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

View File

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

View File

@ -5,22 +5,11 @@
* 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 { delay } from 'delay';
import { IpcMain } from '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 {
private digitalWindow : any;
public opened : boolean = false;
@ -91,8 +80,10 @@ class WebView {
}
onLoad (evt : Event, arg : any) {
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
if (this.loadWait[arg.src]) {
this.loadWait[arg.src]();
delete this.loadWait[arg.src];
}
}
async focus () {
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 {
private platform : string;
private displays : any[];
private display : any;
private tmpdir : string;
private wv : WebView;
private eog : EOG;
constructor (sys : any) {
this.platform = sys.platform;
@ -202,14 +164,8 @@ class Display {
await this.wv.open();
}
}
public async show (frame : number) {
let padded : string = padded_frame(frame);
let ext : string = 'png';
let tmppath : string;
tmppath = pathJoin(this.tmpdir, `export-${padded}.${ext}`);
await this.wv.show(tmppath);
public async show (src : string) {
await this.wv.show(src);
}
public async showPath (pathStr : string) {
return await this.wv.show(pathStr);

View File

@ -2,12 +2,16 @@
/** @module ffmpeg **/
import uuid from 'uuid/v4';
import { join } from 'path';
import { exists, mkdir, readdir, unlink } from 'fs-extra';
import { exec } from 'exec';
//const spawn = require('spawn');
import { exit } from 'exit';
interface FilmoutState {
frame : number;
path : string;
hash : string;
info : any;
}
/** @class FFMPEG **/
@ -63,39 +67,46 @@ class FFMPEG {
*
* @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 video : string = state.path;
const w : number = state.info.width;
const h : number = state.info.height;
const padded : string = this.padded_frame(frameNum);
let ext : string = 'tif';
let rgb : any[] = light.color;
let ext : string = 'png';
//let rgb : any[] = light.color;
let tmpoutput : string;
let cmd : string;
let output : any;
let cmd2 : string;
let output2 : any;
//let cmd2 : string;
//let output2 : any;
let fileExists = false;
let scale : string = '';
if (w && h) {
scale = `,scale=${w}:${h}`;
}
//console.dir(state)
tmpoutput = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
//if (system.platform !== 'nix') {
ext = 'png';
//}
try {
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) => {
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}"`;
//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 -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}" -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 (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
/*if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
try {
this.log.info(cmd2);
output2 = await exec(cmd2);
@ -118,8 +129,8 @@ class FFMPEG {
}
}
if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);
return tmpoutput
if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);*/
return tmpoutput;
}
/**
@ -131,17 +142,23 @@ class FFMPEG {
*
* @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;
let ext : string = 'tif';
let tmpoutput : string;
let ext : string = 'png';
let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd : string;
let output : any;
let scale : string = '';
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmpoutput = 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 {
await mkdir(tmppath);
} catch (err) {
@ -149,6 +166,16 @@ class FFMPEG {
}
//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
**/
public async clear (frame : number) {
const padded : string = this.padded_frame(frame);
let ext : string = 'tif';
public async clear (state : any) {
const padded : string = this.padded_frame(state.frame);
let ext : string = 'png';
let tmppath : string;
let tmpoutput : string;
let cmd : string;
let fileExists : boolean;
//if (system.platform !== 'nix') {
ext = 'png';
//}
tmppath = join(this.TMPDIR, `export-${padded}.${ext}`);
tmppath = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await exists(tmppath);
@ -178,7 +199,7 @@ class FFMPEG {
this.log.error(err);
}
if (!exists) return false;
if (!fileExists) return false;
try {
await unlink(tmppath);
@ -202,6 +223,12 @@ class FFMPEG {
} catch (err) {
this.log.error(err);
}
files = files.filter((file : string) => {
if (file.indexOf('-export-') !== -1) {
return true;
}
return false;
});
if (files) {
files.forEach(async (file : string, index : any) => {
try {

View File

@ -5,6 +5,7 @@ import { default as animated } from 'animated-gif-detector';
import { extname } from 'path';
import { readFile } from 'fs-extra';
import { delay } from 'delay';
import { createHash } from 'crypto';
/**
* @module FilmOut
@ -71,7 +72,16 @@ class FilmOut {
this.ipc.on('filmout_close', this.close.bind(this));
this.ipc.on('preview', this.preview.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.
@ -102,21 +112,17 @@ class FilmOut {
/**
* Begin the process of exporting single frames from the video for display.
**/
async start () {
async start () {
let path;
try {
await this.ffmpeg.clearAll();
path = await this.ffmpeg.frame(this.state, this.light.state);
} catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
try {
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 this.display.show(path);
await delay(20);
}
/**
@ -161,6 +167,13 @@ class FilmOut {
return false;
}
try {
await this.ffmpeg.clearAll();
} catch (err) {
this.log.error(err, 'FILMOUT', true, true);
throw err;
}
if (this.state.still) {
try {
info = await this.stillInfo(arg.path);
@ -196,6 +209,7 @@ class FilmOut {
this.state.fileName = arg.fileName;
this.state.frames = frames;
this.state.info = info;
this.state.hash = this.hash(arg.path);
this.log.info(`Opened ${this.state.fileName}`, '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) });
}
/**
* 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
*
@ -248,10 +282,11 @@ class FilmOut {
this.log.error(err, 'FILMOUT', true, true);;
throw err;
}
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} arg Arguments from message
@ -263,6 +298,7 @@ class FilmOut {
state.frame = arg.frame;
this.log.info(`Previewing frame ${state.frame} of ${state.fileName}`);
try {
path = await this.ffmpeg.frame(state, { color : [255, 255, 255] });
} catch (err) {
@ -272,7 +308,7 @@ class FilmOut {
try {
await this.display.open();
await this.display.show(arg.frame);
await this.display.show(path);
} catch (err) {
this.log.error(err, 'FILMOUT', true, true);
}