Skip to content

Commit

Permalink
feat: update core deps and bugfix / robustness fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
transitive-bullshit committed Feb 10, 2020
1 parent e7ce078 commit 0d66ef7
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 7,423 deletions.
4 changes: 3 additions & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = async (argv) => {
.option('-f, --frame-format <format>', 'format to use for temp frame images', /^(raw|png|jpg)$/i, 'raw')
.option('-c, --concurrency <number>', 'number of videos to process in parallel', (v) => parseInt(v), 4)
.option('-C, --no-cleanup-frames', 'disables cleaning up temp frame images')
.option('-v, --verbose', 'enable verbose debug logging from FFmpeg')
.option('-O, --temp-dir <dir>', 'temporary working directory to store frame data')
.parse(argv)

Expand Down Expand Up @@ -49,7 +50,8 @@ module.exports = async (argv) => {

frameFormat: program.frameFormat,
cleanupFrames: program.cleanupFrames,
tempDir: program.tempDir
tempDir: program.tempDir,
verbose: !!program.verbose
})

console.log(program.output)
Expand Down
2 changes: 2 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = async (opts) => {
const gl = GL(width, height)

if (!gl) {
console.error('Failed to create OpenGL context. Please see https://github.com/stackgl/headless-gl#supported-platforms-and-nodejs-versions for compatibility.')

throw new Error('failed to create OpenGL context')
}

Expand Down
13 changes: 10 additions & 3 deletions lib/extract-video-frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ const ffmpeg = require('fluent-ffmpeg')
module.exports = (opts) => {
const {
videoPath,
framePattern
framePattern,
verbose = false
} = opts

return new Promise((resolve, reject) => {
ffmpeg(videoPath)
const cmd = ffmpeg(videoPath)
.outputOptions([
'-loglevel', 'info',
'-pix_fmt', 'rgba',
'-start_number', '0'
])
.output(framePattern)
.on('start', (cmd) => console.log({ cmd }))
.on('end', () => resolve(framePattern))
.on('error', (err) => reject(err))
.run()

if (verbose) {
cmd.on('stderr', (err) => console.error(err))
}

cmd.run()
})
}
12 changes: 10 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const fs = require('fs-extra')
const rmfr = require('rmfr')
const tempy = require('tempy')

Expand All @@ -21,9 +22,14 @@ module.exports = async (opts) => {
audio = undefined,
videos,
output,
tempDir
tempDir,
verbose = false
} = opts

if (tempDir) {
fs.ensureDirSync(tempDir)
}

const temp = tempDir || tempy.directory()

console.time(`ffmpeg-concat`)
Expand All @@ -42,7 +48,8 @@ module.exports = async (opts) => {
transitions,
outputDir: temp,
frameFormat,
renderAudio: !audio
renderAudio: !audio,
verbose
})
console.timeEnd(`init-frames`)

