UNPKG

shinobi-video-synopsis

Version:

Create Video Synopsis from provided videos. Frames are extracted from each video provided and backgrounds are subtracted around moving elements and merged into a single video for quick review.

183 lines (182 loc) 7.35 kB
const EventEmitter = require('events'); exports.newEngine = (engineOptions) => { engineOptions = engineOptions ? engineOptions : {} const engine = {} const { mainDirectory, createBaseFolder, deleteFile, grabFirstFrame, getVideoFileName, } = require('./utils.js'); const fs = require('fs'); const { createNewBackgroundSubtractor } = require('./extractFramesFromVideosAndRemoveBackgrounds.js'); const { mergeFramePairs } = require('./mergeFramePairs.js'); const { createVideoFromJpegImages } = require('./createVideoFromMergedFrames.js'); const extractFramesFromVideo = createNewBackgroundSubtractor() const initiatedAt = new Date(); const std = new EventEmitter(); var percentCompleted = 0; const subtractedBackgrounds = engineOptions.subtractedBackgroundsDirectory || mainDirectory + 'rawSubtractions/' const mergedFramesDirectory = engineOptions.mergedFramesDirectory || mainDirectory + 'mergedFrames/' const firstFrameDirectory = engineOptions.firstFrameDirectory || mainDirectory + 'firstFrame/' const ffmpegBinary = engineOptions.ffmpegBinary || 'ffmpeg' var alreadyRunning = false; var processLogs = [] const sendPercentComplete = (percent) => { percentCompleted = percent; std.emit('percent',percent) if(engineOptions.debugLog)console.log('shinobi-video-synopsis',percent) } engine.std = std engine.logStderr = (...args) => { processLogs.push(args) std.emit('stderr',args) if(engineOptions.debugLog)console.log(...(['shinobi-video-synopsis'].concat(args))) } engine.extractFramesFromVideo = extractFramesFromVideo engine.mergeFramePairs = mergeFramePairs engine.createVideoFromJpegImages = createVideoFromJpegImages engine.about = async () => { return { runningProcesses: alreadyRunning, percentCompleted: percentCompleted, initiatedAt: initiatedAt, debugLog: engineOptions.debugLog || false, cancelledProcess: engine.cancelledProcess, logs: processLogs, } } engine.cleanBuildFiles = async () => { await deleteFile(subtractedBackgrounds) await deleteFile(firstFrameDirectory) await deleteFile(mergedFramesDirectory) } engine.cancel = () => { let message = 'Cancelling...' if(!engine.cancelledProcess){ engine.cancelledProcess = true try{ if(engine.videoBuildProcess)engine.videoBuildProcess.kill('SIGTERM') }catch(err){ engine.logStderr(err) } }else if(!engine.runningProcesses){ message = 'Already Cancelled!' } return { ok: true, msg: message } } engine.run = async (options) => { if(!options.videos)return console.log('No Videos Provided') if(alreadyRunning)return "Already Running"; processLogs = [] engine.runningProcesses = true engine.cancelledProcess = false alreadyRunning = true; const videos = options.videos // create folders sendPercentComplete(0) createBaseFolder(subtractedBackgrounds) createBaseFolder(firstFrameDirectory) createBaseFolder(mergedFramesDirectory) // extract frames and remove backgrounds, BackgroundSubtractorMOG2 sendPercentComplete(5) var extractionPercent = 0 for (let i = 0; i < videos.length; i++) { if(engine.cancelledProcess){ alreadyRunning = false; engine.runningProcesses = false return { ok: false, msg: "Cancelled" } } const videoName = videos[i]; const videoFilename = videoName + '.mp4'; createBaseFolder(subtractedBackgrounds + getVideoFileName(videoName)) grabFirstFrame(firstFrameDirectory,videoFilename,getVideoFileName(videoName)); engine.logStderr(`Extracting frames from video named "${videoName}.mp4"...`) await extractFramesFromVideo({ engine: engine, videoName: videoName, subtractedBackgroundsDirectory: subtractedBackgrounds, delayBetweenFrames: 50, }); extractionPercent += 20 / videos.length sendPercentComplete(parseInt(5 + extractionPercent)) engine.logStderr(`Done extracting frames from "${videoName}.mp4"!`) } // create listing of which frames to overlap then merge them and write into ${subtractedBackgrounds} if(engine.cancelledProcess){ alreadyRunning = false; engine.runningProcesses = false return { ok: false, msg: "Cancelled" } } engine.logStderr("Merging Frames Extracted...") sendPercentComplete(35) var extractionPercent = 0 const mergeFramePairsResponse = await mergeFramePairs({ //engine running the request engine: engine, //folder where files are subtractedBackgroundsDirectory: subtractedBackgrounds, //file list (videoNames only, not full path) videoNames: fs.readdirSync(subtractedBackgrounds), // mergedFramesDirectory: mergedFramesDirectory, firstFrameDirectory: firstFrameDirectory, onFrameComplete: (currentNumber,maxNumber) => { extractionPercent += 15 / maxNumber sendPercentComplete(parseInt(35 + extractionPercent)) } }) sendPercentComplete(50) if(engine.cancelledProcess){ alreadyRunning = false; engine.runningProcesses = false return { ok: false, msg: "Cancelled" } } // create a video from merged frames engine.logStderr("Create Video From Merged Frames...") sendPercentComplete(65) options.fps = options.fps || 25 const selectedFps = mergeFramePairsResponse.numberOfFrames < options.fps ? mergeFramePairsResponse.numberOfFrames : options.fps const buildResponse = await createVideoFromJpegImages({ //engine running the request engine: engine, //ffmpeg binary path ffmpegBinary: ffmpegBinary, //folder where files are mergedFramesDirectory: mergedFramesDirectory, //file list (filenames only, not full path) frames: fs.readdirSync(mergedFramesDirectory), //fps fps: selectedFps, //final video location output: options.output }) sendPercentComplete(85) engine.logStderr("Completed Building!") engine.logStderr({ mergeFramePairs: mergeFramePairsResponse, videoBuild: buildResponse, }) sendPercentComplete(100) alreadyRunning = false; engine.runningProcesses = false return { mergeFramePairs: mergeFramePairsResponse, videoBuild: buildResponse, } } return engine }