subtitle
Version:
Stream-based library for parsing and manipulating subtitles
418 lines (342 loc) • 10.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var stream = require('stream');
var multipipe = _interopDefault(require('multipipe'));
var split2 = _interopDefault(require('split2'));
var stripBom = _interopDefault(require('strip-bom'));
var filter = function filter(callback) {
return new stream.Transform({
objectMode: true,
autoDestroy: false,
transform: function transform(chunk, _encoding, next) {
callback(chunk) ? next(null, chunk) : next();
}
});
};
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var padLeft = function padLeft(value, length) {
if (length === void 0) {
length = 2;
}
return value.toString().padStart(length, '0');
};
var createDuplex = function createDuplex(options) {
return new stream.Duplex(_extends({
objectMode: true,
autoDestroy: false,
read: function read() {}
}, options));
};
function formatTimestamp(timestamp, options) {
if (options === void 0) {
options = {
format: 'SRT'
};
}
var date = new Date(0, 0, 0, 0, 0, 0, timestamp);
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
var ms = Math.floor(timestamp - (hours * 3600000 + minutes * 60000 + seconds * 1000));
return padLeft(hours) + ":" + padLeft(minutes) + ":" + padLeft(seconds) + (options.format === 'WebVTT' ? '.' : ',') + padLeft(ms, 3);
}
var map = function map(mapper) {
var index = 0;
return new stream.Transform({
objectMode: true,
autoDestroy: false,
transform: function transform(chunk, _encoding, callback) {
callback(null, mapper(chunk, index++));
}
});
};
var Parser = /*#__PURE__*/function () {
function Parser(_ref) {
var push = _ref.push;
this.push = push;
this.state = {
expect: 'header',
row: 0,
hasContentStarted: false,
isWebVTT: false,
node: {},
buffer: []
};
}
var _proto = Parser.prototype;
_proto.isIndex = function isIndex(line) {
return /^\d+$/.test(line.trim());
};
_proto.isTimestamp = function isTimestamp(line) {
return RE_TIMESTAMP.test(line);
};
_proto.isVttComment = function isVttComment(line) {
return /^NOTE/.test(line);
};
_proto.getError = function getError(expected, index, row) {
return new Error("expected " + expected + " at row " + (index + 1) + ", but received: \"" + row + "\"");
};
_proto.parseLine = function parseLine(line) {
var contents = this.state.row === 0 ? stripBom(line) : line;
if (!this.state.hasContentStarted) {
if (contents.trim()) {
this.state.hasContentStarted = true;
} else {
return;
}
}
var parse = {
header: this.parseHeader,
id: this.parseId,
timestamp: this.parseTimestamp,
text: this.parseText,
vtt_comment: this.parseVttComment
}[this.state.expect];
parse.call(this, contents);
this.state.row++;
};
_proto.flush = function flush() {
if (this.state.buffer.length > 0) {
this.pushNode();
}
};
_proto.parseHeader = function parseHeader(line) {
if (!this.state.isWebVTT) {
this.state.isWebVTT = /^WEBVTT/.test(line);
if (this.state.isWebVTT) {
this.state.node.type = 'header';
} else {
this.parseId(line);
return;
}
}
this.state.buffer.push(line);
if (!line) {
this.state.expect = 'id';
return;
}
};
_proto.parseId = function parseId(line) {
this.state.expect = 'timestamp';
if (this.state.node.type === 'header') {
this.pushNode();
}
if (this.isIndex(line)) return;
if (this.state.isWebVTT && this.isVttComment(line)) {
this.state.expect = 'vtt_comment';
return;
}
this.parseTimestamp(line);
};
_proto.parseVttComment = function parseVttComment(line) {
this.state.expect = 'vtt_comment';
if (line.trim() === '') {
this.state.expect = 'id';
}
};
_proto.parseTimestamp = function parseTimestamp(line) {
if (!this.isTimestamp(line)) {
throw this.getError('timestamp', this.state.row, line);
}
this.state.node = {
type: 'cue',
data: _extends({}, parseTimestamps(line), {
text: ''
})
};
this.state.expect = 'text';
};
_proto.parseText = function parseText(line) {
if (this.state.buffer.length === 0) {
this.state.buffer.push(line);
return;
}
if (this.isTimestamp(line)) {
var lastIndex = this.state.buffer.length - 1;
if (this.isIndex(this.state.buffer[lastIndex])) {
this.state.buffer.pop();
}
this.pushNode();
this.parseTimestamp(line);
return;
}
if (this.isVttComment(line)) {
this.pushNode();
this.parseVttComment(line);
return;
}
this.state.buffer.push(line);
};
_proto.pushNode = function pushNode() {
if (this.state.node.type === 'cue') {
while (true) {
var lastItem = this.state.buffer[this.state.buffer.length - 1];
if (['', '\n'].includes(lastItem)) {
this.state.buffer.pop();
} else {
break;
}
}
while (true) {
var firstItem = this.state.buffer[0];
if (['', '\n'].includes(firstItem)) {
this.state.buffer.shift();
} else {
break;
}
}
this.state.node.data.text = this.state.buffer.join('\n');
}
if (this.state.node.type === 'header') {
this.state.node.data = this.state.buffer.join('\n').trim();
}
this.push(this.state.node);
this.state.node = {};
this.state.buffer = [];
};
return Parser;
}();
var parse = function parse() {
var parser = new Parser({
push: function push(node) {
return outputStream.push(node);
}
});
var stream = createDuplex({
write: function write(chunk, _encoding, next) {
try {
parser.parseLine(chunk.toString());
} catch (err) {
return next(err);
}
next();
}
});
var splitStream = split2();
splitStream.on('finish', function () {
parser.flush();
stream.push(null);
});
var outputStream = multipipe(splitStream, stream, {
objectMode: true
});
return outputStream;
};
var parseSync = function parseSync(input) {
var buffer = [];
var parser = new Parser({
push: function push(node) {
return buffer.push(node);
}
});
input.replace(/\r\n/g, '\n').split('\n').forEach(function (line) {
return parser.parseLine(line);
});
parser.flush();
return buffer;
};
function parseTimestamp(timestamp) {
var match = timestamp.match(/^(?:(\d{1,}):)?(\d{1,2}):(\d{1,2})[,.](\d{1,3})$/);
if (!match) {
throw new Error('Invalid SRT or VTT time format: "' + timestamp + '"');
}
var hours = match[1] ? parseInt(match[1], 10) * 3600000 : 0;
var minutes = parseInt(match[2], 10) * 60000;
var seconds = parseInt(match[3], 10) * 1000;
var milliseconds = parseInt(match[4], 10);
return hours + minutes + seconds + milliseconds;
}
var RE_TIMESTAMP = /^((?:\d{1,}:)?\d{1,2}:\d{1,2}[,.]\d{1,3}) --> ((?:\d{1,}:)?\d{1,2}:\d{1,2}[,.]\d{1,3})(?: (.*))?$/;
function parseTimestamps(value) {
var match = RE_TIMESTAMP.exec(value);
if (!match) {
throw new Error('Invalid timestamp format');
}
var timestamp = {
start: parseTimestamp(match[1]),
end: parseTimestamp(match[2])
};
if (match[3]) {
timestamp.settings = match[3];
}
return timestamp;
}
var resync = function resync(time) {
return map(function (node) {
if (node.type === 'cue') {
return _extends({}, node, {
data: _extends({}, node.data, {
start: node.data.start + time,
end: node.data.end + time
})
});
}
return node;
});
};
var Formatter = /*#__PURE__*/function () {
function Formatter(options) {
this.options = options;
this.hasReceivedHeader = false;
this.isVTT = options.format === 'WebVTT';
this.index = 1;
}
var _proto = Formatter.prototype;
_proto.format = function format(node) {
var buffer = '';
if (node.type === 'header' && this.isVTT) {
this.hasReceivedHeader = true;
buffer += node.data + "\n\n";
}
if (node.type === 'cue') {
if (!this.hasReceivedHeader && this.isVTT) {
this.hasReceivedHeader = true;
buffer += 'WEBVTT\n\n';
}
buffer += this.formatCue(node.data, this.index++, this.options);
}
return buffer;
};
_proto.formatCue = function formatCue(cue, index, options) {
return ["" + (index > 1 ? '\n' : '') + index, formatTimestamp(cue.start, options) + " --> " + formatTimestamp(cue.end, options) + (options.format === 'WebVTT' && cue.settings ? ' ' + cue.settings : ''), cue.text, ''].join('\n');
};
return Formatter;
}();
var stringify = function stringify(options) {
var formatter = new Formatter(options);
return map(function (chunk) {
return formatter.format(chunk);
});
};
var stringifySync = function stringifySync(list, options) {
var formatter = new Formatter(options);
return list.reduce(function (buffer, node) {
return buffer + formatter.format(node);
}, '');
};
exports.RE_TIMESTAMP = RE_TIMESTAMP;
exports.filter = filter;
exports.formatTimestamp = formatTimestamp;
exports.map = map;
exports.parse = parse;
exports.parseSync = parseSync;
exports.parseTimestamp = parseTimestamp;
exports.parseTimestamps = parseTimestamps;
exports.resync = resync;
exports.stringify = stringify;
exports.stringifySync = stringifySync;
//# sourceMappingURL=subtitle.cjs.development.js.map