Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize temp frame creation and transcoding #23

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified index.js
100644 → 100755
Empty file.
15 changes: 12 additions & 3 deletions lib/extract-video-frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ const ffmpeg = require('fluent-ffmpeg')
module.exports = (opts) => {
const {
videoPath,
framePattern
framePattern,
seek,
duration,
fps
} = opts

return new Promise((resolve, reject) => {
ffmpeg(videoPath)
const cmd = ffmpeg(videoPath)

if (seek) cmd.seek(seek / 1000)
if (duration) cmd.setDuration(duration / 1000)

cmd
.outputOptions([
'-pix_fmt', 'rgba',
'-start_number', '0'
'-start_number', '0',
'-r', fps
])
.output(framePattern)
.on('start', (cmd) => console.log({ cmd }))
Expand Down
10 changes: 6 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ module.exports = async (opts) => {
try {
console.time(`init-frames`)
const {
frames,
renders,
scenes,
theme
} = await initFrames({
log,
Expand All @@ -44,12 +45,12 @@ module.exports = async (opts) => {
console.timeEnd(`init-frames`)

console.time(`render-frames`)
const framePattern = await renderFrames({
const framePatterns = await renderFrames({
log,
concurrency,
outputDir: temp,
frameFormat,
frames,
renders,
theme,
onProgress: (p) => {
log(`render ${(100 * p).toFixed()}%`)
Expand All @@ -60,10 +61,11 @@ module.exports = async (opts) => {
console.time(`transcode-video`)
await transcodeVideo({
log,
framePattern,
framePatterns,
frameFormat,
audio,
output,
scenes,
theme,
onProgress: (p) => {
log(`transcode ${(100 * p).toFixed()}%`)
Expand Down
122 changes: 73 additions & 49 deletions lib/init-frames.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict'

const ffmpegProbe = require('ffmpeg-probe')
const fs = require('fs-extra')
const leftPad = require('left-pad')
const path = require('path')
const pMap = require('p-map')
Expand All @@ -25,13 +24,10 @@ module.exports = async (opts) => {

const scenes = await pMap(videos, (video, index) => {
return module.exports.initScene({
log,
index,
videos,
transition,
transitions,
frameFormat,
outputDir
transitions
})
}, {
concurrency
Expand All @@ -44,40 +40,68 @@ module.exports = async (opts) => {
fps
} = scenes[0]

const frames = []
let numFrames = 0
const renders = []

scenes.forEach((scene, index) => {
scene.frameStart = numFrames
for (let i = 0; i < scenes.length; ++i) {
const prev = scenes[i - 1]
const scene = scenes[i]
const next = scenes[i + 1]

scene.numFramesTransition = Math.floor(scene.transition.duration * fps / 1000)
scene.numFramesPreTransition = Math.max(0, scene.numFrames - scene.numFramesTransition)
scene.trimStart = (prev ? prev.transition.duration : 0)

numFrames += scene.numFramesPreTransition
// sanitize transition durations to never be longer than scene durations
scene.transition.duration = Math.max(0, Math.min(scene.transition.duration, scene.duration - scene.trimStart))

for (let frame = 0; frame < scene.numFrames; ++frame) {
const cFrame = scene.frameStart + frame

if (!frames[cFrame]) {
const next = (frame < scene.numFramesPreTransition ? undefined : scenes[index + 1])
if (next) {
scene.transition.duration = Math.min(scene.transition.duration, next.duration)
}

frames[cFrame] = {
current: scene,
next
}
}
scene.trimEnd = scene.duration - (next ? scene.transition.duration : 0)
scene.trimDuration = scene.trimEnd - scene.trimStart

if (next) {
const sceneGetFrame = await module.exports.initFrames({
log,
prefix: `post-${i}`,
frameFormat,
outputDir,
videoPath: scene.video,
seek: scene.trimEnd,
duration: scene.transition.duration,
fps
})

const nextGetFrame = await module.exports.initFrames({
log,
prefix: `pre-${i}`,
frameFormat,
outputDir,
videoPath: next.video,
seek: 0,
duration: scene.transition.duration,
fps
})

const numFrames = Math.floor(scene.transition.duration * fps / 1000)

renders.push({
scene,
next,
numFrames,
sceneGetFrame,
nextGetFrame
})
}
})
}

const duration = scenes.reduce((sum, scene, index) => (
scene.duration + sum - scene.transition.duration
), 0)

return {
frames,
renders,
scenes,
theme: {
numFrames,
duration,
width,
height,
Expand All @@ -88,13 +112,10 @@ module.exports = async (opts) => {

module.exports.initScene = async (opts) => {
const {
log,
index,
videos,
transition,
transitions,
frameFormat,
outputDir
transitions
} = opts

const video = videos[index]
Expand All @@ -106,11 +127,10 @@ module.exports.initScene = async (opts) => {
width: probe.width,
height: probe.height,
duration: probe.duration,
fps: probe.fps,
numFrames: parseInt(probe.streams[0].nb_frames)
}

scene.fps = probe.fps

const t = (transitions ? transitions[index] : transition)
scene.transition = {
name: 'fade',
Expand All @@ -123,29 +143,33 @@ module.exports.initScene = async (opts) => {
scene.transition.duration = 0
}

const fileNamePattern = `scene-${index}-%012d.${frameFormat}`
return scene
}

module.exports.initFrames = async (opts) => {
const {
log,
prefix,
frameFormat,
outputDir,
videoPath,
seek,
duration,
fps
} = opts

const fileNamePattern = `scene-${prefix}-%012d.${frameFormat}`
const framePattern = path.join(outputDir, fileNamePattern)
await extractVideoFrames({
log,
videoPath: scene.video,
framePattern
videoPath,
framePattern,
seek,
duration,
fps
})

scene.getFrame = (frame) => {
return (frame) => {
return framePattern.replace('%012d', leftPad(frame, 12, '0'))
}

// guard to ensure we only use frames that exist
while (scene.numFrames > 0) {
const frame = scene.getFrame(scene.numFrames - 1)
const exists = await fs.pathExists(frame)

if (exists) {
break
} else {
scene.numFrames--
}
}

return scene
}
Loading