mcopy/src/ffmpeg/index.ts

274 lines
6.1 KiB
TypeScript
Raw Normal View History

'use strict';
/** @module ffmpeg **/
import { join } from 'path';
import { exists, mkdir, readdir, unlink } from 'fs-extra';
import { exec } from 'exec';
interface FilmoutState {
frame : number;
path : string;
hash : string;
info : any;
}
/** @class FFMPEG **/
class FFMPEG {
private bin : string;
2020-01-20 06:15:20 +00:00
private convert : string;
private log : any;
private id : string = 'ffmpeg';
private TMPDIR : string;
/**
* @constructor
* Creates an ffmpeg class
*
* @param {object} sys System object to be used to get temp directory
**/
constructor (sys : any) {
this.bin = sys.deps.ffmpeg;
2020-01-20 06:15:20 +00:00
this.convert = sys.deps.convert;
this.TMPDIR = join(sys.tmp, 'mcopy_digital');
this.init();
}
/**
* Async method to call async functions from constructor
**/
async init () {
const Log = require('log');
this.log = await Log({ label : this.id });
await this.checkDir();
}
/**
* Add padding to a number to 5 places. Return a string.
*
* @param {integer} i Integer to pad
*
* @returns {string} Padded string
**/
private padded_frame (i : number) {
let len = (i + '').length;
let str = i + '';
for (let x = 0; x < 8 - len; x++) {
str = '0' + str;
}
return str;
}
/**
* Render a single frame from a video or image to a png.
*
* @param {object} state State object containing file data
* @param {object} light Object containing color information for frame
*
* @returns {string} Path of frame
**/
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 = 'png';
//let rgb : any[] = light.color;
let tmpoutput : string;
let cmd : string;
let output : any;
//let cmd2 : string;
//let output2 : any;
let fileExists = false;
let scale : string = '';
if (w && h) {
scale = `,scale=${w}:${h}`;
}
tmpoutput = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await exists(tmpoutput);
} catch (err) {
//
}
if (fileExists) {
this.log.info(`File ${tmpoutput} exists`);
return tmpoutput;
}
//rgb = rgb.map((e : string) => {
// return parseInt(e);
//});
//
cmd = `${this.bin} -y -i "${video}" -vf "select='gte(n\\,${frameNum})'${scale}" -vframes 1 -compression_algo raw -pix_fmt rgb24 -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"
try {
this.log.info(cmd);
output = await exec(cmd);
} catch (err) {
this.log.error(err);
}
if (output && output.stdout) this.log.info(`"${output.stdout}"`);
/*if (this.convert && (rgb[0] !== 255 || rgb[1] !== 255 || rgb[2] !== 255)) {
try {
this.log.info(cmd2);
output2 = await exec(cmd2);
} catch (err) {
this.log.error(err);
}
}
if (output2 && output2.stdout) this.log.info(`"${output2.stdout}"`);*/
return tmpoutput;
}
/**
* Render all frames in a video to the temp directory.
* Not in use.
*
* @param {string} video Path to video
* @param {object} obj Not sure
*
* @returns {?}
**/
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 = 'png';
let tmpoutput : string = join(tmppath, `${state.hash}-export-%08d.${ext}`);
let cmd : string;
let output : any;
let scale : string = '';
if (w && h) {
scale = `scale=${w}:${h}`;
}
cmd = `${this.bin} -y -i "${video}" -vf "${scale}" -compression_algo raw -pix_fmt rgb24 -crf 0 "${tmpoutput}"`;
try {
await mkdir(tmppath);
} catch (err) {
this.log.error(err);
}
//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;
}
/**
* Clears a specific frame from the tmp directory
*
* @param {integer} frame Integer of frame to clear
*
* @returns {boolean} True if successful, false if not
**/
public async clear (state : any) {
const padded : string = this.padded_frame(state.frame);
let ext : string = 'png';
let tmppath : string;
let fileExists : boolean;
tmppath = join(this.TMPDIR, `${state.hash}-export-${padded}.${ext}`);
try {
fileExists = await exists(tmppath);
} catch (err) {
this.log.error(err);
}
if (!fileExists) return false;
try {
await unlink(tmppath);
this.log.info(`Cleared frame ${tmppath}`);
} catch (err) {
this.log.error(err);
}
return true;
}
/**
* Deletes all frames in temp directory.
*
**/
public async clearAll () {
const tmppath : string = this.TMPDIR;
let files : any;
try {
files = await readdir(tmppath);
} 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 {
await unlink(join(tmppath, file));
} catch (err) {
this.log.error(err);
}
});
}
}
/**
* Checks if mcopy temp directory exists. If it doesn't,
* creates it.
**/
private async checkDir () {
let fileExists : boolean;
try {
fileExists = await exists(this.TMPDIR);
} catch (err) {
this.log.error('Error checking for tmp dir', err);
}
if (!fileExists) {
try {
await mkdir(this.TMPDIR);
this.log.info(`Created tmpdir ${this.TMPDIR}`);
} catch (err) {
this.log.error('Error creating tmp dir', err);
}
}
try {
await this.clearAll();
} catch (err) {
this.log.error(err);
}
}
}
module.exports = (sys : any) => {
return new FFMPEG(sys);
}