@deepauto/ffcreatorlite
Version:
FFCreatorLite is a lightweight and flexible short video production library
281 lines (246 loc) • 6.45 kB
JavaScript
'use strict';
/**
* FFCreator - FFCreatorLite main class, a container contains multiple scenes and pictures, etc.
* Can be used alone, more often combined with FFCreatorCenter.
*
* ####Example:
*
* const creator = new FFCreator({ cacheDir, outputDir, width: 800, height: 640, audio });
* creator.addChild(scene);
* creator.output(output);
* creator.openLog();
* creator.start();
*
*
* ####Note:
* The library depends on `ffmpeg` and `webgl` (linux server uses headless-webgl).
*
* @class
*/
const forEach = require('lodash.foreach');
const FFCon = require('./node/cons');
const Conf = require('./conf/conf');
const Utils = require('./utils/utils');
const Renderer = require('./core/renderer');
const FFmpegUtil = require('./utils/ffmpeg');
const FFAudio = require('./node/audio');
class FFCreator extends FFCon {
constructor(conf = {}) {
super({ type: 'creator', ...conf });
this.conf = new Conf(conf);
const queue = this.getConf('queue');
this.renderer = new Renderer({ queue, creator: this });
// this.closeLog();
this.scenes = [];
this.taskId = null;
this.inCenter = false;
}
/**
* Set the fps of the composite video.
* @param {number} fps - the fps of the composite video
* @public
*/
setFps(fps) {
this.setConf('fps', fps);
}
/**
* Set configuration.
* @param {string} key - the config key
* @param {any} val - the config val
* @public
*/
setConf(key, val) {
this.conf.setVal(key, val);
}
/**
* Get configuration.
* @param {string} key - the config key
* @return {any} the config val
* @public
*/
getConf(key) {
return this.conf.getVal(key);
}
/**
* Set the stage size of the scene
* @param {number} width - stage width
* @param {number} height - stage height
* @public
*/
setSize(w, h) {
this.setConf('w', w);
this.setConf('h', h);
}
/**
* Add the background music of the scene
* @param {object} args - music conf object
* @public
*/
addAudio(args) {
if (!args) return;
args = typeof args === 'string' ? { path: args } : args;
if (args.loop === undefined) args.loop = true;
this.audios.push(new FFAudio(args));
}
/**
* Get the duration of the previous scene
* @private
*/
getPrevScenesDuration(index) {
let duration = 0;
const scenes = this.children;
for (let i = 0; i <= index; i++) {
const scene = scenes[i];
if (scene) duration += scene.getNormalDuration();
}
return duration;
}
/**
* Combine all sounds audio into an array
* @private
*/
concatAudios() {
let audios = [].concat(this.audios);
const scenes = this.scenes;
forEach(scenes, (scene, index) => {
forEach(scene.audios, audio => {
if (index > 0) {
const duration = this.getPrevScenesDuration(index - 1);
audio.start = duration + audio.start;
}
if (!audio.hasSSTO()) {
audio.setSs(0);
audio.setTo(scene.duration);
}
});
audios = audios.concat(scene.audios);
});
return audios;
}
/**
* Get the duration of the scenes
* @private
*/
getScenesDuration() {
let duration = 0;
for (let i = 0; i < this.scenes.length; i++) {
const scene = this.scenes[i];
duration += scene.getNormalDuration();
}
return duration;
}
/**
* Set the video output path
* @param {string} output - the video output path
* @public
*/
setOutput(output) {
this.setConf('output', output);
}
/**
* Get Current ffmpeg version
* @return {string} current ffmpeg version
* @public
*/
async getFFmpegVersion() {
return await FFmpegUtil.getVersion();
}
getOutput() {
return this.getConf('output');
}
/**
* Open logger switch
* @public
*/
openLog() {
this.setConf('log', true);
}
/**
* Close logger switch
* @public
*/
closeLog() {
this.setConf('log', false);
}
addChild(child) {
this.scenes.push(child);
super.addChild(child);
}
async start() {
await Utils.sleep(20);
this.emit('start');
const { renderer } = this;
renderer.on('error', event => {
this.emitsClone('error', event);
this.deleteAllCacheFile();
});
renderer.on('progress', event => {
this.emitsClone('progress', event);
});
renderer.on('all-complete', event => {
if (this.inCenter) {
event.taskId = this.taskId;
}
event.creator = this.id;
this.emitsClone('complete', event);
this.deleteAllCacheFile();
});
renderer.start();
}
getTotalFrames() {
let frames = 0;
forEach(this.scenes, scene => (frames += scene.getTotalFrames()));
return frames;
}
/**
* Create output path, only used when using FFCreatorCenter.
* @public
*/
generateOutput() {
const upStreaming = this.getConf('upStreaming');
let outputDir = this.getConf('outputDir');
if (this.inCenter && outputDir && !upStreaming) {
// YYYYMMDDHHmmss
const output = `${outputDir}/${Utils.getFormatTime()}.mp4`;
this.setConf('output', output);
}
}
/**
* Get the video output path
* @return {string} output - the video output path
* @public
*/
getFile() {
return this.getConf('output');
}
/**
* delete All Cache File
* @private
*/
deleteAllCacheFile() {
if(this.getConf('debug')) return;
forEach(this.scenes, scene => scene.deleteCacheFile());
}
destroy() {
super.destroy();
this.renderer.destroy();
forEach(this.scenes, scene => scene.destroy());
}
/**
* Set the installation path of the current server ffmpeg.
* @param {string} path - installation path of the current server ffmpeg
* @public
*/
static setFFmpegPath(path) {
FFmpegUtil.setFFmpegPath(path);
}
/**
* Set the installation path of the current server ffprobe.
* @param {string} path - installation path of the current server ffprobe
* @public
*/
static setFFprobePath(path) {
FFmpegUtil.setFFprobePath(path);
}
}
module.exports = FFCreator;