UNPKG

ffcreator

Version:

FFCreator is a lightweight and flexible short video production library

298 lines (258 loc) 6.89 kB
'use strict'; /** * FFCreatorCenter - A global FFCreator task scheduling center. * You don’t have to use it, you can easily implement a task manager by yourself. * * ####Example: * * FFCreatorCenter.addTask(()=>{ * const creator = new FFCreator; * return creator; * }); * * * ####Note: * On the server side, you only need to start FFCreatorCenter, * remember to add error logs to the events in it * * @object */ const util = require('util'); const EventEmitter = require('eventemitter3'); const TaskQueue = require('./task'); const Progress = require('./progress'); const Utils = require('../utils/utils'); const FFLogger = require('../utils/logger'); const FFmpegUtil = require('../utils/ffmpeg'); const FFCreatorCenter = { cursor: 0, num: 0, delay: 500, state: 'free', log: false, temps: {}, progress: new Progress(), event: new EventEmitter(), taskQueue: new TaskQueue(), /** * Close logger switch * @public */ closeLog() { FFLogger.enable = false; }, /** * Open logger switch * @public */ openLog() { FFLogger.enable = true; }, openCatchLog() { this.log = true; }, /** * Add a production task * @param {function} task - a production task * @public */ addTask(task) { const id = this.taskQueue.push({ task }); if (this.state === 'free') this.start(); return id; }, /** * Add a production task by template * @param {string} tempid - a template id * @public */ addTaskByTemplate(id, params) { const task = this.temps[id]; const tid = this.taskQueue.push({ task, params }); if (this.state === 'free') this.start(); return tid; }, /** * Listen to production task events * @param {string} id - a task id * @param {strint} eventName - task name * @param {function} func - task event handler * @public */ onTask(id, eventName, func) { const handler = result => { if (result.id == id) { this.event.removeListener(eventName, handler); // this.removeTaskObj(eventName, result); func(result); } }; this.event.on(eventName, handler); }, removeTaskObj(eventName, result) { // remove taskObj after 5s if (eventName === 'single-error' || eventName === 'single-complete') { setTimeout(() => { const { taskObj } = result; this.taskQueue.remove(taskObj); }, 5000); } }, /** * Listen to production task Error events * @param {string} id - a task id * @param {function} func - task event handler * @public */ onTaskError(id, func) { this.onTask(id, 'single-error', func); }, /** * Listen to production task Complete events * @param {string} id - a task id * @param {function} func - task event handler * @public */ onTaskComplete(id, func) { this.onTask(id, 'single-complete', func); }, /** * Start a task * @async * @public */ async start() { const taskObj = this.taskQueue.getTaskByIndex(this.cursor); this.execTask(taskObj); }, /** * Get the status of a task by id * @public */ getTaskState(id) { return this.taskQueue.getTaskState(id); }, getInfo() { const { cursor, num } = this; const tasks = this.taskQueue.getLength(); return { num, cursor, tasks }; }, getResultFile(id) { return this.taskQueue.getResultFile(id); }, getProgress(id) { return this.progress.getPercent(id); }, async execTask(taskObj) { this.state = 'busy'; try { this.num++; this.cursor++; const { task, params } = taskObj; const creator = await task(params); if (!creator) { this.handlingError({ taskObj, error: 'execTask: await taskObj.task(taskObj.id) return null', }); } else { this.initCreator(creator, taskObj); } } catch (error) { if (this.log) console.log(error); this.handlingError({ taskObj, error }); } }, initCreator(creator, taskObj) { if (creator.destroyed) { return setTimeout(this.nextTask.bind(this), this.delay); } creator.inCenter = true; creator.generateOutput(); // event listeners creator.on('start', () => { const result = { id: taskObj.id, taskObj }; this.event.emit('single-start', result); }); creator.on('error', event => { this.handlingError({ taskObj, error: event }); }); creator.on('progress', progress => { this.progress.id = taskObj.id; this.progress.state = progress.state; this.progress.percent = progress.percent; }); creator.on('complete', () => { try { const { id } = taskObj; const file = creator.getFile(); taskObj.state = 'complete'; taskObj.file = file; this.progress.add(id); this.taskQueue.store(id); const result = { id, file, taskObj }; this.event.emit('single-complete', result); FFLogger.info(`Creator production completed. id:${id} file: ${file}`); } catch (error) { FFLogger.error(`Creator production error. ${util.inspect(error)}`); } setTimeout(this.nextTask.bind(this), this.delay); }); }, handlingError({ taskObj, error = 'normal' }) { error = Utils.getErrStack(error); const result = { id: taskObj.id, taskObj, error }; taskObj.state = 'error'; this.event.emit('single-error', result); setTimeout(this.nextTask.bind(this), this.delay); FFLogger.error(`Creator production error. ${util.inspect(error)}`); }, nextTask() { if (this.cursor >= this.taskQueue.getLength()) { this.resetTasks(); this.event.emit('all-complete'); return; } const taskObj = this.taskQueue.getTaskByIndex(this.cursor); this.execTask(taskObj); }, resetTasks() { this.cursor = 0; this.state = 'free'; this.taskQueue.clear(); }, /** * add a creator task template * * @param {string} id - task template id name * @param {function} taskFunc - task template * @public */ createTemplate(id, taskFunc) { this.temps[id] = taskFunc; }, /** * Set the installation path of the current server ffmpeg. * If not set, the ffmpeg command of command will be found by default. * * @param {string} path - installation path of the current server ffmpeg * @public */ setFFmpegPath(path) { FFmpegUtil.setFFmpegPath(path); }, /** * Set the installation path of the current server ffprobe. * If not set, the ffprobe command of command will be found by default. * * @param {string} path - installation path of the current server ffprobe * @public */ setFFprobePath(path) { FFmpegUtil.setFFprobePath(path); }, setFFPath() { FFmpegUtil.setFFPath(); } }; module.exports = FFCreatorCenter;