forked from transitive-bullshit/ffmpeg-concat
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 942eaed
Showing
26 changed files
with
5,349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
indent_style = space | ||
indent_size = 2 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"extends": [ | ||
"standard" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# See https://help.github.com/ignore-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
node_modules | ||
|
||
# builds | ||
build | ||
dist | ||
|
||
# misc | ||
.DS_Store | ||
.env | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
.cache | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
media |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
language: node_js | ||
node_js: | ||
- 9 | ||
- 8 | ||
before_install: | ||
- sudo add-apt-repository ppa:mc3man/trusty-media -y | ||
- sudo apt-get update -q | ||
- sudo apt-get install ffmpeg -y |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/usr/bin/env node | ||
'use strict' | ||
|
||
module.exports = require('./lib') | ||
|
||
if (!module.parent) { | ||
require('./lib/cli')(process.argv) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#!/usr/bin/env node | ||
'use strict' | ||
|
||
const concat = require('.') | ||
const fs = require('fs') | ||
const program = require('commander') | ||
const { version } = require('../package') | ||
|
||
module.exports = async (argv) => { | ||
program | ||
.version(version) | ||
.usage('[options] <videos...>') | ||
.option('-o, --output <output>', 'path to mp4 file to write', (s) => s, 'out.mp4') | ||
.option('-t, --transition-name <name>', 'name of gl-transition to use', (s) => s, 'fade') | ||
.option('-d, --transition-duration <duration>', 'duration of transition to use in ms', parseInt, 500) | ||
.option('-T, --transitions <file>', 'json file to load transitions from') | ||
.option('-f, --frame-format <format>', 'format to use for temp frame images', /^(raw|png|jpg)$/i, 'raw') | ||
.option('-C, --no-cleanup-frames', 'disables cleaning up temp frame images') | ||
.action(async (videos, opts) => { | ||
}) | ||
.parse(argv) | ||
|
||
let transitions | ||
|
||
if (program.transitions) { | ||
try { | ||
transitions = JSON.parse(fs.readFileSync(program.transitions, 'utf8')) | ||
} catch (err) { | ||
console.error(`error parsing transitions file "${program.transitions}"`, err) | ||
throw err | ||
} | ||
} | ||
|
||
try { | ||
const videos = program.args.filter((v) => typeof v === 'string') | ||
|
||
await concat({ | ||
log: console.log, | ||
|
||
videos, | ||
output: program.output, | ||
|
||
transition: { | ||
name: program.transitionName, | ||
duration: program.transitionDuration | ||
}, | ||
transitions, | ||
|
||
frameFormat: program.frameFormat, | ||
cleanupFrames: program.cleanupFerames | ||
}) | ||
|
||
console.log(program.output) | ||
} catch (err) { | ||
console.error('concat error', err) | ||
throw err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
'use strict' | ||
|
||
const GL = require('gl') | ||
|
||
const createFrameWriter = require('./frame-writer') | ||
const createTransition = require('./transition') | ||
|
||
module.exports = async (opts) => { | ||
const { | ||
frameFormat, | ||
theme | ||
} = opts | ||
|
||
const { | ||
width, | ||
height | ||
} = theme | ||
|
||
const gl = GL(width, height) | ||
|
||
if (!gl) { | ||
throw new Error('failed to create OpenGL context') | ||
} | ||
|
||
const frameWriter = await createFrameWriter({ | ||
gl, | ||
width, | ||
height, | ||
frameFormat | ||
}) | ||
|
||
const ctx = { | ||
gl, | ||
width, | ||
height, | ||
frameWriter, | ||
transition: null | ||
} | ||
|
||
ctx.setTransition = async ({ name, resizeMode }) => { | ||
if (ctx.transition) { | ||
ctx.transition.dispose() | ||
ctx.transition = null | ||
} | ||
|
||
ctx.transition = await createTransition({ | ||
gl, | ||
name, | ||
resizeMode | ||
}) | ||
} | ||
|
||
ctx.capture = ctx.frameWriter.write.bind(ctx.frameWriter) | ||
|
||
ctx.render = async (...args) => { | ||
if (ctx.transition) { | ||
return ctx.transition.draw(...args) | ||
} | ||
} | ||
|
||
ctx.flush = async () => { | ||
return ctx.frameWriter.flush() | ||
} | ||
|
||
ctx.dispose = async () => { | ||
if (ctx.transition) { | ||
ctx.transition.dispose() | ||
ctx.transition = null | ||
} | ||
|
||
gl.destroy() | ||
} | ||
|
||
return ctx | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use strict' | ||
|
||
const ffmpeg = require('fluent-ffmpeg') | ||
|
||
module.exports = (opts) => { | ||
const { | ||
videoPath, | ||
framePattern | ||
} = opts | ||
|
||
return new Promise((resolve, reject) => { | ||
ffmpeg(videoPath) | ||
.outputOptions([ | ||
'-pix_fmt', 'rgba', | ||
'-start_number', '0' | ||
]) | ||
.output(framePattern) | ||
.on('start', (cmd) => console.log({ cmd })) | ||
.on('end', () => resolve(framePattern)) | ||
.on('error', (err) => reject(err)) | ||
.run() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
'use strict' | ||
|
||
// TODO: this worker pool approach was an experiment that failed to yield any | ||
// performance advantages. we should revert back to the straightforward version | ||
// even though dis ist prettttyyyyyyy codezzzzzz. | ||
|
||
const fs = require('fs') | ||
const pRace = require('p-race') | ||
const sharp = require('sharp') | ||
const util = require('util') | ||
|
||
const fsOpen = util.promisify(fs.open.bind(fs)) | ||
const fsWrite = util.promisify(fs.write.bind(fs)) | ||
const fsClose = util.promisify(fs.close.bind(fs)) | ||
|
||
module.exports = async (opts) => { | ||
const { | ||
concurrency = 1, | ||
frameFormat = 'raw', | ||
gl, | ||
width, | ||
height | ||
} = opts | ||
|
||
if (frameFormat !== 'png' && frameFormat !== 'raw') { | ||
throw new Error(`frame writer unsupported format "${frameFormat}"`) | ||
} | ||
|
||
let pool = [] | ||
let inactive = [] | ||
let active = { } | ||
|
||
for (let i = 0; i < concurrency; ++i) { | ||
const byteArray = new Uint8Array(width * height * 4) | ||
|
||
const worker = { | ||
id: i, | ||
byteArray, | ||
promise: null | ||
} | ||
|
||
if (frameFormat === 'png') { | ||
const buffer = Buffer.from(byteArray.buffer) | ||
worker.encoder = sharp(buffer, { | ||
raw: { | ||
width, | ||
height, | ||
channels: 4 | ||
} | ||
}).png({ | ||
compressionLevel: 0, | ||
adaptiveFiltering: false | ||
}) | ||
} | ||
|
||
pool.push(worker) | ||
inactive.push(i) | ||
} | ||
|
||
const writeFrame = async ({ filePath, worker }) => { | ||
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, worker.byteArray) | ||
|
||
try { | ||
if (frameFormat === 'png') { | ||
await new Promise((resolve, reject) => { | ||
worker.encoder.toFile(filePath, (err) => { | ||
if (err) reject(err) | ||
resolve() | ||
}) | ||
}) | ||
} else { | ||
const { byteArray } = worker | ||
const fd = await fsOpen(filePath, 'w') | ||
|
||
/* | ||
// write file in 64k chunks | ||
const chunkSize = 2 ** 17 | ||
let offset = 0 | ||
while (offset < byteArray.byteLength) { | ||
const length = Math.min(chunkSize, byteArray.length - offset) | ||
await fsWrite(fd, byteArray, offset, length) | ||
offset += length | ||
} | ||
*/ | ||
|
||
// write file in one large chunk | ||
await fsWrite(fd, byteArray) | ||
await fsClose(fd) | ||
} | ||
} catch (err) { | ||
delete active[worker.id] | ||
inactive.push(worker.id) | ||
throw err | ||
} | ||
|
||
delete active[worker.id] | ||
inactive.push(worker.id) | ||
|
||
return filePath | ||
} | ||
|
||
const reserve = async () => { | ||
if (inactive.length) { | ||
const id = inactive.pop() | ||
const worker = pool[id] | ||
active[id] = worker | ||
return worker | ||
} else { | ||
await pRace(Object.values(active).map(v => v.promise)) | ||
return reserve() | ||
} | ||
} | ||
|
||
return { | ||
write: async (filePath) => { | ||
const worker = await reserve() | ||
worker.promise = writeFrame({ | ||
filePath, | ||
worker | ||
}) | ||
}, | ||
|
||
flush: async () => { | ||
return Promise.all(Object.values(active).map(v => v.promise)) | ||
}, | ||
|
||
dispose: () => { | ||
pool = null | ||
active = null | ||
inactive = null | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict' | ||
|
||
const parseUrl = require('url-parse') | ||
|
||
const extWhitelist = new Set([ | ||
// videos | ||
'gif', | ||
'mp4', | ||
'webm', | ||
'mkv', | ||
'mov', | ||
'avi', | ||
|
||
// images | ||
'bmp', | ||
'jpg', | ||
'jpeg', | ||
'png', | ||
'tif', | ||
'webp', | ||
|
||
// audio | ||
'mp3', | ||
'aac', | ||
'wav', | ||
'flac', | ||
'opus', | ||
'ogg' | ||
]) | ||
|
||
module.exports = (url, opts = { strict: true }) => { | ||
const { pathname } = parseUrl(url) | ||
const parts = pathname.split('.') | ||
const ext = (parts[parts.length - 1] || '').trim().toLowerCase() | ||
|
||
if (!opts.strict || extWhitelist.has(ext)) { | ||
return ext | ||
} | ||
} |
Oops, something went wrong.