Skip to content

Instantly share code, notes, and snippets.

@frantic1048
Last active December 1, 2016 08:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frantic1048/58fa64f2aa3029de6e643a5e8a1550f5 to your computer and use it in GitHub Desktop.
Save frantic1048/58fa64f2aa3029de6e643a5e8a1550f5 to your computer and use it in GitHub Desktop.
Exract video frames with subtitle according to a .ass subtitle
#!/usr/bin/env node
/**
* loi.js
*
* Extract video frames with subtitle according to .ass subtitle file.
*
* usage:
* loi.js VIDEO_FILE SUBTITLE_FILE
*
* requirement:
* - Node.js
* - ffmpeg executable in PATH
*/
const read = (...argv) =>
require('fs').readFileSync(...argv).toString()
const execffmpeg = (args) =>
require('child_process').spawnSync('ffmpeg', args, {stdio: [0, 0, 0]})
const log = (i) => console.log(i)
const usage = `
Extract video frames with subtitle according to .ass subtitle file.
Usage: loi.js VIDEO_FILE SUBTITLE_FILE
`
let videoPath
let subtitlePath
if (process.argv.length !== 4) {
log('unsatisfied number of input files, exiting...')
log(process.argv)
log(usage)
} else {
videoPath = process.argv[2]
subtitlePath = process.argv[3]
}
/**
* convert 00:00:00.000 to 00.000 format
* @param {string} time 00:00:00.000 format time
* @return {string} 00.000 format time
*/
function normalizeTimestamp (time) {
const [h, m, s] = time
.split(':')
.map(parseFloat)
const resultTime = h * 60 * 60 + m * 60 + s
return `${resultTime.toFixed(2)}`
}
function extractASSDialogues (subtitle) {
const filter = /^Dialogue/
const newline = /\r\n|\r|\n/
const extra = /{.*?}/g
const dialogues = subtitle
.split(newline)
.filter(v => filter.test(v))
.map(v => {
const s = v.replace(extra, '').split(',')
// s[1] is start time
// s[2] is end time
return [s[1], s[9]]
})
.filter(v => v[0] !== '0:00:00.00')
// add 0.2s shift
// to ensure fade-in subtitle fully appeared
.map(v => [normalizeTimestamp(v[0]) + 0.2, v[1]])
return dialogues
}
function captureDialogues (dialogues) {
for (const d of dialogues) {
log(`${d[0]} ${d[1]}`)
const args = [
'-loglevel', 'warning',
'-y',
'-ss', `${d[0]}`,
'-i', `${videoPath}`,
'-vf',
/**
* workaround for rendering correct subtitle
* when using input seeking
* https://trac.ffmpeg.org/ticket/2067#comment:15
*/
`setpts=PTS+${d[0]}/TB,subtitles=${subtitlePath},setpts=PTS-STARTPTS`,
'-vframes', '1',
`${d[1]}.png`
]
execffmpeg(args)
}
}
const dialogues = extractASSDialogues(read(subtitlePath))
captureDialogues(dialogues)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment