Display export progress and time estimate while all frames are being exported
This commit is contained in:
parent
891c34ad88
commit
1c940d6df7
|
@ -4,6 +4,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
const path_1 = require("path");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const exec_1 = require("exec");
|
||||
const child_process_1 = require("child_process");
|
||||
async function spawnAsync(bin, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = child_process_1.spawn(bin, args);
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.on('exit', (code) => {
|
||||
if (code === 0) {
|
||||
return resolve({ stdout, stderr });
|
||||
}
|
||||
else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr);
|
||||
}
|
||||
});
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
return child;
|
||||
});
|
||||
}
|
||||
/** @class FFMPEG **/
|
||||
class FFMPEG {
|
||||
/**
|
||||
|
@ -14,6 +39,7 @@ class FFMPEG {
|
|||
**/
|
||||
constructor(sys) {
|
||||
this.id = 'ffmpeg';
|
||||
this.onProgress = () => { };
|
||||
this.bin = sys.deps.ffmpeg;
|
||||
this.convert = sys.deps.convert;
|
||||
this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital');
|
||||
|
@ -42,6 +68,35 @@ class FFMPEG {
|
|||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
* Parse the stderr output of ffmpeg
|
||||
*
|
||||
* @param {string} line Stderr line
|
||||
**/
|
||||
parseStderr(line) {
|
||||
//frame= 6416 fps= 30 q=31.0 size= 10251kB time=00:03:34.32 bitrate= 391.8kbits/s speed= 1x
|
||||
let obj = {};
|
||||
if (line.substring(0, 'frame='.length) === 'frame=') {
|
||||
try {
|
||||
obj.frame = line.split('frame=')[1].split('fps=')[0];
|
||||
obj.frame = parseInt(obj.frame);
|
||||
obj.fps = line.split('fps=')[1].split('q=')[0];
|
||||
obj.fps = parseFloat(obj.fps);
|
||||
obj.time = line.split('time=')[1].split('bitrate=')[0];
|
||||
obj.speed = line.split('speed=')[1].trim().replace('x', '');
|
||||
obj.speed = parseFloat(obj.speed);
|
||||
obj.size = line.split('size=')[1].split('time=')[0].trim();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
console.log(line);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
/**
|
||||
* Render a single frame from a video or image to a png.
|
||||
*
|
||||
|
@ -125,13 +180,26 @@ class FFMPEG {
|
|||
const tmppath = this.TMPDIR;
|
||||
let ext = 'png';
|
||||
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
|
||||
let cmd;
|
||||
let args;
|
||||
let output;
|
||||
let scale = '';
|
||||
let estimated = -1;
|
||||
//cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
args = [
|
||||
'-y',
|
||||
'-i', video
|
||||
];
|
||||
if (w && h) {
|
||||
scale = `scale=${w}:${h}`;
|
||||
args.push('-vf');
|
||||
args.push(`scale=${w}:${h}`);
|
||||
}
|
||||
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
args = args.concat([
|
||||
'-compression_algo', 'raw',
|
||||
'-pix_fmt', 'rgb24',
|
||||
'-crf', '0',
|
||||
tmpoutput
|
||||
]);
|
||||
console.dir(args);
|
||||
console.dir(state);
|
||||
try {
|
||||
await fs_extra_1.mkdir(tmppath);
|
||||
}
|
||||
|
@ -139,15 +207,54 @@ 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);
|
||||
return new Promise((resolve, reject) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
this.log.info(`${this.bin} ${args.join(' ')}`);
|
||||
this.child = child_process_1.spawn(this.bin, args);
|
||||
this.child.on('exit', (code) => {
|
||||
console.log('GOT TO EXIT');
|
||||
if (code === 0) {
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
return resolve(true);
|
||||
}
|
||||
else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr + stdout);
|
||||
}
|
||||
});
|
||||
this.child.stdout.on('data', (data) => {
|
||||
const line = data.toString();
|
||||
stdout += line;
|
||||
});
|
||||
this.child.stderr.on('data', (data) => {
|
||||
const line = data.toString();
|
||||
const obj = this.parseStderr(line);
|
||||
if (obj.frame && state.frames) {
|
||||
obj.progress = obj.frame / state.frames;
|
||||
}
|
||||
if (obj.frame && obj.speed && state.frames && state.info.fps) {
|
||||
//scale by speed
|
||||
obj.remaining = ((state.frames - obj.frame) / state.info.fps) / obj.speed;
|
||||
obj.estimated = state.info.seconds / obj.speed;
|
||||
if (obj.estimated > estimated) {
|
||||
estimated = obj.estimated;
|
||||
}
|
||||
}
|
||||
if (obj.frame) {
|
||||
//log.info(`${input.name} ${obj.frame}/${input.frames} ${Math.round(obj.progress * 1000) / 10}% ${Math.round(obj.remaining)} seconds remaining of ${Math.round(obj.estimated)}`);
|
||||
this.onProgress(obj);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
cancel() {
|
||||
if (this.child) {
|
||||
this.child.kill();
|
||||
this.log.info(`Stopped exporting sequence with ffmpeg`);
|
||||
}
|
||||
catch (err) {
|
||||
this.log.error(err);
|
||||
throw err;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Clears a specific frame from the tmp directory
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,21 @@ class FFPROBE {
|
|||
constructor(sys) {
|
||||
this.bin = sys.deps.ffprobe;
|
||||
}
|
||||
/**
|
||||
* Parse the fps entry into a float representing the fps of a video
|
||||
**/
|
||||
parseFps(fpsStr) {
|
||||
let fps = 30.0;
|
||||
let parts;
|
||||
if (fpsStr.indexOf('/') !== -1) {
|
||||
parts = fpsStr.split('/');
|
||||
fps = parseFloat(parts[0]) / parseFloat(parts[1]);
|
||||
}
|
||||
else {
|
||||
fps = parseFloat(fpsStr);
|
||||
}
|
||||
return fps;
|
||||
}
|
||||
/**
|
||||
* Get info on a video in json format. Use for filmout.
|
||||
*
|
||||
|
@ -49,6 +64,9 @@ class FFPROBE {
|
|||
catch (err) {
|
||||
return raw.stdout;
|
||||
}
|
||||
if (json.format && json.format.duration) {
|
||||
json.seconds = parseFloat(json.format.duration);
|
||||
}
|
||||
if (json && json.streams) {
|
||||
vid = json.streams.find((stream) => {
|
||||
if (stream.width && stream.height)
|
||||
|
@ -58,6 +76,7 @@ class FFPROBE {
|
|||
if (vid) {
|
||||
json.width = vid.width;
|
||||
json.height = vid.height;
|
||||
json.fps = this.parseFps(vid.r_frame_rate);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ffprobe/index.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ffprobe/index.ts"],"names":[],"mappings":"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"}
|
|
@ -67,6 +67,9 @@ class FilmOut {
|
|||
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));
|
||||
this.ffmpeg.onProgress = (obj) => {
|
||||
this.ui.send('pre_export_progress', { progress: obj });
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Create a hash of a string.
|
||||
|
@ -206,6 +209,12 @@ class FilmOut {
|
|||
this.state.frames = frames;
|
||||
this.state.info = info;
|
||||
this.state.hash = this.hash(arg.path);
|
||||
if (info.seconds) {
|
||||
this.state.seconds = info.seconds;
|
||||
}
|
||||
else if (info.fps && frames) {
|
||||
this.state.seconds = frames / info.fps;
|
||||
}
|
||||
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
|
||||
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
|
||||
this.state.enabled = true;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -419,7 +419,7 @@ cmd.camera_to = function (t) {
|
|||
cont = confirm(`Do you want to ${(total > 0 ? 'advance' : 'rewind')} the camera ${total} frame${(total === 1 ? '' : 's')} to frame ${val}?`)
|
||||
if (cont) {
|
||||
gui.overlay(true);
|
||||
gui.spinner(true, `Camera ${(total > 0 ? 'advancing' : 'rewinding')} ${total} frame${(total === 1 ? '' : 's')} `, true, true);
|
||||
gui.spinner(true, `Camera ${(total > 0 ? 'advancing' : 'rewinding')} ${total} frame${(total === 1 ? '' : 's')} `, true, false);
|
||||
seq.exec(steps, Math.abs(total));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ class FilmOut {
|
|||
ipcRenderer.on('system', this.onSystem.bind(this));
|
||||
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
|
||||
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
|
||||
ipcRenderer.on('pre_export_progress', this.onPreExportProgress.bind(this));
|
||||
}
|
||||
onSystem(evt, args) {
|
||||
let option;
|
||||
|
@ -210,9 +211,11 @@ class FilmOut {
|
|||
if (light.disabled) {
|
||||
//light.enable();
|
||||
}
|
||||
console.dir(state);
|
||||
//console.dir(state);
|
||||
this.state.frame = 0;
|
||||
this.state.frames = state.frames;
|
||||
this.state.seconds = state.seconds;
|
||||
this.state.fps = state.fps;
|
||||
this.state.width = state.info.width;
|
||||
this.state.height = state.info.height;
|
||||
this.state.name = state.fileName;
|
||||
|
@ -245,27 +248,38 @@ class FilmOut {
|
|||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let proceed = false;
|
||||
if (this.state.path && this.state.path !== '') {
|
||||
proceed = yield gui.confirm(`Export all frames for ${this.state.name}? This may take a while, but will allow filmout sequences to run faster.`);
|
||||
proceed = yield gui.confirm(`Export all frames of ${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', {});
|
||||
gui.spinner(true, `Exporting frames of ${this.state.name}`, true, false);
|
||||
ipcRenderer.send('pre_export', { state: this.state });
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
if (args.completed && args.completed === true) {
|
||||
gui.notify('FILMOUT', `Exported frames of ${this.state.name}`);
|
||||
log.info(`Exported frames of ${this.state.name}`, 'FILMOUT', true);
|
||||
}
|
||||
else {
|
||||
log.error(args.err);
|
||||
log.info('onPreExport Error');
|
||||
log.error(JSON.stringify(args));
|
||||
}
|
||||
gui.overlay(false);
|
||||
gui.spinner(false);
|
||||
}
|
||||
onPreExportProgress(evt, args) {
|
||||
console.dir(args);
|
||||
const elem = $('.progress-bar');
|
||||
let progress = 0;
|
||||
if (args.progress.progress) {
|
||||
progress = args.progress.progress * 100;
|
||||
}
|
||||
elem.attr('aria-valuenow', progress);
|
||||
elem.css('width', `${progress}%`);
|
||||
gui.spinner(true, `Exporting frames of ${this.state.name} in ${humanizeDuration(Math.round(args.progress.estimated) * 1000)}`, true, true);
|
||||
}
|
||||
advance() {
|
||||
this.state.frame++;
|
||||
if (this.state.frame >= this.state.frames) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -62,6 +62,7 @@ class FilmOut {
|
|||
ipcRenderer.on('system', this.onSystem.bind(this));
|
||||
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
|
||||
ipcRenderer.on('pre_export', this.onPreExport.bind(this));
|
||||
ipcRenderer.on('pre_export_progress', this.onPreExportProgress.bind(this));
|
||||
}
|
||||
onSystem (evt : Event, args : any) {
|
||||
let option : any;
|
||||
|
@ -217,9 +218,11 @@ class FilmOut {
|
|||
if (light.disabled) {
|
||||
//light.enable();
|
||||
}
|
||||
console.dir(state);
|
||||
//console.dir(state);
|
||||
this.state.frame = 0;
|
||||
this.state.frames = state.frames;
|
||||
this.state.seconds = state.seconds;
|
||||
this.state.fps = state.fps;
|
||||
this.state.width = state.info.width;
|
||||
this.state.height = state.info.height;
|
||||
this.state.name = state.fileName;
|
||||
|
@ -253,26 +256,45 @@ class FilmOut {
|
|||
let proceed = false;
|
||||
|
||||
if (this.state.path && this.state.path !== '') {
|
||||
proceed = await gui.confirm(`Export all frames for ${this.state.name}? This may take a while, but will allow filmout sequences to run faster.`);
|
||||
proceed = await gui.confirm(`Export all frames of ${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', { });
|
||||
gui.spinner(true, `Exporting frames of ${this.state.name}`, true, false);
|
||||
ipcRenderer.send('pre_export', { state : this.state });
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (args.completed && args.completed === true) {
|
||||
gui.notify('FILMOUT', `Exported frames of ${this.state.name}`);
|
||||
log.info(`Exported frames of ${this.state.name}`, 'FILMOUT', true);
|
||||
} else {
|
||||
log.error(args.err);
|
||||
log.info('onPreExport Error');
|
||||
log.error(JSON.stringify(args));
|
||||
}
|
||||
|
||||
gui.overlay(false);
|
||||
gui.spinner(false);
|
||||
}
|
||||
|
||||
onPreExportProgress (evt : Event, args : any) {
|
||||
console.dir(args);
|
||||
const elem : any = $('.progress-bar');
|
||||
let progress : number = 0;
|
||||
|
||||
if (args.progress.progress) {
|
||||
progress = args.progress.progress * 100;
|
||||
}
|
||||
|
||||
elem.attr('aria-valuenow', progress);
|
||||
elem.css('width', `${progress}%`);
|
||||
gui.spinner(true, `Exporting frames of ${this.state.name} in ${humanizeDuration(Math.round(args.progress.estimated) * 1000)}`, true, false);
|
||||
|
||||
}
|
||||
|
||||
advance () {
|
||||
this.state.frame++;
|
||||
if (this.state.frame >= this.state.frames) {
|
||||
|
@ -280,6 +302,7 @@ class FilmOut {
|
|||
}
|
||||
$('#filmout_position').val(this.state.frame).trigger('change');
|
||||
}
|
||||
|
||||
rewind () {
|
||||
this.state.frame--;
|
||||
if (this.state.frame < 0) {
|
||||
|
|
|
@ -4,6 +4,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
const path_1 = require("path");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const exec_1 = require("exec");
|
||||
const child_process_1 = require("child_process");
|
||||
async function spawnAsync(bin, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = child_process_1.spawn(bin, args);
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.on('exit', (code) => {
|
||||
if (code === 0) {
|
||||
return resolve({ stdout, stderr });
|
||||
}
|
||||
else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr);
|
||||
}
|
||||
});
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
return child;
|
||||
});
|
||||
}
|
||||
/** @class FFMPEG **/
|
||||
class FFMPEG {
|
||||
/**
|
||||
|
@ -14,6 +39,7 @@ class FFMPEG {
|
|||
**/
|
||||
constructor(sys) {
|
||||
this.id = 'ffmpeg';
|
||||
this.onProgress = () => { };
|
||||
this.bin = sys.deps.ffmpeg;
|
||||
this.convert = sys.deps.convert;
|
||||
this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital');
|
||||
|
@ -42,6 +68,35 @@ class FFMPEG {
|
|||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
* Parse the stderr output of ffmpeg
|
||||
*
|
||||
* @param {string} line Stderr line
|
||||
**/
|
||||
parseStderr(line) {
|
||||
//frame= 6416 fps= 30 q=31.0 size= 10251kB time=00:03:34.32 bitrate= 391.8kbits/s speed= 1x
|
||||
let obj = {};
|
||||
if (line.substring(0, 'frame='.length) === 'frame=') {
|
||||
try {
|
||||
obj.frame = line.split('frame=')[1].split('fps=')[0];
|
||||
obj.frame = parseInt(obj.frame);
|
||||
obj.fps = line.split('fps=')[1].split('q=')[0];
|
||||
obj.fps = parseFloat(obj.fps);
|
||||
obj.time = line.split('time=')[1].split('bitrate=')[0];
|
||||
obj.speed = line.split('speed=')[1].trim().replace('x', '');
|
||||
obj.speed = parseFloat(obj.speed);
|
||||
obj.size = line.split('size=')[1].split('time=')[0].trim();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
console.log(line);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
/**
|
||||
* Render a single frame from a video or image to a png.
|
||||
*
|
||||
|
@ -125,13 +180,26 @@ class FFMPEG {
|
|||
const tmppath = this.TMPDIR;
|
||||
let ext = 'png';
|
||||
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
|
||||
let cmd;
|
||||
let args;
|
||||
let output;
|
||||
let scale = '';
|
||||
let estimated = -1;
|
||||
//cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
args = [
|
||||
'-y',
|
||||
'-i', video
|
||||
];
|
||||
if (w && h) {
|
||||
scale = `scale=${w}:${h}`;
|
||||
args.push('-vf');
|
||||
args.push(`scale=${w}:${h}`);
|
||||
}
|
||||
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
args = args.concat([
|
||||
'-compression_algo', 'raw',
|
||||
'-pix_fmt', 'rgb24',
|
||||
'-crf', '0',
|
||||
tmpoutput
|
||||
]);
|
||||
console.dir(args);
|
||||
console.dir(state);
|
||||
try {
|
||||
await fs_extra_1.mkdir(tmppath);
|
||||
}
|
||||
|
@ -139,15 +207,54 @@ 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);
|
||||
return new Promise((resolve, reject) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
this.log.info(`${this.bin} ${args.join(' ')}`);
|
||||
this.child = child_process_1.spawn(this.bin, args);
|
||||
this.child.on('exit', (code) => {
|
||||
console.log('GOT TO EXIT');
|
||||
if (code === 0) {
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
return resolve(true);
|
||||
}
|
||||
else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr + stdout);
|
||||
}
|
||||
});
|
||||
this.child.stdout.on('data', (data) => {
|
||||
const line = data.toString();
|
||||
stdout += line;
|
||||
});
|
||||
this.child.stderr.on('data', (data) => {
|
||||
const line = data.toString();
|
||||
const obj = this.parseStderr(line);
|
||||
if (obj.frame && state.frames) {
|
||||
obj.progress = obj.frame / state.frames;
|
||||
}
|
||||
if (obj.frame && obj.speed && state.frames && state.info.fps) {
|
||||
//scale by speed
|
||||
obj.remaining = ((state.frames - obj.frame) / state.info.fps) / obj.speed;
|
||||
obj.estimated = state.info.seconds / obj.speed;
|
||||
if (obj.estimated > estimated) {
|
||||
estimated = obj.estimated;
|
||||
}
|
||||
}
|
||||
if (obj.frame) {
|
||||
//log.info(`${input.name} ${obj.frame}/${input.frames} ${Math.round(obj.progress * 1000) / 10}% ${Math.round(obj.remaining)} seconds remaining of ${Math.round(obj.estimated)}`);
|
||||
this.onProgress(obj);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
cancel() {
|
||||
if (this.child) {
|
||||
this.child.kill();
|
||||
this.log.info(`Stopped exporting sequence with ffmpeg`);
|
||||
}
|
||||
catch (err) {
|
||||
this.log.error(err);
|
||||
throw err;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Clears a specific frame from the tmp directory
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,21 @@ class FFPROBE {
|
|||
constructor(sys) {
|
||||
this.bin = sys.deps.ffprobe;
|
||||
}
|
||||
/**
|
||||
* Parse the fps entry into a float representing the fps of a video
|
||||
**/
|
||||
parseFps(fpsStr) {
|
||||
let fps = 30.0;
|
||||
let parts;
|
||||
if (fpsStr.indexOf('/') !== -1) {
|
||||
parts = fpsStr.split('/');
|
||||
fps = parseFloat(parts[0]) / parseFloat(parts[1]);
|
||||
}
|
||||
else {
|
||||
fps = parseFloat(fpsStr);
|
||||
}
|
||||
return fps;
|
||||
}
|
||||
/**
|
||||
* Get info on a video in json format. Use for filmout.
|
||||
*
|
||||
|
@ -49,6 +64,9 @@ class FFPROBE {
|
|||
catch (err) {
|
||||
return raw.stdout;
|
||||
}
|
||||
if (json.format && json.format.duration) {
|
||||
json.seconds = parseFloat(json.format.duration);
|
||||
}
|
||||
if (json && json.streams) {
|
||||
vid = json.streams.find((stream) => {
|
||||
if (stream.width && stream.height)
|
||||
|
@ -58,6 +76,7 @@ class FFPROBE {
|
|||
if (vid) {
|
||||
json.width = vid.width;
|
||||
json.height = vid.height;
|
||||
json.fps = this.parseFps(vid.r_frame_rate);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ffprobe/index.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ffprobe/index.ts"],"names":[],"mappings":"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"}
|
|
@ -67,6 +67,9 @@ class FilmOut {
|
|||
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));
|
||||
this.ffmpeg.onProgress = (obj) => {
|
||||
this.ui.send('pre_export_progress', { progress: obj });
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Create a hash of a string.
|
||||
|
@ -206,6 +209,12 @@ class FilmOut {
|
|||
this.state.frames = frames;
|
||||
this.state.info = info;
|
||||
this.state.hash = this.hash(arg.path);
|
||||
if (info.seconds) {
|
||||
this.state.seconds = info.seconds;
|
||||
}
|
||||
else if (info.fps && frames) {
|
||||
this.state.seconds = frames / info.fps;
|
||||
}
|
||||
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
|
||||
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
|
||||
this.state.enabled = true;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,12 +5,49 @@
|
|||
import { join } from 'path';
|
||||
import { exists, mkdir, readdir, unlink } from 'fs-extra';
|
||||
import { exec } from 'exec';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
interface FilmoutState {
|
||||
frame : number;
|
||||
path : string;
|
||||
hash : string;
|
||||
info : any;
|
||||
frames?: number;
|
||||
}
|
||||
|
||||
interface StdErr {
|
||||
frame : number;
|
||||
fps : number;
|
||||
time : string;
|
||||
speed : number;
|
||||
size : string;
|
||||
remaining? : number;
|
||||
progress? : number;
|
||||
estimated? : number;
|
||||
}
|
||||
|
||||
async function spawnAsync (bin : string, args : string[]) {
|
||||
return new Promise((resolve : Function, reject : Function) => {
|
||||
const child = spawn(bin, args);
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.on('exit', (code : number) => {
|
||||
if (code === 0) {
|
||||
return resolve({ stdout, stderr });
|
||||
} else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr);
|
||||
}
|
||||
});
|
||||
child.stdout.on('data', (data : string) => {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', (data : string) => {
|
||||
stderr += data;
|
||||
});
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
/** @class FFMPEG **/
|
||||
|
@ -21,6 +58,8 @@ class FFMPEG {
|
|||
private log : any;
|
||||
private id : string = 'ffmpeg';
|
||||
private TMPDIR : string;
|
||||
private child : any;
|
||||
public onProgress : Function = () => {};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
|
@ -59,6 +98,37 @@ class FFMPEG {
|
|||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the stderr output of ffmpeg
|
||||
*
|
||||
* @param {string} line Stderr line
|
||||
**/
|
||||
private parseStderr (line : string) : StdErr {
|
||||
//frame= 6416 fps= 30 q=31.0 size= 10251kB time=00:03:34.32 bitrate= 391.8kbits/s speed= 1x
|
||||
let obj : any = {};
|
||||
|
||||
if (line.substring(0, 'frame='.length) === 'frame=') {
|
||||
try {
|
||||
obj.frame = line.split('frame=')[1].split('fps=')[0];
|
||||
obj.frame = parseInt(obj.frame);
|
||||
obj.fps = line.split('fps=')[1].split('q=')[0];
|
||||
obj.fps = parseFloat(obj.fps);
|
||||
obj.time = line.split('time=')[1].split('bitrate=')[0];
|
||||
obj.speed = line.split('speed=')[1].trim().replace('x', '');
|
||||
obj.speed = parseFloat(obj.speed);
|
||||
obj.size = line.split('size=')[1].split('time=')[0].trim();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.log(line);
|
||||
process.exit();
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single frame from a video or image to a png.
|
||||
*
|
||||
|
@ -149,15 +219,31 @@ class FFMPEG {
|
|||
const tmppath : string = this.TMPDIR;
|
||||
let ext : string = 'png';
|
||||
let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`);
|
||||
let cmd : string;
|
||||
let args : string[];
|
||||
let output : any;
|
||||
let scale : string = '';
|
||||
let estimated : number = -1;
|
||||
|
||||
//cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
|
||||
args = [
|
||||
'-y',
|
||||
'-i', video
|
||||
];
|
||||
|
||||
if (w && h) {
|
||||
scale = `scale=${w}:${h}`;
|
||||
args.push('-vf');
|
||||
args.push(`scale=${w}:${h}`);
|
||||
}
|
||||
|
||||
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
|
||||
|
||||
args = args.concat([
|
||||
'-compression_algo', 'raw',
|
||||
'-pix_fmt', 'rgb24',
|
||||
'-crf', '0',
|
||||
tmpoutput
|
||||
]);
|
||||
|
||||
console.dir(args)
|
||||
console.dir(state)
|
||||
|
||||
try {
|
||||
await mkdir(tmppath);
|
||||
|
@ -167,15 +253,61 @@ 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 new Promise((resolve : Function, reject : Function) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
this.log.info(`${this.bin} ${args.join(' ')}`);
|
||||
this.child = spawn(this.bin, args);
|
||||
|
||||
this.child.on('exit', (code : number) => {
|
||||
console.log('GOT TO EXIT');
|
||||
if (code === 0) {
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
return resolve(true);
|
||||
} else {
|
||||
console.error(`Process exited with code: ${code}`);
|
||||
console.error(stderr);
|
||||
return reject(stderr + stdout);
|
||||
}
|
||||
});
|
||||
|
||||
this.child.stdout.on('data', (data : any) => {
|
||||
const line : string = data.toString();
|
||||
stdout += line;
|
||||
});
|
||||
|
||||
this.child.stderr.on('data', (data : any) => {
|
||||
const line : string = data.toString();
|
||||
const obj : StdErr = this.parseStderr(line);
|
||||
|
||||
if (obj.frame && state.frames) {
|
||||
obj.progress = obj.frame / state.frames;
|
||||
}
|
||||
|
||||
if (obj.frame && obj.speed && state.frames && state.info.fps) {
|
||||
//scale by speed
|
||||
obj.remaining = ((state.frames - obj.frame) / state.info.fps) / obj.speed;
|
||||
obj.estimated = state.info.seconds / obj.speed;
|
||||
if (obj.estimated > estimated) {
|
||||
estimated = obj.estimated;
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.frame) {
|
||||
//log.info(`${input.name} ${obj.frame}/${input.frames} ${Math.round(obj.progress * 1000) / 10}% ${Math.round(obj.remaining)} seconds remaining of ${Math.round(obj.estimated)}`);
|
||||
this.onProgress(obj);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public cancel () {
|
||||
if (this.child) {
|
||||
this.child.kill();
|
||||
this.log.info(`Stopped exporting sequence with ffmpeg`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,21 @@ class FFPROBE {
|
|||
constructor (sys : any) {
|
||||
this.bin = sys.deps.ffprobe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the fps entry into a float representing the fps of a video
|
||||
**/
|
||||
private parseFps (fpsStr : string) {
|
||||
let fps : number = 30.0;
|
||||
let parts : string[];
|
||||
if (fpsStr.indexOf('/') !== -1) {
|
||||
parts = fpsStr.split('/');
|
||||
fps = parseFloat(parts[0]) / parseFloat(parts[1]);
|
||||
} else {
|
||||
fps = parseFloat(fpsStr);
|
||||
}
|
||||
return fps
|
||||
}
|
||||
/**
|
||||
* Get info on a video in json format. Use for filmout.
|
||||
*
|
||||
|
@ -54,6 +69,10 @@ class FFPROBE {
|
|||
return raw.stdout;
|
||||
}
|
||||
|
||||
if (json.format && json.format.duration) {
|
||||
json.seconds = parseFloat(json.format.duration);
|
||||
}
|
||||
|
||||
if (json && json.streams) {
|
||||
vid = json.streams.find((stream : any) => {
|
||||
if (stream.width && stream.height) return stream;
|
||||
|
@ -63,6 +82,7 @@ class FFPROBE {
|
|||
if (vid) {
|
||||
json.width = vid.width;
|
||||
json.height = vid.height;
|
||||
json.fps = this.parseFps(vid.r_frame_rate)
|
||||
}
|
||||
|
||||
return json;
|
||||
|
|
|
@ -73,7 +73,12 @@ 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));
|
||||
this.ipc.on('pre_export', this.onPreExport.bind(this));
|
||||
|
||||
this.ffmpeg.onProgress = (obj : any) => {
|
||||
this.ui.send('pre_export_progress', { progress: obj });
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Create a hash of a string.
|
||||
|
@ -211,6 +216,12 @@ class FilmOut {
|
|||
this.state.info = info;
|
||||
this.state.hash = this.hash(arg.path);
|
||||
|
||||
if (info.seconds) {
|
||||
this.state.seconds = info.seconds;
|
||||
} else if (info.fps && frames) {
|
||||
this.state.seconds = frames / info.fps;
|
||||
}
|
||||
|
||||
this.log.info(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
|
||||
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
|
||||
this.state.enabled = true;
|
||||
|
|
Loading…
Reference in New Issue