@transcrobes/subs-convert
Version:
Convert subtitles from one format to another
965 lines (961 loc) • 31.1 kB
JavaScript
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