UNPKG

subsrt-ts

Version:

Subtitle JavaScript library and command line tool with no dependencies.

136 lines (135 loc) 5 kB
import { buildHandler } from "../handler.js"; const FORMAT_NAME = "vtt"; const helper = { /** * Converts a time string in format of hh:mm:ss.fff or hh:mm:ss,fff to milliseconds. * @param s The time string to convert * @throws {TypeError} If the time string is invalid * @returns Milliseconds */ toMilliseconds: (s) => { const match = /^\s*(\d{1,2}:)?(\d{1,2}):(\d{1,2})(?:[.,](\d{1,3}))?\s*$/.exec(s); if (!match) { throw new TypeError(`Invalid time format: ${s}`); } const hh = match[1] ? parseInt(match[1].replace(":", "")) : 0; const mm = parseInt(match[2], 10); const ss = parseInt(match[3], 10); const ff = match[4] ? parseInt(match[4], 10) : 0; const ms = hh * 3600 * 1000 + mm * 60 * 1000 + ss * 1000 + ff; return ms; }, /** * Converts milliseconds to a time string in format of hh:mm:ss.fff. * @param ms Milliseconds * @returns Time string in format of hh:mm:ss.fff */ toTimeString: (ms) => { const hh = Math.floor(ms / 1000 / 3600); const mm = Math.floor((ms / 1000 / 60) % 60); const ss = Math.floor((ms / 1000) % 60); const ff = Math.floor(ms % 1000); const time = `${(hh < 10 ? "0" : "") + hh}:${mm < 10 ? "0" : ""}${mm}:${ss < 10 ? "0" : ""}${ss}.${ff < 100 ? "0" : ""}${ff < 10 ? "0" : ""}${ff}`; return time; }, }; /** * Parses captions in WebVTT format (Web Video Text Tracks Format). * @param content The subtitle content * @param options Parse options * @returns Parsed captions */ const parse = (content, options) => { var _a; let index = 1; const captions = []; const parts = content.split(/\r?\n\s*\n/); for (const part of parts) { // WebVTT data const regex = /^([^\r\n]+\r?\n)?((?:\d{1,2}:)?\d{1,2}:\d{1,2}(?:[.,]\d{1,3})?)\s*-->\s*((?:\d{1,2}:)?\d{1,2}:\d{1,2}(?:[.,]\d{1,3})?)[^\S\r\n]?.*\r?\n([\s\S]*)$/; const match = regex.exec(part); if (match) { const caption = {}; caption.type = "caption"; caption.index = index++; if (match[1]) { caption.cue = match[1].replace(/[\r\n]*/g, ""); } caption.start = helper.toMilliseconds(match[2]); caption.end = helper.toMilliseconds(match[3]); caption.duration = caption.end - caption.start; caption.content = match[4]; caption.text = caption.content .replace(/<[^>]+>/g, "") // <b>bold</b> or <i>italic</i> .replace(/\{[^}]+\}/g, ""); // {b}bold{/b} or {i}italic{/i} captions.push(caption); continue; } // WebVTT meta // FIXME: prevent backtracking // eslint-disable-next-line regexp/no-super-linear-backtracking const meta = (_a = /^([A-Z]+)(\r?\n([\s\S]*))?$/.exec(part)) !== null && _a !== void 0 ? _a : /^([A-Z]+)\s+([^\r\n]*)$/.exec(part); if (meta) { const caption = {}; caption.type = "meta"; caption.name = meta[1]; if (meta[3]) { caption.data = meta[3]; } captions.push(caption); continue; } if (options.verbose) { console.warn("Unknown part", part); } } return captions; }; /** * Builds captions in WebVTT format (Web Video Text Tracks Format). * @param captions The captions to build * @param options Build options * @returns The built captions string in WebVTT format */ const build = (captions, options) => { var _a; const eol = (_a = options.eol) !== null && _a !== void 0 ? _a : "\r\n"; let content = `WEBVTT${eol}${eol}`; for (let i = 0; i < captions.length; i++) { const caption = captions[i]; if (caption.type === "meta") { if (caption.name === "WEBVTT") { continue; } content += caption.name + eol; content += typeof caption.data === "string" ? caption.data + eol : ""; content += eol; continue; } if (!caption.type || caption.type === "caption") { content += (i + 1).toString() + eol; content += `${helper.toTimeString(caption.start)} --> ${helper.toTimeString(caption.end)}${eol}`; content += caption.text + eol; content += eol; continue; } if (options.verbose) { console.log("SKIP:", caption); } } return content; }; /** * Detects whether the content is in WebVTT format. * @param content The subtitle content * @returns Whether the content is in WebVTT format */ const detect = (content) => { /* WEBVTT ... */ return /^\s*WEBVTT\r?\n/.test(content); }; export default buildHandler({ name: FORMAT_NAME, build, detect, helper, parse }); export { FORMAT_NAME as name, build, detect, helper, parse };