Display export progress and time estimate while all frames are being exported

This commit is contained in:
sixteenmillimeter 2020-03-09 15:46:06 -04:00
parent 891c34ad88
commit 1c940d6df7
19 changed files with 534 additions and 64 deletions

View File

@ -4,6 +4,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec"); const exec_1 = require("exec");
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 **/
class FFMPEG { class FFMPEG {
/** /**
@ -14,6 +39,7 @@ class FFMPEG {
**/ **/
constructor(sys) { constructor(sys) {
this.id = 'ffmpeg'; this.id = 'ffmpeg';
this.onProgress = () => { };
this.bin = sys.deps.ffmpeg; this.bin = sys.deps.ffmpeg;
this.convert = sys.deps.convert; this.convert = sys.deps.convert;
this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital'); this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital');
@ -42,6 +68,35 @@ class FFMPEG {
} }
return str; 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. * Render a single frame from a video or image to a png.
* *
@ -125,13 +180,26 @@ class FFMPEG {
const tmppath = this.TMPDIR; const tmppath = this.TMPDIR;
let ext = 'png'; let ext = 'png';
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`); let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd; let args;
let output; 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) { 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 { try {
await fs_extra_1.mkdir(tmppath); await fs_extra_1.mkdir(tmppath);
} }
@ -139,15 +207,54 @@ class FFMPEG {
this.log.error(err); this.log.error(err);
} }
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try { return new Promise((resolve, reject) => {
this.log.info(cmd); let stdout = '';
output = await exec_1.exec(cmd); 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);
} }
catch (err) { else {
this.log.error(err); console.error(`Process exited with code: ${code}`);
throw err; 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`);
} }
return true;
} }
/** /**
* Clears a specific frame from the tmp directory * Clears a specific frame from the tmp directory

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,21 @@ class FFPROBE {
constructor(sys) { constructor(sys) {
this.bin = sys.deps.ffprobe; 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. * Get info on a video in json format. Use for filmout.
* *
@ -49,6 +64,9 @@ class FFPROBE {
catch (err) { catch (err) {
return raw.stdout; return raw.stdout;
} }
if (json.format && json.format.duration) {
json.seconds = parseFloat(json.format.duration);
}
if (json && json.streams) { if (json && json.streams) {
vid = json.streams.find((stream) => { vid = json.streams.find((stream) => {
if (stream.width && stream.height) if (stream.width && stream.height)
@ -58,6 +76,7 @@ class FFPROBE {
if (vid) { if (vid) {
json.width = vid.width; json.width = vid.width;
json.height = vid.height; json.height = vid.height;
json.fps = this.parseFps(vid.r_frame_rate);
} }
return json; return json;
} }

View File

@ -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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"}

View File

@ -67,6 +67,9 @@ class FilmOut {
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this)); 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. * Create a hash of a string.
@ -206,6 +209,12 @@ class FilmOut {
this.state.frames = frames; this.state.frames = frames;
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path); 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(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true; this.state.enabled = true;

File diff suppressed because one or more lines are too long

View File

@ -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}?`) cont = confirm(`Do you want to ${(total > 0 ? 'advance' : 'rewind')} the camera ${total} frame${(total === 1 ? '' : 's')} to frame ${val}?`)
if (cont) { if (cont) {
gui.overlay(true); 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)); seq.exec(steps, Math.abs(total));
} }
} }

View File

@ -63,6 +63,7 @@ class FilmOut {
ipcRenderer.on('system', this.onSystem.bind(this)); ipcRenderer.on('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this)); ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this)); ipcRenderer.on('pre_export', this.onPreExport.bind(this));
ipcRenderer.on('pre_export_progress', this.onPreExportProgress.bind(this));
} }
onSystem(evt, args) { onSystem(evt, args) {
let option; let option;
@ -210,9 +211,11 @@ class FilmOut {
if (light.disabled) { if (light.disabled) {
//light.enable(); //light.enable();
} }
console.dir(state); //console.dir(state);
this.state.frame = 0; this.state.frame = 0;
this.state.frames = state.frames; this.state.frames = state.frames;
this.state.seconds = state.seconds;
this.state.fps = state.fps;
this.state.width = state.info.width; this.state.width = state.info.width;
this.state.height = state.info.height; this.state.height = state.info.height;
this.state.name = state.fileName; this.state.name = state.fileName;
@ -245,27 +248,38 @@ class FilmOut {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let proceed = false; let proceed = false;
if (this.state.path && this.state.path !== '') { 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) { if (proceed) {
gui.overlay(true); gui.overlay(true);
gui.spinner(true, `Exporting frames for ${this.state.name}`); gui.spinner(true, `Exporting frames of ${this.state.name}`, true, false);
ipcRenderer.send('pre_export', {}); ipcRenderer.send('pre_export', { state: this.state });
} }
}); });
} }
onPreExport(evt, args) { onPreExport(evt, args) {
log.info('onPreExport'); if (args.completed && args.completed === true) {
if (args.completed) { gui.notify('FILMOUT', `Exported frames of ${this.state.name}`);
gui.notify('FILMOUT', `Exported frames for ${this.state.name}`); log.info(`Exported frames of ${this.state.name}`, 'FILMOUT', true);
log.info(`Exported frames for ${this.state.name}`, 'FILMOUT', true);
} }
else { else {
log.error(args.err); log.info('onPreExport Error');
log.error(JSON.stringify(args));
} }
gui.overlay(false); gui.overlay(false);
gui.spinner(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() { advance() {
this.state.frame++; this.state.frame++;
if (this.state.frame >= this.state.frames) { if (this.state.frame >= this.state.frames) {

File diff suppressed because one or more lines are too long

View File

@ -62,6 +62,7 @@ class FilmOut {
ipcRenderer.on('system', this.onSystem.bind(this)); ipcRenderer.on('system', this.onSystem.bind(this));
ipcRenderer.on('preview_frame', this.onFrame.bind(this)); ipcRenderer.on('preview_frame', this.onFrame.bind(this));
ipcRenderer.on('pre_export', this.onPreExport.bind(this)); ipcRenderer.on('pre_export', this.onPreExport.bind(this));
ipcRenderer.on('pre_export_progress', this.onPreExportProgress.bind(this));
} }
onSystem (evt : Event, args : any) { onSystem (evt : Event, args : any) {
let option : any; let option : any;
@ -217,9 +218,11 @@ class FilmOut {
if (light.disabled) { if (light.disabled) {
//light.enable(); //light.enable();
} }
console.dir(state); //console.dir(state);
this.state.frame = 0; this.state.frame = 0;
this.state.frames = state.frames; this.state.frames = state.frames;
this.state.seconds = state.seconds;
this.state.fps = state.fps;
this.state.width = state.info.width; this.state.width = state.info.width;
this.state.height = state.info.height; this.state.height = state.info.height;
this.state.name = state.fileName; this.state.name = state.fileName;
@ -253,26 +256,45 @@ class FilmOut {
let proceed = false; let proceed = false;
if (this.state.path && this.state.path !== '') { 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) { if (proceed) {
gui.overlay(true); gui.overlay(true);
gui.spinner(true, `Exporting frames for ${this.state.name}`); gui.spinner(true, `Exporting frames of ${this.state.name}`, true, false);
ipcRenderer.send('pre_export', { }); ipcRenderer.send('pre_export', { state : this.state });
} }
} }
onPreExport (evt : Event, args : any) { onPreExport (evt : Event, args : any) {
log.info('onPreExport');
if (args.completed) { if (args.completed && args.completed === true) {
gui.notify('FILMOUT', `Exported frames for ${this.state.name}`); gui.notify('FILMOUT', `Exported frames of ${this.state.name}`);
log.info(`Exported frames for ${this.state.name}`, 'FILMOUT', true); log.info(`Exported frames of ${this.state.name}`, 'FILMOUT', true);
} else { } else {
log.error(args.err); log.info('onPreExport Error');
log.error(JSON.stringify(args));
} }
gui.overlay(false); gui.overlay(false);
gui.spinner(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 () { advance () {
this.state.frame++; this.state.frame++;
if (this.state.frame >= this.state.frames) { if (this.state.frame >= this.state.frames) {
@ -280,6 +302,7 @@ class FilmOut {
} }
$('#filmout_position').val(this.state.frame).trigger('change'); $('#filmout_position').val(this.state.frame).trigger('change');
} }
rewind () { rewind () {
this.state.frame--; this.state.frame--;
if (this.state.frame < 0) { if (this.state.frame < 0) {

View File

@ -4,6 +4,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path"); const path_1 = require("path");
const fs_extra_1 = require("fs-extra"); const fs_extra_1 = require("fs-extra");
const exec_1 = require("exec"); const exec_1 = require("exec");
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 **/
class FFMPEG { class FFMPEG {
/** /**
@ -14,6 +39,7 @@ class FFMPEG {
**/ **/
constructor(sys) { constructor(sys) {
this.id = 'ffmpeg'; this.id = 'ffmpeg';
this.onProgress = () => { };
this.bin = sys.deps.ffmpeg; this.bin = sys.deps.ffmpeg;
this.convert = sys.deps.convert; this.convert = sys.deps.convert;
this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital'); this.TMPDIR = path_1.join(sys.tmp, 'mcopy_digital');
@ -42,6 +68,35 @@ class FFMPEG {
} }
return str; 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. * Render a single frame from a video or image to a png.
* *
@ -125,13 +180,26 @@ class FFMPEG {
const tmppath = this.TMPDIR; const tmppath = this.TMPDIR;
let ext = 'png'; let ext = 'png';
let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`); let tmpoutput = path_1.join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd; let args;
let output; 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) { 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 { try {
await fs_extra_1.mkdir(tmppath); await fs_extra_1.mkdir(tmppath);
} }
@ -139,15 +207,54 @@ class FFMPEG {
this.log.error(err); this.log.error(err);
} }
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try { return new Promise((resolve, reject) => {
this.log.info(cmd); let stdout = '';
output = await exec_1.exec(cmd); 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);
} }
catch (err) { else {
this.log.error(err); console.error(`Process exited with code: ${code}`);
throw err; 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`);
} }
return true;
} }
/** /**
* Clears a specific frame from the tmp directory * Clears a specific frame from the tmp directory

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,21 @@ class FFPROBE {
constructor(sys) { constructor(sys) {
this.bin = sys.deps.ffprobe; 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. * Get info on a video in json format. Use for filmout.
* *
@ -49,6 +64,9 @@ class FFPROBE {
catch (err) { catch (err) {
return raw.stdout; return raw.stdout;
} }
if (json.format && json.format.duration) {
json.seconds = parseFloat(json.format.duration);
}
if (json && json.streams) { if (json && json.streams) {
vid = json.streams.find((stream) => { vid = json.streams.find((stream) => {
if (stream.width && stream.height) if (stream.width && stream.height)
@ -58,6 +76,7 @@ class FFPROBE {
if (vid) { if (vid) {
json.width = vid.width; json.width = vid.width;
json.height = vid.height; json.height = vid.height;
json.fps = this.parseFps(vid.r_frame_rate);
} }
return json; return json;
} }

View File

@ -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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"}

View File

@ -67,6 +67,9 @@ class FilmOut {
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this)); 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. * Create a hash of a string.
@ -206,6 +209,12 @@ class FilmOut {
this.state.frames = frames; this.state.frames = frames;
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path); 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(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true; this.state.enabled = true;

File diff suppressed because one or more lines are too long

View File

@ -5,12 +5,49 @@
import { join } from 'path'; import { join } from 'path';
import { exists, mkdir, readdir, unlink } from 'fs-extra'; import { exists, mkdir, readdir, unlink } from 'fs-extra';
import { exec } from 'exec'; import { exec } from 'exec';
import { spawn } from 'child_process';
interface FilmoutState { interface FilmoutState {
frame : number; frame : number;
path : string; path : string;
hash : string; hash : string;
info : any; 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 **/ /** @class FFMPEG **/
@ -21,6 +58,8 @@ class FFMPEG {
private log : any; private log : any;
private id : string = 'ffmpeg'; private id : string = 'ffmpeg';
private TMPDIR : string; private TMPDIR : string;
private child : any;
public onProgress : Function = () => {};
/** /**
* @constructor * @constructor
@ -59,6 +98,37 @@ class FFMPEG {
return str; 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. * Render a single frame from a video or image to a png.
* *
@ -149,15 +219,31 @@ class FFMPEG {
const tmppath : string = this.TMPDIR; const tmppath : string = this.TMPDIR;
let ext : string = 'png'; let ext : string = 'png';
let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`); let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd : string; let args : string[];
let output : any; 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) { 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 { try {
await mkdir(tmppath); await mkdir(tmppath);
@ -167,15 +253,61 @@ class FFMPEG {
//ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}" //ffmpeg -i "${video}" -compression_algo raw -pix_fmt rgb24 "${tmpoutput}"
try { return new Promise((resolve : Function, reject : Function) => {
this.log.info(cmd); let stdout = '';
output = await exec(cmd); let stderr = '';
} catch (err) {
this.log.error(err); this.log.info(`${this.bin} ${args.join(' ')}`);
throw err; 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;
} }
return true; 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`);
}
} }
/** /**

View File

@ -14,6 +14,21 @@ class FFPROBE {
constructor (sys : any) { constructor (sys : any) {
this.bin = sys.deps.ffprobe; 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. * Get info on a video in json format. Use for filmout.
* *
@ -54,6 +69,10 @@ class FFPROBE {
return raw.stdout; return raw.stdout;
} }
if (json.format && json.format.duration) {
json.seconds = parseFloat(json.format.duration);
}
if (json && json.streams) { if (json && json.streams) {
vid = json.streams.find((stream : any) => { vid = json.streams.find((stream : any) => {
if (stream.width && stream.height) return stream; if (stream.width && stream.height) return stream;
@ -63,6 +82,7 @@ class FFPROBE {
if (vid) { if (vid) {
json.width = vid.width; json.width = vid.width;
json.height = vid.height; json.height = vid.height;
json.fps = this.parseFps(vid.r_frame_rate)
} }
return json; return json;

View File

@ -74,6 +74,11 @@ class FilmOut {
this.ipc.on('preview_frame', this.previewFrame.bind(this)); this.ipc.on('preview_frame', this.previewFrame.bind(this));
this.ipc.on('display', this.onDisplay.bind(this)); this.ipc.on('display', this.onDisplay.bind(this));
this.ipc.on('pre_export', this.onPreExport.bind(this)); 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. * Create a hash of a string.
@ -211,6 +216,12 @@ class FilmOut {
this.state.info = info; this.state.info = info;
this.state.hash = this.hash(arg.path); 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(`Opened ${this.state.fileName}`, 'FILMOUT', true, true);
this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true); this.log.info(`Frames : ${frames}`, 'FILMOUT', true, true);
this.state.enabled = true; this.state.enabled = true;