@diffusionstudio/core-v2
Version:
The ffmpeg for agents
1,697 lines (1,694 loc) • 143 kB
JavaScript
/*!
* :::::::: :::::::: ::::::::: ::::::::::
* :+: :+: :+: :+: :+: :+: :+:
* +:+ +:+ +:+ +:+ +:+ +:+
* +#+ +#+ +:+ +#++:++#: +#++:++#
* +#+ +#+ +#+ +#+ +#+ +#+
* #+# #+# #+# #+# #+# #+# #+#
* ######## ######## ### ### ##########
* ::: ::: ::::::::
* :+: :+: :+: :+:
* +:+ +:+ +:+
* +#+ +:+ +#+
* +#+ +#+ +#+
* #+#+#+# #+#
* ### ##########
*
* @diffusionstudio/core-v2 v2.0.1
* (c) 2025 Diffusion Studio Inc.
*
* Licensed under the Diffusion Studio Non-Commercial License.
* You may NOT use this software for any commercial purposes.
*
* For commercial licensing, visit:
* https://diffusion.studio
*/
import { ArrayBufferTarget as ue, StreamTarget as fe, FileSystemWritableFileStreamTarget as me, Muxer as pe } from "mp4-muxer";
const G = 30;
class et extends Error {
message;
code;
constructor({ message: t = "", code: e = "" }, ...s) {
super(t, ...s), console.error(t), this.code = e, this.message = t;
}
}
class S extends et {
}
class w extends et {
}
class D extends et {
}
class Et extends et {
}
function ge(r, t = G) {
if (t < 1) throw new w({
code: "invalidArgument",
message: "FPS must be greater or equal to 1"
});
return Math.round(r * t);
}
function js(r, t = G) {
if (t < 1) throw new w({
code: "invalidArgument",
message: "FPS must be greater or equal to 1"
});
return Math.round(r / t * 1e3) / 1e3;
}
function Q(r, t = G) {
if (t < 1) throw new w({
code: "invalidArgument",
message: "FPS must be greater or equal to 1"
});
return Math.round(r / t * 1e3);
}
class f {
/**
* Time state in **milliseconds**
*/
time;
/**
* Create a new timestamp from various time units
* @param millis - Time in milliseconds
* @param seconds - Time in seconds
* @param minutes - Time in minutes
* @param hours - Time in hours
*/
constructor(t = 0, e = 0, s = 0, i = 0) {
this.time = Math.round(t + e * 1e3 + s * 6e4 + i * 36e5);
}
/**
* 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 ge(this.millis / 1e3);
}
set frames(t) {
this.millis = Q(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 = Q(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 f(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 f(this.millis - t.millis);
}
/**
* Create a new timestamp from frames
*/
static fromFrames(t, e) {
const s = new f();
return s.millis = Q(t, e), s;
}
/**
* get a copy of the object
*/
copy() {
return new f(this.millis);
}
toJSON() {
return this.millis;
}
static fromJSON(t) {
return new f(t);
}
}
function Hs(r) {
return Math.floor(r * 255).toString(16).padStart(2, "0").toUpperCase();
}
function _s(r, t) {
return r.reduce(
(e, s) => {
const i = s[t];
return e[i] || (e[i] = []), e[i].push(s), e;
},
// @ts-ignore
{}
);
}
function Ct(r, t) {
return [r.slice(0, t), r.slice(t)].filter((e) => e.length > 0);
}
function A(r, t) {
return t ? Math.floor(Math.random() * (t - r + 1) + r) : r;
}
async function Qs(r) {
r <= 0 || await new Promise((t) => setTimeout(t, r));
}
function As(r) {
if (!r)
throw "Assertion failed!";
}
function Ks(r, t = 300) {
let e;
return (...s) => {
clearTimeout(e), e = setTimeout(() => {
r.apply(r, s);
}, t);
};
}
function ye(r, t, e) {
e < 0 && (e = 0);
const s = r[t];
r.splice(t, 1), r.splice(e, 0, s);
}
function Ds() {
return crypto.randomUUID().split("-").at(0);
}
function Ot(r) {
return typeof r != "function" ? !1 : /^class\s/.test(Function.prototype.toString.call(r));
}
function $s(r) {
return r.charAt(0).toUpperCase() + r.slice(1);
}
function we(r) {
if (r.numberOfChannels === 1)
return r.getChannelData(0);
const t = [];
for (let o = 0; o < r.numberOfChannels; o++)
t.push(r.getChannelData(o));
const e = Math.max(...t.map((o) => o.length)), s = new Float32Array(e * r.numberOfChannels);
let i = 0, n = 0;
for (; n < e; )
t.forEach((o) => {
s[i++] = o[n] !== void 0 ? o[n] : 0;
}), n++;
return s;
}
function H(r, t, e) {
for (let s = 0; s < e.length; s++)
r.setUint8(t + s, e.charCodeAt(s));
}
function be(r, t, e) {
for (let s = 0; s < t.length; s++, e += 2) {
const i = Math.max(-1, Math.min(1, t[s]));
r.setInt16(e, i < 0 ? i * 32768 : i * 32767, !0);
}
return r;
}
function xe(r, t, e) {
const n = t * 2, o = 8, a = 36, l = r.length * 2, c = a + l, u = new ArrayBuffer(o + c), h = new DataView(u);
return H(h, 0, "RIFF"), h.setUint32(4, c, !0), H(h, 8, "WAVE"), H(h, 12, "fmt "), h.setUint32(16, 16, !0), h.setUint16(20, 1, !0), h.setUint16(22, t, !0), h.setUint32(24, e, !0), h.setUint32(28, e * n, !0), h.setUint16(32, n, !0), h.setUint16(34, 16, !0), H(h, 36, "data"), h.setUint32(40, l, !0), be(h, r, o + a);
}
function qs(r, t = "audio/wav") {
const e = we(r), s = xe(e, r.numberOfChannels, r.sampleRate);
return new Blob([s], { type: t });
}
function Ze(r) {
const t = new Float32Array(r.length * r.numberOfChannels);
let e = 0;
for (let s = 0; s < r.numberOfChannels; s++) {
const i = r.getChannelData(s);
t.set(i, e), e += i.length;
}
return t;
}
function Ve(r) {
const t = r.numberOfChannels, e = r.length, s = new Int16Array(e * t);
for (let i = 0; i < e; i++)
for (let n = 0; n < t; n++) {
let o = r.getChannelData(n)[i] * 32767;
o > 32767 && (o = 32767), o < -32767 && (o = -32767), s[i * t + n] = o;
}
return s;
}
async function ti(r, t = 22050, e = Math.sqrt(2)) {
const s = await r.arrayBuffer(), i = new OfflineAudioContext({ sampleRate: t, length: 1 }), n = await i.decodeAudioData(s), o = i.createBuffer(1, n.length, t);
if (n.numberOfChannels >= 2) {
const a = n.getChannelData(0), l = n.getChannelData(1), c = o.getChannelData(0);
for (let u = 0; u < n.length; ++u)
c[u] = e * (a[u] + l[u]) / 2;
return o;
}
return n;
}
function ei(r, t = 44100, e = 2) {
if (r.sampleRate == t && r.numberOfChannels == e)
return r;
const s = Math.floor(r.duration * t), n = new OfflineAudioContext(e, 1, t).createBuffer(e, s, t);
for (let o = 0; o < r.numberOfChannels; o++) {
const a = r.getChannelData(o), l = n.getChannelData(o), c = r.sampleRate / t;
for (let u = 0; u < l.length; u++) {
const h = u * c, p = Math.floor(h), g = Math.ceil(h);
if (g >= a.length)
l[u] = a[p];
else {
const y = h - p;
l[u] = a[p] * (1 - y) + a[g] * y;
}
}
}
return n;
}
async function ke(r) {
const { fps: t, height: e, width: s, bitrate: i } = r, 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',
], o = ["prefer-hardware", "prefer-software"], a = [];
for (const c of n)
for (const u of o)
a.push({
codec: c,
hardwareAcceleration: u,
width: s,
height: e,
bitrate: i,
framerate: t
});
const l = [];
if (!("VideoEncoder" in window))
return l;
for (const c of a) {
const u = await VideoEncoder.isConfigSupported(c);
u.supported && l.push(u.config ?? c);
}
return l.sort(ve);
}
async function We(r) {
const { sampleRate: t, numberOfChannels: e, bitrate: s } = r, i = ["mp4a.40.2", "opus"], n = [];
if (!("AudioEncoder" in window))
return n;
for (const o of i) {
const a = await AudioEncoder.isConfigSupported({
codec: o,
numberOfChannels: e,
bitrate: s,
sampleRate: t
});
a.supported && a.config && n.push(a.config);
}
return n;
}
async function Re(r) {
const t = await We(r.audio), e = await ke(r.video);
if (!e.length)
throw new D({
message: "Encoder can't be configured with any of the tested codecs",
code: "codecsNotSupported"
});
return [e[0], t[0]];
}
function ve(r, t) {
const e = r.hardwareAcceleration ?? "", s = t.hardwareAcceleration ?? "";
return e < s ? -1 : e > s ? 1 : 0;
}
async function Qt(r, t = "untitled") {
const e = document.createElement("a");
if (document.head.appendChild(e), e.download = t, typeof r == "string" && r.startsWith("data:image/svg+xml;base64,")) {
const s = r.split(",")[1], i = atob(s), n = new Array(i.length);
for (let l = 0; l < i.length; l++)
n[l] = i.charCodeAt(l);
const o = new Uint8Array(n), a = new Blob([o], { type: "image/svg+xml" });
e.href = URL.createObjectURL(a), e.download = t.split(".")[0] + ".svg";
} else typeof r == "string" ? e.href = r : e.href = URL.createObjectURL(r);
e.click(), e.remove();
}
async function si(r, t = !0) {
return new Promise((e) => {
const s = document.createElement("input");
s.type = "file", s.accept = r, s.multiple = t, s.onchange = (i) => {
const n = Array.from(i.target?.files ?? []);
e(n);
}, s.click();
});
}
function Yt(r) {
return `${r.hours.toString().padStart(2, "0")}:${r.minutes.toString().padStart(2, "0")}:${r.seconds.toString().padStart(2, "0")},` + r.milliseconds.toString().padStart(3, "0");
}
function Jt(r) {
const t = new Date(1970, 0, 1);
return t.setSeconds(r), t.setMilliseconds(Math.round(r % 1 * 1e3)), {
hours: t.getHours(),
minutes: t.getMinutes(),
seconds: t.getSeconds(),
milliseconds: t.getMilliseconds()
};
}
class Y {
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 f();
}
get stop() {
return this.words.at(-1)?.stop ?? new f();
}
}
var wt = /* @__PURE__ */ ((r) => (r.en = "en", r.de = "de", r))(wt || {});
class bt {
/**
* 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, s, i) {
this.text = t, this.start = new f(e), this.stop = new f(s), this.confidence = i;
}
/**
* Defines the time between start
* and stop returned as a timestamp
*/
get duration() {
return this.stop.subtract(this.start);
}
}
class v {
id = crypto.randomUUID();
language = wt.en;
groups = [];
get text() {
return this.groups.map(({ text: t }) => t).join(" ");
}
get words() {
return this.groups.flatMap(({ words: t }) => t);
}
constructor(t = [], e = wt.en) {
this.groups = t, this.language = e;
}
/**
* Iterate over all words in groups
*/
*iter({ count: t, duration: e, length: s }) {
for (const i of this.groups) {
let n;
for (const [o, a] of i.words.entries())
n && (t && n.words.length >= A(...t) ? (yield n, n = void 0) : e && n?.duration.seconds >= A(...e) ? (yield n, n = void 0) : s && n.text.length >= A(...s) && (yield n, n = void 0)), n ? n.words.push(a) : n = new Y([a]), o == i.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 s = t[e], i = t[e + 1];
i.start.millis - s.stop.millis < 0 ? i.start.millis = s.stop.millis + 1 : s.stop.millis = i.start.millis - 1;
}
return this;
}
/**
* Convert the transcript into a SRT compatible
* string and downloadable blob
*/
toSRT(t = {}) {
let e = 1, s = "";
for (const i of this.iter(t)) {
const n = Jt(i.start.seconds), o = Jt(i.stop.seconds);
s += `${e}
` + Yt(n) + " --> " + Yt(o) + `
${i.text}
`, e += 1;
}
return {
text: s,
blob: new Blob([s], { 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 s = 0;
const i = [];
for (const n of this.groups)
for (const o of n.words)
if (i.length == 0 && e && (s = o.start.millis), i.push(new bt(o.text, o.start.millis - s, o.stop.millis - s)), i.length == t)
return new v([new Y(i)]);
return new v([new Y(i)]);
}
/**
* Create a deep copy of the transcript
* @returns A new Transcript instance
*/
copy() {
return v.fromJSON(this.toJSON());
}
static fromJSON(t) {
const e = new v();
for (const s of t) {
const i = new Y();
for (const n of s)
i.words.push(new bt(n.token, n.start, n.stop));
e.groups.push(i);
}
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 input 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) {
if (t instanceof File)
return v.fromJSON(JSON.parse(await t.text()));
const s = await fetch(t, e);
if (!s.ok)
throw new S({
code: "unexpectedIOError",
message: "An unexpected error occurred while fetching the file"
});
return v.fromJSON(await s.json());
}
}
class Z {
/**
* Unique identifier of the object
*/
id = crypto.randomUUID();
toJSON() {
const t = {};
return (this.constructor.__serializableProperties || []).forEach(({ propertyKey: s, serializer: i }) => {
const n = this[s];
i && n instanceof i ? t[s] = n.toJSON() : t[s] = n;
}), t;
}
static fromJSON(t) {
const e = new this();
return (this.__serializableProperties || []).forEach(({ propertyKey: i, serializer: n }) => {
if (t.hasOwnProperty(i))
if (n) {
const o = n.fromJSON(t[i]);
e[i] = o;
} else
e[i] = t[i];
}), e;
}
}
function d(r) {
return function(t, e) {
t.constructor.__serializableProperties || (t.constructor.__serializableProperties = []), t.constructor.__serializableProperties.push({
propertyKey: e,
serializer: r
});
};
}
function T(r) {
return class extends r {
_handlers = {};
on(e, s) {
if (typeof s != "function")
throw new Error("The callback of an event listener needs to be a function.");
const i = crypto.randomUUID();
return this._handlers[e] ? this._handlers[e][i] = s : this._handlers[e] = { [i]: s }, i;
}
off(e, ...s) {
if (e) {
if (e === "*") {
this._handlers = {};
return;
}
for (const i of Object.values(this._handlers))
e in i && delete i[e];
for (const i of s)
this.off(i);
}
}
emit(e, s) {
const i = new CustomEvent(e, {
detail: s
});
Object.defineProperty(i, "currentTarget", { writable: !1, value: this });
for (const n in this._handlers[e] ?? {})
this._handlers[e]?.[n](i);
for (const n in this._handlers["*"] ?? {})
this._handlers["*"]?.[n](i);
}
bubble(e) {
return this.on("*", (s) => {
e.emit(s.type, s.detail);
});
}
resolve(e) {
return (s, i) => {
this.on("error", i), this.on(e, s);
};
}
};
}
class ft extends T(Z) {
_key;
_value;
_store;
loaded = !1;
constructor(t, e, s) {
super(), this._store = t, this._key = e, this.initValue(s);
}
get key() {
return this._key;
}
get value() {
return this._value;
}
set value(t) {
this._value = t, this._store.set(this._key, t), this.emit("update", void 0);
}
async initValue(t) {
t instanceof Promise ? this._value = await t : this._value = t, this.loaded = !0, this.emit("update", void 0);
}
}
class ri {
storageEngine;
namespace;
constructor(t, e = localStorage) {
this.storageEngine = e, this.namespace = t;
}
define(t, e, s) {
const i = this.get(t);
return i === null ? (this.set(t, e), new ft(this, t, e)) : s && i != null ? new ft(this, t, s(i)) : new ft(this, t, i);
}
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 ni {
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((s, i) => {
this.worker.addEventListener("message", (n) => {
e?.(n.data), n.data.type == "result" && (n.data.type = void 0, s(n.data)), n.data.type == "error" && i(n.data.message);
});
}).then((s) => ({ result: s, error: void 0 })).catch((s) => ({ result: void 0, error: s })).finally(() => {
this.worker.terminate();
});
}
}
function oi(r) {
return async (t) => {
try {
await r(t);
} catch (e) {
self.postMessage({
type: "error",
message: e?.message ?? "An unkown worker error occured"
});
}
};
}
function Se() {
return class extends T(class {
}) {
};
}
function Fe(r) {
const t = r.startsWith("#") ? r.slice(1) : r, e = parseInt(t, 16), s = e >> 16 & 255, i = e >> 8 & 255, n = e & 255;
return { r: s, g: i, b: n };
}
function Xe(r, t, e) {
return `#${((1 << 24) + (Math.round(r) << 16) + (Math.round(t) << 8) + Math.round(e)).toString(16).slice(1)}`;
}
function vt(r, t) {
switch (t) {
case "ease-in":
return r * r;
case "ease-out":
return r * (2 - r);
case "ease-in-out":
return r < 0.5 ? 2 * r * r : -1 + (4 - 2 * r) * r;
case "ease-out-in":
if (r < 0.5) {
const e = r * 2;
return e * (2 - e) / 2;
} else {
const e = (r - 0.5) * 2;
return e * e / 2 + 0.5;
}
default:
return r;
}
}
function K(r, t, e) {
return r + (t - r) * e;
}
function It(r) {
const t = Fe(r);
return Ge(t.r, t.g, t.b);
}
function Ge(r, t, e) {
r /= 255, t /= 255, e /= 255;
const s = Math.max(r, t, e), i = Math.min(r, t, e);
let n = 0, o = 0;
const a = (s + i) / 2;
if (s !== i) {
const l = s - i;
switch (o = a > 0.5 ? l / (2 - s - i) : l / (s + i), s) {
case r:
n = (t - e) / l + (t < e ? 6 : 0);
break;
case t:
n = (e - r) / l + 2;
break;
case e:
n = (r - t) / l + 4;
break;
}
n /= 6;
}
return { h: Math.round(n * 360), s: Math.round(o * 100), l: Math.round(a * 100) };
}
function Te(r, t, e) {
t /= 100, e /= 100, r = (r + 360) % 360;
function s(c, u, h) {
return h < 0 && (h += 1), h > 1 && (h -= 1), h < 1 / 6 ? c + (u - c) * 6 * h : h < 1 / 2 ? u : h < 2 / 3 ? c + (u - c) * (2 / 3 - h) * 6 : c;
}
const i = e < 0.5 ? e * (1 + t) : e + t - e * t, n = 2 * e - i, o = s(n, i, r / 360 + 1 / 3), a = s(n, i, r / 360), l = s(n, i, r / 360 - 1 / 3);
return Xe(
Math.round(o * 255),
Math.round(a * 255),
Math.round(l * 255)
);
}
function Ne(r, t) {
const { frames: e, extrapolate: s = "clamp", easing: i } = r;
if (t <= e[0].frame)
return e[0].value;
if (t >= e[e.length - 1].frame)
return e[e.length - 1].value;
let n, o;
for (let g = 0; g < e.length - 1; g++)
if (t >= e[g].frame && t <= e[g + 1].frame) {
n = e[g], o = e[g + 1];
break;
}
if (!n || !o)
throw new Error("Unexpected error in keyframe interpolation");
const a = (t - n.frame) / (o.frame - n.frame), l = vt(a, n.easing ?? i), c = It(n.value), u = It(o.value);
let h = c.h, p = u.h;
return Math.abs(p - h) > 180 && (h < p ? h += 360 : p += 360), Te(
K(h, p, l),
K(c.s, u.s, l),
K(c.l, u.l, l)
);
}
function At(r, t) {
const { frames: e, extrapolate: s = "clamp", easing: i } = r;
if (t <= e[0].frame)
return e[0].value;
if (t >= e[e.length - 1].frame)
return e[e.length - 1].value;
let n, o;
for (let c = 0; c < e.length - 1; c++)
if (t >= e[c].frame && t <= e[c + 1].frame) {
n = e[c], o = e[c + 1];
break;
}
if (!n || !o)
throw new Error("Unexpected error in keyframe interpolation");
const a = (t - n.frame) / (o.frame - n.frame), l = vt(a, n.easing ?? i);
return K(n.value, o.value, l);
}
function Ue(r, t) {
const { frames: e, extrapolate: s = "clamp" } = r, i = e.map((a) => ({
value: parseFloat(a.value.replace("%", "")),
// Remove '%' and convert to number
frame: a.frame,
easing: a.easing
})), o = At({
key: "",
extrapolate: s,
frames: i
}, t);
return `${Math.round(o)}%`;
}
function Me(r, t) {
const { frames: e, extrapolate: s = "clamp" } = r;
if (t <= e[0].frame)
return e[0].value;
if (t >= e[e.length - 1].frame)
return e[e.length - 1].value;
let i, n;
for (let h = 0; h < e.length - 1; h++)
if (t >= e[h].frame && t <= e[h + 1].frame) {
i = e[h], n = e[h + 1];
break;
}
if (!i || !n)
throw new Error("Unexpected error in keyframe interpolation");
const o = (t - i.frame) / (n.frame - i.frame), a = vt(o, i.easing), l = n.value, c = l.length, u = Math.floor(a * c);
return l.slice(0, u);
}
var Ee = Object.defineProperty, C = (r, t, e, s) => {
for (var i = void 0, n = r.length - 1, o; n >= 0; n--)
(o = r[n]) && (i = o(t, e, i) || i);
return i && Ee(t, e, i), i;
};
const N = class Kt extends T(Z) {
_name;
_delay = new f();
_duration = new f(0, 16);
data = {};
/**
* Flag to check if the clip has been initialized
*/
initialized = !1;
/**
* Defines the type of the clip
*/
type = "base";
/**
* Defines the source of the clip with a
* one-to-many (1:n) relationship
*/
source;
/**
* Flag to check if the clip has been rendered
*/
rendered = !1;
/**
* Timestamp when the clip has been created
*/
createdAt = /* @__PURE__ */ new Date();
disabled = !1;
animations = [];
/**
* 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._delay;
}
/**
* Get the last visible frame
*/
get stop() {
return this._delay.add(this._duration);
}
/**
* Get the delay of the clip
*/
get delay() {
return this._delay;
}
/**
* Get the duration of the clip
*/
get duration() {
return this._duration;
}
constructor(t = {}) {
super(), Object.assign(this, t);
}
/**
* Set the animation time of the clip
* and interpolate the values
* @param time the current absolute time to render
*/
animate(t) {
const e = t.subtract(this.start).frames;
for (const s of this.animations) {
const i = s?.frames[0].value;
typeof i == "number" ? this[s.key] = At(s, e) : typeof i == "string" && i.match(/^[0-9]{1,}%$/) ? this[s.key] = Ue(s, e) : typeof i == "string" && i.match(/^#[0-9abcdef]{3,8}$/i) ? this[s.key] = Ne(s, e) : typeof i == "string" && (this[s.key] = Me(s, e));
}
return this;
}
/**
* Method for connecting the track with the clip
*/
async connect(t) {
this.track = t, this.emit("attach", void 0);
}
/**
* Change clip's offset to zero in seconds. Can be negative
*/
set delay(t) {
typeof t == "number" ? this._delay.frames = t : this._delay = t, this.emit("frame", this._delay.frames);
}
/**
* Set the duration of the clip, needs to be positive
*/
set duration(t) {
typeof t == "number" ? this._duration.frames = t : this._duration = t, this._duration.millis <= 0 && (this._duration.frames = 1), this.emit("frame", this._duration.frames);
}
/**
* Offsets the clip by a given frame number
*/
offset(t) {
return typeof t == "number" ? (this.delay.addFrames(t), this.emit("offset", f.fromFrames(t))) : (this.delay.addMillis(t.millis), this.emit("offset", t)), this.emit("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, e, s = "pause", i = G) {
}
render(t, e) {
}
/**
* Triggered when the clip exits the scene
*/
exit() {
}
/**
* Remove the clip from the track
*/
detach() {
return this.track?.remove(this), this;
}
/**
* Trim the clip to the specified start and stop
*/
trim(t = this.start, e = this.stop) {
return typeof e == "number" && (e = f.fromFrames(e)), typeof t == "number" && (t = f.fromFrames(t)), this.delay = t, this.duration = e.subtract(t), 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?.ticker.frame), typeof t == "number" && (t = f.fromFrames(t)), !t || t.millis <= this.start.millis || t.millis >= this.stop.millis)
throw new w({
code: "splitOutOfRange",
message: "Cannot split clip at the specified time"
});
if (!this.track)
throw new w({
code: "trackNotAttached",
message: "Track must be attached to a track"
});
const e = this.animate(t).copy();
this.duration = t.subtract(this.delay), e.trim(t.addMillis(1), e.stop), e.animations = [];
const s = this.track.clips.findIndex((i) => i.id == this.id);
return await this.track.add(e, s + 1), e;
}
/**
* Create a copy of the clip
*/
copy() {
return Kt.fromJSON(JSON.parse(JSON.stringify(this)));
}
};
C([
d()
], N.prototype, "_name");
C([
d(f)
], N.prototype, "_delay");
C([
d(f)
], N.prototype, "_duration");
C([
d()
], N.prototype, "data");
C([
d()
], N.prototype, "disabled");
C([
d()
], N.prototype, "animations");
let U = N;
var Ce = Object.defineProperty, M = (r, t, e, s) => {
for (var i = void 0, n = r.length - 1, o; n >= 0; n--)
(o = r[n]) && (i = o(t, e, i) || i);
return i && Ce(t, e, i), i;
};
class V extends T(Z) {
duration = new f(0, 16);
/**
* Indicates if the track is loading
*/
state = "IDLE";
data = {};
/**
* 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;
/**
* Access to the element that is used to parse
* the source
*/
element = new Image();
/**
* The object url of the source
*/
get objectURL() {
return this.element.src;
}
/**
* Method for retrieving the file when
* it has been loaded
* @returns Loaded File
*/
async getFile() {
if (!this.file && this.state == "LOADING" && await new Promise(this.resolve("load")), !this.file)
throw new w({
code: "fileNotAccessible",
message: "The desired file cannot be accessed"
});
return this.file;
}
async loadElement() {
this.state != "READY" && (this.element.setAttribute("src", URL.createObjectURL(await this.getFile())), await new Promise((t, e) => {
this.element.onload = () => t(), this.element.onerror = () => e(
new S({
code: "sourceNotProcessable",
message: "An error occurred while processing the input medium."
})
);
}));
}
async loadFile(t) {
this.name = t.name, this.mimeType = kt(t.type), this.external = !1, this.file = t;
}
async loadUrl(t, e) {
const s = await fetch(t, e);
if (!s?.ok) throw new S({
code: "unexpectedIOError",
message: "An unexpected error occurred while fetching the file"
});
const i = await s.blob();
this.name = t.toString().split("/").at(-1) ?? "", this.external = !0, this.file = new File([i], this.name, { type: i.type }), this.externalURL = t, this.mimeType = kt(i.type);
}
async from(t, e) {
try {
this.state = "LOADING", t instanceof File ? await this.loadFile(t) : await this.loadUrl(t, e), await this.loadElement(), this.state = "READY", this.emit("load", void 0);
} catch (s) {
throw this.state = "ERROR", this.emit("error", new Error(String(s))), s;
}
return this;
}
async loaded() {
if (this.state != "READY") {
if (this.state == "ERROR") throw new S({
code: "sourceNotProcessable",
message: "An error occurred while processing the input medium."
});
this.state == "IDLE" && this.file ? await this.from(this.file) : await new Promise(this.resolve("load"));
}
}
/**
* 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.element.removeAttribute("src"), this.externalURL = void 0, delete this.file;
}
/**
* Downloads the file
*/
async download() {
const t = await this.getFile();
Qt(t, this.name);
}
/**
* Get a visulization of the source
* as an html element
*/
async thumbnail() {
return this.element;
}
/**
* Create a new source for the specified input
*/
static async from(t, e, s = new this()) {
return s.from(t, e);
}
}
M([
d()
], V.prototype, "duration");
M([
d()
], V.prototype, "data");
M([
d()
], V.prototype, "type");
M([
d()
], V.prototype, "name");
M([
d()
], V.prototype, "mimeType");
M([
d()
], V.prototype, "externalURL");
M([
d()
], V.prototype, "external");
const zt = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E";
function Oe(r) {
const t = new TextEncoder().encode(r);
let e = "";
const s = t.byteLength;
for (let i = 0; i < s; i++)
e += String.fromCharCode(t[i]);
return btoa(e);
}
function Ye(r) {
if (!r || !r.body) return zt;
const t = r.body.scrollWidth, e = r.body.scrollHeight, s = r.cloneNode(!0), i = s.getElementsByTagName("style").item(0), n = s.getElementsByTagName("body").item(0);
if (n?.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"), !n) return zt;
const o = new XMLSerializer(), a = i ? o.serializeToString(i) : "", l = o.serializeToString(n), c = `
<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}">
body { padding: 0; }
${a}
<foreignObject width="100%" height="100%">
${l}
</foreignObject>
</svg>`;
return "data:image/svg+xml;base64," + Oe(c);
}
function J(r) {
class t extends r {
/**
* The height of the source
*/
get height() {
return 1080;
}
/**
* The width of the source
*/
get width() {
return 1920;
}
/**
* The aspect ratio of the source
*/
get aspectRatio() {
return this.width / this.height;
}
}
return t;
}
class Je extends J(V) {
}
class xt extends J(V) {
type = "html";
/**
* Access to the iframe that is required
* for extracting the html's dimensions
*/
element = document.createElement("iframe");
constructor() {
super(), this.element.style.position = "absolute", this.element.style.width = "0", this.element.style.height = "0", this.element.style.border = "0", this.element.style.visibility = "hidden", document.body.appendChild(this.element);
}
get height() {
return this.element.contentWindow?.document.body.scrollHeight ?? 0;
}
get width() {
return this.element.contentWindow?.document.body.scrollWidth ?? 0;
}
/**
* Access to the html document as loaded
* within the iframe. Can be manipulated with
* javascript
*/
get document() {
return this.element.contentWindow?.document;
}
get imageUrl() {
return Ye(this.document);
}
async thumbnail() {
await this.loaded();
const t = new Image();
return t.className = "object-contain w-full aspect-video h-auto", t.src = this.imageUrl, t;
}
}
function Ie(r, t = {}) {
const { threshold: e = 0.02, hopSize: s = 1024, minDuration: i = 500 } = t, n = [], o = r.getChannelData(0), a = r.sampleRate, l = Math.floor(i / 1e3 * a);
let c = null, u = 0;
for (let h = 0; h < o.length; h += s) {
let p = 0;
const g = Math.min(h + s, o.length);
for (let y = h; y < g; y++)
p += o[y] * o[y];
p = Math.sqrt(p / (g - h)), p < e ? (u += s, c === null && (c = h)) : (c !== null && u >= l && n.push({
start: new f(0, c / a),
stop: new f(0, h / a)
}), c = null, u = 0);
}
return c !== null && u >= l && n.push({
start: new f(0, c / a),
stop: new f(0, o.length / a)
}), n;
}
const mt = 3e3;
class X extends J(V) {
decoding = !1;
_silences;
duration = new f(0, 0, 0, 1);
type = "audio";
element = new Audio();
transcript;
audioBuffer;
async loadElement() {
this.state != "READY" && (this.element.src = URL.createObjectURL(await this.getFile()), await new Promise((t, e) => {
this.element.onloadedmetadata = () => {
this.duration.seconds = this.element.duration, t();
}, this.element.onerror = () => e(
new S({
code: "sourceNotProcessable",
message: "An error occurred while processing the input medium."
})
);
}));
}
async decode(t = 2, e = 48e3, s = !1) {
if (this.decoding && s && (await new Promise(this.resolve("update")), this.audioBuffer))
return this.audioBuffer;
this.decoding = !0;
const i = await this.arrayBuffer(), o = await new OfflineAudioContext(t, 1, e).decodeAudioData(i);
return this.duration.seconds = o.duration, s && (this.audioBuffer = o), this.decoding = !1, this.emit("update", void 0), o;
}
/**
* 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 sample({
length: t = 60,
start: e = 0,
stop: s,
logarithmic: i = !1
} = {}) {
typeof e == "object" && (e = e.millis), typeof s == "object" && (s = s.millis);
const n = this.audioBuffer ?? await this.decode(1, mt, !0), o = n.getChannelData(0), a = Math.floor(Math.max(e * mt / 1e3, 0)), l = s ? Math.floor(Math.min(s * mt / 1e3, n.length)) : n.length, c = Math.floor((l - a) / t), u = new Float32Array(t);
for (let h = 0; h < t; h++) {
const p = a + h * c, g = p + c;
let y = Number.NEGATIVE_INFINITY;
for (let b = p; b < g; b++) {
const Mt = o[b];
Mt > y && (y = Mt);
}
u[h] = i ? Math.log2(1 + y) : y;
}
return u;
}
async thumbnail(t = {}) {
const e = await this.sample(t), s = document.createElement("div");
s.className = "flex flex-row absolute inset-0 audio-samples";
for (const i of e) {
const n = document.createElement("div");
n.className = "audio-sample-item", n.style.height = `${i * 100}%`, s.appendChild(n);
}
return s;
}
/**
* 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 = await new OfflineAudioContext(1, 1, 24e3).decodeAudioData(e);
return this._silences = Ie(i, t), this._silences;
}
}
class Zt extends J(V) {
type = "image";
get height() {
return this.element.naturalHeight;
}
get width() {
return this.element.naturalWidth;
}
async thumbnail() {
return this.element.className = "object-cover w-full aspect-video h-auto", this.element;
}
}
class Vt extends J(X) {
downloadInProgress = !0;
type = "video";
element = document.createElement("video");
get height() {
return this.element.videoHeight;
}
get width() {
return this.element.videoWidth;
}
async loadElement() {
this.state != "READY" && (this.element.controls = !1, this.element.playsInline = !0, this.element.style.display = "hidden", this.element.crossOrigin = "anonymous", this.element.src = this.external ? `${this.externalURL}` : URL.createObjectURL(await this.getFile()), await new Promise((t, e) => {
this.element.onloadedmetadata = () => {
this.duration.seconds = this.element.duration, t();
}, this.element.onerror = () => e(
new S({
code: "sourceNotProcessable",
message: "An error occurred while processing the input medium."
})
);
}));
}
async loadUrl(t, e) {
const s = await fetch(t, e);
if (!s?.ok) throw new S({
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.mimeType = kt(s.headers.get("Content-type")), this.downloadInProgress = !0, s.blob().then((i) => {
this.file = new File([i], this.name, { type: i.type }), this.emit("load", void 0);
}).finally(() => {
this.downloadInProgress = !1;
});
}
async getFile() {
if (!this.file && this.downloadInProgress && await new Promise(this.resolve("load")), !this.file)
throw new w({
code: "fileNotAccessible",
message: "The desired file cannot be accessed"
});
return this.file;
}
async thumbnail() {
return this.element.className = "object-cover w-full aspect-video h-auto", this.element.controls = !1, this.element.addEventListener("mousemove", (t) => {
const e = this.element.getBoundingClientRect(), s = t.clientX - (e?.left ?? 0), i = this.element.duration;
i && e && e.width > 0 && (this.element.currentTime = Math.round(i * (s / e.width)));
}), this.element;
}
}
class ai {
static fromType(t) {
switch (t.type) {
case "video":
return new F();
case "audio":
return new q();
case "html":
return new tt();
case "image":
return new $();
case "text":
return new it();
default:
return new U();
}
}
static fromSource(t) {
if (t.type == "audio" && t instanceof X)
return new q(t);
if (t.type == "video" && t instanceof Vt)
return new F(t);
if (t.type == "image" && t instanceof Zt)
return new $(t);
if (t.type == "html" && t instanceof xt)
return new tt(t);
}
}
class ze {
static fromJSON(t) {
return [new f(t[0]), new f(t[1])];
}
}
var Le = Object.defineProperty, Be = Object.getOwnPropertyDescriptor, st = (r, t, e, s) => {
for (var i = s > 1 ? void 0 : s ? Be(t, e) : t, n = r.length - 1, o; n >= 0; n--)
(o = r[n]) && (i = (s ? o(t, e, i) : o(i)) || i);
return s && i && Le(t, e, i), i;
};
const I = class Dt extends U {
source = new X();
/**
* Is the media currently playing
*/
playing = !1;
range = [new f(), new f()];
constructor(t = {}) {
super(), Object.assign(this, t);
}
get transcript() {
if (!this.source.transcript) return;
if (!this.duration.millis)
return this.source.transcript;
const t = new v(
this.source.transcript.groups.map((e) => {
const s = new Y(
e.words.filter((o) => o.stop.millis > this.range[0].millis && o.start.millis < this.range[1].millis).map((o) => new bt(o.text, o.start.millis, o.stop.millis))
), i = s.words[0], n = s.words[s.words.length - 1];
return i && i.start.millis < this.range[0].millis && (i.start.millis = this.range[0].millis), n && n.stop.millis > this.range[1].millis && (n.stop.millis = this.range[1].millis), s;
}).filter((e) => e.words.length > 0)
);
return t.id = this.source.transcript.id, t;
}
set transcript(t) {
this.source.transcript = t;
}
get start() {
return this.range[0].add(this._delay);
}
get stop() {
return this.range[1].add(this._delay);
}
get duration() {
return this.range[1].subtract(this.range[0]);
}
set duration(t) {
typeof t == "number" ? this.range[1] = this.range[0].copy().addFrames(t) : this.range[1] = this.range[0].add(t), this.range[1].millis <= this.range[0].millis && (this.range[1] = this.range[0].copy().addMillis(1)), this.range[1].millis > this.source.duration.millis && (this.range[1] = this.source.duration), this.emit("frame", this.stop.frames);
}
get muted() {
return this.element?.muted ?? !1;
}
set muted(t) {
this.element && (this.element.muted = t);
}
trim(t = this.start, e = this.stop) {
return typeof t == "number" && (t = f.fromFrames(t)), typeof e == "number" && (e = f.fromFrames(e)), this.subclip(t.subtract(this.delay), e.subtract(this.delay));
}
/**
* Set the media playback to a given time
*/
seek(t) {
return new Promise((e, s) => {
if (!this.element)
return s(
new Et({
code: "elementNotDefined",
message: "Cannot seek on undefined element"
})
);
(t.millis < this.start.millis || t.millis > this.stop.millis) && (t = this.start), this.element.onerror = () => s(this.element?.error), this.element.pause(), this.element.currentTime = t.subtract(this._delay).seconds, this.element.onseeked = () => e();
});
}
exit() {
this.playing && this.element?.pause();
}
/**
* 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 = f.fromFrames(t)), typeof e == "number" && (e = f.fromFrames(e)), t.millis >= e.millis)
throw new w({
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.source.duration.millis && this.source.duration.millis && (e = this.source.duration), this.range = [t, e], this.emit("frame", void 0), this;
}
get volume() {
return this.element?.volume ?? 1;
}
set volume(t) {
this.element && (this.element.volume = t);
}
copy() {
const t = Dt.fromJSON(JSON.parse(JSON.stringify(this)));
return t.source = this.source, t;
}
async split(t) {
if (t || (t = this.track?.composition?.ticker.frame), typeof t == "number" && (t = f.fromFrames(t)), !t || t.millis <= this.start.millis || t.millis >= this.stop.millis)
throw new w({
code: "invalidKeyframe",
message: "Cannot split clip at the specified time"
});
if (!this.track)
throw new Et({
code: "trackNotDefined",
message: "Clip must be attached to a track"
});
const e = t.subtract(this._delay), s = this.animate(t).copy();
this.range[1] = e.copy(), s.range[0] = e.copy().addMillis(1), s.animations = [];
const i = this.track.clips.findIndex((n) => n.id == this.id);
return await this.track.add(s, i + 1), s;
}
/**
* 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 createCaptions(t) {
if (!this.track?.composition)
throw new w({
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).createCaptions(t);
}
/**
* Remove silences from the clip
*
* @param options - Options for silence detection
*/
async removeSilences(t = {}) {
await this.init();
const e = (await this.source.silences(t)).filter((n) => Lt(n, this.range)).sort((n, o) => n.start.millis - o.start.millis);
if (e.length == 0)
return [this];
const s = t.padding ?? 500, i = [this];
for (const n of e) {
const o = i.at(-1);
if (!o) break;
if (!Lt(n, o.range)) continue;
const a = new f(
Math.min(n.start.millis + s, n.stop.millis)
);
if (n.start.millis > o.range[0].millis && n.stop.millis < o.range[1].millis) {
const l = o.copy();
o.range[1] = a, l.range[0] = n.stop, i.push(l);
} else n.start.millis <= o.range[0].millis ? o.range[0] = n.stop : n.stop.millis >= o.range[1].millis && (o.range[1] = a);
}
return i;
}
};
st([
d(ze)
], I.prototype, "range", 2);
st([
d(v)
], I.prototype, "transcript", 1);
st([
d()
], I.prototype, "muted", 1);
st([
d()
], I.prototype, "volume", 1);
let z = I;
function Lt(r, t) {
return r.start.millis >= t[0].millis && r.start.millis <= t[1].millis || r.stop.millis <= t[1].millis && r.stop.millis >= t[0].millis;
}
function $t(r, t) {
return t == "lower" ? r.toLocaleLowerCase() : t == "upper" ? r.toUpperCase() : r;
}
function pt(r = "#000000", t = 100) {
return `${r}${Math.round(t / 100 * 255).toString(16)}`;
}
function m(r, t) {
return typeof r == "number" ? r : Number.parseInt(r.replace("%", "")) * t / 100;
}
class St {
/**
* The canvas element
*/
canvas = document.createElement("canvas");
/**
* The context of the canvas
*/
ctx = this.canvas.getContext("2d");
/**
* The resolution of the canvas
*/
resolution = 1;
/**
* The width of the canvas
*/
width = 1920;
/**
* The height of the canvas
*/
height = 1080;
/**
* The background color of the canvas
*/
background = "#000000";
/**
* The scale of the text
*/
textScale = 4;
constructor(t = 1920, e = 1080, s = "#000000", i = 1) {
this.canvas.style.background = s, this.ctx.imageSmoothingEnabled = !1, this.resolution = i, this.background = s, this.resize(t, e);
}
/**
* Resize the canvas
*/
resize(t, e) {
return this.width = Math.round(t), this.height = Math.round(e), this.canvas.width = Math.round(this.width * this.resolution), this.canvas.height = Math.round(this.height * this.resolution), this;
}
/**
* Copy the renderer
*/
copy(t = this.resolution) {
return new St(this.width, this.height, this.background, t);
}
clear(t) {
let e = 0, s = 0, i = this.width * this.resolution, n = this.height * this.resolution;
return this.ctx.fillStyle = this.background, t && (e = m(t.x ?? 0, this.width) * this.resolution, s = m(t.y ?? 0, this.height) * this.resolution, i = m(t.width, this.width) * this.resolution, n = m(t.height, this.height) * this.resolution), this.ctx.clearRect(e, s, i, n), this.ctx.fillRect(e, s, i, n), this;
}
rect(t) {
return this.ctx.beginPath(), t.radius ? this.ctx.roundRect(
m(t.x ?? 0, this.width) * this.resolution,
m(t.y ?? 0, this.height) * this.resolution,
m(t.width, this.width) * this.resolution,
m(t.height, this.height) * this.resolution,
t.radius * this.resolution
) : this.ctx.rect(
m(t.x ?? 0, this.width) * this.resolution,
m(t.y ?? 0, this.height) * this.resolution,
m(t.width, this.width) * this.resolution,
m(t.height, this.height) * this.resolution
), this.ctx.closePath(), this;
}
circle(t) {
return this.ctx.beginPath(), this.ctx.arc(
m(t.cx, this.width) * this.resolution,
m(t.cy, this.height) * this.resolution,
t.radius * this.resolution,
0,
Math.PI * 2
), this.ctx.closePath(), this;
}
image(t, e) {
return e.width && e.height ? this.ctx.drawImage(
t,
m(e.x ?? 0, this.width) * this.resolution,
m(e.y ?? 0, this.height) * this.resolution,
m(e.width, this.width) * this.resolution,
m(e.height, this.height) * this.resolution
) : this.ctx.drawIm