Skip to content

Instantly share code, notes, and snippets.

@mirkonasato
Last active November 21, 2016 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mirkonasato/98412f65df28be90efd056346c38752f to your computer and use it in GitHub Desktop.
Save mirkonasato/98412f65df28be90efd056346c38752f to your computer and use it in GitHub Desktop.
Concatenates multiple srt files together, adjusting the times
#!/usr/bin/env node
//
// Concatenates multiple srt files together, adjusting the times
//
// Requires ffprobe and an mp4 file along with each srt file to calculate time offsets
//
// Usage: cat-srt.js video1.srt video2.srt video3.srt output.srt
//
const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;
const BOM = '\uFEFF';
const infiles = process.argv.slice(2, -1);
const outfile = process.argv[process.argv.length - 1];
const TIME_PATTERN = /^(\d{2}):(\d{2}):(\d{2}),(\d{3})$/;
function getStreamDuration(file) {
const seconds = execSync(
`ffprobe -v error -of default=noprint_wrappers=1:nokey=1 -show_entries format=duration ${file}`
);
return Math.floor(parseFloat(seconds.toString()) * 1000);
}
function toMillis(timeStr) {
const groups = TIME_PATTERN.exec(timeStr);
const hours = parseInt(groups[1]);
const minutes = parseInt(groups[2]);
const seconds = parseInt(groups[3]);
const milliseconds = parseInt(groups[4]);
return milliseconds + seconds * 1000 + minutes * 60 * 1000 + hours * 60 * 60 * 1000;
}
function zeroPad(n, digits) {
return ('0'.repeat(digits) + n).slice(-digits);
}
function toTimeStr(millis) {
const milliseconds = zeroPad(millis % 1000, 3);
const seconds = zeroPad(Math.floor(millis / 1000) % 60, 2);
const minutes = zeroPad(Math.floor(millis / (60 * 1000)) % 60, 2)
const hours = zeroPad(Math.floor(millis / (60 * 60 * 1000)), 2);
return `${hours}:${minutes}:${seconds},${milliseconds}`;
}
function shiftTimes(line, offset) {
return line.split(' --> ')
.map(s => toMillis(s.trim()))
.map(t => t + offset)
.map(toTimeStr)
.join(' --> ');
}
const output = [];
let slotId = 1;
let offset = 0;
for (file of infiles) {
const duration = getStreamDuration(path.basename(file, '.srt') + '.mp4');
let input = fs.readFileSync(file, { encoding: 'utf8' });
if (input.charAt(0) === BOM) {
input = input.substring(1);
}
const lines = input.split('\n');
for (let i = 0; i < lines.length; i += 4) {
if (/^\d+$/.test(lines[i])) {
output.push(slotId++);
output.push(shiftTimes(lines[i + 1], offset));
output.push(lines[i + 2] || '…'); // replace empty text to avoid issues
output.push(lines[i + 3]);
}
}
offset += duration;
}
fs.writeFileSync(outfile, BOM + output.join('\n') + '\n', { encoding: 'utf8' });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment