Last active
September 18, 2021 08:45
-
-
Save m4n1ok/af0ab3fc939a3b376c639645e4bb2f68 to your computer and use it in GitHub Desktop.
Encode video directory into mp4 and webm
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
/** | |
* Generate web videos mp4 + webm from given folder | |
* You can pass options by file in videos.json | |
* Options are crop size | |
* NODE and FFMEPG is required. On mac brew install node && brew install ffmpeg | |
* FFMPEG command are inspired by https://gist.github.com/Vestride/278e13915894821e1d6f | |
* eg: node videos.js --input=../inputDir --output=../dir/outputDir --prefix=compressed --r-audio | |
* if missing outputDir, inputDir will be use | |
*/ | |
const path = require('path') | |
const fs = require('fs') | |
const util = require('util') | |
const {spawnSync} = require('child_process') | |
const args = process.argv | |
let videoFileOptions = false | |
let videosInputFolder = false | |
let videosOutputFolder = false | |
let prefixOutFilename = '_' | |
let removeAudio = false | |
args.forEach(arg => { | |
if (arg.startsWith('--input=')) { | |
videosInputFolder = arg.replace('--input=', '') | |
return | |
} | |
if (arg.startsWith('--output=')) { | |
videosOutputFolder = arg.replace('--output=', '') | |
return | |
} | |
if (arg.startsWith('--prefix=')) { | |
prefixOutFilename = arg.replace('--prefix=', '') | |
} | |
if (arg.startsWith('--r-audio')) { | |
removeAudio = true | |
} | |
}) | |
if (!videosInputFolder) { | |
console.log('A folder directory must be specified') | |
return | |
} | |
const videosDir = path.join(__dirname, videosInputFolder) | |
videosOutputFolder = videosOutputFolder ? path.join(__dirname, videosOutputFolder) : videosDir | |
// List videos to encode and get videos json config file if exists | |
const videosFiles = fs.readdirSync(videosDir).map((file) => { | |
return path.join(videosDir, file) | |
}).filter(function (file) { | |
if (path.basename(file) === 'videos.json') { | |
videoFileOptions = file | |
return false | |
} | |
return fs.statSync(file).isFile() | |
}) | |
// Read video file options if exist | |
if (videoFileOptions) { | |
videoFileOptions = JSON.parse(fs.readFileSync(videoFileOptions)) | |
} | |
// Encode videos | |
videosFiles.forEach((file) => { | |
const filename = path.basename(file) | |
const ext = path.extname(file) | |
const fileNameWExt = path.basename(file, ext) | |
const fileOpts = videoFileOptions.find(f => f.filename === filename) | |
// ffmpeg -an -i input.mov -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 output.mp4 | |
/** | |
* ffmpeg -i <source> -c:v libvpx-vp9 -pass 1 -b:v 1000K -threads 1 -speed 4 \ | |
-tile-columns 0 -frame-parallel 0 -auto-alt-ref 1 -lag-in-frames 25 \ | |
-g 9999 -aq-mode 0 -an -f webm /dev/null | |
ffmpeg -i <source> -c:v libvpx-vp9 -pass 2 -b:v 1000K -threads 1 -speed 0 \ | |
-tile-columns 0 -frame-parallel 0 -auto-alt-ref 1 -lag-in-frames 25 \ | |
-g 9999 -aq-mode 0 -c:a libopus -b:a 64k -f webm out.webm | |
*/ | |
let mp4Exec = `ffmpeg -i ${file} -vcodec libx264 -profile:v baseline -level 3` | |
let audio = removeAudio | |
if (fileOpts) { | |
// remove audio track | |
if ('audio' in fileOpts) { | |
audio = fileOpts.audio | |
} | |
if ('crop' in fileOpts) { | |
mp4Exec += ` -vf "crop=${fileOpts.crop.w}:${fileOpts.crop.h}:${fileOpts.crop.x}:${fileOpts.crop.y}"` | |
} | |
} | |
if (audio) { | |
mp4Exec += ' -an' | |
} | |
const outMp4Filename = prefixOutFilename + fileNameWExt + '.mp4' | |
const outMp4path = path.join(videosOutputFolder, outMp4Filename) | |
const outWebmFilename = prefixOutFilename + fileNameWExt + '.webm' | |
mp4Exec += ` -y ${outMp4path}` | |
const webmExecPass1 = `ffmpeg -i ${outMp4path} -c:v libvpx-vp9 -pass 1 -b:v 1000K -threads 1 -speed 4 \\ | |
-tile-columns 0 -frame-parallel 0 -auto-alt-ref 1 -lag-in-frames 25 \\ | |
-g 9999 -aq-mode 0 -an -y -f webm /dev/null` | |
const webmExecPass2 = `ffmpeg -i ${outMp4path} -c:v libvpx-vp9 -pass 2 -b:v 1000K -threads 1 -speed 0 \\ | |
-tile-columns 0 -frame-parallel 0 -auto-alt-ref 1 -lag-in-frames 25 \\ | |
-g 9999 -aq-mode 0 -c:a libopus -b:a 64k -y -f webm ${path.join(videosOutputFolder, outWebmFilename)}` | |
console.log(`***********************`) | |
console.log(`processing file ${outMp4Filename}`) | |
console.log(`***********************`) | |
encodeVideo(mp4Exec) | |
console.log(`***********************`) | |
console.log(`end of processing file ${outMp4Filename}`) | |
console.log(`***********************`) | |
console.log(`***********************`) | |
console.log(`processing file ${outWebmFilename} pass 1`) | |
console.log(`***********************`) | |
encodeVideo(webmExecPass1) | |
console.log(`***********************`) | |
console.log(`end of processing file ${outWebmFilename} pass 1`) | |
console.log(`***********************`) | |
console.log(`***********************`) | |
console.log(`processing file ${outWebmFilename} pass 2`) | |
console.log(`***********************`) | |
encodeVideo(webmExecPass2) | |
console.log(`***********************`) | |
console.log(`end of processing file ${outWebmFilename} pass 2`) | |
console.log(`***********************`) | |
}) | |
function encodeVideo (cmd) { | |
spawnSync(cmd, {stdio: 'inherit', shell: 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
[ | |
{ | |
"filename": "filename.ext", | |
"audio": false, | |
"crop": { | |
"x": "292", | |
"y": "0", | |
"w": "375", | |
"h": "500" | |
} | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment