@webav/av-cliper
Version:
WebCodecs-based, combine video, audio, images, text, with animation support 基于 WebCodecs 合成 视频、音频、图片、文字,支持动画
1,659 lines • 87.2 kB
JavaScript
var vi = Object.defineProperty;
var Je = (s) => {
throw TypeError(s);
};
var Si = (s, t, e) => t in s ? vi(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
var S = (s, t, e) => Si(s, typeof t != "symbol" ? t + "" : t, e), Fe = (s, t, e) => t.has(s) || Je("Cannot " + e);
var n = (s, t, e) => (Fe(s, t, "read from private field"), e ? e.call(s) : t.get(s)), d = (s, t, e) => t.has(s) ? Je("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(s) : t.set(s, e), h = (s, t, e, i) => (Fe(s, t, "write to private field"), i ? i.call(s, e) : t.set(s, e), e), B = (s, t, e) => (Fe(s, t, "access private method"), e);
import wt from "@webav/mp4box.js";
import { workerTimer as Ti, Log as A, autoReadStream as ze, file2stream as oi, EventTool as Ve, recodemux as Ai } from "@webav/internal-utils";
import { Log as Bn } from "@webav/internal-utils";
import * as ki from "wave-resampler";
import { tmpfile as Ee, write as Pe } from "opfs-tools";
function Ii(s) {
return document.createElement(s);
}
function Fi(s, t) {
const e = Ii("pre");
e.style.cssText = `margin: 0; ${t}; visibility: hidden; position: fixed;`, e.textContent = s, document.body.appendChild(e);
const { width: i, height: r } = e.getBoundingClientRect();
e.remove(), e.style.visibility = "visible";
const a = new Image();
a.width = i, a.height = r;
const o = `
<svg xmlns="http://www.w3.org/2000/svg" width="${i}" height="${r}">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">${e.outerHTML}</div>
</foreignObject>
</svg>
`.replace(/\t/g, "").replace(/#/g, "%23");
return a.src = `data:image/svg+xml;charset=utf-8,${o}`, a;
}
async function kn(s, t) {
const e = Fi(s, t);
await new Promise((a) => {
e.onload = a;
});
const i = new OffscreenCanvas(e.width, e.height), r = i.getContext("2d");
return r == null || r.drawImage(e, 0, 0, e.width, e.height), await createImageBitmap(i);
}
function Ri(s) {
const t = new Float32Array(
s.map((i) => i.length).reduce((i, r) => i + r)
);
let e = 0;
for (const i of s)
t.set(i, e), e += i.length;
return t;
}
function Ei(s) {
const t = [];
for (let e = 0; e < s.length; e += 1)
for (let i = 0; i < s[e].length; i += 1)
t[i] == null && (t[i] = []), t[i].push(s[e][i]);
return t.map(Ri);
}
function ci(s) {
if (s.format === "f32-planar") {
const t = [];
for (let e = 0; e < s.numberOfChannels; e += 1) {
const i = s.allocationSize({ planeIndex: e }), r = new ArrayBuffer(i);
s.copyTo(r, { planeIndex: e }), t.push(new Float32Array(r));
}
return t;
} else if (s.format === "f32") {
const t = new ArrayBuffer(s.allocationSize({ planeIndex: 0 }));
return s.copyTo(t, { planeIndex: 0 }), Di(new Float32Array(t), s.numberOfChannels);
} else if (s.format === "s16") {
const t = new ArrayBuffer(s.allocationSize({ planeIndex: 0 }));
return s.copyTo(t, { planeIndex: 0 }), Pi(new Int16Array(t), s.numberOfChannels);
}
throw Error("Unsupported audio data format");
}
function Pi(s, t) {
const e = s.length / t, i = Array.from(
{ length: t },
() => new Float32Array(e)
);
for (let r = 0; r < e; r++)
for (let a = 0; a < t; a++) {
const o = s[r * t + a];
i[a][r] = o / 32768;
}
return i;
}
function Di(s, t) {
const e = s.length / t, i = Array.from(
{ length: t },
() => new Float32Array(e)
);
for (let r = 0; r < e; r++)
for (let a = 0; a < t; a++)
i[a][r] = s[r * t + a];
return i;
}
function Le(s) {
return Array(s.numberOfChannels).fill(0).map((t, e) => s.getChannelData(e));
}
async function Bi(s, t) {
var o;
const e = {
type: t,
data: s
}, i = new ImageDecoder(e);
await Promise.all([i.completed, i.tracks.ready]);
let r = ((o = i.tracks.selectedTrack) == null ? void 0 : o.frameCount) ?? 1;
const a = [];
for (let c = 0; c < r; c += 1)
a.push((await i.decode({ frameIndex: c })).image);
return a;
}
function Ye(s) {
var i, r;
const t = Math.max(...s.map((a) => {
var o;
return ((o = a[0]) == null ? void 0 : o.length) ?? 0;
})), e = new Float32Array(t * 2);
for (let a = 0; a < t; a++) {
let o = 0, c = 0;
for (let l = 0; l < s.length; l++) {
const m = ((i = s[l][0]) == null ? void 0 : i[a]) ?? 0, u = ((r = s[l][1]) == null ? void 0 : r[a]) ?? m;
o += m, c += u;
}
e[a] = o, e[a + t] = c;
}
return e;
}
async function _i(s, t, e) {
const i = s.length, r = Array(e.chanCount).fill(0).map(() => new Float32Array(0));
if (i === 0) return r;
const a = Math.max(...s.map((m) => m.length));
if (a === 0) return r;
if (globalThis.OfflineAudioContext == null)
return s.map(
(m) => new Float32Array(
ki.resample(m, t, e.rate, {
method: "sinc",
LPF: !1
})
)
);
const o = new globalThis.OfflineAudioContext(
e.chanCount,
a * e.rate / t,
e.rate
), c = o.createBufferSource(), l = o.createBuffer(i, a, t);
return s.forEach((m, u) => l.copyToChannel(m, u)), c.buffer = l, c.connect(o.destination), c.start(), Le(await o.startRendering());
}
function Ue(s) {
return new Promise((t) => {
const e = Ti(() => {
e(), t();
}, s);
});
}
function De(s, t, e) {
const i = e - t, r = new Float32Array(i);
let a = 0;
for (; a < i; )
r[a] = s[(t + a) % s.length], a += 1;
return r;
}
function li(s, t) {
const e = Math.floor(s.length / t), i = new Float32Array(e);
for (let r = 0; r < e; r++) {
const a = r * t, o = Math.floor(a), c = a - o;
o + 1 < s.length ? i[r] = s[o] * (1 - c) + s[o + 1] * c : i[r] = s[o];
}
return i;
}
const T = {
sampleRate: 48e3,
channelCount: 2,
codec: "mp4a.40.2"
};
function Ne(s, t) {
const e = t.videoTracks[0], i = {};
if (e != null) {
const a = Oi(s.getTrackById(e.id)).buffer, { descKey: o, type: c } = e.codec.startsWith("avc1") ? { descKey: "avcDecoderConfigRecord", type: "avc1" } : e.codec.startsWith("hvc1") ? { descKey: "hevcDecoderConfigRecord", type: "hvc1" } : { descKey: "", type: "" };
o !== "" && (i.videoTrackConf = {
timescale: e.timescale,
duration: e.duration,
width: e.video.width,
height: e.video.height,
brands: t.brands,
type: c,
[o]: a
}), i.videoDecoderConf = {
codec: e.codec,
codedHeight: e.video.height,
codedWidth: e.video.width,
description: a
};
}
const r = t.audioTracks[0];
if (r != null) {
const a = je(s);
i.audioTrackConf = {
timescale: r.timescale,
samplerate: r.audio.sample_rate,
channel_count: r.audio.channel_count,
hdlr: "soun",
type: r.codec.startsWith("mp4a") ? "mp4a" : r.codec,
description: je(s)
}, i.audioDecoderConf = {
codec: r.codec.startsWith("mp4a") ? T.codec : r.codec,
numberOfChannels: r.audio.channel_count,
sampleRate: r.audio.sample_rate,
...a == null ? {} : Mi(a)
};
}
return i;
}
function Oi(s) {
for (const t of s.mdia.minf.stbl.stsd.entries) {
const e = t.avcC ?? t.hvcC ?? t.av1C ?? t.vpcC;
if (e != null) {
const i = new wt.DataStream(
void 0,
0,
wt.DataStream.BIG_ENDIAN
);
return e.write(i), new Uint8Array(i.buffer.slice(8));
}
}
throw Error("avcC, hvcC, av1C or VPX not found");
}
function je(s, t = "mp4a") {
var i;
const e = (i = s.moov) == null ? void 0 : i.traks.map((r) => r.mdia.minf.stbl.stsd.entries).flat().find(({ type: r }) => r === t);
return e == null ? void 0 : e.esds;
}
function Mi(s) {
var c;
const t = (c = s.esd.descs[0]) == null ? void 0 : c.descs[0];
if (t == null) return {};
const [e, i] = t.data, r = ((e & 7) << 1) + (i >> 7), a = (i & 127) >> 3;
return {
sampleRate: [
96e3,
88200,
64e3,
48e3,
44100,
32e3,
24e3,
22050,
16e3,
12e3,
11025,
8e3,
7350
][r],
numberOfChannels: a
};
}
async function zi(s, t, e) {
const i = wt.createFile(!1);
i.onReady = (a) => {
var l, m;
t({ mp4boxFile: i, info: a });
const o = (l = a.videoTracks[0]) == null ? void 0 : l.id;
o != null && i.setExtractionOptions(o, "video", { nbSamples: 100 });
const c = (m = a.audioTracks[0]) == null ? void 0 : m.id;
c != null && i.setExtractionOptions(c, "audio", { nbSamples: 100 }), i.start();
}, i.onSamples = e, await r();
async function r() {
let a = 0;
const o = 30 * 1024 * 1024;
for (; ; ) {
const c = await s.read(o, {
at: a
});
if (c.byteLength === 0) break;
c.fileStart = a;
const l = i.appendBuffer(c);
if (l == null) break;
a = l;
}
i.stop();
}
}
let He = 0;
function Re(s) {
return s.kind === "file" && s.createReader instanceof Function;
}
var Ce, Jt, Yt, V, R, $, jt, L, rt, _t, bt, W, D, Ot;
const yt = class yt {
constructor(t, e = {}) {
d(this, Ce, He++);
d(this, Jt, A.create(`MP4Clip id:${n(this, Ce)},`));
S(this, "ready");
d(this, Yt, !1);
d(this, V, {
// 微秒
duration: 0,
width: 0,
height: 0,
audioSampleRate: 0,
audioChanCount: 0
});
d(this, R);
d(this, $, []);
d(this, jt, 1);
d(this, L, []);
d(this, rt, []);
d(this, _t, null);
d(this, bt, null);
d(this, W, {
video: null,
audio: null
});
d(this, D, { audio: !0 });
/**
* 拦截 {@link MP4Clip.tick} 方法返回的数据,用于对图像、音频数据二次处理
* @param time 调用 tick 的时间
* @param tickRet tick 返回的数据
*
* @see [移除视频绿幕背景](https://webav-tech.github.io/WebAV/demo/3_2-chromakey-video)
*/
S(this, "tickInterceptor", async (t, e) => e);
d(this, Ot, new AbortController());
if (!(t instanceof ReadableStream) && !Re(t) && !Array.isArray(t.videoSamples))
throw Error("Illegal argument");
h(this, D, { audio: !0, ...e }), h(this, jt, typeof e.audio == "object" && "volume" in e.audio ? e.audio.volume : 1);
const i = async (r) => (await Pe(n(this, R), r), n(this, R));
h(this, R, Re(t) ? t : "localFile" in t ? t.localFile : Ee()), this.ready = (t instanceof ReadableStream ? i(t).then(
(r) => Ke(r, n(this, D))
) : Re(t) ? Ke(t, n(this, D)) : Promise.resolve(t)).then(
async ({ videoSamples: r, audioSamples: a, decoderConf: o, headerBoxPos: c }) => {
h(this, L, r), h(this, rt, a), h(this, W, o), h(this, $, c);
const { videoFrameFinder: l, audioFrameFinder: m } = Li(
{
video: o.video == null ? null : {
...o.video,
hardwareAcceleration: n(this, D).__unsafe_hardwareAcceleration__
},
audio: o.audio
},
await n(this, R).createReader(),
r,
a,
n(this, D).audio !== !1 ? n(this, jt) : 0
);
return h(this, _t, l), h(this, bt, m), h(this, V, Vi(o, r, a)), n(this, Jt).info("MP4Clip meta:", n(this, V)), { ...n(this, V) };
}
);
}
get meta() {
return { ...n(this, V) };
}
/**
* 提供视频头(box: ftyp, moov)的二进制数据
* 使用任意 mp4 demxer 解析即可获得详细的视频信息
* 单元测试包含使用 mp4box.js 解析示例代码
*/
async getFileHeaderBinData() {
await this.ready;
const t = await n(this, R).getOriginFile();
if (t == null) throw Error("MP4Clip localFile is not origin file");
return await new Blob(
n(this, $).map(
({ start: e, size: i }) => t.slice(e, e + i)
)
).arrayBuffer();
}
/**
* 获取素材指定时刻的图像帧、音频数据
* @param time 微秒
*/
async tick(t) {
var r, a, o;
if (t >= n(this, V).duration)
return await this.tickInterceptor(t, {
audio: await ((r = n(this, bt)) == null ? void 0 : r.find(t)) ?? [],
state: "done"
});
const [e, i] = await Promise.all([
((a = n(this, bt)) == null ? void 0 : a.find(t)) ?? [],
(o = n(this, _t)) == null ? void 0 : o.find(t)
]);
return i == null ? await this.tickInterceptor(t, {
audio: e,
state: "success"
}) : await this.tickInterceptor(t, {
video: i,
audio: e,
state: "success"
});
}
/**
* 生成缩略图,默认每个关键帧生成一个 100px 宽度的缩略图。
*
* @param imgWidth 缩略图宽度,默认 100
* @param opts Partial<ThumbnailOpts>
* @returns Promise<Array<{ ts: number; img: Blob }>>
*/
async thumbnails(t = 100, e) {
n(this, Ot).abort(), h(this, Ot, new AbortController());
const i = n(this, Ot).signal;
await this.ready;
const r = "generate thumbnails aborted";
if (i.aborted) throw Error(r);
const { width: a, height: o } = n(this, V), c = Wi(
t,
Math.round(o * (t / a)),
{ quality: 0.1, type: "image/png" }
);
return new Promise(
async (l, m) => {
let u = [];
const w = n(this, W).video;
if (w == null || n(this, L).length === 0) {
p();
return;
}
i.addEventListener("abort", () => {
m(Error(r));
});
async function p() {
i.aborted || l(
await Promise.all(
u.map(async (b) => ({
ts: b.ts,
img: await b.img
}))
)
);
}
function x(b) {
u.push({
ts: b.timestamp,
img: c(b)
});
}
const { start: f = 0, end: y = n(this, V).duration, step: g } = e ?? {};
if (g) {
let b = f;
const C = new hi(
await n(this, R).createReader(),
n(this, L),
{
...w,
hardwareAcceleration: n(this, D).__unsafe_hardwareAcceleration__
}
);
for (; b <= y && !i.aborted; ) {
const v = await C.find(b);
v && x(v), b += g;
}
C.destroy(), p();
} else
await Yi(
n(this, L),
n(this, R),
w,
i,
{ start: f, end: y },
(b, C) => {
b != null && x(b), C && p();
}
);
}
);
}
async split(t) {
if (await this.ready, t <= 0 || t >= n(this, V).duration)
throw Error('"time" out of bounds');
const [e, i] = Xi(
n(this, L),
t
), [r, a] = Gi(
n(this, rt),
t
), o = new yt(
{
localFile: n(this, R),
videoSamples: e ?? [],
audioSamples: r ?? [],
decoderConf: n(this, W),
headerBoxPos: n(this, $)
},
n(this, D)
), c = new yt(
{
localFile: n(this, R),
videoSamples: i ?? [],
audioSamples: a ?? [],
decoderConf: n(this, W),
headerBoxPos: n(this, $)
},
n(this, D)
);
return await Promise.all([o.ready, c.ready]), [o, c];
}
async clone() {
await this.ready;
const t = new yt(
{
localFile: n(this, R),
videoSamples: [...n(this, L)],
audioSamples: [...n(this, rt)],
decoderConf: n(this, W),
headerBoxPos: n(this, $)
},
n(this, D)
);
return await t.ready, t.tickInterceptor = this.tickInterceptor, t;
}
/**
* 拆分 MP4Clip 为仅包含视频轨道和音频轨道的 MP4Clip
* @returns Mp4CLip[]
*/
async splitTrack() {
await this.ready;
const t = [];
if (n(this, L).length > 0) {
const e = new yt(
{
localFile: n(this, R),
videoSamples: [...n(this, L)],
audioSamples: [],
decoderConf: {
video: n(this, W).video,
audio: null
},
headerBoxPos: n(this, $)
},
n(this, D)
);
await e.ready, e.tickInterceptor = this.tickInterceptor, t.push(e);
}
if (n(this, rt).length > 0) {
const e = new yt(
{
localFile: n(this, R),
videoSamples: [],
audioSamples: [...n(this, rt)],
decoderConf: {
audio: n(this, W).audio,
video: null
},
headerBoxPos: n(this, $)
},
n(this, D)
);
await e.ready, e.tickInterceptor = this.tickInterceptor, t.push(e);
}
return t;
}
destroy() {
var t, e;
n(this, Yt) || (n(this, Jt).info("MP4Clip destroy"), h(this, Yt, !0), (t = n(this, _t)) == null || t.destroy(), (e = n(this, bt)) == null || e.destroy());
}
};
Ce = new WeakMap(), Jt = new WeakMap(), Yt = new WeakMap(), V = new WeakMap(), R = new WeakMap(), $ = new WeakMap(), jt = new WeakMap(), L = new WeakMap(), rt = new WeakMap(), _t = new WeakMap(), bt = new WeakMap(), W = new WeakMap(), D = new WeakMap(), Ot = new WeakMap();
let Qe = yt;
function Vi(s, t, e) {
const i = {
duration: 0,
width: 0,
height: 0,
audioSampleRate: 0,
audioChanCount: 0
};
s.video != null && t.length > 0 && (i.width = s.video.codedWidth ?? 0, i.height = s.video.codedHeight ?? 0), s.audio != null && e.length > 0 && (i.audioSampleRate = T.sampleRate, i.audioChanCount = T.channelCount);
let r = 0, a = 0;
if (t.length > 0)
for (let o = t.length - 1; o >= 0; o--) {
const c = t[o];
if (!c.deleted) {
r = c.cts + c.duration;
break;
}
}
if (e.length > 0) {
const o = e.at(-1);
a = o.cts + o.duration;
}
return i.duration = Math.max(r, a), i;
}
function Li(s, t, e, i, r) {
return {
audioFrameFinder: r === 0 || s.audio == null || i.length === 0 ? null : new Ni(
t,
i,
s.audio,
{
volume: r,
targetSampleRate: T.sampleRate
}
),
videoFrameFinder: s.video == null || e.length === 0 ? null : new hi(
t,
e,
s.video
)
};
}
async function Ke(s, t = {}) {
let e = null;
const i = { video: null, audio: null };
let r = [], a = [], o = [], c = -1, l = -1;
const m = await s.createReader();
await zi(
m,
(p) => {
e = p.info;
const x = p.mp4boxFile.ftyp;
o.push({ start: x.start, size: x.size });
const f = p.mp4boxFile.moov;
o.push({ start: f.start, size: f.size });
let { videoDecoderConf: y, audioDecoderConf: g } = Ne(
p.mp4boxFile,
p.info
);
i.video = y ?? null, i.audio = g ?? null, y == null && g == null && A.error("MP4Clip no video and audio track"), A.info(
"mp4BoxFile moov ready",
{
...p.info,
tracks: null,
videoTracks: null,
audioTracks: null
},
i
);
},
(p, x, f) => {
if (x === "video") {
c === -1 && (c = f[0].dts);
for (const y of f)
r.push(w(y, c, "video"));
} else if (x === "audio" && t.audio) {
l === -1 && (l = f[0].dts);
for (const y of f)
a.push(w(y, l, "audio"));
}
}
), await m.close();
const u = r.at(-1) ?? a.at(-1);
if (e == null)
throw Error("MP4Clip stream is done, but not emit ready");
if (u == null)
throw Error("MP4Clip stream not contain any sample");
return _e(r), A.info("mp4 stream parsed"), {
videoSamples: r,
audioSamples: a,
decoderConf: i,
headerBoxPos: o
};
function w(p, x = 0, f) {
const y = f === "video" && p.is_sync ? Ji(p.data, p.description.type) : -1;
let g = p.offset, b = p.size;
return y >= 0 && (g += y, b -= y), {
...p,
is_idr: y >= 0,
offset: g,
size: b,
cts: (p.cts - x) / p.timescale * 1e6,
dts: (p.dts - x) / p.timescale * 1e6,
duration: p.duration / p.timescale * 1e6,
timescale: 1e6,
// 音频数据量可控,直接保存在内存中
data: f === "video" ? null : p.data
};
}
}
var F, xt, Ct, Qt, vt, X, _, at, St, Mt, Kt, zt, ot, qt, Tt, Zt;
class hi {
constructor(t, e, i) {
d(this, F, null);
d(this, xt, 0);
d(this, Ct, { abort: !1, st: performance.now() });
S(this, "find", async (t) => {
(n(this, F) == null || n(this, F).state === "closed" || t <= n(this, xt) || t - n(this, xt) > 3e6) && n(this, Tt).call(this, t), n(this, Ct).abort = !0, h(this, xt, t), h(this, Ct, { abort: !1, st: performance.now() });
const e = await n(this, zt).call(this, t, n(this, F), n(this, Ct));
return h(this, Mt, 0), e;
});
// fix VideoFrame duration is null
d(this, Qt, 0);
d(this, vt, !1);
d(this, X, 0);
d(this, _, []);
d(this, at, 0);
d(this, St, 0);
d(this, Mt, 0);
d(this, Kt, !1);
d(this, zt, async (t, e, i) => {
if (e == null || e.state === "closed" || i.abort) return null;
if (n(this, _).length > 0) {
const r = n(this, _)[0];
return t < r.timestamp ? null : (n(this, _).shift(), t > r.timestamp + (r.duration ?? 0) ? (r.close(), await n(this, zt).call(this, t, e, i)) : (!n(this, Kt) && n(this, _).length < 10 && n(this, qt).call(this, e).catch((a) => {
throw h(this, Kt, !0), n(this, Tt).call(this, t), a;
}), r));
}
if (n(this, ot) || n(this, at) < n(this, St) && e.decodeQueueSize > 0) {
if (performance.now() - i.st > 6e3)
throw Error(
`MP4Clip.tick video timeout, ${JSON.stringify(n(this, Zt).call(this))}`
);
h(this, Mt, n(this, Mt) + 1), await Ue(15);
} else {
if (n(this, X) >= this.samples.length)
return null;
try {
await n(this, qt).call(this, e);
} catch (r) {
throw n(this, Tt).call(this, t), r;
}
}
return await n(this, zt).call(this, t, e, i);
});
d(this, ot, !1);
d(this, qt, async (t) => {
var r, a;
if (n(this, ot) || t.decodeQueueSize > 600) return;
let e = n(this, X) + 1;
if (e > this.samples.length) return;
h(this, ot, !0);
let i = !1;
for (; e < this.samples.length; e++) {
const o = this.samples[e];
if (!i && !o.deleted && (i = !0), o.is_idr) break;
}
if (i) {
const o = this.samples.slice(n(this, X), e);
if (((r = o[0]) == null ? void 0 : r.is_idr) !== !0)
A.warn("First sample not idr frame");
else {
const c = performance.now(), l = await di(o, this.localFileReader), m = performance.now() - c;
if (m > 1e3) {
const u = o[0], w = o.at(-1), p = w.offset + w.size - u.offset;
A.warn(
`Read video samples time cost: ${Math.round(m)}ms, file chunk size: ${p}`
);
}
if (t.state === "closed") return;
h(this, Qt, ((a = l[0]) == null ? void 0 : a.duration) ?? 0), Be(t, l, {
onDecodingError: (u) => {
if (n(this, vt))
throw u;
n(this, at) === 0 && (h(this, vt, !0), A.warn("Downgrade to software decode"), n(this, Tt).call(this));
}
}), h(this, St, n(this, St) + l.length);
}
}
h(this, X, e), h(this, ot, !1);
});
d(this, Tt, (t) => {
var i, r;
if (h(this, ot, !1), n(this, _).forEach((a) => a.close()), h(this, _, []), t == null || t === 0)
h(this, X, 0);
else {
let a = 0;
for (let o = 0; o < this.samples.length; o++) {
const c = this.samples[o];
if (c.is_idr && (a = o), !(c.cts < t)) {
h(this, X, a);
break;
}
}
}
h(this, St, 0), h(this, at, 0), ((i = n(this, F)) == null ? void 0 : i.state) !== "closed" && ((r = n(this, F)) == null || r.close());
const e = {
...this.conf,
...n(this, vt) ? { hardwareAcceleration: "prefer-software" } : {}
};
h(this, F, new VideoDecoder({
output: (a) => {
if (h(this, at, n(this, at) + 1), a.timestamp === -1) {
a.close();
return;
}
let o = a;
a.duration == null && (o = new VideoFrame(a, {
duration: n(this, Qt)
}), a.close()), n(this, _).push(o);
},
error: (a) => {
if (a.message.includes("Codec reclaimed due to inactivity")) {
h(this, F, null), A.warn(a.message);
return;
}
const o = `VideoFinder VideoDecoder err: ${a.message}, config: ${JSON.stringify(e)}, state: ${JSON.stringify(n(this, Zt).call(this))}`;
throw A.error(o), Error(o);
}
})), n(this, F).configure(e);
});
d(this, Zt, () => {
var t, e;
return {
time: n(this, xt),
decState: (t = n(this, F)) == null ? void 0 : t.state,
decQSize: (e = n(this, F)) == null ? void 0 : e.decodeQueueSize,
decCusorIdx: n(this, X),
sampleLen: this.samples.length,
inputCnt: n(this, St),
outputCnt: n(this, at),
cacheFrameLen: n(this, _).length,
softDeocde: n(this, vt),
clipIdCnt: He,
sleepCnt: n(this, Mt),
memInfo: ui()
};
});
S(this, "destroy", () => {
var t, e;
((t = n(this, F)) == null ? void 0 : t.state) !== "closed" && ((e = n(this, F)) == null || e.close()), h(this, F, null), n(this, Ct).abort = !0, n(this, _).forEach((i) => i.close()), h(this, _, []), this.localFileReader.close();
});
this.localFileReader = t, this.samples = e, this.conf = i;
}
}
F = new WeakMap(), xt = new WeakMap(), Ct = new WeakMap(), Qt = new WeakMap(), vt = new WeakMap(), X = new WeakMap(), _ = new WeakMap(), at = new WeakMap(), St = new WeakMap(), Mt = new WeakMap(), Kt = new WeakMap(), zt = new WeakMap(), ot = new WeakMap(), qt = new WeakMap(), Tt = new WeakMap(), Zt = new WeakMap();
function Ui(s, t) {
for (let e = 0; e < t.length; e++) {
const i = t[e];
if (s >= i.cts && s < i.cts + i.duration)
return e;
if (i.cts > s) break;
}
return 0;
}
var te, ee, U, At, G, et, M, Vt, ie, ne, ve, Se;
class Ni {
constructor(t, e, i, r) {
d(this, te, 1);
d(this, ee);
d(this, U, null);
d(this, At, { abort: !1, st: performance.now() });
S(this, "find", async (t) => {
const e = t <= n(this, G) || t - n(this, G) > 1e5;
(n(this, U) == null || n(this, U).state === "closed" || e) && n(this, ve).call(this), e && (h(this, G, t), h(this, et, Ui(t, this.samples))), n(this, At).abort = !0;
const i = t - n(this, G);
h(this, G, t), h(this, At, { abort: !1, st: performance.now() });
const r = await n(this, ie).call(this, Math.ceil(i * (n(this, ee) / 1e6)), n(this, U), n(this, At));
return h(this, Vt, 0), r;
});
d(this, G, 0);
d(this, et, 0);
d(this, M, {
frameCnt: 0,
data: []
});
d(this, Vt, 0);
d(this, ie, async (t, e = null, i) => {
if (e == null || i.abort || e.state === "closed" || t === 0)
return [];
const r = n(this, M).frameCnt - t;
if (r > 0)
return r < T.sampleRate / 10 && n(this, ne).call(this, e), qe(n(this, M), t);
if (e.decoding) {
if (performance.now() - i.st > 3e3)
throw i.abort = !0, Error(
`MP4Clip.tick audio timeout, ${JSON.stringify(n(this, Se).call(this))}`
);
h(this, Vt, n(this, Vt) + 1), await Ue(15);
} else {
if (n(this, et) >= this.samples.length - 1)
return qe(n(this, M), n(this, M).frameCnt);
n(this, ne).call(this, e);
}
return n(this, ie).call(this, t, e, i);
});
d(this, ne, (t) => {
if (t.decodeQueueSize > 10) return;
const i = [];
let r = n(this, et);
for (; r < this.samples.length; ) {
const a = this.samples[r];
if (r += 1, !a.deleted && (i.push(a), i.length >= 10))
break;
}
h(this, et, r), t.decode(
i.map(
(a) => new EncodedAudioChunk({
type: "key",
timestamp: a.cts,
duration: a.duration,
data: a.data
})
)
);
});
d(this, ve, () => {
var t;
h(this, G, 0), h(this, et, 0), h(this, M, {
frameCnt: 0,
data: []
}), (t = n(this, U)) == null || t.close(), h(this, U, Hi(
this.conf,
{
resampleRate: T.sampleRate,
volume: n(this, te)
},
(e) => {
n(this, M).data.push(e), n(this, M).frameCnt += e[0].length;
}
));
});
d(this, Se, () => {
var t, e;
return {
time: n(this, G),
decState: (t = n(this, U)) == null ? void 0 : t.state,
decQSize: (e = n(this, U)) == null ? void 0 : e.decodeQueueSize,
decCusorIdx: n(this, et),
sampleLen: this.samples.length,
pcmLen: n(this, M).frameCnt,
clipIdCnt: He,
sleepCnt: n(this, Vt),
memInfo: ui()
};
});
S(this, "destroy", () => {
h(this, U, null), n(this, At).abort = !0, h(this, M, {
frameCnt: 0,
data: []
}), this.localFileReader.close();
});
this.localFileReader = t, this.samples = e, this.conf = i, h(this, te, r.volume), h(this, ee, r.targetSampleRate);
}
}
te = new WeakMap(), ee = new WeakMap(), U = new WeakMap(), At = new WeakMap(), G = new WeakMap(), et = new WeakMap(), M = new WeakMap(), Vt = new WeakMap(), ie = new WeakMap(), ne = new WeakMap(), ve = new WeakMap(), Se = new WeakMap();
function Hi(s, t, e) {
let i = 0, r = 0;
const a = (u) => {
if (r += 1, u.length !== 0) {
if (t.volume !== 1)
for (const w of u)
for (let p = 0; p < w.length; p++) w[p] *= t.volume;
u.length === 1 && (u = [u[0], u[0]]), e(u);
}
}, o = $i(a), c = t.resampleRate !== s.sampleRate;
let l = new AudioDecoder({
output: (u) => {
const w = ci(u);
c ? o(
() => _i(w, u.sampleRate, {
rate: t.resampleRate,
chanCount: u.numberOfChannels
})
) : a(w), u.close();
},
error: (u) => {
u.message.includes("Codec reclaimed due to inactivity") || m("MP4Clip AudioDecoder err", u);
}
});
l.configure(s);
function m(u, w) {
const p = `${u}: ${w.message}, state: ${JSON.stringify(
{
qSize: l.decodeQueueSize,
state: l.state,
inputCnt: i,
outputCnt: r
}
)}`;
throw A.error(p), Error(p);
}
return {
decode(u) {
i += u.length;
try {
for (const w of u) l.decode(w);
} catch (w) {
m("decode audio chunk error", w);
}
},
close() {
l.state !== "closed" && l.close();
},
get decoding() {
return i > r && l.decodeQueueSize > 0;
},
get state() {
return l.state;
},
get decodeQueueSize() {
return l.decodeQueueSize;
}
};
}
function $i(s) {
const t = [];
let e = 0;
function i(o, c) {
t[c] = o, r();
}
function r() {
const o = t[e];
o != null && (s(o), e += 1, r());
}
let a = 0;
return (o) => {
const c = a;
a += 1, o().then((l) => i(l, c)).catch((l) => i(l, c));
};
}
function qe(s, t) {
const e = [new Float32Array(t), new Float32Array(t)];
let i = 0, r = 0;
for (; r < s.data.length; ) {
const [a, o] = s.data[r];
if (i + a.length > t) {
const c = t - i;
e[0].set(a.subarray(0, c), i), e[1].set(o.subarray(0, c), i), s.data[r][0] = a.subarray(c, a.length), s.data[r][1] = o.subarray(c, o.length);
break;
} else
e[0].set(a, i), e[1].set(o, i), i += a.length, r++;
}
return s.data = s.data.slice(r), s.frameCnt -= t, e;
}
async function di(s, t) {
const e = s[0], i = s.at(-1);
if (i == null) return [];
const r = i.offset + i.size - e.offset;
if (r < 3e7) {
const a = new Uint8Array(
await t.read(r, { at: e.offset })
);
return s.map((o) => {
const c = o.offset - e.offset;
return new EncodedVideoChunk({
type: o.is_sync ? "key" : "delta",
timestamp: o.cts,
duration: o.duration,
data: a.subarray(c, c + o.size)
});
});
}
return await Promise.all(
s.map(async (a) => new EncodedVideoChunk({
type: a.is_sync ? "key" : "delta",
timestamp: a.cts,
duration: a.duration,
data: await t.read(a.size, {
at: a.offset
})
}))
);
}
function Wi(s, t, e) {
const i = new OffscreenCanvas(s, t), r = i.getContext("2d");
return async (a) => (r.drawImage(a, 0, 0, s, t), a.close(), await i.convertToBlob(e));
}
function Xi(s, t) {
if (s.length === 0) return [];
let e = 0, i = 0, r = -1;
for (let l = 0; l < s.length; l++) {
const m = s[l];
if (r === -1 && t < m.cts && (r = l - 1), m.is_idr)
if (r === -1)
e = l;
else {
i = l;
break;
}
}
const a = s[r];
if (a == null) throw Error("Not found video sample by time");
const o = s.slice(0, i === 0 ? s.length : i).map((l) => ({ ...l }));
for (let l = e; l < o.length; l++) {
const m = o[l];
t < m.cts && (m.deleted = !0, m.cts = -1);
}
_e(o);
const c = s.slice(a.is_idr ? r : e).map((l) => ({ ...l, cts: l.cts - t }));
for (const l of c)
l.cts < 0 && (l.deleted = !0, l.cts = -1);
return _e(c), [o, c];
}
function Gi(s, t) {
if (s.length === 0) return [];
let e = -1;
for (let a = 0; a < s.length; a++) {
const o = s[a];
if (!(t > o.cts)) {
e = a;
break;
}
}
if (e === -1) throw Error("Not found audio sample by time");
const i = s.slice(0, e).map((a) => ({ ...a })), r = s.slice(e).map((a) => ({ ...a, cts: a.cts - t }));
return [i, r];
}
function Be(s, t, e) {
let i = 0;
if (s.state === "configured") {
for (; i < t.length; i++) s.decode(t[i]);
s.flush().catch((r) => {
if (!(r instanceof Error)) throw r;
if (r.message.includes("Decoding error") && e.onDecodingError != null) {
e.onDecodingError(r);
return;
}
if (!r.message.includes("Aborted due to close"))
throw r;
});
}
}
function Ji(s, t) {
if (t !== "avc1" && t !== "hvc1") return 0;
const e = new DataView(s.buffer);
let i = 0;
for (; i < s.byteLength - 4; ) {
if (t === "avc1" && (e.getUint8(i + 4) & 31) === 5)
return i;
if (t === "hvc1") {
const r = e.getUint8(i + 4) >> 1 & 63;
if (r === 19 || r === 20) return i;
}
i += e.getUint32(i) + 4;
}
return -1;
}
async function Yi(s, t, e, i, r, a) {
const o = await t.createReader(), c = await di(
s.filter(
(u) => !u.deleted && u.is_sync && u.cts >= r.start && u.cts <= r.end
),
o
);
if (c.length === 0 || i.aborted) return;
let l = 0;
Be(m(), c, {
onDecodingError: (u) => {
A.warn("thumbnailsByKeyFrame", u), l === 0 ? Be(m(!0), c, {
onDecodingError: (w) => {
o.close(), A.error("thumbnailsByKeyFrame retry soft deocde", w);
}
}) : (a(null, !0), o.close());
}
});
function m(u = !1) {
const w = {
...e,
...u ? { hardwareAcceleration: "prefer-software" } : {}
}, p = new VideoDecoder({
output: (x) => {
l += 1;
const f = l === c.length;
a(x, f), f && (o.close(), p.state !== "closed" && p.close());
},
error: (x) => {
const f = `thumbnails decoder error: ${x.message}, config: ${JSON.stringify(w)}, state: ${JSON.stringify(
{
qSize: p.decodeQueueSize,
state: p.state,
outputCnt: l,
inputCnt: c.length
}
)}`;
throw A.error(f), Error(f);
}
});
return i.addEventListener("abort", () => {
o.close(), p.state !== "closed" && p.close();
}), p.configure(w), p;
}
}
function _e(s) {
let t = 0, e = null;
for (const i of s)
if (!i.deleted) {
if (i.is_sync && (t += 1), t >= 2) break;
(e == null || i.cts < e.cts) && (e = i);
}
e != null && e.cts < 2e5 && (e.duration += e.cts, e.cts = 0);
}
function ui() {
try {
const s = performance.memory;
return {
jsHeapSizeLimit: s.jsHeapSizeLimit,
totalJSHeapSize: s.totalJSHeapSize,
usedJSHeapSize: s.usedJSHeapSize,
percentUsed: (s.usedJSHeapSize / s.jsHeapSizeLimit).toFixed(3),
percentTotal: (s.totalJSHeapSize / s.jsHeapSizeLimit).toFixed(3)
};
} catch {
return {};
}
}
var E, z, I, Te, fi;
const gt = class gt {
/**
* 静态图片可使用流、ImageBitmap 初始化
*
* 动图需要使用 VideoFrame[] 或提供图片类型
*/
constructor(t) {
d(this, Te);
S(this, "ready");
d(this, E, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, z, null);
d(this, I, []);
S(this, "tickInterceptor", async (t, e) => e);
const e = (i) => (h(this, z, i), n(this, E).width = i.width, n(this, E).height = i.height, n(this, E).duration = 1 / 0, { ...n(this, E) });
if (t instanceof ReadableStream)
this.ready = new Response(t).blob().then((i) => createImageBitmap(i)).then(e);
else if (t instanceof ImageBitmap)
this.ready = Promise.resolve(e(t));
else if (Array.isArray(t) && t.every((i) => i instanceof VideoFrame)) {
h(this, I, t);
const i = n(this, I)[0];
if (i == null) throw Error("The frame count must be greater than 0");
h(this, E, {
width: i.displayWidth,
height: i.displayHeight,
duration: n(this, I).reduce(
(r, a) => r + (a.duration ?? 0),
0
)
}), this.ready = Promise.resolve({ ...n(this, E), duration: 1 / 0 });
} else if ("type" in t)
this.ready = B(this, Te, fi).call(this, t.stream, t.type).then(() => ({
width: n(this, E).width,
height: n(this, E).height,
duration: 1 / 0
}));
else
throw Error("Illegal arguments");
}
/**
* ⚠️ 静态图片的 duration 为 Infinity
*
* 使用 Sprite 包装时需要将它的 duration 设置为有限数
*
*/
get meta() {
return { ...n(this, E) };
}
async tick(t) {
if (n(this, z) != null)
return await this.tickInterceptor(t, {
video: await createImageBitmap(n(this, z)),
state: "success"
});
const e = t % n(this, E).duration;
return await this.tickInterceptor(t, {
video: (n(this, I).find(
(i) => e >= i.timestamp && e <= i.timestamp + (i.duration ?? 0)
) ?? n(this, I)[0]).clone(),
state: "success"
});
}
async split(t) {
if (await this.ready, n(this, z) != null)
return [
new gt(await createImageBitmap(n(this, z))),
new gt(await createImageBitmap(n(this, z)))
];
let e = -1;
for (let a = 0; a < n(this, I).length; a++) {
const o = n(this, I)[a];
if (!(t > o.timestamp)) {
e = a;
break;
}
}
if (e === -1) throw Error("Not found frame by time");
const i = n(this, I).slice(0, e).map((a) => new VideoFrame(a)), r = n(this, I).slice(e).map(
(a) => new VideoFrame(a, {
timestamp: a.timestamp - t
})
);
return [new gt(i), new gt(r)];
}
async clone() {
await this.ready;
const t = n(this, z) == null ? n(this, I).map((e) => e.clone()) : await createImageBitmap(n(this, z));
return new gt(t);
}
destroy() {
var t;
A.info("ImgClip destroy"), (t = n(this, z)) == null || t.close(), n(this, I).forEach((e) => e.close());
}
};
E = new WeakMap(), z = new WeakMap(), I = new WeakMap(), Te = new WeakSet(), fi = async function(t, e) {
h(this, I, await Bi(t, e));
const i = n(this, I)[0];
if (i == null) throw Error("No frame available in gif");
h(this, E, {
duration: n(this, I).reduce((r, a) => r + (a.duration ?? 0), 0),
width: i.codedWidth,
height: i.codedHeight
}), A.info("ImgClip ready:", n(this, E));
};
let Ze = gt;
var kt, it, ct, J, Ae, mi, lt, Y;
const tt = class tt {
/**
*
* @param dataSource 音频文件流
* @param opts 音频配置,控制音量、是否循环
*/
constructor(t, e = {}) {
d(this, Ae);
S(this, "ready");
d(this, kt, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, it, new Float32Array());
d(this, ct, new Float32Array());
d(this, J);
/**
* 拦截 {@link AudioClip.tick} 方法返回的数据,用于对音频数据二次处理
* @param time 调用 tick 的时间
* @param tickRet tick 返回的数据
*
* @see [移除视频绿幕背景](https://webav-tech.github.io/WebAV/demo/3_2-chromakey-video)
*/
S(this, "tickInterceptor", async (t, e) => e);
// 微秒
d(this, lt, 0);
d(this, Y, 0);
h(this, J, {
loop: !1,
volume: 1,
...e
}), this.ready = B(this, Ae, mi).call(this, t).then(() => ({
// audio 没有宽高,无需绘制
width: 0,
height: 0,
duration: e.loop ? 1 / 0 : n(this, kt).duration
}));
}
/**
* 音频元信息
*
* ⚠️ 注意,这里是转换后(标准化)的元信息,非原始音频元信息
*/
get meta() {
return {
...n(this, kt),
sampleRate: T.sampleRate,
chanCount: 2
};
}
/**
* 获取音频素材完整的 PCM 数据
*/
getPCMData() {
return [n(this, it), n(this, ct)];
}
/**
* 返回上次与当前时刻差对应的音频 PCM 数据;
*
* 若差值超过 3s 或当前时间小于上次时间,则重置状态
* @example
* tick(0) // => []
* tick(1e6) // => [leftChanPCM(1s), rightChanPCM(1s)]
*
*/
async tick(t) {
if (!n(this, J).loop && t >= n(this, kt).duration)
return await this.tickInterceptor(t, { audio: [], state: "done" });
const e = t - n(this, lt);
if (t < n(this, lt) || e > 3e6)
return h(this, lt, t), h(this, Y, Math.ceil(
n(this, lt) / 1e6 * T.sampleRate
)), await this.tickInterceptor(t, {
audio: [new Float32Array(0), new Float32Array(0)],
state: "success"
});
h(this, lt, t);
const i = Math.ceil(
e / 1e6 * T.sampleRate
), r = n(this, Y) + i, a = n(this, J).loop ? [
De(n(this, it), n(this, Y), r),
De(n(this, ct), n(this, Y), r)
] : [
n(this, it).slice(n(this, Y), r),
n(this, ct).slice(n(this, Y), r)
];
return h(this, Y, r), await this.tickInterceptor(t, { audio: a, state: "success" });
}
/**
* 按指定时间切割,返回前后两个音频素材
* @param time 时间,单位微秒
*/
async split(t) {
await this.ready;
const e = Math.ceil(t / 1e6 * T.sampleRate), i = new tt(
this.getPCMData().map((a) => a.slice(0, e)),
n(this, J)
), r = new tt(
this.getPCMData().map((a) => a.slice(e)),
n(this, J)
);
return [i, r];
}
async clone() {
await this.ready;
const t = new tt(this.getPCMData(), n(this, J));
return await t.ready, t;
}
/**
* 销毁实例,释放资源
*/
destroy() {
h(this, it, new Float32Array(0)), h(this, ct, new Float32Array(0)), A.info("---- audioclip destroy ----");
}
};
kt = new WeakMap(), it = new WeakMap(), ct = new WeakMap(), J = new WeakMap(), Ae = new WeakSet(), mi = async function(t) {
tt.ctx == null && (tt.ctx = new AudioContext({
sampleRate: T.sampleRate
}));
const e = performance.now(), i = t instanceof ReadableStream ? await ji(t, tt.ctx) : t;
A.info("Audio clip decoded complete:", performance.now() - e);
const r = n(this, J).volume;
if (r !== 1)
for (const a of i)
for (let o = 0; o < a.length; o += 1) a[o] *= r;
n(this, kt).duration = i[0].length / T.sampleRate * 1e6, h(this, it, i[0]), h(this, ct, i[1] ?? n(this, it)), A.info(
"Audio clip convert to AudioData, time:",
performance.now() - e
);
}, lt = new WeakMap(), Y = new WeakMap(), S(tt, "ctx", null);
let ti = tt;
async function ji(s, t) {
const e = await new Response(s).arrayBuffer();
return Le(await t.decodeAudioData(e));
}
var It, se, Lt, Ut;
const ke = class ke {
constructor(t) {
S(this, "ready");
d(this, It, {
// 微秒
duration: 0,
width: 0,
height: 0
});
d(this, se, () => {
});
/**
* 实时流的音轨
*/
S(this, "audioTrack");
d(this, Lt, null);
d(this, Ut);
h(this, Ut, t), this.audioTrack = t.getAudioTracks()[0] ?? null, n(this, It).duration = 1 / 0;
const e = t.getVideoTracks()[0];
e != null ? (e.contentHint = "motion", this.ready = new Promise((i) => {
h(this, se, Qi(e, (r) => {
n(this, It).width = r.width, n(this, It).height = r.height, h(this, Lt, r), i(this.meta);
}));
})) : this.ready = Promise.resolve(this.meta);
}
get meta() {
return {
...n(this, It)
};
}
async tick() {
return {
video: n(this, Lt) == null ? null : await createImageBitmap(n(this, Lt)),
audio: [],
state: "success"
};
}
async split() {
return [await this.clone(), await this.clone()];
}
async clone() {
return new ke(n(this, Ut).clone());
}
destroy() {
n(this, Ut).getTracks().forEach((t) => t.stop()), n(this, se).call(this);
}
};
It = new WeakMap(), se = new WeakMap(), Lt = new WeakMap(), Ut = new WeakMap(), S(ke, "ctx", null);
let ei = ke;
function Qi(s, t) {
let e = !1, i;
return ze(
new MediaStreamTrackProcessor({
track: s
}).readable,
{
onChunk: async (r) => {
if (!e) {
const { displayHeight: a, displayWidth: o } = r, c = o ?? 0, l = a ?? 0, m = new OffscreenCanvas(c, l);
i = m.getContext("2d"), t(m), e = !0;
}
i.drawImage(r, 0, 0), r.close();
},
onDone: async () => {
}
}
);
}
var P, re, j, Q, K, O, nt, st, Ie, pi;
const Gt = class Gt {
constructor(t, e) {
d(this, Ie);
S(this, "ready");
d(this, P, []);
d(this, re, {
width: 0,
height: 0,
duration: 0
});
d(this, j, {
color: "#FFF",
textBgColor: null,
type: "srt",
fontSize: 30,
letterSpacing: null,
bottomOffset: 30,
fontFamily: "Noto Sans SC",
strokeStyle: "#000",
lineWidth: null,
lineCap: null,
lineJoin: null,
textShadow: {
offsetX: 2,
offsetY: 2,
blur: 4,
color: "#000"
},
videoWidth: 1280,
videoHeight: 720
});
d(this, Q);
d(this, K);
d(this, O, null);
d(this, nt, 0);
d(this, st, 0);
var l;
if (h(this, P, Array.isArray(t) ? t : Ki(t).map(({ start: m, end: u, text: w }) => ({
start: m * 1e6,
end: u * 1e6,
text: w
}))), n(this, P).length === 0) throw Error("No subtitles content");
h(this, j, Object.assign(n(this, j), e)), h(this, st, e.textBgColor == null ? 0 : (e.fontSize ?? 50) * 0.2);
const { fontSize: i, fontFamily: r, videoWidth: a, videoHeight: o, letterSpacing: c } = n(this, j);
h(this, nt, i + n(this, st) * 2), h(this, Q, new OffscreenCanvas(a, o)), h(this, K, n(this, Q).getContext("2d")), n(this, K).font = `${i}px ${r}`, n(this, K).textAlign = "center", n(this, K).textBaseline = "top", n(this, K).letterSpacing = c ?? "0px", h(this, re, {
width: a,
height: o,
duration: ((l = n(this, P).at(-1)) == null ? void 0 : l.end) ?? 0
}), this.ready = Promise.resolve(this.meta);
}
get meta() {
return { ...n(this, re) };
}
/**
* @see {@link IClip.tick}
*/
async tick(t) {
var a, o;
if (n(this, O) != null && t >= n(this, O).timestamp && t <= n(this, O).timestamp + (n(this, O).duration ?? 0))
return { video: n(this, O).clone(), state: "success" };
let e = 0;
for (; e < n(this, P).length && !(t <= n(this, P)[e].end); e += 1)
;
const i = n(this, P)[e] ?? n(this, P).at(-1);
if (t > i.end) return { state: "done" };
if (t < i.start) {
n(this, K).clearRect(0, 0, n(this, Q).width, n(this, Q).height);
const c = new VideoFrame(n(this, Q), {
timestamp: t,
// 直到下个字幕出现的时机
duration: i.start - t
});
return (a = n(this, O)) == null || a.close(), h(this, O, c), { video: c.clone(), state: "success" };
}
B(this, Ie, pi).call(this, i.text);
const r = new VideoFrame(n(this, Q), {
timestamp: t,
duration: i.end - t
});
return (o = n(this, O)) == null || o.close(), h(this, O, r), { video: r.clone(), state: "success" };
}
/**
* @see {@link IClip.split}
*/
async split(t) {
await this.ready;
let e = -1;
for (let c = 0; c < n(this, P).length; c++) {
const l = n(this, P)[c];
if (!(t > l.start)) {
e = c;
break;
}
}
if (e === -1) throw Error("Not found subtitle by time");
const i = n(this, P).slice(0, e).map((c) => ({ ...c }));
let r = i.at(-1), a = null;
r != null && r.end > t && (a = {
start: 0,
end: r.end - t,
text: r.text
}, r.end = t);
const o = n(this, P).slice(e).map((c) => ({ ...c, start: c.start - t, end: c.end - t }));
return a != null && o.unshift(a), [
new Gt(i, n(this, j)),
new Gt(o, n(this, j))
];
}
/**
* @see {@link IClip.clone}
*/
async clone() {
return new Gt(n(this, P).slice(0), n(this, j));
}
/**
* @see {@link IClip.destroy}
*/
destroy() {
var t;
(t = n(this, O)) == null || t.close();
}
};
P = new WeakMap(), re = new WeakMap(), j = new WeakMap(), Q = new WeakMap(), K = new WeakMap(), O = new WeakMap(), nt = new WeakMap(), st = new WeakMap(), Ie = new WeakSet(), pi = function(t) {
const e = t.split(`
`).reverse().map((g) => g.trim()), { width: i, height: r } = n(this, Q), {
color: a,
fontSize: o,
textBgColor: c,
textShadow: l,
strokeStyle: m,
lineWidth: u,
lineCap: w,
lineJoin: p,
bottomOffset: x
} = n(this, j), f = n(this, K);
f.clearRect(0, 0, i, r), f.globalAlpha = 0.6;
let y = x;
for (const g of e) {
const b = f.measureText(g), C = i / 2;
c != null && (f.shadowOffsetX = 0, f.shadowOffsetY = 0, f.shadowBlur = 0, f.fillStyle = c, f.globalAlpha = 0.5, f.fillRect(
C - b.actualBoundingBoxLeft - n(this, st),
r - y - n(this, nt),
b.width + n(this, st) * 2,
n(this, nt)
)), f.shadowColor = l.color, f.shadowOffsetX = l.offsetX, f.shadowOffsetY = l.offsetY, f.shadowBlur = l.blur, f.globalAlpha = 1, m != null && (f.lineWidth = u ?? o / 6, w != null && (f.lineCap = w), p != null && (f.lineJoin = p), f.strokeStyle = m, f.strokeText(
g,
C,
r - y - n(this, nt) + n(this, st)
)), f.fillStyle = a, f.fillText(
g,
C,
r - y - n(this, nt) + n(this, st)
), y += n(this, nt) + o * 0.2;
}
};
let ii = Gt;
function ni(s) {
const t = s.match(/(\d{2}):(\d{2}