UNPKG

ffcreator

Version:

FFCreator is a lightweight and flexible short video production library

203 lines (174 loc) 4.63 kB
'use strict'; /** * Timeline - A timeline class to manage scenes and animations * * ####Example: * * const timeline = new Timeline(60); * timeline.annotate(scenes); * timeline.nextFrame(); * * @class */ const FrameData = require('./frame'); const forEach = require('lodash/forEach'); const TimelineUpdate = require('./update'); class Timeline { constructor(fps) { this.fps = fps; this.frame = 0; this.duration = 0; this.framesNum = 0; this.frameData = new FrameData(); this.segments = []; this.scenes = []; } /** * Marking the frameData of the scenes. * Split scene into sub-frameData, new timeline frameData stitching method * |--------|===| (s1) * |---|-----|===| (s2) * |---|-------| (s3) * * @param {array} ffscenes - An array of scenes * @public */ annotate(ffscenes) { const { segments, scenes } = this; let point = 0; let preTrans = 0; forEach(ffscenes, (scene, index) => { const isFirst = index === 0; const isLast = index === ffscenes.length - 1; const total = scene.getFramesNum(); const trans = isLast ? 0 : scene.getTransFramesNum(); // normal single scene const norNum = total - trans - preTrans; const segment1 = { type: 'normal', start: point, end: point + norNum, total: norNum, scenesIndex: [index], }; segments.push(segment1); point = segment1.end; // scene of transition animation if (trans) { const segment2 = { type: 'transition', start: point, end: point + trans, total: trans, scenesIndex: [index, index + 1], }; segments.push(segment2); point = segment2.end; } // calculate the start and end time of the scene let sceneStart, sceneEnd; if (preTrans) { sceneStart = isFirst ? segment1.start - preTrans : segment1.start - preTrans + 1; } else { sceneStart = isFirst ? segment1.start : segment1.start + 1; } sceneEnd = point; scenes.push({ sceneStart, sceneEnd }); // set this cache variable preTrans = trans; this.framesNum = point; // set total duration const realDuration = index === ffscenes.length - 1 ? ffscenes[index].duration : ffscenes[index].getRealDuration(); this.duration += realDuration; }); } /** * Get the data of the current frame. * @return {FrameData} the data of the current frame * @public */ getFrameData() { const { scenes, segments, frame, frameData } = this; const isFirst = this.isFirst(); const isLast = this.isLast(); frameData.frame = frame; frameData.isFirst = isFirst; frameData.isLast = isLast; frameData.sceneStart = false; frameData.sceneEnd = false; frameData.sceneOver = false; // set the scene to start and end for (let i = 0; i < scenes.length; i++) { const { sceneStart, sceneEnd } = scenes[i]; if (sceneStart === frame) { frameData.sceneStart = true; // break; } if (sceneEnd === frame) { frameData.sceneEnd = true; // break; } if (sceneEnd + 1 === frame) { frameData.sceneOver = true; // break; } } // set the current scenesIndex and type for (let i = 0; i < segments.length; i++) { const { start, end, total, type, scenesIndex } = segments[i]; if ((frame > start && frame <= end) || isFirst) { frameData.type = type; frameData.scenesIndex = scenesIndex; frameData.progress = type === 'transition' ? (frame - start) / total : 0; return frameData; } } return null; } /** * Determine if it is the first frame * @public */ isFirst() { const { frame } = this; return frame === 0; } /** * Determine if the last frameData is reached * @public */ isLast() { const { frame } = this; return frame === this.framesNum - 1; } isOver() { const { frame } = this; return frame > this.framesNum - 1; } /** * update the next frameData * @public */ nextFrame() { const { fps } = this; if (!this.isOver()) { this.frame++; TimelineUpdate.update(fps); } } setFps(fps) { this.fps = fps; } getFramesNum() { return this.framesNum; } destroy() { this.scenes.length = 0; this.segments.length = 0; this.frameData = null; } } module.exports = Timeline;