Skip to content

Instantly share code, notes, and snippets.

@m4n1ok
Last active September 18, 2021 08:45
Show Gist options
  • Save m4n1ok/af0ab3fc939a3b376c639645e4bb2f68 to your computer and use it in GitHub Desktop.
Save m4n1ok/af0ab3fc939a3b376c639645e4bb2f68 to your computer and use it in GitHub Desktop.
Encode video directory into mp4 and webm
/**
* 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})
}
[
{
"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