Skip to content

Commit

Permalink
concat source audio
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-habib committed Aug 18, 2019
1 parent f400557 commit 6a82d48
Show file tree
Hide file tree
Showing 9 changed files with 7,449 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dist
.env.test.local
.env.production.local
.cache
.vscode

temp
out.mp4
Expand Down
29 changes: 29 additions & 0 deletions lib/extract-audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const ffmpeg = require('fluent-ffmpeg')

module.exports = (opts) => {
const {
log,
videoPath,
outputFileName,
start,
duration
} = opts

return new Promise((resolve, reject) => {
const cmd = ffmpeg(videoPath)
.noVideo()
.audioCodec('libmp3lame')
.on('start', cmd => log({ cmd }))
.on('end', () => resolve(outputFileName))
.on('error', (err) => reject(err))
if (start) {
cmd.seekInput(start)
}
if (duration) {
cmd.duration(duration)
}
cmd.save(outputFileName)
})
}
19 changes: 17 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const tempy = require('tempy')

const initFrames = require('./init-frames')
const renderFrames = require('./render-frames')
const renderAudio = require('./render-audio')
const transcodeVideo = require('./transcode-video')

const noop = () => { }
Expand All @@ -31,6 +32,7 @@ module.exports = async (opts) => {
console.time(`init-frames`)
const {
frames,
scenes,
theme
} = await initFrames({
log,
Expand All @@ -39,7 +41,8 @@ module.exports = async (opts) => {
transition,
transitions,
outputDir: temp,
frameFormat
frameFormat,
renderAudio: !audio
})
console.timeEnd(`init-frames`)

Expand All @@ -57,12 +60,24 @@ module.exports = async (opts) => {
})
console.timeEnd(`render-frames`)

console.time(`render-audio`)
let concatAudioFile = audio
if (!audio && scenes.filter(s => s.sourceAudioPath).length === scenes.length) {
concatAudioFile = await renderAudio({
log,
scenes,
outputDir: temp,
fileName: 'audioConcat.mp3'
})
}
console.timeEnd(`render-audio`)

console.time(`transcode-video`)
await transcodeVideo({
log,
framePattern,
frameFormat,
audio,
audio: concatAudioFile,
output,
theme,
onProgress: (p) => {
Expand Down
33 changes: 33 additions & 0 deletions lib/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const videos = [
path.join(fixturesPath, '1.mp4'),
path.join(fixturesPath, '2.mp4')
]
const videosWithAudio = [
path.join(fixturesPath, '0a.mp4'),
path.join(fixturesPath, '0a.mp4'),
path.join(fixturesPath, '0a.mp4')
]

test('concat 3 mp4s with using constant 500ms transitions', async (t) => {
const output = tempy.file({ extension: 'mp4' })
Expand Down Expand Up @@ -86,3 +91,31 @@ test('concat 9 mp4s with unique transitions', async (t) => {

await rmfr(output)
})

test('concat 3 mp4s with source audio and unique transitions', async (t) => {
const output = tempy.file({ extension: 'mp4' })
await concat({
log: console.log,
output,
videos: videosWithAudio,
transitions: [
{
name: 'circleOpen',
duration: 1000
},
{
name: 'crossWarp',
duration: 1000
}
]
})

const probe = await ffmpegProbe(output)
t.is(probe.width, 1280)
t.is(probe.height, 720)
t.is(probe.streams.length, 2)
t.truthy(probe.duration >= 11000)
t.truthy(probe.duration <= 15000)

await rmfr(output)
})
26 changes: 23 additions & 3 deletions lib/init-frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const path = require('path')
const pMap = require('p-map')

const extractVideoFrames = require('./extract-video-frames')
const extractAudio = require('./extract-audio')

module.exports = async (opts) => {
const {
Expand All @@ -16,7 +17,8 @@ module.exports = async (opts) => {
transition,
transitions,
frameFormat,
outputDir
outputDir,
renderAudio = false
} = opts

if (transitions && videos.length - 1 !== transitions.length) {
Expand All @@ -31,7 +33,8 @@ module.exports = async (opts) => {
transition,
transitions,
frameFormat,
outputDir
outputDir,
renderAudio
})
}, {
concurrency
Expand Down Expand Up @@ -94,7 +97,8 @@ module.exports.initScene = async (opts) => {
transition,
transitions,
frameFormat,
outputDir
outputDir,
renderAudio
} = opts

const video = videos[index]
Expand Down Expand Up @@ -124,7 +128,9 @@ module.exports.initScene = async (opts) => {
}

const fileNamePattern = `scene-${index}-%012d.${frameFormat}`
const audioFileName = `scene-${index}.mp3`
const framePattern = path.join(outputDir, fileNamePattern)
const audioPath = path.join(outputDir, audioFileName)
await extractVideoFrames({
log,
videoPath: scene.video,
Expand All @@ -147,5 +153,19 @@ 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)

await extractAudio({
log,
videoPath: scene.video,
outputFileName: audioPath,
start: previousTransitionDuration / 2000,
duration: scene.duration / 1000 - previousTransitionDuration / 2000 - scene.transition.duration / 2000
})
scene.sourceAudioPath = audioPath
}

return scene
}
38 changes: 38 additions & 0 deletions lib/render-audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

const fs = require('fs-extra')
const path = require('path')
const ffmpeg = require('fluent-ffmpeg')

module.exports = async (opts) => {
const {
log,
scenes,
outputDir,
fileName
} = opts

return new Promise((resolve, reject) => {
const concatListPath = path.join(outputDir, 'audioConcat.txt')
const toConcat = scenes.filter(scene => scene.sourceAudioPath).map(scene => `file '${scene.sourceAudioPath}'`)
const outputFileName = path.join(outputDir, fileName)
fs.outputFile(concatListPath, toConcat.join('\n')).then(() => {
log(`created ${concatListPath}`)
const cmd = ffmpeg()
.input(concatListPath)
.inputOptions(['-f concat', '-safe 0'])
.on('start', cmd => log(cmd))
.on('end', () => resolve(outputFileName))
.on('error', (err, stdout, stderr) => {
if (err) {
console.error('failed to concat audio', err, stdout, stderr)
}
reject(err)
})
cmd.save(outputFileName)
}).catch(err => {
console.error(`failed to concat audio ${err}`)
reject(err)
})
})
}
Binary file added media/0a.mp4
Binary file not shown.
Loading

0 comments on commit 6a82d48

Please sign in to comment.