subsync
Version:
Sync your (srt) subtitles using the command line
112 lines (90 loc) • 3.56 kB
JavaScript
var argv = require('optimist').
demand(1).
usage([
"usage: $0 <flags> <spec> [spec, ...] < input.srt > output.srt",
" where <flags> could be:",
" -e : Encoding [i.e: latin1]. Default is utf8.",
" where <spec> is <position>+<shift> or <position>-<shift>",
" position can be hh:mm:ss or @",
" shift is a number in seconds.",
" subsync will shift the subtitles at the specified position by the specified amount in seconds",
"",
" example: $0 @+1 @+20 - start shifting by 1s at the beginning, and end with +20 seconds at the end",
" example: $0 @+0 1:00:00-4 - Start shifting by 0s at the beginning, gradually decrease to -4s at 1 hour and hold it there."].join('\n')).
argv;
function parseTime(str) {
var elems = str.replace(',','.').split(':').map(parseFloat);
return elems[0] * 3600 + elems[1] * 60 + elems[2];
}
function stringifyTime(t) {
var h = Math.floor(t/3600);
var m = Math.floor((t - h * 3600) / 60);
var s = (t - h * 3600 - m * 60).toFixed(3);
return [h,m,s].map(padz).join(':').replace('.',',');
}
function parseSpec(spec) {
var positive = ~spec.indexOf('+');
var timepos = spec.split(/[-+]/);
var t = parseTime(timepos[0]),
shift = parseFloat(timepos[1]);
if (!positive) shift = 0 - shift;
return { at: t, shift: shift };
}
function padz(n) { return n >= 10 ? n : '0'+n; }
function createShifter(specs) {
return function(pos) {
for (var k = 1; specs[k].at < pos; ++k);
var start = specs[k-1], end = specs[k];
var percent = (pos - start.at) / (end.at - start.at)
var shift = start.shift + percent * (end.shift - start.shift);
return pos + shift;
}
}
// Setting encoding. See https://github.com/spion/subsync/issues/2
var encoding = argv.e ? argv.e : 'utf8';
var sub = [];
process.stdin.setEncoding(encoding);
process.stdin.resume();
process.stdin.on('data', function(d) {
sub.push(d);
});
process.stdin.on('end', function() {
var lines = sub.join('').split(/\r*\n/);
var subs = [];
var expect = 'new', last;
lines.forEach(function(l) {
if (expect == 'new') {
last = {id: parseInt(l, 10), text:''};
if (!isNaN(last.id)) expect = 'time';
} else if (expect == 'time') {
var twoTimes = l.split(/\s+-+>\s+/);
last.start = parseTime(twoTimes[0]);
last.end = parseTime(twoTimes[1]);
expect = 'text_end';
}
else {
if (l.match(/^\s*$/)) {
subs.push(last);
expect = 'new';
}
else last.text += l + '\r\n';
}
});
var maxat = subs.reduce(function(acc, el) { return acc > el.end ? acc : el.end; }, 0);
var specs = argv._.map(parseSpec);
specs.unshift({at: -1, shift: specs[0].shift});
specs.push({at: maxat + 1, shift: specs[specs.length - 1].shift});
specs = specs.filter(function(spec) { return !isNaN(spec.at); });
specs = specs.sort(function(a, b) { return a.at - b.at; });
var shifter = createShifter(specs);
subs.forEach(function(sub) {
sub.start = shifter(sub.start);
sub.end = shifter(sub.end);
})
subs.map(function(sub) {
return [sub.id, '\r\n', stringifyTime(sub.start), ' --> ', stringifyTime(sub.end), '\r\n', sub.text, '\r\n'].join('')
}).forEach(function(str) {
process.stdout.write(str);
});
});