Expand Down Expand Up @@ -80,6 +87,7 @@ module.exports = async (opts) => {
audio: concatAudioFile,
output,
theme,
verbose,
onProgress: (p) => {
log(`transcode ${(100 * p).toFixed()}%`)
}
Expand Down
12 changes: 8 additions & 4 deletions lib/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ const videosWithAudio = [
path.join(fixturesPath, '0a.mp4')
]

test('concat 3 mp4s with using constant 500ms transitions', async (t) => {
test.serial('concat 3 mp4s with using constant 500ms transitions', async (t) => {
const output = tempy.file({ extension: 'mp4' })
await concat({
log: console.log,
verbose: true,
output,
videos,
transition: {
name: 'directionalwipe',
duration: 500
}
},
cleanupFrames: false
})

const probe = await ffmpegProbe(output)
Expand All @@ -41,10 +43,11 @@ test('concat 3 mp4s with using constant 500ms transitions', async (t) => {
await rmfr(output)
})

test('concat 9 mp4s with unique transitions', async (t) => {
test.serial('concat 9 mp4s with unique transitions', async (t) => {
const output = tempy.file({ extension: 'mp4' })
await concat({
log: console.log,
verbose: true,
output,
videos: videos.concat(videos).concat(videos),
transitions: [
Expand Down Expand Up @@ -92,10 +95,11 @@ test('concat 9 mp4s with unique transitions', async (t) => {
await rmfr(output)
})

test('concat 3 mp4s with source audio and unique transitions', async (t) => {
test.serial('concat 3 mp4s with source audio and unique transitions', async (t) => {
const output = tempy.file({ extension: 'mp4' })
await concat({
log: console.log,
verbose: true,
output,
videos: videosWithAudio,
transitions: [
Expand Down
108 changes: 71 additions & 37 deletions lib/init-frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,61 @@ module.exports = async (opts) => {
transitions,
frameFormat,
outputDir,
renderAudio = false
renderAudio = false,
verbose
} = opts

if (transitions && videos.length - 1 !== transitions.length) {
throw new Error('number of transitions must equal number of videos minus one')
throw new Error(
'number of transitions must equal number of videos minus one'
)
}

const scenes = await pMap(videos, (video, index) => {
return module.exports.initScene({
log,
index,
videos,
transition,
transitions,
frameFormat,
outputDir,
renderAudio
})
}, {
concurrency
})
const scenes = await pMap(
videos,
(video, index) => {
return module.exports.initScene({
log,
index,
videos,
transition,
transitions,
frameFormat,
outputDir,
renderAudio,
verbose
})
},
{
concurrency
}
)

// first video dictates dimensions and fps
const {
width,
height,
fps
} = scenes[0]
const { width, height, fps } = scenes[0]

const frames = []
let numFrames = 0

scenes.forEach((scene, index) => {
scene.frameStart = numFrames

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

numFrames += scene.numFramesPreTransition

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])
const next =
frame < scene.numFramesPreTransition ? undefined : scenes[index + 1]

frames[cFrame] = {
current: scene,
Expand All @@ -72,9 +82,10 @@ module.exports = async (opts) => {
}
})

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

return {
frames,
Expand All @@ -98,28 +109,41 @@ module.exports.initScene = async (opts) => {
transitions,
frameFormat,
outputDir,
renderAudio
renderAudio,
verbose
} = opts

const video = videos[index]
const probe = await ffmpegProbe(video)
const format = (probe.format && probe.format.format_name) || 'unknown'

if (!probe.streams || !probe.streams[0]) {
throw new Error(`Unsupported input video format "${format}": ${video}`)
}

const scene = {
video,
index,
width: probe.width,
height: probe.height,
duration: probe.duration,
numFrames: parseInt(probe.streams[0].nb_frames)
numFrames: parseInt(probe.streams[0].nb_frames),
fps: probe.fps
}

scene.fps = probe.fps
if (isNaN(scene.numFrames) || isNaN(scene.duration)) {
throw new Error(`Unsupported input video format "${format}": ${video}`)
}

if (verbose) {
console.error(scene)
}

const t = (transitions ? transitions[index] : transition)
const t = transitions ? transitions[index] : transition
scene.transition = {
name: 'fade',
duration: 500,
params: { },
params: {},
...t
}

Expand All @@ -134,7 +158,8 @@ module.exports.initScene = async (opts) => {
await extractVideoFrames({
log,
videoPath: scene.video,
framePattern
framePattern,
verbose
})

scene.getFrame = (frame) => {
Expand All @@ -153,16 +178,25 @@ module.exports.initScene = async (opts) => {
}
}

if (renderAudio && probe.streams && probe.streams.filter(s => s.codec_type === 'audio').length) {
const previousTransition = (index > 0 && transitions ? transitions[ index - 1 ] : transition)
const previousTransitionDuration = index === 0 ? 0 : (previousTransition.duration || 500)
if (
renderAudio &&
probe.streams &&
probe.streams.filter((s) => s.codec_type === 'audio').length
) {
const previousTransition =
index > 0 && transitions ? transitions[index - 1] : transition
const previousTransitionDuration =
index === 0 ? 0 : previousTransition.duration || 500

await extractAudio({
log,
videoPath: scene.video,
outputFileName: audioPath,
start: previousTransitionDuration / 2000,
duration: scene.duration / 1000 - previousTransitionDuration / 2000 - scene.transition.duration / 2000
duration:
scene.duration / 1000 -
previousTransitionDuration / 2000 -
scene.transition.duration / 2000
})
scene.sourceAudioPath = audioPath
}
Expand Down
7 changes: 6 additions & 1 deletion lib/transcode-video.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ module.exports = async (opts) => {
framePattern,
onProgress,
output,
theme
theme,
verbose
} = opts

return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -63,6 +64,10 @@ module.exports = async (opts) => {
cmd.on('progress', onTranscodeProgress(onProgress, theme.duration))
}

if (verbose) {
cmd.on('stderr', (err) => console.error(err))
}

cmd
.outputOptions(outputOptions)
.output(output)
Expand Down
Loading

0 comments on commit 0d66ef7

Please sign in to comment.