UNPKG

ffcreatorlite

Version:

FFCreatorLite is a lightweight and flexible short video production library

344 lines (296 loc) 7.87 kB
'use strict'; /** * FFNode Class - FFCreatorLite displays the basic class of the object, * Other display objects need to inherit from this class. * * ####Example: * * const node = new FFNode({ x: 10, y: 20 }); * * @class */ const forEach = require('lodash/forEach'); const FFBase = require('../core/base'); const Utils = require('../utils/utils'); const FilterUtil = require('../utils/filter'); const FFAnimations = require('../animate/animations'); class FFNode extends FFBase { constructor(conf = {}) { super({ type: 'node', ...conf }); const { x = 0, y = 0, scale = 1, rotate = 0, animations = [], w, h, props } = this.conf; this.index = 0; this.fIndex = 0; this.duration = 0; this.appearTime = 0; this.filters = []; this.preFilters = []; this.customFilters = []; this.parent = null; this.hasInput = false; this.setXY(x, y); this.setWH(w, h); this.setPorps(props); this.setScale(scale); this.setRotate(rotate); this.animations = new FFAnimations(animations); this.animations.setTarget(this); } /** * Get the vid in the ffmpeg filter * @param {boolean} k - Whether to include outer brackets * @return {string} vid * @public */ getFId(k = false) { const vid = `${this.index}:v`; return k ? `[${vid}]` : `${vid}`; } /** * Get the input id in the ffmpeg filter * @param {boolean} k - Whether to include outer brackets * @return {string} input id * @public */ getInId(k = false) { if (this.fIndex === 0) { return this.getFId(k); } else { return this.getOutId(k); } } /** * Get the output id in the ffmpeg filter * @param {boolean} k - Whether to include outer brackets * @return {string} output id * @public */ getOutId(k = false) { const id = `${this.id}-${this.fIndex}`; return k ? `[${id}]` : `${id}`; } /** * Generate new output id * @public */ genNewOutId() { this.fIndex++; } /** * Set display object scale * @param {number} scale * @public */ setScale(scale = 1) { this.scale = scale; } /** * Set display object rotate * @param {number} rotate * @public */ setRotate(rotate = 0) { this.rotate = rotate; } setAppearTime(appearTime) { this.appearTime = appearTime; } /** * Set display object width and height * @param {number} width - object width * @param {number} height - object height * @public */ setWH(w, h) { this.setSize(w, h); } /** * Set display object width and height * @param {number} width - object width * @param {number} height - object height * @public */ setSize(w, h) { if (w === undefined) return; this.w = w; this.h = h; } /** * Get display object width and height * @return {string} 1000*120 * @public */ getSize(dot = '*') { return `${this.w}${dot}${this.h}`; } /** * Set the duration of node in the scene * @param {number} duration * @public */ setDuration(duration) { this.duration = duration; } /** * Set display object x,y position * @param {number} x - x position * @param {number} y - y position * @public */ setXY(x = 0, y = 0) { this.x = x; this.y = y; } setPorps(a, b) { if (b === undefined) { this.props = a; } else { this[a] = b; } } /** * Set display object x,y position from style object * @param {object} style - css style object * @public */ setXYFromStyle(style) { const x = parseInt(style.left); const y = parseInt(style.top); return this.setXY(x, y); } /** * Add one/multiple animations or effects * @public */ setAnimations(animations) { this.animations.setAnimations(animations); } /** * Add special animation effects * @param {string} type - animation effects name * @param {number} time - time of animation * @param {number} delay - delay of animation * @public */ addEffect(type, time, delay) { this.animations.addEffect(type, time, delay); } addAnimate(animation) { return this.animations.addAnimate(animation); } /** * concatFilters - Core algorithm: processed into ffmpeg filter syntax * 1. add preset filters -> pre filter * 2. scale+rotate -> pre filter * 3. other filters * 4. fade/zoompan * 5. x/y -> last overlay * * @param {object} context - context * @private */ concatFilters(context) { // 1. recorrect position this.animations.replaceEffectConfVal(); this.recorrectPosition(); // 2. add preset filters this.filters = this.preFilters.concat(this.filters); // 3. add scale rotate filters const srFilter = FilterUtil.assembleSRFilter({ scale: this.scale, rotate: this.rotate }); if (srFilter) this.filters.push(srFilter); // 4. add others custom filters this.filters = this.filters.concat(this.customFilters); // 5. add animations filters this.appearTime = this.appearTime || this.animations.getAppearTime(); // Because overlay enable is used, remove this // this.animations.modifyDelayTime(this.appearTime); const aniFilters = this.animations.concatFilters(); this.filters = this.filters.concat(aniFilters); // 6. set overlay filter x/y if (!FilterUtil.getOverlayFromFilters(this.filters)) { const xyFilter = FilterUtil.createOverlayFilter(this.x, this.y); this.filters.push(xyFilter); } // 7. add appearTime setpts // Because overlay enable is used, remove this // this.filters.push(FilterUtil.createSetptsFilter(this.appearTime)); // 8. add this duration time this.addDurationToOverlay(); // 9. add inputs and outputs this.addInputsAndOutputs(context); return this.filters; } recorrectPosition() { if (this.animations.hasAnimate('rotate')) { const w = this.w; const h = this.h; const diagonal = Math.sqrt(w * w + h * h); this.x += Utils.floor((w - diagonal) / 2, 0); this.y += Utils.floor((h - diagonal) / 2, 0); } else if (this.animations.hasZoompanPad()) { //const scale = this.animations.getMaxScale(); // this.x -= ((scale - 1) * this.w) / 2; // this.y -= ((scale - 1) * this.h) / 2; } } /** * Add Duration interval time to filter * @private */ addDurationToOverlay() { this.appearTime = this.appearTime || this.animations.getAppearTime(); this.duration = this.duration || this.animations.getDuration(); FilterUtil.addDurationToOverlay({ filters: this.filters, appearTime: this.appearTime, duration: this.duration, }); } /** * Add input param and output param to filter * @private */ addInputsAndOutputs(context) { if (!this.filters.length) return; forEach(this.filters, (filter, index) => { const inputs = this.getInId(); this.genNewOutId(); const outputs = this.getOutId(); this.filters[index] = FilterUtil.setInputsAndOutputs({ filter, inputs, outputs, contextInputs: context.input, }); }); // 5. set context input context.input = this.getOutId(); } /** * other methods * @private */ addFilter(filter) { this.customFilters.push(filter); } addPreFilter(filter) { this.preFilters.push(filter); } addInput(command) { //command.addInput(this.conf.path); } addOutput(command) { //command.addInput(this.conf.path); } addOptions() {} addBlend(blend) { this.addPreFilter(`blend=all_expr='${blend}'`); } addTBlend(blend, mode = 'all_mode') { this.addPreFilter(`tblend=${mode}=${blend}`); } isReady() { return new Promise(resolve => resolve()); } toFilter() {} } module.exports = FFNode;