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:
parent
aec3e29476
commit
d70de98256
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"ext_port": 1111,
|
||||
"profiles": {
|
||||
"mcopy": {
|
||||
|
|
|
@ -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,10 +69,11 @@ class WebView {
|
|||
}.bind(this));
|
||||
}
|
||||
onLoad(evt, arg) {
|
||||
console.dir(arg);
|
||||
if (this.loadWait[arg.src]) {
|
||||
this.loadWait[arg.src]();
|
||||
delete this.loadWait[arg.src];
|
||||
}
|
||||
}
|
||||
async focus() {
|
||||
if (!this.digitalWindow) {
|
||||
console.warn(`Cannot show focus screen because window does not exist`);
|
||||
|
@ -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
|
@ -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) {
|
||||
//
|
||||
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}"`;
|
||||
}
|
||||
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 -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
|
@ -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
|
@ -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
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mcopy-app",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"ext_port": 1111,
|
||||
"profiles": {
|
||||
"mcopy": {
|
||||
|
|
|
@ -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,10 +69,11 @@ class WebView {
|
|||
}.bind(this));
|
||||
}
|
||||
onLoad(evt, arg) {
|
||||
console.dir(arg);
|
||||
if (this.loadWait[arg.src]) {
|
||||
this.loadWait[arg.src]();
|
||||
delete this.loadWait[arg.src];
|
||||
}
|
||||
}
|
||||
async focus() {
|
||||
if (!this.digitalWindow) {
|
||||
console.warn(`Cannot show focus screen because window does not exist`);
|
||||
|
@ -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
|
@ -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) {
|
||||
//
|
||||
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}"`;
|
||||
}
|
||||
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 -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
|
@ -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
|
@ -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": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"ext_port": 1111,
|
||||
"profiles": {
|
||||
"mcopy": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mcopy",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"ext_port": 1111,
|
||||
"profiles": {
|
||||
"mcopy": {
|
||||
|
|
|
@ -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,9 +80,11 @@ class WebView {
|
|||
}
|
||||
|
||||
onLoad (evt : Event, arg : any) {
|
||||
if (this.loadWait[arg.src]) {
|
||||
this.loadWait[arg.src]();
|
||||
delete this.loadWait[arg.src];
|
||||
}
|
||||
}
|
||||
async focus () {
|
||||
if (!this.digitalWindow) {
|
||||
console.warn(`Cannot show focus screen because window does not exist`);
|
||||
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
//}
|
||||
|
||||
tmpoutput = join(this.TMPDIR, `export-${padded}.${ext}`);
|
||||
|
||||
rgb = rgb.map((e : string) => {
|
||||
return parseInt(e);
|
||||
});
|
||||
try {
|
||||
fileExists = await exists(tmpoutput);
|
||||
} catch (err) {
|
||||
//
|
||||
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}"`;
|
||||
}
|
||||
|
||||
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 -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,16 +142,22 @@ 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';
|
||||
//}
|
||||
if (w && h) {
|
||||
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 {
|
||||
await mkdir(tmppath);
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -72,6 +73,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
|
||||
*/
|
||||
private hash (data : string) {
|
||||
return createHash('sha1').update(data).digest('hex');
|
||||
}
|
||||
/**
|
||||
* Sets filmout direction.
|
||||
|
@ -103,20 +113,16 @@ 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(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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue