UNPKG

@snack-dev/browser-studio

Version:

Build bleeding edge video processing applications

1,718 lines (1,717 loc) 141 kB
import { Container as I, Filter as Fe, Sprite as Y, Texture as ot, TextStyle as Ue, Text as st, Color as bt, CanvasTextMetrics as Ge, Graphics as ne, autoDetectRenderer as Xe } from "pixi.js"; import { GlowFilter as ke } from "pixi-filters"; import { ArrayBufferTarget as oe, FileSystemWritableFileStreamTarget as Ne, Muxer as re } from "mp4-muxer"; const G = 30; class X extends Error { message; code; constructor({ message: t = "", code: e = "" }, ...i) { super(t, ...i), console.error(t), this.code = e, this.message = t; } } class v extends X { } class g extends X { } class ii extends X { } class k extends X { } class Lt extends X { } function Ce(o, t = G) { if (t < 1) throw new g({ code: "invalidArgument", message: "FPS must be greater or equal to 1" }); return Math.round(o * t); } function ni(o, t = G) { if (t < 1) throw new g({ code: "invalidArgument", message: "FPS must be greater or equal to 1" }); return Math.round(o / t * 1e3) / 1e3; } function E(o, t = G) { if (t < 1) throw new g({ code: "invalidArgument", message: "FPS must be greater or equal to 1" }); return Math.round(o / t * 1e3); } class u { /** * Time state in **milliseconds** */ time; /** * Create a new timestamp from **milliseconds** */ constructor(t = 0) { this.time = Math.round(t); } /** * Base unit of the timestamp */ get millis() { return this.time; } set millis(t) { this.time = Math.round(t); } /** * Defines the time in frames at the * current frame rate */ get frames() { return Ce(this.millis / 1e3); } set frames(t) { this.millis = E(t); } /** * Convert the timestamp to seconds */ get seconds() { return this.millis / 1e3; } set seconds(t) { this.millis = t * 1e3; } /** * Equivalent to millis += x */ addMillis(t) { return this.millis = this.millis + t, this; } /** * Equivalent to frames += x */ addFrames(t) { const e = E(t); return this.millis = this.millis + e, this; } /** * add two timestamps the timestamp being added will adapt * its fps to the current fps * @returns A new Timestamp instance with the added frames */ add(t) { return new u(t.millis + this.millis); } /** * subtract two timestamps timestamp being subtracted * will adapt its fps to the current fps * @returns A new Timestamp instance with the added frames */ subtract(t) { return new u(this.millis - t.millis); } /** * Create a new timestamp from seconds */ static fromSeconds(t) { const e = new u(); return e.millis = t * 1e3, e; } /** * Create a new timestamp from frames */ static fromFrames(t, e) { const i = new u(); return i.millis = E(t, e), i; } /** * get a copy of the object */ copy() { return new u(this.millis); } toJSON() { return this.millis; } static fromJSON(t) { return new u(t); } } function oi(o) { return Math.floor(o * 255).toString(16).padStart(2, "0").toUpperCase(); } function ri(o, t) { return o.reduce( (e, i) => { const s = i[t]; return e[s] || (e[s] = []), e[s].push(i), e; }, // @ts-ignore {} ); } function Yt(o, t) { return [o.slice(0, t), o.slice(t)].filter((e) => e.length > 0); } function it(o, t) { return t ? Math.floor(Math.random() * (t - o + 1) + o) : o; } async function ai(o) { o <= 0 || await new Promise((t) => setTimeout(t, o)); } function li(o) { if (!o) throw "Assertion failed!"; } function ci(o, t = 300) { let e; return (...i) => { clearTimeout(e), e = setTimeout(() => { o.apply(o, i); }, t); }; } function Oe(o, t, e) { e < 0 && (e = 0); const i = o[t]; o.splice(t, 1), o.splice(e, 0, i); } function hi() { return crypto.randomUUID().split("-").at(0); } function Bt(o) { return typeof o != "function" ? !1 : /^class\s/.test(Function.prototype.toString.call(o)); } function di(o) { return o.charAt(0).toUpperCase() + o.slice(1); } function Te(o) { if (o.numberOfChannels === 1) return o.getChannelData(0); const t = []; for (let r = 0; r < o.numberOfChannels; r++) t.push(o.getChannelData(r)); const e = Math.max(...t.map((r) => r.length)), i = new Float32Array(e * o.numberOfChannels); let s = 0, n = 0; for (; n < e; ) t.forEach((r) => { i[s++] = r[n] !== void 0 ? r[n] : 0; }), n++; return i; } function $(o, t, e) { for (let i = 0; i < e.length; i++) o.setUint8(t + i, e.charCodeAt(i)); } function Ee(o, t, e) { for (let i = 0; i < t.length; i++, e += 2) { const s = Math.max(-1, Math.min(1, t[i])); o.setInt16(e, s < 0 ? s * 32768 : s * 32767, !0); } return o; } function _e(o, t, e) { const n = t * 2, r = 8, a = 36, c = o.length * 2, h = a + c, p = new ArrayBuffer(r + h), d = new DataView(p); return $(d, 0, "RIFF"), d.setUint32(4, h, !0), $(d, 8, "WAVE"), $(d, 12, "fmt "), d.setUint32(16, 16, !0), d.setUint16(20, 1, !0), d.setUint16(22, t, !0), d.setUint32(24, e, !0), d.setUint32(28, e * n, !0), d.setUint16(32, n, !0), d.setUint16(34, 16, !0), $(d, 36, "data"), d.setUint32(40, c, !0), Ee(d, o, r + a); } function ui(o, t = "audio/wav") { const e = Te(o), i = _e(e, o.numberOfChannels, o.sampleRate); return new Blob([i], { type: t }); } function Me(o) { const t = new Float32Array(o.length * o.numberOfChannels); let e = 0; for (let i = 0; i < o.numberOfChannels; i++) { const s = o.getChannelData(i); t.set(s, e), e += s.length; } return t; } function ae(o) { const t = o.numberOfChannels, e = o.length, i = new Int16Array(e * t); for (let s = 0; s < e; s++) for (let n = 0; n < t; n++) { let r = o.getChannelData(n)[s] * 32767; r > 32767 && (r = 32767), r < -32767 && (r = -32767), i[s * t + n] = r; } return i; } async function pi(o, t = 22050, e = Math.sqrt(2)) { const i = await o.arrayBuffer(), s = new OfflineAudioContext({ sampleRate: t, length: 1 }), n = await s.decodeAudioData(i), r = s.createBuffer(1, n.length, t); if (n.numberOfChannels >= 2) { const a = n.getChannelData(0), c = n.getChannelData(1), h = r.getChannelData(0); for (let p = 0; p < n.length; ++p) h[p] = e * (a[p] + c[p]) / 2; return r; } return n; } function Je(o, t = 44100, e = 2) { if (o.sampleRate == t && o.numberOfChannels == e) return o; const i = Math.floor(o.duration * t), n = new OfflineAudioContext(e, 1, t).createBuffer(e, i, t); for (let r = 0; r < o.numberOfChannels; r++) { const a = o.getChannelData(r), c = n.getChannelData(r), h = o.sampleRate / t; for (let p = 0; p < c.length; p++) { const d = p * h, f = Math.floor(d), m = Math.ceil(d); if (m >= a.length) c[p] = a[f]; else { const y = d - f; c[p] = a[f] * (1 - y) + a[m] * y; } } } return n; } async function le(o) { const { fps: t, height: e, width: i, bitrate: s } = o, n = [ "avc1.640034", "avc1.4d0034", "avc1.640028", "avc1.640C32", "avc1.64001f", "avc1.42001E" // TODO: 'hev1.1.6.L93.B0', 'hev1.2.4.L93.B0', 'vp09.00.10.08', 'av01.0.04M.08', 'vp8', ], r = ["prefer-hardware", "prefer-software"], a = []; for (const h of n) for (const p of r) a.push({ codec: h, hardwareAcceleration: p, width: i, height: e, bitrate: s, framerate: t }); const c = []; if (!("VideoEncoder" in window)) return c; for (const h of a) { const p = await VideoEncoder.isConfigSupported(h); p.supported && c.push(p.config ?? h); } return c.sort(Be); } async function Le(o) { const { sampleRate: t, numberOfChannels: e, bitrate: i } = o, s = ["mp4a.40.2", "opus"], n = []; if (!("AudioEncoder" in window)) return n; for (const r of s) { const a = await AudioEncoder.isConfigSupported({ codec: r, numberOfChannels: e, bitrate: i, sampleRate: t }); a.supported && n.push(a.config); } return n; } async function Ye(o) { const t = await Le(o.audio), e = await le(o.video); if (!e.length) throw new k({ message: "Encoder can't be configured with any of the tested codecs", code: "codecsNotSupported" }); return [e[0], t[0]]; } function Be(o, t) { const e = o.hardwareAcceleration ?? "", i = t.hardwareAcceleration ?? ""; return e < i ? -1 : e > i ? 1 : 0; } async function ce(o, t = "untitled") { const e = document.createElement("a"); if (document.head.appendChild(e), e.download = t, typeof o == "string" && o.startsWith("data:image/svg+xml;base64,")) { const i = o.split(",")[1], s = atob(i), n = new Array(s.length); for (let c = 0; c < s.length; c++) n[c] = s.charCodeAt(c); const r = new Uint8Array(n), a = new Blob([r], { type: "image/svg+xml" }); e.href = URL.createObjectURL(a), e.download = t.split(".")[0] + ".svg"; } else typeof o == "string" ? e.href = o : e.href = URL.createObjectURL(o); e.click(), e.remove(); } async function fi(o, t = !0) { return new Promise((e) => { const i = document.createElement("input"); i.type = "file", i.accept = o, i.multiple = t, i.onchange = (s) => { const n = Array.from(s.target?.files ?? []); e(n); }, i.click(); }); } function It(o) { return `${o.hours.toString().padStart(2, "0")}:${o.minutes.toString().padStart(2, "0")}:${o.seconds.toString().padStart(2, "0")},` + o.milliseconds.toString().padStart(3, "0"); } function zt(o) { const t = new Date(1970, 0, 1); return t.setSeconds(o), t.setMilliseconds(Math.round(o % 1 * 1e3)), { hours: t.getHours(), minutes: t.getMinutes(), seconds: t.getSeconds(), milliseconds: t.getMilliseconds() }; } class q { words = []; constructor(t) { t && (this.words = t); } get duration() { return this.stop.subtract(this.start); } get text() { return this.words.map(({ text: t }) => t).join(" "); } get start() { return this.words.at(0)?.start ?? new u(); } get stop() { return this.words.at(-1)?.stop ?? new u(); } } var St = /* @__PURE__ */ ((o) => (o.en = "en", o.de = "de", o))(St || {}); class jt { /** * A unique identifier for the word */ id = crypto.randomUUID(); /** * Defines the text to be displayed */ text; /** * Defines the time stamp at * which the text is spoken */ start; /** * Defines the time stamp at * which the text was spoken */ stop; /** * Defines the confidence of * the predicition */ confidence; /** * Create a new Word object * @param text The string contents of the word * @param start Start in **milliseconds** * @param stop Stop in **milliseconds** * @param confidence Predicition confidence */ constructor(t, e, i, s) { this.text = t, this.start = new u(e), this.stop = new u(i), this.confidence = s; } /** * Defines the time between start * and stop returned as a timestamp */ get duration() { return this.stop.subtract(this.start); } } class U { id = crypto.randomUUID(); language = St.en; groups = []; get text() { return this.groups.map(({ text: t }) => t).join(" "); } get words() { return this.groups.flatMap(({ words: t }) => t); } constructor(t = [], e = St.en) { this.groups = t, this.language = e; } /** * Iterate over all words in groups */ *iter({ count: t, duration: e, length: i }) { for (const s of this.groups) { let n; for (const [r, a] of s.words.entries()) n && (t && n.words.length >= it(...t) ? (yield n, n = void 0) : e && n?.duration.seconds >= it(...e) ? (yield n, n = void 0) : i && n.text.length >= it(...i) && (yield n, n = void 0)), n ? n.words.push(a) : n = new q([a]), r == s.words.length - 1 && (yield n); } } /** * This method will optimize the transcipt for display */ optimize() { const t = this.groups.flatMap((e) => e.words); for (let e = 0; e < t.length - 1; e++) { const i = t[e], s = t[e + 1]; s.start.millis - i.stop.millis < 0 ? s.start.millis = i.stop.millis + 1 : i.stop.millis = s.start.millis - 1; } return this; } /** * Convert the transcript into a SRT compatible * string and downloadable blob */ toSRT(t = {}) { let e = 1, i = ""; for (const s of this.iter(t)) { const n = zt(s.start.seconds), r = zt(s.stop.seconds); i += `${e} ` + It(n) + " --> " + It(r) + ` ${s.text} `, e += 1; } return { text: i, blob: new Blob([i], { type: "text/plain;charset=utf8" }) }; } toJSON() { return this.groups.map( (t) => t.words.map((e) => ({ token: e.text, start: e.start.millis, stop: e.stop.millis })) ); } /** * Create a new Transcript containing the * first `{count}` words * @param count Defines the number of words required * @param startAtZero Defines if the first word should start at 0 milliseconds * @returns A new Transcript instance */ slice(t, e = !0) { let i = 0; const s = []; for (const n of this.groups) for (const r of n.words) if (s.length == 0 && e && (i = r.start.millis), s.push(new jt(r.text, r.start.millis - i, r.stop.millis - i)), s.length == t) return new U([new q(s)]); return new U([new q(s)]); } /** * Create a deep copy of the transcript * @returns A new Transcript instance */ copy() { return U.fromJSON(this.toJSON()); } static fromJSON(t) { const e = new U(); for (const i of t) { const s = new q(); for (const n of i) s.words.push(new jt(n.token, n.start, n.stop)); e.groups.push(s); } return e; } /** * Fetch captions from an external resource and parse them. JSON needs * to be of the form `{ token: string; start: number; stop: number; }[][]` * @param url Location of the captions * @param init Additional fetch parameters such as method or headers * @returns A Transcript with processed captions */ static async from(t, e) { const i = await fetch(t, e); if (!i.ok) throw new v({ code: "unexpectedIOError", message: "An unexpected error occurred while fetching the file" }); return U.fromJSON(await i.json()); } } function nt(o, t, e) { return o + (t - o) * e; } function Ie(o, t, e) { const i = Number.parseInt(o.slice(1), 16), s = Number.parseInt(t.slice(1), 16), n = i >> 16 & 255, r = i >> 8 & 255, a = i & 255, c = s >> 16 & 255, h = s >> 8 & 255, p = s & 255, d = Math.round(nt(n, c, e)), f = Math.round(nt(r, h, e)), m = Math.round(nt(a, p, e)); return `#${((1 << 24) + (d << 16) + (f << 8) + m).toString(16).slice(1)}`; } const ze = { linear: (o) => o, easeIn: (o) => o * o, easeOut: (o) => o * (2 - o), easeInOut: (o) => o < 0.5 ? 2 * o * o : -1 + (4 - 2 * o) * o }; class R { /** * Defines the range of the input values * in milliseconds */ input; /** * Defines the range of the output values */ output; /** * Defines the required options that * control the behaviour of the keyframe */ options; /** * Constructs a Keyframe object. * @param inputRange - The range of input values (e.g., frame numbers). * @param outputRange - The range of output values (e.g., opacity, degrees, colors). * @param options - Additional options for extrapolation, type, and easing. */ constructor(t, e, i = {}) { if (t.length !== e.length) throw new g({ code: "invalidKeyframes", message: "inputRange and outputRange must have the same length" }); this.input = t.map((s) => E(s)), this.output = e, this.options = { extrapolate: "clamp", easing: "linear", type: "number", ...JSON.parse(JSON.stringify(i)) }; } /** * Normalizes the frame number to a value between 0 and 1 based on the input range. * @param frame - The current frame number. * @returns The normalized value. */ normalize(t) { const { input: e } = this; if (t < e[0]) return this.options.extrapolate === "clamp" ? { t: 0, segment: 0 } : { t: (t - e[0]) / (e[1] - e[0]), segment: 0 }; if (t > e[e.length - 1]) return this.options.extrapolate === "clamp" ? { t: 1, segment: e.length - 2 } : { t: (t - e[e.length - 2]) / (e[e.length - 1] - e[e.length - 2]), segment: e.length - 2 }; for (let i = 0; i < e.length - 1; i++) { const s = e[i], n = e[i + 1]; if (t >= s && t <= n) return { t: (t - s) / (n - s), segment: i }; } return { t: 0, segment: 0 }; } /** * Interpolates the output value based on the normalized frame value. * @param t - The normalized frame value (between 0 and 1). * @param segment - The current segment index. * @returns The interpolated output value. */ interpolate(t, e) { const i = this.output[e], s = this.output[e + 1], n = ze[this.options.easing](t); if (typeof i == "number" && typeof s == "number") return nt(i, s, n); if (typeof i == "string" && typeof s == "string") return Ie(i, s, n); if (this.output.length == 1) return this.output[0]; throw new g({ code: "invalidKeyframes", message: "Unsupported output range types" }); } /** * Evaluates the interpolated value for a given milliseconds number. * @param time - The current time in milliseconds or as a timestamp * @returns The interpolated output value. */ value(t) { const { t: e, segment: i } = this.normalize(typeof t == "number" ? t : t.millis); return this.interpolate(e, i); } /** * Add a new keyframe to the animation * @param frame time of the keyframe * @param value value of the keyframe */ push(t, e) { return this.input.push(E(t)), this.output.push(e), this; } toJSON() { return this; } static fromJSON(t) { const e = new R([], []); return Object.assign(e, t), e; } } class Z { /** * Unique identifier of the object */ id = crypto.randomUUID(); toJSON() { const t = {}; return (this.constructor.__serializableProperties || []).forEach(({ propertyKey: i, serializer: s }) => { const n = this[i]; s && n instanceof s ? t[i] = n.toJSON() : t[i] = n; }), t; } static fromJSON(t) { const e = new this(); return (this.__serializableProperties || []).forEach(({ propertyKey: s, serializer: n }) => { if (t.hasOwnProperty(s)) if (n) { const r = n.fromJSON(t[s]); e[s] = r; } else e[s] = t[s]; }), e; } } function l(o) { return function(t, e) { t.constructor.__serializableProperties || (t.constructor.__serializableProperties = []), t.constructor.__serializableProperties.push({ propertyKey: e, serializer: o }); }; } function N(o) { return class extends o { _handlers = {}; on(e, i) { if (typeof i != "function") throw new Error("The callback of an event listener needs to be a function."); const s = crypto.randomUUID(); return this._handlers[e] ? this._handlers[e][s] = i : this._handlers[e] = { [s]: i }, s; } off(e, ...i) { if (e) { if (e === "*") { this._handlers = {}; return; } for (const s of Object.values(this._handlers)) e in s && delete s[e]; for (const s of i) this.off(s); } } trigger(e, i) { const s = new CustomEvent(e, { detail: i }); Object.defineProperty(s, "currentTarget", { writable: !1, value: this }); for (const n in this._handlers[e] ?? {}) this._handlers[e]?.[n](s); for (const n in this._handlers["*"] ?? {}) this._handlers["*"]?.[n](s); } bubble(e) { return this.on("*", (i) => { e.trigger(i.type, i.detail); }); } resolve(e) { return (i, s) => { this.on("error", s), this.on(e, i); }; } }; } class xt extends N(Z) { _key; _value; _store; loaded = !1; constructor(t, e, i) { super(), this._store = t, this._key = e, this.initValue(i); } get key() { return this._key; } get value() { return this._value; } set value(t) { this._value = t, this._store.set(this._key, t), this.trigger("update", void 0); } async initValue(t) { t instanceof Promise ? this._value = await t : this._value = t, this.loaded = !0, this.trigger("update", void 0); } } class gi { storageEngine; namespace; constructor(t, e = localStorage) { this.storageEngine = e, this.namespace = t; } define(t, e, i) { const s = this.get(t); return s === null ? (this.set(t, e), new xt(this, t, e)) : i && s != null ? new xt(this, t, i(s)) : new xt(this, t, s); } set(t, e) { this.storageEngine.setItem( this.getStorageId(t), JSON.stringify({ value: e }) ); } get(t) { const e = this.storageEngine.getItem(this.getStorageId(t)); return e ? JSON.parse(e).value : null; } remove(t) { this.storageEngine.removeItem(this.getStorageId(t)); } getStorageId(t) { return this.namespace ? `${this.namespace}.${t}` : t; } } class yi { worker; constructor(t) { this.worker = new t(), this.worker.onerror = console.error; } async run(t, e) { return this.worker.postMessage({ type: "init", ...t ?? {} }), await new Promise((i, s) => { this.worker.addEventListener("message", (n) => { e?.(n.data), n.data.type == "result" && (n.data.type = void 0, i(n.data)), n.data.type == "error" && s(n.data.message); }); }).then((i) => ({ result: i, error: void 0 })).catch((i) => ({ result: void 0, error: i })).finally(() => { this.worker.terminate(); }); } } function wi(o) { return async (t) => { try { await o(t); } catch (e) { self.postMessage({ type: "error", message: e?.message ?? "An unkown worker error occured" }); } }; } function je() { return class extends N(class { }) { }; } function Ot(o, t, e = 0) { if (!(o instanceof I || o instanceof Fe || e == 3)) for (const i in o) { const s = o[i]; i && (s instanceof R && (o[i] = s.value(t)), s != null && typeof s == "object" && Object.keys(s).length && Ot(s, t, e + 1)); } } var Pe = Object.defineProperty, at = (o, t, e, i) => { for (var s = void 0, n = o.length - 1, r; n >= 0; n--) (r = o[n]) && (s = r(t, e, s) || s); return s && Pe(t, e, s), s; }; const z = class he extends N(Z) { _name; _start = new u(); _stop = u.fromSeconds(16); /** * Defines the type of the clip */ type = "base"; /** * Defines the source of the clip with a * one-to-many (1:n) relationship */ source; /** * The view that contains the render related information */ view = new I(); /** * Timestamp when the clip has been created */ createdAt = /* @__PURE__ */ new Date(); disabled = !1; /** * Track is ready to be rendered */ state = "IDLE"; /** * Access the parent track */ track; /** * Human readable identifier ot the clip */ get name() { return this._name ?? this.source?.name; } set name(t) { this._name = t; } /** * Get the first visible frame */ get start() { return this._start; } /** * Get the last visible frame */ get stop() { return this._stop; } constructor(t = {}) { super(), Object.assign(this, t); } /** * Method for connecting the track with the clip */ async connect(t) { this.state = "ATTACHED", this.track = t, this.trigger("attach", void 0); } /** * Change clip's offset to zero in seconds. Can be negative */ set start(t) { typeof t == "number" ? this.start.frames = t : this._start = t, this.start.millis >= this.stop.millis && (this.stop.millis = this.start.millis + 1), this.trigger("frame", this.start.frames); } /** * Set the last visible time that the * clip is visible */ set stop(t) { typeof t == "number" ? this.stop.frames = t : this._stop = t, this.stop.millis <= this.start.millis && (this.start.millis = this.stop.millis - 1), this.trigger("frame", this.stop.frames); } /** * Offsets the clip by a given frame number */ offsetBy(t) { return typeof t == "number" ? (this.start.addFrames(t), this.stop.addFrames(t), this.trigger("offsetBy", u.fromFrames(t))) : (this.start.addMillis(t.millis), this.stop.addMillis(t.millis), this.trigger("offsetBy", t)), this.trigger("frame", void 0), this; } /** * Triggered when the clip is * added to the composition */ async init() { } /** * Triggered when the clip enters the scene */ enter() { } /** * Triggered for each redraw of the scene. * Can return a promise which will be awaited * during export. * @param time the current time to render */ update(t) { } /** * Triggered when the clip exits the scene */ exit() { } /** * Remove the clip from the track */ detach() { return this.track?.remove(this), this; } /** * Split the clip into two clips at the specified time * @param time split, will use the current frame of the composition * a fallback * @returns The clip that was created by performing this action */ async split(t) { if (t || (t = this.track?.composition?.frame), typeof t == "number" && (t = u.fromFrames(t)), !t || t.millis <= this.start.millis || t.millis >= this.stop.millis) throw new g({ code: "splitOutOfRange", message: "Cannot split clip at the specified time" }); if (!this.track) throw new g({ code: "trackNotAttached", message: "Track must be attached to a track" }); const e = this.copy(); this.stop = t.copy(), e.start = t.copy().addMillis(1), Ot(e, e.start.subtract(this.start)); const i = this.track.clips.findIndex((s) => s.id == this.id); return await this.track.add(e, i + 1), e; } /** * Create a copy of the clip */ copy() { return he.fromJSON(JSON.parse(JSON.stringify(this))); } /** * Modify the properties of the clip and * trigger an update afterwards */ set(t) { return t && Object.assign(this, t), this.trigger("update", void 0), this; } }; at([ l() ], z.prototype, "_name"); at([ l(u) ], z.prototype, "_start"); at([ l(u) ], z.prototype, "_stop"); at([ l() ], z.prototype, "disabled"); let _ = z; var He = Object.defineProperty, C = (o, t, e, i) => { for (var s = void 0, n = o.length - 1, r; n >= 0; n--) (r = o[n]) && (s = r(t, e, s) || s); return s && He(t, e, s), s; }; class W extends N(Z) { /** * Indicates if the track is loading */ state = "IDLE"; /** * Metadata associated with the source */ metadata; objectURL; duration = u.fromSeconds(16); /** * Indicates whether the source is used inside the composition */ added = !1; type = "base"; name = ""; mimeType; externalURL; external = !1; /** * Access to the data of the source */ file; /** * By default this is a URL.createObjectURL proxy */ async createObjectURL() { return this.objectURL ? this.objectURL : (this.objectURL = URL.createObjectURL(await this.getFile()), this.objectURL); } /** * Method for retrieving the file when * it has been loaded * @returns The loaded file */ async getFile() { if (!this.file && this.state == "LOADING" && await new Promise(this.resolve("load")), !this.file) throw new g({ code: "fileNotAccessible", message: "The desired file cannot be accessed" }); return this.file; } async loadFile(t) { this.name = t.name, this.mimeType = Nt(t.type), this.external = !1, this.file = t; } async loadUrl(t, e) { const i = await fetch(t, e); if (!i?.ok) throw new v({ code: "unexpectedIOError", message: "An unexpected error occurred while fetching the file" }); const s = await i.blob(); this.name = t.toString().split("/").at(-1) ?? "", this.external = !0, this.file = new File([s], this.name, { type: s.type }), this.externalURL = t, this.mimeType = Nt(s.type); } async from(t, e) { try { this.state = "LOADING", t instanceof File ? await this.loadFile(t) : await this.loadUrl(t, e), this.state = "READY", this.trigger("load", void 0); } catch (i) { throw this.state = "ERROR", this.trigger("error", new Error(String(i))), i; } return this; } /** * Get the source as an array buffer */ async arrayBuffer() { return await (await this.getFile()).arrayBuffer(); } /** * Clean up the data associated with this object */ async remove() { this.state = "IDLE", this.objectURL && (URL.revokeObjectURL(this.objectURL), this.objectURL = void 0), delete this.file; } /** * Downloads the file */ async download() { const t = await this.getFile(); ce(t, this.name); } /** * Get a visulization of the source * as an html element */ async thumbnail() { return document.createElement("div"); } /** * Create a new source for the specified input */ static async from(t, e, i = new this()) { return i.from(t, e); } } C([ l() ], W.prototype, "objectURL"); C([ l() ], W.prototype, "duration"); C([ l() ], W.prototype, "type"); C([ l() ], W.prototype, "name"); C([ l() ], W.prototype, "mimeType"); C([ l() ], W.prototype, "externalURL"); C([ l() ], W.prototype, "external"); const Pt = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E"; function Ae(o) { const t = new TextEncoder().encode(o); let e = ""; const i = t.byteLength; for (let s = 0; s < i; s++) e += String.fromCharCode(t[s]); return btoa(e); } function Ht(o) { if (!o || !o.body) return Pt; const t = o.body.scrollWidth, e = o.body.scrollHeight, i = o.cloneNode(!0), s = i.getElementsByTagName("style").item(0), n = i.getElementsByTagName("body").item(0); if (n?.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"), !n) return Pt; const r = new XMLSerializer(), a = s ? r.serializeToString(s) : "", c = r.serializeToString(n), h = ` <svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}"> body { padding: 0; } ${a} <foreignObject width="100%" height="100%"> ${c} </foreignObject> </svg>`; return "data:image/svg+xml;base64," + Ae(h); } class Ft extends W { type = "html"; /** * Access to the iframe that is required * for extracting the html's dimensions */ iframe; constructor() { super(); const t = document.createElement("iframe"); t.style.position = "absolute", t.style.width = "0", t.style.height = "0", t.style.border = "0", t.style.visibility = "hidden", document.body.appendChild(t), this.iframe = t; } /** * Access to the html document as loaded * within the iframe. Can be manipulated with * javascript */ get document() { return this.iframe.contentWindow?.document; } async createObjectURL() { return !this.file && this.state == "LOADING" && await new Promise(this.resolve("load")), this.objectURL ? this.objectURL : (this.objectURL = Ht(this.document), this.objectURL); } async loadUrl(t, e) { await super.loadUrl(t, e), this.iframe.setAttribute("src", URL.createObjectURL(this.file)), await new Promise((i, s) => { this.iframe.onload = () => i(), this.iframe.onerror = (n) => s(n); }); } async loadFile(t) { await super.loadFile(t), this.iframe.setAttribute("src", URL.createObjectURL(this.file)), await new Promise((e, i) => { this.iframe.onload = () => e(), this.iframe.onerror = (s) => i(s); }); } /** * Update the object url using the current * contents of the iframe document */ update() { this.objectURL && (this.objectURL = Ht(this.document)); } async thumbnail() { const t = new Image(); return t.src = await this.createObjectURL(), t.className = "object-contain w-full aspect-video h-auto", t; } } function De(o, t = {}) { const { threshold: e = 0.02, hopSize: i = 1024, minDuration: s = 500 } = t, n = [], r = o.getChannelData(0), a = o.sampleRate, c = Math.floor(s / 1e3 * a); let h = null, p = 0; for (let d = 0; d < r.length; d += i) { let f = 0; const m = Math.min(d + i, r.length); for (let y = d; y < m; y++) f += r[y] * r[y]; f = Math.sqrt(f / (m - d)), f < e ? (p += i, h === null && (h = d)) : (h !== null && p >= c && n.push({ start: u.fromSeconds(h / a), stop: u.fromSeconds(d / a) }), h = null, p = 0); } return h !== null && p >= c && n.push({ start: u.fromSeconds(h / a), stop: u.fromSeconds(r.length / a) }), n; } const Zt = 3e3; class B extends W { type = "audio"; decoding = !1; _silences; transcript; audioBuffer; async decode(t = 2, e = 48e3, i = !1) { if (this.decoding && i && (await new Promise(this.resolve("update")), this.audioBuffer)) return this.audioBuffer; this.decoding = !0; const s = await this.arrayBuffer(), r = await new OfflineAudioContext(t, 1, e).decodeAudioData(s); return this.duration.seconds = r.duration, i && (this.audioBuffer = r), this.decoding = !1, this.trigger("update", void 0), r; } /** * @deprecated Use fastsampler instead. */ async samples(t = 60, e = 50, i = 0) { const s = this.audioBuffer ?? await this.decode(1, 3e3, !0), n = Math.round(s.sampleRate / e), r = s.sampleRate * s.duration - n, a = Math.ceil(r / t), c = s.getChannelData(0), h = []; for (let p = 0; p < r; p += a) { let d = 0; for (let f = p; f < p + n; f++) d += Math.abs(c[p]); h.push(Math.log1p(d / n * 100)); } return h.map((p) => Math.round(p / Math.max(...h) * (100 - i)) + i); } /** * Fast sampler that uses a window size to calculate the max value of the samples in the window. * @param options - Sampling options. * @returns An array of the max values of the samples in the window. */ async fastsampler({ length: t = 60, start: e = 0, stop: i, logarithmic: s = !1 } = {}) { typeof e == "object" && (e = e.millis), typeof i == "object" && (i = i.millis); const n = this.audioBuffer ?? await this.decode(1, Zt, !0), r = n.getChannelData(0), a = Math.floor(Math.max(e * Zt / 1e3, 0)), c = i ? Math.floor(Math.min(i * Zt / 1e3, n.length)) : n.length, h = Math.floor((c - a) / t), p = new Float32Array(t); for (let d = 0; d < t; d++) { const f = a + d * h, m = f + h; let y = -1 / 0; for (let w = f; w < m; w++) { const Jt = r[w]; Jt > y && (y = Jt); } p[d] = s ? Math.log2(1 + y) : y; } return p; } async thumbnail(...t) { const e = await this.samples(...t), i = document.createElement("div"); i.className = "flex flex-row absolute space-between inset-0 audio-samples"; for (const s of e) { const n = document.createElement("div"); n.className = "audio-sample-item", n.style.height = `${s}%`, i.appendChild(n); } return i; } /** * Find silences in the audio clip. Results are cached. * * uses default sample rate of 3000 * @param options - Silences options. * @returns An array of the silences (in ms) in the clip. */ async silences(t = {}) { if (this._silences) return this._silences; const e = await this.arrayBuffer(), i = new AudioContext(), s = await i.decodeAudioData(e); return this._silences = De(s, t), i.close(), this._silences; } } class Ut extends W { type = "image"; async thumbnail() { const t = new Image(); return t.src = await this.createObjectURL(), t.className = "object-cover w-full aspect-video h-auto", t; } } class Gt extends B { type = "video"; downloadInProgress = !0; async loadUrl(t, e) { const i = await fetch(t, e); if (!i?.ok) throw new v({ code: "unexpectedIOError", message: "An unexpected error occurred while fetching the file" }); this.name = t.toString().split("/").at(-1) ?? "", this.external = !0, this.externalURL = t, this.objectURL = String(t), this.mimeType = Nt(i.headers.get("Content-type")), this.getBlob(i); } async getFile() { if (!this.file && this.downloadInProgress && await new Promise(this.resolve("load")), !this.file) throw new g({ code: "fileNotAccessible", message: "The desired file cannot be accessed" }); return this.file; } async thumbnail() { const t = document.createElement("video"); return t.className = "object-cover w-full aspect-video h-auto", t.controls = !1, t.addEventListener("loadedmetadata", () => { this.duration.seconds = t.duration, this.trigger("update", void 0); }), t.addEventListener("mousemove", (e) => { const i = e.currentTarget, s = i?.getBoundingClientRect(), n = e.clientX - (s?.left ?? 0), r = i?.duration; r && s && s.width > 0 && (i.currentTime = Math.round(r * (n / s.width))); }), t.src = await this.createObjectURL(), t; } async getBlob(t) { try { this.downloadInProgress = !0; const e = await t.blob(); this.file = new File([e], this.name, { type: e.type }), this.trigger("load", void 0); } catch (e) { this.state = "ERROR", this.trigger("error", new Error(String(e))); } finally { this.downloadInProgress = !1; } } } class bi { static fromType(t) { switch (t.type) { case "video": return new kt(); case "audio": return new rt(); case "html": return new $t(); case "image": return new Dt(); case "text": return new P(); case "complex_text": return new H(); default: return new _(); } } static fromSource(t) { if (t.type == "audio" && t instanceof B) return new rt(t); if (t.type == "video" && t instanceof Gt) return new kt(t); if (t.type == "image" && t instanceof Ut) return new Dt(t); if (t.type == "html" && t instanceof Ft) return new $t(t); } } class Qe { static fromJSON(t) { return [new u(t[0]), new u(t[1])]; } } var Ke = Object.defineProperty, $e = Object.getOwnPropertyDescriptor, M = (o, t, e, i) => { for (var s = i > 1 ? void 0 : i ? $e(t, e) : t, n = o.length - 1, r; n >= 0; n--) (r = o[n]) && (s = (i ? r(t, e, s) : r(s)) || s); return i && s && Ke(t, e, s), s; }; const O = class de extends _ { source = new B(); _offset = new u(); /** * Is the media currently playing */ playing = !1; duration = new u(); range = [new u(), this.duration]; constructor(t = {}) { super(), Object.assign(this, t); } get transcript() { return this.source.transcript; } set transcript(t) { this.source.transcript = t; } get start() { return this.range[0].add(this.offset); } get stop() { return this.range[1].add(this.offset); } set start(t) { typeof t == "number" && (t = u.fromFrames(t)); const e = t.subtract(this.offset); e.millis >= 0 && e.millis < this.range[1].millis ? this.range[0].millis = e.millis : e.millis < 0 ? this.range[0].millis = 0 : this.range[0].millis = this.range[1].millis - 1, this.trigger("frame", void 0); } set stop(t) { typeof t == "number" && (t = u.fromFrames(t)); const e = t.subtract(this.offset); e.millis > this.range[0].millis && e.millis <= this.duration.millis ? this.range[1] = e : e.millis > this.duration.millis ? this.range[1] = this.duration : this.range[1].millis = this.range[0].millis + 1, this.trigger("frame", void 0); } /** * Offest from frame 0 of the composition */ get offset() { return this._offset; } /** * Change clip's offset to zero in frames. Can be negative */ set offset(t) { typeof t == "number" ? this._offset.frames = t : this._offset = t, this.trigger("frame", this.offset.frames); } /** * Offsets the clip by a given frame number */ offsetBy(t) { return typeof t == "number" ? (this.offset.addFrames(t), this.trigger("offsetBy", u.fromFrames(t))) : (this.offset.addMillis(t.millis), this.trigger("offsetBy", t)), this.trigger("frame", void 0), this; } get muted() { return this.element?.muted ?? !1; } set muted(t) { this.element && (this.element.muted = t); } /** * Set the media playback to a given time */ seek(t) { return new Promise((e, i) => { if (!this.element) return i( new Lt({ code: "elementNotDefined", message: "Cannot seek on undefined element" }) ); (t.millis < this.start.millis || t.millis > this.stop.millis) && (t = this.start), this.element.onerror = () => i(this.element?.error), this.element.pause(), this.element.currentTime = t.subtract(this.offset).seconds, this.element.onseeked = () => e(); }); } /** * Returns a slice of a media clip with trimmed start and stop */ subclip(t, e) { if (t || (t = this.range[0]), e || (e = this.range[1]), typeof t == "number" && (t = u.fromFrames(t)), typeof e == "number" && (e = u.fromFrames(e)), t.millis >= e.millis) throw new g({ code: "invalidKeyframe", message: "Start can't lower than or equal the stop" }); return t.millis < 0 && (this.range[0].millis = 0, t = this.range[0]), e.millis > this.duration.millis && this.duration.millis && (e = this.duration), this.range = [t, e], this.trigger("frame", void 0), this; } get volume() { return this.element?.volume ?? 1; } set volume(t) { this.element && (this.element.volume = t); } copy() { const t = de.fromJSON(JSON.parse(JSON.stringify(this))); return t.source = this.source, t; } async split(t) { if (t || (t = this.track?.composition?.frame), typeof t == "number" && (t = u.fromFrames(t)), !t || t.millis <= this.start.millis || t.millis >= this.stop.millis) throw new g({ code: "invalidKeyframe", message: "Cannot split clip at the specified time" }); if (!this.track) throw new Lt({ code: "trackNotDefined", message: "Clip must be attached to a track" }); t = t.subtract(this.offset); const e = this.copy(); this.range[1] = t.copy(), e.range[0] = t.copy().addMillis(1), Ot(e, e.start.subtract(this.start)); const i = this.track.clips.findIndex((s) => s.id == this.id); return await this.track.add(e, i + 1), e; } /** * Generates a new caption track for the current clip using the specified captioning strategy. * @param strategy An optional CaptionPresetStrategy to define how captions should be generated. */ async addCaptions(t) { if (!this.track?.composition) throw new g({ code: "compositionNotDefined", message: "Captions can only be generated after the clip has been added to the composition" }); return await this.track.composition.createTrack("caption").from(this).generate(t); } set(t) { return super.set(t); } /** * @deprecated use `addCaptions` instead */ async generateCaptions(t) { return this.addCaptions(t); } /** * Remove silences from the clip * * @param options - Options for silence detection */ async removeSilences(t = {}) { ["READY", "ATTACHED"].includes(this.state) || await this.init(); const e = (await this.source.silences(t)).filter((n) => At(n, this.range)).sort((n, r) => n.start.millis - r.start.millis); if (e.length == 0) return [this]; const i = t.padding ?? 500, s = [this]; for (const n of e) { const r = s.at(-1); if (!r) break; if (!At(n, r.range)) continue; const a = new u( Math.min(n.start.millis + i, n.stop.millis) ); if (n.start.millis > r.range[0].millis && n.stop.millis < r.range[1].millis) { const c = r.copy(); r.range[1] = a, c.range[0] = n.stop, s.push(c); } else n.start.millis <= r.range[0].millis ? r.range[0] = n.stop : n.stop.millis >= r.range[1].millis && (r.range[1] = a); } return s; } }; M([ l(u) ], O.prototype, "_offset", 2); M([ l(u) ], O.prototype, "duration", 2); M([ l(Qe) ], O.prototype, "range", 2); M([ l(U) ], O.prototype, "transcript", 1); M([ l() ], O.prototype, "muted", 1); M([ l() ], O.prototype, "volume", 1); let lt = O; function At(o, t) { return o.start.millis >= t[0].millis && o.start.millis <= t[1].millis || o.stop.millis <= t[1].millis && o.stop.millis >= t[0].millis; } class tt { static fromJSON(t) { return typeof t == "object" ? R.fromJSON(t) : t; } } class Rt { static fromJSON(t) { return typeof t.x == "object" && (t.x = R.fromJSON(t.x)), typeof t.y == "object" && (t.y = R.fromJSON(t.y)), t; } } let qe = class { target; animation; constructor(t) { this.target = t; } init(t, e, i = 0, s) { if (!(t in this.target)) throw new Error(`Property [${String(t)}] cannot be assigned`); const n = [i], r = [e]; typeof this.target[t] == typeof e && i != 0 && (n.unshift(0), r.unshift(this.target[t])), this.target[t] = this.animation = new R(n, r, { easing: s }); } }; function ts(o) { const t = new Proxy(o, { get(e, i) { return i == "to" ? (s, n) => { if (!e.animation) throw new g({ code: "undefinedKeyframe", message: "Cannot use 'to() before selecting a property" }); const a = new u(e.animation.input.at(-1)).frames + n; return e.animation.push(a, s), t; } : (s, n, r) => (e.init(i, s, n, r), t); } }); return t; } class es extends qe { } var ss = Object.defineProperty, is = Object.getOwnPropertyDescriptor, F = (o, t, e, i) => { for (var s = i > 1 ? void 0 : i ? is(t, e) : t, n = o.length - 1, r; n >= 0; n--) (r = o[n]) && (s = (i ? r(t, e, s) : r(s)) || s); return i && s && ss(t, e, s), s; }; function ct(o) { class t extends o { /** * Apply one or more `Pixi.js` filters to the clip. * @example * clip.filters = [new BlurFilter()]; */ filters; _height; _width; _position = { x: this.view.position.x, y: this.view.position.y }; _scale; rotation = this.view.angle; alpha = 1; translate = { x: 0, y: 0 }; /** * The coordinate of the object relative to the local coordinates of the parent. * @default { x: 0, y: 0 } */ get position() { return this._position; } set position(i) { typeof i == "string" ? (this._position = { x: "50%", y: "50%" }, this.anchor = { x: 0.5, y: 0.5 }) : this._position = i; } /** * The scale factors of this object along the local coordinate axes. * Will be added to the scale applied by setting height and/or width * @default { x: 1, y: 1 } */ get scale() { return this._scale ?? { x: this.view.scale.x, y: this.view.scale.y }; } set scale(i) { typeof i == "number" || i instanceof R || typeof i == "function" ? this._scale = { x: i, y: i } : this._scale = i; } /** * The position of the clip on the x axis relative. * An alias to position.x * @default 0 */ get x() { return this._position.x; } set x(i) { this._position.x = i; } /** * The position of the clip on the y axis. An alias to position.y * @default 0 */ get y() { return this._position.y; } set y(i) { this._position.y = i; } /** * Offset relative to the x position * @default 0 */ get translateX() { return this.translate.x; } set translateX(i) { this.translate.x = i; } /** * Offset relative to the y position * @default 0 */ get translateY() { return this.translate.y; } set translateY(i) { this.translate.y = i; } /** * The height of the clip/container */ get height() { return this._height ?? this.view.height; } set height(i) { this._height = i; } /** * The width of the clip/container */ get width() { return this._width ?? this.view.width; } set width(i) { this._width = i; } /** * The mask to apply to the clip */ get mask() { return this.view.mask; } set mask(i) { this.view.mask = i ?? null; } get anchor() { return this.view.children[0] instanceof Y ? { x: this.view.children[0].anchor.x, y: this.view.children[0].anchor.y } : { x: 0, y: 0 }; } set anchor(i) { const s = typeof i == "number" ? { x: i, y: i } : i; for (const n of this.view.children) n instanceof Y && n.anchor.set(s.x, s.y); } enter() { this.filters && !this.view.filters && (this.view.filters = this.filters); } exit() { this.filters && this.view.filters && (this.view.filters = null); } animate() { return ts( new es(this) ); } } return F([ l(tt) ], t.prototype, "_height", 2), F([ l(tt) ], t.prototype, "_width", 2), F([ l(Rt) ], t.prototype, "_position", 2), F([ l(Rt) ], t.prototype, "_scale", 2), F([ l(tt) ], t.prototype, "rotation", 2), F([ l(tt) ], t.prototype, "alpha", 2), F([ l(Rt) ], t.prototype, "translate", 2), F([ l() ], t.prototype, "anchor", 1), t; } function ht(o, t, e) { const i = e.value; return e.value = function(...s) { const n = s[0].subtract(this.start), r = { width: this.track?.composition?.width ?? 1920, height: this.track?.composition?.height ?? 1080 }; let a; typeof this.translate.x == "number" ? a = this.translate.x : typeof this.translate.x == "function" ? a = this.translate.x.bind(this)(n) : a = this.translate.x.value(n); let c; typeof this.translate.y == "number" ? c = this.translate.y : typeof this.translate.y == "function" ? c = this.translate.y.bind(this)(n) : c = t