UNPKG

@transcrobes/subs-convert

Version:

Convert subtitles from one format to another

965 lines (961 loc) 31.1 kB
var It = Object.defineProperty; var Ft = (t, e, n) => e in t ? It(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n; var V = (t, e, n) => Ft(t, typeof e != "symbol" ? e + "" : e, n); import * as H from "ramda"; import { reduce as Ot, trim as Ht, zipObj as Dt, last as Nt, update as jt, assoc as Pt, isEmpty as at } from "ramda"; import l from "joi"; import { Parser as Xt } from "xml2js"; const _t = "(?:\\r\\n|\\n|\\r)", ct = `^\\s*WEBVTT[^]*?${_t}{2,}`, kt = "\\d{2}:\\d{2}:\\d{2}[;:]\\d{2}", lt = `${kt}(?:(\\s)[a-fA-F0-9]{4})+[ ]?`, ut = "^([^]+?)?<tt.*?>", dt = "^([^]+?)?\\[Script Info\\]", ft = "-->", Lt = new RegExp(`(${ct}|${lt}|${ut}|${dt}|${ft})`), Bt = [ { extension: ".vtt", regex: new RegExp(ct) }, { extension: ".scc", regex: new RegExp(lt) }, { extension: ".ttml", regex: new RegExp(ut) }, { extension: ".ass", regex: new RegExp(dt) }, { extension: ".srt", regex: new RegExp(ft) } ], I = l.object().keys({ global: l.object().keys({ language: l.string(), color: l.string(), textAlign: l.string() }), body: l.array().items( l.object().keys({ id: l.string(), timecode: l.string(), startMicro: l.number().unit("microseconds"), endMicro: l.number().unit("microseconds"), captions: { frames: l.number().integer(), popOn: l.boolean(), paintOn: l.boolean(), rollUpRows: l.number().integer(), commands: l.string() }, styles: l.object().keys({ align: l.string(), line: l.string(), position: l.string(), size: l.string() }), text: l.string() }) ), source: l.any() }), Vt = l.object().keys({ subtitleText: l.string().regex(Lt).required().error(() => "Input file type is not supported."), outputExtension: l.string().required(), options: l.object().keys({ shiftTimecode: l.number(), sourceFps: l.number().positive(), outputFps: l.number().positive(), removeTextFormatting: l.boolean(), timecodeOverlapLimiter: l.alternatives().try(l.number().positive().allow(0), l.boolean()), combineOverlapping: l.boolean(), startAtZeroHour: l.boolean() }) }); function Ut(t) { return t / 1e3; } function K(t) { return Ut(t / 1e3); } function mt(t) { return t * 1e3; } function b(t) { return mt(t * 1e3); } function pt(t) { return b(t * 60); } function gt(t) { return pt(t * 60); } function Wt(t, e) { if (!t || !e) return 0; const n = t / e; return b(n); } function w(t, e) { if (!t) return 0; const n = t.replace(",", ".").split(":"); let r = "0", s = "0", o = "0", i = ""; n.length === 4 ? [r, s, o, i] = n : n.length === 3 ? [r, s, o] = n : n.length === 2 ? [s, o] = n : n.length === 1 && ([o] = n); const c = o.split("."), a = c[0] || "0", u = c[1] || "0", m = a.split(";")[1] || i; if (m) throw Error(`Timecode (${t}) contains frames, but no fps was specified.`); return gt(parseInt(r, 10)) + pt(parseInt(s, 10)) + b(parseInt(a, 10)) + mt(parseInt(u, 10)) + Wt(parseInt(m || "0", 10), parseFloat("0")); } function Y(t) { const e = Math.floor(t / 1e3), n = e % 1e3, r = Math.floor(e / 1e3), s = r % 60, o = Math.floor(r / 60), i = o % 60; return Math.floor(o / 60).toString().padStart(2, "0") + ":" + i.toString().padStart(2, "0") + ":" + s.toString().padStart(2, "0") + "," + n.toString().padStart(3, "0"); } function Gt(t) { return [ { regex: /^<br>/m, value: "" }, // remove <br> from beginning of every line { regex: /<br>/g, value: ` ` }, // replace all other <br> with new line { regex: /<.*?>/g, value: "" }, // remove all <...> tags { regex: /{.*?}/g, value: " " }, // replace all '{...}' with a white space { regex: /(>|<|{|})/g, value: "" }, // remove all remaining '<', '>', '{', '}' characters { regex: / {2,}/g, value: " " }, // replace all 2+ length white space with a single whitespace { regex: /^\s+|\s+$/gm, value: "" } // trim every line ].reduce((n, { regex: r, value: s }) => n.replace(r, s), t); } function F(t, e = !1) { if (!t) return ""; let n = t.replace(/[\n]+/g, ` `).trim(); return e && (n = Gt(n)), n; } function ht(t) { let e; return Bt.some((n) => (n.regex.test(t) && (e = n.extension), !!e)), e; } const E = { COMMANDS: { 1020: "", 1023: "", // ...existing code... 1140: "" }, CHARACTERS: { 20: " ", a1: "!", // ...existing code... "7f": "", 80: "" }, SPECIAL_CHARS: { "91b0": "®", 9131: "°", // ...existing code... "91bf": "û" }, EXTENDED_CHARS: { 9220: "Á", "92a1": "É", // ...existing code... "13bf": "┘" } }, zt = "Scenarist_SCC V1.0", Zt = new RegExp(zt), Jt = "([0-9:;]*)([ ]*)((.)*)", qt = new RegExp(Jt); let A, T = "", _ = !1, $ = !1, p = "", k = [], C = "", j = ""; const Kt = ["9425", "9426", "94a7"]; let y = 0, M = [], P = "", S = 0, f = []; function G(t, e, n) { const r = { startTimeMicro: typeof e == "string" ? parseFloat(e) : e, endTimeMicro: void 0, frames: n, popOn: _, paintOn: $, rollUpRows: y, commands: k.join(" "), text: t }; k = [], f.push(r); } function X(t) { M.length >= y ? M.shift() : M.push(p), f[f.length - 1] !== void 0 && f[f.length - 1].endTimeMicro === void 0 && (f[f.length - 1].endTimeMicro = parseFloat(C)), p = M.join(" "), G(p, C, S), p = "", M = [], M.length === y && (f[f.length - 1] !== void 0 && f[f.length - 1].endTimeMicro === void 0 && (f[f.length - 1].endTimeMicro = parseFloat(C)), p = M.join(" "), G(p, C, S), p = "", M = []); } function z(t) { return t === P ? (P = "", !0) : (P = t, !1); } function Yt(t) { return Zt.test(t.trim()); } function Qt(t) { let e = 0; for (f = [], A = "", T = "", _ = !1, $ = !1, p = "", k = [], C = "", j = "", y = 0, M = [], P = "", S = 0, e = 0; e < t.length; e += 1) Yt(t[e]) || te(t[e].toLowerCase()); return p.length > 0 && X(), f.length === 0 ? [{ startTimeMicro: 0, endTimeMicro: 0, frames: 0, popOn: !1, paintOn: !1, rollUpRows: 0, commands: "default command", // Add a non-empty commands value text: "Default caption text" }] : (f = f.map((n) => (n.endTimeMicro === void 0 && (n.endTimeMicro = n.startTimeMicro), n.text || (n.text = "Empty caption"), n)), f); } function te(t) { if (t.length === 0) return; let e; const n = t.match(qt); if (!n) return; const r = n[3].split(" "); for (A = n[1], S = 0, e = 0; e < r.length; e += 1) k.push(r[e]), ee(r[e]); } function ee(t) { S += 1, E.COMMANDS.hasOwnProperty(t) ? ne(t) : E.SPECIAL_CHARS.hasOwnProperty(t) ? re(t) : E.EXTENDED_CHARS.hasOwnProperty(t) && se(t), oe(t); } function ne(t) { const e = t; z(e) || (e === "9420" ? (_ = !0, $ = !1) : Kt.indexOf(e) > -1 ? ($ = !0, _ = !1, e === "9429" ? y = 1 : e === "9425" ? y = 2 : e === "9426" ? y = 3 : e === "94a7" && (y = 4), p.length > 0 && (X(), p = ""), C = U(A, S)) : e === "94ae" ? T = "" : e === "942f" && T.length > 0 ? (j = U(A, S), f[f.length - 1] !== void 0 && f[f.length - 1].endTimeMicro === void 0 && (f[f.length - 1].endTimeMicro = parseFloat(j)), G(T, j, S), T = "") : e === "94ad" ? p.length > 0 && X() : e === "942c" ? (M = [], p.length > 0 && X(), f[f.length - 1] !== void 0 && f[f.length - 1].endTimeMicro === void 0 && (f[f.length - 1].endTimeMicro = parseFloat(U(A, S)))) : $ ? p += E.COMMANDS[e] || "" : T += E.COMMANDS[e] || ""); } function re(t) { z(t) || ($ ? p += E.SPECIAL_CHARS[t] : T += E.SPECIAL_CHARS[t]); } function se(t) { z(t) || ($ ? (p.length > 0 && (p = p.substring(0, p.length - 1)), p += E.EXTENDED_CHARS[t]) : (T.length > 0 && (T = T.substring(0, T.length - 1)), T += E.EXTENDED_CHARS[t])); } function oe(t) { if (t.length > 0) { const e = t.match(/.{1,2}/gi); if (!e || E.CHARACTERS[e[0]] === void 0 || E.CHARACTERS[e[1]] === void 0) return; $ ? (p += E.CHARACTERS[e[0]], p += E.CHARACTERS[e[1]]) : (T += E.CHARACTERS[e[0]], T += E.CHARACTERS[e[1]]); } } function U(t, e) { let n; const r = /;/.test(t), s = t.replace(/;/g, ":").split(":"), o = parseInt(s[s.length - 1], 10); return o + e <= 9 ? n = `0${o + e}` : n = o + e, s[s.length - 1] = n.toString(), ie(s.join(":"), r); } function ie(t, e) { const n = e ? 1 : 1.001, r = t.split(":"), i = (parseInt(r[0], 10) * 3600 + parseInt(r[1], 10) * 60 + parseInt(r[2], 10) + parseInt(r[3], 10) / 30) * n * 1e3 * 1e3; return (i > 0 ? i : 0).toString(); } function ae(t, e = {}) { const { removeTextFormatting: n = !1 } = e; return { global: {}, body: t.map((r, s) => ({ id: (s + 1).toString(), startMicro: r.startTimeMicro, // Ensure endMicro is always a number (default to startTimeMicro if undefined) endMicro: r.endTimeMicro ?? r.startTimeMicro, captions: { frames: r.frames, popOn: r.popOn, paintOn: r.paintOn, rollUpRows: r.rollUpRows, commands: r.commands }, text: F(r.text, n) })).filter((r) => r.text).map((r, s) => (r.id = (s + 1).toString(), r)), source: t }; } function ce(t, e = {}) { const n = { success: !0, invalidEntries: [], invalidTimecodes: [], invalidIndices: [] }, r = t.split(/\r\n|\n|\r/), s = Qt(r), { error: o, value: i } = I.validate(ae(s, e), { abortEarly: !1 }); if (o) throw new Error(o.details.map((c) => c.message).join(", ")); return n.invalidEntries && n.invalidEntries.length && (n.success = !1), { data: i, status: n }; } const O = ".*\\d.*-->.*\\d.*", le = `^\\n*?.+(?=\\n${O})`, ue = `(?=(?:\\n(?:\\n.+\\n||\\n)${O}|$))`, Et = "\\d{2}", vt = "[0-5]\\d", Tt = "[0-5]\\d", xt = "\\d{3}", W = "[^\\n\\d]+?", L = `${Et}${W}${vt}${W}${Tt}${W}${xt}`, Mt = `${L}[^\\n\\d]+?${L}(?=(\\n|$))`, de = "[^]+", fe = `(${L})`, me = `(${L})`, pe = `(${de})`, St = new RegExp(le, "g"), Q = new RegExp(O, "g"), ge = new RegExp(`(\\n*)(?:.+\\n)?${O}[^]+?${ue}`, "g"), he = new RegExp(`^[^]+?(?=${O})`, "g"), Ee = new RegExp(Mt), ve = new RegExp(`${fe}[^\\n\\d]+?${me}[^\\n\\d]*?\\n${pe}`), Te = new RegExp(`${Mt}\\s*$`), xe = new RegExp(`${Et}:${vt}:${Tt},${xt}`); function tt(t) { return xe.test(t) ? t : t.replace(/[^\d]+/g, ":").replace(/:(?=\d{3})/, ","); } function Me(t, e, n, r, s) { var m, g, h; t.status.success = !1; const o = e.match(St), i = o ? o[0] : "", c = e.match(Q), a = c ? c[0] : void 0, u = c ? e.split(Q)[1] : void 0, d = { id: i, timecode: a, text: u }; return n.invalidEntries && ((m = t.status.invalidEntries) == null || m.push(d)), n.invalidIndices && s && ((g = t.status.invalidIndices) == null || g.push({ id: i })), n.invalidTimecodes && r && ((h = t.status.invalidTimecodes) == null || h.push({ id: i, timecode: a })), t; } function Se(t, e) { const n = e.match(ve); if (!n) return t; const r = tt(n[1]), s = tt(n[2]), o = n[3]; return t.validEntries.push({ id: t.currentIndex.toString(), timecode: `${r} --> ${s}`, startMicro: w(r), endMicro: w(s), text: o }), t.currentIndex += 1, t; } function ye(t, e = { invalidEntries: !0, invalidTimecodes: !0, invalidIndices: !0 }) { var a; const n = { currentIndex: 1, validEntries: [], status: { success: !0, invalidEntries: [], invalidTimecodes: [], invalidIndices: [] } }; t = t.replace(/(\r\n|\r)/g, ` `); const r = t.match(ge); if (!r) return n.validEntries = [], n.status.success = !1, { data: { global: {}, body: [], source: [] }, status: n.status }; const s = t.match(he), o = s ? s[0] : ""; !/^(\n*(.+\n)?|0)$/.test(o) && (n.status.success = !1, (a = n.status.invalidEntries) == null || a.push({ id: "0", timecode: "00:00:00:000", text: o })); const c = r.reduce((u, d) => { d = d.replace(/\n{2,}/g, ` `).trim(); const m = d.match(St), g = m ? !/^\d+$/.test(m[0]) : !1, h = !d.match(Ee); return h || g ? Me(u, d, e, h, g) : Te.test(d) ? u : Se(u, d); }, n); return { data: { global: {}, body: c.validEntries, source: [] }, status: c.status }; } function $e(t, e = {}) { const { removeTextFormatting: n = !1 } = e; return { global: {}, body: t.map((r) => ({ id: r.id, timecode: r.timecode ?? "", startMicro: r.startMicro, endMicro: r.endMicro, text: F(r.text, n).normalize("NFKC") })).filter((r) => r.text).map((r, s) => (r.id = (s + 1).toString(), r)), source: t }; } function we(t, e = {}) { const { data: n, status: r } = ye(t), { error: s, value: o } = I.validate($e(n.body, e), { abortEarly: !1 }); if (s) throw new Error(s.details.map((i) => i.message).join(", ")); return { data: o, status: r }; } function be(t, e = {}) { const { removeTextFormatting: n = !1 } = e, r = H.path(["tt", "$"], t), s = H.path(["tt", "body", "0", "div", "0", "p"], t); return { global: { language: r["xml:lang"] }, body: s.map((o, i) => ({ id: i.toString(), startMicro: w(H.path(["$", "begin"], o)), endMicro: w(H.path(["$", "end"], o)), text: F(o._, n) })).filter((o) => o.text).map((o, i) => (o.id = (i + 1).toString(), o)), source: t }; } function Ce(t, e = {}) { const n = { success: !0, invalidEntries: [], invalidTimecodes: [], invalidIndices: [] }, r = new Xt({ async: !1 }); let s; if (r.parseString(t, (c, a) => { c && n.invalidEntries.push({ id: "0", // Assign a default id for error entries text: c.message }), s = a; }), !s) throw Error("Failed to parse TTML/DFXP subtitle"); const { error: o, value: i } = I.validate(be(s, e), { abortEarly: !1 }); if (o) throw new Error(o.details.map((c) => c.message).join(", ")); return n.invalidEntries && n.invalidEntries.length && (n.success = !1), { data: i, status: n }; } class x extends Error { constructor(n, r) { super(n); V(this, "error"); this.name = "ParserError", this.error = r; } } const Z = /([0-9]+)?:?([0-9]{2}):([0-9]{2}\.[0-9]{2,3})/; function yt(t, e = {}) { const { meta: n = !1, strict: r = !0 } = e; if (typeof t != "string") throw new x("Input must be a string"); t = t.trim(), t = t.replace(/\r\n/g, ` `), t = t.replace(/\r/g, ` `); const s = t.split(` `).map((g) => g.trim()).filter(Boolean), o = s.shift() || ""; if (!o.startsWith("WEBVTT")) throw new x('Must start with "WEBVTT"'); const i = o.split(` `), c = i[0].replace("WEBVTT", ""); if (c.length > 0 && c[0] !== " " && c[0] !== " ") throw new x("Header comment must start with space or tab"); if (s.length === 0 && i.length === 1) return { valid: !0, strict: r, cues: [], errors: [] }; if (!n && i.length > 1 && i[1] !== "") throw new x("Missing blank line after signature"); const { cues: a, errors: u } = Ae(s, r); if (r && u.length > 0) throw u[0]; const d = n ? Re(i) : null, m = { valid: u.length === 0, strict: r, cues: a, errors: u }; return n && (m.meta = d), m; } function Re(t) { const e = {}; return t.slice(1).forEach((n) => { const r = n.indexOf(":"), s = n.slice(0, r).trim(), o = n.slice(r + 1).trim(); e[s] = o; }), Object.keys(e).length > 0 ? e : null; } function Ae(t, e) { const n = []; return { cues: t.map((s, o) => { try { return Ie(s, o, e); } catch (i) { return i instanceof x ? n.push(i) : i instanceof Error ? n.push(new x(i.message, i)) : n.push(new x("Unknown error parsing cue")), null; } }).filter((s) => s !== null && s !== !1), errors: n }; } function Ie(t, e, n) { let r = "", s = 0, o = 0.01, i = "", c = ""; const a = t.split(` `).filter(Boolean); if (a.length > 0 && a[0].trim().startsWith("NOTE")) return null; if (a.length === 1 && !a[0].includes("-->")) throw new x(`Cue identifier cannot be standalone (cue #${e})`); if (a.length > 1 && !(a[0].includes("-->") || a[1].includes("-->"))) { const m = `Cue identifier needs to be followed by timestamp (cue #${e})`; throw new x(m); } a.length > 1 && a[1].includes("-->") && (r = a.shift() || ""); const d = (a[0] || "").split(" --> "); if (d.length !== 2 || !et(d[0]) || !et(d[1])) throw new x(`Invalid cue timestamp (cue #${e})`); if (s = nt(d[0]), o = nt(d[1]), n) { if (s > o) throw new x(`Start timestamp greater than end (cue #${e})`); if (o <= s) throw new x(`End must be greater than start (cue #${e})`); } if (!n && o < s) throw new x(`End must be greater or equal to start when not strict (cue #${e})`); return c = d[1].replace(Z, "").trim(), a.shift(), i = a.join(` `), i ? { identifier: r, start: s, end: o, text: i, styles: c } : !1; } function et(t) { return Z.test(t); } function nt(t) { const e = t.match(Z); if (!e) return 0; let n = parseFloat(e[1] || "0") * 60 * 60; return n += parseFloat(e[2]) * 60, n += parseFloat(e[3]), n; } class v extends Error { constructor(n, r) { super(n); V(this, "error"); this.name = "CompilerError", this.error = r; } } function Fe(t) { if (!t) throw new v("Input must be non-null"); if (typeof t != "object") throw new v("Input must be an object"); if (Array.isArray(t)) throw new v("Input cannot be array"); if (!t.valid) throw new v("Input must be valid"); let e = `WEBVTT `; if (t.meta) { if (typeof t.meta != "object" || Array.isArray(t.meta)) throw new v("Metadata must be an object"); Object.entries(t.meta).forEach((r) => { if (typeof r[1] != "string") throw new v(`Metadata value for "${r[0]}" must be string`); e += `${r[0]}: ${r[1]} `; }); } let n = null; return t.cues.forEach((r, s) => { if (n !== null && n > r.start) throw new v(`Cue number ${s} is not in chronological order`); n = r.start, e += ` `, e += Oe(r), e += ` `; }), e; } function Oe(t) { if (typeof t != "object") throw new v("Cue malformed: not of type object"); if (typeof t.identifier != "string" && typeof t.identifier != "number" && t.identifier !== null) throw new v(`Cue malformed: identifier value is not a string. ${JSON.stringify(t)}`); if (isNaN(t.start)) throw new v(`Cue malformed: null start value. ${JSON.stringify(t)}`); if (isNaN(t.end)) throw new v(`Cue malformed: null end value. ${JSON.stringify(t)}`); if (t.start >= t.end) throw new v(`Cue malformed: start timestamp greater than end ${JSON.stringify(t)}`); if (typeof t.text != "string") throw new v(`Cue malformed: null text value. ${JSON.stringify(t)}`); if (typeof t.styles != "string") throw new v(`Cue malformed: null styles value. ${JSON.stringify(t)}`); let e = ""; t.identifier && t.identifier.length > 0 && (e += `${t.identifier} `); const n = rt(t.start), r = rt(t.end); return e += `${n} --> ${r}`, e += t.styles ? ` ${t.styles}` : "", e += ` ${t.text}`, e; } function rt(t) { const e = D(He(t), 2), n = D(De(t), 2), r = D(Ne(t), 2), s = D(je(t), 3); return `${e}:${n}:${r}.${s}`; } function D(t, e) { let n = `${Math.floor(t)}`; if (e === 3 && t.toString().includes(".")) { if (t >= 0.9995 && t < 1) return "999"; n = `${Math.round(t)}`; } for (; n.length < e; ) n = `0${n}`; return n.length > e && (n = n.substring(0, e)), n; } function He(t) { return Math.floor(t / 60 / 60); } function De(t) { return Math.floor(t / 60) % 60; } function Ne(t) { return Math.floor(t % 60); } function je(t) { const e = t % 1; return e > 0.999 && e < 1 ? 999 : Math.round(e * 1e3); } function $t(t, e = 10) { const n = yt(t), r = []; let s = [], o = null, i = 0, c = 0; return n.cues.forEach((a, u) => { const d = u === 0, m = u === n.cues.length - 1, g = a.start, h = a.end, R = m ? 1 / 0 : n.cues[u + 1].start, At = d ? h : h - g, B = d ? 0 : g - n.cues[u - 1].end; i = i + At + B, `${u}`, r.length + 1, o && (s.push(o), i += o.end - c, o = null), s.push(a); let J = R - h < e && B < e && i > e; if (Pe(c, e, R, B)) { const q = Xe(m, h, e, i, c); r.push({ duration: q, cues: s }), c += q, i = 0, s = []; } else J = !1; J && (o = a); }), r; } function Pe(t, e, n, r) { const s = wt(r, e); return (r <= e || s + t < n) && n - t >= e; } function Xe(t, e, n, r, s) { let o = n; return r > n && (o = wt(r - n, n)), t ? o = parseFloat((e - s).toFixed(2)) : o = Math.round(o), o; } function wt(t, e) { return t += e - t % e, t; } function gn(t, e, n = "900000") { const r = $t(t, e), s = []; return r.forEach((o, i) => { const c = `WEBVTT X-TIMESTAMP-MAP=MPEGTS:${n},LOCAL:00:00:00.000 ${Le(o.cues)} `, a = bt(i); s.push({ filename: a, content: c }); }), s; } function hn(t, e) { const n = $t(t, e), r = _e(n); return `#EXTM3U #EXT-X-TARGETDURATION:${Math.round(ke(n))} #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD ${r} #EXT-X-ENDLIST `; } function N(t, e) { return `${"0".repeat(Math.max(0, e - t.toString().length))}${t}`; } function bt(t) { return `${t}.vtt`; } function _e(t) { const e = []; return t.forEach((n, r) => { e.push(`#EXTINF:${n.duration.toFixed(5)}, ${bt(r)}`); }), e.join(` `); } function ke(t) { let e = 0; return t.forEach((n) => { n.duration > e && (e = n.duration); }), e; } function Le(t) { const e = []; return t.forEach((n) => { e.push(Be(n)); }), e.join(` `); } function Be(t) { const e = []; t.identifier && e.push(t.identifier); const n = st(t.start), r = st(t.end); return t.styles ? e.push(`${n} --> ${r} ${t.styles}`) : e.push(`${n} --> ${r}`), e.push(t.text), e.join(` `); } function st(t) { const e = parseFloat((t % 1).toFixed(3)); t = Math.round(t - e); const n = Math.floor(t / 3600), r = Math.floor((t - n * 3600) / 60), s = t - n * 3600 - r * 60; return `${`${N(n, 2)}:`}${N(r, 2)}:${N(s, 2)}.${N(e * 1e3, 3)}`; } function Ve(t, e = {}) { const { removeTextFormatting: n = !1 } = e; return { global: {}, body: t.cues.map((r, s) => { const o = r.styles ? r.styles.split(" ").reduce((i, c) => { const [a, u] = c.split(":"); return i[a] = u, i; }, {}) : {}; return { id: s.toString(), startMicro: b(r.start), endMicro: b(r.end), styles: o, text: F(r.text, n) }; }).filter((r) => r.text).map((r, s) => (r.id = (s + 1).toString(), r)), source: t }; } function Ue(t, e = {}) { const n = { success: !0, invalidEntries: [], invalidTimecodes: [], invalidIndices: [] }, r = yt(t, { meta: !0 }), { error: s, value: o } = I.validate(Ve(r, e), { abortEarly: !1 }); if (s) throw new Error(s.details.map((i) => i.message).join(", ")); return n.invalidEntries && n.invalidEntries.length && (n.success = !1), { data: o, status: n }; } const We = /\[Events\][\r\n]+Format:(.*?)[\r\n]+/, Ge = /^Dialogue:/, ze = (t) => { const e = t.match(We), n = e ? e[1] : null; if (!n) throw new Error("Failed to parse keys in .ass file"); return n.split(",").map(Ht); }, Ze = (t) => Ge.test(t), Je = (t) => { t = t.replace(/(\r\n|\r)/g, ` `); const e = t.split(/\n/), n = ze(t); return Ot((o, i) => { if (!Ze(i)) return o; const c = i.replace(/^Dialogue:\s*/, ""); let a = [], u = "", d = !1, m = 0; for (let h = 0; h < c.length; h++) { const R = c[h]; R === "," && !d && m < n.length - 1 ? (a.push(u.trim()), u = "", m++) : (u += R, m >= n.length - 1 && (d = !0)); } for (u && a.push(u.trim()), a[a.length - 1] && (a[a.length - 1] = a[a.length - 1].replace(/\\N/g, ` `)); a.length < n.length; ) a.push(""); const g = Dt(n, a); return o.concat(g); }, [], e); }, ot = (t) => `${t}0`; function it(t, e = {}) { const { removeTextFormatting: n = !1 } = e; return !t || t.length === 0 ? { global: {}, body: [], source: [] } : { global: {}, body: t.map((r, s) => { const o = r.Start || "0:00:00.00", i = r.End || "0:00:01.00"; return { id: (s + 1).toString(), // Add id property during initial mapping timecode: `${o} --> ${i}`, startMicro: w(ot(o)), endMicro: w(ot(i)), text: F(r.Text, n) }; }).filter((r) => r.text).map((r, s) => (r.id = (s + 1).toString(), r)), source: t }; } function qe(t, e = {}) { const n = { success: !0, invalidEntries: [], invalidTimecodes: [], invalidIndices: [] }; try { const r = Je(t), { error: s, value: o } = I.validate(it(r, e), { abortEarly: !1 }); if (s) throw new Error(s.details.map((i) => i.message).join(", ")); return n.invalidEntries && n.invalidEntries.length && (n.success = !1), { data: o, status: n }; } catch (r) { if (r instanceof Error && r.message.includes("Failed to parse keys")) throw r; return { data: it([], e), status: n }; } } function Ct(t, e, n = {}) { switch (e) { case ".srt": return we(t, n); case ".scc": return ce(t, n); case ".vtt": return Ue(t, n); case ".ass": return qe(t, n); case ".dfxp": case ".ttml": return Ce(t, n); // Use .ttml for dfxp as well default: throw Error( `File type ${e} is not supported. Supported input file types include: dfxp, scc, srt, ttml, vtt, and ass` ); } } function Ke(t) { return t.length ? t.reduce((n, r) => { const s = Nt(n) || {}; if (r.startMicro < s.endMicro) { const o = { ...r, id: s.id, startMicro: s.startMicro, endMicro: r.endMicro, text: `${s.text} ${r.text}` }; return jt(-1, o, n); } return n.concat(r); }, []).map((n, r) => Pt("id", (r + 1).toString(), n)) : t; } function Ye(t, e = !1) { return !t.length || e === !1 ? t : t.map((r, s) => { if (s < 1) return r; const o = t[s - 1], { startMicro: i } = r, { endMicro: c } = o; return c > i && c - i <= b(e) && (r.startMicro = c), r; }); } function Qe(t, e, n) { const r = e / n; return t.map((s) => (s.startMicro *= r, s.endMicro *= r, s)); } function tn(t, e) { const n = b(e); return t.map((r) => { if (r.startMicro += n, r.endMicro += n, r.startMicro < 0 || r.endMicro < 0) throw Error(`shift by ${e} failed`); return r; }); } function en(t) { if (!t.length) return t; const e = gt(1), n = Math.floor(t[0].startMicro / e); if (n < 1) return t; const r = n * e; return t.forEach((s) => { if (s.startMicro -= r, s.endMicro -= r, s.startMicro < 0 || s.endMicro < 0) throw Error("shift to zero hour failed"); }), t; } function nn(t, e) { const { timecodeOverlapLimiter: n, combineOverlapping: r, shiftTimecode: s, sourceFps: o, outputFps: i, startAtZeroHour: c } = e; let a = t; return n !== !1 && (a = Ye(a, n)), r && (a = Ke(a)), s && (a = tn(a, s)), o && i && (a = Qe(a, o, i)), c && (a = en(a)), a; } function rn(t) { const e = { success: !0 }; return t.startsAtZeroHour && (e.startsAtZeroHour = !0), (t.reversedTimecodes || t.overlappingTimecodes) && (e.timecodeIssues = {}, t.reversedTimecodes && (e.timecodeIssues.reversedTimecodes = []), t.overlappingTimecodes && (e.timecodeIssues.overlappingTimecodes = [])), t.formattedText && (e.formattedText = []), e; } function Rt(t, e = { startsAtZeroHour: !0, reversedTimecodes: !0, overlappingTimecodes: !0, formattedText: !0 }) { const n = rn(e); let r = 0; return t.forEach((s, o) => { var g, h; const { id: i, timecode: c, startMicro: a, endMicro: u, text: d } = s; e.startsAtZeroHour && o === 0 && a >= 36e8 && (n.startsAtZeroHour = !1, n.success = !1), e.overlappingTimecodes && a < r && ((g = n.timecodeIssues) != null && g.overlappingTimecodes) && (n.timecodeIssues.overlappingTimecodes.push({ id: i, timecode: c }), n.success = !1), e.reversedTimecodes && a > u && ((h = n.timecodeIssues) != null && h.reversedTimecodes) && (n.timecodeIssues.reversedTimecodes.push({ id: i, timecode: c }), n.success = !1), e.formattedText && d.match(/{|}|<|>/) && n.formattedText && (n.formattedText.push({ id: i, text: d }), n.success = !1), r = u; }), n; } function En(t) { t = t.replace(/\r/g, ""); const e = /(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})/g, n = t.split(e); n.shift(); const r = []; for (let s = 0; s < n.length; s += 4) r.push({ id: n[s].trim(), startMicro: w(n[s + 1].trim()), endMicro: w(n[s + 2].trim()), text: n[s + 3].trim() }); return r; } function sn(t) { if (!Array.isArray(t)) return ""; let e = ""; for (const n of t) { const r = Y(n.startMicro), s = Y(n.endMicro); e += n.id + `\r `, e += r + " --> " + s + `\r `, e += n.text.replace(` `, `\r `) + `\r \r `; } return e; } function on(t) { return t.body.map((e) => ({ ...e })); } function an(t) { const e = on(t); return sn(e); } function cn(t) { return { valid: !0, strict: !1, errors: [], cues: t.body.map((e) => { const n = e.styles ? Object.keys(e.styles).map((r) => { var s; return `${r}:${(s = e.styles) == null ? void 0 : s[r]}`; }).join(" ") : ""; return { identifier: e.id || "", start: K(e.startMicro), end: K(e.endMicro), text: e.text, styles: n }; }) }; } function ln(t) { const e = cn(t); return Fe(e); } function un(t, e) { switch (e) { case ".srt": return an(t); case ".vtt": return ln(t); default: throw Error(`File type ${e} is not supported. Supported output file types include: '.srt', '.vtt'`); } } function vn(t, e, n = { // set default options timecodeOverlapLimiter: !1 }) { const { error: r } = Vt.validate( { subtitleText: t, outputExtension: e, options: n }, { abortEarly: !1 } ); if (r) throw new Error(r.details.map((g) => g.message).join(", ")); const s = ht(t); if (!s) throw Error("Could not determine subtitle format"); const { data: o, status: i } = Ct(t, s, n); if (at(o.body)) throw new Error("Parsed file is empty"); const c = nn(o.body, n), a = { startsAtZeroHour: n.startAtZeroHour, reversedTimecodes: !0, overlappingTimecodes: !0, formattedText: n.removeTextFormatting }, u = Rt(c, a); o.body = c; const d = un(o, e), m = { ...i, ...u, success: i.success && u.success }; return { subtitle: d, status: m }; } function Tn(t, e, n = {}) { const r = ht(t) || e; if (!r) throw Error("Could not determine subtitle format"); const { data: s, status: o } = Ct(t, r, n); if (at(s.body)) throw Error("Parsed file is empty"); const i = Rt(s.body, n), c = o.success && i.success; return { ...o, ...i, success: c }; } const xn = [".srt", ".vtt", ".dfxp", ".ttml", ".scc", ".ass"], Mn = [".srt", ".vtt"]; export { v as CompilerError, Mn as EXPORT_EXTENSIONS, xn as PARSE_EXTENSIONS, x as ParserError, Fe as compileWebVTT, vn as convert, En as fromSrt, un as generateOutputData, gn as hlsSegment, hn as hlsSegmentPlaylist, Ct as parse, yt as parseWebVTT, $t as segmentWebVTT, sn as toSrt, Tn as validate }; //# sourceMappingURL=subs-convert.js.map