UNPKG

subtitle

Version:

Stream-based library for parsing and manipulating subtitles

418 lines (342 loc) 10.1 kB
'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