diff --git a/dist/assets/__vite-browser-external-BU5UGKjj.js b/dist/assets/__vite-browser-external-BU5UGKjj.js new file mode 100644 index 0000000..8769489 --- /dev/null +++ b/dist/assets/__vite-browser-external-BU5UGKjj.js @@ -0,0 +1,2 @@ +import { t as require___vite_browser_external } from "./src-Bv2YeDx1.js"; +export default require___vite_browser_external(); diff --git a/dist/assets/index-DCxWcq5K.js b/dist/assets/index-BUD8Xh0Q.js similarity index 92% rename from dist/assets/index-DCxWcq5K.js rename to dist/assets/index-BUD8Xh0Q.js index bb574f0..b0d0425 100644 --- a/dist/assets/index-DCxWcq5K.js +++ b/dist/assets/index-BUD8Xh0Q.js @@ -1,3 +1,4 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mediabunny-aac-encoder-CtuZhjiD.js","assets/src-Bv2YeDx1.js"])))=>i.map(i=>d[i]); import mathjax3 from "https://esm.sh/markdown-it-mathjax3"; //#region \0vite/modulepreload-polyfill.js (function polyfill() { @@ -664,11 +665,213 @@ function kbBind() { }); } //#endregion +//#region \0vite/preload-helper.js +var scriptRel = "modulepreload"; +var assetsURL = function(dep) { + return "/" + dep; +}; +var seen = {}; +var __vitePreload = function preload(baseModule, deps, importerUrl) { + let promise = Promise.resolve(); + if (deps && deps.length > 0) { + const links = document.getElementsByTagName("link"); + const cspNonceMeta = document.querySelector("meta[property=csp-nonce]"); + const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce"); + function allSettled(promises) { + return Promise.all(promises.map((p) => Promise.resolve(p).then((value) => ({ + status: "fulfilled", + value + }), (reason) => ({ + status: "rejected", + reason + })))); + } + promise = allSettled(deps.map((dep) => { + dep = assetsURL(dep, importerUrl); + if (dep in seen) return; + seen[dep] = true; + const isCss = dep.endsWith(".css"); + const cssSelector = isCss ? "[rel=\"stylesheet\"]" : ""; + if (!!importerUrl) for (let i = links.length - 1; i >= 0; i--) { + const link = links[i]; + if (link.href === dep && (!isCss || link.rel === "stylesheet")) return; + } + else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) return; + const link = document.createElement("link"); + link.rel = isCss ? "stylesheet" : scriptRel; + if (!isCss) link.as = "script"; + link.crossOrigin = ""; + link.href = dep; + if (cspNonce) link.setAttribute("nonce", cspNonce); + document.head.appendChild(link); + if (isCss) return new Promise((res, rej) => { + link.addEventListener("load", res); + link.addEventListener("error", () => rej(/* @__PURE__ */ new Error(`Unable to preload CSS for ${dep}`))); + }); + })); + } + function handlePreloadError(err) { + const e = new Event("vite:preloadError", { cancelable: true }); + e.payload = err; + window.dispatchEvent(e); + if (!e.defaultPrevented) throw err; + } + return promise.then((res) => { + for (const item of res || []) { + if (item.status !== "rejected") continue; + handlePreloadError(item.reason); + } + return baseModule().catch(handlePreloadError); + }); +}; +//#endregion +//#region src/video.js +var SHORT_SIDE = 360, FPS = 4, VIDEO_BPS = { + hevc: 13e4, + avc: 16e4 +}, AUDIO_BPS = 32e3; +var even = (n) => Math.max(2, Math.round(n / 2) * 2); +var mb; +var loadMb = async () => { + if (mb) return mb; + const m = await __vitePreload(() => import("./src-Bv2YeDx1.js"), []); + if (!await m.canEncodeAudio("aac", { + numberOfChannels: 1, + sampleRate: 48e3, + bitrate: AUDIO_BPS + })) (await __vitePreload(async () => { + const { registerAacEncoder } = await import("./mediabunny-aac-encoder-CtuZhjiD.js"); + return { registerAacEncoder }; + }, __vite__mapDeps([0,1]))).registerAacEncoder(); + return mb = m; +}; +var isVideo = (name, mime) => /^video\//.test(mime) || /\.(mp4|m4v|mov|webm|mkv|3gp)$/i.test(name); +async function compressVideo(file, { onProgress, signal } = {}) { + const { Input, Output, Conversion, ALL_FORMATS, BlobSource, BufferTarget, Mp4OutputFormat, Quality, canEncodeVideo } = await loadMb(); + const input = new Input({ + source: new BlobSource(file), + formats: ALL_FORMATS + }), track = await input.getPrimaryVideoTrack(); + if (!track) throw new Error("no video track"); + const w = await track.getDisplayWidth(), h = await track.getDisplayHeight(), s = Math.min(1, SHORT_SIDE / Math.min(w, h)), width = even(w * s), height = even(h * s); + let codec = null; + for (const c of ["hevc", "avc"]) if (await canEncodeVideo(c, { + width, + height, + frameRate: FPS, + bitrate: VIDEO_BPS[c] + })) { + codec = c; + break; + } + if (!codec) throw new Error("this device cannot encode HEVC or H.264"); + const output = new Output({ + format: new Mp4OutputFormat({ fastStart: "in-memory" }), + target: new BufferTarget() + }); + const video = { + frameRate: FPS, + codec, + quality: new Quality({ bitrate: VIDEO_BPS[codec] }), + forceTranscode: true + }; + if (await track.getFlip()) Object.assign(video, { + width, + height, + fit: "fill" + }); + else { + const cx = new OffscreenCanvas(width, height).getContext("2d"), rot = await track.getRotation(); + cx.imageSmoothingQuality = "high"; + Object.assign(video, { + process: (f) => (f.draw(cx, 0, 0, width, height), cx.canvas), + processedWidth: width, + processedHeight: height, + rotate: (360 - rot) % 360 + }); + } + const conv = await Conversion.init({ + input, + output, + tracks: "primary", + video, + audio: { + numberOfChannels: 1, + codec: "aac", + quality: new Quality({ bitrate: AUDIO_BPS }), + forceTranscode: true + } + }); + if (!conv.isValid) throw new Error(conv.discardedTracks.map((t) => t.reason).join(", ") || "unsupported video"); + if (signal) { + if (signal.aborted) return null; + signal.addEventListener("abort", () => conv.cancel(), { once: true }); + } + conv.onProgress = (p) => onProgress?.(p); + try { + await conv.execute(); + } catch (e) { + if (signal?.aborted) return null; + throw e; + } + return { + type: "video_url", + video_url: { url: await asDataURL(new Blob([output.target.buffer], { type: "video/mp4" })) } + }; +} +var blobUrls = /* @__PURE__ */ new Set(); +var toBlobUrl = async (u) => { + const o = URL.createObjectURL(await (await fetch(u)).blob()); + blobUrls.add(o); + return o; +}; +var revokeVideoUrls = () => { + blobUrls.forEach((u) => URL.revokeObjectURL(u)); + blobUrls.clear(); +}; +function openPlayer(src) { + const o = document.createElement("div"); + o.className = "fixed inset-0 z-[70] bg-black flex items-center justify-center"; + o.innerHTML = ""; + const v = o.querySelector("video"), done = () => { + v.pause(); + o.remove(); + removeEventListener("keydown", esc); + document.fullscreenElement && document.exitFullscreen().catch(() => {}); + }, esc = (e) => e.key === "Escape" && history.back(); + v.src = src; + history.pushState({ video: 1 }, ""); + addEventListener("popstate", done, { once: true }); + addEventListener("keydown", esc); + o.querySelector("button").onclick = () => history.back(); + o.addEventListener("fullscreenchange", () => !document.fullscreenElement && o.isConnected && history.back()); + document.body.append(o); + window.lucide?.createIcons(); + o.requestFullscreen?.().catch(() => {}); +} +function videoStrip(parts) { + const wrap = document.createElement("div"); + wrap.className = "flex gap-2 overflow-x-auto no-scrollbar px-4"; + for (const p of parts) { + const b = document.createElement("button"); + b.type = "button"; + b.setAttribute("aria-label", "Play video"); + b.className = "relative shrink-0 h-40 rounded-xl overflow-hidden bg-black"; + b.innerHTML = ""; + const src = toBlobUrl(p.video_url?.url || ""); + src.then((u) => b.querySelector("video").src = u + "#t=0.1").catch(() => {}); + b.onclick = async () => openPlayer(await src); + wrap.append(b); + } + return wrap; +} +//#endregion //#region src/attachments.js -async function toAttach(file) { +async function toAttach(file, opts) { if (!file) return null; if (file instanceof File) { const name = file.name || "file", mime = (file.type || "application/octet-stream").toLowerCase(); + if (isVideo(name, mime)) return compressVideo(file, opts); if (/^image\//.test(mime) || /\.(png|jpe?g|webp|gif)$/i.test(name)) return { type: "image_url", image_url: { url: mime === "image/webp" || /\.webp$/i.test(name) ? await asDataURL(file) : await imgToWebp(file, 2048, 94) } @@ -711,6 +914,10 @@ async function toAttach(file) { file_data: file.data } }; + if (/^video\//.test(mime)) return { + type: "video_url", + video_url: { url: `data:${mime};base64,${file.data}` } + }; if (/^audio\//.test(mime)) { const fmt = /mp3/.test(mime) ? "mp3" : "wav"; return { @@ -813,66 +1020,6 @@ var renderSuneHTML = async () => { } }; //#endregion -//#region \0vite/preload-helper.js -var scriptRel = "modulepreload"; -var assetsURL = function(dep) { - return "/" + dep; -}; -var seen = {}; -var __vitePreload = function preload(baseModule, deps, importerUrl) { - let promise = Promise.resolve(); - if (deps && deps.length > 0) { - const links = document.getElementsByTagName("link"); - const cspNonceMeta = document.querySelector("meta[property=csp-nonce]"); - const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce"); - function allSettled(promises) { - return Promise.all(promises.map((p) => Promise.resolve(p).then((value) => ({ - status: "fulfilled", - value - }), (reason) => ({ - status: "rejected", - reason - })))); - } - promise = allSettled(deps.map((dep) => { - dep = assetsURL(dep, importerUrl); - if (dep in seen) return; - seen[dep] = true; - const isCss = dep.endsWith(".css"); - const cssSelector = isCss ? "[rel=\"stylesheet\"]" : ""; - if (!!importerUrl) for (let i = links.length - 1; i >= 0; i--) { - const link = links[i]; - if (link.href === dep && (!isCss || link.rel === "stylesheet")) return; - } - else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) return; - const link = document.createElement("link"); - link.rel = isCss ? "stylesheet" : scriptRel; - if (!isCss) link.as = "script"; - link.crossOrigin = ""; - link.href = dep; - if (cspNonce) link.setAttribute("nonce", cspNonce); - document.head.appendChild(link); - if (isCss) return new Promise((res, rej) => { - link.addEventListener("load", res); - link.addEventListener("error", () => rej(/* @__PURE__ */ new Error(`Unable to preload CSS for ${dep}`))); - }); - })); - } - function handlePreloadError(err) { - const e = new Event("vite:preloadError", { cancelable: true }); - e.payload = err; - window.dispatchEvent(e); - if (!e.defaultPrevented) throw err; - } - return promise.then((res) => { - for (const item of res || []) { - if (item.status !== "rejected") continue; - handlePreloadError(item.reason); - } - return baseModule().catch(handlePreloadError); - }); -}; -//#endregion //#region src/main.js (() => { let k, v = visualViewport; @@ -1299,6 +1446,8 @@ function _createMessageRow(m) { $head.append($avatar, $name, $copyBtn, $deleteBtn); const $bubble = $(`
`); $row.append($head, $bubble); + const vids = (Array.isArray(meta.content) ? meta.content : []).filter((p) => p?.type === "video_url"); + if (vids.length) $row.append(videoStrip(vids)); return $row; } function msgRow(m) { @@ -1334,9 +1483,8 @@ var clearChat = () => { el.suneHtml.dispatchEvent(new CustomEvent("sune:unmount")); state.messages = []; el.messages.innerHTML = ""; - state.attachments = []; - updateAttachBadge(); - el.fileInput.value = ""; + clearAttachments(); + revokeVideoUrls(); }; var payloadWithSampling = (b) => { const o = Object.assign({}, b), s = SUNE, p = { @@ -1736,21 +1884,49 @@ $(el.sunesSyncDownloadBtn).on("click", () => { hideSunesSyncPopover(); performSuneDownload(false); }); +var attachGen = 0, attachJob = null, attachQueue = Promise.resolve(), attachWait = false; function updateAttachBadge() { const n = state.attachments.length; - el.attachBadge.textContent = String(n); - el.attachBadge.classList.toggle("hidden", n === 0); + el.attachBadge.textContent = attachJob ? Math.floor(attachJob.p * 100) + "%" : String(n); + el.attachBadge.classList.toggle("hidden", !n && !attachJob); } -var addAttachments = async (files) => { - for (const f of files) { - const at = await toAttach(f).catch(() => null); - if (at) state.attachments.push(at); - } +function clearAttachments() { + attachGen++; + attachJob?.ac.abort(); + attachJob = null; + state.attachments = []; updateAttachBadge(); + el.fileInput.value = ""; +} +var addAttachments = (files) => { + const g = attachGen; + attachQueue = attachQueue.then(async () => { + for (const f of files) { + if (g !== attachGen) return; + const job = attachJob = { + ac: new AbortController(), + p: 0 + }; + updateAttachBadge(); + const at = await toAttach(f, { + signal: job.ac.signal, + onProgress: (p) => { + job.p = p; + updateAttachBadge(); + } + }).catch((e) => { + if (g === attachGen) alert(`Couldn't attach ${f.name || "file"}: ${e?.message || e}`); + }); + if (g !== attachGen) return; + attachJob = null; + if (at) state.attachments.push(at); + updateAttachBadge(); + } + }).catch(() => {}); }; var hideAttachPopover = () => el.attachPopover.classList.add("hidden"); function showAttachPopover() { - if (!state.attachments.length || !el.attachPopover.classList.contains("hidden")) return; + if (!state.attachments.length && !attachJob || !el.attachPopover.classList.contains("hidden")) return; haptic(); el.attachPopover.classList.remove("hidden"); positionPopover(el.attachBtn, el.attachPopover); @@ -1774,8 +1950,7 @@ $(el.attachBtn).on("pointerdown", (e) => { }); $(el.attachPopover).on("click", (e) => { if (!e.target.closest("[data-action=\"clear\"]")) return; - state.attachments = []; - updateAttachBadge(); + clearAttachments(); hideAttachPopover(); }); $(document).on("click", (e) => { @@ -1784,20 +1959,23 @@ $(document).on("click", (e) => { $(window).on("resize", () => { if (!el.attachPopover.classList.contains("hidden")) positionPopover(el.attachBtn, el.attachPopover); }); -$(el.fileInput).on("change", async () => { +$(el.fileInput).on("change", () => { const files = [...el.fileInput.files || []]; el.fileInput.value = ""; - await addAttachments(files); + addAttachments(files); }); $(el.composer).on("paste", async (e) => { const files = [...(e.clipboardData || e.originalEvent?.clipboardData)?.files || []]; if (!files.length) return; e.preventDefault(); - await addAttachments(files); + addAttachments(files); }); $(el.composer).on("submit", async (e) => { e.preventDefault(); - if (state.busy) return; + if (state.busy || attachWait) return; + attachWait = true; + await attachQueue; + attachWait = false; const text = el.input.value.trim(); if (!text && !state.attachments.length) return SUNE.infer(); await ensureThreadOnFirstUser(text || "(attachments)"); @@ -2829,3 +3007,4 @@ Object.assign(window, { cleanSuneStorage }); //#endregion +export { __vitePreload as t }; diff --git a/dist/assets/mediabunny-aac-encoder-CtuZhjiD.js b/dist/assets/mediabunny-aac-encoder-CtuZhjiD.js new file mode 100644 index 0000000..8355e6b --- /dev/null +++ b/dist/assets/mediabunny-aac-encoder-CtuZhjiD.js @@ -0,0 +1,3493 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/__vite-browser-external-BU5UGKjj.js","assets/src-Bv2YeDx1.js"])))=>i.map(i=>d[i]); +import { t as __vitePreload } from "./index-BUD8Xh0Q.js"; +import { CustomAudioEncoder, EncodedPacket, Logging, n as __require$1, r as __toESM, registerEncoder } from "./src-Bv2YeDx1.js"; +//#region node_modules/@mediabunny/aac-encoder/dist/bundles/mediabunny-aac-encoder.mjs +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var __require = /* @__PURE__ */ ((x) => typeof __require$1 !== "undefined" ? __require$1 : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof __require$1 !== "undefined" ? __require$1 : a)[b] }) : x)(function(x) { + if (typeof __require$1 !== "undefined") return __require$1.apply(this, arguments); + throw Error("Dynamic require of \"" + x + "\" is not supported"); +}); +var Bitstream = class _Bitstream { + constructor(bytes) { + this.bytes = bytes; + /** Current offset in bits. */ + this.pos = 0; + } + seekToByte(byteOffset) { + this.pos = 8 * byteOffset; + } + readBit() { + const byteIndex = Math.floor(this.pos / 8); + const byte = this.bytes[byteIndex] ?? 0; + const bitIndex = 7 - (this.pos & 7); + const bit = (byte & 1 << bitIndex) >> bitIndex; + this.pos++; + return bit; + } + readBits(n) { + if (n === 1) return this.readBit(); + let result = 0; + for (let i = 0; i < n; i++) { + result <<= 1; + result |= this.readBit(); + } + return result; + } + writeBits(n, value) { + const end = this.pos + n; + for (let i = this.pos; i < end; i++) { + const byteIndex = Math.floor(i / 8); + let byte = this.bytes[byteIndex]; + const bitIndex = 7 - (i & 7); + byte &= ~(1 << bitIndex); + byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; + this.bytes[byteIndex] = byte; + } + this.pos = end; + } + copyBits(n, other) { + let i = 0; + for (; i < n - 7; i += 8) this.writeBits(8, other.readBits(8)); + const leftover = n - i; + if (leftover > 0) this.writeBits(leftover, other.readBits(leftover)); + } + readAlignedByte() { + if (this.pos % 8 !== 0) throw new Error("Bitstream is not byte-aligned."); + const byteIndex = this.pos / 8; + const byte = this.bytes[byteIndex] ?? 0; + this.pos += 8; + return byte; + } + skipBits(n) { + this.pos += n; + } + getBitsLeft() { + return this.bytes.length * 8 - this.pos; + } + clone() { + const clone = new _Bitstream(this.bytes); + clone.pos = this.pos; + return clone; + } +}; +var aacFrequencyTable = [ + 96e3, + 88200, + 64e3, + 48e3, + 44100, + 32e3, + 24e3, + 22050, + 16e3, + 12e3, + 11025, + 8e3, + 7350 +]; +var aacChannelMap = [ + -1, + 1, + 2, + 3, + 4, + 5, + 6, + 8 +]; +var parseAacAudioSpecificConfig = (bytes) => { + if (!bytes || bytes.byteLength < 2) throw new TypeError("AAC description must be at least 2 bytes long."); + const bitstream = new Bitstream(bytes); + const objectType = readAacObjectType(bitstream); + const { frequencyIndex, sampleRate } = readAacSamplingFrequency(bitstream); + const channelConfiguration = bitstream.readBits(4); + let numberOfChannels = null; + if (channelConfiguration >= 1 && channelConfiguration <= 7) numberOfChannels = aacChannelMap[channelConfiguration]; + let coreObjectType = objectType; + let psPresent = false; + let outputSampleRate = sampleRate; + if (objectType === 5 || objectType === 29) { + psPresent = objectType === 29; + outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate; + coreObjectType = readAacObjectType(bitstream); + if (coreObjectType === 22) bitstream.skipBits(4); + } else while (bitstream.getBitsLeft() > 15) { + const searchStart = bitstream.pos; + if (bitstream.readBits(11) !== 695) { + bitstream.pos = searchStart + 1; + continue; + } + if (readAacObjectType(bitstream) === 5 && bitstream.readBits(1)) { + outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate; + if (bitstream.getBitsLeft() > 11 && bitstream.readBits(11) === 1352) psPresent = !!bitstream.readBits(1); + } + break; + } + if (numberOfChannels !== null && numberOfChannels > 1) psPresent = false; + return { + objectType, + coreObjectType, + frequencyIndex, + channelConfiguration, + outputSampleRate, + outputNumberOfChannels: psPresent && numberOfChannels === 1 ? 2 : numberOfChannels + }; +}; +var readAacObjectType = (bitstream) => { + const objectType = bitstream.readBits(5); + return objectType === 31 ? 32 + bitstream.readBits(6) : objectType; +}; +var readAacSamplingFrequency = (bitstream) => { + const frequencyIndex = bitstream.readBits(4); + if (frequencyIndex === 15) return { + frequencyIndex, + sampleRate: bitstream.readBits(24) + }; + return { + frequencyIndex, + sampleRate: frequencyIndex < aacFrequencyTable.length ? aacFrequencyTable[frequencyIndex] : null + }; +}; +var buildAdtsHeaderTemplate = (config) => { + const header = new Uint8Array(7); + const bitstream = new Bitstream(header); + const { coreObjectType, frequencyIndex, channelConfiguration } = config; + const profile = coreObjectType - 1; + bitstream.writeBits(12, 4095); + bitstream.writeBits(1, 0); + bitstream.writeBits(2, 0); + bitstream.writeBits(1, 1); + bitstream.writeBits(2, profile); + bitstream.writeBits(4, frequencyIndex); + bitstream.writeBits(1, 0); + bitstream.writeBits(3, channelConfiguration); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.skipBits(13); + bitstream.writeBits(11, 2047); + bitstream.writeBits(2, 0); + return { + header, + bitstream + }; +}; +var writeAdtsFrameLength = (bitstream, frameLength) => { + bitstream.pos = 30; + bitstream.writeBits(13, frameLength); +}; +async function inlineWorker(scriptText) { + if (typeof Worker !== "undefined" && typeof Bun === "undefined") { + const blob = new Blob([scriptText], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + return new Worker(url, { type: typeof Deno !== "undefined" ? "module" : void 0 }); + } else { + let Worker3; + try { + Worker3 = (await __vitePreload(async () => { + const { Worker } = await import("./__vite-browser-external-BU5UGKjj.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)); + return { Worker }; + }, __vite__mapDeps([0,1]))).Worker; + } catch { + Worker3 = __require("worker_threads").Worker; + } + return new Worker3(scriptText, { eval: true }); + } +} +function Worker2() { + return inlineWorker(`var sr=Object.defineProperty;var dr=(k,a,r)=>a in k?sr(k,a,{enumerable:!0,configurable:!0,writable:!0,value:r}):k[a]=r;var CA=(k=>typeof require!="undefined"?require:typeof Proxy!="undefined"?new Proxy(k,{get:(a,r)=>(typeof require!="undefined"?require:a)[r]}):k)(function(k){if(typeof require!="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+k+'" is not supported')});var vA=(k,a,r)=>dr(k,typeof a!="symbol"?a+"":a,r);async function Er(k={}){var DA,_A,fA;var a,r=k,d=!!globalThis.window,s=!!globalThis.WorkerGlobalScope,E=((_A=(DA=globalThis.process)==null?void 0:DA.versions)==null?void 0:_A.node)&&((fA=globalThis.process)==null?void 0:fA.type)!="renderer",f=[],q="./this.program",u=(A,j)=>{throw j},F="",UA="",OA,VA;if(d||s){try{UA=new URL(".",F).href}catch(A){}s&&(VA=A=>{var j=new XMLHttpRequest;return j.open("GET",A,!1),j.responseType="arraybuffer",j.send(null),new Uint8Array(j.response)}),OA=async A=>{var j=await fetch(A,{credentials:"same-origin"});if(j.ok)return j.arrayBuffer();throw new Error(j.status+" : "+j.url)}}var K=console.log.bind(console),I=console.error.bind(console),R,M=!1,x;function SA(A){for(var j=0,t=A.length,n=new Uint8Array(t),e;j\\xF7\\xEC\\x9A<\\xAA\\xB9h1\\x87T\\xEF?\\x9D8\\x86\\xCB\\x82\\xE7\\x8F\\xBC\\xD9\\xFC"PM\\xEF?\\x8D\\xC3\\xA6DAo\\x8A<\\xD6\\x8Cb\\x88;F\\xEF?}\\xE4\\xB0z\\x80<\\x96\\xDC}\\x91I?\\xEF?\\x94\\xA8\\xA8\\xE3\\xFD\\x8E\\x96<8bunz8\\xEF?}Ht\\xF2^\\x87\\xA6\\xB2O\\xCE1\\xEF?\\xF2\\xE7\\x98+G\\x80<\\xDD|\\xE2eE+\\xEF?^\\bq?{\\xB8\\x96\\xBC\\x81c\\xF5\\xE1\\xDF$\\xEF?1\\xAB m\\xE1\\xF7\\x82<\\xE1\\xDE\\xF5\\x9D\\xEF?\\xFA\\xBFo\\x9B!=\\xBC\\x90\\xD9\\xDA\\xD0\\x7F\\xEF?\\xB4 +\\fr\\x827\\x8B<\\v\\xE4\\xA6\\x85\\xEF?\\x8F\\xCB\\xCE\\x89\\x92n\\xA9\\xAF\\f\\xEF?\\xB6\\xAB\\xB0MuM\\x83<\\xB71 +\\xFE\\xEF?Lt\\xAC\\xE2B\\x86<1\\xD8L\\xFCp\\xEF?J\\xF8\\xD3]9\\xDD\\x8F<\\xFFd\\xB2\\b\\xFC\\xEE?[\\x8E;\\x80\\xA3\\x86\\xBC\\xF1\\x9F\\x92_\\xC5\\xF6\\xEE?hPK\\xCC\\xEDJ\\x92\\xBC\\xCB\\xA9:7\\xA7\\xF1\\xEE?\\x8E-Q\\x1B\\xF8\\x07\\x99\\xBCf\\xD8m\\xAE\\xEC\\xEE?\\xD26\\x94>\\xE8\\xD1q\\xBC\\xF7\\x9F\\xE54\\xDB\\xE7\\xEE?\\x1B\\xCE\\xB3\\x99\\xBC\\xE5\\xA8\\xC3-\\xE3\\xEE?mL*\\xA7H\\x9F\\x85<"4L\\xA6\\xDE\\xEE?\\x8Ai(z\\\`\\x93\\xBC\\x80\\xACE\\xDA\\xEE?[\\x89H\\x8F\\xA7X\\xBC*.\\xF7! +\\xD6\\xEE?\\x1B\\x9AIg\\x9B,|\\xBC\\x97\\xA8P\\xD9\\xF5\\xD1\\xEE?\\xAC\\xC2\\\`\\xEDcC<-\\x89a\\\`\\b\\xCE\\xEE?\\xEFd; f\\x96 Z~dx\\xBCt_\\xEC\\xE8u\\x9F\\xEE?\\xB0}\\x8B\\xC0J\\xEE\\x86\\xBCt\\x81\\xA5H\\x9A\\x9F\\xEE?\\x8A\\xE6U2\\x86\\xBC\\xC9gBV\\xEB\\x9F\\xEE?\\xD3\\xD4 ^\\xCB\\x9C\\x90]\\xDEOi\\xA0\\xEE?\\xA5M\\xB9\\xDC2{\\xBC\\x87\\xEBs\\xA1\\xEE?k\\xC0gT\\xFD\\xEC\\x94<2\\xC10\\xED\\xA1\\xEE?Ul\\xD6\\xAB\\xE1\\xEBe T'\\xA4\\xEE?47;\\xF1\\xB6i\\x93\\xBC\\xCEL\\x99\\x89\\xA5\\xEE?\\xFF:\\x84^\\x80\\xBC\\xAD\\xC7#F\\xA7\\xEE?nWr\\xD8P\\xD4\\x94\\xBC\\xED\\x92D\\x9B\\xD9\\xA8\\xEE?\\0\\x8A[g\\xAD\\x90<\\x99f\\x8A\\xD9\\xC7\\xAA\\xEE?\\xB4\\xEA\\xF0\\xC1/\\xB7\\x8D<\\xDB\\xA0*B\\xE5\\xAC\\xEE?\\xFF\\xE7\\xC5\\x9C\\\`\\xB6e\\xBC\\x8CD\\xB52\\xAF\\xEE?D_\\xF3Y\\x83\\xF6{<6w\\x99\\xAE\\xB1\\xEE?\\x83=\\xA7 \\x93\\xBC\\xC6\\xFF\\x91\\v[\\xB4\\xEE?)l\\x8B\\xB8\\xA9]\\xBC\\xE5\\xC5\\xCD\\xB07\\xB7\\xEE?Y\\xB9\\x90|\\xF9#l\\xBCR\\xC8\\xCBD\\xBA\\xEE?\\xAA\\xF9\\xF4"CC\\x92\\xBCPN\\xDE\\x9F\\x82\\xBD\\xEE?K\\x8Ef\\xD7l\\xCA\\x85\\xBC\\xBA\\x07\\xCAp\\xF1\\xC0\\xEE?'\\xCE\\x91+\\xFC\\xAFq<\\x90\\xF0\\xA3\\x82\\x91\\xC4\\xEE?\\xBBs +\\xE15\\xD2m<##\\xE3c\\xC8\\xEE?c"b"\\xC5\\x87\\xBCe\\xE5]{f\\xCC\\xEE?\\xD51\\xE2\\xE3\\x86\\x8B<3-J\\xEC\\x9B\\xD0\\xEE?\\xBB\\xBC\\xD3\\xD1\\xBB\\x91\\xBC]%>\\xB2\\xD5\\xEE?\\xD21\\xEE\\x9C1\\xCC\\x90 \\xB4\\x07!\\xD5\\x82\\xBC_\\x9B{3\\x97|\\xEF?\\xC9\\rG;\\xB9*\\x89\\xBC)\\xA1\\xF5F\\x86\\xEF?\\xD3\\x88:\\\`\\xB6t<\\xF6?\\x8B\\xE7.\\x90\\xEF?qr\\x9DQ\\xEC\\xC5\\x83<\\x83L\\xC7\\xFBQ\\x9A\\xEF?\\xF0\\x91\\xD3\\x8F\\xF7\\x8F\\xBC\\xDA\\x90\\xA4\\xA2\\xAF\\xA4\\xEF?}t#\\xE2\\x98\\xAE\\x8D\\xBC\\xF1g\\x8E-H\\xAF\\xEF?\\b \\xAAA\\xBC\\xC3\\x8E<'Za\\xEE\\x1B\\xBA\\xEF?2\\xEB\\xA9\\xC3\\x94+\\x84<\\x97\\xBAk7+\\xC5\\xEF?\\xEE\\x85\\xD11\\xA9d\\x8A<@En[v\\xD0\\xEF?\\xED\\xE3;\\xE4\\xBA7\\x8E\\xBC\\xBE\\x9C\\xAD\\xFD\\xDB\\xEF?\\x9D\\xCD\\x91M;\\x89w<\\xD8\\x90\\x9E\\x81\\xC1\\xE7\\xEF?\\x89\\xCC\\\`A\\xC1S<\\xF1q\\x8F+\\xC2\\xF3\\xEF?\\0\\0\\xA0N\\0\\xEB\\xA7~ u\\x86\\xFA\\0\\xB9,\\xFD\\xB7\\x8Az\\xBC\\0\\xCC\\xA2\\0=I\\xD7\\0\\b\\0\\x93\\b\\x8F*_\\xB7\\xFAX\\xD9\\xFD\\xCA\\xBD\\xE1\\xCD\\xDC@x\\0}ga\\xEC\\0\\xE5 +\\xD4\\0\\xCC>Ov\\x98\\xAF\\0\\0D\\0\\xAE\\0\\xAE\\\`\\0\\xFAw!\\xEB+\\0\\\`A\\x92\\0\\xA9\\xA3nN\\0A\\xB8\\v\\f\\0\\0\\0\\0\\0\\0\\0\\0*\\0A\\xD8\\v'9H\\0A\\xEE\\v\\x92\\0A\\x82\\x1B\\v\\xC6\\xF18R\\\`S\\0\\0\\xCA\\0\\0\\0\\0\\0\\0\\0\\0\\xBB\\xDB\\xEB\\x07+\\x07;\\x07P\\x07\\xE2\\x88\\x9E\\0\\x80\\0Proxy\\0Navy\\0infinity\\0global_quality\\0sub-pel motion estimation quality\\0Ivory\\0dxtory\\0Dxtory\\0very\\0february\\0january\\0Commentary\\0%sreal_to_imaginary\\0any\\0july\\0%sinv_only\\0%sfwd_only\\0deprecated, for compatibility only\\0allow potentially unsafe hwaccel frame output that might require special care to process successfully\\0ADPCM Creative Technology\\0set error concealment strategy\\0LightGrey\\0nokey\\0low frequency\\0Emergency\\0rc_init_occupancy\\0pcm_bluray\\0gray\\0DimGray\\0DarkGray\\0LightSlateGray\\0DarkSlateGray\\0may\\0DPCM Interplay\\0ADPCM IMA Acorn Replay\\0low_delay\\0afq->remaining_samples == afq->remaining_delay\\0force low delay\\0vuyx\\0aptx\\0orig_dst_frames == ((void*)0) || orig_dst_frames == dst->hw_frames_ctx\\0adpcm_psx\\0hqx\\0dpx\\0adpcm_ima_xbox\\0ADPCM IMA Xbox\\0simplemmx\\0xvidmmx\\0simbiosis_imx\\0displaymatrix\\0alias_pix\\0brender_pix\\0vmix\\0downmix\\0adpcm_ima_magix\\0ADPCM IMA Magix\\0ahx\\0pgx\\0Complex\\0adpcm_ima_moflex\\0speex\\0Speex\\0adpcm_adx\\0image/x-pcx\\0dctmax\\0qmax\\0mblmax\\0sp5x\\0-+ 0X0x\\0v210x\\0-0X+0X 0X-0x+0x 0x\\0pow\\0snow\\0monow\\0Snow\\0rate distortion optimal, slow\\0LightYellow\\0GreenYellow\\0LightGoldenRodYellow\\0mpeg2_aac_low\\0nssew\\0HoneyDew\\0qdraw\\0prores_raw\\0Apple QuickDraw\\0pcm_mulaw\\0pcm_alaw\\0PCM mu-law / G.711 mu-law\\0PCM A-law / G.711 A-law\\0dxv\\0pgmyuv\\0huffyuv\\0magicyuv\\0ayuv\\0nuv\\0tv\\0qsv\\0flashsv\\0apv\\0tmv\\0smv\\0wcmv\\0adpcm_ima_amv\\0klv\\0jv\\0tgv\\x004gv\\0mvdv\\0pdv\\0gdv\\0zmbv\\0adpcm_ima_wav\\x00012v\\0frwu\\0Peru\\0ipu\\0mp3adu\\0vdpau\\0AVCodecContext\\0AVBSFContext\\0bintext\\0realtext\\0dvb_teletext\\0DVB teletext\\0mov_text\\0Binary text\\0eXtended BINary text\\0iCEDraw text\\0MOV text\\0raw UTF-8 text\\0Rext\\0unsafe_output\\0allow decoders to produce unaligned output\\0ch_layout\\0noout\\0webvtt\\0august\\0DSD (Direct Stream Digital), most significant bit first\\0DSD (Direct Stream Digital), least significant bit first\\0codec_whitelist\\0bsf_list\\0dst\\0sunrast\\0sum of absolute differences, fast\\0srt\\0log_sqrt\\0ASCII/ANSI art\\0truemotion2rt\\0adpcm_ima_qt\\0output_corrupt\\0hypot\\0Log square root\\0ifnot\\0!afq->frame_count\\0rc_override_count\\0application/x-font\\0application/x-truetype-font\\0OpenType font\\0TrueType font\\0fastint\\0print\\0Pictor/PC Paint\\0allow to output YUV pixel formats with a different chroma sampling than 4:2:0 and/or other than 8 bits per component\\0GSM Microsoft variant\\0Huffyuv FFmpeg variant\\0compliant\\0hymt\\0request_sample_fmt\\0default\\0celt\\0side_data_pkt\\0bt2020_12bit\\0bt2020_10bit\\0PCM unsigned 8-bit\\0PCM signed 8-bit\\0SGI RLE 8-bit\\0ADPCM Sound Blaster Pro 2.6-bit\\0PCM D-Cinema audio signed 24-bit\\0ADPCM Sound Blaster Pro 4-bit\\0Uncompressed YUV 4:1:1 12-bit\\0ADPCM Sound Blaster Pro 2-bit\\0Uncompressed RGB 10-bit\\0Uncompressed 4:4:4 10-bit\\0Uncompressed 4:2:2 10-bit\\0BT.2020 - 12 bit\\0BT.2020 - 10 bit\\0downmix right\\0top front right\\0bottom front right\\0surround direct right\\0binaural right\\0top back right\\0wide right\\0top side right\\0top surround right\\0side surround right\\0nsse weight\\0straight\\0Straight\\0gt\\0prft\\0ADPCM Microsoft\\0topleft\\0bottomleft\\0Top-left\\0Bottom-left\\0downmix left\\0top front left\\0bottom front left\\0surround direct left\\0binaural left\\0top back left\\0wide left\\0top side left\\0top surround left\\0side surround left\\0Left\\0binkaudio_rdft\\0i_qoffset\\0b_qoffset\\0log_level_offset\\0set the log level offset\\0pixlet\\0Apple Pixlet\\0DarkViolet\\0BlueViolet\\0dvd_nav_packet\\0side_data_prefer_packet\\0DVD Nav packet\\0strict\\0pict\\0autodetect\\0err_detect\\0aspect\\0ildct\\0skip_idct\\0binkaudio_dct\\0bitexact\\0DPCM Squareroot-Delta-Exact\\0DPCM Cuberoot-Delta-Exact\\0adpcm_ct\\0bt\\0fft_float\\0rdft_float\\0mdct_float\\0dstI_float\\0dctI_float\\0pixel_format\\0CPiA video format\\0set pixel format\\0Spruce subtitle format\\0sample format\\x004kflat\\x002kflat\\0Wheat\\0image/jxs\\0jpegxs\\0adpcm_ima_ws\\0export_mvs\\0guess_mvs\\0cavs\\0opus\\0adpcm_circus\\0ADPCM Circus\\0number of bits which should be loaded into the rc buffer before decoding starts\\0ADPCM Electronic Arts\\0ADPCM IMA Cunning Developments\\0enhancements\\0fits\\0use fewest bits\\0mpegts\\0dts\\0Effects\\0gauss\\0adpcm_ima_iss\\0DTS Express\\0wmalossless\\0Lagarith lossless\\0Sonic lossless\\0RealAudio Lossless\\0Windows Media Audio Lossless\\0diamond type & size for motion estimation pre-pass\\0*(const AVClass **)avctx->priv_data == codec->priv_class\\0%s, factors\\0consider all spec non compliancies as errors\\0consider things that violate the spec, are fast to check and have not been seen in the wild as errors\\0ignore errors\\0sum of squared errors\\0buffers\\0work around various bugs in Microsoft's broken decoders\\0ignore cropping information from sps\\0thread_ops\\x008bps\\0fraps\\0Fraps\\0Dolby Digital Plus + Dolby Atmos\\0Dolby TrueHD + Dolby Atmos\\0acos\\0aac_tns\\0aac_pns\\0cdtoons\\0decode at 1= 1/2, 2=1/4, 3=1/8 resolutions\\0EIA-608 closed captions\\0ivtv VBI captions\\0JPEG 2000 no codestream restrictions\\0use frame durations\\0threading operations\\0detect bitstream specification deviations\\0picture buffer allocations\\0allow unofficial extensions\\0venc_params\\0gsm_ms\\0adpcm_ms\\0aac_ms\\0jpegls\\0motionpixels\\0max_pixels\\0Maximum number of pixels\\0%d channels\\0sonicls\\0mp4als\\0Frame data might be split into multiple chunks\\0refine the two motion vectors used in bidirectional macroblocks\\0allow non-spec-compliant speedup tricks\\0pjs\\0trellis\\0vorbis\\0Vorbis\\0aac_is\\0work around not autodetected encoder bugs\\0allow non-standardized experimental things\\0generate/parse embedded ICC profiles from/to colorimetry tags\\0hwaccel_flags\\0slice_flags\\0set error detection flags\\0refs\\0propagate opaque values\\0lowres\\0prores\\0compresses bright areas stronger than medium ones\\0compresses dark areas stronger than medium ones\\0discard all frames except keyframes\\0extra_hw_frames\\0has_b_frames\\0minimum interval between IDR-frames\\0QP offset between P- and I-frames\\0QP factor between P- and I-frames\\0set maximum number of B-frames between non-B-frames\\0QP offset between P- and B-frames\\0QP factor between P- and B-frames\\0discard useless frames\\0discard all frames\\0discard all bidirectional frames\\0discard all non-reference frames\\0Output even potentially corrupted frames\\0export reconstructed frames\\0skip loop filtering process for the selected frames\\0skip IDCT/dequantization for the selected frames\\0skip decoding for the selected frames\\0truncated frames\\0discard all frames except I frames\\0ADPCM Argonaut Games\\0HDMV Presentation Graphic Stream subtitles\\0DVD subtitles\\0DVB subtitles\\0max_samples\\0Maximum number of samples\\0icc_profiles\\0color_primaries\\0color primaries\\0strictly conform to all the things in the spec no matter what the consequences\\0sum of absolute vertical differences\\0sum of squared vertical differences\\0noise preserving sum of squared differences\\0sum of absolute Hadamard transformed differences\\0sum of absolute DCT transformed differences\\0slices\\0how strictly to follow the standards\\0dds\\0set the number of threads\\0color transfer characteristics\\0cdgraphics\\0Scalable Vector Graphics\\0adpcm_ima_ea_eacs\\0abs\\0adpcm_ea_xas\\0Forces the use of PCEs\\0verify embedded CRCs\\0use strong deblock filter for damaged MBs\\0, found %i matches%s\\0%s%s%s%s\\0@%s\\0,%s\\0exr\\0qblur\\0ftr\\0aac_ssr\\0ignore_err\\0sipr\\0scpr\\0bitor\\0pictor\\0i_qfactor\\0b_qfactor\\0dump_separator\\0set information dump field separator\\0Gryphon's Anim Compressor\\0ScreenPressor\\0Unknown error\\0consider things that a sane encoder should not do as an error\\0floor\\0taylor\\x00256color\\0psnr\\0cljr\\0bidir\\0abgr\\x000bgr\\0vplayer\\0subviewer\\0Silver\\0ignore level even if the codec level used is unknown or higher than the maximum supported level reported by the hardware driver\\0Waveform Archiver\\0Voice Over\\0favor_inter\\0front right-of-center\\0front left-of-center\\0top front center\\0bottom front center\\0top center\\0top back center\\0Center\\0skip_loop_filter\\0use loop filter\\0Number of extra hardware frames to allocate for the user\\0nellymoser\\0prosumer\\0Avid 1:1 10-bit RGB Packer\\0fast integer\\0accurate integer\\0buffer\\0sample format audio decoders should prefer\\0field_order\\0Field order\\0AAC encoder\\0Encoder\\0pre_decoder\\0DirectDraw Surface image decoder\\0Decoder\\0aac_coder\\0Lavender\\0local_header\\0global_header\\0october\\0frame_number\\0november\\0september\\0december\\0hdr\\0lscr\\0sar\\0dsd_msbf_planar\\0dsd_lsbf_planar\\0pcm_s16le_planar\\0pcm_s24le_planar\\0pcm_s32le_planar\\0pcm_s16be_planar\\0pcm_s8_planar\\0PCM signed 8-bit planar\\0PCM signed 16-bit big-endian planar\\0PCM signed 16-bit little-endian planar\\0PCM signed 24-bit little-endian planar\\0PCM signed 32-bit little-endian planar\\0PCM signed 20-bit little-endian planar\\0DSD (Direct Stream Digital), most significant bit first, planar\\0DSD (Direct Stream Digital), least significant bit first, planar\\0linear\\0Linear\\0twinvq\\0osq\\0roq\\0speedhq\\0tgq\\0eq\\0subq\\x008svx_exp\\0aac_ltp\\0dss_sp\\0avrp\\0lerp\\0gbrp\\0qp\\0yop\\0skip_top\\0ignorecrop\\0ass_ro_flush_noop\\0twoloop\\0cgop\\0Top\\0image/x-xwindowdump\\0qcomp\\0ildctcmp\\0precmp\\0subcmp\\0use mbcmp\\0wbmp\\0image/x-ms-bmp\\0mlp\\0qcelp\\0adpcm_ima_alp\\0subrip\\0mobiclip\\0dc_clip\\0skip\\0PapayaWhip\\0adpcm_thp\\0ictcp\\0image/webp\\0gbrap\\0image/x-xpixmap\\0image/x-portable-pixmap\\0image/x-xbitmap\\0hap\\0Vidvox Hap\\0ICtCp\\0yuv444p\\0yuvj444p\\0yuva444p\\0atrac3p\\0h263p\\0yuv422p\\0yuvj422p\\0yuva422p\\0y41p\\0yuv411p\\0yuvj411p\\0yuv440p\\0yuvj440p\\0yuv420p\\0yuvj420p\\0yuva420p\\0yuv410p\\0default_get_buffer called on frame %p\\0adpcm_sanyo\\0ADPCM Sanyo\\0vo\\0simpleauto\\0Tomato\\0screenpresso\\0Screenpresso\\0wmapro\\0Gainsboro\\0zero\\0ycgco-ro\\0mono\\0sample aspect ratio\\0CamStudio\\0dvaudio\\0fastaudio\\0dsicinaudio\\0smackaudio\\0vmdaudio\\0bmv_audio\\0paf_audio\\0mpegh_3d_audio\\0Smacker audio\\0Bonk audio\\0Marian's A-pac audio\\0Discworld II BMV audio\\0DV audio\\0Delphine Software International CIN audio\\0Sierra VMD audio\\0LucasArts VIMA audio\\0MobiClip FastAudio\\0Monkey's Audio\\0Amazing Studio Packed Animation File Audio\\0HCOM Audio\\0MPEG-H 3D Audio\\0Micronas SC-4 Audio\\0adpcm_argo\\0Indigo\\0print specific debug info\\0picture info\\0stereo\\0interplayvideo\\0rawvideo\\0dvvideo\\0utvideo\\0sheervideo\\0clearvideo\\0tiertexseqvideo\\0dsicinvideo\\0mmvideo\\0binkvideo\\0smackvideo\\0vmdvideo\\0bmv_video\\0paf_video\\0hnm4video\\0mpeg2video\\0mpeg1video\\0raw video\\0CDToons video\\0Motion Pixels video\\0CD Graphics video\\0Smacker video\\0Bink video\\0Autodesk Animator Flic video\\0MagicYUV video\\0Electronic Arts CMV video\\0Discworld II BMV video\\0Bitmap Brothers JV video\\0Electronic Arts TGV video\\0QuickTime 8BPS video\\0id RoQ video\\0Electronic Arts TGQ video\\0Tiertex Limited SEQ video\\0Nintendo Gamecube THP video\\0Delphine Software International CIN video\\0id Quake II CIN video\\0Commodore CDXL video\\0Electronic Arts TQI video\\0LucasArts SANM/SMUSH video\\0Mobotix MxPEG video\\0Interplay MVE video\\0Sierra VMD video\\0Bethesda VID video\\0HNM 4 video\\0RL2 video\\0MPEG-2 video\\0MPEG-1 video\\0Westwood Studios VQA (Vector Quantized Animation) video\\0AVS (Audio Video Standard) video\\0QuickTime Animation (RLE) video\\0BitJazz SheerVideo\\0Iterated Systems ClearVideo\\0vMix Video\\0Electronic Arts Madcow Video\\0Ut Video\\0ZeroCodec Lossless Video\\0Zip Motion Blocks Video\\0Argonaut Games Video\\0Brooktree ProSumer Video\\0MobiClip Video\\0Sigmatel Motion Video\\0WinCAM Motion Video\\0Gremlin Digital Video\\0Advanced Professional Video\\0VMware Screen Codec / VMware Video\\0Kega Game Video\\0Amazing Studio Packed Animation File Video\\0Simbiosis Interactive IMX Video\\0AMV Video\\0IPU Video\\0Psygnosis YOP Video\\0American Laser Games MM Video\\0Digital Pictures SGA Video\\0loco\\0mmco\\0ycgco\\0Nellymoser Asao\\0RosyBrown\\0SandyBrown\\0SaddleBrown\\0unknown\\0avrn\\0adpcm_ima_acorn\\0Crimson\\0Maroon\\0LightSalmon\\0DarkSalmon\\0Perceptual noise substitution\\0use best rate distortion\\0arib_caption\\0ARIB STD-B24 caption\\0IBM UltiMotion\\0error recognition\\0pre motion estimation compare function\\0macroblock compare function\\0interlaced DCT compare function\\0full-pel ME compare function\\0sub-pel ME compare function\\0H.263 advanced intra coding / MPEG-4 AC prediction\\0abort decoding on minor error detection\\0rate-distortion optimal quantization\\0ADPCM Playstation\\0select IDCT implementation\\0reference frames to consider for motion compensation\\0skip motion compensation\\0use 1/4-pel motion compensation\\0frame_duration\\0diamond type & size for motion estimation\\0interlaced motion estimation\\0Deluxe Paint Animation\\0chroma_sample_location\\0chroma sample location\\0intra_dc_precision\\0LemonChiffon\\0simpleneon\\0true,y,yes,enable,enabled,on\\0acelp.kelvin\\0Moccasin\\0qmin\\0mblmin\\0keyint_min\\0DPCM Gremlin\\0idcin\\0xbin\\0film_grain\\0aac_main\\0replaygain\\0Multiview Main\\0sgn\\0block_align\\0shorten\\0Shorten\\0msnsiren\\0MSN Siren\\0Linen\\0between\\0MS Expression Encoder Screen\\0MS ATC Screen\\0MS Windows Media Video V9 Screen\\0YellowGreen\\0ForestGreen\\0LightGreen\\0LawnGreen\\0DarkGreen\\0MediumSpringGreen\\0DarkOliveGreen\\0LimeGreen\\0PaleGreen\\0LightSeaGreen\\0MediumSeaGreen\\0DarkSeaGreen\\0vbn\\0LightCyan\\0DarkCyan\\0Wing Commander IV / Xxan\\0atan\\0isnan\\0vulkan\\0PCM 64-bit floating point big-endian\\0PCM 32-bit floating point big-endian\\0PCM unsigned 16-bit big-endian\\0PCM signed 16-bit big-endian\\0PCM signed 64-bit big-endian\\0PCM signed 20|24-bit big-endian\\0PCM unsigned 24-bit big-endian\\0PCM signed 24-bit big-endian\\0PCM unsigned 32-bit big-endian\\0PCM signed 32-bit big-endian\\0PCM 64-bit floating point little-endian\\0PCM 32-bit floating point little-endian\\0PCM 16.8 floating point little-endian\\0PCM 24.0 floating point little-endian\\0PCM unsigned 16-bit little-endian\\0PCM signed 16-bit little-endian\\0PCM signed 64-bit little-endian\\0PCM unsigned 24-bit little-endian\\0PCM signed 24-bit little-endian\\0PCM unsigned 32-bit little-endian\\0PCM signed 32-bit little-endian\\0G.726 ADPCM little-endian\\0faan\\0DPCM Xan\\0Wing Commander III / Xan\\0Tan\\0adpcm_4xm\\0dfpwm\\0Plum\\0aac_latm\\0gsm\\0simplearm\\0xpm\\0ppm\\0adpcm_ima_apm\\0skip_bottom\\0Bottom\\0/dev/urandom\\0hcom\\0sanm\\0ntsc-film\\0Film\\0jpegxl_anim\\0Coding algorithm\\0DCT algorithm\\0phm\\0pgm\\0adpcm_agm\\0pfm\\0gem\\0wady_dpcm\\0interplay_dpcm\\0roq_dpcm\\0gremlin_dpcm\\0xan_dpcm\\0sol_dpcm\\0derf_dpcm\\0sdx2_dpcm\\0cbd2_dpcm\\0interplayacm\\0xbm\\0pbm\\0iff_ilbm\\0pam\\0bitstream\\0Comma-separated list of side data types for which user-supplied (container) data is preferred over coded bytestream\\0application/octet-stream\\0attempt to decode anyway if HW accelerated decoder's supported profiles do not exactly match the stream\\0raw MPEG-TS stream\\0MintCream\\0g2m\\0s302m\\0\\x1B[48;5;%um\\x1B[38;5;%um%s\\x1B[0m\\0\\x1B[%u;3%um%s\\x1B[0m\\0\\x1B[48;5;%um\\x1B[38;5;%dm%s\\x1B[0m\\0bt470m\\0smpte170m\\0smpte240m\\0image/jxl\\0vixl\\0jpegxl\\0cdxl\\0careful\\0stl\\0rate control\\0DPCM Sol\\0image/svg+xml\\0ttml\\0vnull\\0anull\\0%simdct_full\\0SeaShell\\0showall\\0%sasm_call\\0april\\0ceil\\0content_light_level\\0compression_level\\0ignore_level\\0avctx.level\\0std_qpel\\x003GPP TS 26.403-inspired model\\0opencl\\0bt2020_ncl\\0bt2020_cl\\0Eval\\0skip_manual\\0experimental\\0binaural\\0LightCoral\\0spal\\0qpal\\0atrac3pal\\0Windows Media Audio 9 Professional\\0hexagonal\\0octagonal\\0hexadecagonal\\0normal\\0mmal\\0Sequential\\x008SVX exponential\\0unofficial\\0%sreal_to_real\\0Teal\\0spherical\\0ADPCM IMA Radical\\0atrac3al\\0adpcm_dtk\\0tcplx_mask\\0scplx_mask\\0p_mask\\0dark_mask\\0lumi_mask\\0adpcm_zork\\0ADPCM IMA Capcom's MT Framework\\0ADPCM Zork\\0cook\\0bonk\\0HotPink\\0LightPink\\0DeepPink\\0Cornsilk\\0deblock\\0number of bits needed for the block\\0FireBrick\\0crccheck\\0wavpack\\0Black\\0WavPack\\0tak\\0cinepak\\0Cinepak\\0Cirrus Logic AccuPak\\x004k\\x002k\\0r10k\\0vi\\0avui\\0a64_multi\\0adpcm_ima_ssi\\0ansi\\0cri\\0tqi\\0vaapi\\0qoi\\0faani\\0randomi\\0sami\\0adpcm_ima_oki\\0DarkKhaki\\0hi\\0sgi\\0bfi\\0di\\x004kdci\\x002kdci\\x008SVX fibonacci\\0ivtv_vbi\\0h263i\\0Media 100i\\0, prio: %i\\0mszh\\0allow_high_depth\\0wavesynth\\0lagarith\\0detect improper bitstream length\\0((dst_linesize) >= 0 ? (dst_linesize) : (-(dst_linesize))) >= bytewidth\\0((src_linesize) >= 0 ? (src_linesize) : (-(src_linesize))) >= bytewidth\\0set cutoff bandwidth\\0do not reset ASS ReadOrder field on flush\\0LavenderBlush\\0cosh\\0squish\\0ADPCM Shockwave Flash\\0sinh\\0tanh\\0AVFrame to AVPacket passthrough\\0Multiview High\\0Stereo High\\0allow_profile_mismatch\\0march\\0Fast search\\0truespeech\\0DSP Group TrueSpeech\\0libavcodec/put_bits.h\\0svg\\0debug\\0epg\\0log\\0Log\\0apng\\0image/png\\0Go2Meeting\\0apply_cropping\\0Temporal noise shaping\\0adpcm_ima_cunning\\0SubRip subtitle with embedded timing\\0temporal complexity masking\\0spatial complexity masking\\0inter masking\\0do_nothing\\0set input text subtitles character encoding\\0skip bitstream encoding\\0error[?] variables will be set during encoding\\0set the number of slices, used in parallelized encoding\\0Intensity stereo coding\\0Force M/S stereo coding\\0no_padding\\0mxpeg\\0qpeg\\0mpeg\\0smvjpeg\\0adpcm_ima_smjpeg\\0ljpeg\\0image/jpeg\\0ycocg\\0bt470bg\\0codec_tag\\0pcm_lxf\\0adpcm_swf\\0%spreshuf\\0!avpkt->data || avpkt->buf\\0ttf\\0otf\\0adpcm_ima_mtf\\0isinf\\0amf\\0ralf\\0exif\\0image/gif\\0sqcif\\x0016cif\\x004cif\\0ffvhuff\\0PeachPuff\\0cutoff\\0false,n,no,disable,disabled,off\\0image/tiff\\0qdiff\\0dct_coeff\\0noref\\x000123456789ABCDEFabcdef\\0idf\\0dsd_msbf\\0dsd_lsbf\\0adpcm_mtaf\\0vp6f\\0direct_blocksize\\0bufsize\\0video_size\\0val || !min_size\\0frame_size\\0pre_dia_size\\0set video size\\0set the group of picture (GOP) size\\0High 4:4:4 Predictive\\0ADPCM IMA Simon & Schuster Interactive\\0progressive\\0Progressive\\0aggressive\\0Olive\\0Bisque\\0copy_opaque\\0LightSkyBlue\\0DeepSkyBlue\\0MidnightBlue\\0LightBlue\\0CadetBlue\\0CornflowerBlue\\0DodgerBlue\\0PowderBlue\\0MediumBlue\\0LightSteelBlue\\0RoyalBlue\\0DarkBlue\\0MediumSlateBlue\\0DarkSlateBlue\\0AliceBlue\\0Dialogue\\0SCTE 35 Message Queue\\0bgr4_byte\\0rgb4_byte\\0lte\\0GhostWhite\\0NavajoWhite\\0FloralWhite\\0AntiqueWhite\\0gte\\0maxrate\\0minrate\\0Chocolate\\0ADPCM IMA PlayDate\\0simplearmv5te\\0Chartreuse\\0rc_max_vbv_use\\0rc_min_vbv_use\\0autodetect a suitable number of threads to use\\0vsse\\0nsse\\0inverse\\0MistyRose\\0MediumTurquoise\\0DarkTurquoise\\0PaleTurquoise\\0comfortnoise\\0RFC 3389 Comfort Noise\\0pkt_timebase\\0time_base\\0Azure\\0RemotelyAnywhere Screen Capture\\0LEAD Screen Capture\\0Main Still Picture\\0ignore\\0strictly conform to a older more strict version of the spec or reference software\\0ycgco-re\\0application/vnd.ms-opentype\\0color_primaries_type\\0chroma_sample_location_type\\0color_range_type\\0alpha_mode_type\\0audio_service_type\\0colorspace_type\\0thread_type\\0color_trc_type\\0mb_type\\0select multithreading type\\0audio service type\\0macroblock (MB) type\\x004kscope\\x002kscope\\0adpcm_ima_escape\\0ADPCM IMA Acorn Escape\\0june\\0none\\0autoselect a good one\\0Aquamarine\\0MediumAquaMarine\\0Constrained Baseline\\0bidir_refine\\0ilme\\0time\\0drm_prime\\0ADPCM IMA QuickTime\\0Duck TrueMotion 2.0 Real Time\\0Lime\\0place global headers in extradata instead of every keyframe\\0Show all frames before the first keyframe\\0wrapped_avframe\\0skip_frame\\0recon_frame\\0amount of motion predictors from the previous frame\\0favor predicting from the previous frame\\0discard no frame\\0Percentage of damaged samples to discard a frame\\0v30xle\\0Thistle\\0hdmv_text_subtitle\\0hdmv_pgs_subtitle\\0dvd_subtitle\\0dvb_subtitle\\0RealText subtitle\\0HDMV Text subtitle\\0VPlayer subtitle\\0SubViewer subtitle\\0SubRip subtitle\\0JACOsub subtitle\\0WebVTT subtitle\\0SAMI subtitle\\0MicroDVD subtitle\\0MPL2 subtitle\\0SubViewer v1 subtitle\\0PJS (Phoenix Japanimation Society) subtitle\\0SSA (SubStation Alpha) subtitle\\0ASS (Advanced SSA) subtitle\\0qtrle\\0msrle\\0sgirle\\0MediumPurple\\0simple\\0Simple\\0bits_per_raw_sample\\0bits_per_coded_sample\\0while\\0icc_profile\\0avctx.profile\\0N-bit Profile\\0Simple Studio Profile\\0Simple Face Animation Profile\\0Main Profile\\0Advanced Coding Profile\\0Advanced Scalable Texture Profile\\0Basic Animated Texture Profile\\0Advanced Core Profile\\0Advanced Real Time Simple Profile\\0Advanced Simple Profile\\0Simple Scalable Profile\\0Code Scalable Profile\\0Hybrid Profile\\0Photoshop PSD file\\0vble\\0fft_double\\0rdft_double\\0mdct_double\\0dstI_double\\0dctI_double\\0gbrp12msble\\0yuv444p12msble\\0gbrp10msble\\0yuv444p10msble\\0Spatially Scalable\\0SNR Scalable\\0only decode/encode grayscale\\0use fixed qscale\\0adpcm_thp_le\\0gray9le\\0gbrp9le\\0yuv444p9le\\0yuva444p9le\\0yuv422p9le\\0yuva422p9le\\0yuv420p9le\\0yuva420p9le\\0xv48le\\0bgr48le\\0rgb48le\\0rgba128le\\0rgb96le\\0xv36le\\0adpcm_g726le\\0gray16le\\0pcm_u16le\\0pcm_s16le\\0bayer_bggr16le\\0gbrp16le\\0gbrap16le\\0yuv444p16le\\0yuva444p16le\\0yuv422p16le\\0yuva422p16le\\0yuv420p16le\\0yuva420p16le\\0bayer_gbrg16le\\0bayer_grbg16le\\0grayf16le\\0gbrpf16le\\0gbrapf16le\\0rgbf16le\\0yaf16le\\0rgbaf16le\\0pcm_f16le\\0bayer_rggb16le\\0ya16le\\0p416le\\0y216le\\0p216le\\0p016le\\0bgr565le\\0rgb565le\\0bgr555le\\0rgb555le\\0ayuv64le\\0pcm_s64le\\0pcm_f64le\\0bgra64le\\0rgba64le\\0bgr444le\\0rgb444le\\0pcm_u24le\\0pcm_s24le\\0pcm_f24le\\0gray14le\\0gbrp14le\\0gbrap14le\\0yuv444p14le\\0yuv422p14le\\0yuv420p14le\\0gray32le\\0pcm_u32le\\0pcm_s32le\\0gbrap32le\\0grayf32le\\0gbrpf32le\\0gbrapf32le\\0rgbf32le\\0yaf32le\\0rgbaf32le\\0pcm_f32le\\0xyz12le\\0gray12le\\0gbrp12le\\0gbrap12le\\0yuv444p12le\\0yuva444p12le\\0yuv422p12le\\0yuva422p12le\\0yuv440p12le\\0yuv420p12le\\0p412le\\0y212le\\0p212le\\0p012le\\0xv30le\\0nv20le\\0gray10le\\0x2bgr10le\\0gbrp10le\\0gbrap10le\\0yuv444p10le\\0yuva444p10le\\0yuv422p10le\\0yuva422p10le\\0yuv440p10le\\0yuv420p10le\\0yuva420p10le\\0x2rgb10le\\0p410le\\0y210le\\0p210le\\0p010le\\0WhiteSmoke\\0Karaoke\\0Amuse Graphics Movie\\0ADPCM 4X Movie\\0aac_xhe\\0mpeg2_aac_he\\0Darkorange\\0color_range\\0me_range\\0Orange\\0color range\\0Beige\\0iedge\\0Timed Text Markup Language\\0discard_damaged_percentage\\0wmv3image\\0vc1image\\0GEM Raster image\\0Sun Rasterfile image\\0X-face image\\0Truevision Targa image\\0V.Flash PTX image\\0Alias/Wavefront PIX image\\0BRender PIX image\\0PC Paintbrush PCX image\\0OpenEXR image\\0SGI image\\0TIFF image\\0Renderware TXD (TeXture Dictionary) image\\0HDR (Radiance RGBE format) image\\0PNG (Portable Network Graphics) image\\0APNG (Animated Portable Network Graphics) image\\0XWD (X Window Dump) image\\0WBMP (Wireless Application Protocol Bitmap) image\\0PAM (Portable AnyMap) image\\0PGM (Portable GrayMap) image\\0XPM (X PixMap) image\\0PBM (Portable BitMap) image\\0XBM (X BitMap) image\\0PHM (Portable HalfFloatMap) image\\0PFM (Portable FloatMap) image\\0PPM (Portable PixelMap) image\\0DPX (Digital Picture Exchange) image\\0PGMYUV (Portable GrayMap YUV) image\\0Vizrt Binary Image\\0Windows Media Video 9 Image\\0sub_charenc_mode\\0alpha_mode\\0use internal 2-pass ratecontrol in second pass mode\\0use internal 2-pass ratecontrol in first pass mode\\0set input text subtitles character encoding mode\\0alpha mode\\0explode\\0startcode\\0Electronic Program Guide\\0aac_pce\\0Brute Force & Ignorance\\0Main Audio Service\\0wmavoice\\0QCELP / PureVoice\\0Windows Media Audio Voice\\0FTR Voice\\0slice\\0colorspace\\0color space\\0%sinplace\\0%sout_of_place\\0xvid_ilace\\0xface\\0OldLace\\0v30xbe\\0cube\\0gbrp12msbbe\\0yuv444p12msbbe\\0gbrp10msbbe\\0yuv444p10msbbe\\0gray9be\\0gbrp9be\\0yuv444p9be\\0yuva444p9be\\0yuv422p9be\\0yuva422p9be\\0yuv420p9be\\0yuva420p9be\\0xv48be\\0bgr48be\\0rgb48be\\0rgba128be\\0rgb96be\\0xv36be\\0gray16be\\0pcm_u16be\\0pcm_s16be\\0bayer_bggr16be\\0gbrp16be\\0gbrap16be\\0yuv444p16be\\0yuva444p16be\\0yuv422p16be\\0yuva422p16be\\0yuv420p16be\\0yuva420p16be\\0bayer_gbrg16be\\0bayer_grbg16be\\0grayf16be\\0gbrpf16be\\0gbrapf16be\\0rgbf16be\\0yaf16be\\0rgbaf16be\\0bayer_rggb16be\\0ya16be\\0p416be\\0y216be\\0p216be\\0p016be\\0bgr565be\\0rgb565be\\0bgr555be\\0rgb555be\\0ayuv64be\\0pcm_s64be\\0pcm_f64be\\0bgra64be\\0rgba64be\\0bgr444be\\0rgb444be\\0pcm_u24be\\0pcm_s24be\\0gray14be\\0gbrp14be\\0gbrap14be\\0yuv444p14be\\0yuv422p14be\\0yuv420p14be\\0gray32be\\0pcm_u32be\\0pcm_s32be\\0gbrap32be\\0grayf32be\\0gbrpf32be\\0gbrapf32be\\0rgbf32be\\0yaf32be\\0rgbaf32be\\0pcm_f32be\\0xyz12be\\0gray12be\\0gbrp12be\\0gbrap12be\\0yuv444p12be\\0yuva444p12be\\0yuv422p12be\\0yuva422p12be\\0yuv440p12be\\0yuv420p12be\\0p412be\\0y212be\\0p212be\\0p012be\\0xv30be\\0nv20be\\0gray10be\\0x2bgr10be\\0gbrp10be\\0gbrap10be\\0yuv444p10be\\0yuva444p10be\\0yuv422p10be\\0yuva422p10be\\0yuv440p10be\\0yuv420p10be\\0yuva420p10be\\0x2rgb10be\\0p410be\\0y210be\\0p210be\\0p010be\\0dolby_e\\0bt1361e\\0txd\\0xwd\\0microdvd\\0pcm_dvd\\0pcm_s24daud\\0satd\\0psd\\0forward\\0Standard\\0avdiscard\\0ADPCM IMA Westwood\\0BurlyWood\\0mod\\0Two loop searching method\\0DarkGoldenRod\\0PaleGoldenRod\\0metasound\\0round\\0Voxware MetaSound\\0BlanchedAlmond\\0s->buf_ptr < s->buf_end\\0bitand\\0adpcm_xmd\\0videotoolbox_vld\\0d3d11va_vld\\0dxva2_vld\\0mv0_threshold\\0Gold\\0aac_eld\\0aac_ld\\0xvid\\0bethsoftvid\\0MediumOrchid\\0DarkOrchid\\0dnxhd\\0wqhd\\0nhd\\0cfhd\\0truehd\\0quadhd\\0aptx_hd\\0Reserved\\0limited\\0sum of absolute differences, median predicted\\0JPEG XL animated\\0List of decoders that are allowed to be used\\0Avid Meridien Uncompressed\\0Forward Uncompressed\\0Error number %d occurred\\0last_pred\\0Visually Impaired\\0Hearing Impaired\\0number of macroblock rows at the top which are skipped\\0number of macroblock rows at the bottom which are skipped\\0%sunaligned\\0bitpacked\\0Bitpacked\\0premultiplied\\0Premultiplied\\0unspecified\\0Unspecified\\0bikeshed\\0Extended\\0Advanced\\0MediumVioletRed\\0PaleVioletRed\\0IndianRed\\0DarkRed\\0OrangeRed\\0cscd\\0photocd\\0gcd\\0mbd\\0quad\\0vsad\\0msad\\0adpcm_ima_rad\\0mad\\0adpcm_ima_ea_sead\\0lead\\0stereo3d\\0USR%d\\0AMBI%d\\0ambisonic %d\\0adpcm_psxc\\0vvc\\0kmvc\\0fmvc\\0hevc\\0lcevc\\0Lavc\\0dnxuc\\0mwsc\\0sntsc\\0qntsc\\0tdsc\\0rasc\\0aasc\\0evrc\\0color_trc\\0wavarc\\0vqc\\0adpcm_ima_apc\\0trunc\\0cmp_func\\0vmnc\\0sub_charenc\\0smpte_436m_anc\\0bt2020nc\\0chroma-derived-nc\\0smc\\0nomc\\0imc\\0qdmc\\0ylc\\0cllc\\0notchlc\\0QDesign Music\\0sonic\\0Sonic\\0mimic\\0Mimic\\0flic\\0encoding level, usually corresponding to the profile level, codec-specific\\0aic\\0srgc\\0adpcm_afc\\0altivec\\0zerocodec\\0ohcodec\\0mediacodec\\0unknown_codec\\0Wave synthesis pseudo-codec\\0Null audio codec\\0Karl Morton's video codec\\0Null video codec\\0Canopus Lossless Codec\\0VBLE Lossless Codec\\0YUY2 Lossless Codec\\0On2 Audio for Video Codec\\0MidiVid Archive Codec\\0Apple Intermediate Codec\\0innoHeim/Rsupport Screen Capture Codec\\0Mandsoft Screen Capture Codec\\0TechSmith Screen Capture Codec\\0MatchWare Screen Capture Codec\\0FM Screen Capture Codec\\0Screen Recorder Gold Codec\\0Avid AVI Codec\\0AJA Kona 10-bit RGB Codec\\0mdec\\0pcm_vidc\\0tscc\\0rscc\\0mscc\\0fcc\\0Scc\\0sbc\\0arbc\\0ilbc\\0dirac\\0Dirac\\0apac\\0flac\\0alac\\0iac\\0aac\\0mdct_inv_float_c\\0mdct_naive_inv_float_c\\0mdct_pfa_9xM_inv_float_c\\0mdct_pfa_7xM_inv_float_c\\0mdct_pfa_5xM_inv_float_c\\0mdct_pfa_15xM_inv_float_c\\0mdct_pfa_3xM_inv_float_c\\0fft_float_c\\0fft_pfa_ns_float_c\\0fft9_ns_float_c\\0fft8_ns_float_c\\0fft32768_ns_float_c\\0fft2048_ns_float_c\\0fft128_ns_float_c\\0fft7_ns_float_c\\0fft4096_ns_float_c\\0fft256_ns_float_c\\0fft65536_ns_float_c\\0fft16_ns_float_c\\0fft5_ns_float_c\\0fft15_ns_float_c\\0fft4_ns_float_c\\0fft16384_ns_float_c\\0fft64_ns_float_c\\0fft1024_ns_float_c\\0fft3_ns_float_c\\0fft2_ns_float_c\\0fft8192_ns_float_c\\0fft131072_ns_float_c\\0fft32_ns_float_c\\0fft512_ns_float_c\\0rdft_r2r_float_c\\0rdft_c2r_float_c\\0mdct_inv_full_float_c\\0fft_naive_small_float_c\\0fft_inplace_small_float_c\\0rdft_r2i_float_c\\0fft_naive_float_c\\0fft_inplace_float_c\\0mdct_fwd_float_c\\0mdct_naive_fwd_float_c\\0mdct_pfa_9xM_fwd_float_c\\0mdct_pfa_7xM_fwd_float_c\\0mdct_pfa_5xM_fwd_float_c\\0mdct_pfa_15xM_fwd_float_c\\0mdct_pfa_3xM_fwd_float_c\\0fft9_fwd_float_c\\0fft7_fwd_float_c\\0fft5_fwd_float_c\\0fft3_fwd_float_c\\0rdft_r2c_float_c\\0fft_pfa_float_c\\0dstI_float_c\\0dctI_float_c\\0dctII_float_c\\0dctIII_float_c\\0rdft_r2r_mod2_float_c\\0rdft_r2i_mod2_float_c\\0mdct_inv_double_c\\0mdct_naive_inv_double_c\\0mdct_pfa_9xM_inv_double_c\\0mdct_pfa_7xM_inv_double_c\\0mdct_pfa_5xM_inv_double_c\\0mdct_pfa_15xM_inv_double_c\\0mdct_pfa_3xM_inv_double_c\\0fft_double_c\\0fft_pfa_ns_double_c\\0fft9_ns_double_c\\0fft8_ns_double_c\\0fft32768_ns_double_c\\0fft2048_ns_double_c\\0fft128_ns_double_c\\0fft7_ns_double_c\\0fft4096_ns_double_c\\0fft256_ns_double_c\\0fft65536_ns_double_c\\0fft16_ns_double_c\\0fft5_ns_double_c\\0fft15_ns_double_c\\0fft4_ns_double_c\\0fft16384_ns_double_c\\0fft64_ns_double_c\\0fft1024_ns_double_c\\0fft3_ns_double_c\\0fft2_ns_double_c\\0fft8192_ns_double_c\\0fft131072_ns_double_c\\0fft32_ns_double_c\\0fft512_ns_double_c\\0rdft_r2r_double_c\\0rdft_c2r_double_c\\0mdct_inv_full_double_c\\0fft_naive_small_double_c\\0fft_inplace_small_double_c\\0rdft_r2i_double_c\\0fft_naive_double_c\\0fft_inplace_double_c\\0mdct_fwd_double_c\\0mdct_naive_fwd_double_c\\0mdct_pfa_9xM_fwd_double_c\\0mdct_pfa_7xM_fwd_double_c\\0mdct_pfa_5xM_fwd_double_c\\0mdct_pfa_15xM_fwd_double_c\\0mdct_pfa_3xM_fwd_double_c\\0fft9_fwd_double_c\\0fft7_fwd_double_c\\0fft5_fwd_double_c\\0fft3_fwd_double_c\\0rdft_r2c_double_c\\0fft_pfa_double_c\\0dstI_double_c\\0dctI_double_c\\0dctII_double_c\\0dctIII_double_c\\0rdft_r2r_mod2_double_c\\0rdft_r2i_mod2_double_c\\0mdct_inv_int32_c\\0mdct_naive_inv_int32_c\\0mdct_pfa_9xM_inv_int32_c\\0mdct_pfa_7xM_inv_int32_c\\0mdct_pfa_5xM_inv_int32_c\\0mdct_pfa_15xM_inv_int32_c\\0mdct_pfa_3xM_inv_int32_c\\0fft_int32_c\\0fft_pfa_ns_int32_c\\0fft9_ns_int32_c\\0fft8_ns_int32_c\\0fft32768_ns_int32_c\\0fft2048_ns_int32_c\\0fft128_ns_int32_c\\0fft7_ns_int32_c\\0fft4096_ns_int32_c\\0fft256_ns_int32_c\\0fft65536_ns_int32_c\\0fft16_ns_int32_c\\0fft5_ns_int32_c\\0fft15_ns_int32_c\\0fft4_ns_int32_c\\0fft16384_ns_int32_c\\0fft64_ns_int32_c\\0fft1024_ns_int32_c\\0fft3_ns_int32_c\\0fft2_ns_int32_c\\0fft8192_ns_int32_c\\0fft131072_ns_int32_c\\0fft32_ns_int32_c\\0fft512_ns_int32_c\\0rdft_r2r_int32_c\\0rdft_c2r_int32_c\\0mdct_inv_full_int32_c\\0fft_naive_small_int32_c\\0fft_inplace_small_int32_c\\0rdft_r2i_int32_c\\0fft_naive_int32_c\\0fft_inplace_int32_c\\0mdct_fwd_int32_c\\0mdct_naive_fwd_int32_c\\0mdct_pfa_9xM_fwd_int32_c\\0mdct_pfa_7xM_fwd_int32_c\\0mdct_pfa_5xM_fwd_int32_c\\0mdct_pfa_15xM_fwd_int32_c\\0mdct_pfa_3xM_fwd_int32_c\\0fft9_fwd_int32_c\\0fft7_fwd_int32_c\\0fft5_fwd_int32_c\\0fft3_fwd_int32_c\\0rdft_r2c_int32_c\\0fft_pfa_int32_c\\0dstI_int32_c\\0dctI_int32_c\\0dctII_int32_c\\0dctIII_int32_c\\0rdft_r2r_mod2_int32_c\\0rdft_r2i_mod2_int32_c\\0bt2020c\\0libavutil/tx.c\\0libavutil/hwcontext.c\\0libavutil/channel_layout.c\\0libavutil/opt.c\\0libavcodec/options.c\\0libavutil/imgutils.c\\0libavcodec/get_buffer.c\\0libavutil/buffer.c\\0libavcodec/aaccoder.c\\0libavutil/mem.c\\0libavcodec/audio_frame_queue.c\\0libavcodec/encode.c\\0libavcodec/aacenc.c\\0libavcodec/avcodec.c\\0chroma-derived-c\\0%d%*1[:/]%d%c\\0%d:%d%c\\0amr_wb\\0vb\\0xsub\\0jacosub\\0tb\\0monob\\0amr_nb\\0zlib\\x008svx_fib\\0argb\\x000rgb\\0mjpegb\\0bb\\0OliveDrab\\0rpza\\0vuya\\0dxa\\0adpcm_ea_maxis_xa\\0adpcm_xa\\0rwa\\0uyva\\0Aqua\\0tta\\0DarkMagenta\\0mastering_display_metadata\\0green_metadata\\0timed ID3 metadata\\0SMPTE 336M Key-Length-Value (KLV) metadata\\0place global headers at every keyframe instead of in extradata\\0bin_data\\0export_side_data\\0!avpkt->data && !avpkt->side_data\\0!avpkt->data\\0binary data\\0export Producer Reference Time through packet side data\\0Export metadata as side data\\0do not skip samples and export skip information as frame side data\\0export motion vectors through frame side data\\0export film grain parameters through frame side data\\0export video encoding parameters through frame side data\\0export picture enhancement metadata through frame side data\\0ssa\\0aura\\0nointra\\0High 4:4:4 Intra\\0High 4:2:2 Intra\\0High 10 Intra\\0theora\\0Theora\\0bgra\\0ws_vqa\\0hq_hqa\\0qoa\\0Sienna\\0qpel_chroma\\0hpel_chroma\\0adpcm_vima\\0daala\\0Daala\\0rka\\0Fuchsia\\0cpia\\0PCM signed 16|20|24-bit big-endian for Blu-ray media\\0mvha\\0skip_alpha\\0Skip processing alpha\\0adpcm_yamaha\\0ADPCM Yamaha\\0wxga\\0wuxga\\0wquxga\\0whuxga\\0wsxga\\0wqsxga\\0whsxga\\0qxga\\0woxga\\0wvga\\0svga\\0fwqvga\\0qqvga\\0hqvga\\0hvga\\0image/x-tga\\0pcm_sga\\0image/x-targa\\0ega\\0cga\\0dfa\\0adpcm_ea\\0cuda\\0adpcm_ima_pda\\0adpcm_aica\\0hca\\0rgba\\0gray8a\\0vp6a\\0%d channels (%[^)]\\0flags: [\\0[%i]: [\\0DPCM Marble WADY\\0Canopus HQX\\0DNXHR HQX\\0CRI AHX\\0ADPCM IMA MobiClip MOFLEX\\0DTS-HD MA + DTS:X IMAX\\0DTS-HD MA + DTS:X\\0Apple ProRes RAW\\0Cintel RAW\\0Resolume DXV\\0HuffYUV\\x008088flex TMV\\0ADPCM IMA AMV\\0ADPCM IMA WAV\\0HuffYUV MT\\0LT\\0RealAudio SIPR / ACELP.NET\\0floating point AAN IDCT\\0use interlaced DCT\\0floating point AAN DCT\\0application/MP2T\\0JPEG XS\\0DTS\\0SMPTE ST 2038 VANC in MPEG-2 TS\\0ADPCM IMA Funcom ISS\\0IMGUTILS\\0JPEG-LS\\0JPEG LS\\0DTS-ES\\0ADPCM IMA Electronic Arts EACS\\0ADPCM Electronic Arts XAS\\0%J:%M:%S\\0%H:%M:%S\\0WR\\0TTR\\0TSR\\0SSR\\0AV_LOG_FORCE_COLOR\\0AV_LOG_FORCE_NOCOLOR\\0AV_LOG_FORCE_256COLOR\\0BIR\\0TFR\\0BFR\\0SDR\\0TBR\\0DPCM id RoQ\\0XQ\\0VQF TwinVQ\\0MidiVid VQ\\0DNXHR SQ\\0NewTek SpeedHQ\\0RAW HQ\\0DNXHR HQ\\0WebP\\0LTP\\0closed GOP\\0LEAD MCMP\\0ADPCM IMA High Voltage Software ALP\\0ADPCM Nintendo THP\\0LOCO\\0YCGCO\\0Sipro ACELP.KELVIN\\0IFF ACBM/ANIM/DEEP/ILBM/PBM/RGB8/RGBN\\0NAN\\0GSM\\0TERM\\0ADPCM IMA Ubisoft APM\\0ADPCM AmuseGraphics Movie AGM\\0SEGA CRI ADX ADPCM\\0G.726 ADPCM\\0G.722 ADPCM\\0Interplay ACM\\0SMPTE 302M\\0BT.470 M\\0SMPTE 170 M\\0SMPTE 240 M\\0Miro VideoXL\\0JPEG XL\\0WL\\0TTL\\0TSL\\0SSL\\0NULL\\0ALL\\0BIL\\0TFL\\0BFL\\0SDL\\0Chroma-derived NCL\\0BT.2020 NCL\\0Chroma-derived CL\\0BT.2020 CL\\0TBL\\0ADPCM Nintendo Gamecube DTK\\0UNK\\0JPEG 2000 digital cinema 4K\\0JPEG 2000 digital cinema 2K\\0PI\\0ADPCM IMA Dialogic OKI\\0PHI\\0AMBI\\0LCL (LossLess Codec Library) MSZH\\0Q-team QPEG\\0NuppelVideo/RTJPEG\\0ADPCM IMA Loki SDL MJPEG\\0Lossless JPEG\\0Motion JPEG\\0BT.470 BG\\0DPCM Xilam DERF\\0INF\\0ADPCM MTAF\\0NONE\\0Microsoft RLE\\0Autodesk RLE\\0LFE\\0codec->cb_type == FF_CODEC_CB_TYPE_ENCODE\\0EBU 3213-E\\0Dolby E\\0UNSD\\0Matrox Uncompressed SD\\0ADPCM Konami XMD\\0ELD\\0VC3/DNxHD\\0TrueHD\\0DNXHD\\0GoPro CineForm HD\\0Kodak Photo CD\\0ADPCM IMA Electronic Arts SEAD\\0TC\\0TDSC\\0FRC\\0ViewQuest VQC\\0ADPCM IMA CRYO APC\\0MXF SMPTE-436M ANC\\0NotchLC\\0FLC\\0Mirillis FIC\\0TFC\\0BFC\\0ADPCM Nintendo Gamecube AFC\\0PCM Archimedes VIDC\\0FCC\\0TBC\\0xHE-AAC\\0ADPCM Playstation C\\0Profile C\\0Beam Software VB\\0XSUB\\0DNXHR LB\\0LCL (LossLess Codec Library) ZLIB\\0RGB\\0Apple MJPEG-B\\0Feeble Files/ScummVM DXA\\0ADPCM Electronic Arts Maxis CDROM XA\\0ADPCM CDROM XA\\0Auravision AURA\\0DTS-HD HRA\\0Canopus HQ/HQA\\0DTS-HD MA\\0PCM SGA\\0Chronomaster DFA\\0QP2LAMBDA\\0ADPCM Yamaha AICA\\0CRI HCA\\0Profile A\\0@\\0?\\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0 \\0=\\0:\\0vp9\\0atrac9\\0Google VP9\\0g729\\0G.729\\0bt709\\0BT.709\\0Windows Media Video 9\\0gray8,y8\\0pcm_u8\\0pcm_s8\\0bayer_bggr8\\0bgr8\\0vp8\\0pal8\\0musepack8\\0bayer_gbrg8\\0bayer_grbg8\\0rgb8\\0bayer_rggb8\\0ya8\\0Musepack SV8\\0On2 VP8\\0ra_288\\0smpte_2038\\0gamma28\\0g728\\0G.728\\0smpte428\\0eia_608\\0v408\\0v308\\0UTF-8\\0Windows Media Video 8\\0vp7\\0musepack7\\0Musepack SV7\\0On2 VP7\\0arib-std-b67\\0ARIB STD-B67\\0Windows Media Video 7\\0simplearmv6\\0vp6\\0mace6\\0On2 VP6\\0adpcm_g726\\0nv16\\0Pinnacle TARGA CineWave YUV16\\0log316\\0targa_y216\\x009.1.6\\0vp5\\0indeo5\\0imm5\\0a64_multi5\\0On2 VP5\\0Infinity IMM5\\0smpte2085\\0SMPTE 2085\\0scte_35\\0Intel Indeo Video Interactive 5\\0yuv4\\0mv4\\0adpcm_ima_dat4\\0bgr4\\0vp4\\0ump4\\0indeo4\\0mp3on4\\0adpcm_ima_hvqm4\\0imm4\\0adpcm_ima_dk4\\0mpeg4\\0xan_wc4\\0misc4\\0ac4\\0rgb4\\0adpcm_sbpro_4\\0iec61966_2_4\\0ADPCM IMA Eurocom DAT4\\0On2 VP4\\0MP3onMP4\\0ADPCM IMA HVQM4\\0Infinity IMM4\\0ADPCM IMA Duck DK4\\0Uncompressed packed QT 4:4:4:4\\0High 4:4:4\\0Uncompressed packed 4:4:4\\0CAVLC 4:4:4\\0Main 10 4:4:4\\0smpte2084\\0SMPTE 2084\\0adpcm_n64\\0ADPCM Silicon Graphics N64\\0h264\\0Multicolor charset for Commodore 64\\0vyu444\\x004444\\0DNXHR 444\\0ra_144\\0nv24\\0bgr24\\0rgb24\\0escape124\\0Escape 124\\0DTS 96/24\\0AVS2-P2/IEEE1857.4\\x009.1.4\\x007.1.4\\x005.1.4\\0AC-4\\0iec61966-2-4\\0IEC 61966-2-4\\0Intel Indeo Video Interactive 4\\0wmv3\\0msmpeg4v3\\0avs3\\0adpcm_ea_r3\\0svq3\\0vp3\\0mp3\\0indeo3\\0adpcm_ima_dk3\\0mace3\\0timed_id3\\0xan_wc3\\0lc3\\0atrac3\\0eac3\\0adpcm_sbpro_3\\0ADPCM Electronic Arts R3\\0Sorenson Vector Quantizer 3 / Sorenson Video 3 / SVQ3\\0On2 VP3\\0ADPCM IMA Duck DK3\\0c93\\0Interplay C93\\0h263\\0Intel H.263\\0ebu3213\\0Lavc62.23.103\\x007.2.3\\0Intel Indeo 3\\0MPEG-4 part 2 Microsoft variant version 3\\0Profile 3\\0flashsv2\\0asv2\\0wmv2\\0wmav2\\0aac_he_v2\\0HE-AACv2\\0msmpeg4v2\\0Flash Screen Video v2\\0Windows Media Video 9 Image v2\\0avs2\\0mts2\\0mss2\\0pass2\\0flags2\\0adpcm_ea_r2\\0msp2\\0mp2\\0image/jp2\\0indeo2\\0truemotion2\\0atan2\\0adpcm_ima_hvqm2\\0qdm2\\0rl2\\0mpl2\\0mvc2\\0pool->alloc || pool->alloc2\\0codec2\\0tscc2\\0ipt-c2\\0aura2\\0xma2\\0qpel_chroma2\\0adpcm_sbpro_2\\0ASUS V2\\0ADPCM Electronic Arts R2\\0ADPCM IMA HVQM2\\0LFE2\\0IPT-C2\\0High 4:2:2\\0nv42\\0fft_int32\\0rdft_int32\\0mdct_int32\\0dstI_int32\\0dctI_int32\\0bgr32\\0rgb32\\0smpte432\\0jedec-p22\\0gamma22\\0JEDEC P22\\0adpcm_g722\\0uyvy422\\0yuyv422\\0yvyu422\\0nv12\\0d3d12\\0bt2020-12\\x0022.2\\x007.1.2\\x005.1.2\\x003.1.2\\0SMPTE 431-2\\0low frequency 2\\0LCEVC (Low Complexity Enhancement Video Coding) / MPEG-5 LCEVC / MPEG-5 part 2\\0MPEG-4 part 2\\0Silicon Graphics Motion Video Compressor 2\\0Xbox Media Audio 2\\0Windows Media Audio 2\\0Intel Indeo 2\\0MPEG-4 part 2 Microsoft variant version 2\\0H.263 / H.263-1996, H.263+ / H.263-1998 / H.263 version 2\\0Microsoft Paint (MSP) version 2\\0Profile 2\\0TechSmith Screen Codec 2\\0QDesign Music Codec 2\\0Auravision Aura 2\\0rtv1\\0asv1\\0wnv1\\0wmv1\\0flv1\\0kgv1\\0ffv1\\0wmav1\\0msmpeg4v1\\0Flash Screen Video v1\\0mss1\\0pass1\\0subviewer1\\0vcr1\\0adpcm_ea_r1\\0svq1\\0mp1\\0msvideo1\\0truemotion1\\0westwood_snd1\\0mvc1\\0atrac1\\0msa1\\0xma1\\0smpte428_1\\0g723_1\\0iec61966_2_1\\0Winnov WNV1\\0Alliance for Open Media AV1\\0ASUS V1\\0ATI VCR1\\0ADPCM Electronic Arts R1\\0Sorenson Vector Quantizer 1 / Sorenson Video 1 / SVQ1\\0MACE (Macintosh Audio Compression/Expansion) 6:1\\0MACE (Macintosh Audio Compression/Expansion) 3:1\\0bt1361\\0BT.1361\\0h261\\0H.261\\0smpte431\\0nv21\\0d3d11\\0uyyvyy411\\0m101\\x007.1\\x006.1\\x005.1\\x004.1\\0G.723.1\\x002.1\\0SMPTE VC-1\\0SMPTE 428-1\\0SMPTE 422-1\\0iec61966-2-1\\0IEC 61966-2-1\\0FFmpeg video codec #1\\0Silicon Graphics Motion Video Compressor 1\\0Xbox Media Audio 1\\0Windows Media Audio 1\\0Microsoft Video 1\\0JPEG 2000 codestream restriction 1\\0MPEG-4 part 2 Microsoft variant version 1\\0MS Screen 1\\0Profile 1\\0bgr0\\0rgb0\\0Uncompressed packed 4:2:0\\0hd480\\0hd1080\\0rv60\\0uhd2160\\0DNxUncompressed / SMPTE RDD 50\\0rv40\\0rv30\\0mv30\\0escape130\\0Escape 130\\0rv20\\0hd720\\0uhd4320\\0diff >= 0 && diff <= 120\\0bt2020\\0BT.2020\\0rv10\\0main10\\0v410\\0v210\\0r210\\0AVS3-P2/IEEE1857.10\\0bt2020-10\\0H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\\0Main 10\\0High 10\\0log100\\0media100\\0jpeg2000\\0JPEG 2000\\x007.0\\0RealVideo 6.0\\x005.0\\0RealVideo 4.0\\0RealVideo 3.0\\0MidiVid 3.0\\0RealVideo 2.0\\0Duck TrueMotion 2.0\\0video quantizer scale compression (VBR). Constant of ratecontrol equation. Recommended range for default rc_eq: 0.0-1.0\\0RealVideo 1.0\\0Duck TrueMotion 1.0\\0JPEG 2000 codestream restriction 0\\0Profile 0\\0ret >= 0\\0ret == 0\\0frame->nb_extended_buf == 0\\0ret <= 0\\0Set video bitrate tolerance (in bits/s). In 1-pass mode, bitrate tolerance specifies how far ratecontrol is willing to deviate from the target average bitrate value. This is not related to minimum/maximum bitrate. Lowering tolerance too much has an adverse effect on quality.\\0A device with a derived frame context cannot be used as the destination of a HW -> HW transfer.\\0A device with a derived frame context cannot be used as the source of a HW -> HW transfer.\\0maximum bitrate (in bits/s). Used for VBV together with bufsize.\\0minimum bitrate (in bits/s). Most useful in setting up a CBR encode. It is of little use otherwise.\\0YCgCo-R, even add.\\0YCgCo-R, odd add.\\0,\\0+\\0set audio sampling rate (in Hz)\\0sum of squared quantization errors (avoid, low quality)\\0OSQ (Original Sound Quality)\\0AAC LATM (Advanced Audio Coding LATM syntax)\\0iterative motion vector (MV) search (slow)\\x006.1(front)\\x007.0(front)\\x006.0(front)\\0PGX (JPEG2000 Test Format)\\0CompuServe GIF (Graphics Interchange Format)\\0set ratecontrol buffer size (in bits)\\0ATRAC3 AL (Adaptive TRansform Acoustic Coding 3 Advanced Lossless)\\0ATRAC3+ AL (Adaptive TRansform Acoustic Coding 3+ Advanced Lossless)\\0DCA (DTS Coherent Acoustics)\\0set bitrate (in bits/s)\\0TAK (Tom's lossless Audio Kompressor)\\0limit motion vectors range (1023 for DivX player)\\0DST (Direct Stream Transfer)\\x004GV (Fourth Generation Vocoder)\\0SMV (Selectable Mode Vocoder)\\0Sony PlayStation MDEC (Motion DECoder)\\0IAC (Indeo Audio Coder)\\0IMC (Intel Music Coder)\\0BMP (Windows and OS/2 bitmap)\\0Apple ProRes (iCodec Pro)\\0TTA (True Audio)\\0RKA (RK Audio)\\0QOA (Quite OK Audio)\\0RTV1 (RivaTuner Video)\\0DV (Digital Video)\\0FLV / Sorenson Spark / Sorenson H.263 (Flash Video)\\0PDV (PlayDate Video)\\0DFPWM (Dynamic Filter Pulse Width Modulation)\\0old standard qpel (autodetected per FOURCC/version)\\0edge padding bug (autodetected per FOURCC/version)\\0direct-qpel-blocksize bug (autodetected per FOURCC/version)\\0On2 VP6 (Flash version)\\0ADPCM Nintendo THP (Little-Endian)\\0FITS (Flexible Image Transport System)\\0Multicolor charset for Commodore 64, extended with 5th color (colram)\\0(null)\\0On2 VP6 (Flash version, with alpha channel)\\x005.1.2(back)\\x006.1(back)\\x003.0(back)\\0aptX (Audio Processing Technology for Bluetooth)\\0aptX HD (Audio Processing Technology for Bluetooth)\\0MLP (Meridian Lossless Packing)\\0AAC (Advanced Audio Coding)\\0H.265 / HEVC (High Efficiency Video Coding)\\0MPEG-5 EVC (Essential Video Coding)\\0H.266 / VVC (Versatile Video Coding)\\0ATRAC1 (Adaptive TRansform Acoustic Coding)\\0Chinese AVS (Audio Video Standard) (AVS1-P2, JiZhun profile)\\0QOI (Quite OK Image)\\0macroblock decision algorithm (high quality mode)\\x007.1(wide)\\x007.1(wide-side)\\0quad(side)\\x005.1(side)\\x005.0(side)\\0AMR-NB (Adaptive Multi-Rate NarrowBand)\\0AMR-WB (Adaptive Multi-Rate WideBand)\\0padding bug (autodetected)\\0codec2 (very low bitrate speech codec)\\0SBC (low-complexity subband codec)\\0ALAC (Apple Lossless Audio Codec)\\0FLAC (Free Lossless Audio Codec)\\0Opus (Opus Interactive Audio Codec)\\0LC3 (Low Complexity Communication Codec)\\0iLBC (Internet Low Bitrate Codec)\\0EVRC (Enhanced Variable Rate Codec)\\0Xvid interlacing bug (autodetected if FOURCC == XVIX)\\0Sunplus JPEG (SP5X)\\0Creative YUV (CYUV)\\0Constrained Energy Lapped Transform (CELT)\\0Bink Audio (RDFT)\\0use only bitexact functions (except (I)DCT)\\0Bink Audio (DCT)\\0MPEG-4 Audio Lossless Coding (ALS)\\0maximum difference between the quantizer scales (VBR)\\0video quantizer scale blur (VBR)\\0maximum macroblock Lagrange factor (VBR)\\0minimum macroblock Lagrange factor (VBR)\\0maximum video quantizer scale (VBR)\\0minimum video quantizer scale (VBR)\\0Digital Speech Standard - Standard Play mode (DSS SP)\\0per-block quantization parameter (QP)\\0RealAudio 2.0 (28.8K)\\0RealAudio 1.0 (14.4K)\\0QuickTime Graphics (SMC)\\0QuickTime video (RPZA)\\0ATRAC9 (Adaptive TRansform Acoustic Coding 9)\\0(autodetected if FOURCC == UMP4)\\0memory management control operations (H.264)\\0use four motion vectors per macroblock (MPEG-4)\\0ATSC A/52B (AC-3, E-AC-3)\\0ATSC A/52A (AC-3)\\0ADU (Application Data Unit) MP3 (MPEG audio layer 3)\\0ATRAC3 (Adaptive TRansform Acoustic Coding 3)\\0Cook / Cooker / Gecko (RealAudio G2)\\0MP2 (MPEG audio layer 2)\\0Westwood Audio (SND1)\\0JPEG (2^n-1)\\0MP1 (MPEG audio layer 1)\\0ATRAC3+ (Adaptive TRansform Acoustic Coding 3+)\\0MPEG (219*2^(n-8))\\0sep && sep != '\\\\\\\\' && (sep < 'a' || sep > 'z') && (sep < 'A' || sep > 'Z') && (sep < '0' || sep > '9')\\0bsf_list(\\0%d channels (\\0ambisonic \\0[%s @ %p] \\0, len: \\0 %i: \\0Codec '%s' (%d) is not supported by the bitstream filter '%s'. Supported codecs are: \\0%s - type: \\0For transform of length %i, %s, \\0[%i, \\0%s, factor: %i, \\0], \\0%s (%d) \\0 \\0 + \\r\\0Trying to remove %d samples, but the queue is empty +\\0Too many bits %f > %d per frame requested, clamping to max +\\0ignoring invalid SAR: %u/%u +\\0Unable to parse "%s" option value "%s" as channel layout +\\0Invalid channel layout +\\0Ignoring attempt to flush encoder that doesn't support it +\\0Reconstructed frame output requested from an encoder not supporting it +\\0Picture size %ux%u exceeds specified max pixel count %lld, see the documentation if you wish to increase it +\\0AVOption type %d of option %s not implemented yet +\\0dimensions not set +\\0%s requires channel layout to be set +\\0Mismatching AVCodecContext.pix_fmt and AVHWFramesContext.format +\\0Value %f for parameter '%s' is not a valid set of 32bit integer flags +\\0Ignoring invalid width/height values +\\0Codec type or id mismatches +\\0Character encoding is only supported with subtitles codecs +\\0Unable to parse "%s" option value "%s" as %s +\\0const_values array too small for %s +\\0Cannot assign fewer than %u elements to array option %s +\\0Cannot assign more than %u elements to array option %s +\\0The "%s" option is deprecated: %s +\\0Error parsing decoder bitstream filters '%s': %s +\\0 %s +\\0Unable to parse "%s" option value "%s" as color +\\0pic->data[*]!=NULL in avcodec_default_get_buffer +\\0Specified sample format %s is not supported by the %s encoder +\\0Specified sample rate %d is not supported by the %s encoder +\\0Specified channel layout '%s' is not supported by the %s encoder +\\0default_get_buffer called on pic %p +\\0Unable to parse "%s" option value "%s" as duration +\\0Unable to parse "%s" option value "%s" as boolean +\\0Internal error, put_bits buffer too small +\\0Bitrate %lld is extremely low, maybe you mean %lldk +\\0%d frames left in the queue on closing +\\0Input contains (near) NaN/+-Inf +\\0PNS unavailable in the "mpeg2_aac_low" profile, turning off +\\0Qavg: %.3f +\\0%f +\\0Unable to parse "%s" option value "%s" as image size +\\0Trying to remove %d more samples than there are in the queue +\\0Unable to parse "%s" option value "%s" as video rate +\\0gray decoding requested but not enabled at configuration time +\\0Queue input is backward in time +\\0frame_size (%d) was not respected for a non-last frame +\\0Failed to allocate packet of size %lld +\\0Picture size %ux%u is invalid +\\0Option '%s' is not a runtime option and so cannot be set after the object has been initialized +\\0Codec '%s' is bitmap-based, subtitles character encoding will be ignored +\\0av_image_get_linesize failed +\\0get_encode_buffer() failed +\\0Assertion %s failed at %s:%d +\\0The maximum value for lowres supported by the decoder is %d +\\0Specified bit depth %d not possible with the specified pixel formats depth %d +\\0Failed to allocate packet of size %d +\\0Invalid video pixel format: %d +\\0Invalid audio sample format: %d +\\0Too many channels: %d +\\0Invalid block align: %d +\\0Invalid sample rate: %d +\\0Invalid side data type: %d +\\0 %d +\\0Failed to set close on exec +\\0Value %f for parameter '%s' out of range [%g - %g] +\\0Value %d for parameter '%s' out of %s format range [%d - %d] +\\0Supported channel layouts: +\\0Supported pixel formats: +\\0Supported sample formats: +\\0Supported sample rates: +\\0Supported alpha modes: +\\0Transform tree: +\\0Codec 0x%x is not in the full list. +\\0The copy_opaque flag is set, but the encoder does not support it. +\\0The %s '%s' is experimental but experimental codecs are not enabled, add '-strict %d' if you want to use it. +\\0The encoder timebase is not set. +\\0Failed to allocate a %s/%s frame from a fixed pool of hardware frames. +\\0Specified pixel format %s is not supported by the %s encoder. +\\0Specified alpha mode '%s' is not supported by the %s encoder. +\\0Invalid mapping found when attempting unmap. +\\0The encoder bitrate is negative. +\\0Invalid option type. +\\0Failed to map frame into derived frame context: %d. +\\0A non-NULL packet sent after an EOF. +\\0Consider setting extra_hw_frames to a larger value (currently set to %d, giving a pool size of %d). +\\0Alternatively use the non experimental %s '%s'. +\\0Mismatching AVCodecContext.sw_pix_fmt (%s) and AVHWFramesContext.sw_format (%s) +\\0nb_samples (%d) > frame_size (%d) +\\0Invalid minimum required packet size %lld (max allowed is %d) +\\0avpkt->{data,buf} != NULL in avcodec_default_get_encode_buffer() +\\0No buffer returned by get_encode_buffer() +\\0This AVCodecContext was allocated for %s, but %s passed to avcodec_open2() +\\0Codec (%s) not on whitelist '%s' +\\0Cannot find color '%s' +\\0Invalid chars '%s' at the end of expression '%s' +\\0Missing ')' or too many args in '%s' +\\0Unknown function in '%s' +\\0Invalid alpha value specifier '%s' in '%s' +\\0Missing ')' in '%s' +\\0Undefined constant or missing '(' in '%s' +\\0Invalid 0xRRGGBB[AA] color string: '%s' +\\0Using a PCE to encode channel layout "%s" +\\0Unsupported channel layout "%s" +\\0Unable to open iconv context with input character encoding "%s" +\\0Unable to parse "%s" option value "%s" +\\0Profile not supported! +\\0Success\\0Illegal byte sequence\\0Domain error\\0Result not representable\\0Not a tty\\0Permission denied\\0Operation not permitted\\0No such file or directory\\0No such process\\0File exists\\0Value too large for defined data type\\0No space left on device\\0Out of memory\\0Resource busy\\0Interrupted system call\\0Resource temporarily unavailable\\0Invalid seek\\0Cross-device link\\0Read-only file system\\0Directory not empty\\0Connection reset by peer\\0Operation timed out\\0Connection refused\\0Host is down\\0Host is unreachable\\0Address in use\\0Broken pipe\\0I/O error\\0No such device or address\\0Block device required\\0No such device\\0Not a directory\\0Is a directory\\0Text file busy\\0Exec format error\\0Invalid argument\\0Argument list too long\\0Symbolic link loop\\0Filename too long\\0Too many open files in system\\0No file descriptors available\\0Bad file descriptor\\0No child process\\0Bad address\\0File too large\\0Too many links\\0No locks available\\0Resource deadlock would occur\\0State not recoverable\\0Owner died\\0Operation canceled\\0Function not implemented\\0No message of desired type\\0Identifier removed\\0Device not a stream\\0No data available\\0Device timeout\\0Out of streams resources\\0Link has been severed\\0Protocol error\\0Bad message\\0File descriptor in bad state\\0Not a socket\\0Destination address required\\0Message too large\\0Protocol wrong type for socket\\0Protocol not available\\0Protocol not supported\\0Socket type not supported\\0Not supported\\0Protocol family not supported\\0Address family not supported by protocol\\0Address not available\\0Network is down\\0Network unreachable\\0Connection reset by network\\0Connection aborted\\0No buffer space available\\0Socket is connected\\0Socket not connected\\0Cannot send after socket shutdown\\0Operation already in progress\\0Operation in progress\\0Stale file handle\\0Remote I/O error\\0Quota exceeded\\0No medium found\\0Wrong medium type\\0Multihop attempted\\0Required key not available\\0Key has expired\\0Key has been revoked\\0Key was rejected by service\\0\\0\\0\\0\\0\\0\\0\\xF0?t\\x85\\xD3\\xB0\\xD9\\xEF?\\x89\\xF9lX\\xB5\\xEF?Q[\\xD0\\x93\\xEF?{Q}<\\xB8r\\xEF?\\xAA\\xB9h1\\x87T\\xEF?8bunz8\\xEF?\\xE1\\xDE\\xF5\\x9D\\xEF?\\xB71 +\\xFE\\xEF?\\xCB\\xA9:7\\xA7\\xF1\\xEE?"4L\\xA6\\xDE\\xEE?-\\x89a\\\`\\b\\xCE\\xEE?'*6\\xD5\\xDA\\xBF\\xEE?\\x82O\\x9DV+\\xB4\\xEE?)TH\\xDD\\x07\\xAB\\xEE?\\x85U:\\xB0~\\xA4\\xEE?\\xCD;\\x7Ff\\x9E\\xA0\\xEE?t_\\xEC\\xE8u\\x9F\\xEE?\\x87\\xEBs\\xA1\\xEE?\\xCEL\\x99\\x89\\xA5\\xEE?\\xDB\\xA0*B\\xE5\\xAC\\xEE?\\xE5\\xC5\\xCD\\xB07\\xB7\\xEE?\\x90\\xF0\\xA3\\x82\\x91\\xC4\\xEE?]%>\\xB2\\xD5\\xEE?\\xAD\\xD3Z\\x99\\x9F\\xE8\\xEE?G^\\xFB\\xF2v\\xFF\\xEE?\\x9CR\\x85\\xDD\\x9B\\xEF?i\\x90\\xEF\\xDC 7\\xEF?\\x87\\xA4\\xFB\\xDCX\\xEF?_\\x9B{3\\x97|\\xEF?\\xDA\\x90\\xA4\\xA2\\xAF\\xA4\\xEF?@En[v\\xD0\\xEF?\\0\\0\\0\\0\\0\\0\\xE8B\\x94#\\x91K\\xF8j\\xAC?\\xF3\\xC4\\xFAP\\xCE\\xBF\\xCE?\\xD6R\\f\\xFFB.\\xE6?\\0\\0\\0\\0\\0\\x008C\\xFE\\x82+eGG@\\x94#\\x91K\\xF8j\\xBC>\\xF3\\xC4\\xFAP\\xCE\\xBF.?\\xD6R\\f\\xFFB.\\x96?\\0A\\xD6\\x8C\\v\\xF0?\\0\\0\\0\\0\\0\\0\\xF8?\\0\\0\\0\\0\\0\\0\\0\\0\\xD0\\xCFC\\xEB\\xFDL>\\0A\\xFB\\x8C\\v\\xE4@\\xB8\\xE2?\\xBE\\xF3\\xF8y\\xECa\\xF6?0\\x96[\\xC6\\xFE\\xDE\\xBF=\\x88\\xAFJ\\xEDq\\xF5?\\xA4\\xFC\\xD42h\\v\\xDB\\xBF\\xB0\\xF0\\xF09\\x95\\xF4?{\\xB7 +\\x8BA\\xD7\\xBF\\x85\\xB8\\xB0\\x95\\xC9\\xF3?{\\xCFm\\xE9\\x9D\\xD3\\xBF\\xA5d\\x88\\f\\r\\xF3?1\\xB6\\xF2\\xF3\\x9B\\xD0\\xBF\\xA0\\x8E\\v{"^\\xF2?\\xF0z;\\x1B|\\xC9\\xBF?4JJ\\xBB\\xF1?\\x9F<\\xAF\\x93\\xE3\\xF9\\xC2\\xBF\\xBA\\xE5\\x8A\\xF0X#\\xF1?\\\\\\x8Dx\\xBF\\xCB\\\`\\xB9\\xBF\\xA7\\0\\x99A?\\x95\\xF0?\\xCE_G\\xB6\\x9Do\\xAA\\xBF\\0\\0\\0\\0\\0\\0\\xF0?\\0\\0\\0\\0\\0\\0\\0\\0\\xACG\\x9A\\xFD\\x8C\\\`\\xEE?=\\xF5$\\x9F\\xCA8\\xB3?\\xA0j\\xB3\\xA4\\xEC?\\xBA\\x918T\\xA9v\\xC4?\\xE6\\xFCjW6 \\xEB?\\xD2\\xE4\\xC4J\\v\\x84\\xCE?-\\xAA\\xA1c\\xD1\\xC2\\xE9?e\\xC6\\xF0E\\xD4?\\xEDAx\\xE6\\x86\\xE8?\\xF8\\x9F\\x1B,\\x9C\\x8E\\xD8?bHS\\xF5\\xDCg\\xE7?\\xCC{\\xB1N\\xA4\\xE0\\xDC?M~ +\\xF7\\xB6\\xD7\\xBF\\xE02hI\\xBF\\xCA\\xDE?\\xDE\\xE3\\xFA\\x9FG\\xE7\\xBF\\xB8\\xC85_G\\xF7?\\xBE\\xF3\\xF8y\\xECa\\xF6?\\xDE\\xAA\\x8C\\x80\\xF7{\\xD5\\xBF=\\x88\\xAFJ\\xEDq\\xF5?\\xDBm\\xC0\\xA7\\xF0\\xBE\\xD2\\xBF\\xB0\\xF0\\xF09\\x95\\xF4?g:Q\\x7F\\xAE\\xD0\\xBF\\x85\\xB8\\xB0\\x95\\xC9\\xF3?\\xE9$\\x82\\xA6\\xD81\\xCB\\xBF\\xA5d\\x88\\f\\r\\xF3?Xw\\xC0 +OW\\xC6\\xBF\\xA0\\x8E\\v{"^\\xF2?\\0\\x81\\x9C\\xC7+\\xAA\\xC1\\xBF?4JJ\\xBB\\xF1?^\\x8C\\xCEvN\\xBA\\xBF\\xBA\\xE5\\x8A\\xF0X#\\xF1?\\xCCaZ<\\x97\\xB1\\xBF\\xA7\\0\\x99A?\\x95\\xF0?\\f\\xE18\\xF4R\\xA2\\xBF\\0\\0\\0\\0\\0\\0\\xF0?\\0\\0\\0\\0\\0\\0\\0\\0\\xACG\\x9A\\xFD\\x8C\\\`\\xEE?\\x84Y\\xF2]\\xAA\\xA5\\xAA?\\xA0j\\xB3\\xA4\\xEC?\\xB4.6\\xAAS^\\xBC?\\xE6\\xFCjW6 \\xEB?\\b\\xDB w\\xE5&\\xC5?-\\xAA\\xA1c\\xD1\\xC2\\xE9?pG"\\r\\x86\\xC2\\xCB?\\xEDAx\\xE6\\x86\\xE8?\\xE1~\\xA0\\xC8\\x8B\\xD1?bHS\\xF5\\xDCg\\xE7? \\xEE\\xB6W0\\xD4?\\xEF9\\xFA\\xFEB.\\xE6?4\\x83\\xB8H\\xA3\\xD0\\xBFj\\v\\xE0\\v[W\\xD5?#A +\\xF2\\xFE\\xFF\\xDF\\xBF\\xC9\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\xFF\\xFF\\xFF\\xFF;+\\0\\0\\0\\0\\0\\x80\\xC9\\0\\0d<\\0A\\x80\\x92\\v\\x84{\\0\\0\\f\\x99\\0A\\x90\\x92\\v\\x8El\\0\\0\\xFF\\xA1\\0\\0\\0\\0\\0P\\0b\\0A\\xB0\\x92\\v +\\xF0e\\0\\xC8\\xC8\\0\\0\\xD0\\xC8\\0A\\xC8\\x92\\v\\x07\\0\\0\\\`\\xA7\\b\\0A\\xD9\\x92\\v\\f\\xC9\\0\\0=\\0\\0\\0>\\0\\0\\0?\\0A\\x80\\x93\\v\\r\\x84+\\0\\0\\xC5@\\0\\0\\0\\0\\0\\0A\\xA6\\x93\\v\\xF0? \\0\\0\\0\\x88+\\0\\0@.\\0\\x004e\\0\\0\\0\\0\\0\\0\\v\\0A\\xCE\\x93\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\x88+\\0\\0\\x9D\\0\\0\\x92H\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xFE\\x93\\vS\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\x88+\\0\\0\\xFF\\0\\0tJ\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\xF0? \\0\\0\\0\\0\\0\\0\\0 +!\\0\\0\\\\J\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xDE\\x94\\v#\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\xF0? \\0\\0\\0\\0\\0\\0\\0\\xCF\\0\\0P8\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0A\\x8E\\x95\\v#\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\xF0? \\0\\0\\0\\0\\0\\0\\0\\xC7\\0\\0I\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0A\\xBE\\x95\\v\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\xF0? \\0\\0\\0\\0\\0\\0\\0\\xBF_\\0\\0v'\\0\\0\\0\\0\\0\\0A\\xEE\\x95\\v\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\xF0? \\0\\0\\0\\0\\0\\0\\0\\xB4;\\0A\\x8C\\x96\\v\\v\\0A\\x9E\\x96\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0\\xD9\\0A\\xBC\\x96\\v\\v\\0\\0\\0\\0A\\xCE\\x96\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0\\0(\\0A\\xEC\\x96\\v\\v\\0\\0\\0\\0A\\xFE\\x96\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0\\xF5-\\0A\\x9C\\x97\\v\\v\\0\\0\\0\\0A\\xAE\\x97\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0@[\\0A\\xCC\\x97\\v\\v\\0\\0\\0\\0A\\xDE\\x97\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0a\\x8F\\0A\\xFC\\x97\\v\\v\\0\\0\\0\\0A\\x8E\\x98\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0f\\0A\\xAC\\x98\\v\\v\\0\\0\\0\\0A\\xBE\\x98\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0\\xFEe\\0A\\xDC\\x98\\v\\v\\0\\0\\0&\\0A\\xEE\\x98\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\x002[\\0A\\x8C\\x99\\v\\v\\0\\0\\0)\\0A\\x9E\\x99\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0\\xD3\\0A\\xBC\\x99\\v\\v\\0\\0\\0\\x80\\0A\\xCE\\x99\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0\\0:[\\0A\\xEC\\x99\\v\\v\\0\\0\\0\\x83\\0A\\xFE\\x99\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\xBBT\\0A\\xC0\\x9A\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\x9A\\v \\0\\0\\0\\0\\0\\0\\0A\\xF0\\x9A\\v \\0\\0\\0\\0\\0\\0\\x07\\0A\\x88\\x9B\\v +\\0\\0\\0\\0\\0\\0\\x07\\0A\\xA0\\x9B\\v \\0\\0\\0\\0\\0\\x007\\0A\\xB8\\x9B\\v \\0\\0\\0\\0\\0\\0?\\0A\\xD0\\x9B\\v +\\0\\0\\0\\b\\0\\0\\0?\\0A\\xF0\\x9B\\v \\0\\0\\0\\0\\0\\0\\0A\\x88\\x9C\\v\\0A\\xF8\\x9D\\v\\0A\\x98\\x9E\\v \\0\\0\\0\\0\\0\\0\\0A\\xB0\\x9E\\v\\0A\\xC0\\x9E\\v\\0A\\xA0\\xA0\\v\\0A\\xB1\\xA0\\v\\0A\\xC0\\xA0\\v \\0\\0\\0\\0\\0\\0\\v\\0A\\xD8\\xA0\\v\\0A\\xE4\\xA0\\v\\0\\0\\0\\0A\\xC8\\xA2\\v\\0A\\xD9\\xA2\\v\\0A\\xE8\\xA2\\v +\\0\\0\\0\\0\\0\\0\\0A\\x80\\xA3\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xA4\\v\\0A\\x81\\xA5\\v\\0A\\x90\\xA5\\v \\0\\0\\0\\0\\0\\0\\x07\\0A\\xA8\\xA5\\v\\0A\\xB8\\xA5\\v\\0A\\x98\\xA7\\v\\0A\\xA9\\xA7\\v\\0A\\xB8\\xA7\\v \\0\\0\\0\\0\\0\\0\\0A\\xD0\\xA7\\v\\0A\\xDC\\xA7\\v\\0\\0\\0\\0A\\xC0\\xA9\\v\\0\\0A\\xD1\\xA9\\v\\0A\\xE0\\xA9\\v +\\0\\0\\0\\0\\0\\0\\x07\\0A\\xF8\\xA9\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xAB\\v\\0A\\xE8\\xAB\\v\\0A\\xF9\\xAB\\v\\0A\\x88\\xAC\\v +\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xAC\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xAD\\v\\0A\\xD0\\xAD\\v\\0A\\x90\\xAE\\v\\0A\\xA1\\xAE\\v\\0A\\xB0\\xAE\\v +\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xAE\\v\\0\\0\\0\\0A\\xD8\\xAE\\v\\0A\\xF8\\xAE\\v\\0A\\xD8\\xAF\\v\\0A\\xB8\\xB0\\v\\0A\\xC9\\xB0\\v\\0A\\xD8\\xB0\\v \\0\\0\\0\\0\\0\\x003\\0A\\xF0\\xB0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xB1\\v\\0A\\xA0\\xB2\\v\\0A\\xE0\\xB2\\v\\0A\\xF1\\xB2\\v\\0A\\x80\\xB3\\v +\\0\\0\\0\\0\\0\\0\\x07\\0A\\x98\\xB3\\v\\0\\0\\0\\0A\\xA8\\xB3\\v\\0A\\xC8\\xB3\\v\\0A\\xA8\\xB4\\v\\0A\\x88\\xB5\\v\\0\\0A\\x99\\xB5\\v\\0A\\xA8\\xB5\\v +\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xB5\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x90\\xB6\\v\\0A\\xD0\\xB6\\v\\0A\\xF0\\xB6\\v\\0A\\xB0\\xB7\\v\\0\\0\\0A\\xC1\\xB7\\v\\0A\\xD0\\xB7\\v \\0\\0\\0\\0\\0\\x007\\0A\\xE8\\xB7\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB8\\xB8\\v\\0A\\x98\\xB9\\v\\0A\\xD8\\xB9\\v\\0\\0A\\xE9\\xB9\\v\\0A\\xF8\\xB9\\v \\0\\0\\0\\0\\0\\0?\\0A\\x90\\xBA\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xBA\\v\\0A\\xA0\\xBB\\v\\0A\\xC0\\xBB\\v\\0A\\x80\\xBC\\v\\0\\0\\0A\\x91\\xBC\\v\\0A\\xA0\\xBC\\v +\\0\\0\\0\\0\\0\\0\\x07\\x07\\0A\\xB8\\xBC\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xBC\\v\\0A\\xC8\\xBD\\v\\0A\\xE8\\xBD\\v\\0A\\xA8\\xBE\\v\\0\\0A\\xB9\\xBE\\v\\0A\\xC8\\xBE\\v +\\0\\0\\0\\0\\0\\0\\xC3\\0A\\xE0\\xBE\\v\\0\\0\\0\\0A\\xF0\\xBE\\v\\0\\0\\0\\0A\\x90\\xBF\\v\\0A\\xD0\\xBF\\v\\0A\\xF0\\xBF\\v\\0A\\xD0\\xC0\\v\\0A\\xE1\\xC0\\v\\0A\\xF0\\xC0\\v +\\0\\0\\0\\0\\0\\x007\\0A\\x88\\xC1\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xC1\\v\\0A\\xB8\\xC2\\v\\0\\0\\0\\0A\\xF8\\xC2\\v\\0\\0A\\x89\\xC3\\v\\0A\\x98\\xC3\\v +\\0\\0\\0\\x07\\0\\0\\0\\x07\\0A\\xB0\\xC3\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xC4\\v\\0A\\xC0\\xC4\\v\\0A\\xE0\\xC4\\v\\0\\0\\0\\0A\\xA0\\xC5\\v\\0\\0\\0A\\xB1\\xC5\\v\\0A\\xC0\\xC5\\v +\\0\\0\\0\\x07\\0\\0\\0?\\0A\\xD8\\xC5\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xC6\\v\\0A\\xE8\\xC6\\v\\0A\\x88\\xC7\\v\\0\\0\\0\\0A\\xC8\\xC7\\v\\0\\0\\0A\\xD9\\xC7\\v\\0A\\xE8\\xC7\\v +\\0\\0\\0\\x07\\0\\0\\0\\xCB\\0A\\x80\\xC8\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xC8\\v\\0A\\x90\\xC9\\v\\0A\\xB0\\xC9\\v\\0\\0\\0\\0A\\xF0\\xC9\\v\\0\\0\\0A\\x81\\xCA\\v\\0A\\x90\\xCA\\v +\\0\\0\\0\\x07\\0\\0\\x007\\0A\\xA8\\xCA\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xCA\\v\\0A\\xF8\\xCA\\v\\0A\\xB8\\xCB\\v\\0A\\xD8\\xCB\\v\\0A\\x98\\xCC\\v\\0\\0A\\xA9\\xCC\\v\\0A\\xB8\\xCC\\v +\\0\\0\\0\\x07\\0\\0\\0\\xC7\\0A\\xD0\\xCC\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xCD\\v\\0A\\xA0\\xCD\\v\\0A\\xE0\\xCD\\v\\0A\\x80\\xCE\\v\\0A\\xC0\\xCE\\v\\0\\0A\\xD1\\xCE\\v\\0A\\xE0\\xCE\\v +\\0\\0\\0\\b\\0\\0\\0?\\0A\\xF8\\xCE\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xCF\\v\\0\\0\\0\\0A\\x88\\xD0\\v\\0A\\xA8\\xD0\\v\\0\\0\\0\\0A\\xE8\\xD0\\v\\0\\0\\0A\\xF9\\xD0\\v\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\xCF\\0A\\xA0\\xD1\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xD1\\v\\0\\0\\0\\0A\\xB0\\xD2\\v\\0A\\xD0\\xD2\\v\\0\\0\\0\\0A\\x90\\xD3\\v\\0\\0\\0A\\xA1\\xD3\\v\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\xFF\\0A\\xC8\\xD3\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\xD4\\v\\0\\0\\0\\0A\\xD8\\xD4\\v\\0A\\xF8\\xD4\\v\\0\\0\\0\\0A\\xB8\\xD5\\v\\0\\0\\0A\\xC9\\xD5\\v\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\x007\\x07\\0A\\xF0\\xD5\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xD6\\v\\0A\\xC0\\xD6\\v\\0A\\x80\\xD7\\v\\0A\\xA0\\xD7\\v\\0\\0\\0\\0A\\xE0\\xD7\\v\\0\\0A\\xF1\\xD7\\v\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\x007\\0A\\x98\\xD8\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xD8\\v\\0A\\xE8\\xD8\\v\\0A\\xA8\\xD9\\v\\0\\0\\0\\0A\\xC8\\xD9\\v\\0\\0\\0\\0A\\x88\\xDA\\v\\0\\0\\0A\\x99\\xDA\\v\\x07\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\xF3\\0A\\xC0\\xDA\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xDA\\v\\0A\\x90\\xDB\\v\\0A\\xB4\\xDB\\v\\0A\\xD0\\xDB\\v\\0A\\xF0\\xDB\\v\\0\\0\\0\\0A\\xB0\\xDC\\v\\0\\0A\\xC1\\xDC\\v\\x07\\b \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x007\\xF7\\x80\\0A\\xE8\\xDC\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\xDD\\v\\0\\0\\0\\0A\\xB8\\xDD\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xDD\\v\\0\\0\\0\\0A\\xF8\\xDD\\v\\0\\0\\0\\0A\\x98\\xDE\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xDE\\v +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\b +\\v\\f\\r\\0A\\x91\\xDF\\v\\0A\\xA0\\xDF\\v\\0\\0A\\xB0\\xDF\\v\\0\\0A\\xC0\\xDF\\v\\0\\0A\\xD0\\xDF\\v\\0\\0A\\xF0\\xDF\\v\\b\\0\\x07\\0A\\x80\\xE1\\v0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xE1\\v\\xD3@\\0\\0\\0A\\0\\0\\0B\\0\\0\\0C\\0\\0\\0\\0\\0\\0\\0\\xCE\\xE6T\\xBE\\xC9?\\xD0\\xBEy\\xBF\\xBD>>\\xBF\\xD7\\xB3]\\xBFqxs\\xBF\\xFD\\x98~\\xBFo\\xE8~?4:v?l)e?\\xDBJL?Qw,?B\\xC4?\\xAB\\xF4\\xB8>\\xD5(<>))/113//+++((\\f\\f\\f\\0\\0\\0\\0\\0\\0\\xE8\\xFF\\0\\xE6\\xFF\\0\\xE7\\xFF\\0\\xE5\\xFF\\0\\xF5\\xFF\\x07\\0\\xF1\\xFF\\x07\\0\\xED\\xFF\\x07\\0\\xF6\\xFF\\x07\\0\\xEE\\xFF\\x07\\0\\xEF\\xFF\\x07\\0\\xF0\\xFF\\x07\\0\\xFC\\xFF\\x07\\0\\xFD\\xFF\\x07\\0\\xFF\\xFF\\x07\\0\\xFE\\xFF\\x07\\0\\xF7\\xFF\\x07\\0\\xF8\\xFF\\x07\\0\\xFB\\xFF\\x07\\0\\xF9\\xFF\\x07\\0\\xE4\\xFF\\0\\xFA\\xFF\\x07\\0\\xE3\\xFF\\0\\xEF\\xFF\\0\\xF0\\xFF\\0\\xF5\\xFF\\0\\0\\xEE\\xFF\\0\\xF2\\xFF\\0\\0\\xF3\\xFF\\0\\0\\xF4\\xFF\\0\\0\\xF1\\xFF\\0\\0\\xF6\\x7F\\0\\0\\xF7\\x7F\\0\\0\\xF9?\\0\\0\\xF5?\\0\\0\\xF7?\\0\\0\\xF3?\\0\\0\\xF6?\\0\\0\\xF2?\\0\\0\\xF7\\0\\0\\xF5\\0\\0\\xF9\\0\\0\\xF7\\0\\0\\xF6\\0\\0\\xF9\\x07\\0\\0\\xF4\\0\\0\\xF8\\x07\\0\\0\\xF9\\0\\0\\xF7\\0\\0\\xF5\\0\\0\\xF8\\0\\0\\xF7\\0\\0\\xFA\\0\\0\\0\\xF8\\0\\0\\0\\xF6\\0\\0\\0y\\0\\0\\0:\\0\\0\\x008\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\f\\0\\0\\0\\x1B\\0\\0\\x009\\0\\0\\0;\\0\\0\\0x\\0\\0\\0z\\0\\0\\0\\xF7\\0\\0\\0\\xF9\\0\\0\\0\\xF6\\0\\0\\xF9\\0\\0\\xF4\\0\\0\\xF6\\0\\0\\xF8\\0\\0\\xF5\\x07\\0\\0\\xF4\\x07\\0\\0\\xF6\\x07\\0\\0\\xF7\\x07\\0\\0\\xF5\\0\\0\\xF8\\0\\0\\xF4\\0\\0\\xF6\\0\\0\\xF8\\0\\0\\xF8?\\0\\0\\xF4?\\0\\0\\xF0\\xFF\\0\\0\\xF4\\x7F\\0\\0\\xF6\\xFF\\0\\0\\xF5\\x7F\\0\\0\\xE2\\xFF\\0\\xD9\\xFF\\x07\\0\\xDA\\xFF\\x07\\0\\xDB\\xFF\\x07\\0\\xDC\\xFF\\x07\\0\\xDD\\xFF\\x07\\0\\xDE\\xFF\\x07\\0\\xD8\\xFF\\x07\\0\\xD2\\xFF\\x07\\0\\xD3\\xFF\\x07\\0\\xD4\\xFF\\x07\\0\\xD5\\xFF\\x07\\0\\xD6\\xFF\\x07\\0\\xF2\\xFF\\x07\\0\\xDF\\xFF\\x07\\0\\xE7\\xFF\\x07\\0\\xE8\\xFF\\x07\\0\\xE9\\xFF\\x07\\0\\xEA\\xFF\\x07\\0\\xEB\\xFF\\x07\\0\\xE6\\xFF\\x07\\0\\xE0\\xFF\\x07\\0\\xE1\\xFF\\x07\\0\\xE2\\xFF\\x07\\0\\xE3\\xFF\\x07\\0\\xE4\\xFF\\x07\\0\\xE5\\xFF\\x07\\0\\xD7\\xFF\\x07\\0\\xEC\\xFF\\x07\\0\\xF4\\xFF\\x07\\0\\xF3\\xFF\\x07\\0A\\xC0\\xE6\\v\\xA2\\xF8\\x07\\xF1\\xFD\\x07\\xF5h\\0\\xF0\\xF7\\x07\\xEC\\xF5\\x07\\xF1r\\0\\xF4t\\0\\0v\\0\\xEBl\\0\\xF6\\xFC\\x07\\xE1\\xF1\\x07\\xF0a\\0\\xF6\\xF2\\x07\\xEA\\xFB\\x07\\xF2i\\0\\xEDw\\0\\0o\\0\\xE6d\\0\\xE5g\\0\\0b\\0\\0\\0\\0\\0e\\0\\0m\\0\\xE9c\\0\\xE4k\\0\\0q\\0\\xE3p\\0\\xF3\\xFE\\x07\\xE7\\xF3\\x07\\xEF\\\`\\0\\xEE\\xF0\\x07\\xE2\\xFA\\x07\\xF3j\\0\\xE8u\\0\\0s\\0\\xF4n\\0\\xF7\\xF6\\x07\\xE0\\xF9\\x07\\xF2f\\0\\xF5\\xFF\\x07\\xF7\\xF4\\x07\\0A\\xF0\\xE7\\v\\xA2\\xF3o\\0\\xFD\\xEB\\0#\\0\\xEA\\0\\xF7\\xE8\\0\\xFA\\xF2\\0-\\0p\\0 \\0\\0+\\0n\\0(\\0\\xE9\\0\\xF9f\\0\\xF8\\0\\xE7\\0\\x1B\\0\\xF1\\0\\xF4k\\0\\xF5\\xEC\\0*\\0l\\0,\\0 +\\0'\\0g\\0\\0\\xF5\\0$\\0\\b\\0\\0 \\0\\0\\0\\x07\\0\\0\\v\\x000\\0\\xEF\\0\\0d\\0\\0\\f\\0)\\0\\xF3\\0/\\0\\xF0\\0\\xFCq\\0\\xF2\\xF4\\0!\\0\\xE6\\0\\xF7\\0h\\0\\xF8\\xEE\\0"\\0e\\x001\\0\\0&\\0\\xED\\0%\\0j\\0\\xFBr\\0\\xFEi\\0.\\0\\xF6\\0\\xFFm\\0\\xF6\\0A\\xA2\\xE9\\v\\xA0 \\0\\xEF\\0\\v\\0\\0\\xF0\\0\\xEB\\xE6\\xF2 +\\x005\\0\\xEF4\\x007\\0\\xE9\\xED\\xE7\\xF3\\xEE\\xED\\xFA\\xEC\\xF2\\xF9\\x07\\xF8\\x07\\xF8\\xF8\\b\\x008\\0\\xF66\\0u\\0\\xF1\\xEB\\xEC\\xF4\\0v\\0\\xF4\\x079\\0t\\0\\xEF\\xF3\\xF4\\xF6\\x07\\xE8\\xEA\\xFC\\xF2\\0\\xF1\\xFB\\xF5\\xF3\\x07\\xFC\\xEE\\0\\xF7\\xFE\\x7F\\xF0\\xF5\\x07\\xFD\\x7F\\xFB\\xFA?\\xFF\\xFF\\xF1\\0\\xF0\\xFC?\\xEA\\xEE\\xFB?\\xF6\\xFA\\xFC\\x7F\\xF2\\x07\\xF5\\xFE\\xFF\\xF4\\xF7\\x07\\xFB\\x7F\\xF7\\xF9\\xFA\\x7F\\0A\\xD0\\xEA\\v\\xA2\\x07\\0\\0\\xF6\\0\\0\\b\\0\\xEF\\0\\xEF\\xF3\\0\\xF8\\x07\\0\\0\\xED\\0\\0\\0\\xE2\\0\\xF0\\0p\\0\\xF0\\xEE\\xF1\\0\\xFA\\x07\\xEE\\0\\xE4\\0\\xF2\\xF6\\x07\\xEF\\xFD\\x07\\0\\0\\xF2\\0 \\0\\0\\xE5\\0\\xF4\\0\\xE8\\0\\xF4\\0\\0\\xE7\\0\\0\\0\\0k\\0\\xE3\\0i\\0\\xF3\\xEB\\0\\xE6\\0\\xF6n\\0j\\0\\xF4\\xEC\\xF0\\xF9\\xF5\\0\\xEC\\0\\xFB\\x07\\xEA\\0o\\0\\xF7\\xF9\\x07\\xF3\\xFF\\xE9\\0m\\0\\xF8l\\0h\\0\\xF5\\xEE\\xF2\\xF4\\x07\\xF7\\x07\\xF1\\xFE\\xED\\xF1\\xF5\\x07\\xFE\\x07\\xF5\\xFC\\x07\\0A\\x80\\xEC\\v\\xA2\\xFF\\xF7\\xF4\\x07\\xE8\\x07\\xF1\\xEE\\x07\\xF9\\x07\\xF8\\xFD\\xFD\\xF1\\x07\\xE8\\xE8\\xF0\\0\\xEC\\xEE\\xF2\\x07\\xFA\\xF4\\xEF\\xF2\\xE8\\0p\\0\\xEC\\0\\xF0\\xEA\\xF3\\x07\\xEB\\x07\\xEB\\xEA\\0\\0\\b\\0\\0\\xEE\\0\\xEF\\xED\\x07\\xF0\\xF2\\0s\\0\\v\\0\\0\\0 +\\0q\\0\\xF3\\0\\xE9\\x07\\xEF\\x07\\xEE\\xEF\\0\\0 \\0\\x1B\\0\\xEB\\0\\xE9\\xEC\\x07\\xF6\\x07\\xEB\\xF3\\xED\\0r\\0\\xE9\\0\\xF1\\xED\\xF7\\x07\\xF6\\xF0\\x07\\xE9\\xED\\xF1\\0\\xEA\\xEC\\xF8\\x07\\xF9\\xFC\\xFC\\xF5\\xEA\\x07\\xF3\\xF2\\xF5\\x07\\xFB\\xFE\\0A\\xB0\\xED\\v\\xA2\\xFE\\x07\\xFD\\xF1\\xEB\\xF4\\xEA\\xF0\\xFC\\xFD\\x07\\xF6\\xE5\\xEA\\0l\\0q\\0h\\0\\xF0\\0\\xE6\\xF7\\xF3\\xEF\\x002\\0'\\0(\\0&\\x001\\0\\xEB\\0\\xF7\\xE8o\\0.\\0\\b\\0\\0\\0)\\0k\\0\\xEE\\xEFr\\0-\\0\\0\\0\\0\\0/\\0s\\0\\xFA\\xE7n\\0+\\0\\x07\\0\\0\\0,\\0m\\0\\xEC\\xF9\\xEE\\x000\\0$\\0*\\0%\\x003\\0\\xEC\\0\\xF2\\xF8\\xE4\\xED\\0j\\0p\\0i\\0t\\0\\xF1\\0\\xFA\\xFF\\x07\\xF9\\xF6\\xED\\xF8\\xE9\\xF5\\xFB\\xFC\\x07\\0A\\xE2\\xEE\\v\\xD0\\x007\\0t\\0\\xF2\\0\\xEB\\xED\\xF7\\x07\\0\\f\\x005\\0q\\0\\xEC\\0\\xEE\\0\\xEE\\xF56\\x004\\0r\\0\\xEA\\0\\xF1\\0\\xE9\\xF3\\xF5s\\0p\\0\\xEB\\0\\xF0\\0\\xF1\\xF0\\xEC\\xFA\\xF3\\0\\xED\\0\\xE8\\xEF\\xEF\\xF1\\xF9\\xFB\\x07\\xED\\xEF\\0\\xEA\\xF2\\xF3\\xF8\\xF9\\x07\\xFC\\x07\\xEE\\xEC\\xF4\\xF4\\xF7\\xF8\\x07\\xFD\\xFE\\xF6\\x07\\xF0\\xF2\\xF6\\xFA\\x07\\xFD\\x07\\xFC\\xFF\\0\\0\\x000\\0o\\0\\xF1\\0\\xFA\\xFE\\0\\0\\0\\0\\0,\\0j\\0u\\0\\xF8\\0\\0\\0\\0\\0.\\0i\\0r\\0\\xF5\\0/\\0\\0\\0*\\x002\\0l\\0\\xEC\\0\\xFA\\0q\\0+\\0-\\x001\\0m\\0p\\0\\xF2\\0\\xF9\\xEF\\0h\\x003\\0k\\0n\\0\\xEE\\0\\xF9\\0\\xFC\\xF8t\\0s\\0\\xED\\0\\xF0\\0\\xF6\\0\\xF6\\xFD\\xFD\\xF3\\0\\xF4\\0\\xF7\\0\\xF7\\xFB\\xFC\\xFF\\0\\0\\x007\\0\\xE7\\0\\xDE\\xCE\\xD9\\xC8\\x07\\xCD\\x07\\xC8\\xDD\\xE4\\xEC\\0\\f\\x005\\0r\\0\\xEA\\0\\xED\\0\\xE2\\xD1\\xD3\\xE0\\xD8\\x07\\xCF\\xD56\\x004\\0q\\0\\xE8\\0\\xEC\\0\\xE1\\xCF\\xDD\\xDB\\xD0\\x07\\xC7\\xD4\\xE4\\xE6\\0p\\0\\xE9\\0\\xDD\\xE3\\xD2\\xDC\\xCC\\x07\\xCA\\x07\\xDE\\x07\\xD8\\xEA\\xDB\\xDF\\xEB\\0\\xDC\\xE6\\xD5\\xDE\\xCB\\x07\\xDD\\x07\\xDC\\x07\\xCD\\xE2\\xE7\\xE1\\xD0\\xE0\\xE4\\xD6\\xC5\\x07\\xD1\\x07\\xDB\\x07\\xD2\\xE0\\x07\\xD9\\xEB\\xE3\\xE9\\xC4\\x07\\xE5\\xD7\\xC6\\x07\\xCF\\x07\\xDA\\x07\\xCB\\xDA\\xE3\\xE9\\xE6\\xF3\\xF7\\xD3\\x07\\xD8\\xE1\\xD4\\x07\\xD9\\x07\\xD3\\xDE\\xDD\\xD9\\xE2\\xEA\\xF1\\xF6\\xD2\\x07\\xD4\\xDA\\xC7\\x07\\xD7\\x07\\xE2\\x07\\xCE\\xDB\\xD8\\xEE\\xF0?\\xF4\\xF2?\\xE1\\x07\\xDF\\xC9\\x07\\xD6\\x07\\xCA\\xD0\\xE5\\xE6\\xEB\\xEF\\xF3?\\xF4?\\xF5?\\xE0\\xCE\\x07\\xD5\\x07\\xC6\\xD1\\xE1\\xE0\\xE8\\xF0\\xF1?\\xF8?\\xF6?\\xFC\\x7F\\xE8\\xDF\\x07\\xC9\\xD7\\xDC\\xDC\\xDF\\xED\\xF5\\xF9?\\xFB?\\xFD\\x7F\\xFE\\x7F\\xE7\\xCC\\xD6\\xDF\\xDE\\xDA\\xE5\\xF2\\xFA?\\xF7?\\xFC?\\xFD?\\xFF\\x7F\\0A\\xC0\\xF3\\v\\xD2"\\0\\b\\0\\0&\\0_\\0\\xD3\\0\\xCF\\xD0\\xD7\\xED\\xF0\\x07\\xF6\\x07\\xFD\\x07\\0\\0\\0\\0 \\0 \\0T\\0\\\`\\0\\xD5\\0\\xDC\\0\\xD4\\xCD\\xDE\\xE7\\x07\\0\\0\\0\\f\\0\\0(\\0[\\0\\xCD\\0\\xD9\\0\\xCE\\xDC\\xD9\\xF1%\\0\\v\\0 +\\0\\r\\0$\\0W\\0a\\0\\xCC\\0\\xDD\\0\\xCC\\xDE\\xD3\\xE7]\\0!\\0\\0#\\0'\\0Y\\0d\\0\\xD8\\0\\xDF\\0\\xD2\\xE2\\xDD\\xEE\\xD1\\0U\\0)\\0V\\0X\\0b\\0\\xCE\\0\\xE0\\0\\xE2\\0\\xDA\\xD4\\xE3\\xEB\\x07\\xC9^\\0Z\\0\\\\\\0c\\0\\xCA\\0\\xDA\\0\\xC7\\xCA\\xE0\\xDB\\xE8\\xEC\\x07\\xE3\\xD2\\0\\xCB\\0\\xD0\\0\\xD7\\0\\xDB\\0\\xC6\\xD5\\xD8\\xCA\\xDA\\xEA\\x07\\xF1\\x07\\xE1\\xD4\\0\\xCF\\0\\xD6\\0\\xDE\\0\\xE1\\0\\xD0\\xD6\\xD1\\xD5\\xF2\\xEE\\x07\\xFB\\x07\\xE9\\xCD\\xC8\\xCB\\xD1\\xD7\\xDF\\xCF\\xE0\\xEF\\xE6\\x07\\xF8\\x07\\xFA\\xEB\\xDD\\xD3\\xD9\\xDB\\xD2\\xCC\\xDC\\xEA\\xED\\x07\\xF3\\x07\\xF9\\x07\\xF9\\xF2\\x07\\xCE\\xE4\\xCB\\xD8\\xD6\\xE2\\xE5\\xE8\\x07\\xF4\\x07\\xF5\\x07\\xF7\\x07\\xFB\\xFA\\x07\\xEC\\xDF\\xE1\\xE4\\xE6\\xF0\\xE9\\x07\\xEF\\x07\\xF8\\xFE\\xFC\\xFF\\0A\\xA2\\xF6\\v\\xBF\\0\\0=\\0\\x9C\\0\\xC6\\0\\xA7\\x90\\xC2\\xDF\\xE6\\x07\\xF3\\x07\\xFB\\xEC\\x07\\xFA\\xFE\\x8E\\0\\0\\b\\0\\x007\\0B\\0\\x92\\0\\xAF\\0\\x91\\xA5\\xB5\\x9E\\xC0\\xA2\\xCD\\xD6\\x07\\xAE\\0\\0\\x07\\0 \\0\\x009\\0@\\0\\x8E\\0\\xA3\\0\\xB8\\0\\x99\\xAC\\xC1\\xB1\\x96\\xBE\\xCA\\x9D\\0<\\0\\0\\0\\0;\\0D\\0\\x91\\0\\xA5\\0\\xBE\\0\\x96\\xAE\\xB9\\xA1\\x91\\xA5\\xD5\\x94\\0\\x9A\\x006\\x008\\0:\\0A\\0\\x8C\\0\\x9B\\0\\xB0\\0\\xC3\\0\\x9E\\xAB\\xBC\\x9F\\x8F\\xA9\\xCF\\x93\\0\\xBF\\0>\\0?\\0C\\0E\\0\\x9E\\0\\xA7\\0\\xB9\\0\\x94\\xA2\\xBA\\xC3\\xA6\\xA7\\xBB\\xD4\\x9F\\0\\xA0\\x8F\\0\\x8D\\0\\x90\\0\\x98\\0\\xA6\\0\\xB6\\0\\xC4\\0\\x9F\\xAF\\xBF\\x99\\xBF\\xB4\\xC9\\xE7\\xA8\\0\\xB6\\xAB\\0\\xA4\\0\\xAA\\0\\xB2\\0\\xC2\\0\\xC5\\0\\x98\\xA4\\xB8\\x8C\\xA4\\xC4\\xC6\\xDD\\xE8\\xAD\\0\\xAF\\x92\\xBD\\0\\xBC\\0\\x8E\\x97\\x9A\\xA3\\xB1\\x8D\\x98\\xB7\\xD3\\xD1\\xDB\\xDD\\x07\\xB4\\0\\xDE\\xA9\\x9B\\x9C\\xA1\\xAA\\xAD\\xB3\\x8B\\xB2\\xB8\\xCE\\xE1\\xE0\\xD2\\x07\\xE5\\x07\\xB7\\0\\xE3\\x07\\xBB\\xA8\\xA6\\xB0\\xB2\\xB7\\x9B\\x9A\\xBA\\xB5\\xD6\\xD7\\x07\\xE4\\xD8\\x07\\xEA\\x07\\xBA\\0\\xE8\\x07\\xA0\\xBD\\xB4\\x8A\\xC4\\x92\\xAA\\xB0\\xBC\\xD7\\xD4\\x07\\xDC\\x07\\xDB\\x07\\xD5\\x07\\xF0\\x07\\xC1\\0\\xFB\\x07\\xC8\\xA3\\x95\\x9D\\xAC\\xAE\\xC5\\xD8\\xE2\\xE6\\xE4\\x07\\xE7\\x07\\xE0\\x07\\xE9\\x07\\xF7\\x07\\x90\\xF2\\x07\\x93\\xBE\\xC0\\x94\\x97\\xAD\\xC3\\xC1\\xD2\\xDA\\x07\\xD9\\x07\\xDF\\x07\\xEB\\x07\\xF4\\x07\\xFA\\x07\\x95\\xF8\\x07\\xBD\\x9C\\xAB\\xA8\\xB3\\xB9\\xD0\\xE3\\xE5\\xE2\\x07\\xDE\\x07\\xED\\x07\\xF1\\x07\\xF9\\x07\\xFC\\x07\\x93\\xFD\\xDC\\xB6\\xC7\\xCC\\xCB\\xD9\\xDA\\xD3\\x07\\xE1\\x07\\xEE\\x07\\xEF\\x07\\xF5\\x07\\xF6\\x07\\xFC\\xFF\\x9D\\xC2\\xB5\\0\\xA1\\0\\x96\\0\\x97\\0\\x95\\0\\x99\\0\\xA0\\0\\xA2\\0\\xAC\\0\\xA9\\0\\xB1\\0\\xB3\\0\\xBB\\0\\xC0\\0\\x8F\\0A\\xF0\\xFA\\vQ\\v \\v +\\x07 +\\v \\v +\\x07 +\\x07\\x07 \\x07 +\\v \\v \\x07 \\v \\v \\x07 \\x07\\x07 \\x07 \\x07\\x07\\x07\\x07 \\x07 \\x07\\x07 \\x07 \\v \\v \\x07 \\v \\v +\\x07 \\x07\\x07 \\x07 +\\v \\v +\\x07 \\v \\v\\0A\\xD0\\xFB\\vQ \\x07 \\b\\b \\b \\b\\x07\\x07\\b \\x07\\b\\b\\b \\x07 \\b\\x07\\x07\\b\\b\\x07\\b\\b \\x07 \\b\\b\\b\\x07 \\b\\x07\\b\\x07 \\x07 \\x07\\b \\x07 \\0A\\xB0\\xFC\\vQ\\b\\b + + +\\r \\v\\v +\\f +\\x07 + + +\\f\\x07\\v\\x07 + \\v +\\r\\b \\f +\\v\\f\\b + \\v\\r\\b + +\\f\\f\\v\\f +\\v\\f\\f\\0A\\x90\\xFD\\vQ\\b\\b \\b\\v\\b\\b\\b\\x07 + \\b\\v\\b\\b +\\v +\\v\\b\\b\\b\\b +\\b\\x07\\b\\x07 \\b\\b +\\x07\\x07 + +\\b\\b\\v\\b\\x07 +\\v +\\f\\b\\x07 +\\x07\\x07 + \\v\\v +\\f + \\v\\v +\\v\\0A\\xF0\\xFD\\vQ\\r\\f\\v\\v +\\v\\v\\f\\r\\f\\v + \\b +\\v\\f\\f + \\b\\x07\\b +\\v\\v \\b\\b \\v +\\b\\x07\\x07\\b\\v\\v \\b\\b \\v\\v + \\b\\x07\\b +\\v\\f\\v + \\b +\\v\\f\\r\\f\\f\\v + +\\v\\f\\r\\0A\\xD0\\xFE\\vQ\\v + +\\v + \\b\\x07\\x07\\x07\\b + \\b\\b \\x07\\x07 \\x07\\x07 \\x07\\x07 \\b\\b + \\b\\x07\\x07\\x07\\x07\\b +\\v + +\\v\\0A\\xB0\\xFF\\v\\x81\\x07\\b +\\v\\x07\\b\\b \\x07\\b\\b +\\x07\\x07\\b\\b + +\\b\\b + + +\\v \\b + +\\v\\v + + +\\v\\f\\f\\v + + +\\v\\v\\f\\f\\x07\\b +\\x07\\x07\\b\\x07\\x07\\b\\x07\\b\\b\\x07\\x07\\x07\\b \\b\\x07\\x07\\x07\\b\\b + \\x07\\x07\\b\\b\\b +\\b\\b\\b +\\b + +\\v\\v\\f\\f\\r\\r\\x07\\b\\b + + +\\v\\f\\f\\x07\\b\\b + + +\\v\\f\\f\\f\\b\\x07\\b + +\\v\\v\\v\\f\\f\\r \\b + +\\v\\v\\v\\f\\f\\f\\r + +\\v\\v\\v\\f\\v\\f\\f\\r\\r\\v +\\v\\v\\v\\f\\f\\f\\f\\r\\r\\r\\v + +\\v\\v\\f\\f\\r\\r\\r\\r\\r\\r\\v + +\\v\\v\\v\\f\\f\\r\\r\\r\\v +\\v\\v\\f\\f\\f\\f\\r\\r\\f\\v\\v\\f\\f\\f\\r\\r\\r\\f\\v\\f\\f\\f\\r\\r\\r\\r\\r\\f\\f\\f\\r\\r\\r\\r\\0\\0\\0\\0\\0\\0\\0\\x07\\b + + +\\v\\v\\f\\x07\\x07\\b\\b + +\\v\\x07\\b\\b + +\\x07\\x07\\b\\b + +\\x07\\x07\\x07\\b\\b + +\\b\\x07\\x07\\x07\\x07\\b\\b\\b + +\\v \\x07\\x07\\x07\\x07\\b\\b + +\\v \\b\\b\\b\\b\\b + +\\v\\v \\b\\b\\b\\b\\b + + +\\v\\v + + + +\\v\\v\\f + + + + +\\v\\v\\v\\f\\v + + + + + +\\v\\v\\v\\v\\f\\v + + + + + +\\v\\v\\f\\f\\f\\f\\0\\0\\0\\0\\0\\0\\0\\x07\\b\\b + + +\\v\\v\\f\\v\\f\\f +\\x07\\x07\\b\\b + + + +\\v\\b\\x07\\x07\\b\\b\\b + + + +\\b\\x07\\x07\\x07\\b\\b\\b + + + +\\b\\b\\x07\\x07\\x07\\x07\\b\\b\\b\\b + + + +\\b\\b\\x07\\x07\\x07\\x07\\b\\b\\b + + + +\\b \\b\\b\\b\\b\\b\\b\\b + + + + +\\b \\b\\b\\b\\b\\b\\b + + + + + +\\b + \\b\\b + + + + + +\\v\\b + + + + + + +\\v\\v\\b\\v + + + + +\\v +\\v\\v\\b\\v + + + + + + +\\v\\v\\v\\v\\v\\b\\v + + + + + + + + + +\\v\\v\\v\\v\\v \\v + + + + + + +\\v\\v\\v\\v\\v\\v \\v + + + + + + + + +\\v\\v\\v\\v\\v\\v \\f + + + + + + +\\v\\v\\v\\v\\v\\v\\f\\f \\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b \\0A\\xC2\\x85\\vF\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0A\\x92\\x86\\vr\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0A\\x8E\\x87\\v\\x80\\xBF\\0\\0\\x80\\xBF\\0A\\xA2\\x87\\v\\x80\\xBF\\0A\\xAE\\x87\\vz\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0A\\xB2\\x88\\vZ\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0A\\x96\\x89\\v"\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0A\\xC6\\x89\\v&\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0A\\xF6\\x89\\v +\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0A\\x8A\\x8A\\v\\x80\\xBF\\0\\0\\x80\\xBF\\0A\\x9A\\x8A\\v\\x80\\xBF\\0A\\xAA\\x8A\\v\\x80\\xBF\\0\\0\\x80?\\0A\\xBE\\x8A\\v\\x80\\xBF\\0A\\xDE\\x8A\\v\\x80?\\0A\\xEA\\x8A\\v\\x80?\\0\\0\\x80\\xBF\\0A\\xFA\\x8A\\v\\x80?\\0A\\x8A\\x8B\\v"\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0A\\xB6\\x8B\\v"\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0A\\xE6\\x8B\\v&\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0A\\x96\\x8C\\vR\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0A\\xF2\\x8C\\vr\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0A\\xEE\\x8D\\v\\x80\\xBF\\0\\0\\x80?\\0A\\x82\\x8E\\v\\x80?\\0A\\x8E\\x8E\\vz\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0A\\x92\\x8F\\v>\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0A\\xEE\\x8F\\v\\x80?\\0A\\xFC\\x8F\\vE!@\\0A\\x8A\\x90\\v\\x80?\\0A\\x9A\\x90\\v\\x80?\\0\\0\\x80?\\0A\\xAA\\x90\\v\\x80?E!@\\0\\0\\0\\0\\0\\0\\0\\0E!@\\0A\\xC8\\x90\\v E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\0\\0E!@E!@\\0\\0\\0\\0\\0\\0\\x80?\\0A\\xF6\\x90\\v&\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0A\\xA6\\x91\\v&\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\0\\0\\0\\0\\x80?E!@\\0A\\xD6\\x91\\v"\\x80?E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?E!@E!@\\0\\0\\0\\0E!@\\0A\\x84\\x92\\v\\x80E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0E!@\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\x80?E!@\\0\\0\\0\\0E!@E!@\\0\\0\\0\\0\\0\\0\\0\\0E!@E!@\\0\\0\\x80?\\0\\0\\0\\0E!@E!@E!@\\0\\0\\x80?\\0A\\x92\\x93\\v\\x80?\\0A\\x9E\\x93\\vz\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0E!@E!@\\0\\0\\x80?\\0\\0\\x80?\\0A\\xA2\\x94\\v\\x86\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@E!@\\0\\0\\x80?E!@\\0A\\xB2\\x95\\v\\x82\\x80?E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?E!@\\0\\0\\x80?E!@E!@\\0\\0\\0\\0\\0\\0\\x80?E!@E!@\\0\\0\\x80?\\0\\0\\x80?E!@E!@E!@E!@\\0A\\xC0\\x96\\vE!@\\0A\\xCE\\x96\\v\\xD2\\x80?E!@\\0\\0\\0\\0\\0\\0\\0\\0E!@E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\0\\0\\0\\0\\x80?E!@E!@\\0\\0\\0\\0E!@\\0\\0\\0\\0E!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@\\0\\0\\0\\0E!@E!@E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\0\\0E!@E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\0\\0E!@\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\0\\0\\x80?E!@E!@\\0\\0\\x80?E!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@\\0\\0\\x80?E!@\\0\\0\\x80?E!@E!@E!@E!@\\0\\0\\0\\0\\0\\0\\0\\0E!@E!@\\0\\0\\0\\0\\0\\0\\x80?E!@E!@\\0\\0\\0\\0E!@E!@E!@\\0\\0\\x80?\\0\\0\\0\\0E!@E!@\\0\\0\\x80?\\0\\0\\x80?E!@E!@\\0\\0\\x80?E!@E!@E!@E!@\\0\\0\\0\\0E!@E!@E!@\\0\\0\\x80?E!@E!@E!@E!@\\xF5/\\xCB\\xC0\\xF5/\\xCB\\xC0\\xF5/\\xCB\\xC0\\xBAt\\x8A\\xC0\\xF5/\\xCB\\xC0E!\\xC0\\xF5/\\xCB\\xC0\\0\\0\\x80\\xBF\\xF5/\\xCB\\xC0\\0\\0\\0\\0\\xF5/\\xCB\\xC0\\0\\0\\x80?\\xF5/\\xCB\\xC0E!@\\xF5/\\xCB\\xC0\\xBAt\\x8A@\\xF5/\\xCB\\xC0\\xF5/\\xCB@\\xBAt\\x8A\\xC0\\xF5/\\xCB\\xC0\\xBAt\\x8A\\xC0\\xBAt\\x8A\\xC0\\xBAt\\x8A\\xC0E!\\xC0\\xBAt\\x8A\\xC0\\0\\0\\x80\\xBF\\xBAt\\x8A\\xC0\\0\\0\\0\\0\\xBAt\\x8A\\xC0\\0\\0\\x80?\\xBAt\\x8A\\xC0E!@\\xBAt\\x8A\\xC0\\xBAt\\x8A@\\xBAt\\x8A\\xC0\\xF5/\\xCB@E!\\xC0\\xF5/\\xCB\\xC0E!\\xC0\\xBAt\\x8A\\xC0E!\\xC0E!\\xC0E!\\xC0\\0\\0\\x80\\xBFE!\\xC0\\0\\0\\0\\0E!\\xC0\\0\\0\\x80?E!\\xC0E!@E!\\xC0\\xBAt\\x8A@E!\\xC0\\xF5/\\xCB@\\0\\0\\x80\\xBF\\xF5/\\xCB\\xC0\\0\\0\\x80\\xBF\\xBAt\\x8A\\xC0\\0\\0\\x80\\xBFE!\\xC0\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\x80\\xBF\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\x80\\xBFE!@\\0\\0\\x80\\xBF\\xBAt\\x8A@\\0\\0\\x80\\xBF\\xF5/\\xCB@\\0\\0\\0\\0\\xF5/\\xCB\\xC0\\0\\0\\0\\0\\xBAt\\x8A\\xC0\\0\\0\\0\\0E!\\xC0\\0\\0\\0\\0\\0\\0\\x80\\xBF\\0A\\xAE\\x9C\\v\\xBA\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\x80?\\xF5/\\xCB\\xC0\\0\\0\\x80?\\xBAt\\x8A\\xC0\\0\\0\\x80?E!\\xC0\\0\\0\\x80?\\0\\0\\x80\\xBF\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\xBAt\\x8A@\\0\\0\\x80?\\xF5/\\xCB@E!@\\xF5/\\xCB\\xC0E!@\\xBAt\\x8A\\xC0E!@E!\\xC0E!@\\0\\0\\x80\\xBFE!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@E!@E!@\\xBAt\\x8A@E!@\\xF5/\\xCB@\\xBAt\\x8A@\\xF5/\\xCB\\xC0\\xBAt\\x8A@\\xBAt\\x8A\\xC0\\xBAt\\x8A@E!\\xC0\\xBAt\\x8A@\\0\\0\\x80\\xBF\\xBAt\\x8A@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\x80?\\xBAt\\x8A@E!@\\xBAt\\x8A@\\xBAt\\x8A@\\xBAt\\x8A@\\xF5/\\xCB@\\xF5/\\xCB@\\xF5/\\xCB\\xC0\\xF5/\\xCB@\\xBAt\\x8A\\xC0\\xF5/\\xCB@E!\\xC0\\xF5/\\xCB@\\0\\0\\x80\\xBF\\xF5/\\xCB@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\x80?\\xF5/\\xCB@E!@\\xF5/\\xCB@\\xBAt\\x8A@\\xF5/\\xCB@\\xF5/\\xCB@\\0A\\xFE\\x9E\\v\\xF2\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\0\\0\\x8Eq.A\\0\\0\\0\\0\\x90?VA\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\xBAt\\x8A@\\0\\0\\x80?\\xF5/\\xCB@\\0\\0\\x80?O\\xCC\\bA\\0\\0\\x80?\\x8Eq.A\\0\\0\\x80?\\x90?VAE!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@E!@E!@\\xBAt\\x8A@E!@\\xF5/\\xCB@E!@O\\xCC\\bAE!@\\x8Eq.AE!@\\x90?VA\\xBAt\\x8A@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\x80?\\xBAt\\x8A@E!@\\xBAt\\x8A@\\xBAt\\x8A@\\xBAt\\x8A@\\xF5/\\xCB@\\xBAt\\x8A@O\\xCC\\bA\\xBAt\\x8A@\\x8Eq.A\\xBAt\\x8A@\\x90?VA\\xF5/\\xCB@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\x80?\\xF5/\\xCB@E!@\\xF5/\\xCB@\\xBAt\\x8A@\\xF5/\\xCB@\\xF5/\\xCB@\\xF5/\\xCB@O\\xCC\\bA\\xF5/\\xCB@\\x8Eq.A\\xF5/\\xCB@\\x90?VAO\\xCC\\bA\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\x80?O\\xCC\\bAE!@O\\xCC\\bA\\xBAt\\x8A@O\\xCC\\bA\\xF5/\\xCB@O\\xCC\\bAO\\xCC\\bAO\\xCC\\bA\\x8Eq.AO\\xCC\\bA\\x90?VA\\x8Eq.A\\0\\0\\0\\0\\x8Eq.A\\0\\0\\x80?\\x8Eq.AE!@\\x8Eq.A\\xBAt\\x8A@\\x8Eq.A\\xF5/\\xCB@\\x8Eq.AO\\xCC\\bA\\x8Eq.A\\x8Eq.A\\x8Eq.A\\x90?VA\\x90?VA\\0\\0\\0\\0\\x90?VA\\0\\0\\x80?\\x90?VAE!@\\x90?VA\\xBAt\\x8A@\\x90?VA\\xF5/\\xCB@\\x90?VAO\\xCC\\bA\\x90?VA\\x8Eq.A\\x90?VA\\x90?VA\\0A\\xFE\\xA2\\v\\xBA +\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\0\\0\\x8Eq.A\\0\\0\\0\\0\\x90?VA\\0\\0\\0\\0\\0\\0\\x80A\\0\\0\\0\\0\\x1B\\xC4\\x95A\\0\\0\\0\\0\\xD3Z\\xACA\\0\\0\\0\\0\\xD3\\xB5\\xC3A\\0\\0\\0\\0\\xFF\\xC8\\xDBA\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\xBAt\\x8A@\\0\\0\\x80?\\xF5/\\xCB@\\0\\0\\x80?O\\xCC\\bA\\0\\0\\x80?\\x8Eq.A\\0\\0\\x80?\\x90?VA\\0\\0\\x80?\\0\\0\\x80A\\0\\0\\x80?\\x1B\\xC4\\x95A\\0\\0\\x80?\\xD3Z\\xACA\\0\\0\\x80?\\xD3\\xB5\\xC3A\\0\\0\\x80?\\xFF\\xC8\\xDBAE!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@E!@E!@\\xBAt\\x8A@E!@\\xF5/\\xCB@E!@O\\xCC\\bAE!@\\x8Eq.AE!@\\x90?VAE!@\\0\\0\\x80AE!@\\x1B\\xC4\\x95AE!@\\xD3Z\\xACAE!@\\xD3\\xB5\\xC3AE!@\\xFF\\xC8\\xDBA\\xBAt\\x8A@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\x80?\\xBAt\\x8A@E!@\\xBAt\\x8A@\\xBAt\\x8A@\\xBAt\\x8A@\\xF5/\\xCB@\\xBAt\\x8A@O\\xCC\\bA\\xBAt\\x8A@\\x8Eq.A\\xBAt\\x8A@\\x90?VA\\xBAt\\x8A@\\0\\0\\x80A\\xBAt\\x8A@\\x1B\\xC4\\x95A\\xBAt\\x8A@\\xD3Z\\xACA\\xBAt\\x8A@\\xD3\\xB5\\xC3A\\xBAt\\x8A@\\xFF\\xC8\\xDBA\\xF5/\\xCB@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\x80?\\xF5/\\xCB@E!@\\xF5/\\xCB@\\xBAt\\x8A@\\xF5/\\xCB@\\xF5/\\xCB@\\xF5/\\xCB@O\\xCC\\bA\\xF5/\\xCB@\\x8Eq.A\\xF5/\\xCB@\\x90?VA\\xF5/\\xCB@\\0\\0\\x80A\\xF5/\\xCB@\\x1B\\xC4\\x95A\\xF5/\\xCB@\\xD3Z\\xACA\\xF5/\\xCB@\\xD3\\xB5\\xC3A\\xF5/\\xCB@\\xFF\\xC8\\xDBAO\\xCC\\bA\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\x80?O\\xCC\\bAE!@O\\xCC\\bA\\xBAt\\x8A@O\\xCC\\bA\\xF5/\\xCB@O\\xCC\\bAO\\xCC\\bAO\\xCC\\bA\\x8Eq.AO\\xCC\\bA\\x90?VAO\\xCC\\bA\\0\\0\\x80AO\\xCC\\bA\\x1B\\xC4\\x95AO\\xCC\\bA\\xD3Z\\xACAO\\xCC\\bA\\xD3\\xB5\\xC3AO\\xCC\\bA\\xFF\\xC8\\xDBA\\x8Eq.A\\0\\0\\0\\0\\x8Eq.A\\0\\0\\x80?\\x8Eq.AE!@\\x8Eq.A\\xBAt\\x8A@\\x8Eq.A\\xF5/\\xCB@\\x8Eq.AO\\xCC\\bA\\x8Eq.A\\x8Eq.A\\x8Eq.A\\x90?VA\\x8Eq.A\\0\\0\\x80A\\x8Eq.A\\x1B\\xC4\\x95A\\x8Eq.A\\xD3Z\\xACA\\x8Eq.A\\xD3\\xB5\\xC3A\\x8Eq.A\\xFF\\xC8\\xDBA\\x90?VA\\0\\0\\0\\0\\x90?VA\\0\\0\\x80?\\x90?VAE!@\\x90?VA\\xBAt\\x8A@\\x90?VA\\xF5/\\xCB@\\x90?VAO\\xCC\\bA\\x90?VA\\x8Eq.A\\x90?VA\\x90?VA\\x90?VA\\0\\0\\x80A\\x90?VA\\x1B\\xC4\\x95A\\x90?VA\\xD3Z\\xACA\\x90?VA\\xD3\\xB5\\xC3A\\x90?VA\\xFF\\xC8\\xDBA\\0\\0\\x80A\\0\\0\\0\\0\\0\\0\\x80A\\0\\0\\x80?\\0\\0\\x80AE!@\\0\\0\\x80A\\xBAt\\x8A@\\0\\0\\x80A\\xF5/\\xCB@\\0\\0\\x80AO\\xCC\\bA\\0\\0\\x80A\\x8Eq.A\\0\\0\\x80A\\x90?VA\\0\\0\\x80A\\0\\0\\x80A\\0\\0\\x80A\\x1B\\xC4\\x95A\\0\\0\\x80A\\xD3Z\\xACA\\0\\0\\x80A\\xD3\\xB5\\xC3A\\0\\0\\x80A\\xFF\\xC8\\xDBA\\x1B\\xC4\\x95A\\0\\0\\0\\0\\x1B\\xC4\\x95A\\0\\0\\x80?\\x1B\\xC4\\x95AE!@\\x1B\\xC4\\x95A\\xBAt\\x8A@\\x1B\\xC4\\x95A\\xF5/\\xCB@\\x1B\\xC4\\x95AO\\xCC\\bA\\x1B\\xC4\\x95A\\x8Eq.A\\x1B\\xC4\\x95A\\x90?VA\\x1B\\xC4\\x95A\\0\\0\\x80A\\x1B\\xC4\\x95A\\x1B\\xC4\\x95A\\x1B\\xC4\\x95A\\xD3Z\\xACA\\x1B\\xC4\\x95A\\xD3\\xB5\\xC3A\\x1B\\xC4\\x95A\\xFF\\xC8\\xDBA\\xD3Z\\xACA\\0\\0\\0\\0\\xD3Z\\xACA\\0\\0\\x80?\\xD3Z\\xACAE!@\\xD3Z\\xACA\\xBAt\\x8A@\\xD3Z\\xACA\\xF5/\\xCB@\\xD3Z\\xACAO\\xCC\\bA\\xD3Z\\xACA\\x8Eq.A\\xD3Z\\xACA\\x90?VA\\xD3Z\\xACA\\0\\0\\x80A\\xD3Z\\xACA\\x1B\\xC4\\x95A\\xD3Z\\xACA\\xD3Z\\xACA\\xD3Z\\xACA\\xD3\\xB5\\xC3A\\xD3Z\\xACA\\xFF\\xC8\\xDBA\\xD3\\xB5\\xC3A\\0\\0\\0\\0\\xD3\\xB5\\xC3A\\0\\0\\x80?\\xD3\\xB5\\xC3AE!@\\xD3\\xB5\\xC3A\\xBAt\\x8A@\\xD3\\xB5\\xC3A\\xF5/\\xCB@\\xD3\\xB5\\xC3AO\\xCC\\bA\\xD3\\xB5\\xC3A\\x8Eq.A\\xD3\\xB5\\xC3A\\x90?VA\\xD3\\xB5\\xC3A\\0\\0\\x80A\\xD3\\xB5\\xC3A\\x1B\\xC4\\x95A\\xD3\\xB5\\xC3A\\xD3Z\\xACA\\xD3\\xB5\\xC3A\\xD3\\xB5\\xC3A\\xD3\\xB5\\xC3A\\xFF\\xC8\\xDBA\\xFF\\xC8\\xDBA\\0\\0\\0\\0\\xFF\\xC8\\xDBA\\0\\0\\x80?\\xFF\\xC8\\xDBAE!@\\xFF\\xC8\\xDBA\\xBAt\\x8A@\\xFF\\xC8\\xDBA\\xF5/\\xCB@\\xFF\\xC8\\xDBAO\\xCC\\bA\\xFF\\xC8\\xDBA\\x8Eq.A\\xFF\\xC8\\xDBA\\x90?VA\\xFF\\xC8\\xDBA\\0\\0\\x80A\\xFF\\xC8\\xDBA\\x1B\\xC4\\x95A\\xFF\\xC8\\xDBA\\xD3Z\\xACA\\xFF\\xC8\\xDBA\\xD3\\xB5\\xC3A\\xFF\\xC8\\xDBA\\xFF\\xC8\\xDBA\\0A\\xCE\\xAD\\v\\xFA\\x80?\\0\\0\\0\\0E!@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\0\\0\\x8Eq.A\\0\\0\\0\\0\\x90?VA\\0\\0\\0\\0\\0\\0\\x80A\\0\\0\\0\\0\\x1B\\xC4\\x95A\\0\\0\\0\\0\\xD3Z\\xACA\\0\\0\\0\\0\\xD3\\xB5\\xC3A\\0\\0\\0\\0\\xFF\\xC8\\xDBA\\0\\0\\0\\0\\xEF\\x89\\xF4A\\0\\0\\0\\0\\xCD\\xF7B\\0\\0\\0\\0\\xF9B\\0\\0\\0\\0\\0\\0\\x80B\\0\\0\\x80?\\0\\0\\0\\0\\0\\0\\x80?\\0\\0\\x80?\\0\\0\\x80?E!@\\0\\0\\x80?\\xBAt\\x8A@\\0\\0\\x80?\\xF5/\\xCB@\\0\\0\\x80?O\\xCC\\bA\\0\\0\\x80?\\x8Eq.A\\0\\0\\x80?\\x90?VA\\0\\0\\x80?\\0\\0\\x80A\\0\\0\\x80?\\x1B\\xC4\\x95A\\0\\0\\x80?\\xD3Z\\xACA\\0\\0\\x80?\\xD3\\xB5\\xC3A\\0\\0\\x80?\\xFF\\xC8\\xDBA\\0\\0\\x80?\\xEF\\x89\\xF4A\\0\\0\\x80?\\xCD\\xF7B\\0\\0\\x80?\\xF9B\\0\\0\\x80?\\0\\0\\x80BE!@\\0\\0\\0\\0E!@\\0\\0\\x80?E!@E!@E!@\\xBAt\\x8A@E!@\\xF5/\\xCB@E!@O\\xCC\\bAE!@\\x8Eq.AE!@\\x90?VAE!@\\0\\0\\x80AE!@\\x1B\\xC4\\x95AE!@\\xD3Z\\xACAE!@\\xD3\\xB5\\xC3AE!@\\xFF\\xC8\\xDBAE!@\\xEF\\x89\\xF4AE!@\\xCD\\xF7BE!@\\xF9BE!@\\0\\0\\x80B\\xBAt\\x8A@\\0\\0\\0\\0\\xBAt\\x8A@\\0\\0\\x80?\\xBAt\\x8A@E!@\\xBAt\\x8A@\\xBAt\\x8A@\\xBAt\\x8A@\\xF5/\\xCB@\\xBAt\\x8A@O\\xCC\\bA\\xBAt\\x8A@\\x8Eq.A\\xBAt\\x8A@\\x90?VA\\xBAt\\x8A@\\0\\0\\x80A\\xBAt\\x8A@\\x1B\\xC4\\x95A\\xBAt\\x8A@\\xD3Z\\xACA\\xBAt\\x8A@\\xD3\\xB5\\xC3A\\xBAt\\x8A@\\xFF\\xC8\\xDBA\\xBAt\\x8A@\\xEF\\x89\\xF4A\\xBAt\\x8A@\\xCD\\xF7B\\xBAt\\x8A@\\xF9B\\xBAt\\x8A@\\0\\0\\x80B\\xF5/\\xCB@\\0\\0\\0\\0\\xF5/\\xCB@\\0\\0\\x80?\\xF5/\\xCB@E!@\\xF5/\\xCB@\\xBAt\\x8A@\\xF5/\\xCB@\\xF5/\\xCB@\\xF5/\\xCB@O\\xCC\\bA\\xF5/\\xCB@\\x8Eq.A\\xF5/\\xCB@\\x90?VA\\xF5/\\xCB@\\0\\0\\x80A\\xF5/\\xCB@\\x1B\\xC4\\x95A\\xF5/\\xCB@\\xD3Z\\xACA\\xF5/\\xCB@\\xD3\\xB5\\xC3A\\xF5/\\xCB@\\xFF\\xC8\\xDBA\\xF5/\\xCB@\\xEF\\x89\\xF4A\\xF5/\\xCB@\\xCD\\xF7B\\xF5/\\xCB@\\xF9B\\xF5/\\xCB@\\0\\0\\x80BO\\xCC\\bA\\0\\0\\0\\0O\\xCC\\bA\\0\\0\\x80?O\\xCC\\bAE!@O\\xCC\\bA\\xBAt\\x8A@O\\xCC\\bA\\xF5/\\xCB@O\\xCC\\bAO\\xCC\\bAO\\xCC\\bA\\x8Eq.AO\\xCC\\bA\\x90?VAO\\xCC\\bA\\0\\0\\x80AO\\xCC\\bA\\x1B\\xC4\\x95AO\\xCC\\bA\\xD3Z\\xACAO\\xCC\\bA\\xD3\\xB5\\xC3AO\\xCC\\bA\\xFF\\xC8\\xDBAO\\xCC\\bA\\xEF\\x89\\xF4AO\\xCC\\bA\\xCD\\xF7BO\\xCC\\bA\\xF9BO\\xCC\\bA\\0\\0\\x80B\\x8Eq.A\\0\\0\\0\\0\\x8Eq.A\\0\\0\\x80?\\x8Eq.AE!@\\x8Eq.A\\xBAt\\x8A@\\x8Eq.A\\xF5/\\xCB@\\x8Eq.AO\\xCC\\bA\\x8Eq.A\\x8Eq.A\\x8Eq.A\\x90?VA\\x8Eq.A\\0\\0\\x80A\\x8Eq.A\\x1B\\xC4\\x95A\\x8Eq.A\\xD3Z\\xACA\\x8Eq.A\\xD3\\xB5\\xC3A\\x8Eq.A\\xFF\\xC8\\xDBA\\x8Eq.A\\xEF\\x89\\xF4A\\x8Eq.A\\xCD\\xF7B\\x8Eq.A\\xF9B\\x8Eq.A\\0\\0\\x80B\\x90?VA\\0\\0\\0\\0\\x90?VA\\0\\0\\x80?\\x90?VAE!@\\x90?VA\\xBAt\\x8A@\\x90?VA\\xF5/\\xCB@\\x90?VAO\\xCC\\bA\\x90?VA\\x8Eq.A\\x90?VA\\x90?VA\\x90?VA\\0\\0\\x80A\\x90?VA\\x1B\\xC4\\x95A\\x90?VA\\xD3Z\\xACA\\x90?VA\\xD3\\xB5\\xC3A\\x90?VA\\xFF\\xC8\\xDBA\\x90?VA\\xEF\\x89\\xF4A\\x90?VA\\xCD\\xF7B\\x90?VA\\xF9B\\x90?VA\\0\\0\\x80B\\0\\0\\x80A\\0\\0\\0\\0\\0\\0\\x80A\\0\\0\\x80?\\0\\0\\x80AE!@\\0\\0\\x80A\\xBAt\\x8A@\\0\\0\\x80A\\xF5/\\xCB@\\0\\0\\x80AO\\xCC\\bA\\0\\0\\x80A\\x8Eq.A\\0\\0\\x80A\\x90?VA\\0\\0\\x80A\\0\\0\\x80A\\0\\0\\x80A\\x1B\\xC4\\x95A\\0\\0\\x80A\\xD3Z\\xACA\\0\\0\\x80A\\xD3\\xB5\\xC3A\\0\\0\\x80A\\xFF\\xC8\\xDBA\\0\\0\\x80A\\xEF\\x89\\xF4A\\0\\0\\x80A\\xCD\\xF7B\\0\\0\\x80A\\xF9B\\0\\0\\x80A\\0\\0\\x80B\\x1B\\xC4\\x95A\\0\\0\\0\\0\\x1B\\xC4\\x95A\\0\\0\\x80?\\x1B\\xC4\\x95AE!@\\x1B\\xC4\\x95A\\xBAt\\x8A@\\x1B\\xC4\\x95A\\xF5/\\xCB@\\x1B\\xC4\\x95AO\\xCC\\bA\\x1B\\xC4\\x95A\\x8Eq.A\\x1B\\xC4\\x95A\\x90?VA\\x1B\\xC4\\x95A\\0\\0\\x80A\\x1B\\xC4\\x95A\\x1B\\xC4\\x95A\\x1B\\xC4\\x95A\\xD3Z\\xACA\\x1B\\xC4\\x95A\\xD3\\xB5\\xC3A\\x1B\\xC4\\x95A\\xFF\\xC8\\xDBA\\x1B\\xC4\\x95A\\xEF\\x89\\xF4A\\x1B\\xC4\\x95A\\xCD\\xF7B\\x1B\\xC4\\x95A\\xF9B\\x1B\\xC4\\x95A\\0\\0\\x80B\\xD3Z\\xACA\\0\\0\\0\\0\\xD3Z\\xACA\\0\\0\\x80?\\xD3Z\\xACAE!@\\xD3Z\\xACA\\xBAt\\x8A@\\xD3Z\\xACA\\xF5/\\xCB@\\xD3Z\\xACAO\\xCC\\bA\\xD3Z\\xACA\\x8Eq.A\\xD3Z\\xACA\\x90?VA\\xD3Z\\xACA\\0\\0\\x80A\\xD3Z\\xACA\\x1B\\xC4\\x95A\\xD3Z\\xACA\\xD3Z\\xACA\\xD3Z\\xACA\\xD3\\xB5\\xC3A\\xD3Z\\xACA\\xFF\\xC8\\xDBA\\xD3Z\\xACA\\xEF\\x89\\xF4A\\xD3Z\\xACA\\xCD\\xF7B\\xD3Z\\xACA\\xF9B\\xD3Z\\xACA\\0\\0\\x80B\\xD3\\xB5\\xC3A\\0\\0\\0\\0\\xD3\\xB5\\xC3A\\0\\0\\x80?\\xD3\\xB5\\xC3AE!@\\xD3\\xB5\\xC3A\\xBAt\\x8A@\\xD3\\xB5\\xC3A\\xF5/\\xCB@\\xD3\\xB5\\xC3AO\\xCC\\bA\\xD3\\xB5\\xC3A\\x8Eq.A\\xD3\\xB5\\xC3A\\x90?VA\\xD3\\xB5\\xC3A\\0\\0\\x80A\\xD3\\xB5\\xC3A\\x1B\\xC4\\x95A\\xD3\\xB5\\xC3A\\xD3Z\\xACA\\xD3\\xB5\\xC3A\\xD3\\xB5\\xC3A\\xD3\\xB5\\xC3A\\xFF\\xC8\\xDBA\\xD3\\xB5\\xC3A\\xEF\\x89\\xF4A\\xD3\\xB5\\xC3A\\xCD\\xF7B\\xD3\\xB5\\xC3A\\xF9B\\xD3\\xB5\\xC3A\\0\\0\\x80B\\xFF\\xC8\\xDBA\\0\\0\\0\\0\\xFF\\xC8\\xDBA\\0\\0\\x80?\\xFF\\xC8\\xDBAE!@\\xFF\\xC8\\xDBA\\xBAt\\x8A@\\xFF\\xC8\\xDBA\\xF5/\\xCB@\\xFF\\xC8\\xDBAO\\xCC\\bA\\xFF\\xC8\\xDBA\\x8Eq.A\\xFF\\xC8\\xDBA\\x90?VA\\xFF\\xC8\\xDBA\\0\\0\\x80A\\xFF\\xC8\\xDBA\\x1B\\xC4\\x95A\\xFF\\xC8\\xDBA\\xD3Z\\xACA\\xFF\\xC8\\xDBA\\xD3\\xB5\\xC3A\\xFF\\xC8\\xDBA\\xFF\\xC8\\xDBA\\xFF\\xC8\\xDBA\\xEF\\x89\\xF4A\\xFF\\xC8\\xDBA\\xCD\\xF7B\\xFF\\xC8\\xDBA\\xF9B\\xFF\\xC8\\xDBA\\0\\0\\x80B\\xEF\\x89\\xF4A\\0\\0\\0\\0\\xEF\\x89\\xF4A\\0\\0\\x80?\\xEF\\x89\\xF4AE!@\\xEF\\x89\\xF4A\\xBAt\\x8A@\\xEF\\x89\\xF4A\\xF5/\\xCB@\\xEF\\x89\\xF4AO\\xCC\\bA\\xEF\\x89\\xF4A\\x8Eq.A\\xEF\\x89\\xF4A\\x90?VA\\xEF\\x89\\xF4A\\0\\0\\x80A\\xEF\\x89\\xF4A\\x1B\\xC4\\x95A\\xEF\\x89\\xF4A\\xD3Z\\xACA\\xEF\\x89\\xF4A\\xD3\\xB5\\xC3A\\xEF\\x89\\xF4A\\xFF\\xC8\\xDBA\\xEF\\x89\\xF4A\\xEF\\x89\\xF4A\\xEF\\x89\\xF4A\\xCD\\xF7B\\xEF\\x89\\xF4A\\xF9B\\xEF\\x89\\xF4A\\0\\0\\x80B\\xCD\\xF7B\\0\\0\\0\\0\\xCD\\xF7B\\0\\0\\x80?\\xCD\\xF7BE!@\\xCD\\xF7B\\xBAt\\x8A@\\xCD\\xF7B\\xF5/\\xCB@\\xCD\\xF7BO\\xCC\\bA\\xCD\\xF7B\\x8Eq.A\\xCD\\xF7B\\x90?VA\\xCD\\xF7B\\0\\0\\x80A\\xCD\\xF7B\\x1B\\xC4\\x95A\\xCD\\xF7B\\xD3Z\\xACA\\xCD\\xF7B\\xD3\\xB5\\xC3A\\xCD\\xF7B\\xFF\\xC8\\xDBA\\xCD\\xF7B\\xEF\\x89\\xF4A\\xCD\\xF7B\\xCD\\xF7B\\xCD\\xF7B\\xF9B\\xCD\\xF7B\\0\\0\\x80B\\xF9B\\0\\0\\0\\0\\xF9B\\0\\0\\x80?\\xF9BE!@\\xF9B\\xBAt\\x8A@\\xF9B\\xF5/\\xCB@\\xF9BO\\xCC\\bA\\xF9B\\x8Eq.A\\xF9B\\x90?VA\\xF9B\\0\\0\\x80A\\xF9B\\x1B\\xC4\\x95A\\xF9B\\xD3Z\\xACA\\xF9B\\xD3\\xB5\\xC3A\\xF9B\\xFF\\xC8\\xDBA\\xF9B\\xEF\\x89\\xF4A\\xF9B\\xCD\\xF7B\\xF9B\\xF9B\\xF9B\\0\\0\\x80B\\0\\0\\x80B\\0\\0\\0\\0\\0\\0\\x80B\\0\\0\\x80?\\0\\0\\x80BE!@\\0\\0\\x80B\\xBAt\\x8A@\\0\\0\\x80B\\xF5/\\xCB@\\0\\0\\x80BO\\xCC\\bA\\0\\0\\x80B\\x8Eq.A\\0\\0\\x80B\\x90?VA\\0\\0\\x80B\\0\\0\\x80A\\0\\0\\x80B\\x1B\\xC4\\x95A\\0\\0\\x80B\\xD3Z\\xACA\\0\\0\\x80B\\xD3\\xB5\\xC3A\\0\\0\\x80B\\xFF\\xC8\\xDBA\\0\\0\\x80B\\xEF\\x89\\xF4A\\0\\0\\x80B\\xCD\\xF7B\\0\\0\\x80B\\xF9B\\0\\0\\x80B\\0\\0\\x80B\\0A\\xD2\\xBF\\vR\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0$\\0(\\0,\\x000\\x004\\x008\\0@\\0H\\0P\\0X\\0\\\`\\0l\\0x\\0\\x84\\0\\x90\\0\\x9C\\0\\xAC\\0\\xBC\\0\\xD4\\0\\xF0\\0@\\x80\\xC0\\0@\\x80\\xC0\\0@\\x80\\xC0\\0\\0A\\xB2\\xC0\\v\\xC2\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0$\\0(\\0,\\x000\\x004\\x008\\0@\\0H\\0P\\0X\\0d\\0p\\0|\\0\\x8C\\0\\x9C\\0\\xAC\\0\\xC0\\0\\xD8\\0\\xF0\\0\\f0X\\x80\\xA8\\xD0\\xF8 Hp\\x98\\xC0\\xE88\\\`\\x88\\xB0\\xD8\\0\\0\\0\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0$\\0(\\x000\\x008\\0@\\0H\\0P\\0X\\0\\\`\\0l\\0x\\0\\x84\\0\\x90\\0\\xA0\\0\\xB0\\0\\xC4\\0\\xD8\\0\\xF0\\0\\b$@\\\`\\x80\\xA0\\xC0\\xE0\\0 @\\\`\\x80\\xA0\\xC0\\xE0\\0 @\\\`\\x80\\xA0\\0\\0A\\x82\\xC2\\vf\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0$\\0(\\x000\\x008\\0@\\0H\\0P\\0X\\0\\\`\\0l\\0x\\0\\x84\\0\\x90\\0\\xA0\\0\\xB0\\0\\xC4\\0\\xD8\\0\\xF0\\0\\b$@\\\`\\x80\\xA0\\xC0\\xE0\\0 @\\\`\\x80\\xA0\\xC0\\xE0\\0 @\\\`\\x80\\xA0\\xC0\\xE0\\0\\0A\\xF2\\xC2\\v\\xB6\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0$\\0(\\0,\\x004\\0<\\0D\\0L\\0T\\0\\\\\\0d\\0l\\0t\\0|\\0\\x88\\0\\x94\\0\\xA0\\0\\xAC\\0\\xBC\\0\\xCC\\0\\xDC\\0\\xF0\\04Pl\\x8C\\xB0\\xD4\\xFC(X\\x8C\\xC0\\0@\\x80\\xC0\\0\\0\\0\\b\\0\\0\\0 \\0(\\x000\\x008\\0@\\0H\\0P\\0X\\0d\\0p\\0|\\0\\x88\\0\\x94\\0\\xA0\\0\\xAC\\0\\xB8\\0\\xC4\\0\\xD4\\0\\xE4\\0\\xF4\\0,@Xp\\x8C\\xA8\\xC8\\xEC<h\\x98\\xCC@\\x80\\xC0\\0\\0A\\xB2\\xC4\\vP\\f\\0\\0$\\x000\\0<\\0H\\0T\\0\\\`\\0l\\0x\\0\\x84\\0\\x90\\0\\x9C\\0\\xAC\\0\\xBC\\0\\xCC\\0\\xDC\\0\\xEC\\0\\xFC\\0\\f 4H\\\\t\\x8C\\xA4\\xC0\\xDC\\xFC Dl\\x98\\xC8\\xFC4p\\xB0\\0\\0A\\x90\\xC5\\v3\\xD0\\0\\xD0\\x000 \\0\\x90 \\0\\x90 \\0\\0!\\0p!\\0p!\\0\\xD0!\\0\\xD0!\\0\\xD0!\\x000"\\x000"\\0A\\xD2\\xC5\\v\\0\\b\\0\\f\\0\\0\\0\\0 \\0(\\x000\\0@\\0\\\\\\0\\x80\\0A\\xF2\\xC5\\v\\xD1\\0\\b\\0\\f\\0\\0\\0\\0$\\0,\\x008\\0D\\0P\\0\\\`\\0p\\0\\x80\\0\\0\\0\\0\\0\\0\\b\\0\\f\\0\\0\\0\\0\\0$\\0,\\x004\\0@\\0L\\0\\\\\\0l\\0\\x80\\0\\0\\0\\0\\b\\0\\f\\0\\0\\0\\0\\0 \\0(\\x000\\0<\\0H\\0X\\0l\\0\\x80\\0\\0\\0\\0\\b\\0\\f\\0\\0\\0\\0\\0$\\0,\\x004\\0<\\0H\\0X\\0l\\0\\x80\\0\\xD0"\\0\\xD0"\\0\\xD0"\\0\\xF0"\\0\\xF0"\\0\\xF0"\\0#\\0#\\x000#\\x000#\\x000#\\0P#\\0P#\\0"(*3..***'' +\\0\\0\\0\\0\\x80?\\xC3\\xAA\\x85?\\xC2\\x95\\x8B?\\xD3\\xC3\\x91?\\xF07\\x98?2\\xF5\\x9E?\\xD7\\xFE\\xA5??X\\xAD?\\xF3\\xB5?\\xA4\\b\\xBD?*g\\xC5?\\x8C$\\xCE?\\xFDD\\xD7?\\xDF\\xCC\\xE0?\\xC7\\xC0\\xEA?}%\\xF5?D\\0\\0\\0E\\0\\0\\0E\\0\\0\\0F\\0\\0\\0F\\0\\0\\0G\\0\\0\\0G\\0\\0\\0H\\0\\0\\0H\\0\\0\\0H\\0\\0\\0H\\0\\0\\0I\\0\\0\\0J\\0\\0\\0K\\0\\0\\0L\\0\\0\\0L\\0\\0\\0M\\0\\0\\0N\\0\\0\\0O\\0\\0\\0P\\0\\0\\0Q\\0\\0\\0R\\0\\0\\0S\\0\\0\\0T\\0\\0\\0U\\0\\0\\0V\\0\\0\\0W\\0\\0\\0X\\0\\0\\0N\\0\\0\\0O\\0\\0\\0P\\0\\0\\0Q\\0\\0\\0R\\0\\0\\0S\\0\\0\\0T\\0\\0\\0U\\0\\0\\0V\\0\\0\\0W\\0\\0\\0\\0\\x07\\x07\\f\\f\\0 \\b\\b\\r\\rp\\xFD\\0\\0\\xD0\\xFD\\0\\x000\\xFE\\0\\0\\x90\\xFE\\0\\0\\xF0\\xFE\\0\\0P\\xFF\\0\\0\\xB0\\xFF\\0\\0\\xF0\\xFF\\0\\x000\\0\\0\\xE0\\0\\0\\x90\\0\\0\\0\\0\\0\\xC0\\0\\xC0\\0\\xD0\\x07\\0\\xD0\\x07\\0\\xE0\\f\\0\\xE0\\f\\0p\\0p\\0p\\0p\\0\\xC0\\0\\0\\0\\0\\0@\\xF3\\0\\0\\xF0\\xF3\\0\\0\\xA0\\xF4\\0\\0P\\xF5\\0\\0\\0\\xF6\\0\\0\\xB0\\xF6\\0\\0\\\`\\xF7\\0\\0\\xE0\\xF7\\0\\0\\\`\\xF8\\0\\0\\xC0\\xF9\\0\\0 \\xFB\\0\\0\\0\\0\\0\\0\\r\\r\\f\\f\\f\\v\\f\\v + + + \\b\\b\\b\\x07\\x07\\x07\\b\\b + + +\\v\\v\\v\\v\\f\\f\\r\\r\\r\\0\\0\\0\\xF0%\\x000&\\0A\\xD1\\xCB\\v\\xB6\\x07\\b +\\v\\0\\f\\r\\0\\x07\\b +\\v\\r\\0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D\\0\\0\\0E\\0\\0\\0E\\0\\0\\0F\\0\\0\\0F\\0\\0\\0G\\0\\0\\0G\\0\\0\\0H\\0\\0\\0H\\0\\0\\0H\\0\\0\\0H\\0\\0\\0Y\\0\\0\\0J\\0\\0\\0K\\0\\0\\0L\\0\\0\\0L\\0\\0\\0\\x90&\\0\\xA0&\\0A\\x90\\xCD\\vu\\f\\r\\b + +\\f\\f\\f\\f\\f\\0\\x07\\x07\\x07 \\v\\b\\b\\b$\\b\\b\\b\\f\\f\\f\\b\\b\\b\\f\\f\\b\\b\\f\\f\\b\\b\\b\\b\\f\\0A\\x90\\xCE\\v3\\xBE&\\0\\xBE&\\0\\xBE&\\0\\xCA&\\0\\xCA&\\0\\xCA&\\0\\xD8&\\0\\xD8&\\0\\xE7&\\0\\xE7&\\0\\xE7&\\0\\xF6&\\0\\xF6&\\0A\\xD0\\xCE\\v\\x91\\b\\b\\b\\b\\b\\f\\f\\f\\f\\f$,@@@@@@@@@@@\\0\\0\\0\\0\\0\\0\\0\\b\\b\\b\\b\\f\\f\\f$((((((((((((((((((\\0\\b\\b\\b\\b\\b\\b\\b\\f\\f\\f\\f \\\`\\0A\\xF0\\xCF\\v3\\b\\b\\b\\b\\b\\b\\b\\f\\f\\f\\f \\0A\\xB0\\xD0\\v\\xC3\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\f\\f\\f\\f $$(,044@@@@@\\0\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\b\\f\\f\\f\\f\\f\\f\\f\\f\\f $((,048<@@@\\0\\0\\0\\0\\0\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f $$(,048<@P\\0\\0\\0\\0\\0\\0\\0\\0P'\\0P'\\0\\x80'\\0\\xB0'\\0\\xB0'\\0\\xF0'\\x000(\\x000(\\0\\\`(\\0\\\`(\\0\\\`(\\0\\x90(\\0\\x90(\\0A\\x80\\xD2\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xD2\\v#\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0Z\\0\\0\\0\\0\\0\\0\\0d<\\0A\\xCC\\xD2\\v\\b\\0A\\xD8\\xD2\\v;[\\0\\0\\0\\\\\\0\\0\\0\\0\\0\\0\\0k\\0\\0\\0\\0\\0\\0\\x84)\\0\\0\\0\\0]\\0\\0\\0^\\0\\0\\0_\\0\\0\\0\\\`\\0\\0\\0k\\0\\0a\\0\\0\\0\\0\\0\\0\\0d<\\0A\\xC0\\xD3\\v\\0\\0\\0\\0\\0\\0\\0\\xE02\\0\\0=5\\0\\0\\0A\\xDC\\xD3\\v-\\0\\0\\0\\0\\0\\0\\0\\xD52\\0\\x0005\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xAB\\0\\0\\0\\0\\0\\0\\0\\0\\xF7\\x94\\0\\0\\xFC\\x94\\0\\0\\0A\\x94\\xD4\\v\\0\\0\\0\\0\\0\\0\\0\\xD9\\x8E\\0\\0\\x88\\x92\\0\\0 +\\0A\\xB0\\xD4\\v\\0\\0\\0\\0\\0\\0\\0\\x97\\0\\0\\x88\\x98\\0\\0\\0A\\xCC\\xD4\\v\\0\\0\\0\\0\\0\\0\\0\\xDF\\x96\\0\\0\\xEE\\x97\\0\\0 +\\0A\\xE8\\xD4\\v-\\x07\\0\\0\\0\\0\\0\\0\\0\\xBAJ\\0\\0\\x84\\x85\\0\\0\\0\\0\\0\\xD0d\\0\\xC0\\xAC\\0\\b\\0\\0\\0\\0\\0\\0\\0r{\\0\\0\\xA6\\x87\\0\\0\\0A\\xA0\\xD5\\v \\0\\0\\0\\0\\0\\0\\0\\xC0J\\0\\0v\\x85\\0\\0\\0A\\xBC\\xD5\\v +\\0\\0\\0\\0\\0\\0\\0L\\0\\x007\\xA5\\0\\0\\0A\\xD8\\xD5\\v\\v\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\xD5\\x81\\0\\0\\x07\\0A\\xF4\\xD5\\v-\\f\\0\\0\\0\\0\\0\\0\\0\\xE1\\x8B\\0\\0\\xEE\\x91\\0\\0 +\\0\\0\\0\\0\\0\\0\\0P\\xAB\\0\\r\\0\\0\\0\\0\\0\\0\\0F2\\0\\0\\xEB2\\0\\0\\0A\\xAC\\xD6\\v\\0\\0\\0\\0\\0\\0\\0V\\x93\\0\\0\\x96\\0\\0\\0A\\xC8\\xD6\\v\\0\\0\\0\\0\\0\\0\\0t\\x8F\\0\\0^\\x92\\0\\0\\0A\\xE4\\xD6\\v\\0\\0\\0\\0\\0\\0\\0\\xEA\\x8D\\0\\0\\x8F\\0\\0\\0A\\x80\\xD7\\v\\0\\0\\0\\0\\0\\0\\0<\\x93\\0\\0\\xA2\\x8A\\0\\0\\0A\\x9C\\xD7\\v\\0\\0\\0\\0\\0\\0\\0V\\x8F\\0\\0O\\x8A\\0\\0\\0A\\xB8\\xD7\\v\\0\\0\\0\\0\\0\\0\\0p/\\0\\0\\x9C\\x92\\0\\0 +\\0A\\xD4\\xD7\\v\\0\\0\\0\\0\\0\\0\\0\\xCBF\\0\\0\\xDE\\x8E\\0\\0 +\\0A\\xF0\\xD7\\v\\0\\0\\0\\0\\0\\0\\0A\\x93\\0\\0e\\x9F\\0\\0\\0A\\x8C\\xD8\\v\\0\\0\\0\\0\\0\\0\\0\\x9D\\x93\\0\\0P\\x94\\0\\0\\0A\\xA8\\xD8\\v\\0\\0\\0\\0\\0\\0\\0\\x8E\\0\\0v\\x8E\\0\\0 +\\0A\\xC4\\xD8\\v\\0\\0\\0\\0\\0\\0\\0O2\\0\\0R\\x9F\\0\\0\\0A\\xE0\\xD8\\v\\0\\0\\0\\0\\0\\0\\0a\\0\\0\\xDE\\x80\\0\\0\\0A\\xFC\\xD8\\v\\0\\0\\0\\0\\0\\0\\0m\\0\\0K\\xA5\\0\\0\\0A\\x98\\xD9\\v-\\x1B\\0\\0\\0\\0\\0\\0\\0 +\\x8D\\0\\0S\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xAA\\0\\0\\0\\0\\0\\0\\0\\0\\x8E\\0\\0\\x8F\\0\\0\\0A\\xD0\\xD9\\v\\0\\0\\0\\0\\0\\0\\0 +\\x8E\\0\\0\\xAC\\x8E\\0\\0\\0A\\xEC\\xD9\\v\\0\\0\\0\\0\\0\\0\\0z~\\0\\0\\x81~\\0\\0\\0A\\x88\\xDA\\v\\0\\0\\0\\0\\0\\0\\x002\\x93\\0\\0&\\x94\\0\\0\\0A\\xA4\\xDA\\v \\0\\0\\0\\0\\0\\0\\0Q\\x8F\\0\\0\\x80\\x90\\0\\0\\0A\\xC0\\xDA\\v!\\0\\0\\0\\0\\0\\0\\0K\\x93\\0\\0\\x7F\\x95\\0\\0\\0A\\xDC\\xDA\\v"\\0\\0\\0\\0\\0\\0\\0D@\\0\\0)[\\0\\0\\0A\\xF8\\xDA\\v#\\0\\0\\0\\0\\0\\0\\0\\x8C\\x93\\0\\0.\\x94\\0\\0\\0A\\x94\\xDB\\v$\\0\\0\\0\\0\\0\\0\\0)\\0\\0F\\0\\0\\0A\\xB0\\xDB\\v%\\0\\0\\0\\0\\0\\0\\0 \\x85\\0\\0\\0A\\xD0\\xE0\\v-=\\0\\0\\0\\0\\0\\0\\0\\xF5H\\0\\0\\xDD\\\\\\0\\0\\0\\0\\0\\xD8d\\0\\0\\0\\0\\0>\\0\\0\\0\\0\\0\\0\\0n@\\0\\0>^\\0\\0\\0A\\x88\\xE1\\v?\\0\\0\\0\\0\\0\\0\\0nA\\0\\0\\xCD]\\0\\0\\0A\\xA4\\xE1\\v@\\0\\0\\0\\0\\0\\0\\0\\xE8@\\0\\0\\x9B]\\0\\0\\0A\\xC0\\xE1\\vA\\0\\0\\0\\0\\0\\0\\0Z\\0\\0\\x81^\\0\\0\\0A\\xDC\\xE1\\v-B\\0\\0\\0\\0\\0\\0\\0{A\\0\\0\\x7F]\\0\\0\\0\\0\\0\\xE0d\\0\\0\\0\\0\\0C\\0\\0\\0\\0\\0\\0\\0eK\\0\\0\\0\\0\\0A\\x94\\xE2\\vD\\0\\0\\0\\0\\0\\0\\0\\xC0\\x96\\0\\0\\xD4\\x97\\0\\0 +\\0A\\xB0\\xE2\\vE\\0\\0\\0\\0\\0\\0\\0\\xBB\\x96\\0\\0\\xC6\\x97\\0\\0 +\\0A\\xCC\\xE2\\vIF\\0\\0\\0\\0\\0\\0\\0\\xCA\\x93\\0\\0A\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE0\\xAB\\0G\\0\\0\\0\\0\\0\\0\\0\\xE5\\x8D\\0\\0v\\x89\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\xE0\\xAB\\0H\\0\\0\\0\\0\\0\\0\\0\\xCC7\\0\\0:\\x83\\0\\0\\0A\\xA0\\xE3\\vI\\0\\0\\0\\0\\0\\0\\x007\\x93\\0\\0\\xFE\\x93\\0\\0\\0A\\xBC\\xE3\\vJ\\0\\0\\0\\0\\0\\0\\0'i\\0\\0\\xCC\\x85\\0\\0\\0A\\xD8\\xE3\\vK\\0\\0\\0\\0\\0\\0\\0\\xEE\\x8F\\0\\0P\\x92\\0\\0\\0A\\xF4\\xE3\\vL\\0\\0\\0\\0\\0\\0\\0z\\0\\0\\x80\\0\\0\\0A\\x90\\xE4\\vM\\0\\0\\0\\0\\0\\0\\0\\xF5\\x8F\\0\\0\\xFC\\x97\\0\\0\\0A\\xAC\\xE4\\v-N\\0\\0\\0\\0\\0\\0\\0\\x9C.\\0\\0\\xCE\\x9E\\0\\0\\0\\0\\0\\xE8d\\0\\0\\0\\0\\0O\\0\\0\\0\\0\\0\\0\\0mh\\0\\0~0\\0\\0\\0A\\xE4\\xE4\\vP\\0\\0\\0\\0\\0\\0\\0\\x912\\0\\0\\x937\\0\\0\\0A\\x80\\xE5\\vQ\\0\\0\\0\\0\\0\\0\\0\\xC5\\0\\0<6\\0\\0\\0A\\x9C\\xE5\\vR\\0\\0\\0\\0\\0\\0\\0|\\x1B\\0\\0\\x825\\0\\0\\0A\\xB8\\xE5\\vS\\0\\0\\0\\0\\0\\0\\0\\xA32\\0\\0)3\\0\\0\\0A\\xD4\\xE5\\vT\\0\\0\\0\\0\\0\\0\\0w\\0\\0J\\x85\\0\\0\\0A\\xF0\\xE5\\vU\\0\\0\\0\\0\\0\\0\\0\\xECh\\0\\0\\xAAj\\0\\0\\0A\\x8C\\xE6\\vV\\0\\0\\0\\0\\0\\0\\0\\x82\\0\\0\\\`\\x93\\0\\0\\0A\\xA8\\xE6\\vW\\0\\0\\0\\0\\0\\0\\0{\\x1B\\0\\0\\xBC\\xA2\\0\\0 +\\0A\\xC4\\xE6\\v-X\\0\\0\\0\\0\\0\\0\\0\\x9D\\x97\\0\\0\\xA6\\x97\\0\\0\\x07\\0\\0\\0\\xF0d\\0\\xE0\\xAA\\0Y\\0\\0\\0\\0\\0\\0\\0ci\\0\\0\\xED6\\0\\0\\0A\\xFC\\xE6\\vZ\\0\\0\\0\\0\\0\\0\\0\\x8B\\0\\x007\\x8B\\0\\0\\0A\\x98\\xE7\\v[\\0\\0\\0\\0\\0\\0\\0\\xC4\\x8A\\0\\0\\xCE\\x8A\\0\\0\\0A\\xB4\\xE7\\v\\\\\\0\\0\\0\\0\\0\\0\\0\\xF7K\\0\\0\\x7F\\xA0\\0\\0\\0A\\xD0\\xE7\\v-]\\0\\0\\0\\0\\0\\0\\0\\xD1\\x7F\\0\\0\\\\\\0\\0\\0\\0\\0\\xF8d\\0\\0\\0\\0\\0^\\0\\0\\0\\0\\0\\0\\0\\x852\\0\\0D4\\0\\0\\0A\\x88\\xE8\\v_\\0\\0\\0\\0\\0\\0\\0u2\\0\\04\\0\\0\\0A\\xA4\\xE8\\vI\\\`\\0\\0\\0\\0\\0\\0\\0\\xA4K\\0\\0\\x87\\\\\\0\\0\\0\\0\\0e\\0\\0\\0\\0\\0a\\0\\0\\0\\0\\0\\0\\0PK\\0\\0\\xB4\\x9C\\0\\0\\0\\0\\0\\fe\\0\\0\\0\\0\\0b\\0\\0\\0\\0\\0\\0\\0\\x90{\\0\\0\\xB4\\x87\\0\\0\\0A\\xF8\\xE8\\v-c\\0\\0\\0\\0\\0\\0\\x006f\\0\\0K\\x86\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xC0\\xA9\\0d\\0\\0\\0\\0\\0\\0\\0\\xE6.\\0\\0(4\\0\\0\\0A\\xB0\\xE9\\ve\\0\\0\\0\\0\\0\\0\\0\\x9CF\\0\\0}\\\\\\0\\0\\0A\\xCC\\xE9\\vf\\0\\0\\0\\0\\0\\0\\0\\xC7\\x8E\\0\\0\\xCB\\x8E\\0\\0\\0A\\xE8\\xE9\\vg\\0\\0\\0\\0\\0\\0\\0f\\0\\0\\x075\\0\\0\\0A\\x84\\xEA\\vh\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\\\\\0\\0\\0A\\xA0\\xEA\\vi\\0\\0\\0\\0\\0\\0\\0\\xCAd\\0\\0\\x92\\\\\\0\\0\\0A\\xBC\\xEA\\vj\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0.\\xA1\\0\\0\\0A\\xD8\\xEA\\vk\\0\\0\\0\\0\\0\\0\\0\\xA5\\0\\0k7\\0\\0\\0A\\xF4\\xEA\\vl\\0\\0\\0\\0\\0\\0\\0:{\\0\\0a\\x87\\0\\0\\0A\\x90\\xEB\\v-m\\0\\0\\0\\0\\0\\0\\x005\\0\\0W\\\\\\0\\0\\0\\0\\0e\\0\\0\\0\\0\\0n\\0\\0\\0\\0\\0\\0\\0x\\0\\0\\xE0[\\0\\0\\0A\\xC8\\xEB\\vo\\0\\0\\0\\0\\0\\0\\0\\xB0\\x8B\\0\\0\\xC5\\x8D\\0\\0\\0A\\xE4\\xEB\\vp\\0\\0\\0\\0\\0\\0\\0 \\x8B\\0\\0j\\x8B\\0\\0\\0A\\x80\\xEC\\vq\\0\\0\\0\\0\\0\\0\\0\\xDBi\\0\\0\\xE1i\\0\\0\\0A\\x9C\\xEC\\vr\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0&5\\0\\0\\0A\\xB8\\xEC\\vs\\0\\0\\0\\0\\0\\0\\0a\\x8D\\0\\0k\\x8D\\0\\0\\0A\\xD4\\xEC\\vt\\0\\0\\0\\0\\0\\0\\0ol\\0\\0ul\\0\\0\\0A\\xF0\\xEC\\vu\\0\\0\\0\\0\\0\\0\\0\\xA0F\\0\\0\\xC7_\\0\\0\\0A\\x8C\\xED\\vv\\0\\0\\0\\0\\0\\0\\0\\x97\\0\\0n3\\0\\0\\0A\\xA8\\xED\\vw\\0\\0\\0\\0\\0\\0\\0\\r \\0\\03\\0\\0\\0A\\xC4\\xED\\vx\\0\\0\\0\\0\\0\\0\\0\\xB0\\0\\0\\xB83\\0\\0\\0A\\xE0\\xED\\vy\\0\\0\\0\\0\\0\\0\\0\\xE0-\\0\\0\\xF43\\0\\0\\0A\\xFC\\xED\\vz\\0\\0\\0\\0\\0\\0\\0\\\`F\\0\\0\\x994\\0\\0\\0A\\x98\\xEE\\v{\\0\\0\\0\\0\\0\\0\\0=~\\0\\0\\x88\\0\\0\\0A\\xB4\\xEE\\v|\\0\\0\\0\\0\\0\\0\\0Z\\x90\\0\\0\\x1B\\x93\\0\\0\\0A\\xD0\\xEE\\v}\\0\\0\\0\\0\\0\\0\\0[\\0\\0\\x89\\0\\0\\0A\\xEC\\xEE\\v~\\0\\0\\0\\0\\0\\0\\0\\x8E\\0\\0\\xE6\\x80\\0\\0\\0A\\x88\\xEF\\v\\x7F\\0\\0\\0\\0\\0\\0\\0+\\x97\\0\\0\\x89\\0\\0\\0A\\xA4\\xEF\\v\\x80\\0\\0\\0\\0\\0\\0\\0c\\0\\0\\\\^\\0\\0\\0A\\xC0\\xEF\\v\\x81\\0\\0\\0\\0\\0\\0\\0\\x9Fh\\0\\0\\xFD5\\0\\0\\0A\\xDC\\xEF\\v\\x82\\0\\0\\0\\0\\0\\0\\0\\xDD\\0\\0\\xF8f\\0\\0\\0A\\xF8\\xEF\\v\\x83\\0\\0\\0\\0\\0\\0\\0H\\x8F\\0\\0~\\x8F\\0\\0\\0A\\x94\\xF0\\v\\x84\\0\\0\\0\\0\\0\\0\\0/'\\0\\03\\0\\0\\0A\\xB0\\xF0\\v\\x85\\0\\0\\0\\0\\0\\0\\x000\\x97\\0\\0W\\0\\0\\0A\\xCC\\xF0\\v\\x86\\0\\0\\0\\0\\0\\0\\0\\xA6@\\0\\0\\xD7:\\0\\0\\0A\\xE8\\xF0\\v\\x87\\0\\0\\0\\0\\0\\0\\0\\x992\\0\\x0073\\0\\0\\0A\\x84\\xF1\\v\\x88\\0\\0\\0\\0\\0\\0\\0rA\\0\\0X\\x83\\0\\0\\0A\\xA0\\xF1\\v\\x89\\0\\0\\0\\0\\0\\0\\0F\\x93\\0\\07\\0\\0\\0A\\xBC\\xF1\\v\\x8A\\0\\0\\0\\0\\0\\0\\0.\\0\\0\\x7F7\\0\\0\\0A\\xD8\\xF1\\v\\x8B\\0\\0\\0\\0\\0\\0\\0\\xB4\\x89\\0\\0\\x8A\\0\\0\\0A\\xF4\\xF1\\v\\x8C\\0\\0\\0\\0\\0\\0\\0#(\\0\\0\\x81\\0\\0\\0A\\x90\\xF2\\v\\x8D\\0\\0\\0\\0\\0\\0\\0WF\\0\\0\\xAF\\0\\0\\0A\\xAC\\xF2\\v\\x8E\\0\\0\\0\\0\\0\\0\\0?F\\0\\0\\x8D\\0\\0\\0A\\xC8\\xF2\\v\\x8F\\0\\0\\0\\0\\0\\0\\0,\\x8B\\0\\0\\xE1\\xA0\\0\\0\\0A\\xE4\\xF2\\v\\x90\\0\\0\\0\\0\\0\\0\\x002F\\0\\0"l\\0\\0\\0A\\x80\\xF3\\v\\x91\\0\\0\\0\\0\\0\\0\\0\\x97J\\0\\0\\xCE4\\0\\0\\0A\\x9C\\xF3\\v\\x92\\0\\0\\0\\0\\0\\0\\0\\x07G\\0\\0\\x85\\0\\0\\0A\\xB8\\xF3\\v-\\x93\\0\\0\\0\\0\\0\\0\\0\\xFC!\\0\\0\\xEC\\x9E\\0\\0\\0\\0\\0\\0\\0\\0\\0\\\`\\xAC\\0\\x94\\0\\0\\0\\0\\0\\0\\0\\xAD\\0\\0\\x9F3\\0\\0\\0A\\xF0\\xF3\\v\\x95\\0\\0\\0\\0\\0\\0\\0\\xDF\\x7F\\0\\0=\\x88\\0\\0\\0A\\x8C\\xF4\\v\\x96\\0\\0\\0\\0\\0\\0\\0\\xBC[\\0\\0\\xB8^\\0\\0\\0A\\xA8\\xF4\\v\\x97\\0\\0\\0\\0\\0\\0\\0\\xC6[\\0\\0\\x94\\x8F\\0\\0\\0A\\xC4\\xF4\\v\\x98\\0\\0\\0\\0\\0\\0\\0W2\\0\\06\\0\\0\\0A\\xE0\\xF4\\v\\x99\\0\\0\\0\\0\\0\\0\\0\\xB72\\0\\0\\x883\\0\\0\\0A\\xFC\\xF4\\v\\x9A\\0\\0\\0\\0\\0\\0\\0V\\0\\0\\xECj\\0\\0\\0A\\x98\\xF5\\v\\x9B\\0\\0\\0\\0\\0\\0\\0\\xF5\\r\\0\\0\\xFC\\r\\0\\0\\0A\\xB4\\xF5\\v\\x9C\\0\\0\\0\\0\\0\\0\\0&\\x97\\0\\0o\\0\\0\\0A\\xD0\\xF5\\v-\\x9D\\0\\0\\0\\0\\0\\0\\0\\xCEd\\0\\x003]\\0\\0\\0\\0\\0e\\0\\0\\0\\0\\0\\x9E\\0\\0\\0\\0\\0\\0\\0C\\0\\0\\x844\\0\\0\\0A\\x88\\xF6\\v-\\x9F\\0\\0\\0\\0\\0\\0\\0jA\\0\\0\\xE9]\\0\\0\\0\\0\\0$e\\0\\0\\0\\0\\0\\xA0\\0\\0\\0\\0\\0\\0\\0Rj\\0\\0#6\\0\\0\\0A\\xC0\\xF6\\v\\xA1\\0\\0\\0\\0\\0\\0\\0v\\x93\\0\\0H\\x96\\0\\0\\0A\\xDC\\xF6\\v\\xA2\\0\\0\\0\\0\\0\\0\\0\\xD5\\x93\\0\\x005<\\0\\0\\0A\\xF8\\xF6\\v\\xA3\\0\\0\\0\\0\\0\\0\\0M\\x90\\0\\0\\xEC\\x92\\0\\0\\0A\\x94\\xF7\\v\\xA4\\0\\0\\0\\0\\0\\0\\0\\xB8\\x8F\\0\\0<\\0\\0\\0A\\xB0\\xF7\\v\\xA5\\0\\0\\0\\0\\0\\0\\0\\xB4i\\0\\0\\xD5j\\0\\0\\0A\\xCC\\xF7\\v\\xA6\\0\\0\\0\\0\\0\\0\\0\\xBD\\x8F\\0\\0C<\\0\\0\\0A\\xE8\\xF7\\v-\\xA7\\0\\0\\0\\0\\0\\0\\0H\\x89\\0\\0S\\x89\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xAC\\0\\xA8\\0\\0\\0\\0\\0\\0\\x007j\\0\\0Dk\\0\\0\\0A\\xA0\\xF8\\v\\xA9\\0\\0\\0\\0\\0\\0\\0\\xCA\\x96\\0\\0\\xD4\\x96\\0\\0\\0A\\xBC\\xF8\\v\\xAA\\0\\0\\0\\0\\0\\0\\0\\x9BB\\0\\0\\xF9H\\0\\0\\0A\\xD8\\xF8\\v-\\xAB\\0\\0\\0\\0\\0\\0\\0\\xF6.\\0\\0\\xE5\\x82\\0\\0\\x07\\0\\0\\0,e\\0\\0\\0\\0\\0\\xAC\\0\\0\\0\\0\\0\\0\\0\\xCB2\\0\\05\\0\\0\\0A\\x90\\xF9\\v-\\xAD\\0\\0\\0\\0\\0\\0\\0\\xF6h\\0\\0\\x1B\\xA2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\xAA\\0\\xAE\\0\\0\\0\\0\\0\\0\\x003j\\0\\0\\xEE\\x86\\0\\0\\0A\\xC8\\xF9\\v\\xAF\\0\\0\\0\\0\\0\\0\\0\\xB3\\0\\0+\\\\\\0\\0\\0A\\xE4\\xF9\\v\\xB0\\0\\0\\0\\0\\0\\0\\0\\xBD\\0\\0E\\\\\\0\\0\\0A\\x80\\xFA\\v\\xB1\\0\\0\\0\\0\\0\\0\\0\\xC12\\0\\0 7\\0\\0\\0A\\x9C\\xFA\\v\\xB2\\0\\0\\0\\0\\0\\0\\0\\xF2'\\0\\0o\\\\\\0\\0\\x07\\0A\\xB8\\xFA\\v\\xB3\\0\\0\\0\\0\\0\\0\\0e\\x8A\\0\\0\\x80\\x8A\\0\\0\\0A\\xD4\\xFA\\v\\xB4\\0\\0\\0\\0\\0\\0\\0\\xA5@\\0\\0\\xB34\\0\\0\\0A\\xF0\\xFA\\v\\xB5\\0\\0\\0\\0\\0\\0\\0]T\\0\\0\\xAE\\0\\0\\0A\\x8C\\xFB\\v\\xB6\\0\\0\\0\\0\\0\\0\\0\\xC9\\x93\\0\\0\\x95\\x95\\0\\0\\0A\\xA8\\xFB\\v\\xB7\\0\\0\\0\\0\\0\\0\\0%\\x90\\0\\0\\xFC\\x91\\0\\0\\0A\\xC4\\xFB\\v\\xB8\\0\\0\\0\\0\\0\\0\\0_\\0\\0T\\x80\\0\\0\\0A\\xE0\\xFB\\v\\xB9\\0\\0\\0\\0\\0\\0\\0i\\0\\0\\xA5\\x86\\0\\0\\0A\\xFC\\xFB\\v\\xBA\\0\\0\\0\\0\\0\\0\\0\\x94~\\0\\0\\x88\\0\\0\\0A\\x98\\xFC\\v\\xBB\\0\\0\\0\\0\\0\\0\\x009/\\0\\0=/\\0\\0\\0A\\xB4\\xFC\\v\\xBC\\0\\0\\0\\0\\0\\0\\0\\xF2&\\0\\0[+\\0\\0\\x07\\0A\\xD0\\xFC\\v\\xBD\\0\\0\\0\\0\\0\\0\\0V\\0\\0\\xD1\\x80\\0\\0\\0A\\xEC\\xFC\\v\\xBE\\0\\0\\0\\0\\0\\0\\0,0\\0\\x0090\\0\\0\\0A\\x88\\xFD\\v\\xBF\\0\\0\\0\\0\\0\\0\\0Ol\\0\\0]k\\0\\0\\0A\\xA4\\xFD\\v\\xC0\\0\\0\\0\\0\\0\\0\\0\\xB3\\x8F\\0\\0\\x80\\x8D\\0\\0\\0A\\xC0\\xFD\\v\\xC1\\0\\0\\0\\0\\0\\0\\0\\xFA\\0\\0\\x99\\x9C\\0\\0\\0A\\xDC\\xFD\\v\\xC2\\0\\0\\0\\0\\0\\0\\0\\xF4\\x8D\\0\\x005\\x97\\0\\0\\0A\\xF8\\xFD\\v\\xC3\\0\\0\\0\\0\\0\\0\\0\\xDB\\x8F\\0\\0\\xC2\\x92\\0\\0\\0A\\x94\\xFE\\v-\\xC4\\0\\0\\0\\0\\0\\0\\0\\xE8h\\0\\0k\\xA2\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\x80\\xAA\\0\\xC5\\0\\0\\0\\0\\0\\0\\0\\x90/\\0\\0\\x1B\\0\\0\\0A\\xCC\\xFE\\v\\xC6\\0\\0\\0\\0\\0\\0\\0.\\0\\0\\xB8*\\0\\0\\0A\\xE8\\xFE\\v\\xC7\\0\\0\\0\\0\\0\\0\\0\\xD8\\0\\0\\x89\\0\\0\\0A\\x84\\xFF\\v\\xC8\\0\\0\\0\\0\\0\\0\\0:F\\0\\0\\xDDf\\0\\0\\0A\\xA0\\xFF\\v\\xC9\\0\\0\\0\\0\\0\\0\\0\\v\\x8B\\0\\0\\xE6\\x8A\\0\\0\\0A\\xBC\\xFF\\v\\xCA\\0\\0\\0\\0\\0\\0\\0D\\x8A\\0\\0\\x9C\\x8C\\0\\0\\0A\\xD8\\xFF\\v\\xCB\\0\\0\\0\\0\\0\\0\\0?\\x8A\\0\\0r\\x8C\\0\\0\\0A\\xF4\\xFF\\v\\xCC\\0\\0\\0\\0\\0\\0\\0\\x8A\\x8B\\0\\0h\\x96\\0\\0\\0A\\x90\\x80\\v\\xCD\\0\\0\\0\\0\\0\\0\\08\\0\\0l\\0\\0\\0A\\xAC\\x80\\v\\xCE\\0\\0\\0\\0\\0\\0\\0\\xE1~\\0\\0\\xEC\\0A\\xC8\\x80\\v\\xCF\\0\\0\\0\\0\\0\\0\\0r\\\`\\0\\0\\xF5[\\0\\0\\0A\\xE4\\x80\\v\\xD0\\0\\0\\0\\0\\0\\0\\0x\\0\\0\\x83\\0\\0\\0A\\x80\\x81\\v\\xD1\\0\\0\\0\\0\\0\\0\\0\\xA7J\\0\\0\\x916\\0\\0\\0A\\x9C\\x81\\v-\\xD2\\0\\0\\0\\0\\0\\0\\0\\xEAH\\0\\0]\\0\\0\\0\\0\\x004e\\0\\0\\0\\0\\0\\xD3\\0\\0\\0\\0\\0\\0\\0\\xC9~\\0\\0\\xCF~\\0\\0\\0A\\xD4\\x81\\v\\xD4\\0\\0\\0\\0\\0\\0\\0Ef\\0\\0b\\x86\\0\\0\\0A\\xF0\\x81\\v\\xD5\\0\\0\\0\\0\\0\\0\\0\\xBE\\0\\0\\x8EQ\\0\\0\\0A\\x8C\\x82\\v\\xD6\\0\\0\\0\\0\\0\\0\\0 \\x95\\0\\0\\x86\\0\\0\\0A\\xA8\\x82\\v\\xD7\\0\\0\\0\\0\\0\\0\\0i\\0\\0_3\\0\\0\\0A\\xC4\\x82\\v\\xD8\\0\\0\\0\\0\\0\\0\\0_2\\0\\0\\xC35\\0\\0\\0A\\xE0\\x82\\v\\xD9\\0\\0\\0\\0\\0\\0\\0\\xB0i\\0\\0\\0k\\0\\0\\0A\\xFC\\x82\\v\\xDA\\0\\0\\0\\0\\0\\0\\0\\xF4d\\0\\0\\xFEU\\0\\0\\0A\\x98\\x83\\v\\xDB\\0\\0\\0\\0\\0\\0\\0\\xB7\\0\\0\\xBE\\0\\0\\0A\\xB4\\x83\\v\\xDC\\0\\0\\0\\0\\0\\0\\0\\xD8-\\0\\0\\xC6\\x82\\0\\0\\0A\\xD0\\x83\\v\\xDD\\0\\0\\0\\0\\0\\0\\0\\xF1h\\0\\0\\xE0k\\0\\0\\0A\\xEC\\x83\\v\\xDE\\0\\0\\0\\0\\0\\0\\0(\\0\\0\\x8C(\\0\\0\\0A\\x88\\x84\\v\\xDF\\0\\0\\0\\0\\0\\0\\0j2\\0\\0\\xD65\\0\\0\\0A\\xA4\\x84\\vI\\xE0\\0\\0\\0\\0\\0\\0\\0j@\\0\\0\\xB8]\\0\\0\\0\\0\\0 \\0\\0\\0A\\xB4\\x91\\v\\b\\0\\0\\0\\0\\0hY\\0\\0\\xD9?\\0\\0\\0A\\xD0\\x91\\v \\0\\0\\0\\0\\0'c\\0\\0Z>\\0\\0\\0A\\xEC\\x91\\v +\\0\\0\\0\\0\\0^Y\\0\\0\\xB7?\\0\\0\\0A\\x88\\x92\\v\\v\\0\\0\\0\\0\\0c\\0\\0;>\\0\\0\\0A\\xA4\\x92\\v\\f\\0\\0\\0\\0\\0Y\\0\\0\\x97?\\0\\0\\0A\\xC0\\x92\\v\\r\\0\\0\\0\\0\\0\\xCAb\\0\\0>\\0\\0\\0A\\xDC\\x92\\v\\0\\0\\0\\0\\0\\xF7X\\0\\0u?\\0\\0\\0A\\xF8\\x92\\v\\0\\0\\0\\0\\0\\xC0b\\0\\0\\xFF=\\0\\0\\0A\\x94\\x93\\v\\0\\0\\0\\0\\0\\xE3d\\0\\0\\xDC\\0\\0\\0A\\xB0\\x93\\v\\0\\0\\0\\0\\0,\\0\\0\\x9E,\\0\\0\\0A\\xCC\\x93\\v\\0\\0\\0\\0\\0\\xDBd\\0\\0\\xDF=\\0\\0\\0A\\xE8\\x93\\v\\0\\0\\0\\0\\0uc\\0\\0a=\\0\\0\\0A\\x84\\x94\\v\\0\\0\\0\\0\\0\\xB6Y\\0\\0\\x9F>\\0\\0\\0A\\xA0\\x94\\v\\0\\0\\0\\0\\0\\x92b\\0\\0<=\\0\\0\\0A\\xBC\\x94\\v\\0\\0\\0\\0\\0\\xC9X\\0\\0w>\\0\\0\\0A\\xD8\\x94\\v\\0\\0\\0\\0\\0S\\0\\0\\xE6~\\0\\0\\0A\\xF4\\x94\\v\\0\\0\\0\\0\\0\\xE9J\\0\\0-\\0\\0\\0A\\x90\\x95\\v\\0\\0\\0\\0\\0\\x9FB\\0\\0\\xF8\\x83\\0\\0\\0A\\xAC\\x95\\v\\x1B\\0\\0\\0\\0\\0T,\\0\\0b,\\0\\0\\0A\\xC8\\x95\\v\\0\\0\\0\\0\\0!,\\0\\0\\xC5,\\0\\0\\0A\\xE4\\x95\\v\\0\\0\\0\\0\\x002,\\0\\0\\xEC,\\0\\0\\0A\\x80\\x96\\v\\0\\0\\0\\0\\0C,\\0\\0z,\\0\\0\\0A\\x9C\\x96\\v\\0\\0\\0\\0\\0\\xBFX\\0\\0U?\\0\\0\\0A\\xB8\\x96\\v \\0\\0\\0\\0\\0\\x88b\\0\\0\\xC2=\\0\\0\\0A\\xD4\\x96\\v!\\0\\0\\0\\0\\0VX\\0\\0\\xC7>\\0\\0\\0A\\xF0\\x96\\v"\\0\\0\\0\\0\\0\\vY\\0\\0\\xED>\\0\\0\\0A\\x8C\\x97\\v#\\0\\0\\0\\0\\0Al\\0\\0\\x87\\0\\0\\0A\\xA8\\x97\\v$\\0\\0\\0\\0\\0\\xC1\\x7F\\0\\x005\\x88\\0\\0\\0A\\xC5\\x97\\v\\0\\0\\0\\0\\xCC\\0\\0zQ\\0\\0\\0A\\xE0\\x97\\v\\0\\0\\0\\0\\xCA\\0\\0\\x81\\0\\0\\0A\\xFC\\x97\\v\\0\\0\\0\\0\\x8E\\0\\0\\xB4\\x8E\\0\\0\\0A\\x98\\x98\\v\\0\\0\\0\\0\\xD3\\x8B\\0\\0_\\x8C\\0\\0\\0A\\xB4\\x98\\v\\0\\0\\0\\0Y\\x1B\\0\\0e\\0\\0\\0A\\xD0\\x98\\v\\0\\0\\0\\0\\xAFJ\\0\\0]\\x85\\0\\0\\0A\\xEC\\x98\\v\\0\\0\\0\\0\\xF6\\0\\0\\x91\\0\\0\\0A\\x88\\x99\\v\\x07\\0\\0\\0\\0>@\\0\\0#[\\0\\0\\0A\\xA4\\x99\\v\\b\\0\\0\\0\\0\\xA6{\\0\\0\\xF2\\x87\\0\\0\\0A\\xC0\\x99\\v \\0\\0\\0\\0#\\0\\0\\xBF\\x83\\0\\0\\0A\\xDC\\x99\\v +\\0\\0\\0\\0\\xE3\\x7F\\0\\0\\xEF\\x1B\\0\\0\\0A\\xF8\\x99\\v\\v\\0\\0\\0\\0\\xD6\\x8A\\0\\0\\xD2\\x83\\0\\0\\0A\\x94\\x9A\\v\\f\\0\\0\\0\\0\\x9D\\0\\0\\xE0\\0\\0\\0A\\xB0\\x9A\\v\\r\\0\\0\\0\\0\\xF1J\\0\\0H\\0\\0\\0A\\xCC\\x9A\\v\\0\\0\\0\\0A\\x7F\\0\\0N\\x7F\\0\\0\\0A\\xE8\\x9A\\v\\0\\0\\0\\0\\xFE\\x8B\\0\\0\\xFD\\0\\0\\0A\\x84\\x9B\\v\\0\\0\\0\\0O\\x8E\\0\\0\\xBC\\0\\0\\0A\\xA0\\x9B\\v\\0\\0\\0\\0r\\x90\\0\\x009\\0\\0\\0A\\xBC\\x9B\\v\\0\\0\\0\\0\\xE0.\\0\\0'\\x83\\0\\0\\0A\\xD8\\x9B\\v\\0\\0\\0\\0\\x9B\\0\\0\\xF3\\x80\\0\\0\\0A\\xF4\\x9B\\v\\0\\0\\0\\0\\x91\\x93\\0\\x007\\x94\\0\\0\\0A\\x90\\x9C\\v\\0\\0\\0\\0\\xF9\\x8D\\0\\0]\\x8E\\0\\0\\0A\\xAC\\x9C\\v\\0\\0\\0\\0\\xCF\\x8F\\0\\0\\x88\\x90\\0\\0\\0A\\xC8\\x9C\\v\\0\\0\\0\\0\\xA3h\\0\\0\\x83\\x86\\0\\0\\0A\\xE4\\x9C\\v\\0\\0\\0\\0S'\\0\\0\\xEC\\x81\\0\\0\\0A\\x80\\x9D\\v\\0\\0\\0\\0i'\\0\\0\\v\\x82\\0\\0\\0A\\x9C\\x9D\\v\\0\\0\\0\\0\\x94{\\0\\0\\xCD\\x87\\0\\0\\0A\\xB8\\x9D\\v\\x1B\\0\\0\\0\\0_\\0\\0\\xB7\\x81\\0\\0\\0A\\xD4\\x9D\\v\\0\\0\\0\\x004\\x91\\0\\0\\xDE\\x83\\0\\0\\0A\\xF0\\x9D\\v\\0\\0\\0\\0Fi\\0\\0\\xBC\\x86\\0\\0\\0A\\x8C\\x9E\\v\\0\\0\\0\\0\\xBE~\\0\\0f1\\0\\0\\0A\\xA8\\x9E\\v\\0\\0\\0\\0@j\\0\\0\\x87\\0\\0\\0A\\xC4\\x9E\\v \\0\\0\\0\\0\\x81F\\0\\0\\xFC\\x84\\0\\0\\0A\\xE0\\x9E\\v!\\0\\0\\0\\0E\\0\\0\\xA1\\x84\\0\\0\\0A\\xFC\\x9E\\v"\\0\\0\\0\\0\\x91h\\0\\0\\xF4D\\0\\0\\0A\\x98\\x9F\\v#\\0\\0\\0\\0gW\\0\\0\\xF9?\\0\\0\\0A\\xB4\\x9F\\v$\\0\\0\\0\\0\\xD5V\\0\\0\\x97\\xA0\\0\\0\\0A\\xD0\\x9F\\v%\\0\\0\\0\\0U\\0\\0\\xE69\\0\\0\\0A\\xEC\\x9F\\v&\\0\\0\\0\\0\\xFF\\x7F\\0\\0X\\x88\\0\\0\\0A\\x88\\xA0\\v'\\0\\0\\0\\0\\x93\\x8B\\0\\0\\x8C\\0\\0\\0A\\xA4\\xA0\\v(\\0\\0\\0\\0\\xECK\\0\\0\\xAE\\x85\\0\\0\\0A\\xC0\\xA0\\v)\\0\\0\\0\\0\\xEC@\\0\\0\\xA1\\x83\\0\\0\\0A\\xDC\\xA0\\v*\\0\\0\\0\\0\\xF71\\0\\0\\xF6$\\0\\0\\0A\\xF8\\xA0\\v+\\0\\0\\0\\0IF\\0\\0\\x92L\\0\\0\\0A\\x94\\xA1\\v,\\0\\0\\0\\0JE\\0\\0uE\\0\\0\\0A\\xB0\\xA1\\v-\\0\\0\\0\\0r@\\0\\0\\x8B\\x83\\0\\0\\0A\\xCC\\xA1\\v.\\0\\0\\0\\0\\xAA.\\0\\0\\x83\\0\\0\\0A\\xE8\\xA1\\v/\\0\\0\\0\\0(K\\0\\0UE\\0\\0\\0A\\x84\\xA2\\v0\\0\\0\\0\\0*I\\0\\0\\0\\0\\0A\\xA0\\xA2\\v1\\0\\0\\0\\0\\0\\0r\\x80\\0\\0\\0A\\xBC\\xA2\\v2\\0\\0\\0\\08\\0\\0\\xA4\\0\\0\\0A\\xD8\\xA2\\v3\\0\\0\\0\\0\\xBAe\\0\\x006\\x86\\0\\0\\0A\\xF4\\xA2\\v4\\0\\0\\0\\0g\\0\\0v\\0\\0\\0A\\x90\\xA3\\v5\\0\\0\\0\\0\\xFF/\\0\\0\\v0\\0\\0\\0A\\xAC\\xA3\\v6\\0\\0\\0\\0\\xBE\\x8B\\0\\0A\\x8C\\0\\0\\0A\\xC8\\xA3\\v7\\0\\0\\0\\0\\xF1\\x7F\\0\\0.N\\0\\0\\0A\\xE4\\xA3\\v8\\0\\0\\0\\0\\xE5\\x8C\\0\\0\\xEF\\x8C\\0\\0\\0A\\x80\\xA4\\v9\\0\\0\\0\\0\\x07\\x90\\0\\0\\xA1\\x90\\0\\0\\0A\\x9C\\xA4\\v:\\0\\0\\0\\0\\xD6\\0\\0\\xE6\\0\\0\\0A\\xB8\\xA4\\v;\\0\\0\\0\\0\\xDDh\\0\\0C\\x87\\0\\0\\0A\\xD4\\xA4\\v<\\0\\0\\0\\0\\x85\\x1B\\0\\0\\x92\\x1B\\0\\0\\0A\\xF0\\xA4\\v=\\0\\0\\0\\0\\xE0P\\0\\0\\xF1P\\0\\0\\0A\\x8D\\xA5\\v \\0\\0\\0\\0S{\\0\\0x\\xA3\\0\\0\\0A\\xA8\\xA5\\v \\0\\0\\0\\x003{\\0\\0\\xA0\\xA3\\0\\0\\0A\\xC5\\xA5\\v0\\0\\0\\0\\0I\\x8D\\0\\0_\\xA7\\0\\0\\0A\\xE0\\xA5\\v0\\0\\0\\0\\0 \\x8A\\0\\0I\\xA7\\0\\0\\0A\\xFD\\xA5\\v@\\0\\0\\0\\0A\\0\\0\\x98\\x82\\0\\0\\0A\\x98\\xA6\\v@\\0\\0\\0\\0\\bA\\0\\0\\x95\\0\\0\\0A\\xB4\\xA6\\v@\\0\\0\\0\\0-A\\0\\0@\\0\\0\\0A\\xD0\\xA6\\v@\\0\\0\\0\\x006A\\0\\0:C\\0\\0\\0A\\xEC\\xA6\\v@\\0\\0\\0\\0IA\\0\\0g\\0\\0\\0A\\x88\\xA7\\v@\\0\\0\\0\\0 A\\0\\0\\x91;\\0\\0\\0A\\xA4\\xA7\\v@\\0\\0\\0\\0?A\\0\\0\\x9A\\x85\\0\\0\\0A\\xC0\\xA7\\v\\x07@\\0\\0\\0\\0\\xFE@\\0\\0C\\x80\\0\\0\\0A\\xDC\\xA7\\v\\b@\\0\\0\\0\\0SA\\0\\0\\x83\\0\\0\\0A\\xF9\\xA7\\vP\\0\\0\\0\\0\\xE0\\x8F\\0\\0\\xA9\\0\\0\\0A\\x94\\xA8\\vP\\0\\0\\0\\0\\x8E\\0\\0\\x99\\xA8\\0\\0\\0A\\xB0\\xA8\\v-P\\0\\0\\0\\0\\x8El\\0\\0\\xFF\\xA1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA9\\0P\\0\\0\\0\\0K\\x8E\\0\\0k\\xA8\\0\\0\\0A\\xE8\\xA8\\v-P\\0\\0\\0\\0M\\0\\0\\x8F\\x9D\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0P\\xA9\\0P\\0\\0\\0\\0\\xFC \\0\\0!\\0\\0\\0A\\xA0\\xA9\\vP\\0\\0\\0\\0\\x880\\0\\0"1\\0\\0\\0A\\xBC\\xA9\\v\\x07P\\0\\0\\0\\0P\\x93\\0\\0\\xD3\\x95\\0\\0\\0A\\xD8\\xA9\\v\\bP\\0\\0\\0\\0[\\x8F\\0\\0:\\x92\\0\\0\\0A\\xF4\\xA9\\v P\\0\\0\\0\\0'\\x8E\\0\\0\\xB7\\x94\\0\\0\\0A\\x90\\xAA\\v +P\\0\\0\\0\\0\\xC8\\x8A\\0\\0\\x86\\x94\\0\\0\\0A\\xAC\\xAA\\v\\vP\\0\\0\\0\\0\\xB10\\0\\0U1\\0\\0\\0A\\xC8\\xAA\\v\\fP\\0\\0\\0\\0\\x80l\\0\\0M\\xA4\\0\\0\\0A\\xE4\\xAA\\v\\rP\\0\\0\\0\\0\\xEB\\0\\0}\\xA8\\0\\0\\0A\\x80\\xAB\\vP\\0\\0\\0\\0\\xB7\\x8B\\0\\x008\\x8C\\0\\0\\0A\\x9C\\xAB\\vP\\0\\0\\0\\0\\xE7;\\0\\0\\xEF;\\0\\0\\0A\\xB8\\xAB\\vP\\0\\0\\0\\0\\x85l\\0\\0+\\xA4\\0\\0\\0A\\xD4\\xAB\\vP\\0\\0\\0\\0\\xBB\\x93\\0\\0\\xA9\\0\\0\\0A\\xF0\\xAB\\vP\\0\\0\\0\\0\\\\@\\0\\0\\x82\\x83\\0\\0\\0A\\x8C\\xAC\\vP\\0\\0\\0\\0\\x90\\0\\0\\x93\\0\\0\\0A\\xA8\\xAC\\vP\\0\\0\\0\\0\\x80E\\0\\0\\xE0\\xA8\\0\\0\\0A\\xC4\\xAC\\vP\\0\\0\\0\\0\\x9EH\\0\\0\\xA9H\\0\\0\\0A\\xE0\\xAC\\vP\\0\\0\\0\\0\\xBD{\\0\\0\\x9F\\0\\0\\0A\\xFC\\xAC\\vP\\0\\0\\0\\0\\xA60\\0\\0\\xDD0\\0\\0\\0A\\x98\\xAD\\vP\\0\\0\\0\\0\\xA4.\\0\\0\\xFB_\\0\\0\\0A\\xB4\\xAD\\vP\\0\\0\\0\\0\\xEDE\\0\\0\\xFBE\\0\\0\\x07\\0A\\xD0\\xAD\\vP\\0\\0\\0\\0\\x9A0\\0\\0+1\\0\\0\\0A\\xEC\\xAD\\v\\x1BP\\0\\0\\0\\0\\xA7i\\0\\0\\xB6\\x9E\\0\\0\\0A\\x88\\xAE\\vP\\0\\0\\0\\0i\\x8A\\0\\0s\\x8A\\0\\0\\0A\\xA4\\xAE\\vP\\0\\0\\0\\0\\xA0.\\0\\0\\xDF\\xA1\\0\\0\\0A\\xC0\\xAE\\vP\\0\\0\\0\\0\\xEF\\0\\0\\0\\0\\0A\\xDC\\xAE\\vP\\0\\0\\0\\0C\\x8E\\0\\0\\xB2\\xA8\\0\\0\\0A\\xF8\\xAE\\v P\\0\\0\\0\\0Q\\0\\0\\x8E1\\0\\0\\0A\\x94\\xAF\\v!P\\0\\0\\0\\0\\xA4*\\0\\0\\xDC7\\0\\0\\0A\\xB0\\xAF\\v"P\\0\\0\\0\\0\\xBD\\x89\\0\\0\\xF4\\x89\\0\\0\\0A\\xCC\\xAF\\v#P\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xAF\\v$P\\0\\0\\0\\0\\xF2_\\0\\0\\r\\\`\\0\\0\\0A\\x84\\xB0\\v%P\\0\\0\\0\\0F0\\0\\0^D\\0\\0\\0A\\xA0\\xB0\\v&P\\0\\0\\0\\0y\\0\\0\\xB9\\0\\0\\0A\\xBC\\xB0\\v'P\\0\\0\\0\\0h/\\0\\0Z\\xA9\\0\\0\\0A\\xD8\\xB0\\v-(P\\0\\0\\0\\0J\\x8E\\0\\0Q\\xA8\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\xA9\\0)P\\0\\0\\0\\0(\\0\\0\\x81\\0\\0\\0A\\x90\\xB1\\v*P\\0\\0\\0\\0\\xA2\\x93\\0\\0A\\xA9\\0\\0\\0A\\xAC\\xB1\\v+P\\0\\0\\0\\0\\xC9-\\0\\0\\xA7\\x82\\0\\0\\0A\\xC8\\xB1\\v-,P\\0\\0\\0\\0Jf\\0\\0U\\x86\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xB0\\xA9\\0-P\\0\\0\\0\\0R \\0\\0\\xD9\\xA5\\0\\0\\0A\\x80\\xB2\\v.P\\0\\0\\0\\0\\xCE\\x93\\0\\0\\x90\\xA2\\0\\0\\0A\\x9C\\xB2\\v/P\\0\\0\\0\\0j\\0\\0\\x8A\\xA5\\0\\0\\0A\\xB8\\xB2\\v0P\\0\\0\\0\\0P\\0\\0\\xC8\\xA5\\0\\0\\0A\\xD4\\xB2\\v-1P\\0\\0\\0\\0S@\\0\\0 \\x9C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA9\\x002P\\0\\0\\0\\0\\xABi\\0\\0\\xC1i\\0\\0\\0A\\x8C\\xB3\\v3P\\0\\0\\0\\0]\\0\\0_\\xA5\\0\\0\\0A\\xA8\\xB3\\v4P\\0\\0\\0\\0\\xEA\\x93\\0\\x005\\x95\\0\\0\\0A\\xC4\\xB3\\v5P\\0\\0\\0\\0^\\x89\\0\\0c\\x89\\0\\0\\0A\\xE0\\xB3\\v6P\\0\\0\\0\\0\\xEC-\\0\\0\\xBAD\\0\\0\\0A\\xFC\\xB3\\v7P\\0\\0\\0\\0_{\\0\\0\\xB3F\\0\\0\\0A\\x98\\xB4\\v8P\\0\\0\\0\\0\\xBA0\\0\\0\\v1\\0\\0\\0A\\xB4\\xB4\\v9P\\0\\0\\0\\0@K\\0\\0\\xA6\\0\\0\\0A\\xD0\\xB4\\v:P\\0\\0\\0\\0\\x8Al\\0\\0\\x9E\\x9E\\0\\0\\0A\\xEC\\xB4\\v;P\\0\\0\\0\\0jl\\0\\0\\xBB\\xA4\\0\\0\\0A\\x88\\xB5\\v P\\0\\0\\0\\0F\\0\\0\\xC4\\x9D\\0\\0\\0A\\xDC\\xB5\\v?P\\0\\0\\0\\0je\\0\\0ze\\0\\0\\0A\\xF8\\xB5\\v@P\\0\\0\\0\\0\\xC40\\0\\0\\x9D1\\0\\0\\0A\\x94\\xB6\\vAP\\0\\0\\0\\0i\\0\\0k\\0\\0\\0A\\xB0\\xB6\\vBP\\0\\0\\0\\0\\xFD-\\0\\0\\xED\\xA6\\0\\0\\0A\\xCC\\xB6\\vCP\\0\\0\\0\\0F\\x90\\0\\0\\xE1\\xA3\\0\\0\\0A\\xE8\\xB6\\vDP\\0\\0\\0\\0\\xFDF\\0\\0}j\\0\\0\\0A\\x84\\xB7\\vEP\\0\\0\\0\\0\\xCFi\\0\\0\\xD5i\\0\\0\\0A\\xA0\\xB7\\vFP\\0\\0\\0\\0J \\0\\0\\x97\\0\\0\\0A\\xBC\\xB7\\vGP\\0\\0\\0\\0,i\\0\\0\\xDD\\xA4\\0\\0\\0A\\xD8\\xB7\\vHP\\0\\0\\0\\0\\x92\\0\\0Y\\x9E\\0\\0\\0A\\xF4\\xB7\\vIP\\0\\0\\0\\0\\xE3K\\0\\0"\\0\\0\\0A\\x90\\xB8\\vJP\\0\\0\\0\\0\\xDAK\\0\\0\\xEA\\0\\0\\0A\\xAC\\xB8\\vKP\\0\\0\\0\\0\\0,\\0\\0z-\\0\\0\\0A\\xC8\\xB8\\vLP\\0\\0\\0\\0\\xF0+\\0\\0:-\\0\\0\\0A\\xE4\\xB8\\vMP\\0\\0\\0\\0\\xB4\\0\\x009\\x9E\\0\\0\\0A\\x80\\xB9\\vNP\\0\\0\\0\\0]A\\0\\0\\xEA\\x83\\0\\0\\0A\\x9C\\xB9\\vOP\\0\\0\\0\\0\\xDA\\x93\\0\\0\\xC0\\x95\\0\\0\\0A\\xB8\\xB9\\vPP\\0\\0\\0\\0\\\`\\x90\\0\\0'\\x92\\0\\0\\0A\\xD4\\xB9\\vQP\\0\\0\\0\\0t\\0\\0\\x9E\\0\\0\\0A\\xF0\\xB9\\vRP\\0\\0\\0\\0E\\0\\0\\x07\\x9D\\0\\0\\0A\\x8C\\xBA\\vSP\\0\\0\\0\\0TD\\0\\0J\\x9D\\0\\0\\0A\\xA8\\xBA\\vTP\\0\\0\\0\\0\\xBAd\\0\\0\\x86\\0\\0\\0A\\xC4\\xBA\\vUP\\0\\0\\0\\0 \\0\\0z\\xA1\\0\\0\\0A\\xE0\\xBA\\vVP\\0\\0\\0\\0Xf\\0\\0\\xAB\\xA1\\0\\0\\0A\\xFC\\xBA\\vWP\\0\\0\\0\\0al\\0\\0\\b\\xA4\\0\\0\\0A\\x98\\xBB\\vXP\\0\\0\\0\\0L\\x89\\0\\0\\xA5\\xA7\\0\\0\\0A\\xB4\\xBB\\vYP\\0\\0\\0\\0\\xA0@\\0\\0\\xC81\\0\\0\\0A\\xD0\\xBB\\vZP\\0\\0\\0\\0d;\\0\\0E\\x83\\0\\0\\0A\\xEC\\xBB\\v[P\\0\\0\\0\\0\\xCE0\\0\\0\\xD31\\0\\0\\0A\\x88\\xBC\\v\\\\P\\0\\0\\0\\0\\xFA;\\0\\0<\\0\\0\\0A\\xA4\\xBC\\v]P\\0\\0\\0\\0 +\\x80\\0\\0j\\x88\\0\\0\\0A\\xC0\\xBC\\v^P\\0\\0\\0\\0\\x900\\0\\0{1\\0\\0\\0A\\xDC\\xBC\\v_P\\0\\0\\0\\0\\xF7;\\0\\0\\0<\\0\\0\\0A\\xF8\\xBC\\v\\\`P\\0\\0\\0\\0H@\\0\\0\\xAE\\x9F\\0\\0\\0A\\x94\\xBD\\vaP\\0\\0\\0\\0\\x85E\\0\\0\\xEB0\\0\\0\\x07\\0A\\xB0\\xBD\\vbP\\0\\0\\0\\0\\xEF\\x8B\\0\\0\\xE31\\0\\0\\0A\\xCC\\xBD\\vcP\\0\\0\\0\\0{l\\0\\0\\xF60\\0\\0\\0A\\xE8\\xBD\\vdP\\0\\0\\0\\0\\xFC'\\0\\0'\\\`\\0\\0\\0A\\x84\\xBE\\veP\\0\\0\\0\\0;i\\0\\0\\xAF)\\0\\0\\0A\\xA0\\xBE\\vfP\\0\\0\\0\\0\\xD5~\\0\\0\\x9F\\0\\0\\x07\\0A\\xBC\\xBE\\vgP\\0\\0\\0\\0\\xF5\\x8B\\0\\0\\xA5\\x8D\\0\\0\\0A\\xD8\\xBE\\vhP\\0\\0\\0\\0\\xD0-\\0\\0\\x9C\\0\\0\\0A\\xF4\\xBE\\viP\\0\\0\\0\\0\\x9B~\\0\\0&\\x9F\\0\\0\\0A\\x90\\xBF\\vjP\\0\\0\\0\\0?\\x8E\\0\\0\\x92\\xA4\\0\\0\\0A\\xAC\\xBF\\vkP\\0\\0\\0\\0#\\x8A\\0\\0(\\x8A\\0\\0\\0A\\xC8\\xBF\\vlP\\0\\0\\0\\0\\xF6\\0\\0j\\x80\\0\\0\\0A\\xE5\\xBF\\vp\\0\\0\\0\\0S\\0\\x006%\\0\\0\\0\\0\\0A\\x80\\xC0\\vp\\0\\0\\0\\0S\\0\\0D%\\0\\0\\0\\0\\0A\\x9C\\xC0\\vp\\0\\0\\0\\0\\x89\\0\\0\\x7F\\0\\0\\0\\0\\0A\\xB8\\xC0\\vp\\0\\0\\0\\0={\\0\\0r\\x87\\0\\0\\0\\0\\0A\\xD4\\xC0\\vp\\0\\0\\0\\x009~\\0\\0T\\0\\0\\0\\0\\0A\\xF0\\xC0\\vp\\0\\0\\0\\0?\\0\\0v\\0\\0\\0\\0\\0A\\x8C\\xC1\\vp\\0\\0\\0\\0\\xFFR\\0\\0\\v%\\0\\0\\0\\0\\0A\\xA8\\xC1\\v\\x07p\\0\\0\\0\\0%\\0\\x002\\0A\\xC4\\xC1\\v\\bp\\0\\0\\0\\0\\xA2\\0\\0\\0\\x005T\\0\\0\\0\\0\\0A\\xE8\\xC4\\vp\\0\\0\\0\\0\\xECR\\0\\0=S\\0\\0\\0\\0\\0A\\x84\\xC5\\vp\\0\\0\\0\\0QC\\0\\0\\x86[\\0\\0\\0\\0\\0A\\xA0\\xC5\\vp\\0\\0\\0\\0\\x878\\0\\0\\x948\\0A\\xB8\\xC5\\v\\xF0\\xAC\\0p\\0\\0\\0\\0\\xC2F\\0\\0"\\0A\\xD9\\xC5\\v)\\x80\\0\\0\\0\\0 K\\0\\0e\\0\\0\\0\\0\\0\\0de\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\0b\\x8B\\0\\0\\xB2M\\0A\\x90\\xC6\\v\\x80\\0\\0\\0\\0\\xDEH\\0\\0\\xA6_\\0A\\xAC\\xC6\\v\\x80\\0\\0\\0\\0\\0\\0\\0H\\0\\0\\0A\\xC8\\xC6\\v\\x80\\0\\0\\0\\0\\0\\xA4;\\0\\0T\\0\\0\\0A\\xE4\\xC6\\v\\x80\\0\\0\\0\\0\\0\\xD6K\\0\\0i\\0\\0\\0A\\x80\\xC7\\v*\\x80\\0\\0\\0\\0$K\\0\\0W\\0\\0\\0\\0\\0\\0pe\\0\\0\\0\\0\\0\\x07\\x80\\0\\0\\0\\0\\xA9\\0\\0 +|\\0A\\xB8\\xC7\\v\\b\\x80\\0\\0\\0\\0\\xE1\\0\\0\\b\\0A\\xD4\\xC7\\v \\x80\\0\\0\\0\\0-\\x8E\\0\\0\\xF7{\\0A\\xF0\\xC7\\v* +\\x80\\0\\0\\0\\0t|\\0\\0\\xBD|\\0\\0\\0\\0\\0\\0xe\\0\\0\\0\\0\\0\\v\\x80\\0\\0\\0\\0\\x8A\\0\\0\\x97\\x81\\0A\\xA8\\xC8\\v\\f\\x80\\0\\0\\0\\0\\xFBh\\0\\0\\x9F\\x91\\0A\\xC4\\xC8\\v\\r\\x80\\0\\0\\0\\0ti\\0\\0\\xCF\\x86\\0A\\xE2\\xC8\\v+\\0\\0\\0\\0F\\0\\0~B\\0\\0\\0\\0\\0\\0\\x80e\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0R\\0\\0:H\\0\\0\\0A\\x98\\xC9\\v\\0\\0\\0\\0\\0VC\\0\\0\\xC4j\\0A\\xB4\\xC9\\v\\0\\0\\0\\0\\\\C\\0\\0\\x99j\\0A\\xD0\\xC9\\v\\xB2\\xC6J\\0\\0\\0\\0\\0\\0\\xEFH\\0\\0\\0\\0\\0\\0/\\0\\0\\0\\0\\0\\0\\x91.\\0\\0\\0\\0\\0\\0\\xE4\\x8F\\0\\0\\0\\0\\0\\0\\xC9\\x7F\\0\\0\\xB5\\x7F\\0\\0\\0\\0\\0\\0\\x9EK\\0\\0\\0\\0\\0\\0JK\\0\\0\\0\\0\\0\\0-\\0\\0\\0\\0\\0\\0Q.\\0\\0\\0\\0\\0\\0)/\\0\\0\\0\\0\\0\\0\\xF0.\\0\\0\\0\\0\\0\\0\\xEFH\\0\\0\\0\\0\\0\\0/\\0\\0\\0\\0\\0\\0CC\\0\\0\\0\\0\\0\\0C\\0\\0\\0\\0\\0\\0C\\0\\0\\0\\0\\0\\0H\\x1B\\0\\0\\0\\0\\0\\0;\\0\\0(\\0\\0\\0\\0\\0\\0\\xD9O\\0\\0\\0\\0\\0\\0\\xFDA\\0\\0\\0\\0\\0\\0z\\x81\\0A\\x90\\xCB\\v\\x92\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0#\\0\\0\\0\\0\\0\\0&\\0\\0\\0\\0\\0\\0(\\0\\0\\0\\0\\0\\0)\\0\\0\\0\\0\\0\\0\\0\\0w\\0\\x88X\\0\\0\\xFA\\0\\0\\x80\\xBB\\0\\0D\\xAC\\0\\0\\0}\\0\\0\\xC0]\\0\\0"V\\0\\0\\x80>\\0\\0\\xE0.\\0\\0+\\0\\0@\\0\\0\\xB6\\0A\\xB0\\xCC\\vC\\xF8\\0\\0b\\0\\0\\0\\\`f\\0d<\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0c\\0\\0\\0\\0\\0\\0\\0d\\0\\0\\0e\\0\\0\\0\\0\\0\\0\\0\\x84{\\0\\0\\xAC\\x9D\\0\\0(\\0\\0\\0\\0\\0\\0@\\r\\0A\\x86\\xCD\\v\\xE0C\\0\\0\\0\\0\\0\\0\\0\\x83{\\0\\0\\xAC\\x9D\\0\\0(\\0\\0\\0\\0\\0\\0\\0\\xF4\\0A\\xB2\\xCD\\v!\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\0\\0\\0\\0\\xA6\\0\\0\\x99\\0\\0t\\0\\0\\0\\0\\0\\0 =\\0A\\xE2\\xCD\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\xD2!\\0\\0\\0\\0\\0\\x000\\0\\0\\0\\0A\\x92\\xCE\\v\\xE0\\xFF\\xFF\\xFF\\xEFA;\\0\\0\\0\\xD2!\\0\\0\\xC6g\\0\\0\\xA1\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xBE\\xCE\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\x8F\\x8B\\0\\0!\\xA8\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xEE\\xCE\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xD9C\\0\\0a:\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x9E\\xCF\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0C.\\0\\0[*\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\b\\0A\\xCE\\xCF\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xCEV\\0\\0\\xC4V\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xFE\\xCF\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\xD2!\\0\\x002R\\0\\0\\x1B$\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0@\\0A\\xBC\\xD0\\v\\xD2!\\0\\0\\xE9L\\0\\0\\xDD!\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\x80\\0A\\xEC\\xD0\\v\\xD2!\\0\\0\\x81:\\0\\0Y\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\x9C\\xD1\\v\\xD2!\\0\\0{\\x93\\0\\0$_\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xBE\\xD1\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\xD2!\\0\\0\\xC2\\x8F\\0\\0\\xF0^\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xEE\\xD1\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\xD2!\\0\\0^\\0\\0\\xA7V\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0 \\0A\\x9E\\xD2\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xFC(\\0\\0\\xF5I\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\x80\\0A\\xCE\\xD2\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0@\\0\\0P\\x81\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0A\\xFE\\xD2\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xBB\\0\\0\\xF4\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\b\\0A\\xAE\\xD3\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xA4+\\0\\0\\xB1Q\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0@\\0A\\xDE\\xD3\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0^\\0\\0\\x9C\\xA5\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\x80\\0A\\x8E\\xD4\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0\\xD2!\\0\\x007j\\0\\0f9\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0A\\xBE\\xD4\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0fQ\\0\\0\\xBA:\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0 \\0A\\xEE\\xD4\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0H.\\0\\0\\xEE\\x82\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x80\\0A\\x9E\\xD5\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xD9\\0\\0\\xF2#\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\xCE\\xD5\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD2!\\0\\0\\xC8\\x8F\\0\\0\\0\\0\\0\\x004\\0\\0\\0\\0A\\x82\\xD6\\v\\xE0\\xFF\\xFF\\xFF\\xEFA;\\0\\0\\0\\xC8\\x8F\\0\\0\\x9D\\0\\0\\xC8 \\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xAE\\xD6\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0\\xD6\\0\\0\\xDDI\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xD6\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0#.\\0\\0E\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0A\\x8E\\xD7\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0\\x97+\\0\\x005|\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\xBE\\xD7\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0\\x81 \\0\\0Y \\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\x80\\0A\\xEE\\xD7\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0xC\\0\\0\\xEDQ\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0@\\0A\\x9E\\xD8\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0f\\x1B\\0\\0a}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0A\\xCE\\xD8\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xC8\\x8F\\0\\0D\\0\\0}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0 \\0A\\xFE\\xD8\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA +\\0\\0\\0\\xC8\\x8F\\0\\0..\\0\\0\\xD6G\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0@\\0A\\xAE\\xD9\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA"\\0\\0\\0\\xC8\\x8F\\0\\0x%\\0\\0f!\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x80\\0A\\xDE\\xD9\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA"\\0\\0\\0\\xC8\\x8F\\0\\0}|\\0\\0}\\0\\0\\xA8\\0\\0\\0A\\x92\\xDA\\v\\xE0\\xFF\\xFF\\xFF\\xEFA;\\0\\0\\0}|\\0\\0w\\x1B\\0\\0a}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xBE\\xDA\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0}|\\0\\0\\x8C\\0\\0\\xC9|\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xEE\\xDA\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA9\\0\\0\\0}|\\0\\0\\xE3\\0\\0\\xC4}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x9E\\xDB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0}|\\0\\0\\xA9;\\0\\0\\x8F}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\xCE\\xDB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0}|\\0\\0$\\0\\0\\xFD}\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xFE\\xDB\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0}|\\0\\0 O\\0\\0\\0\\0\\0\\0@\\0\\0\\0\\x07\\0A\\xB2\\xDC\\v\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0\\xE7J\\0\\0XL\\0\\0 \\0\\0\\0\\0\\0\\f\\0A\\xDE\\xDC\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\xC6-\\0\\0\\xAB\\x9B\\0\\0,\\0\\0\\0A\\x92\\xDD\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\v\\0\\0\\0\\0\\0\\0\\0wK\\0\\0\\xC1G\\0\\0X\\0\\0\\0A\\xBE\\xDD\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\0\\0\\0\\x001L\\0\\0\\0\\0\\0\\0P\\0\\0\\0A\\xF2\\xDD\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA \\0\\0\\0\\0\\0\\0\\0\\xBA+\\0\\0\\0\\0\\0\\0\\xD0\\0\\0\\0A\\x9E\\xDE\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0\\xFE\\0\\0\\0\\0\\0\\0X\\0\\0\\0\\0A\\xCE\\xDE\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0e.\\0\\0\\x98\\0\\0\\x80\\0\\0\\0A\\xF6\\xDE\\v'\\xE0?\\0\\0\\0\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0\\xF6'\\0\\x002\\xA6\\0\\0\\x84\\0\\0\\0A\\xA6\\xDF\\v+\\xE0?\\0\\0\\0\\0\\0\\0\\xF0\\xBF\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0z;\\0\\0\\xC9\\xA6\\0\\0\\x88\\0\\0\\0\\0\\0\\0A\\xDE\\xDF\\v#\\xF0\\xBF\\0\\0\\0\\0\\0@Q@\\0\\0\\0\\0\\0\\0\\0@\\0\\0\\xA5\\xA6\\0\\0\\x8C\\0\\0\\0\\0\\0\\0A\\x8E\\xE0\\v#\\xF0\\xBF\\0\\0\\0\\0\\0\\0\\x90@\\0\\0\\0\\0\\0\\0\\0\\xA9K\\0\\0\\xFC\\xA5\\0\\0\\x90\\0\\0\\0\\0\\0\\0A\\xBE\\xE0\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\xE9K\\0\\0#\\0\\0\\xA8\\0\\0\\0\\0A\\xEE\\xE0\\v\\xF0\\xBF\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\x004(\\0\\0d#\\0\\0\\xAC\\0\\0\\0\\0A\\x96\\xE1\\v'\\xF4?\\0\\0\\0\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0\\xDFJ\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xCE\\xE1\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0\\xDAH\\0\\0!\\0\\0\\xC8\\0\\0\\0\\0\\0\\0A\\xFE\\xE1\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0#\\0A\\x9C\\xE2\\v\\v\\0\\0\\0\\0A\\xAE\\xE2\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0g\\\`\\0\\0\\xA5\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xE2\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xAB\\x8B\\0\\0\\xD3\\xA7\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\x8E\\xE3\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\x8CJ\\0\\0\\xC6\\xA3\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xBE\\xE3\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xA5\\0A\\xDC\\xE3\\v\\v\\0\\0\\0 \\0A\\xEE\\xE3\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xA6~\\0A\\x8C\\xE4\\v\\v\\0\\0\\0@\\0A\\x9E\\xE4\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xD5C\\0\\0\\xDC\\x9F\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\x80\\0A\\xCE\\xE4\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0e\\x90\\0A\\xEC\\xE4\\v\\v\\0\\0\\0\\0\\0A\\xFE\\xE4\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xFCK\\0\\0C\\xA0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xAE\\xE5\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\x81[\\0\\0\\xA0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xDE\\xE5\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xB2~\\0A\\xFC\\xE5\\v\\v\\0\\0\\0\\0\\b\\0A\\x8E\\xE6\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\xC8.\\0A\\xAC\\xE6\\v\\v\\0\\0\\0\\0\\0A\\xBE\\xE6\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0 \\0\\0\\r\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0 \\0A\\xEE\\xE6\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0Ti\\0\\0\\xC2$\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0@\\0A\\x9E\\xE7\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\x80[\\0A\\xBC\\xE7\\v\\v\\0\\0\\0\\0\\x80\\0A\\xCE\\xE7\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xDAH\\0\\0\\0\\0\\xCD&\\0\\0\\xCC\\0\\0\\0A\\xFE\\xE7\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0\\0\\0~O\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xAE\\xE8\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0\\0\\0\\xA5%\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xE8\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0\\xA3D\\0A\\xFC\\xE8\\v\\v\\0A\\x8E\\xE9\\v\\x7F\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0\\xCBD\\0\\0\\xC7\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0)D\\0\\0;!\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\xFE\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\x1B\\0\\0\\0\\0\\0\\x83\\0\\0B#\\0\\0\\xB0\\0\\0\\0\\0A\\x96\\xEA\\v'\\xF4?\\0\\0\\0\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0.\\0\\0\\xBE!\\0\\0\\xD8\\0\\0\\0A\\xCE\\xEA\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\xE4E\\0\\0\\x8D'\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xFE\\xEA\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\x7FA\\0\\0\\x82\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xAE\\xEB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\xF1*\\0\\0G\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xEB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\x94_\\0\\0\\x999\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\x8E\\xEC\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\b(\\0\\0\\xE1\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\x80\\0A\\xBE\\xEC\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0!C\\0\\0{\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0A\\xEE\\xEC\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\x003\\0\\0N\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0A\\x9E\\xED\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\xD1L\\0\\0\\xA8(\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\x07\\0A\\xCE\\xED\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0.\\0\\0\\x99"\\0\\0\\0\\0\\0\\0\\x98\\0\\0\\0\\0A\\x82\\xEE\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0\\xDB;\\0\\0\\0\\0\\0\\0T\\0\\0\\0A\\xB2\\xEE\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x98\\0\\0\\0A\\xDE\\xEE\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0N\\0\\0\\xDD\\x9A\\0\\0\\xA0\\0\\0\\0A\\x92\\xEF\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0N\\0\\0\\x9B\\0\\0\\xA8\\0\\0\\0A\\xBE\\xEF\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\rL\\0\\0\\xE1\\x9C\\0\\0\\x94\\0\\0\\0A\\xEE\\xEF\\vO\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0*(\\0\\0\\xEC"\\0\\0\\xB4\\0\\0\\0\\0\\0\\0\\x9A\\x99\\x99\\x99\\x99\\x99\\xE9\\xBF\\0\\0\\0\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0y\\0\\0\\xCA"\\0\\0\\xB8\\0\\0\\0\\0A\\xCB\\xF0\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0Z\\0\\0\\xD6@\\0\\x008\\0\\0\\0A\\x82\\xF1\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0 0\\0\\0Q\\0\\0\\0\\0\\0\\0\\v\\0A\\xAE\\xF1\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0s\\0\\0\\xD3*\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xF1\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0\\x8D\\0\\0\\xE0*\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x8E\\xF2\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0\\x93\\0A\\xAC\\xF2\\v\\v\\0\\0\\0\\0A\\xBE\\xF2\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0Jj\\0A\\xDC\\xF2\\v\\v\\0\\0\\0\\0A\\xEE\\xF2\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0@\\0\\0c\\x81\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x9E\\xF3\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0B;\\0A\\xBC\\xF3\\v\\v\\0\\0\\0\\x07\\0A\\xCE\\xF3\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0Z\\0\\0@E\\0\\0"\\0\\0\\xBC\\0\\0\\0\\0A\\xFB\\xF3\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0E\\0\\0aI\\0\\0\\xC0\\0\\0\\0\\0A\\xAB\\xF4\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0$E\\0\\0}I\\0\\0\\xC4\\0\\0\\0\\0A\\xDB\\xF4\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0/E\\0\\0\\x98I\\0\\0\\xC8\\0\\0\\0\\0A\\x8B\\xF5\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\x006E\\0\\x005"\\0\\0\\xCC\\0\\0\\0\\0A\\xBB\\xF5\\v"\\xE0\\xFF\\xFF\\xEF\\xC7\\0\\0\\0\\xE0\\xFF\\xFF\\xEFG\\0\\0\\0\\0\\0\\0\\0K\\0\\0\\xF89\\0\\0<\\0\\0\\0A\\xF2\\xF5\\v\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0 0\\0A\\x8C\\xF6\\v\\v\\0A\\x9E\\xF6\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\x8D\\0A\\xBC\\xF6\\v\\v\\0\\0\\0\\0A\\xCE\\xF6\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0qT\\0A\\xEC\\xF6\\v\\v\\0\\0\\0\\0A\\xFE\\xF6\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\x85\\0A\\x9C\\xF7\\v\\v\\0\\0\\0\\0A\\xAE\\xF7\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0f@\\0A\\xCC\\xF7\\v\\v\\0\\0\\0\\x07\\0A\\xDE\\xF7\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0Jj\\0A\\xFC\\xF7\\v\\v\\0\\0\\0\\b\\0A\\x8E\\xF8\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\\`@\\0A\\xAC\\xF8\\v\\v\\0\\0\\0 +\\0A\\xBE\\xF8\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0AN\\0A\\xDC\\xF8\\v\\v\\0\\0\\0\\0A\\xEE\\xF8\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\xB8\\x8A\\0A\\x8C\\xF9\\v\\v\\0\\0\\0\\0A\\x9E\\xF9\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0<;\\0A\\xBC\\xF9\\v\\v\\0\\0\\0\\0A\\xCE\\xF9\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\rf\\0A\\xEC\\xF9\\v\\v\\0\\0\\0\\0A\\xFE\\xF9\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0\\x8F\\0\\0W\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xAE\\xFA\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0nF\\0\\x008\\x81\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xFA\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\00\\0A\\xFC\\xFA\\v\\v\\0\\0\\0\\x80\\0A\\x8E\\xFB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0K\\0\\0>l\\0\\0\\xFA\\0\\0\\xD0\\0\\0\\0\\0\\0\\0A\\xBE\\xFB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0>l\\0\\0q\\x1B\\0\\0M\\x9C\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xEE\\xFB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0>l\\0\\0\\xAEE\\0\\0\\xA2'\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x9E\\xFC\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0>l\\0\\0\\xCC)\\0\\0rR\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xCE\\xFC\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0>l\\0\\0\\x93T\\0\\0\\0\\0\\0\\0@\\0\\0\\0A\\x82\\xFD\\v\\x1B\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\x009\\0\\0j0\\0\\0l\\0\\0\\0\\x07\\0A\\xB6\\xFD\\v$@\\0\\0\\0\\0\\0\\0\\0\\xEC+\\0\\0j0\\0\\0l\\0\\0\\0\\x07\\0A\\xE6\\xFD\\v$@\\0\\0\\0\\0\\0\\0\\0\\xD8H\\0\\0 2\\0\\0\\xD4\\0\\0\\0A\\x92\\xFE\\v\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0\\xD8H\\0\\0\\0\\0#2\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xBE\\xFE\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0?i\\0\\0-C\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xEE\\xFE\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\x7FA\\0A\\x8C\\xFF\\v\\v\\0\\0\\0\\0A\\x9E\\xFF\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\x85P\\0\\0\\xBBP\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\xCE\\xFF\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0.\\0\\0#\\xA7\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xFE\\xFF\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xAFK\\0A\\x9C\\x80\\v\\v\\0\\0\\0@\\0A\\xAE\\x80\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xE8{\\0A\\xCC\\x80\\v\\x07\\v\\0\\0\\0\\0\\0\\x80\\0A\\xDE\\x80\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xD0.\\0A\\xFC\\x80\\v\\v\\0\\0\\0\\x80\\0A\\x8E\\x81\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\x9C_\\0A\\xAC\\x81\\v\\v\\0\\0\\0\\0\\0A\\xBE\\x81\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xE0+\\0\\0\\xB88\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0A\\xEE\\x81\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xD17\\0\\0\\xF4\\xA7\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\b\\0A\\x9E\\x82\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\x006!\\0A\\xBC\\x82\\v\\v\\0\\0\\0\\0\\0A\\xCE\\x82\\v$\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\0\\0\\xAC\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\x80\\0A\\xFE\\x82\\v%\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0j\\0\\0m\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0A\\xAE\\x83\\v&\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0\\xA2i\\0\\0H:\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0A\\xDE\\x83\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xD8H\\0\\0@L\\0\\0\\x90:\\0\\0\\xE4\\0\\0\\0\\0A\\x8E\\x84\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0&g\\0\\0>R\\0\\0\\xE8\\0\\0\\0\\0A\\xBE\\x84\\v\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0
(\\0\\0M(\\0\\0\\x98\\0\\0\\0A\\x98\\xC9\\v;\\0\\0\\0\\0\\0\\0\\0[\\0\\0\\xB0f\\0\\0\\x9C\\0\\0\\0A\\xC8\\xC9\\vE:\\0\\0\\0\\0\\0\\0\\0\\xDF\\0\\0\\xFE\\0\\0t\\0\\0\\0\\r\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\xF0\\xBF\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0L\\0\\0IL\\0\\0\\\\\\0\\0\\0\\f\\0A\\xA2\\xCA\\v"\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0% \\0\\0\\xB0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\x7F\\0A\\xD2\\xCA\\v"\\xC0\\xFF\\xFF\\xFF\\xDFA;\\0\\0\\0\\0\\0\\0\\0R%\\0\\0^%\\0\\0\\xC0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\x7F\\0A\\x82\\xCB\\v\\xC0\\xFF\\xFF\\xFF\\xDFA\\v\\0\\0\\0\\0\\0\\0\\0\\xA4!\\0\\0\\0\\0\\0\\0\\xEC\\0\\0\\0\\0\\0\\0A\\xB2\\xCB\\v\\xE0\\xFF\\xFF\\xFF\\xEFA\\0\\0\\0\\xA4!\\0\\0\\xBCC\\0\\0/)\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xDE\\xCB\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xA4!\\0\\0\\xECF\\0\\0\\x91\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\x8E\\xCC\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xA4!\\0\\0uH\\0\\0B\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0A\\xBE\\xCC\\v#\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xA4!\\0\\0\\x93\\0\\0z\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\b\\0A\\xEE\\xCC\\vS\\xE0\\xC1\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\xA4!\\0\\0\\x89"\\0\\0k*\\0\\0\\xF0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\xF0\\xBF\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA\\0\\0\\0\\0\\0\\0\\0\\xA1[\\0\\0\\xACR\\0\\0\\xBC\\0\\0\\0\\0\\0_\\0A\\xD6\\xCD\\vY@\\0\\0\\0\\0\\0\\0\\0\\xF0\\0\\0\\x89A\\0\\0\\xD8\\0\\0\\0\\0A\\xFE\\xCD\\v\\xF0\\xBF\\0\\0\\xC0\\xFF\\xFF\\xFF\\xDFA:\\0\\0\\0b\\0\\0\\xBD;\\0A\\x9C\\xCE\\v\\v\\0\\0\\0\\0A\\xB8\\xCE\\v + +\\0\\0\\0b\\0\\0\\xA5\\0A\\xCC\\xCE\\v\\v\\0\\0\\0\\0A\\xE8\\xCE\\v + +\\0\\0\\0b\\0\\0\\xEAD\\0A\\xFC\\xCE\\v\\v\\0\\0\\0\\0A\\x98\\xCF\\v + +\\0\\0\\0b\\0\\0\\xBAh\\0A\\xAC\\xCF\\v\\v\\0\\0\\0\\0A\\xC8\\xCF\\v + +\\0\\0\\0b\\0\\0GP\\0A\\xDC\\xCF\\v\\v\\0\\0\\0\\x07\\0A\\xF8\\xCF\\v + +\\0\\0\\0b\\0\\0\\xCD{\\0A\\x8C\\xD0\\v\\v\\0\\0\\0\\0A\\xA8\\xD0\\v + +\\0\\0\\0b\\0\\0\\x96C\\0A\\xBC\\xD0\\v\\v\\0\\0\\0\\0A\\xD8\\xD0\\v + +\\0\\0\\0b\\0\\0\\xAFT\\0A\\xEC\\xD0\\v\\v\\0\\0\\0\\0A\\x88\\xD1\\v + +\\0\\0\\0b\\0\\0EK\\0A\\x9C\\xD1\\v\\v\\0\\0\\0(\\0A\\xB8\\xD1\\v +\\0\\0\\0b\\0A\\xF0\\xD1\\v\\xA4\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\xEB\\x86\\0\\0\\0\\0\\0<\\x87\\0\\0\\0\\0\\0k\\x8F\\0\\0\\0\\0\\0H\\x86\\0\\0&\\0\\0\\0G\\x86\\0\\0\\0\\0\\0\\0\\xD2;\\0\\0\\0\\0\\0B\\x82\\0\\0\\0\\0\\0\\xEA\\x82\\0\\0)\\0\\0\\0;\\x87\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\x93\\x81\\0\\0\\0\\0\\0\\xE5\\x81\\0\\0(\\0\\0\\0v\\x8D\\0\\x002\\0\\0\\0\\x88\\0\\0<\\0\\0\\0+\\x88\\0\\0=\\0\\0\\0\\xA3\\x80\\0\\0>\\0\\0\\0\\x8C\\x80\\0\\0F\\0\\0\\0m\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xA0\\xD3\\vT\\0\\0\\0\\x86\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\xA7\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\\\\\x86\\0\\0\\0\\0\\0w\\x87\\0\\0\\0\\0\\0\\xBD\\x82\\0\\0\\0\\0\\0\\xDC\\x82\\0\\0\\0\\0\\0\\\`\\x80\\0\\0\\0\\0\\0?\\x8D\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\x80\\xD4\\v\\x94B\\0\\0\\0PQ\\0\\0B\\0\\0DQ\\0\\0M\\0\\0\\0\\xD2;\\0\\0X\\0\\0\\0!h\\0\\0d\\0\\0\\0pH\\0\\0n\\0\\0\\0\\x85\\x97\\0\\0n\\b\\0\\0l~\\0\\0z\\0\\0\\0\\xBD\\x90\\0\\0z\\b\\0\\0[~\\0\\0\\x90\\0\\0\\0\\x91\\x8C\\0\\0\\xF4\\0\\0\\0|L\\0\\0\\xF4\\b\\0\\0J~\\0\\0,\\0\\0\\0\\xB6\\x8C\\0\\0v\\0\\0\\0ZH\\0\\0\\x80\\0\\0\\0iH\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0}\\x97\\0\\0!\\0\\0\\0\\xC2\\x8C\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xA0\\xD5\\v4\\0\\0\\0\\xD2;\\0\\0\\0\\0\\0}\\x97\\0\\0\\0\\0\\0dO\\0\\0\\0\\0\\0\\x8E\\0\\0\\0\\0\\0\\xC8;\\0\\0 \\0\\0\\0]l\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xE0\\xD5\\v\\xF4\\0\\0\\0\\xAA\\x98\\0\\0\\0\\0\\0\\xFB\\x95\\0\\0\\0\\x80\\0\\x004\\0\\0\\0\\0\\0\\xDD\\x84\\0\\0\\0\\0\\0\\xC1\\x84\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\xC2\\x90\\0\\0\\0\\0\\0pH\\0\\0\\0\\0\\0\\x87V\\0\\0\\0\\0\\0\\x9AV\\0\\0\\0\\0\\0\\xD2;\\0\\0\\0\\0\\0xT\\0\\0\\x9C\\xFF\\xFF\\xFF\\\`f\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\xB2U\\0\\0\\0\\0\\0\\xC1U\\0\\0\\0\\0\\0zU\\0\\0\\0\\0\\0\\vU\\0\\0\\0\\0\\0\\xC9T\\0\\0\\0\\0\\x009U\\0\\0\\0\\0\\0\\xEDT\\0\\0\\x07\\0\\0\\0RU\\0\\0\\b\\0\\0\\0\\xEFU\\0\\0 \\0\\0\\0\\x87U\\0\\0 +\\0\\0\\0\\xD9U\\0\\0\\v\\0\\0\\0U\\0\\0\\f\\0\\0\\0qU\\0\\0\\r\\0\\0\\x000U\\0\\0\\0\\0\\0\\xD7T\\0\\0\\0\\0\\0\\xA9U\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xE4\\xD7\\v xT\\0\\0\\0\\0\\0\\xD2;\\0\\0\\0\\0\\0\\xFE\\0\\0\\0\\0\\0*h\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\x94\\xD8\\v \\xCD\\x98\\0\\0\\0\\0\\0T\\x96\\0\\0\\0\\0\\0\\xE2\\x92\\0\\0\\0\\0\\0>\\x8F\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xC4\\xD8\\vP\\xD2;\\0\\0\\0\\0\\0pH\\0\\0\\0\\0\\0tD\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\xAA\\r\\0\\0\\0\\0\\0\\x81\\0\\0\\0\\0\\0\\0e\\0\\0\\0\\0\\0\\xE2\\x82\\0\\0\\0\\0\\0:\\x8D\\0\\0\\0\\0\\0\\xA4\\x82\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xA4\\xD9\\v\\xCD\\x80\\0\\0\\0\\0\\0\\xD5\\x82\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\xC0\\xD9\\vD\\xC0\\0\\0\\0PQ\\0\\0\\xC1\\0\\0\\0\\xAFD\\0\\0\\xC2\\0\\0\\0\\xC5L\\0\\0\\xC3\\0\\0\\0\\xCD\\0\\0\\xF7\\0\\0\\0\\xDD\\x81\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0r\\x88\\0\\0\\0\\0\\0W\\x87\\0\\0\\x9D\\xFF\\xFF\\xFF\\0A\\x94\\xDA\\v\\x9DPQ\\0\\0\\0\\0\\0\\xD2;\\0\\0\\x9D\\xFF\\xFF\\xFF\\0\\0\\0\\0\\xDEC\\0\\0f\\0\\0\\0g\\0\\0\\0h\\0\\0\\0i\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\x0033\\xF3@\\0\\0\\x0033\\xF3@\\0\\0\\x0033\\xF3@ \\0\\0\\x0033\\xF3@(\\0\\0\\x0033\\xF3@0\\0\\0\\x0033\\xF3@8\\0\\0\\x0033\\xF3@@\\0\\0\\0\\xCD\\xCC\\xEC@P\\0\\0\\0\\0\\0\\xE0@\\\`\\0\\0\\x0033\\xD3@p\\0\\0\\0ff\\xC6@\\x80\\0\\0\\0ff\\xC6@\\xA0\\0\\0\\0ff\\xC6@\\xB6l\\xD8\\xB2f\\xC6\\x9666\\0A\\xC0\\xDB\\v\\x8EZ\\x84\\0\\0\\xEC\\0\\0\\x8D\\x82\\0\\0\\xEA\\0\\0\\x87\\0\\0\\x1B*\\0\\0\\xD9\\x85\\0\\0)\\0\\0\\x9E\\x84\\0\\0\\0\\0\\x95\\x82\\0\\0\\0\\0\\xEA\\x86\\0\\0\\xEE)\\0\\0\\xAA\\x86\\0\\0\\xD8)\\0\\x008\\x87\\0\\x007*\\0\\0E\\x84\\0\\x006\\0\\0C\\x82\\0\\x009\\0\\0\\xA2\\x86\\0\\0(*\\0\\0U\\x84\\0\\0\\xD6\\0\\0\\xFB\\x86\\0\\0*\\0\\0\\x88\\x82\\0\\0\\xD3\\0\\0\\x9D\\x84\\0\\0\\0\\x007\\x87\\0\\x003*\\0\\0\\x94\\x82\\0\\0\\x1B\\0A\\xA8\\xDD\\v~^\\x84\\0\\0\\xC9\\0\\0\\x91\\x82\\0\\0\\xC5\\0\\x009\\x84\\0\\0(\\0\\x007\\x82\\0\\0*\\0\\0]\\x84\\0\\0\\xF7\\0\\0\\x90\\x82\\0\\0\\xF6\\0\\0\\xB1\\x90\\0\\0\\x8F\\x91\\0\\0@\\x84\\0\\x002\\0\\0>\\x82\\0\\x005\\0\\0\\xFF\\x86\\0\\0*\\0\\0Y\\x84\\0\\0\\xE5\\0\\0\\x8C\\x82\\0\\0\\xE3\\0\\0D\\x84\\0\\0R\\0\\0B\\x82\\0\\0W\\0\\0<\\x84\\0\\0@\\0\\0:\\x82\\0\\0D\\0A\\xA8\\xDF\\vQ\\x84\\0\\0\\f\\0\\0\\x84\\x82\\0\\0\\f\\0A\\xC0\\xDF\\ve0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xDF\\v02\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xE0\\v=\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\v\\0A\\xA0\\xE0\\v\\xEA\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0A\\xC0\\xE0\\vp\\xA1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xE0\\v\\xD0\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0A\\x80\\xE1\\v\\x82h\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x003\\0A\\xA0\\xE1\\vY\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xE1\\v9\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xE1\\v\\xC2\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x007\\0A\\x80\\xE2\\vn\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0A\\xA0\\xE2\\v1\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xE2\\v-\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0?\\0A\\xE0\\xE2\\vd\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xE3\\v\\xBE\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\x07\\0A\\xA0\\xE3\\v\\x8E\\x9C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xC3\\0A\\xC0\\xE3\\v}\\x91\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0P\\0A\\xE0\\xE3\\v\\x81D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x007\\0A\\x80\\xE4\\v)\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\x07\\0A\\xA0\\xE4\\vf\\xA1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0?\\0A\\xC0\\xE4\\vx\\x9C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\xCB\\0A\\xE0\\xE4\\v\\xB0\\x97\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\x007\\0A\\x80\\xE5\\v\\x83\\x9C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\xC7\\0A\\xA0\\xE5\\v%\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0?\\0A\\xC0\\xE5\\v@\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\xFF\\0A\\xE0\\xE5\\vJ\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\xCF\\0A\\x80\\xE6\\vw\\x91\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0V\\0A\\xA0\\xE6\\vZ\\xA1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0?P\\0A\\xC0\\xE6\\v\\x8BD\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\x007\\x07\\0A\\xE0\\xE6\\v\\x87\\\`\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\x003\\xD0\\0A\\x80\\xE7\\v\\x9F\\x8D\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\xD6\\0A\\xA0\\xE7\\vq\\x91\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0?V\\0A\\xC0\\xE7\\v\\x99\\x8D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0?\\xD6\\0A\\xE1\\xE7\\v\\x8F\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0?V\\0\\b\\0A\\x80\\xE8\\v\\x93\\x8D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xD6\\0A\\xA0\\xE8\\v\\x8B\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xD6\\x000\\0A\\xC0\\xE8\\v\\x95D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x007\\xF7\\x80\\0A\\xE0\\xE8\\v\\r6D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF7\\xE8\\v\\\`\\0\\0\\0\\0\\0\\0\\0\\0\\xCE\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\\`\\0A\\xA0\\xE9\\vl\\x91\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\0\\xF8\\0A\\xC0\\xE9\\v?\\xC0\\xAF\\0\\xE0\\xAF\\0\\0\\xB0\\0\\\`\\xB0\\0\\xE0\\xB0\\0@\\xB1\\0\\0\\xB2\\0\\xA0\\xB2\\0\\0\\0\\0\\0\\x80\\xB3\\0\\0\\0\\0\\0\\xC0\\xB3\\0\\0\\0\\0\\0\\0\\xB4\\0\\0\\0\\0\\0 \\xB4\\0A\\x9C\\xEA\\v\\xAD\\xA0\\xB4\\0Bitstream filter not found\\0Internal bug, should not have happened\\0Buffer too small\\0Decoder not found\\0Demuxer not found\\0Encoder not found\\0End of file\\0Immediate exit requested\\0Generic error in an external library\\0Filter not found\\0Input changed\\0Invalid data found when processing input\\0Muxer not found\\0Option not found\\0Output changed\\0Not yet implemented in FFmpeg, patches welcome\\0Protocol not found\\0Stream not found\\0Unknown error occurred\\0Experimental feature\\0Input and output changed\\0Server returned 400 Bad Request\\0Server returned 401 Unauthorized (authorization failed)\\0Server returned 403 Forbidden (access denied)\\0Server returned 404 Not Found\\0Server returned 429 Too Many Requests\\0Server returned 4XX Client Error, but not one of 40{0,1,3,4}\\0Server returned 5XX Server Error reply\\0\\0\\0\\0\\xCC\\x81\\0\\0\\0\\0\\0\\0\\0\\0\\0d<\\0\\0\\0\\0\\b\\0A\\xF0\\xF0\\vA\\xC44\\0A\\xD0\\0\\0\\xC4\\0\\0\\xE2\\0\\0 \\xFD\\0\\0(\\0\\0"\\0\\0\\x07"\\0A\\xB0\\xF1\\v. \\xFA\\0\\0\\xDB\\0\\0\\xC9\\0\\0\\xD5\\0\\0\\xCF\\0\\03\\0\\0'\\0\\0\\x9B\\0\\0\\xC0\\0\\0\\x99\\0\\0\\x93\\0\\0\\xD6\\0A\\xD0\\xF2\\v\\xD5\\0\\0\\xCF\\0\\0\\xD5\\0\\0\\xCF\\0\\0\\xD5\\0\\0\\xCF\\0A\\xF8\\xF2\\vN\\0\\0\\0\\xCC\\x88\\0\\0\\0\\0\\0\\xA7\\x88\\0\\0\\b\\0\\0\\0<\\x89\\0\\0\\b\\0\\0\\0\\x89\\0\\0\\0\\0\\0\\xC4\\x88\\0\\0\\0\\0\\0\\xFC\\x88\\0\\0\\b\\0\\0\\0\\xF1\\x88\\0\\0\\0\\0\\0\\x8D\\x88\\0\\0\\0\\0\\0\\x80\\x88\\0\\0\\b\\0\\0\\x003\\x89\\0A\\xD0\\xF3\\v]\\b\\0\\0\\0\\x89\\0\\0\\0\\0\\0\\xAD\\x88\\0\\0\\0\\0\\0\\xB7\\x88\\0\\0\\b\\0\\0\\0\\x89\\0\\0\\b\\0\\0\\0\\xDC\\x88\\0\\0\\0\\0\\0\\xD4\\x88\\0\\0\\0\\0\\0\\xE7\\x88\\0\\0\\0\\0\\0\\x96\\x88\\0\\0\\0\\0\\0(\\x89\\0\\0D\\0\\0\\0\\0\\0\\0\\0\\0\\0d<\\0(\\0\\0\\0,\\0A\\xD6\\xF4\\v\\v\\xB0C\\0\\xC8Ngm\\xC1\\xABC\\0A\\x86\\xF5\\v\\v\\xD0A\\0\\0\\0\\0e\\xCD\\xCDA \\0A\\xE6\\xF5\\v\\v\\x90@\\0\\0\\0\\0\\0@\\x8F@\\0A\\x96\\xF6\\v\\v0A\\0\\0\\0\\0\\x80\\x84.A\\0A\\xDE\\xF6\\v\\vC\\0\\x004&\\xF5k\\fC\\0A\\xBE\\xF7\\v\\vpB\\0\\0\\0\\xA2\\x94mB\\f\\0A\\xB6\\xF8\\v\\v\\xF0D\\xB4\\x9D\\xD9yCx\\xEAD\\0A\\xCE\\xF8\\v\\vPDP\\xEF\\xE2\\xD6\\xE4KD\\0A\\xF6\\xF9\\v\\v0<\\xACC\\xD2\\xD1]r2<\\xEE\\0A\\xA0\\xFA\\v)\\x8Ar\\x8D\\xF9\\xA2(\\x84?{\\xAEG\\xE1z\\x84?\\xFE\\0\\0\\0\\0\\0\\0\\0 n=\\xA5\\xFEeY@\\0\\0\\0\\0\\0\\0Y@\\0A\\xE6\\xFB\\v\\v\\x90@\\0\\0\\0\\0\\0@\\x8F@\\0A\\x96\\xFC\\v\\vP?\\xFC\\xA9\\xF1\\xD2MbP?\\xFD\\0A\\xAE\\xFC\\v\\v>\\x95\\xD6&\\xE8\\v.>\\xF7\\0A\\xDE\\xFC\\v\\vp=\\xEA-\\x81\\x99\\x97q=\\xF4\\0A\\xD6\\xFD\\v\\v\\xB0>\\x8D\\xED\\xB5\\xA0\\xF7\\xC6\\xB0>\\xFA\\0A\\xB6\\xFE\\v\\v\\xF0:\\xA7\\x8E\\xA8\\x99\\xC2W\\xF3:\\xE8\\0A\\xCE\\xFE\\vD\\x90;O\\x9B +\\xB4\\xE3\\x92;\\xEB\\0\\0\\0\\0\\0\\0\\0\\x86\\0\\0\\0\\0\\0\\0iW\\x8B +\\xBF@\\xF9\\x84\\0\\0\\0\\0\\0\\0-DT\\xFB! @\\x85\\0\\0\\0\\0\\0\\0\\xA8\\xF4\\x97\\x9Bw\\xE3\\xF9?N\\x88\\0A\\x9D\\xFF\\v\\x95\\x80]@i\\0\\0\\xD0\\0\\0\\xE0\\0\\0ZD\\0\\0\\xD0\\0\\0@\\0\\0i\\0\\0\\\`\\0\\0\\xF0\\0\\0\\0OD\\0\\0\\\`\\0\\0 \\0\\0i\\0\\0\\x80\\0\\0\\xE0\\0\\0JD\\0\\0\\0\\0\\0@\\0\\0\\xAF@\\0\\0\\\`\\0\\0\\xF0\\0\\0\\0\\xAA@\\0\\0\\\`\\0\\0\\xF0\\0\\0\\0TK\\0\\0\\x80\\0\\0\\0\\\`\\0\\0\\0UK\\0\\0\\xB0\\0\\0\\0\\x90\\0\\0\\0aK\\0\\0\\\`\\0\\0 \\0\\0\\\`K\\0\\0\\xC0\\0\\0@\\0\\0ZK\\0\\0\\x80\\0\\0\\x80\\0\\0\\xA4\\x7F\\0\\0\\xA0\\0\\0\\0x\\0\\0\\0\\xAB\\x7F\\0\\0@\\0\\0\\xF0\\0\\0\\0\\xB1\\x7F\\0\\0\\x80\\0\\0\\xE0\\0\\0\\x98\\x7F\\0\\0 \\0\\0X\\0\\0\\x8F\\x7F\\0\\0\\0\\0\\0\\0\\0\\0o\\x7F\\0\\0@\\0\\0\\xB0\\0\\0\\x88\\x7F\\0\\0\\0\\b\\0\\0\\0\\0\\0\\x83\\x7F\\0\\0\\0\\0\\0\\0\\0\\0{\\x7F\\0\\0\\0 +\\0\\0\\0\\b\\0\\0\\x82\\x7F\\0\\0\\0\\0\\0\\0\\0\\0\\x93\\x7F\\0\\0T\\0\\0\\xE0\\0\\0[\\x7F\\0\\0V\\0\\0\\0\\0\\0t\\x7F\\0\\0@\\0\\0\\0\\0\\0\\\`\\x7F\\0\\0\\x80\\x07\\0\\0\\xB0\\0\\0\\x8D\\x7F\\0\\0\\0 +\\0\\0@\\0\\0 M\\0\\0\\x90\\xFF\\0\\xDAE\\0\\0\\xB2""\\0\\xF7M\\0\\0\\xFF\\xFA\\xF0\\0p<\\0\\0"\\x8B"\\0\\xD9~\\0\\0\\xFF\\0\\xFF\\0M0\\0\\0\\xDC\\xDC\\xDC\\0\\xE0M\\0\\0\\xF8\\xF8\\xFF\\0\\xF9e\\0\\0\\xFF\\xD7\\0\\0\\\`e\\0\\0\\xDA\\xA5 \\0\\x8C\\0\\0\\x80\\x80\\x80\\0\\xF4<\\0\\0\\0\\x80\\0\\0\\xB2\\0\\0\\xAD\\xFF/\\0\\xE7\\0\\0\\xF0\\xFF\\xF0\\0\\x8AE\\0\\0\\xFFi\\xB4\\0Qh\\0\\0\\xCD\\\\\\\\\\02\\0\\0K\\0\\x82\\0\\xEF\\r\\0\\0\\xFF\\xFF\\xF0\\0\\x93F\\0\\0\\xF0\\xE6\\x8C\\0\\x8E+\\0\\0\\xE6\\xE6\\xFA\\0\\0H\\0\\0\\xFF\\xF0\\xF5\\0\\x87<\\0\\0|\\xFC\\0\\0/;\\0\\0\\xFF\\xFA\\xCD\\0\\x1BM\\0\\0\\xAD\\xD8\\xE6\\0?D\\0\\0\\xF0\\x80\\x80\\0\\xFE<\\0\\0\\xE0\\xFF\\xFF\\0\\xBE\\0\\0\\xFA\\xFA\\xD2\\0|<\\0\\0\\x90\\xEE\\x90\\0\\0\\0\\xD3\\xD3\\xD3\\0\\x92E\\0\\0\\xFF\\xB6\\xC1\\x0098\\0\\0\\xFF\\xA0z\\0\\xD0<\\0\\0 \\xB2\\xAA\\0\\xF5L\\0\\0\\x87\\xCE\\xFA\\0t\\0\\0w\\x88\\x99\\0_M\\0\\0\\xB0\\xC4\\xDE\\0\\xA6\\0\\0\\xFF\\xFF\\xE0\\0\\xACQ\\0\\0\\0\\xFF\\0\\0\\xBC<\\0\\x002\\xCD2\\0 +<\\0\\0\\xFA\\xF0\\xE6\\0\\xC5{\\0\\0\\xFF\\0\\xFF\\x0028\\0\\0\\x80\\0\\0\\x003Q\\0\\0f\\xCD\\xAA\\0TM\\0\\0\\0\\0\\xCD\\0f\\0\\0\\xBAU\\xD3\\0dT\\0\\0\\x93p\\xD8\\0\\xDE<\\0\\0<\\xB3q\\0\\x81M\\0\\0{h\\xEE\\0\\x9B<\\0\\0\\0\\xFA\\x9A\\0\\xC3N\\0\\0H\\xD1\\xCC\\x003h\\0\\0\\xC7\\x85\\0M\\0\\0p\\0\\x91B\\0\\0\\xF5\\xFF\\xFA\\0\\xB9N\\0\\0\\xFF\\xE4\\xE1\\0q;\\0\\0\\xFF\\xE4\\xB5\\0\\xEBM\\0\\0\\xFF\\xDE\\xAD\\0\\xB0\\r\\0\\0\\0\\0\\x80\\0x\\\`\\0\\0\\xFD\\xF5\\xE6\\0\\xDCL\\0\\0\\x80\\x80\\0\\0|{\\0\\0k\\x8E#\\0g[\\0\\0\\xFF\\xA5\\0\\0ch\\0\\0\\xFFE\\0\\0/f\\0\\0\\xDAp\\xD6\\0\\\\e\\0\\0\\xEE\\xE8\\xAA\\0\\xC6<\\0\\0\\x98\\xFB\\x98\\0\\xE1N\\0\\0\\xAF\\xEE\\xEE\\0Ch\\0\\0\\xD8p\\x93\\0\\xD5.\\0\\0\\xFF\\xEF\\xD5\\0mK\\0\\0\\xFF\\xDA\\xB9\\0\\xE2\\0\\0\\xCD\\x85?\\0\\xA0E\\0\\0\\xFF\\xC0\\xCB\\0N@\\0\\0\\xDD\\xA0\\xDD\\0IM\\0\\0\\xB0\\xE0\\xE6\\0jT\\0\\0\\x80\\0\\x80\\0ih\\0\\0\\xFF\\0\\0\\0\\xEC7\\0\\0\\xBC\\x8F\\x8F\\0nM\\0\\0Ai\\xE1\\08\\0\\0\\x8BE\\0I8\\0\\0\\xFA\\x80r\\0\\xF67\\0\\0\\xF4\\xA4\\\`\\0\\xF1<\\0\\0.\\x8BW\\0oC\\0\\0\\xFF\\xF5\\xEE\\0\\x9F~\\0\\0\\xA0R-\\0()\\0\\0\\xC0\\xC0\\xC0\\0M\\0\\0\\x87\\xCE\\xEB\\0\\x95M\\0\\0jZ\\xCD\\0\\x87\\0\\0p\\x80\\x90\\0\\x83\\0\\0\\xFF\\xFA\\xFA\\0\\xA1<\\0\\0\\0\\xFF\\x7F\\0dM\\0\\0F\\x82\\xB4\\0:@\\0\\0\\xD2\\xB4\\x8C\\0\\xE5D\\0\\0\\0\\x80\\x80\\0\\xE4R\\0\\0\\xD8\\xBF\\xD8\\0%0\\0\\0\\xFFcG\\0\\xE5N\\0\\0@\\xE0\\xD0\\0\\xDA\\0\\0\\xEE\\x82\\xEE\\0B\\x1B\\0\\0\\xF5\\xDE\\xB3\\0 +N\\0\\0\\xFF\\xFF\\xFF\\0\\xFBZ\\0\\0\\xF5\\xF5\\xF5\\0\\xCC\\0\\0\\xFF\\xFF\\0\\0d<\\0\\0\\x9A\\xCD2\\0\\0h\\0\\0\\xE4g\\0\\0w\\0\\0\\0\\0\\0\\0\\xB7/\\0\\0\\0\\0A\\xA4\\x8E\\x07\\v\\0A\\xB0\\x8E\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xC4\\x8E\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xD8\\x8E\\x07\\v\\b\\0A\\xF8\\x8E\\x07\\vG\\x91\\0\\0\\0A\\x8C\\x8F\\x07\\v\\0A\\x98\\x8F\\x07\\v)\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xE0\\x8F\\x07\\v [\\x8D\\0\\0\\0\\0\\0 \\0A\\xF4\\x8F\\x07\\v\\0A\\x80\\x90\\x07\\v)\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xC8\\x90\\x07\\v U\\x8D\\0\\0\\0\\0\\0 \\0A\\xDC\\x90\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\x90\\x91\\x07\\v\\b\\0A\\xB0\\x91\\x07\\v v/\\0\\0\\0\\0\\0A\\xC4\\x91\\x07\\v\\0A\\xD0\\x91\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xE4\\x91\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF8\\x91\\x07\\v\\b\\0A\\x98\\x92\\x07\\v N/\\0\\0\\0\\0\\0\\0A\\xAC\\x92\\x07\\v\\0A\\xB8\\x92\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xCC\\x92\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xE0\\x92\\x07\\v\\b\\0A\\x80\\x93\\x07\\v \\xD1/\\0\\0\\0\\0A\\x94\\x93\\x07\\v\\0A\\xA0\\x93\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xB4\\x93\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xC8\\x93\\x07\\v\\b\\0A\\xE8\\x93\\x07\\v \\x95/\\0\\0\\0\\0\\0A\\xFC\\x93\\x07\\v\\0A\\x88\\x94\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x9C\\x94\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xB0\\x94\\x07\\v\\b\\0A\\xD0\\x94\\x07\\v^\\0\\0\\0A\\xE4\\x94\\x07\\v\\0A\\xF0\\x94\\x07\\v\\b\\0A\\xB0\\x95\\x07\\v\\x8C\\x89\\0\\0\\0\\0\\0\\0}\\0\\0\\0\\0\\0\\0A\\xCC\\x95\\x07\\v\\0A\\xD8\\x95\\x07\\v\\0A\\xA0\\x96\\x07\\v M{\\0\\0\\0\\0\\0\\0A\\xB4\\x96\\x07\\v\\r\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\0A\\x88\\x97\\x07\\v \\xB8\\x89\\0\\0\\0\\0\\0\\x82\\0A\\x9C\\x97\\x07\\v\\0A\\xA8\\x97\\x07\\v\\b\\0A\\xF0\\x97\\x07\\v \\xBF/\\0\\0\\0\\0A\\x84\\x98\\x07\\v\\0A\\x90\\x98\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xA4\\x98\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xB8\\x98\\x07\\v\\b\\0A\\xD8\\x98\\x07\\v ~/\\0\\0\\0\\0\\0A\\xEC\\x98\\x07\\v\\0A\\xF8\\x98\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x8C\\x99\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xA0\\x99\\x07\\v\\b\\0A\\xC0\\x99\\x07\\v V/\\0\\0\\0\\0\\0\\0A\\xD4\\x99\\x07\\v\\0A\\xE0\\x99\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF4\\x99\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x88\\x9A\\x07\\v\\b\\0A\\xA8\\x9A\\x07\\v?\\x91\\0\\0\\0A\\xBC\\x9A\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xDC\\x9A\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\x90\\x9B\\x07\\v\\x95\\0\\0\\0A\\xA4\\x9B\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xC4\\x9B\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xF8\\x9B\\x07\\v \\xAF\\x89\\0\\0\\0\\0\\0 \\0A\\x8C\\x9C\\x07\\v\\0A\\x98\\x9C\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\x9C\\x07\\v \\xA2\\x8B\\0\\0\\0\\0\\0$\\0A\\xF4\\x9C\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\x9D\\x07\\v\\0A\\xC8\\x9D\\x07\\v \\xC8M\\0\\0\\0\\0\\0 \\0A\\xDC\\x9D\\x07\\v\\0A\\xE8\\x9D\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\x9E\\x07\\v \\xDF\\x89\\0\\0\\0\\0\\0 \\0A\\xC4\\x9E\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF8\\x9E\\x07\\v\\0A\\x98\\x9F\\x07\\v \\xF9\\x8B\\0\\0\\0\\0\\0$\\0A\\xAC\\x9F\\x07\\v\\0A\\xB8\\x9F\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xA0\\x07\\v \\xD2M\\0\\0\\0\\0\\0 \\0A\\x94\\xA0\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xA0\\x07\\v\\0A\\xE8\\xA0\\x07\\v W\\x91\\0\\0\\0\\0A\\xFC\\xA0\\x07\\v\\0A\\x88\\xA1\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xA1\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD0\\xA1\\x07\\v \\v\\x95\\0\\0\\0\\0A\\xE4\\xA1\\x07\\v\\0A\\xF0\\xA1\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0A\\x98\\xA2\\x07\\v\\b\\0A\\xB8\\xA2\\x07\\v h{\\0\\0\\0\\0\\0\\xA0\\0A\\xCC\\xA2\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\x94\\xA3\\x07\\v\\b\\0A\\xA0\\xA3\\x07\\v \\x80\\0\\0\\0\\0\\0\\xA0\\0A\\xB4\\xA3\\x07\\v\\0A\\xC0\\xA3\\x07\\v=\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\x88\\xA4\\x07\\v \\f)\\0\\0\\0\\0\\0\\xA0\\0A\\x9C\\xA4\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xE4\\xA4\\x07\\v\\b\\0A\\xF0\\xA4\\x07\\v \\x88~\\0\\0\\0\\0\\0\\xA0\\0A\\x84\\xA5\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xB8\\xA5\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD8\\xA5\\x07\\v Ga\\0\\0\\0\\0\\0\\0A\\xEC\\xA5\\x07\\v\\0A\\xF8\\xA5\\x07\\v\\0A\\xB8\\xA6\\x07\\v\\rJa\\0\\0\\0\\0\\0\\0tW\\0\\0\\0A\\xD4\\xA6\\x07\\v\\0A\\xE0\\xA6\\x07\\v\\0A\\xA0\\xA7\\x07\\vwW\\0\\0\\0\\0\\0\\0\\xA6/\\0\\0\\0\\0\\0A\\xBC\\xA7\\x07\\v\\0A\\xC8\\xA7\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xA7\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xA7\\x07\\v\\b\\0A\\x90\\xA8\\x07\\v \\xAE/\\0\\0\\0\\0\\0A\\xA4\\xA8\\x07\\v\\0A\\xB0\\xA8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xC4\\xA8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xA8\\x07\\v\\b\\0A\\xF8\\xA8\\x07\\v \\xC8/\\0\\0\\0\\x90\\0A\\x8C\\xA9\\x07\\v\\0A\\x98\\xA9\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xAC\\xA9\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xA9\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xA9\\x07\\v\\b\\0A\\xE0\\xA9\\x07\\v &a\\0\\0\\0\\0\\0!\\0A\\xF4\\xA9\\x07\\v\\0A\\x80\\xAA\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xAA\\x07\\v FW\\0\\0\\0\\0\\0 \\0A\\xDC\\xAA\\x07\\v\\0A\\xE8\\xAA\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xAB\\x07\\v db\\0\\0\\0\\0\\0!\\0A\\xC4\\xAB\\x07\\v)\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF8\\xAB\\x07\\v\\0A\\x98\\xAC\\x07\\v \\x9BX\\0\\0\\0\\0\\0 \\0A\\xAC\\xAC\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xAC\\x07\\v\\0A\\x80\\xAD\\x07\\v vb\\0\\0\\0\\0\\0!\\0A\\x94\\xAD\\x07\\v)\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xAD\\x07\\v\\0A\\xE8\\xAD\\x07\\v \\xADX\\0\\0\\0\\0\\0 \\0A\\xFC\\xAD\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xAE\\x07\\v\\0A\\xD0\\xAE\\x07\\v [b\\0\\0\\0\\0\\0!\\0A\\xE4\\xAE\\x07\\v\\0A\\xF0\\xAE\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0A\\xB8\\xAF\\x07\\v \\x92X\\0\\0\\0\\0\\0 \\0A\\xCC\\xAF\\x07\\v\\0A\\xD8\\xAF\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xB0\\x07\\v mb\\0\\0\\0\\0\\0!\\0A\\xB4\\xB0\\x07\\v\\0A\\xC0\\xB0\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0A\\x88\\xB1\\x07\\v \\xA4X\\0\\0\\0\\0\\0 \\0A\\x9C\\xB1\\x07\\v\\0A\\xA8\\xB1\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xB1\\x07\\v dF\\0\\0\\0\\0\\b\\0A\\xD8\\xB2\\x07\\v \\xE5W\\0\\0\\0\\0A\\xEC\\xB2\\x07\\v\\0A\\xF8\\xB2\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x8C\\xB3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA0\\xB3\\x07\\v\\0A\\xC0\\xB3\\x07\\v \\xB8a\\0\\0\\0\\0A\\xD4\\xB3\\x07\\v\\0A\\xE0\\xB3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF4\\xB3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x88\\xB4\\x07\\v\\0A\\xA8\\xB4\\x07\\v \\xCCW\\0\\0\\0\\0\\0A\\xBC\\xB4\\x07\\v\\0A\\xC8\\xB4\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xDC\\xB4\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF0\\xB4\\x07\\v\\0A\\x90\\xB5\\x07\\v \\x9Fa\\0\\0\\0\\0\\0A\\xA4\\xB5\\x07\\v\\0A\\xB0\\xB5\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC4\\xB5\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\xB5\\x07\\v\\0A\\xF8\\xB5\\x07\\v \\xB3W\\0\\0\\0\\0\\0\\0A\\x8C\\xB6\\x07\\v\\0A\\x98\\xB6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xAC\\xB6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xB6\\x07\\v\\0A\\xE0\\xB6\\x07\\v \\x86a\\0\\0\\0\\0\\0\\0A\\xF4\\xB6\\x07\\v\\0A\\x80\\xB7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x94\\xB7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA8\\xB7\\x07\\v\\0A\\xC8\\xB7\\x07\\v \\xE1e\\0\\0\\0\\0\\b\\0A\\xB0\\xB8\\x07\\v \\xEEX\\0\\0\\0\\0\\0 \\0A\\xC4\\xB8\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF8\\xB8\\x07\\v\\0A\\x98\\xB9\\x07\\v \\xB7b\\0\\0\\0\\0\\0!\\0A\\xAC\\xB9\\x07\\v)\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xB9\\x07\\v\\0A\\x80\\xBA\\x07\\v \\xE5X\\0\\0\\0\\0\\0 \\0A\\x94\\xBA\\x07\\v\\0A\\xA0\\xBA\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xBA\\x07\\v \\xAEb\\0\\0\\0\\0\\0!\\0A\\xFC\\xBA\\x07\\v\\0A\\x88\\xBB\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0A\\xD0\\xBB\\x07\\v \\xF0\\x89\\0\\0\\0\\0\\0\\x80\\0A\\xE4\\xBB\\x07\\v\\0A\\xF0\\xBB\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xB0\\xBC\\x07\\v\\x80\\0\\0\\0\\0\\0\\0a\\0\\0\\0\\0\\0!\\0A\\xCC\\xBC\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xBD\\x07\\v\\0A\\xA0\\xBD\\x07\\v >W\\0\\0\\0\\0\\0 \\0A\\xB4\\xBD\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xBD\\x07\\v\\0A\\x89\\xBE\\x07\\v\\ba\\0\\0\\0\\0A\\x9C\\xBE\\x07\\v\\0A\\xA8\\xBE\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xBC\\xBE\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD0\\xBE\\x07\\v \\0A\\xF0\\xBE\\x07\\v W\\0\\0\\0\\0A\\x84\\xBF\\x07\\v\\0A\\x90\\xBF\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA4\\xBF\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB8\\xBF\\x07\\v \\0A\\xD8\\xBF\\x07\\v {d\\0\\0\\0\\0A\\xEC\\xBF\\x07\\v\\0A\\xF8\\xBF\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x8C\\xC0\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xC0\\x07\\v +\\0A\\xC0\\xC0\\x07\\v \\xBCZ\\0\\0\\0\\0A\\xD4\\xC0\\x07\\v\\0A\\xE0\\xC0\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xF4\\xC0\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x88\\xC1\\x07\\v +\\0A\\xA8\\xC1\\x07\\v Vd\\0\\0\\0\\0\\0A\\xBC\\xC1\\x07\\v\\0A\\xC8\\xC1\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xC1\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xC1\\x07\\v +\\0A\\x90\\xC2\\x07\\v \\x97Z\\0\\0\\0\\0\\0A\\xA4\\xC2\\x07\\v\\0A\\xB0\\xC2\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xC4\\xC2\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xC2\\x07\\v +\\0A\\xF8\\xC2\\x07\\v \\xD2\\\`\\0\\0\\0\\0\\0\\0A\\x8C\\xC3\\x07\\v\\0A\\x98\\xC3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xAC\\xC3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xC3\\x07\\v \\0A\\xE0\\xC3\\x07\\v \\xF2V\\0\\0\\0\\0\\0\\0A\\xF4\\xC3\\x07\\v\\0A\\x80\\xC4\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x94\\xC4\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA8\\xC4\\x07\\v \\0A\\xC8\\xC4\\x07\\v =d\\0\\0\\0\\0\\0\\0A\\xDC\\xC4\\x07\\v\\0A\\xE8\\xC4\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xFC\\xC4\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x90\\xC5\\x07\\v +\\0A\\xB0\\xC5\\x07\\v ~Z\\0\\0\\0\\0\\0\\0A\\xC4\\xC5\\x07\\v\\0A\\xD0\\xC5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xE4\\xC5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xF8\\xC5\\x07\\v +\\0A\\x98\\xC6\\x07\\v \\xE9\\\`\\0\\0\\0\\0\\0A\\xAC\\xC6\\x07\\v\\0A\\xB8\\xC6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xCC\\xC6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xE0\\xC6\\x07\\v \\0A\\x80\\xC7\\x07\\v W\\0\\0\\0\\0\\0A\\x94\\xC7\\x07\\v\\0A\\xA0\\xC7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB4\\xC7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC8\\xC7\\x07\\v \\0A\\xE8\\xC7\\x07\\v.\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\xC8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xC8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xC8\\x07\\v\\b\\0A\\xD0\\xC8\\x07\\v\\xCA\\\`\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xC8\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x84\\xC9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x98\\xC9\\x07\\v \\0A\\xB8\\xC9\\x07\\v\\xEAV\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xC9\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xEC\\xC9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x80\\xCA\\x07\\v \\0A\\xA0\\xCA\\x07\\v*d\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xCA\\x07\\v +\\0\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xCA\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xCA\\x07\\v +\\0A\\x88\\xCB\\x07\\vkZ\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xCB\\x07\\v +\\0\\0\\0\\0\\0\\0\\0\\0A\\xBC\\xCB\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xCB\\x07\\v +\\0A\\xF0\\xCB\\x07\\vsa\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x90\\xCC\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xA4\\xCC\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB8\\xCC\\x07\\v\\0A\\xD8\\xCC\\x07\\v\\xA0W\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF8\\xCC\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x8C\\xCD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA0\\xCD\\x07\\v\\0A\\xC0\\xCD\\x07\\v \\x87/\\0\\0\\0\\0\\x90\\0A\\xD4\\xCD\\x07\\v\\0A\\xE0\\xCD\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF4\\xCD\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x88\\xCE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xCE\\x07\\v\\b\\0A\\xA8\\xCE\\x07\\v _/\\0\\0\\0\\0\\0\\x90\\0A\\xBC\\xCE\\x07\\v\\0A\\xC8\\xCE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xCE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xCE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x84\\xCF\\x07\\v\\b\\0A\\x90\\xCF\\x07\\v \\va\\0\\0\\0\\x91\\0A\\xA4\\xCF\\x07\\v\\0A\\xB0\\xCF\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC4\\xCF\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\xCF\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\xCF\\x07\\v \\0A\\xF8\\xCF\\x07\\v +W\\0\\0\\0\\x90\\0A\\x8C\\xD0\\x07\\v\\0A\\x98\\xD0\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xAC\\xD0\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xD0\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\xD0\\x07\\v \\0A\\xE0\\xD0\\x07\\v \\xF4\\\`\\0\\0\\0\\0\\x91\\0A\\xF4\\xD0\\x07\\v\\0A\\x80\\xD1\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x94\\xD1\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA8\\xD1\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xBC\\xD1\\x07\\v \\0A\\xC8\\xD1\\x07\\v W\\0\\0\\0\\0\\x90\\0A\\xDC\\xD1\\x07\\v\\0A\\xE8\\xD1\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xFC\\xD1\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x90\\xD2\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA4\\xD2\\x07\\v \\0A\\xB0\\xD2\\x07\\v \\xDD\\\`\\0\\0\\0\\0\\0\\x91\\0A\\xC4\\xD2\\x07\\v\\0A\\xD0\\xD2\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xE4\\xD2\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF8\\xD2\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x8C\\xD3\\x07\\v \\0A\\x98\\xD3\\x07\\v \\xFDV\\0\\0\\0\\0\\0\\x90\\0A\\xAC\\xD3\\x07\\v\\0A\\xB8\\xD3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xCC\\xD3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xE0\\xD3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF4\\xD3\\x07\\v \\0A\\x80\\xD4\\x07\\v \\x87d\\0\\0\\0\\x91\\0A\\x94\\xD4\\x07\\v\\0A\\xA0\\xD4\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xB4\\xD4\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xD4\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xD4\\x07\\v +\\0A\\xE8\\xD4\\x07\\v \\xC8Z\\0\\0\\0\\x90\\0A\\xFC\\xD4\\x07\\v\\0A\\x88\\xD5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xD5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xD5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xC4\\xD5\\x07\\v +\\0A\\xD0\\xD5\\x07\\v bd\\0\\0\\0\\0\\x91\\0A\\xE4\\xD5\\x07\\v\\0A\\xF0\\xD5\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x84\\xD6\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x98\\xD6\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xAC\\xD6\\x07\\v +\\0A\\xB8\\xD6\\x07\\v \\xA3Z\\0\\0\\0\\0\\x90\\0A\\xCC\\xD6\\x07\\v\\0A\\xD8\\xD6\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xEC\\xD6\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x80\\xD7\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x94\\xD7\\x07\\v +\\0A\\xA0\\xD7\\x07\\v Id\\0\\0\\0\\0\\0\\x91\\0A\\xB4\\xD7\\x07\\v\\0A\\xC0\\xD7\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xD7\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xD7\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xFC\\xD7\\x07\\v +\\0A\\x88\\xD8\\x07\\v \\x8AZ\\0\\0\\0\\0\\0\\x90\\0A\\x9C\\xD8\\x07\\v\\0A\\xA8\\xD8\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xBC\\xD8\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xD8\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xE4\\xD8\\x07\\v +\\0A\\xF0\\xD8\\x07\\v \\xC4a\\0\\0\\0\\x91\\0A\\x84\\xD9\\x07\\v\\0A\\x90\\xD9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA4\\xD9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB8\\xD9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xCC\\xD9\\x07\\v\\0A\\xD8\\xD9\\x07\\v \\xF1W\\0\\0\\0\\x90\\0A\\xEC\\xD9\\x07\\v\\0A\\xF8\\xD9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x8C\\xDA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA0\\xDA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB4\\xDA\\x07\\v\\0A\\xC0\\xDA\\x07\\v \\xABa\\0\\0\\0\\0\\x91\\0A\\xD4\\xDA\\x07\\v\\0A\\xE0\\xDA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF4\\xDA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x88\\xDB\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x9C\\xDB\\x07\\v\\0A\\xA8\\xDB\\x07\\v \\xD8W\\0\\0\\0\\0\\x90\\0A\\xBC\\xDB\\x07\\v\\0A\\xC8\\xDB\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xDC\\xDB\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF0\\xDB\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x84\\xDC\\x07\\v\\0A\\x90\\xDC\\x07\\v \\x92a\\0\\0\\0\\0\\0\\x91\\0A\\xA4\\xDC\\x07\\v\\0A\\xB0\\xDC\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC4\\xDC\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\xDC\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\xDC\\x07\\v\\0A\\xF8\\xDC\\x07\\v \\xBFW\\0\\0\\0\\0\\0\\x90\\0A\\x8C\\xDD\\x07\\v\\0A\\x98\\xDD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xAC\\xDD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xDD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\xDD\\x07\\v\\0A\\xE0\\xDD\\x07\\v \\xF2\\0\\0\\0\\0\\b\\0A\\xC8\\xDE\\x07\\v +\\xC0Y\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xDE\\x07\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xB0\\xDF\\x07\\v +\\x7Fc\\0\\0\\0\\0\\0\\0A\\xC4\\xDF\\x07\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\x98\\xE0\\x07\\v \\xE1\\x8A\\0\\0\\0\\0\\0A\\xAC\\xE0\\x07\\v\\0A\\xB8\\xE0\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xCC\\xE0\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\x80\\xE1\\x07\\v QZ\\0\\0\\0\\0\\0A\\x94\\xE1\\x07\\v\\0A\\xA0\\xE1\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\xB4\\xE1\\x07\\v +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xE8\\xE1\\x07\\v d\\0\\0\\0\\0\\0A\\xFC\\xE1\\x07\\v\\0A\\x88\\xE2\\x07\\v +\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xE2\\x07\\v +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xD0\\xE2\\x07\\v \\xA5b\\0\\0\\0\\0\\0\\xA1\\0A\\xE4\\xE2\\x07\\v\\b\\0A\\xF0\\xE2\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB8\\xE3\\x07\\v \\xDCX\\0\\0\\0\\0\\0\\xA0\\0A\\xCC\\xE3\\x07\\v\\b\\0A\\xD8\\xE3\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xE4\\x07\\v \\x9Cb\\0\\0\\0\\0\\0\\xA1\\0A\\xB4\\xE4\\x07\\v)\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xE8\\xE4\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\xE5\\x07\\v \\xD3X\\0\\0\\0\\0\\0\\xA0\\0A\\x9C\\xE5\\x07\\v)\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD0\\xE5\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xE5\\x07\\vO\\x91\\0\\0\\0A\\x84\\xE6\\x07\\v\\0A\\x90\\xE6\\x07\\v)\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD8\\xE6\\x07\\v 8b\\0\\0\\0\\0\\0\\x81\\0A\\xEC\\xE6\\x07\\v\\0A\\xF8\\xE6\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xE7\\x07\\v oX\\0\\0\\0\\0\\0\\x80\\0A\\xD4\\xE7\\x07\\v\\0A\\xE0\\xE7\\x07\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xE8\\x07\\v\\xFB.\\0\\0\\0\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xE8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xE8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xE8\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\x84\\xE9\\x07\\v\\b\\0A\\x90\\xE9\\x07\\v|a\\0\\0\\0\\0\\0\\xB1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xE9\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xC4\\xE9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\xE9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\xE9\\x07\\v\\0A\\xF8\\xE9\\x07\\v\\xA9W\\0\\0\\0\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\xEA\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xAC\\xEA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xEA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\xEA\\x07\\v\\0A\\xE0\\xEA\\x07\\v ~\\0\\0\\0\\0\\0\\0\\b\\0A\\xC8\\xEB\\x07\\v \\xAAD\\0\\0\\0\\0\\0\\0\\b\\0A\\xB0\\xEC\\x07\\v \\xD5e\\0\\0\\0\\0\\b\\0A\\x98\\xED\\x07\\v \\xEC\\x7F\\0\\0\\0\\0\\0\\0\\b\\0A\\x80\\xEE\\x07\\v m{\\0\\0\\0\\0\\0 \\0A\\x94\\xEE\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xEE\\x07\\v\\b\\0A\\xE8\\xEE\\x07\\v c\\x96\\0\\0\\0\\0\\0 \\0A\\xFC\\xEE\\x07\\v\\0A\\x88\\xEF\\x07\\v=\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD0\\xEF\\x07\\v )\\0\\0\\0\\0\\0 \\0A\\xE4\\xEF\\x07\\v=\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xAC\\xF0\\x07\\v\\b\\0A\\xB8\\xF0\\x07\\v ^\\x96\\0\\0\\0\\0\\0 \\0A\\xCC\\xF0\\x07\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xF1\\x07\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xA0\\xF1\\x07\\v \\xE1c\\0\\0\\0\\0A\\xB4\\xF1\\x07\\v\\0A\\xC0\\xF1\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xF1\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xF1\\x07\\v\\f\\0A\\x88\\xF2\\x07\\v "Z\\0\\0\\0\\0A\\x9C\\xF2\\x07\\v\\0A\\xA8\\xF2\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xBC\\xF2\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xF2\\x07\\v\\f\\0A\\xF0\\xF2\\x07\\v \\bc\\0\\0\\0\\0A\\x84\\xF3\\x07\\v\\0A\\x90\\xF3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA4\\xF3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xB8\\xF3\\x07\\v\\0A\\xD8\\xF3\\x07\\v IY\\0\\0\\0\\0A\\xEC\\xF3\\x07\\v\\0A\\xF8\\xF3\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x8C\\xF4\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xA0\\xF4\\x07\\v\\0A\\xC0\\xF4\\x07\\v \\xBCc\\0\\0\\0\\0\\0A\\xD4\\xF4\\x07\\v\\0A\\xE0\\xF4\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xF4\\xF4\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x88\\xF5\\x07\\v\\f\\0A\\xA8\\xF5\\x07\\v \\xFDY\\0\\0\\0\\0\\0A\\xBC\\xF5\\x07\\v\\0A\\xC8\\xF5\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xDC\\xF5\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xF5\\x07\\v\\f\\0A\\x90\\xF6\\x07\\v \\xFCb\\0\\0\\0\\0\\0A\\xA4\\xF6\\x07\\v\\0A\\xB0\\xF6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC4\\xF6\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\xF6\\x07\\v\\0A\\xF8\\xF6\\x07\\v =Y\\0\\0\\0\\0\\0A\\x8C\\xF7\\x07\\v\\0A\\x98\\xF7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xAC\\xF7\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\xF7\\x07\\v\\0A\\xE0\\xF7\\x07\\v \\xA3c\\0\\0\\0\\0\\0\\0A\\xF4\\xF7\\x07\\v\\0A\\x80\\xF8\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x94\\xF8\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xF8\\x07\\v\\f\\0A\\xC8\\xF8\\x07\\v \\xE4Y\\0\\0\\0\\0\\0\\0A\\xDC\\xF8\\x07\\v\\0A\\xE8\\xF8\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xFC\\xF8\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x90\\xF9\\x07\\v\\f\\0A\\xB0\\xF9\\x07\\v \\xF0b\\0\\0\\0\\0\\0\\0A\\xC4\\xF9\\x07\\v\\0A\\xD0\\xF9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xE4\\xF9\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xF8\\xF9\\x07\\v\\0A\\x98\\xFA\\x07\\v 1Y\\0\\0\\0\\0\\0\\0A\\xAC\\xFA\\x07\\v\\0A\\xB8\\xFA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xCC\\xFA\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\xE0\\xFA\\x07\\v\\0A\\x80\\xFB\\x07\\v\\x90c\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xFB\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0\\0A\\xB4\\xFB\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xFB\\x07\\v\\f\\0A\\xE8\\xFB\\x07\\v\\xD1Y\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\xFC\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0\\0A\\x9C\\xFC\\x07\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xFC\\x07\\v\\f\\0A\\xD0\\xFC\\x07\\v\\xDDb\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xFC\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x84\\xFD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x98\\xFD\\x07\\v\\0A\\xB8\\xFD\\x07\\vY\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xFD\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xEC\\xFD\\x07\\v \\0\\0\\0\\0\\0\\0\\0A\\x80\\xFE\\x07\\v\\0A\\xA0\\xFE\\x07\\v \\x9D/\\0\\0\\0\\0\\0A\\xB4\\xFE\\x07\\v\\0A\\xC0\\xFE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xFE\\x07\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xFE\\x07\\v\\b\\0A\\x88\\xFF\\x07\\v +\\xA3\\x89\\0\\0\\0\\0\\0 \\0A\\x9C\\xFF\\x07\\v\\0A\\xA8\\xFF\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xBC\\xFF\\x07\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xFF\\x07\\v\\0A\\xF0\\xFF\\x07\\v +\\xE4\\x89\\0\\0\\0\\0\\0 \\0A\\x84\\x80\\b\\v\\0A\\x90\\x80\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xA4\\x80\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xB8\\x80\\b\\v\\0A\\xD8\\x80\\b\\v +\\xC7\\x89\\0\\0\\0\\0\\0 \\0A\\xEC\\x80\\b\\v\\0A\\xF8\\x80\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x8C\\x81\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\x81\\b\\v\\0A\\xC0\\x81\\b\\v +\\xD3\\x89\\0\\0\\0\\0\\0 \\0A\\xD4\\x81\\b\\v\\0A\\xE0\\x81\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xF4\\x81\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\x82\\b\\v\\0A\\xA8\\x82\\b\\v +\\x91W\\0\\0\\0\\0\\0 \\0A\\xBC\\x82\\b\\v\\0A\\xC8\\x82\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xDC\\x82\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\x82\\b\\v\\0A\\x90\\x83\\b\\v +da\\0\\0\\0\\0\\0!\\0A\\xA4\\x83\\b\\v\\0A\\xB0\\x83\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xC4\\x83\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\x83\\b\\v\\0A\\xF8\\x83\\b\\v +\\\`X\\0\\0\\0\\0\\0 \\0A\\x8C\\x84\\b\\v\\0A\\x98\\x84\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xAC\\x84\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\x84\\b\\v\\0A\\xE0\\x84\\b\\v +)b\\0\\0\\0\\0\\0!\\0A\\xF4\\x84\\b\\v\\0A\\x80\\x85\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\x94\\x85\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\x85\\b\\v\\0A\\xC8\\x85\\b\\v +\\xFEW\\0\\0\\0\\0\\0 \\0A\\xDC\\x85\\b\\v\\0A\\xE8\\x85\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xFC\\x85\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\x90\\x86\\b\\v\\0A\\xB0\\x86\\b\\v +\\xD1a\\0\\0\\0\\0\\0!\\0A\\xC4\\x86\\b\\v\\0A\\xD0\\x86\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xE4\\x86\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xF8\\x86\\b\\v\\0A\\x98\\x87\\b\\v +\\rX\\0\\0\\0\\0\\0 \\0A\\xAC\\x87\\b\\v\\0A\\xB8\\x87\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xCC\\x87\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\x87\\b\\v\\0A\\x80\\x88\\b\\v +\\xE0a\\0\\0\\0\\0\\0!\\0A\\x94\\x88\\b\\v\\0A\\xA0\\x88\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xB4\\x88\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\x88\\b\\v\\0A\\xE8\\x88\\b\\v \\xB0Z\\0\\0\\0\\0\\0A\\xFC\\x88\\b\\v\\0A\\x88\\x89\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\x9C\\x89\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\xB0\\x89\\b\\v +\\0A\\xD0\\x89\\b\\v od\\0\\0\\0\\0\\0A\\xE4\\x89\\b\\v\\0A\\xF0\\x89\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\x84\\x8A\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\x98\\x8A\\b\\v +\\0A\\xB8\\x8A\\b\\v Z\\0\\0\\0\\0\\0A\\xCC\\x8A\\b\\v\\0A\\xD8\\x8A\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xEC\\x8A\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x80\\x8B\\b\\v\\f\\0A\\xA0\\x8B\\b\\v \\xD5c\\0\\0\\0\\0\\0A\\xB4\\x8B\\b\\v\\0A\\xC0\\x8B\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD4\\x8B\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xE8\\x8B\\b\\v\\f\\0A\\x88\\x8C\\b\\v \\xB6X\\0\\0\\0\\0\\0\\x80\\0A\\x9C\\x8C\\b\\v=\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xE4\\x8C\\b\\v\\0A\\xF0\\x8C\\b\\v \\x7Fb\\0\\0\\0\\0\\0\\x81\\0A\\x84\\x8D\\b\\v=\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xCC\\x8D\\b\\v\\0A\\xD8\\x8D\\b\\v \\xC4e\\0\\0\\0\\0\\0\\0\\b\\0A\\xC0\\x8E\\b\\v \\xF4Z\\0\\0\\0\\0A\\xD4\\x8E\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xA8\\x8F\\b\\v \\xB3d\\0\\0\\0\\0A\\xBC\\x8F\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\x90\\x90\\b\\v\\x99c\\0\\0\\0\\0\\0\\xB1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\x90\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0\\0A\\xC4\\x90\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD8\\x90\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xEC\\x90\\b\\v\\f\\0A\\xF8\\x90\\b\\v\\xDAY\\0\\0\\0\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\x91\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0\\0A\\xAC\\x91\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xC0\\x91\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD4\\x91\\b\\v\\f\\0A\\xE0\\x91\\b\\v3d\\0\\0\\0\\0\\0\\xB1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\x92\\b\\v +\\0\\0\\0\\0\\0\\0\\0\\0A\\x94\\x92\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\xA8\\x92\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\xBC\\x92\\b\\v +\\0A\\xC8\\x92\\b\\vtZ\\0\\0\\0\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\x92\\b\\v +\\0\\0\\0\\0\\0\\0\\0\\0A\\xFC\\x92\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\x90\\x93\\b\\v +\\0\\0\\0\\0\\0\\0\\0A\\xA4\\x93\\b\\v +\\0A\\xB0\\x93\\b\\v dj\\0\\0\\0\\0\\0\\0\\b\\0A\\x98\\x94\\b\\v \\x87c\\0\\0\\0\\0\\0\\0A\\xAC\\x94\\b\\v\\0A\\xB8\\x94\\b\\v\\f\\0A\\xF8\\x94\\b\\v\\r\\x8Ac\\0\\0\\0\\0\\0\\0\\xC8Y\\0\\0\\0A\\x94\\x95\\b\\v\\0A\\xA0\\x95\\b\\v\\f\\0A\\xE0\\x95\\b\\v\\xCBY\\0\\0\\0\\0\\0\\0d\\0\\0\\0\\0\\0\\0A\\xFC\\x95\\b\\v\\0A\\x88\\x96\\b\\v +\\0A\\xC8\\x96\\b\\v\\rd\\0\\0\\0\\0\\0\\0XZ\\0\\0\\0A\\xE4\\x96\\b\\v\\0A\\xF0\\x96\\b\\v +\\0A\\xB0\\x97\\b\\v[Z\\0\\0\\0\\0\\0\\0\\x8BX\\0\\0\\0\\0A\\xCC\\x97\\b\\v\\0A\\xD8\\x97\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\x97\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\x98\\b\\v Tb\\0\\0\\0\\0A\\xB4\\x98\\b\\v\\0A\\xC0\\x98\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\x98\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\x99\\b\\v \\x95\\0\\0\\0\\0\\0\\0\\b\\0A\\xF0\\x99\\b\\v \\xC2\\\`\\0\\0\\0\\0\\0\\0A\\x84\\x9A\\b\\v\\0A\\x90\\x9A\\b\\v \\0A\\xD0\\x9A\\b\\v\\r\\xC5\\\`\\0\\0\\0\\0\\0\\0\\xE2V\\0\\0\\0A\\xEC\\x9A\\b\\v\\0A\\xF8\\x9A\\b\\v \\0A\\xB8\\x9B\\b\\v\\xE5V\\0\\0\\0\\0\\0\\0Ec\\0\\0\\0\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\x9B\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xF4\\x9B\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\x88\\x9C\\b\\v \\0A\\xA8\\x9C\\b\\v\\x86Y\\0\\0\\0\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\x9C\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xDC\\x9C\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xF0\\x9C\\b\\v \\0A\\x90\\x9D\\b\\vOc\\0\\0\\0\\0\\0\\xB1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\x9D\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xC4\\x9D\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xD8\\x9D\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\x9D\\b\\v \\0A\\xF8\\x9D\\b\\v\\x90Y\\0\\0\\0\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\x9E\\b\\v \\0\\0\\0\\0\\0\\0\\0\\0A\\xAC\\x9E\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xC0\\x9E\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\x9E\\b\\v \\0A\\xE0\\x9E\\b\\v pQ\\0\\0\\0\\0\\0\\0\\b\\0A\\xC8\\x9F\\b\\v \\xFCC\\0\\0\\0\\0\\0\\0\\b\\0A\\xB0\\xA0\\b\\v \\xD4b\\0\\0\\0\\0\\0\\0A\\xC4\\xA0\\b\\v\\0A\\xD0\\xA0\\b\\v\\0A\\x90\\xA1\\b\\v\\r\\xD7b\\0\\0\\0\\0\\0\\0Y\\0\\0\\0A\\xAC\\xA1\\b\\v\\0A\\xB8\\xA1\\b\\v\\0A\\xF8\\xA1\\b\\vY\\0\\0\\0\\0\\0\\0;c\\0\\0\\0\\0\\0\\0A\\x94\\xA2\\b\\v\\0A\\xA0\\xA2\\b\\v \\0A\\xE0\\xA2\\b\\v>c\\0\\0\\0\\0\\0\\0|Y\\0\\0\\0\\0\\0\\0\\0A\\xFC\\xA2\\b\\v\\0A\\x88\\xA3\\b\\v \\0A\\xC8\\xA3\\b\\v\\x7FY\\0\\0\\0\\0\\0\\0\\xC8c\\0\\0\\0\\0\\x91\\0A\\xE4\\xA3\\b\\v\\0A\\xF0\\xA3\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x84\\xA4\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x98\\xA4\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xAC\\xA4\\b\\v\\f\\0A\\xB8\\xA4\\b\\v Z\\0\\0\\0\\0\\x90\\0A\\xCC\\xA4\\b\\v\\0A\\xD8\\xA4\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xEC\\xA4\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x80\\xA5\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\x94\\xA5\\b\\v\\f\\0A\\xA0\\xA5\\b\\v \\xAFc\\0\\0\\0\\0\\0\\x91\\0A\\xB4\\xA5\\b\\v\\0A\\xC0\\xA5\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD4\\xA5\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xA5\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xFC\\xA5\\b\\v\\f\\0A\\x88\\xA6\\b\\v \\xF0Y\\0\\0\\0\\0\\0\\x90\\0A\\x9C\\xA6\\b\\v\\0A\\xA8\\xA6\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xBC\\xA6\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xD0\\xA6\\b\\v \\f\\0\\0\\0\\0\\0\\0\\0A\\xE4\\xA6\\b\\v\\f\\0A\\xF0\\xA6\\b\\v P\\x8D\\0\\0\\0\\0\\0\\0A\\x84\\xA7\\b\\v\\0A\\x90\\xA7\\b\\v \\b\\0\\0\\0\\0\\0\\0\\0A\\xA4\\xA7\\b\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xD8\\xA7\\b\\v \\xC8\\x90\\0\\0\\0\\0\\0\\0A\\xEC\\xA7\\b\\v\\0A\\xF8\\xA7\\b\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xA8\\b\\v\\b\\0A\\xC0\\xA8\\b\\v 5=\\0\\0\\0\\0\\0\\0\\b\\0A\\xA8\\xA9\\b\\v \\xA5d\\0\\0\\0\\0\\0A\\xBC\\xA9\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\x90\\xAA\\b\\v\\xE6Z\\0\\0\\0A\\xA4\\xAA\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xF8\\xAA\\b\\v \\xD5Z\\0\\0\\0\\0\\0 \\0A\\x8C\\xAB\\b\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0A\\xC0\\xAB\\b\\v +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE0\\xAB\\b\\v \\x94d\\0\\0\\0\\0\\0!\\0A\\xF4\\xAB\\b\\vI\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xC8\\xAC\\b\\v aZ\\0\\0\\0\\0\\0 \\0A\\xDC\\xAC\\b\\v\\0A\\xE8\\xAC\\b\\v= +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xB0\\xAD\\b\\v d\\0\\0\\0\\0\\0!\\0A\\xC4\\xAD\\b\\vI\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x98\\xAE\\b\\v \\xACd\\0\\0\\0\\0\\0A\\xAC\\xAE\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\x80\\xAF\\b\\v \\xEDZ\\0\\0\\0\\0\\0A\\x94\\xAF\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xE8\\xAF\\b\\v \\x9Ed\\0\\0\\0\\0\\0\\0A\\xFC\\xAF\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xD0\\xB0\\b\\v \\xDFZ\\0\\0\\0\\0\\0\\0A\\xE4\\xB0\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0A\\xB8\\xB1\\b\\v Mb\\0\\0\\0\\0\\0A\\xCC\\xB1\\b\\v\\0A\\xD8\\xB1\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xEC\\xB1\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xB2\\b\\v \\x84X\\0\\0\\0\\0\\0A\\xB4\\xB2\\b\\v\\0A\\xC0\\xB2\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xD4\\xB2\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x88\\xB3\\b\\v ?b\\0\\0\\0\\0\\0\\0A\\x9C\\xB3\\b\\v\\0A\\xA8\\xB3\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xBC\\xB3\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xF0\\xB3\\b\\v vX\\0\\0\\0\\0\\0\\0A\\x84\\xB4\\b\\v\\0A\\x90\\xB4\\b\\v \\0\\0\\0\\0\\0\\0\\0A\\xA4\\xB4\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xB4\\b\\v \\x8B{\\0\\0\\0\\0\\0\\x80\\0A\\xEC\\xB4\\b\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xA0\\xB5\\b\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xC0\\xB5\\b\\v +b\\0\\0\\0\\0\\0\\xA1\\0A\\xD4\\xB5\\b\\v\\b\\0A\\xE0\\xB5\\b\\v=\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA8\\xB6\\b\\v +LX\\0\\0\\0\\0\\0\\xA0\\0A\\xBC\\xB6\\b\\v\\b\\0A\\xC8\\xB6\\b\\v=\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x90\\xB7\\b\\v\\0\\0\\0A\\xA4\\xB7\\b\\v)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0A\\xD8\\xB7\\b\\v\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0A\\xF8\\xB7\\b\\v CZ\\0\\0\\0\\0A\\x8C\\xB8\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xE0\\xB8\\b\\v d\\0\\0\\0\\0A\\xF4\\xB8\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xC8\\xB9\\b\\v \\xF4c\\0\\0\\0\\0\\0A\\xDC\\xB9\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xB0\\xBA\\b\\v5Z\\0\\0\\0A\\xC4\\xBA\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\x98\\xBB\\b\\v d\\0\\0\\0\\0\\0\\0A\\xAC\\xBB\\b\\v \\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0 \\0A\\xCC\\xBB\\b\\v) +\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\x80\\xBC\\b\\vJZ\\0\\0\\0A\\x94\\xBC\\b\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0A\\xB4\\xBC\\b\\v) +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xE8\\xBC\\b\\v @a\\0\\0\\0\\0\\0\\0A\\xFC\\xBC\\b\\vI\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xD0\\xBD\\b\\v\\\`W\\0\\0\\0A\\xE4\\xBD\\b\\vI\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xB8\\xBE\\b\\v +Zc\\0\\0\\0\\0\\0!\\0A\\xCC\\xBE\\b\\v\\f\\0A\\xD8\\xBE\\b\\v) \\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0 \\0A\\xA0\\xBF\\b\\v +\\x9BY\\0\\0\\0\\0\\0 \\0A\\xB4\\xBF\\b\\v\\f\\0A\\xC0\\xBF\\b\\v) \\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0 \\0A\\x88\\xC0\\b\\v +kc\\0\\0\\0\\0\\0\\xA1\\0A\\x9C\\xC0\\b\\v\\0A\\xA8\\xC0\\b\\v= \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0 \\0A\\xF0\\xC0\\b\\v +\\xACY\\0\\0\\0\\0\\0\\xA0\\0A\\x84\\xC1\\b\\v\\0A\\x90\\xC1\\b\\v= \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0 \\0A\\xD8\\xC1\\b\\v \\xFBc\\0\\0\\0\\0\\0A\\xEC\\xC1\\b\\v5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\f\\0A\\xC0\\xC2\\b\\v \\xBE~\\xFE\\x81A\\xC1!\\xA1a\\xE1\\x91Q\\xD11\\xB1q\\xF1 \\x89I\\xC9)\\xA9i\\xE9\\x99Y\\xD99\\xB9y\\xF9\\x85E\\xC5%\\xA5e\\xE5\\x95U\\xD55\\xB5u\\xF5\\r\\x8DM\\xCD-\\xADm\\xED\\x9D]\\xDD=\\xBD}\\xFD\\x83C\\xC3#\\xA3c\\xE3\\x93S\\xD33\\xB3s\\xF3\\v\\x8BK\\xCB+\\xABk\\xEB\\x1B\\x9B[\\xDB;\\xBB{\\xFB\\x07\\x87G\\xC7'\\xA7g\\xE7\\x97W\\xD77\\xB7w\\xF7\\x8FO\\xCF/\\xAFo\\xEF\\x9F_\\xDF?\\xBF\\x7F\\xFFu8\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0s16\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0s32\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0flt\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\b\\0\\0\\0dbl\\0\\0\\0\\0\\0@\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0u8p\\0\\0\\0\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0s16p\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0s32p\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0fltp\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0dblp\\0\\0\\0\\0@\\0\\0\\0\\0\\0\\0\\0\\0\\0s64\\0\\0\\0\\0\\0@\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0s64p\\0\\0\\0\\0@\\0\\0\\0\\0\\0\\0 +\\0\\0\\0PT\\0\\xB07\\0\\xF0p\\0\\x906\\0\\x986\\0\\0\\0\\0\\0]C\\0\\0j\\0\\0\\0\\xFF\\xFF\\xFF\\x7F\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xC0\\xFF\\xFF\\xFF\\xFF\\0A\\xF4\\xED\\b\\v \\0\\0\\0\\0\\0\\0k\\0A\\x89\\xEE\\b\\v\\x80\\x80\\0\\0\\0\\0\\0\\0\\xE4\\xFF\\xFF\\xFF\\0\\0\\0\\0\\xE4\\xFF\\xFF\\xFF\\0\\0\\0\\0\\xE4\\xFF\\xFF\\xFF\\xE4\\xFF\\xFF\\xFF\\xE4\\xFF\\xFF\\xFF\\xE4\\xFF\\xFF\\xFFl\\0\\0\\0m\\0\\0\\0n\\0\\0\\0o\\0\\0\\0p\\0\\0\\0q\\0\\0\\0r\\0\\0\\0s\\0\\0\\0t\\0\\0\\0u\\0\\0\\0v\\0\\0\\0w\\0\\0\\0x\\0\\0\\0y\\0\\0\\0z\\0\\0\\0\\0\\0\\0\\0{\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0|\\0\\0\\0 \\0A\\x98\\xEF\\b\\v}\\0\\0\\0\\x07\\0A\\xB0\\xEF\\b\\v\\xE7\\xA08\\09\\0\\x909\\0\\b:\\0\\x80:\\0\\xF8:\\0p;\\0\\xE8;\\0\\\`<\\0\\xD8<\\0P=\\0\\xC8=\\0@>\\0\\xB8>\\x000?\\0\\xA8?\\0 @\\0\\x98@\\0A\\0\\x88A\\0\\0B\\0xB\\0\\xF0B\\0hC\\0\\xE0C\\0XD\\0\\xD0D\\0HE\\0\\xC0E\\x008F\\0\\xB0F\\0(G\\0\\xA0G\\0H\\0\\x90H\\0\\bI\\0\\x80I\\0\\xF8I\\0pJ\\0\\xE8J\\0\\\`K\\0\\xD8K\\0PL\\0\\xC8L\\0@M\\0\\xB8M\\x000N\\0\\xA8N\\0 O\\0\\x98O\\0P\\0\\x88P\\0\\0Q\\0xQ\\0\\xF0Q\\0hR\\0\\xE0R\\0XS\\0A\\xA0\\xF1\\b\\v\\xF8r\\0\\0~\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF8\\xF1\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\x98\\xF2\\b\\v\\x9Br\\0\\0\\x80\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF0\\xF2\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\x90\\xF3\\b\\v\\xCCq\\0\\0\\x81\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE8\\xF3\\b\\v\\r\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0\\0\\x7F\\0A\\x88\\xF4\\b\\vfr\\0\\0\\x82\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE0\\xF4\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\x80\\xF5\\b\\v3s\\0\\0\\x83\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD8\\xF5\\b\\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\x7F\\0A\\xF8\\xF5\\b\\v\\xC1r\\0\\0\\x84\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD0\\xF6\\b\\v\\r\\0\\0\\0@\\0\\0\\0@\\0\\0\\0\\x7F\\0A\\xF0\\xF6\\b\\vr\\0\\0\\x85\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC8\\xF7\\b\\v\\r\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0\\0\\x7F\\0A\\xE8\\xF7\\b\\v>r\\0\\0\\x86\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC0\\xF8\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\xE0\\xF8\\b\\vEs\\0\\0\\x87\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB8\\xF9\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\xD8\\xF9\\b\\v\\xD3r\\0\\0\\x88\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB0\\xFA\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\xD0\\xFA\\b\\v\\xF2q\\0\\0\\x89\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA8\\xFB\\b\\v\\r\\0\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0\\x7F\\0A\\xC8\\xFB\\b\\v*r\\0\\0\\x8A\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA0\\xFC\\b\\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\xC0\\xFC\\b\\v s\\0\\0\\x8B\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x98\\xFD\\b\\v\\r\\0\\0\\0\\0 \\0\\0\\0 \\0\\0\\x7F\\0A\\xB8\\xFD\\b\\v\\xACr\\0\\0\\x8C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x90\\xFE\\b\\v\\r\\0\\0\\0\\0@\\0\\0\\0@\\0\\0\\x7F\\0A\\xB0\\xFE\\b\\v\\xDDq\\0\\0\\x8D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x88\\xFF\\b\\v\\r\\0\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0\\x7F\\0A\\xA8\\xFF\\b\\vQr\\0\\0\\x8E\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x80\\x80 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\xA0\\x80 \\vs\\0\\0\\x8F\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF8\\x80 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x7F\\0A\\x98\\x81 \\v\\xE7r\\0\\0\\x90\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF0\\x81 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x91\\0A\\x90\\x82 \\vxr\\0\\0\\x92\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE8\\x82 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x91\\0A\\x88\\x83 \\vr\\0\\0\\x93\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\x07\\0A\\xE0\\x83 \\v\\r\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\0\\x91\\0A\\x80\\x84 \\v\\xBBq\\0\\0\\x94\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0 \\0A\\xD8\\x84 \\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\x91\\0A\\xF8\\x84 \\v\\x89r\\0\\0\\x95\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD0\\x85 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x91\\0A\\xF0\\x85 \\v\\xE4t\\0\\0\\x90\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0A\\xC8\\x86 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x91\\0A\\xE8\\x86 \\v\\xD2t\\0\\0\\x92\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0A\\xC0\\x87 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x91\\0A\\xE0\\x87 \\v\\xC0t\\0\\0\\x93\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x07\\0A\\xB8\\x88 \\v\\r\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\0\\x91\\0A\\xD8\\x88 \\v\\xAEt\\0\\0\\x94\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88 \\0A\\xB0\\x89 \\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\x91\\0A\\xD0\\x89 \\v\\x9Aq\\0\\0\\x96\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\x8A \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\x97\\0A\\xC8\\x8A \\v\\xECs\\0\\0\\x98\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\x8B \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\x97\\0A\\xB9\\x8B \\v#\\xFE\\xFF\\xFF\\0\\0\\0\\0\\xACs\\0\\0\\x96\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\x8C \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x99\\0A\\xB1\\x8C \\v3\\xFF\\xFF\\xFF\\0\\0\\0\\0\\bu\\0\\0\\x9A\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\x8D \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\x9B\\0A\\xB0\\x8D \\v,\\xA7q\\0\\0\\x9C\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\x8E \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\x9B\\0A\\xA8\\x8E \\v\\xD9s\\0\\0\\x9D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\x8F \\v\\f\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x9A\\x8F \\v"\\xFE\\xFF\\0\\0\\0\\0\\x93s\\0\\0\\x9E\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\x8F \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x9F\\0A\\x92\\x90 \\v&\\xFF\\xFF\\0\\0\\0\\0t\\0\\0\\xA0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\x90 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA1\\0A\\x90\\x91 \\v \\xEDp\\0\\0\\xA2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE8\\x91 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA1\\0A\\x88\\x92 \\v \\x94t\\0\\0\\xA3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE0\\x92 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\x80\\x93 \\v _t\\0\\0\\xA5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\x93 \\v\\r\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xF8\\x93 \\v Et\\0\\0\\xA6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\x94 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xF0\\x94 \\v +t\\0\\0\\xA7\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC8\\x95 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xE8\\x95 \\v yt\\0\\0\\xA8\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\x96 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xE0\\x96 \\v \\x80q\\0\\0\\xA9\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\x97 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xD8\\x97 \\v Kq\\0\\0\\xAA\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\x98 \\v\\r\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xD0\\x98 \\v 1q\\0\\0\\xAB\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\x99 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xC8\\x99 \\v q\\0\\0\\xAC\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\x9A \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xC0\\x9A \\v eq\\0\\0\\xAD\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\x9B \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xA4\\0A\\xB8\\x9B \\v t\\0\\0\\xAE\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\x9C \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xAF\\0A\\xAA\\x9C \\v&\\xFE\\xFF\\0\\0\\0\\0\\xFFp\\0\\0\\xB0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\x9D \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xAF\\0A\\xA2\\x9D \\v&\\xFE\\xFF\\0\\0\\0\\0|s\\0\\0\\xB1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\x9E \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB2\\0A\\xA0\\x9E \\v \\xF6t\\0\\0\\xB3\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\x9E \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\x98\\x9F \\v Xs\\0\\0\\xB5\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\x9F \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\x90\\xA0 \\v Tu\\0\\0\\xB6\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE8\\xA0 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\x88\\xA1 \\v \\xC7s\\0\\0\\xB7\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE0\\xA1 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\x80\\xA2 \\v ku\\0\\0\\xB8\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\xA2 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\xF8\\xA2 \\v js\\0\\0\\xB9\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\xA3 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xB4\\0A\\xF0\\xA3 \\v 5u\\0\\0\\xBA\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xCC\\xA4 \\v \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xBB\\0A\\xE8\\xA4 \\v Du\\0\\0\\xBC\\0\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC4\\xA5 \\v \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xBB\\0A\\xE0\\xA5 \\v 'u\\0\\0\\xBD\\0\\0\\0\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\xA6 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xBE\\0A\\xD8\\xA6 \\v u\\0\\0\\xBF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\xA7 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xBE\\0A\\xD0\\xA7 \\vY\\xC0\\0\\0\\0\\xC1\\0\\0\\0\\xC2\\0\\0\\0\\xC3\\0\\0\\0\\xC4\\0\\0\\0\\xC5\\0\\0\\0\\xC6\\0\\0\\0\\xC7\\0\\0\\0\\xC8\\0\\0\\0\\xC9\\0\\0\\0\\xCA\\0\\0\\0\\xCB\\0\\0\\0\\xCC\\0\\0\\0\\xCD\\0\\0\\0\\xCE\\0\\0\\0\\0\\0\\0\\0\\xCF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD0\\0\\0\\0 \\0A\\xB8\\xA8 \\v\\xD1\\0\\0\\0\\x07\\0A\\xD0\\xA8 \\v\\xE7@U\\0\\xB8U\\x000V\\0\\xA8V\\0 W\\0\\x98W\\0X\\0\\x88X\\0\\0Y\\0xY\\0\\xF0Y\\0hZ\\0\\xE0Z\\0X[\\0\\xD0[\\0H\\\\\\0\\xC0\\\\\\x008]\\0\\xB0]\\0(^\\0\\xA0^\\0_\\0\\x90_\\0\\b\\\`\\0\\x80\\\`\\0\\xF8\\\`\\0pa\\0\\xE8a\\0\\\`b\\0\\xD8b\\0Pc\\0\\xC8c\\0@d\\0\\xB8d\\x000e\\0\\xA8e\\0 f\\0\\x98f\\0g\\0\\x88g\\0\\0h\\0xh\\0\\xF0h\\0hi\\0\\xE0i\\0Xj\\0\\xD0j\\0Hk\\0\\xC0k\\x008l\\0\\xB0l\\0(m\\0\\xA0m\\0n\\0\\x90n\\0\\bo\\0\\x80o\\0\\xF8o\\0A\\xC0\\xAA \\v\\x83n\\0\\0\\xD2\\0A\\xD0\\xAA \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x98\\xAB \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xB8\\xAB \\v+n\\0\\0\\xD4\\0A\\xC8\\xAB \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x90\\xAC \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xB0\\xAC \\vgm\\0\\0\\xD5\\0A\\xC0\\xAC \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x88\\xAD \\v\\r\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0\\0\\xD3\\0A\\xA8\\xAD \\v\\xF9m\\0\\0\\xD6\\0A\\xB8\\xAD \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x80\\xAE \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xA0\\xAE \\v\\xBBn\\0\\0\\xD7\\0A\\xB0\\xAE \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF8\\xAE \\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\xD3\\0A\\x98\\xAF \\vOn\\0\\0\\xD8\\0A\\xA8\\xAF \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF0\\xAF \\v\\r\\0\\0\\0@\\0\\0\\0@\\0\\0\\0\\xD3\\0A\\x90\\xB0 \\v\\x9Em\\0\\0\\xD9\\0A\\xA0\\xB0 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE8\\xB0 \\v\\r\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0\\0\\xD3\\0A\\x88\\xB1 \\v\\xD3m\\0\\0\\xDA\\0A\\x98\\xB1 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE0\\xB1 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\x80\\xB2 \\v\\xCCn\\0\\0\\xDB\\0A\\x90\\xB2 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD8\\xB2 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xF8\\xB2 \\v\\\`n\\0\\0\\xDC\\0A\\x88\\xB3 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD0\\xB3 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xF0\\xB3 \\v\\x8Bm\\0\\0\\xDD\\0A\\x80\\xB4 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC8\\xB4 \\v\\r\\0\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0\\xD3\\0A\\xE8\\xB4 \\v\\xC0m\\0\\0\\xDE\\0A\\xF8\\xB4 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC0\\xB5 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xE0\\xB5 \\v\\x93n\\0\\0\\xDF\\0A\\xF0\\xB5 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB8\\xB6 \\v\\r\\0\\0\\0\\0 \\0\\0\\0 \\0\\0\\xD3\\0A\\xD8\\xB6 \\v;n\\0\\0\\xE0\\0A\\xE8\\xB6 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB0\\xB7 \\v\\r\\0\\0\\0\\0@\\0\\0\\0@\\0\\0\\xD3\\0A\\xD0\\xB7 \\vwm\\0\\0\\xE1\\0A\\xE0\\xB7 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA8\\xB8 \\v\\r\\0\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0\\xD3\\0A\\xC8\\xB8 \\v\\xE5m\\0\\0\\xE2\\0A\\xD8\\xB8 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA0\\xB9 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xC0\\xB9 \\v\\xA6n\\0\\0\\xE3\\0A\\xD0\\xB9 \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x98\\xBA \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xD3\\0A\\xB8\\xBA \\vsn\\0\\0\\xE4\\0A\\xC8\\xBA \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x90\\xBB \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE5\\0A\\xB0\\xBB \\v +n\\0\\0\\xE6\\0A\\xC0\\xBB \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\x88\\xBC \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE5\\0A\\xA8\\xBC \\v\\xB0m\\0\\0\\xE7\\0A\\xB8\\xBC \\v \\0\\0\\0\\0\\0\\0\\xA0\\x07\\0A\\x80\\xBD \\v\\r\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\0\\xE5\\0A\\xA0\\xBD \\vWm\\0\\0\\xE8\\0A\\xB0\\xBD \\v \\0\\0\\0\\0\\0\\0\\xA0 \\0A\\xF8\\xBD \\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\xE5\\0A\\x98\\xBE \\vn\\0\\0\\xE9\\0A\\xA8\\xBE \\v \\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF0\\xBE \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE5\\0A\\x90\\xBF \\vXp\\0\\0\\xE4\\0A\\xA0\\xBF \\v \\0\\0\\0\\0\\0\\0\\x88\\0A\\xE8\\xBF \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE5\\0A\\x88\\xC0 \\vGp\\0\\0\\xE6\\0A\\x98\\xC0 \\v \\0\\0\\0\\0\\0\\0\\x88\\0A\\xE0\\xC0 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xE5\\0A\\x80\\xC1 \\v6p\\0\\0\\xE7\\0A\\x90\\xC1 \\v \\0\\0\\0\\0\\0\\0\\x88\\x07\\0A\\xD8\\xC1 \\v\\r\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\0\\xE5\\0A\\xF8\\xC1 \\v%p\\0\\0\\xE8\\0A\\x88\\xC2 \\v \\0\\0\\0\\0\\0\\0\\x88 \\0A\\xD0\\xC2 \\v\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\xE5\\0A\\xF0\\xC2 \\v8m\\0\\0\\xEA\\0A\\x80\\xC3 \\v\\f\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xC8\\xC3 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xEB\\0A\\xE8\\xC3 \\vko\\0\\0\\xEC\\0A\\xF8\\xC3 \\v\\f\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\xC4 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xEB\\0A\\xD9\\xC4 \\v\\f\\xFE\\xFF\\xFF\\0\\0\\0\\0.o\\0\\0\\xEA\\0A\\xF0\\xC4 \\v\\f\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\xC5 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xED\\0A\\xD1\\xC5 \\v\\f\\xFF\\xFF\\xFF\\0\\0\\0\\0zp\\0\\0\\xEE\\0A\\xE8\\xC5 \\v\\0\\0\\0\\0\\0\\0\\x80\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\xC6 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xEF\\0A\\xD0\\xC6 \\vDm\\0\\0\\xF0\\0A\\xE0\\xC6 \\v\\0\\0\\0\\0\\0\\0\\xA0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\xC7 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xEF\\0A\\xC8\\xC7 \\vYo\\0\\0\\xF1\\0A\\xD8\\xC7 \\v\\f\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\xC8 \\v\\f\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xBA\\xC8 \\v\\v\\xFE\\xFF\\0\\0\\0\\0o\\0\\0\\xF2\\0A\\xD0\\xC8 \\v\\f\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\xC9 \\v\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xF3\\0A\\xB2\\xC9 \\v&\\xFF\\xFF\\0\\0\\0\\0\\x7Fo\\0\\0\\xF4\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\xCA \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF5\\0A\\xB0\\xCA \\v \\x92l\\0\\0\\xF6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\xCB \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF5\\0A\\xA8\\xCB \\v \\fp\\0\\0\\xF7\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\xCC \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xA0\\xCC \\v \\xD9o\\0\\0\\xF9\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\xCC \\v\\r\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\x98\\xCD \\v \\xC0o\\0\\0\\xFA\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\xCD \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\x90\\xCE \\v \\xA7o\\0\\0\\xFB\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE8\\xCE \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\x88\\xCF \\v \\xF2o\\0\\0\\xFC\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE0\\xCF \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\x80\\xD0 \\v m\\0\\0\\xFD\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\xD0 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xF8\\xD0 \\v \\xECl\\0\\0\\xFE\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\xD1 \\v\\r\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xF0\\xD1 \\v \\xD3l\\0\\0\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC8\\xD2 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xE8\\xD2 \\v \\xBAl\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\xD3 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xE0\\xD3 \\v m\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\xD4 \\v\\r\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xF8\\0A\\xD8\\xD4 \\v \\x90o\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\xD5 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xCA\\xD5 \\v&\\xFE\\xFF\\0\\0\\0\\0\\xA3l\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\xD6 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC2\\xD6 \\v&\\xFE\\xFF\\0\\0\\0\\0\\0o\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\xD7 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\xD7 \\v ip\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\xD8 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\xB8\\xD8 \\v \\xDEn\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\xD9 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\xB0\\xD9 \\v \\xC1p\\0\\0 +\\0\\0\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\xDA \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\xA8\\xDA \\v Ho\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\xDB \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\xA0\\xDB \\v \\xD7p\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\xDB \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\x98\\xDC \\v \\xEFn\\0\\0\\r\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\xDC \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\b\\0A\\x90\\xDD \\v \\xA4p\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xEC\\xDD \\v +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\xDE \\v \\xB2p\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE4\\xDE \\v +\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\xDF \\v \\x97p\\0\\0\\0\\0\\f\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\xDF \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\xDF \\v \\x8Ap\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\xE0 \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\xE0 \\vY\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x1B\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0!\\0\\0"\\0\\0\\0\\0\\0\\0#\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0$\\0\\0 \\0A\\xD8\\xE1 \\v%\\0\\0\\x07\\0A\\xF0\\xE1 \\v\\xE7\\xE0q\\0Xr\\0\\xD0r\\0Hs\\0\\xC0s\\x008t\\0\\xB0t\\0(u\\0\\xA0u\\0v\\0\\x90v\\0\\bw\\0\\x80w\\0\\xF8w\\0px\\0\\xE8x\\0\\\`y\\0\\xD8y\\0Pz\\0\\xC8z\\0@{\\0\\xB8{\\x000|\\0\\xA8|\\0 }\\0\\x98}\\0~\\0\\x88~\\0\\0\\x7F\\0x\\x7F\\0\\xF0\\x7F\\0h\\x80\\0\\xE0\\x80\\0X\\x81\\0\\xD0\\x81\\0H\\x82\\0\\xC0\\x82\\x008\\x83\\0\\xB0\\x83\\0(\\x84\\0\\xA0\\x84\\0\\x85\\0\\x90\\x85\\0\\b\\x86\\0\\x80\\x86\\0\\xF8\\x86\\0p\\x87\\0\\xE8\\x87\\0\\\`\\x88\\0\\xD8\\x88\\0P\\x89\\0\\xC8\\x89\\0@\\x8A\\0\\xB8\\x8A\\x000\\x8B\\0\\xA8\\x8B\\0 \\x8C\\0\\x98\\x8C\\0A\\xE0\\xE3 \\vsw\\0\\0&\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB8\\xE4 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xD8\\xE4 \\v\\x1Bw\\0\\0(\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB0\\xE5 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xD0\\xE5 \\vWv\\0\\0)\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA8\\xE6 \\v\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0\\0'\\0A\\xC8\\xE6 \\v\\xE9v\\0\\0*\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA0\\xE7 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xC0\\xE7 \\v\\xABw\\0\\0+\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x98\\xE8 \\v\\0\\0\\0 \\0\\0\\0 \\0\\0\\0'\\0A\\xB8\\xE8 \\v?w\\0\\0,\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x90\\xE9 \\v\\0\\0\\0@\\0\\0\\0@\\0\\0\\0'\\0A\\xB0\\xE9 \\v\\x8Ev\\0\\0-\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x88\\xEA \\v\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0\\0'\\0A\\xA8\\xEA \\v\\xC3v\\0\\0.\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x80\\xEB \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xA0\\xEB \\v\\xBCw\\0\\0/\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF8\\xEB \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\x98\\xEC \\vPw\\0\\x000\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xF0\\xEC \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\x90\\xED \\v{v\\0\\x001\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE8\\xED \\v\\0\\0\\0\\0\\b\\0\\0\\0\\b\\0\\0'\\0A\\x88\\xEE \\v\\xB0v\\0\\x002\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xE0\\xEE \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\x80\\xEF \\v\\x83w\\0\\x003\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD8\\xEF \\v\\0\\0\\0\\0 \\0\\0\\0 \\0\\0'\\0A\\xF8\\xEF \\v+w\\0\\x004\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xD0\\xF0 \\v\\0\\0\\0\\0@\\0\\0\\0@\\0\\0'\\0A\\xF0\\xF0 \\vgv\\0\\x005\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC8\\xF1 \\v\\0\\0\\0\\0\\x80\\0\\0\\0\\x80\\0\\0'\\0A\\xE8\\xF1 \\v\\xD5v\\0\\x006\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xC0\\xF2 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xE0\\xF2 \\v\\x96w\\0\\x007\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB8\\xF3 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0A\\xD8\\xF3 \\vcw\\0\\x008\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xB0\\xF4 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\x009\\0A\\xD0\\xF4 \\v\\xFAv\\0\\0:\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\xA8\\xF5 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\x009\\0A\\xC8\\xF5 \\v\\xA0v\\0\\0;\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\x07\\0A\\xA0\\xF6 \\v\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\x009\\0A\\xC0\\xF6 \\vGv\\0\\0<\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0 \\0A\\x98\\xF7 \\v\\0\\0\\0 \\0\\0\\0 \\0\\0\\x009\\0A\\xB8\\xF7 \\v +w\\0\\0=\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\0A\\x90\\xF8 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\x009\\0A\\xB0\\xF8 \\vHy\\0\\x008\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0A\\x88\\xF9 \\v\\0\\0\\0\\0\\0\\0\\0\\0\\x009\\0A\\xA8\\xF9 \\v7y\\0\\0:\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0A\\x80\\xFA \\v\\0\\0\\0\\0\\0\\0\\0\\0\\x009\\0A\\xA0\\xFA \\v&y\\0\\0;\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x07\\0A\\xF8\\xFA \\v\\0\\0\\0\\x07\\0\\0\\0\\x07\\0\\0\\x009\\0A\\x98\\xFB \\vy\\0\\0<\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88 \\0A\\xF0\\xFB \\v\\0\\0\\0 \\0\\0\\0 \\0\\0\\x009\\0A\\x90\\xFC \\v(v\\0\\0>\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xE8\\xFC \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF?\\0A\\x88\\xFD \\v[x\\0\\0@\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xE0\\xFD \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF?\\0A\\xF9\\xFD \\v#\\xFE\\xFF\\xFF\\0\\0\\0\\0x\\0\\0>\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\xFE \\v\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\0A\\xF1\\xFE \\v3\\xFF\\xFF\\xFF\\0\\0\\0\\0jy\\0\\0B\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\xFF \\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFC\\0A\\xF0\\xFF \\v,4v\\0\\0D\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xA0\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC8\\x80 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFC\\0A\\xE8\\x80 +\\vIx\\0\\0E\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\x81 +\\v\\f\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xDA\\x81 +\\v"\\xFE\\xFF\\0\\0\\0\\0x\\0\\0F\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\x82 +\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0G\\0A\\xD2\\x82 +\\v&\\xFF\\xFF\\0\\0\\0\\0ox\\0\\0H\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\x83 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFI\\0A\\xD0\\x83 +\\v \\x82u\\0\\0J\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\x84 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFI\\0A\\xC8\\x84 +\\v \\xFCx\\0\\0K\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\x85 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xC0\\x85 +\\v \\xC9x\\0\\0M\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\x86 +\\v\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xB8\\x86 +\\v \\xB0x\\0\\0N\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\x87 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xB0\\x87 +\\v \\x97x\\0\\0O\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x88\\x88 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xA8\\x88 +\\v \\xE2x\\0\\0P\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x80\\x89 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xA0\\x89 +\\v v\\0\\0Q\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\x89 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\x98\\x8A +\\v \\xDCu\\0\\0R\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\x8A +\\v\\0\\0\\0 +\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\x90\\x8B +\\v \\xC3u\\0\\0S\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\x07\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE8\\x8B +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\x88\\x8C +\\v \\xAAu\\0\\0T\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xE0\\x8C +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\x80\\x8D +\\v \\xF5u\\0\\0U\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD8\\x8D +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFL\\0A\\xF8\\x8D +\\v \\x80x\\0\\0V\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xD0\\x8E +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFW\\0A\\xEA\\x8E +\\v&\\xFE\\xFF\\0\\0\\0\\0\\x93u\\0\\0X\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC8\\x8F +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFW\\0A\\xE2\\x8F +\\v&\\xFE\\xFF\\0\\0\\0\\0\\xF0w\\0\\0Y\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x07\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xC0\\x90 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFZ\\0A\\xE0\\x90 +\\v Yy\\0\\0[\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB8\\x91 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xD8\\x91 +\\v \\xCEw\\0\\0]\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xB0\\x92 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xD0\\x92 +\\v \\xB1y\\0\\0^\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA8\\x93 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xC8\\x93 +\\v 8x\\0\\0_\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xA0\\x94 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xC0\\x94 +\\v \\xC7y\\0\\0\\\`\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x98\\x95 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xB8\\x95 +\\v \\xDFw\\0\\0a\\0\\0\\b\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x90\\x96 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\\\\\0A\\xB0\\x96 +\\v \\x94y\\0\\0b\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x8C\\x97 +\\v +\\0\\0\\0\\xFF\\xFF\\xFF\\xFFc\\0A\\xA8\\x97 +\\v \\xA2y\\0\\0d\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x90\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\x84\\x98 +\\v +\\0\\0\\0\\xFF\\xFF\\xFF\\xFFc\\0A\\xA0\\x98 +\\v \\x87y\\0\\0e\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF8\\x98 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFf\\0A\\x98\\x99 +\\v zy\\0\\0g\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x80\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\0A\\xF0\\x99 +\\v\\0\\0\\0\\0\\0\\0\\xFF\\xFF\\xFF\\xFFf\\0A\\x90\\x9A +\\v\\x97O\\xBBag\\xAC\\xDD?-DT\\xFB!\\xE9?\\x9B\\xF6\\x81\\xD2\\vs\\xEF?-DT\\xFB!\\xF9?\\xE2e/"\\x7F+z<\\x07\\\\3&\\xA6\\x81<\\xBD\\xCB\\xF0z\\x88\\x07p<\\x07\\\\3&\\xA6\\x91<\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x83\\xF9\\xA2\\0DNn\\0\\xFC)\\0\\xD1W'\\0\\xDD4\\xF5\\0b\\xDB\\xC0\\0<\\x99\\x95\\0A\\x90C\\0cQ\\xFE\\0\\xBB\\xDE\\xAB\\0\\xB7a\\xC5\\0:n$\\0\\xD2MB\\0I\\xE0\\0 \\xEA.\\0\\x92\\xD1\\0\\xEB\\xFE\\0)\\xB1\\0\\xE8>\\xA7\\0\\xF55\\x82\\0D\\xBB.\\0\\x9C\\xE9\\x84\\0\\xB4&p\\0A~_\\0\\xD6\\x919\\0S\\x839\\0\\x9C\\xF49\\0\\x8B_\\x84\\0(\\xF9\\xBD\\0\\xF8;\\0\\xDE\\xFF\\x97\\0\\x98\\0/\\xEF\\0 +Z\\x8B\\0mm\\0\\xCF~6\\0 \\xCB'\\0FO\\xB7\\0\\x9Ef?\\0-\\xEA_\\0\\xBA'u\\0\\xE5\\xEB\\xC7\\0={\\xF1\\0\\xF79\\x07\\0\\x92R\\x8A\\0\\xFBk\\xEA\\0\\xB1_\\0\\b]\\x8D\\x000V\\0{\\xFCF\\0\\xF0\\xABk\\0 \\xBC\\xCF\\x006\\xF4\\x9A\\0\\xE3\\xA9\\0^a\\x91\\0\\b\\x1B\\xE6\\0\\x85\\x99e\\0\\xA0_\\0\\x8D@h\\0\\x80\\xD8\\xFF\\0'sM\\01\\0\\xCAV\\0\\xC9\\xA8s\\0{\\xE2\\\`\\0k\\x8C\\xC0\\0\\xC4G\\0\\xCDg\\xC3\\0 \\xE8\\xDC\\0Y\\x83*\\0\\x8Bv\\xC4\\0\\xA6\\x96\\0D\\xAF\\xDD\\0W\\xD1\\0\\xA5>\\0\\x07\\xFF\\x003~?\\0\\xC22\\xE8\\0\\x98O\\xDE\\0\\xBB}2\\0&=\\xC3\\0k\\xEF\\0\\x9F\\xF8^\\x005:\\0\\x7F\\xF2\\xCA\\0\\xF1\\x87\\0|\\x90!\\0j$|\\0\\xD5n\\xFA\\x000-w\\0;C\\0\\xB5\\xC6\\0\\xC3\\x9D\\0\\xAD\\xC4\\xC2\\0,MA\\0\\f\\0]\\0\\x86}F\\0\\xE3q-\\0\\x9B\\xC6\\x9A\\x003b\\0\\0\\xB4\\xD2|\\0\\xB4\\xA7\\x97\\x007U\\xD5\\0\\xD7>\\xF6\\0\\xA3\\0Mv\\xFC\\0d\\x9D*\\0p\\xD7\\xAB\\0c|\\xF8\\0z\\xB0W\\0\\xE7\\0\\xC0IV\\0;\\xD6\\xD9\\0\\xA7\\x848\\0$#\\xCB\\0\\xD6\\x8Aw\\0ZT#\\0\\0\\xB9\\0\\xF1 +\\x1B\\0\\xCE\\xDF\\0\\x9F1\\xFF\\0fj\\0\\x99Wa\\0\\xAC\\xFBG\\0~\\x7F\\xD8\\0"e\\xB7\\x002\\xE8\\x89\\0\\xE6\\xBF\\\`\\0\\xEF\\xC4\\xCD\\0l6 \\0]?\\xD4\\0\\xDE\\xD7\\0X;\\xDE\\0\\xDE\\x9B\\x92\\0\\xD2"(\\0(\\x86\\xE8\\0\\xE2XM\\0\\xC6\\xCA2\\0\\b\\xE3\\0\\xE0}\\xCB\\0\\xC0P\\0\\xF3\\xA7\\0\\xE0[\\0.4\\0\\x83b\\0\\x83H\\0\\xF5\\x8E[\\0\\xAD\\xB0\\x7F\\0\\xE9\\xF2\\0HJC\\0g\\xD3\\0\\xAA\\xDD\\xD8\\0\\xAE_B\\0ja\\xCE\\0 +(\\xA4\\0\\xD3\\x99\\xB4\\0\\xA6\\xF2\\0\\\\w\\x7F\\0\\xA3\\xC2\\x83\\0a<\\x88\\0\\x8Asx\\0\\xAF\\x8CZ\\0o\\xD7\\xBD\\0-\\xA6c\\0\\xF4\\xBF\\xCB\\0\\x8D\\x81\\xEF\\0&\\xC1g\\0U\\xCAE\\0\\xCA\\xD96\\0(\\xA8\\xD2\\0\\xC2a\\x8D\\0\\xC9w\\0&\\0F\\x9B\\0\\xC4Y\\xC4\\0\\xC8\\xC5D\\0M\\xB2\\x91\\0\\0\\xF3\\0\\xD4C\\xAD\\0)I\\xE5\\0\\xFD\\xD5\\0\\0\\xBE\\xFC\\0\\x94\\xCC\\0p\\xCE\\xEE\\0>\\xF5\\0\\xEC\\xF1\\x80\\0\\xB3\\xE7\\xC3\\0\\xC7\\xF8(\\0\\x93\\x94\\0\\xC1q>\\0. \\xB3\\0\\vE\\xF3\\0\\x88\\x9C\\0\\xAB {\\0.\\xB5\\x9F\\0G\\x92\\xC2\\0{2/\\0\\fUm\\0r\\xA7\\x90\\0k\\xE7\\x001\\xCB\\x96\\0yJ\\0Ay\\xE2\\0\\xF4\\xDF\\x89\\0\\xE8\\x94\\x97\\0\\xE2\\xE6\\x84\\0\\x991\\x97\\0\\x88\\xEDk\\0__6\\0\\xBB\\xFD\\0H\\x9A\\xB4\\0g\\xA4l\\0qrB\\0\\x8D]2\\0\\x9F\\xB8\\0\\xBC\\xE5 \\0\\x8D1%\\0\\xF7t9\\x000\\0\\r\\f\\0K\\bh\\0,\\xEEX\\0G\\xAA\\x90\\0t\\xE7\\0\\xBD\\xD6$\\0\\xF7}\\xA6\\0nHr\\0\\x9F\\xEF\\0\\x8E\\x94\\xA6\\0\\xB4\\x91\\xF6\\0\\xD1SQ\\0\\xCF +\\xF2\\0 \\x983\\0\\xF5K~\\0\\xB2ch\\0\\xDD>_\\0@]\\0\\x85\\x89\\x7F\\0UR)\\x007d\\xC0\\0m\\xD8\\x002H2\\0[Lu\\0Nq\\xD4\\0ETn\\0\\v \\xC1\\0*\\xF5i\\0f\\xD5\\0'\\x07\\x9D\\0]P\\0\\xB4;\\xDB\\0\\xEAv\\xC5\\0\\x87\\xF9\\0Ik}\\0'\\xBA\\0\\x96i)\\0\\xC6\\xCC\\xAC\\0\\xADT\\0\\x90\\xE2j\\0\\x88\\xD9\\x89\\0,rP\\0\\xA4\\xBE\\0w\\x07\\x94\\0\\xF30p\\0\\0\\xFC'\\0\\xEAq\\xA8\\0f\\xC2I\\0d\\xE0=\\0\\x97\\xDD\\x83\\0\\xA3?\\x97\\0C\\x94\\xFD\\0\\r\\x86\\x8C\\x001A\\xDE\\0\\x929\\x9D\\0\\xDDp\\x8C\\0\\xB7\\xE7\\0\\b\\xDF;\\07+\\0\\\\\\x80\\xA0\\0Z\\x80\\x93\\0\\x92\\0\\xE8\\xD8\\0l\\x80\\xAF\\0\\xDB\\xFFK\\x008\\x90\\0Yv\\0b\\xA5\\0a\\xCB\\xBB\\0\\xC7\\x89\\xB9\\0@\\xBD\\0\\xD2\\xF2\\0Iu'\\0\\xEB\\xB6\\xF6\\0\\xDB"\\xBB\\0 +\\xAA\\0\\x89&/\\0d\\x83v\\0 ;3\\0\\x94\\0Q:\\xAA\\0\\xA3\\xC2\\0\\xAF\\xED\\xAE\\0\\\\&\\0m\\xC2M\\0-z\\x9C\\0\\xC0V\\x97\\0?\\x83\\0 \\xF0\\xF6\\0+@\\x8C\\0m1\\x99\\x009\\xB4\\x07\\0\\f \\0\\xD8\\xC3[\\0\\xF5\\x92\\xC4\\0\\xC6\\xADK\\0N\\xCA\\xA5\\0\\xA77\\xCD\\0\\xE6\\xA96\\0\\xAB\\x92\\x94\\0\\xDDBh\\0c\\xDE\\0v\\x8C\\xEF\\0h\\x8BR\\0\\xFC\\xDB7\\0\\xAE\\xA1\\xAB\\0\\xDF1\\0\\0\\xAE\\xA1\\0\\f\\xFB\\xDA\\0dMf\\0\\xED\\xB7\\0)e0\\0WV\\xBF\\0G\\xFF:\\0j\\xF9\\xB9\\0u\\xBE\\xF3\\0(\\x93\\xDF\\0\\xAB\\x800\\0f\\x8C\\xF6\\0\\xCB\\0\\xFA"\\0\\xD9\\xE4\\0=\\xB3\\xA4\\0W\\x1B\\x8F\\x006\\xCD \\0NB\\xE9\\0\\xBE\\xA4\\x003#\\xB5\\0\\xF0\\xAA\\0Oe\\xA8\\0\\xD2\\xC1\\xA5\\0\\v?\\0[x\\xCD\\0#\\xF9v\\0{\\x8B\\0\\x89r\\0\\xC6\\xA6S\\0on\\xE2\\0\\xEF\\xEB\\0\\0\\x9BJX\\0\\xC4\\xDA\\xB7\\0\\xAAf\\xBA\\0v\\xCF\\xCF\\0\\xD1\\0\\xB1\\xF1-\\0\\x8C\\x99\\xC1\\0\\xC3\\xADw\\0\\x86H\\xDA\\0\\xF7]\\xA0\\0\\xC6\\x80\\xF4\\0\\xAC\\xF0/\\0\\xDD\\xEC\\x9A\\0?\\\\\\xBC\\0\\xD0\\xDEm\\0\\x90\\xC7\\0*\\xDB\\xB6\\0\\xA3%:\\0\\0\\xAF\\x9A\\0\\xADS\\x93\\0\\xB6W\\0)-\\xB4\\0K\\x80~\\0\\xDA\\x07\\xA7\\0v\\xAA\\0{Y\\xA1\\0*\\0\\xDC\\xB7-\\0\\xFA\\xE5\\xFD\\0\\x89\\xDB\\xFE\\0\\x89\\xBE\\xFD\\0\\xE4vl\\0\\xA9\\xFC\\0>\\x80p\\0\\x85n\\0\\xFD\\x87\\xFF\\0(>\\x07\\0ag3\\0*\\x86\\0M\\xBD\\xEA\\0\\xB3\\xE7\\xAF\\0\\x8Fmn\\0\\x95g9\\x001\\xBF[\\0\\x84\\xD7H\\x000\\xDF\\0\\xC7-C\\0%a5\\0\\xC9p\\xCE\\x000\\xCB\\xB8\\0\\xBFl\\xFD\\0\\xA4\\0\\xA2\\0l\\xE4\\0Z\\xDD\\xA0\\0!oG\\0b\\xD2\\0\\xB9\\\\\\x84\\0paI\\0kV\\xE0\\0\\x99R\\0PU7\\0\\xD5\\xB7\\x003\\xF1\\xC4\\0n_\\0]0\\xE4\\0\\x85.\\xA9\\0\\xB2\\xC3\\0\\xA126\\0\\b\\xB7\\xA4\\0\\xEA\\xB1\\xD4\\0\\xF7!\\0\\x8Fi\\xE4\\0'\\xFFw\\0\\f\\x80\\0\\x8D@-\\0O\\xCD\\xA0\\0 \\xA5\\x99\\0\\xB3\\xA2\\xD3\\0/] +\\0\\xB4\\xF9B\\0\\xDA\\xCB\\0}\\xBE\\xD0\\0\\x9B\\xDB\\xC1\\0\\xAB\\xBD\\0\\xCA\\xA2\\x81\\0\\bj\\\\\\0.U\\0'\\0U\\0\\x7F\\xF0\\0\\xE1\\x07\\x86\\0\\vd\\0\\x96A\\x8D\\0\\x87\\xBE\\xDE\\0\\xDA\\xFD*\\0k%\\xB6\\0{\\x894\\0\\xF3\\xFE\\0\\xB9\\xBF\\x9E\\0hjO\\0J*\\xA8\\0O\\xC4Z\\0-\\xF8\\xBC\\0\\xD7Z\\x98\\0\\xF4\\xC7\\x95\\0\\rM\\x8D\\0 :\\xA6\\0\\xA4W_\\0?\\xB1\\0\\x808\\x95\\0\\xCC \\0q\\xDD\\x86\\0\\xC9\\xDE\\xB6\\0\\xBF\\\`\\xF5\\0Me\\0\\x07k\\0\\x8C\\xB0\\xAC\\0\\xB2\\xC0\\xD0\\0QUH\\0\\xFB\\0\\x95r\\xC3\\0\\xA3;\\0\\xC0@5\\0\\xDC{\\0\\xE0E\\xCC\\0N)\\xFA\\0\\xD6\\xCA\\xC8\\0\\xE8\\xF3A\\0|d\\xDE\\0\\x9Bd\\xD8\\0\\xD9\\xBE1\\0\\xA4\\x97\\xC3\\0wX\\xD4\\0i\\xE3\\xC5\\0\\xF0\\xDA\\0\\xBA:<\\0FF\\0Uu_\\0\\xD2\\xBD\\xF5\\0n\\x92\\xC6\\0\\xAC.]\\0D\\xED\\0>B\\0a\\xC4\\x87\\0)\\xFD\\xE9\\0\\xE7\\xD6\\xF3\\0"|\\xCA\\0o\\x915\\0\\b\\xE0\\xC5\\0\\xFF\\xD7\\x8D\\0nj\\xE2\\0\\xB0\\xFD\\xC6\\0\\x93\\b\\xC1\\0|]t\\0k\\xAD\\xB2\\0\\xCDn\\x9D\\0>r{\\0\\xC6j\\0\\xF7\\xCF\\xA9\\0)s\\xDF\\0\\xB5\\xC9\\xBA\\0\\xB7\\0Q\\0\\xE2\\xB2\\r\\0t\\xBA$\\0\\xE5}\\\`\\0t\\xD8\\x8A\\0\\r,\\0\\x81\\f\\0~f\\x94\\0)\\0\\x9Fzv\\0\\xFD\\xFD\\xBE\\0VE\\xEF\\0\\xD9~6\\0\\xEC\\xD9\\0\\x8B\\xBA\\xB9\\0\\xC4\\x97\\xFC\\x001\\xA8'\\0\\xF1n\\xC3\\0\\x94\\xC56\\0\\xD8\\xA8V\\0\\xB4\\xA8\\xB5\\0\\xCF\\xCC\\0\\x89-\\0oW4\\0,V\\x89\\0\\x99\\xCE\\xE3\\0\\xD6 \\xB9\\0k^\\xAA\\0>*\\x9C\\0_\\xCC\\0\\xFD\\vJ\\0\\xE1\\xF4\\xFB\\0\\x8E;m\\0\\xE2\\x86,\\0\\xE9\\xD4\\x84\\0\\xFC\\xB4\\xA9\\0\\xEF\\xEE\\xD1\\0.5\\xC9\\0/9a\\x008!D\\0\\x1B\\xD9\\xC8\\0\\x81\\xFC +\\0\\xFBJj\\0/\\xD8\\0S\\xB4\\x84\\0N\\x99\\x8C\\0T"\\xCC\\0*U\\xDC\\0\\xC0\\xC6\\xD6\\0\\v\\x96\\0p\\xB8\\0i\\x95d\\0&Z\\\`\\0?R\\xEE\\0\\x7F\\0\\xF4\\xB5\\0\\xFC\\xCB\\xF5\\x004\\xBC-\\x004\\xBC\\xEE\\0\\xE8]\\xCC\\0\\xDD^\\\`\\0g\\x8E\\x9B\\0\\x923\\xEF\\0\\xC9\\xB8\\0aX\\x9B\\0\\xE1W\\xBC\\0Q\\x83\\xC6\\0\\xD8>\\0\\xDDqH\\0-\\xDD\\0\\xAF\\xA1\\0!,F\\0Y\\xF3\\xD7\\0\\xD9z\\x98\\0\\x9ET\\xC0\\0O\\x86\\xFA\\0V\\xFC\\0\\xE5y\\xAE\\0\\x89"6\\x008\\xAD"\\0g\\x93\\xDC\\0U\\xE8\\xAA\\0\\x82&8\\0\\xCA\\xE7\\x9B\\0Q\\r\\xA4\\0\\x993\\xB1\\0\\xA9\\xD7\\0iH\\0e\\xB2\\xF0\\0\\x7F\\x88\\xA7\\0\\x88L\\x97\\0\\xF9\\xD16\\0!\\x92\\xB3\\0{\\x82J\\0\\x98\\xCF!\\0@\\x9F\\xDC\\0\\xDCGU\\0\\xE1t:\\0g\\xEBB\\0\\xFE\\x9D\\xDF\\0^\\xD4_\\0{g\\xA4\\0\\xBA\\xACz\\0U\\xF6\\xA2\\0+\\x88#\\0A\\xBAU\\0Yn\\b\\0!*\\x86\\x009G\\x83\\0\\x89\\xE3\\xE6\\0\\xE5\\x9E\\xD4\\0I\\xFB@\\0\\xFFV\\xE9\\0\\xCA\\0\\xC5Y\\x8A\\0\\x94\\xFA+\\0\\xD3\\xC1\\xC5\\0\\xC5\\xCF\\0\\xDBZ\\xAE\\0G\\xC5\\x86\\0\\x85Cb\\0!\\x86;\\0,y\\x94\\0a\\x87\\0*L{\\0\\x80,\\0C\\xBF\\0\\x88&\\x90\\0x<\\x89\\0\\xA8\\xC4\\xE4\\0\\xE5\\xDB{\\0\\xC4:\\xC2\\0&\\xF4\\xEA\\0\\xF7g\\x8A\\0\\r\\x92\\xBF\\0e\\xA3+\\0=\\x93\\xB1\\0\\xBD|\\v\\0\\xA4Q\\xDC\\0'\\xDDc\\0i\\xE1\\xDD\\0\\x9A\\x94\\0\\xA8)\\x95\\0h\\xCE(\\0 \\xED\\xB4\\0D\\x9F \\0N\\x98\\xCA\\0p\\x82c\\0~|#\\0\\xB92\\0\\xA7\\xF5\\x8E\\0V\\xE7\\0!\\xF1\\b\\0\\xB5\\x9D*\\0o~M\\0\\xA5Q\\0\\xB5\\xF9\\xAB\\0\\x82\\xDF\\xD6\\0\\x96\\xDDa\\06\\0\\xC4:\\x9F\\0\\x83\\xA2\\xA1\\0r\\xEDm\\x009\\x8Dz\\0\\x82\\xB8\\xA9\\0k2\\\\\\0F'[\\0\\x004\\xED\\0\\xD2\\0w\\0\\xFC\\xF4U\\0YM\\0\\xE0q\\x80\\0A\\xB3\\xB0 +\\v\\x85\\x07@\\xFB!\\xF9?\\0\\0\\0\\0-Dt>\\0\\0\\0\\x80\\x98F\\xF8<\\0\\0\\0\\\`Q\\xCCx;\\0\\0\\0\\x80\\x83\\x1B\\xF09\\0\\0\\0@ %z8\\0\\0\\0\\x80"\\x82\\xE36\\0\\0\\0\\0\\xF3i5utf8\\0char\\0\\0\\xC8wchart\\0\\0\\xC6ucs2be\\0\\0\\xC4ucs2le\\0\\0\\xC5utf16be\\0\\0\\xC2utf16le\\0\\0\\xC1ucs4be\\0utf32be\\0\\0\\xC0ucs4le\\0utf32le\\0\\0\\xC3ascii\\0usascii\\0iso646\\0iso646us\\0\\0\\xC7utf16\\0\\0\\xCAucs4\\0utf32\\0\\0\\xCBucs2\\0\\0\\xCCeucjp\\0\\0\\xD0shiftjis\\0sjis\\0cp932\\0\\0\\xD1iso2022jp\\0\\0\\xD2gb18030\\0\\0\\xD8gbk\\0\\0\\xD9gb2312\\0\\0\\xDAbig5\\0bigfive\\0cp950\\0big5hkscs\\0\\0\\xE0euckr\\0ksc5601\\0ksx1001\\0cp949\\0\\0\\xE8iso88591\\0latin1\\0\\0@iso88592\\0\\0(\\xA0\\xF4WN\\xA4\\xDC\\xF4\\xD4)\\xA8T5UVn\\xB5"\\\\\\xB0\\x98N\\xB4\\xE0\\x95_\\xB8XE\\x95Vo\\r6W\\\\I#\\x8C@\\xC4\\xCCd\\xD01\\f%c\\xD125\\xE3\\x8CC\\xED\\xF4\\xD34\\xD4e\\xCD5M\\x8D\\xA5MY\\xDCts\\xD57J\\x85#\\xCE@\\xE4\\xD0t\\xD09\\r\\xA5s\\xD1:\\xB5\\xE3\\xCEC\\xF1\\xD4<\\xF4e\\xCF=N\\x91\\xA5\\x8FY\\xFC\\xF4\\x83\\\`iso88593\\0\\0(\\xA0\\x90\\xF4\\xD7(\\xA4\\0 \\xD2)\\xA8\\xB04G.\\xB5\\0\\\\\\xB0\\x94$\\xCB,\\xB4\\xD42\\xD2-\\xB8\\xB4DUG/\\xF5@\\\\\\xC0#\\f\\0\\xC4(\\x84\\xD01\\xC8$\\xA3\\xCC2\\xCC4\\xE3\\xCC3\\0D#\\xCD4\\xD4xd\\xCD5e\\xA3\\xCD6\\xDC\\x84\\xD57\\xE0\\x84#\\0\\xE4,\\x94\\xD09\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\0\\xC4#\\xCF<\\xF4|d\\xCF=\\x1B\\xE5\\xA3\\xCF>\\xFC\\x88%\\\`iso88594\\0\\0(\\xA0$\\xD3R\\xA4\\x98T\\xD3)\\xA8T%H[\\xB5"\\xD7+\\xB0S\\xB4\\x9Cd\\x93_\\xB8X5QH\\\\5\\x97P\\0#\\xCC0\\xC4c\\x8CJ\\f%c\\xD125\\xE3\\fJ\\xF54L\\xD4Tc\\xCD5\\xD8\\x9C\\xA5\\xCD6\\xDCt\\xF5\\xD57\\x85#\\xCE8\\xE4\\x94c\\xCEJ\\r\\xA5s\\xD1:\\xB5\\xE3NJ\\xF9DTL\\xF4\\xD4c\\xCF=\\xF8\\xA0\\xA5\\xCF>\\xFCx\\\`iso88595\\0\\0(\\xA0D'\\xDDt\\xD4Ug\\xDDu\\xD8e\\xA7\\xDDv\\xDC\\xB5\\xD2\\x9Dw\\xDF\\x81\\x9Ex\\xE3\\x91W\\x9Ey\\xE7\\xA1\\x97\\x9Ez\\xEB\\xB1\\xD7\\x9E{\\xEF\\xC1\\x9F|\\xF3\\xD1W\\x9F}\\xF7\\xE1\\x97\\x9F~\\xFB\\xF1\\xD7\\x9F\\x7F\\xFF\\xA0\\x80X\\xA0\\x81\\x07"\\x98\\xA0\\x82\\v2\\xD8\\xA0\\x83B\\xA1\\x84RX\\xA1\\x85b\\x98\\xA1\\x86\\x1Br\\xD8\\xA1\\x87&\\x7F\\bb\\x88"\\x8EHb\\x89&\\x9E\\x88b\\x8A*\\x9E\\xB2"\\x8Biso88596\\0\\0(\\xA0\\0\\0\\0\\0\\xA4\\0A\\xC1\\xB7 +\\vb\\xB6\\0A\\xD3\\xB7 +\\vG\\xC0\\x98\\0\\0\\0\\0\\x99\\0\\x94i\\xE6\\x99h\\xA6\\xA9\\xE6\\x9Al\\xB6\\xE9\\xE6\\x9Bp\\xC6)\\xE7\\x9Ct\\xD6i\\xE7\\x9Dx\\xE6\\xA9\\xE7\\x9E|\\xF6\\xE9'\\0\\0\\0\\0\\0\\0\\x7F\\xA8\\xA0\\x83Z\\xA8\\xA1\\x87"\\x9A\\xA8\\xA2\\x8B2\\xDA\\xA8\\xA3\\x8FB)\\0A\\xAA\\xB8 +\\v\\xB5iso88597\\0\\0(\\xA0LL\\xF1($\\x97l\\xCA)\\xA8\\xA4\\x92\\xD8*\\xAC\\xB4@\\xC4\\xB0\\xC4"\\xCB,\\x8A-\\xC6\\xD8-\\x8D9\\xF6\\xD8.\\x90\\xF5\\x99d\\x93QV\\x99e\\x97a\\x96\\x99f\\x9Bq\\xD6\\x99g\\x9F\\x81\\x9Ah\\xA3\\x91@i\\xA6\\x9D\\x86Zj\\xAA\\xAD\\xC6Zk\\xAE\\xBD[l\\xB2\\xCDF[m\\xB6\\xDD\\x86[n\\xBA\\xED\\xC6[o\\xBE\\xFD\\\\p\\xC2\\rG\\\\q\\xC6\\x87\\\\r\\xCA-\\xC7\\\\s\\xCE=\\x07\\0iso88598\\0\\0(\\xA0\\0 \\xCA(\\xA4\\x94b\\xCA)\\xA8\\xA4r\\xCD*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xB8\\xE4r\\xCF.\\xBC\\xF4\\xE2\\v\\0A\\x86\\xBA +\\v\\x9B\\x80\\xC4BId\\x91F\\x89d\\x92J.\\xC9d\\x93N> e\\x94RNIe\\x95V^\\x89e\\x96Zn\\xC9%\\0\\x004\\xEC0\\0iso88599\\0\\x004E#\\xCD4\\xD4Tc\\xCD5\\xD8d\\xA3\\xCD6\\xDC\\xB04\\xD57\\xE0\\x84#\\xCE8\\xE4\\x94c\\xCE9\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\xC5#\\xCF<\\xF4\\xD4c\\xCF=\\xF8\\xE4\\xA3\\xCF>\\xFC\\xB4D\\xD5?iso885910\\0\\0(\\xA0$H(\\x99\\xD3)5AT\\xD5Vr\\xB5\\xF2UP\\xB04QH)\\x9D\\xD3-6EdWsE\\f\\x96P\\0#\\xCC0\\xC4c\\x8CJ\\f%c\\xD125\\xE3\\xCC3\\xD0\\xF44\\xD44\\xD4TcMW\\xD8\\x9C\\xA5\\xCD6\\xDCt\\xE3\\xCD7\\x85#\\xCE8\\xE4\\x94c\\xCEJ\\r\\xA5s\\xD1:\\xB5\\xE3\\xCE;\\xF0\\xF8D\\xD4<\\xF4\\xD4c\\x8FW\\xF8\\xA0\\xA5\\xCF>\\xFC\\xF4\\xE3\\x8FLiso885911\\0tis620\\0\\0(\\xA0x\\xFA)\\xA8\\xA1\\x8A:*\\xA9\\xA5\\x9Az*\\xAA\\xA9\\xAA\\xBA*\\xAB\\xAD\\xBA\\xFA*\\xAC\\xB1\\xCA:+\\xAD\\xB5\\xDAz+\\xAE\\xB9\\xEA\\xBA+\\xAF\\xBD\\xFA\\xFA+\\xB0\\xC1 +;,\\xB1\\xC5{,\\xB2\\xC9*\\xBB,\\xB3\\xCD:\\xFB,\\xB4\\xD1J;-\\xB5\\xD5Z{-\\0\\0\\0\\0\\0\\xB6\\xD9j\\xBB-\\xB7\\xDDz\\xFB-\\xB8\\xE1\\x8A;.\\xB9\\xE5\\x9A{.\\xBA\\xE9\\xAA\\xBB.\\xBB\\xED\\xBA\\xFB.\\xBC\\xF1\\xCA;/\\xBD\\0\\0\\0\\0\\0iso885913\\0\\0(\\xA0\\\\,\\xCA(\\xA4\\\`l\\xCA)\\xD8\\xA4\\xB2\\xD4*\\xAC\\xB4\\xE2\\x8A1\\xB0\\xC4"\\xCB,\\xD7b\\xCB-\\xF8\\xE4\\xC2\\xD4.\\xBC\\xF4\\xE2\\x8B9\\xA9\\x90A\\xC4c\\x91D\\f%\\xE3E \\xC1\\x84RMU\\xED\\xD4\\xD34CUc\\xCD5g\\xE5\\xF4\\xD4W\\xDC\\xC0%\\xD77\\xAD\\xD0A\\xE4\\x94s\\xD1D\\r\\xA5\\xF3VE!\\xC5\\x94\\x92MV\\xF1\\xE4\\xD3 \\xFC\\xF4\\xC3\\xD6?iso885915\\0latin9\\0\\0)$\\x97R\\xD5)V\\xA5\\xA2\\xCA*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,r\\xD5b\\xCB-s\\xE5\\xA2\\xCB.G!\\xD5\\xD6/\\xC0#\\xCC0\\xC4c\\xCC1\\xC8$\\xA3\\xCC2\\xCC4\\xE3\\xCC3\\xD0D#\\xCD4\\xD4Tc\\xCD5\\xD8d\\xA3\\xCD6\\xDCt\\xE3\\xCD7\\xE0\\x84#\\xCE8\\xE4\\x94c\\xCE9\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\xF0\\xC4#\\xCF<\\xF4\\xD4c\\xCF=\\xF8\\xE4\\xA3\\xCF>\\xFC\\xF4\\xE3\\xCF?iso885916\\0\\0(\\xA0TPN$c\\\\\\xD5)V\\xA5\\x92\\xD7*n\\xB5\\xF2\\\\\\xB0\\xC4\\xC2\\x90Nr]l\\xCB-s5\\xA4\\xD7.G!\\xD5V\\\\\\xC0#\\x8C@\\xC4d\\xCC1\\xC8$\\xA3\\xCC2\\xCC4\\xE3\\xCC3\\xED$\\xCD4\\xD4e\\xCDSee\\xA3\\xCD6\\xDCX\\xB4\\xD77\\xE0\\x84#\\xCE@\\xE4d\\xCE9\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\xF1$\\xCF<\\xF4eTf\\xE5\\xA3\\xCF>\\xFC\\\\\\xC4\\xD7?cp1250\\0windows1250\\0\\0 $P1\\0s\\x9C\\xB1\\xC6\\0t\\\\\\x95\\xC7Oe%\\x97[\\0LL\\xB1\\xC5o\\xFC0\\xC4\\0\\x9Cl\\xD5\\xC7Pi5\\xD7[\\xA0\\xF8\\xF5WN\\xA4d\\xCA)\\xA8\\xA42\\xD5*\\xAC\\xB4\\xE2 +\\\\\\xB0\\xC4\\x98N\\xB4\\xD4b\\xCB-\\xB8D\\xD5.7\\r\\x86S\\\\I#\\x8C@\\xC4\\xCCd\\xD01\\f%c\\xD125\\xE3\\x8CC\\xED\\xF4\\xD34\\xD4e\\xCD5M\\x8D\\xA5MY\\xDCts\\xD57J\\x85#\\xCE@\\xE4\\xD0t\\xD09\\r\\xA5s\\xD1:\\xB5\\xE3\\xCEC\\xF1\\xD4<\\xF4e\\xCF=N\\x91\\xA5\\x8FY\\xFC\\xF4\\x83\\\`cp1251\\0windows1251\\0\\0 \\xD2MWq\\x88s\\x9C\\xB1\\xC6$w\\x9C\\x9D\\xC7\\xDAq\\xB7\\x9Dw NL\\xB1\\xC5o\\xFC0\\xC4\\0\\x9C|\\xE2\\xC7(\\xAA\\x98"\\x8B\\xA0t\\xB7"v\\xA4\\xB4h\\xCA)\\xD1\\xA5B\\xDD*\\xAC\\xB4\\xE2\\xCAu\\xB0\\xC4b\\x89.\\xD6b\\xCB-\\x9A,\\xE2.&V7b\\x89\\xDF\\x81\\x9Ex\\xE3\\x91W\\x9Ey\\xE7\\xA1\\x97\\x9Ez\\xEB\\xB1\\xD7\\x9E{\\xEF\\xC1\\x9F|\\xF3\\xD1W\\x9F}\\xF7\\xE1\\x97\\x9F~\\xFB\\xF1\\xD7\\x9F\\x7F\\xFF\\xA0\\x80X\\xA0\\x81\\x07"\\x98\\xA0\\x82\\v2\\xD8\\xA0\\x83B\\xA1\\x84RX\\xA1\\x85b\\x98\\xA1\\x86\\x1Br\\xD8\\xA1\\x87cp1252\\0windows1252\\0\\0 $P1]s\\x9C\\xB1\\xC6}u\\\\\\x95\\xC7G \\0\\0LL\\xB1\\xC5o\\xFC0\\xC4\\x82\\x9Dl\\xD5\\xC7H0W[\\xA0\\x84"\\xCA(\\xA4\\x94b\\xCA)\\xA8\\xA4\\xA2\\xCA*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xB8\\xE4\\xA2\\xCB.\\xBC\\xF4\\xE2\\xCB/\\xC0#\\xCC0\\xC4c\\xCC1\\xC8$\\xA3\\xCC2\\xCC4\\xE3\\xCC3\\xD0D#\\xCD4\\xD4Tc\\xCD5\\xD8d\\xA3\\xCD6\\xDCt\\xE3\\xCD7\\xE0\\x84#\\xCE8\\xE4\\x94c\\xCE9\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\xF0\\xC4#\\xCF<\\xF4\\xD4c\\xCF=\\xF8\\xE4\\xA3\\xCF>\\xFC\\xF4\\xE3\\xCF?cp1253\\0windows1253\\0\\0 $P1]s\\x9C\\xB1\\xC6\\0t\\f\\x80\\xC7\\0\\0\\0\\0\\0\\0LL\\xB1\\xC5o\\xFC0\\xC4\\0\\x9C\\f\\xC0\\xC7\\0\\0\\0\\0\\0\\xA0,\\xC6\\xD8(\\xA4\\x94b\\xCA)\\xA8\\xA4\\xC0*\\xAC\\xB4\\xE2J\\xC4\\xB0\\xC4"\\xCB,\\x8A\\xD5b\\xCB-\\x8D9\\xF6\\xD8.\\x90\\xF5\\x99d\\x93QV\\x99e\\x97a\\x96\\x99f\\x9Bq\\xD6\\x99g\\x9F\\x81\\x9Ah\\xA3\\x91@i\\xA6\\x9D\\x86Zj\\xAA\\xAD\\xC6Zk\\xAE\\xBD[l\\xB2\\xCDF[m\\xB6\\xDD\\x86[n\\xBA\\xED\\xC6[o\\xBE\\xFD\\\\p\\xC2\\rG\\\\q\\xC6\\x87\\\\r\\xCA-\\xC7\\\\s\\xCE=\\x07\\0cp1254\\0windows1254\\0\\0 $P1]s\\x9C\\xB1\\xC6}u\\\\\\x95\\xC7G\\0\\0\\0\\0LL\\xB1\\xC5o\\xFC0\\xC4\\x82\\x9Dl\\xD5\\xC7H\\0@[\\xA0\\x84"\\xCA(\\xA4\\x94b\\xCA)\\xA8\\xA4\\xA2\\xCA*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xB8\\xE4\\xA2\\xCB.\\xBC\\xF4\\xE2\\xCB/\\xC0#\\xCC0\\xC4c\\xCC1\\xC8$\\xA3\\xCC2\\xCC4\\xE3\\xCC3E#\\xCD4\\xD4Tc\\xCD5\\xD8d\\xA3\\xCD6\\xDC\\xB04\\xD57\\xE0\\x84#\\xCE8\\xE4\\x94c\\xCE9\\xE8\\xA4\\xA3\\xCE:\\xEC\\xB4\\xE3\\xCE;\\xC5#\\xCF<\\xF4\\xD4c\\xCF=\\xF8\\xE4\\xA3\\xCF>\\xFC\\xB4D\\xD5?cp1255\\0windows1255\\0\\0 $P1]s\\x9C\\xB1\\xC6}u\\f\\x80\\xC7\\0\\0\\0\\0\\0\\0LL\\xB1\\xC5o\\xFC0\\xC4\\x82\\x9D\\f\\xC0\\xC7\\0\\0\\0\\0\\0\\xA0\\x84"\\xCA("\\x97b\\xCA)\\xA8\\xA4r\\xCD*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xB8\\xE4r\\xCF.\\xBC\\xF4\\xE2\\xCB//\\xC2\\xA3\\x8C3\\xD2X\\xA3\\x8D7\\xE2\\b@\\x8E:\\xEE\\xC8c\\x8F>\\xFE\\bd\\x90]z\\xF9%\\x98a\\0\\0\\0\\0\\0\\0\\0\\0BId\\x91F\\x89d\\x92J.\\xC9d\\x93N> e\\x94RNIe\\x95V^\\x89e\\x96Zn\\xC9%\\0\\x004\\xEC0\\0cp1256\\0windows1256\\0\\0 $OZ1]s\\x9C\\xB1\\xC6}u,\\xA9\\xC7GQzi\\xA5\\x99NL\\xB1\\xC5o\\xFC0\\xC4\\x98\\x9El\\xE9\\xC7H-\\xCC\\xB0\\xA6\\xA0\\x88)\\xCA(\\xA4\\x94b\\xCA)\\xA8\\xA4\\xB2\\xE9*\\xAC\\xB4\\xE2\\xCA+\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xB8\\xE42\\xE6.\\xBC\\xF4\\xE2\\v\\x99\\x9C\\x96i\\xE6\\x99h\\xA6\\xA9\\xE6\\x9Al\\xB6\\xE9\\xE6\\x9Bp\\xC6)\\xE7\\x9Ct\\xD6i\\xE7\\x9Dx\\xE6\\xA9\\xE75{\\xF2\\xD9\\xA7\\x9F\\x7F\\xA8\\xA0\\xE0\\f*\\xA1\\x85z\\xE89\\xE8\\xA4\\xA3\\xCE:\\x88&\\xEA\\xCE;\\x8A.\\xCAh\\xA3\\xF48\\xFA\\xE8=\\x90\\xE6\\xE9>\\xFC4\\xECp\\xA7cp1257\\0windows1257\\0\\0 $P1\\0s\\x9C\\xB1\\xC6\\0t\\f\\x80\\xC7\\0\\xA0\\xE2.\\0LL\\xB1\\xC5o\\xFC0\\xC4\\0\\x9C\\f\\xC0\\xC7\\0\\xBC\\0\\xA0\\0 \\xCA(\\xA4\\0\\\`\\xCA)\\xD8\\xA4\\xB2\\xD4*\\xAC\\xB4\\xE2\\x8A1\\xB0\\xC4"\\xCB,\\xB4\\xD4b\\xCB-\\xF8\\xE4\\xC2\\xD4.\\xBC\\xF4\\xE2\\x8B9\\xA9\\x90A\\xC4c\\x91D\\f%\\xE3E \\xC1\\x84RMU\\xED\\xD4\\xD34CUc\\xCD5g\\xE5\\xF4\\xD4W\\xDC\\xC0%\\xD77\\xAD\\xD0A\\xE4\\x94s\\xD1D\\r\\xA5\\xF3VE!\\xC5\\x94\\x92MV\\xF1\\xE4\\xD3 \\xFC\\xE05\\xF2?koi8r\\0\\0 3\\xD3\\\\\\xB3\\xCD7\\xE3\\x9C\\xB3\\xCE;\\xF3\\xDC\\xF3\\xD6\\\\w\\xED\\xF5\\xD7\\\`\\x87-v\\xCCc\\xA3\\x9C2\\xCB.\\xBF\\f\\x8A\\xCC\\xB0\\xC8r\\xCB=>\\xFF\\f\\xF4\\x87A\\v=4\\xD1E\\x1B}4\\xD2I+\\xBD4\\xD3M;\\xFDttPG-\\xF5\\xD4TWm\\xF5\\xD5Xg\\xADu*\\xFE\\x07\\\`\\x858\\xA1\\x80\\x88\\\`\\x82 +.\\xC8\\\`\\x83z\\xF8 \\x84JX\\\`\\x80\\x1Bjh\\xE0\\x85bha\\x86\\xFD}\\x07^}\\xE3\\x917\\x9Fx\\xF4\\x9D\\x87^z\\xEA\\xAD\\xC7^{\\xEE\\xF9\\xF7|\\xF1\\xC9W^x\\xFB\\xE9g\\xDE}\\xFC\\xE1g_~koi8u\\0\\0 3\\xD3\\\\\\xB3\\xCD7\\xE3\\x9C\\xB3\\xCE;\\xF3\\xDC\\xF3\\xD6\\\\w\\xED\\xF5\\xD7\\\`\\x87-v\\xCCc\\xA3\\x9C2\\xCB.\\xBF\\f\\x8A\\xCC\\xB0\\xC8r\\xCB=>\\xFF\\f\\xF4\\x87" +Mb\\x89E\\x1B}4\\xD2I\\xBB\\xB84\\xD3M;\\xFDtt\\xD4Em\\xDDuTWm\\xF5\\xD5X\\xB7\\xA8u*\\xFE\\x07\\\`\\x858\\xA1\\x80\\x88\\\`\\x82 +.\\xC8\\\`\\x83z\\xF8 \\x84JX\\\`\\x80\\x1Bjh\\xE0\\x85bha\\x86\\xFD}\\x07^}\\xE3\\x917\\x9Fx\\xF4\\x9D\\x87^z\\xEA\\xAD\\xC7^{\\xEE\\xF9\\xF7|\\xF1\\xC9W^x\\xFB\\xE9g\\xDE}\\xFC\\xE1g_~cp437\\0\\0 \\xC7\\xF0\\x93\\x8E8\\xE4\\x80S\\xCE9\\xEA\\xAC\\x83\\xCE;\\xEE\\xB0CL1\\xC9\\x98c\\f=\\xF6\\xC8\\xB3O>\\xFFX\\xC3\\x8D(\\xA3\\x942]\\xE1\\xB43\\x8F>\\xF1D\\xA3\\x8A.\\xBF\\xC0\\xCCJ/\\xBC\\x84\\xB2\\xCA.\\\`\\x87-6\\xCD:?\\r5\\xD1CG\\xFDs\\xD1K+\\x9D\\xB4\\xCD7\\xF3\\xBCs\\xCE3\\xF7\\xCCt\\xD3H\\v}5\\xD5N\\xFB\\xACu\\xD5VK=\\xF5\\xD1Ft\\xD6X\\xE3\\\\s\\xD7\\\\{\\xFD\\xF5\\xD6\\xB3}c\\x99p\\xA5W\\x8Bq\\xA8m\\xB6\\x9Am*#w\\xDB\\xCA-\\xC7\\xF2\\xB2\\xCB1\\xCB|\\xCB\\xB0\\xA0|K\\xCA \\xCB26(cp850\\0\\0 \\xC7\\xF0\\x93\\x8E8\\xE4\\x80S\\xCE9\\xEA\\xAC\\x83\\xCE;\\xEE\\xB0CL1\\xC9\\x98c\\f=\\xF6\\xC8\\xB3O>\\xFFX\\xC3\\r>\\xA3\\\`s\\r]\\xE1\\xB43\\x8F>\\xF1D\\xA3\\x8A.\\xBF\\xB8\\xC2J/\\xBC\\x84\\xB2\\xCA.\\\`\\x87-6\\xCD:\\x07#\\f0\\xA9D\\xFDs\\xD1K\\x8BR\\x8A\\xCD7\\xF3\\xBCs\\xCE3\\xF7<\\xCE0H\\v}5\\xD5N\\xFB\\xAC5)\\xF0@\\xA3\\xCC2\\xC8\\xB4\\xD4\\x8C3\\xCF\\xE0\\\\s\\xD7\\\\\\x9B\\xC2\\xCC\\xD6\\xD3|C\\x8D4\\xF5TS\\x8B?\\xDEh\\xB3M6\\xFDt\\xF3 +-\\xAD\\xC4"\\xB1/\\xB6\\x9Cr.\\xB0\\xA0rK.\\xB3\\xC826(cp866\\0\\0 \\xDF\\x81\\x9Ex\\xE3\\x91W\\x9Ey\\xE7\\xA1\\x97\\x9Ez\\xEB\\xB1\\xD7\\x9E{\\xEF\\xC1\\x9F|\\xF3\\xD1W\\x9F}\\xF7\\xE1\\x97\\x9F~\\xFB\\xF1\\xD7\\x9F\\x7F\\xFF\\xA0\\x80X\\xA0\\x81\\x07"\\x98\\xA0\\x82\\v2\\xD8\\xA0\\x83\\\`\\x87-6\\xCD:?\\r5\\xD1CG\\xFDs\\xD1K+\\x9D\\xB4\\xCD7\\xF3\\xBCs\\xCE3\\xF7\\xCCt\\xD3H\\v}5\\xD5N\\xFB\\xACu\\xD5VK=\\xF5\\xD1Ft\\xD6X\\xE3\\\\s\\xD7\\\\{\\xFD\\xF5\\xD6B\\xA1\\x84RX\\xA1\\x85b\\x98\\xA1\\x86\\x1Br\\xD8\\xA1\\x87\\xD1}H\\x9D\\x88\\xD7\\x95\\xD8\\xDD\\x8A\\xB0\\xA0|K\\xCA&\\x9326(ibm1047\\0cp1047\\0\\0\\x9C$\\\`\\xC8\\x974\\xE2\\xC8\\f4\\xE0\\xC0D \\xC1\\x9D\\x82\\xC0!d \\xC9#t\\xE0\\xC1\\x07\\x80"\\xC8 \\x84(p\\xC1\\x88$\\xA2\\xC8"\\x8C\\\`\\xC0\\x90Db\\xC1$\\x94Tb \\x98d\\xA2\\xC9&T\\xE0\\x89 \\x80"9\\xE0\\x843N9\\xE7\\xC4#\\x8A\\v<\\xA0\\xB0&\\xA4\\xA3\\xCE:\\xE8\\xB4\\xE3\\xCE;\\xEC| *\\xA4\\xB0\\x83-\\xBC \\f1\\xC03L1\\xC7Dc +\\v%|\\xE1\\xC3\\xF8$\\xA3\\xCC2\\xC84\\xE3\\xCC3\\xCC\\x80\\xA1\\xC3\\b@\\x9C\\xD0\\x83\\b\\xD8\\x84!\\xC6d\\x94a\\xC6h\\xA4\\xB1\\xCA.\\xF0\\xF4\\xE3O,\\xB0\\xA8\\xB1\\x1Bm\\xB8\\xF1q\\xC8\\xA1\\x8A.\\xE6\\xE0b\\f)\\xB5\\xF81\\x07u\\xD8q\\x07y\\xE8\\xCA/\\xD0l\\xE1\\x8D+\\xAC\\x8CR\\xCA-\\xA9\\x9Cb\\v/\\xBD\\xF8\\xD2\\r*\\xAFtA\\xCB5{!\\xC4Da\\xC4H$\\xD1 +=\\xF6\\xC83O=}(\\xB1M8\\xF1QH\\x91\\xCB>\\xFC\\xE4\\xA3\\xCF?\\\\\\xDC3UXqYh!\\v5\\xD6H3M50\\xC4 \\xC3\\f4\\xD4\\\`\\xC3\\r8\\xE40\\xCB6\\xDCd\\xA3\\xCD'\\0\\0\\0\\0\\0\\0\\0\\xD1t\\x9E\\0W\\x9D\\xBD*\\x80pR\\xFF\\xFF>' +\\0\\0\\0d\\0\\0\\0\\xE8\\0\\0'\\0\\0\\xA0\\x86\\0@B\\0\\x80\\x96\\x98\\0\\0\\xE1\\xF5\\0\\0\\x005\\0\\0\\0q\\0\\0\\0k\\xFF\\xFF\\xFF\\xCE\\xFB\\xFF\\xFF\\x92\\xBF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0 + + +\\x07\\0\\0 \\v\\0\\0 \\v\\0\\0\\v\\0\\0\\0\\0\\0A\\xB1\\xD8 +\\v!\\0\\0\\0\\0\\0\\0\\0\\0\\0\\v\\r\\0\\r\\0\\0\\0 \\0\\0\\0 \\0\\0\\0\\0A\\xEB\\xD8 +\\v\\f\\0A\\xF7\\xD8 +\\v\\0\\0\\0\\0\\0\\0\\0\\0 \\f\\0\\0\\0\\0\\0\\f\\0\\0\\f\\0A\\xA5\\xD9 +\\v\\0A\\xB1\\xD9 +\\v\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0A\\xDF\\xD9 +\\v\\0A\\xEB\\xD9 +\\v\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0A\\xA2\\xDA +\\v\\0\\0\\0\\0\\0\\0\\0\\0\\0 \\0A\\xD3\\xDA +\\v\\0A\\xDF\\xDA +\\v\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0A\\x8D\\xDB +\\v\\0A\\x99\\xDB +\\v\\xD7\\0\\0\\0\\0\\0\\0\\0\\0 \\0\\0\\0\\0\\0\\0\\0\\0\\x000123456789ABCDEF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\x07\\b \\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF +\\v\\f\\r\\x1B !"#\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF +\\v\\f\\r\\x1B !"#\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\x07\\0\\0\\0\\0\\0\\0\\0-DT\\xFB!\\xE9?-DT\\xFB!\\xE9\\xBF\\xD2!3\\x7F|\\xD9@\\xD2!3\\x7F|\\xD9\\xC0\\0A\\xFF\\xDD +\\v\\xD9\\x80-DT\\xFB! @-DT\\xFB! \\xC08c\\xED>\\xDAI?^\\x98{?\\xDA\\xC9?i7\\xAC1h!"3\\xB43h!\\xA23\\xBE\\xF3\\xF8y\\xECa\\xF6?0\\x96[\\xC6\\xFE\\xDE\\xBF=\\x88\\xAFJ\\xEDq\\xF5?\\xA4\\xFC\\xD42h\\v\\xDB\\xBF\\xB0\\xF0\\xF09\\x95\\xF4?{\\xB7 +\\x8BA\\xD7\\xBF\\x85\\xB8\\xB0\\x95\\xC9\\xF3?{\\xCFm\\xE9\\x9D\\xD3\\xBF\\xA5d\\x88\\f\\r\\xF3?1\\xB6\\xF2\\xF3\\x9B\\xD0\\xBF\\xA0\\x8E\\v{"^\\xF2?\\xF0z;\\x1B|\\xC9\\xBF?4JJ\\xBB\\xF1?\\x9F<\\xAF\\x93\\xE3\\xF9\\xC2\\xBF\\xBA\\xE5\\x8A\\xF0X#\\xF1?\\\\\\x8Dx\\xBF\\xCB\\\`\\xB9\\xBF\\xA7\\0\\x99A?\\x95\\xF0?\\xCE_G\\xB6\\x9Do\\xAA\\xBF\\0\\0\\0\\0\\0\\0\\xF0?\\0\\0\\0\\0\\0\\0\\0\\0\\xACG\\x9A\\xFD\\x8C\\\`\\xEE?=\\xF5$\\x9F\\xCA8\\xB3?\\xA0j\\xB3\\xA4\\xEC?\\xBA\\x918T\\xA9v\\xC4?\\xE6\\xFCjW6 \\xEB?\\xD2\\xE4\\xC4J\\v\\x84\\xCE?-\\xAA\\xA1c\\xD1\\xC2\\xE9?e\\xC6\\xF0E\\xD4?\\xEDAx\\xE6\\x86\\xE8?\\xF8\\x9F\\x1B,\\x9C\\x8E\\xD8?bHS\\xF5\\xDCg\\xE7?\\xCC{\\xB1N\\xA4\\xE0\\xDC?\\vnI\\xC9v\\xD2?z\\xC6u\\xA0i\\xD7\\xBF\\xDD\\xBA\\xA7l +\\xC7\\xDE?\\xC8\\xF6\\xBEHG\\xE7\\xBF+\\xB8*eG\\xF7?\\0A\\xE4\\xE0 +\\v:h\\0\\0h\\0\\0i\\0\\0i\\0\\0i\\0\\0i\\0\\0i\\0\\0i\\0\\0h\\0\\0h\\0\\0i\\0\\0h\\0\\0h\\0\\0h\\0\\0h\\0A\\xC0\\xE1 +\\vi\\0\\0i\\0\\0h\\0\\0h\\0\\0\\0\\0\\0\\0h\\0\\0\\0\\0\\0\\0i\\0A\\xE4\\xE2 +\\v"\\xA9\\0\\0\\xBE\\0\\0V\\0\\0-V\\0\\0\\xCD\\x90\\0\\0\\xE2\\x90\\0\\0\\xB3\\0\\0!V\\0\\0\\xD7\\x90\\0A\\x88\\xE3 +\\v0\\xF9\\0\\0\\0\\0 \\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0A\\xAC\\xE3 +\\v8\\0A\\xC4\\xE3 +\\v\\v6\\0\\0\\x005\\0\\0\\0\\xA0\\xF8\\0A\\xDC\\xE3 +\\v\\0A\\xEC\\xE3 +\\v\\b\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\`)}function ur(A){return A}async function jj(A){return A}async function rj(A,j){try{var t=await jj(A),n=await WebAssembly.instantiate(t,j);return n}catch(e){I(\`failed to asynchronously prepare wasm: \${e}\`),Y(e)}}async function tj(A,j,t){return rj(j,t)}function nj(){var A={a:ir};return A}async function ej(){function A(B,l){return b=B.exports,Br(b),Z(),b}function j(B){return A(B.instance)}var t=nj();if(r.instantiateWasm)return new Promise((B,l)=>{r.instantiateWasm(t,(i,p)=>{B(A(i,p))})});T!=null||(T=Aj());var n=await tj(R,T,t),e=j(n);return e}class Q{constructor(j){vA(this,"name","ExitStatus");this.message=\`Program terminated with exit(\${j})\`,this.status=j}}var z=A=>{for(;A.length>0;)A.shift()(r)},AA=[],kj=A=>AA.push(A),jA=[],aj=A=>jA.push(A),U=!0,rA=new TextDecoder,tA=(A,j,t,n)=>{var e=j+t;if(n)return e;for(;A[j]&&!(j>=e);)++j;return j},nA=(A,j,t)=>{if(!A)return"";var n=tA(G,A,j,t);return rA.decode(G.subarray(A,n))},O={varargs:void 0,getStr(A){var j=nA(A);return j}};function Bj(A,j,t){return O.varargs=t,0}function ij(A,j,t){return O.varargs=t,0}function lj(A,j,t,n){O.varargs=n}var oj=()=>Y(""),eA=0,sj=()=>{U=!1,eA=0},C={},kA=A=>{if(A instanceof Q||A=="unwind")return x;u(1,A)},aA=()=>U||eA>0,BA=A=>{var j;x=A,aA()||((j=r.onExit)==null||j.call(r,A),M=!0),u(A,new Q(A))},dj=(A,j)=>{x=A,BA(A)},Ej=dj,cj=()=>{if(!aA())try{Ej(x)}catch(A){kA(A)}},qj=A=>{if(!M)try{A(),cj()}catch(j){kA(j)}},iA=()=>performance.now(),pj=(A,j)=>{if(C[A]&&(clearTimeout(C[A].id),delete C[A]),!j)return 0;var t=setTimeout(()=>{delete C[A],qj(()=>qA(A,iA()))},j);return C[A]={id:t,timeout_ms:j},0},lA=()=>Date.now(),uj=1,mj=A=>A>=0&&A<=3,Dj=9007199254740992,_j=-9007199254740992,oA=A=>A<_j||A>Dj?NaN:Number(A);function fj(A,j,t){if(j=oA(j),!mj(A))return 28;var n;if(A===0)n=lA();else if(uj)n=iA();else return 52;var e=Math.round(n*1e3*1e3);return w[t>>3]=BigInt(e),0}var Gj=()=>2147483648,Cj=(A,j)=>Math.ceil(A/j)*j,vj=A=>{var j=N.buffer.byteLength,t=(A-j+65535)/65536|0;try{return N.grow(t),Z(),1}catch(n){}},Pj=A=>{var j=G.length;A>>>=0;var t=Gj();if(A>t)return!1;for(var n=1;n<=4;n*=2){var e=j*(1+.2/n);e=Math.min(e,A+100663296);var B=Math.min(t,Cj(Math.max(A,e),65536)),l=vj(B);if(l)return!0}return!1},V={},bj=()=>q||"./this.program",v=()=>{var e,B;if(!v.strings){var A=((B=(e=globalThis.navigator)==null?void 0:e.language)!=null?B:"C").replace("-","_")+".UTF-8",j={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:A,_:bj()};for(var t in V)V[t]===void 0?delete j[t]:j[t]=V[t];var n=[];for(var t in j)n.push(\`\${t}=\${j[t]}\`);v.strings=n}return v.strings},$j=(A,j,t,n)=>{if(!(n>0))return 0;for(var e=t,B=t+n-1,l=0;l =B)break;j[t++]=i}else if(i<=2047){if(t+1>=B)break;j[t++]=192|i>>6,j[t++]=128|i&63}else if(i<=65535){if(t+2>=B)break;j[t++]=224|i>>12,j[t++]=128|i>>6&63,j[t++]=128|i&63}else{if(t+3>=B)break;j[t++]=240|i>>18,j[t++]=128|i>>12&63,j[t++]=128|i>>6&63,j[t++]=128|i&63,l++}}return j[t]=0,t-e},sA=(A,j,t)=>$j(A,G,j,t),gj=(A,j)=>{var t=0,n=0;for(var e of v()){var B=j+t;D[A+n>>2]=B,t+=sA(e,B,1/0)+1,n+=4}return 0},dA=A=>{for(var j=0,t=0;t =55296&&n<=57343?(j+=4,++t):j+=3}return j},Hj=(A,j)=>{var t=v();D[A>>2]=t.length;var n=0;for(var e of t)n+=dA(e)+1;return D[j>>2]=n,0},Fj=A=>52,Ij=(A,j)=>{var t=0,n=0,e=0;{var B=2;A==0?t=2:(A==1||A==2)&&(t=64),e=1}return J[j]=B,X[j+2>>1]=e,w[j+8>>3]=BigInt(t),w[j+16>>3]=BigInt(n),0},Mj=(A,j,t,n)=>52;function xj(A,j,t,n){return j=oA(j),70}for(var hj=[null,[],[]],Lj=(A,j=0,t,n)=>{var e=tA(A,j,t,n);return rA.decode(A.buffer?A.subarray(j,e):new Uint8Array(A.slice(j,e)))},wj=(A,j)=>{var t=hj[A];j===0||j===10?((A===1?K:I)(Lj(t)),t.length=0):t.push(j)},Tj=(A,j,t,n)=>{for(var e=0,B=0;B >2],i=D[j+4>>2];j+=8;for(var p=0;p>2]=e,0},EA=A=>{var j=r["_"+A];return j},Nj=(A,j)=>{J.set(A,j)},cA=A=>uA(A),yj=A=>{var j=dA(A)+1,t=cA(j);return sA(A,t,j),t},Jj=()=>mA(),Uj=A=>pA(A),Oj=(A,j,t,n,e)=>{var B={string:c=>{var $=0;return c!=null&&c!==0&&($=yj(c)),$},array:c=>{var $=cA(c.length);return Nj(c,$),$}};function l(c){return j==="string"?nA(c):j==="boolean"?!!c:c}var i=EA(A),p=[],y=0;if(n)for(var _=0;_ {var e=!t||t.every(l=>l==="number"||l==="boolean"),B=j!=="string";return B&&e&&!n?EA(A):(...l)=>Oj(A,j,t,l,n)},P=new Uint8Array(123),m=25;m>=0;--m)P[48+m]=52+m,P[65+m]=m,P[97+m]=26+m;if(P[43]=62,P[47]=63,r.noExitRuntime&&(U=r.noExitRuntime),r.print&&(K=r.print),r.printErr&&(I=r.printErr),r.wasmBinary&&(R=r.wasmBinary),r.arguments&&(f=r.arguments),r.thisProgram&&(q=r.thisProgram),r.preInit)for(typeof r.preInit=="function"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.shift()();r.cwrap=Vj;var Sj,Kj,Rj,Xj,Wj,Zj,Yj,Qj,zj,Ar,jr,rr,tr,nr,er,qA,pA,uA,mA,kr,ar,N;function Br(A){Sj=r._init_encoder=A.t,Kj=r._malloc=A.u,Rj=r._get_encoder_frame_size=A.v,Xj=r._get_encoder_extradata=A.w,Wj=r._get_encoder_extradata_size=A.x,Zj=r._get_encode_input_ptr=A.y,Yj=r._free=A.z,Qj=r._send_frame=A.A,zj=r._receive_packet=A.B,Ar=r._flush_encoder_start=A.C,jr=r._reset_encoder=A.D,rr=r._get_encoded_data=A.E,tr=r._get_encoded_pts=A.F,nr=r._get_encoded_duration=A.G,er=r._close_encoder=A.H,qA=A.I,pA=A.J,uA=A.K,mA=A.L,kr=N=A.r,ar=A.__indirect_function_table}var ir={a:Bj,q:ij,n:lj,f:oj,k:sj,l:pj,e:fj,d:lA,m:Pj,b:gj,c:Hj,i:Fj,p:Ij,h:Mj,o:xj,g:Tj,j:BA};function lr(){YA();function A(){var j;r.calledRun=!0,!M&&(QA(),h==null||h(r),(j=r.onRuntimeInitialized)==null||j.call(r),zA())}r.setStatus?(r.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>r.setStatus(""),1),A()},1)):A()}var b;return b=await ej(),lr(),W?a=r:a=new Promise((A,j)=>{h=A,L=j}),a}var PA=Er;var o,g=null,gA,HA,FA,IA,MA,xA,hA,LA,wA,TA,NA,yA,cr=async()=>{if(!o){if(g)return g;g=PA(),o=await g,g=null,gA=o.cwrap("init_encoder","number",["number","number","number"]),HA=o.cwrap("get_encoder_frame_size","number",["number"]),FA=o.cwrap("get_encoder_extradata","number",["number"]),IA=o.cwrap("get_encoder_extradata_size","number",["number"]),MA=o.cwrap("get_encode_input_ptr","number",["number","number"]),xA=o.cwrap("send_frame","number",["number","number"]),hA=o.cwrap("receive_packet","number",["number"]),LA=o.cwrap("flush_encoder_start",null,["number"]),wA=o.cwrap("reset_encoder",null,["number"]),TA=o.cwrap("get_encoded_data","number",["number"]),NA=o.cwrap("get_encoded_pts","number",["number"]),yA=o.cwrap("get_encoded_duration","number",["number"])}},qr=async(k,a,r)=>{await cr();let d=gA(k,a,r);if(d===0)throw new Error("Failed to initialize AAC encoder.");let s=HA(d),E=FA(d),f=IA(d),q=o.HEAPU8.slice(E,E+f).buffer;return{ctx:d,frameSize:s,extradata:q}},JA=k=>{let a=[],r;for(;(r=hA(k))>0;){let d=TA(k),s=o.HEAPU8.slice(d,d+r).buffer,E=Number(NA(k)),f=yA(k);a.push({encodedData:s,pts:E,duration:f})}return a},pr=(k,a,r)=>{let d=new Uint8Array(a),s=MA(k,d.length);if(s===0)throw new Error("Failed to allocate encoder input buffer.");o.HEAPU8.set(d,s);let E=xA(k,BigInt(r));if(E<0)throw new Error(\`Encode failed with error code \${E}.\`);return JA(k)},bA=k=>{let{id:a,command:r}=k;(async()=>{try{let s,E=[];switch(r.type){case"init":{let{ctx:q,frameSize:u,extradata:F}=await qr(r.data.numberOfChannels,r.data.sampleRate,r.data.bitrate);s={type:r.type,ctx:q,frameSize:u,extradata:F},E.push(F)}break;case"encode":{let q=pr(r.data.ctx,r.data.audioData,r.data.timestamp);for(let u of q)E.push(u.encodedData);s={type:r.type,packets:q}}break;case"flush":{LA(r.data.ctx);let q=JA(r.data.ctx);for(let u of q)E.push(u.encodedData);wA(r.data.ctx),s={type:r.type,packets:q}}break}$A({id:a,success:!0,data:s},E)}catch(s){$A({id:a,success:!1,error:s})}})()},$A=(k,a)=>{H?H.postMessage(k,a!=null?a:[]):self.postMessage(k,{transfer:a!=null?a:[]})},H=null;try{H=CA("worker_threads").parentPort}catch(k){}H?H.on("message",bA):self.addEventListener("message",k=>bA(k.data));setInterval(()=>{},1e3); +`); +} +var AAC_SAMPLE_RATES = [ + 96e3, + 88200, + 64e3, + 48e3, + 44100, + 32e3, + 24e3, + 22050, + 16e3, + 12e3, + 11025, + 8e3, + 7350 +]; +var AacEncoder = class extends CustomAudioEncoder { + constructor() { + super(...arguments); + this.worker = null; + this.workerError = null; + this.nextMessageId = 0; + this.pendingMessages = /* @__PURE__ */ new Map(); + this.ctx = 0; + this.encoderFrameSize = 0; + this.sampleRate = 0; + this.numberOfChannels = 0; + this.chunkMetadata = {}; + this.useAdts = false; + this.adtsHeaderTemplate = null; + this.description = null; + this.pendingBuffer = new Float32Array(2 ** 16); + this.pendingFrames = 0; + this.nextSampleTimestampInSamples = null; + this.nextPacketTimestampInSamples = null; + } + static supports(codec, config) { + return codec === "aac" && config.numberOfChannels >= 1 && config.numberOfChannels <= 8 && AAC_SAMPLE_RATES.includes(config.sampleRate) && config.bitrate !== void 0; + } + async init() { + this.worker = await Worker2(); + const onMessage = (data) => { + const pending = this.pendingMessages.get(data.id); + assert(pending !== void 0); + this.pendingMessages.delete(data.id); + if (data.success) pending.resolve(data.data); + else pending.reject(data.error); + }; + const onError = (error) => { + this.workerError = error; + for (const pending of this.pendingMessages.values()) pending.reject(error); + this.pendingMessages.clear(); + this.worker?.terminate(); + this.worker = null; + }; + if (this.worker.addEventListener) { + this.worker.addEventListener("message", (event) => onMessage(event.data)); + this.worker.addEventListener("error", (event) => { + onError(new Error(event.message || "AAC encoder worker failed to load or crashed.")); + }); + } else { + const nodeWorker = this.worker; + nodeWorker.on("message", onMessage); + nodeWorker.on("error", onError); + } + assert(this.config.bitrate !== void 0); + this.sampleRate = this.config.sampleRate; + this.numberOfChannels = this.config.numberOfChannels; + const result = await this.sendCommand({ + type: "init", + data: { + numberOfChannels: this.config.numberOfChannels, + sampleRate: this.config.sampleRate, + bitrate: this.config.bitrate + } + }); + this.ctx = result.ctx; + this.encoderFrameSize = result.frameSize; + const description = new Uint8Array(result.extradata); + const aacConfig = this.config.aac; + this.useAdts = aacConfig?.format === "adts"; + if (this.useAdts) { + const audioSpecificConfig = parseAacAudioSpecificConfig(description); + this.adtsHeaderTemplate = buildAdtsHeaderTemplate(audioSpecificConfig); + } + this.description = this.useAdts ? null : description; + this.resetInternalState(); + } + resetInternalState() { + this.pendingFrames = 0; + this.nextSampleTimestampInSamples = null; + this.nextPacketTimestampInSamples = null; + this.chunkMetadata = { decoderConfig: { + codec: "mp4a.40.2", + numberOfChannels: this.config.numberOfChannels, + sampleRate: this.config.sampleRate, + ...this.description ? { description: this.description } : {} + } }; + } + async encode(audioSample) { + if (this.nextSampleTimestampInSamples === null) { + this.nextSampleTimestampInSamples = Math.round(audioSample.timestamp * this.sampleRate); + this.nextPacketTimestampInSamples = this.nextSampleTimestampInSamples; + } + const channels = this.numberOfChannels; + const incomingFrames = audioSample.numberOfFrames; + const totalBytes = audioSample.allocationSize({ + format: "f32", + planeIndex: 0 + }); + const audioBytes = new Uint8Array(totalBytes); + audioSample.copyTo(audioBytes, { + format: "f32", + planeIndex: 0 + }); + const incomingData = new Float32Array(audioBytes.buffer); + const requiredSamples = (this.pendingFrames + incomingFrames) * channels; + if (requiredSamples > this.pendingBuffer.length) { + let newSize = this.pendingBuffer.length; + while (newSize < requiredSamples) newSize *= 2; + const newBuffer = new Float32Array(newSize); + newBuffer.set(this.pendingBuffer.subarray(0, this.pendingFrames * channels)); + this.pendingBuffer = newBuffer; + } + this.pendingBuffer.set(incomingData, this.pendingFrames * channels); + this.pendingFrames += incomingFrames; + while (this.pendingFrames >= this.encoderFrameSize) await this.encodeOneFrame(); + } + async flush() { + if (this.pendingFrames > 0) { + const channels = this.numberOfChannels; + const frameSize = this.encoderFrameSize; + const usedSamples = this.pendingFrames * channels; + const frameSamples = frameSize * channels; + this.pendingBuffer.fill(0, usedSamples, frameSamples); + this.pendingFrames = frameSize; + await this.encodeOneFrame(); + } + const result = await this.sendCommand({ + type: "flush", + data: { ctx: this.ctx } + }); + this.emitPackets(result.packets); + this.resetInternalState(); + } + close() { + this.worker?.terminate(); + } + async encodeOneFrame() { + assert(this.nextSampleTimestampInSamples !== null); + assert(this.nextPacketTimestampInSamples !== null); + const channels = this.numberOfChannels; + const frameSize = this.encoderFrameSize; + const frameSamples = frameSize * channels; + const frameData = this.pendingBuffer.slice(0, frameSamples); + this.pendingFrames -= frameSize; + if (this.pendingFrames > 0) this.pendingBuffer.copyWithin(0, frameSamples, frameSamples + this.pendingFrames * channels); + const audioData = frameData.buffer; + const result = await this.sendCommand({ + type: "encode", + data: { + ctx: this.ctx, + audioData, + timestamp: this.nextSampleTimestampInSamples + } + }, [audioData]); + this.nextSampleTimestampInSamples += frameSize; + this.emitPackets(result.packets); + } + emitPackets(packets) { + assert(this.nextPacketTimestampInSamples !== null); + for (const p of packets) { + let data = new Uint8Array(p.encodedData); + if (this.useAdts) { + assert(this.adtsHeaderTemplate !== null); + const { header, bitstream } = this.adtsHeaderTemplate; + const frameLength = header.byteLength + data.byteLength; + writeAdtsFrameLength(bitstream, frameLength); + const adtsFrame = new Uint8Array(frameLength); + adtsFrame.set(header, 0); + adtsFrame.set(data, header.byteLength); + data = adtsFrame; + } + const packet = new EncodedPacket(data, "key", this.nextPacketTimestampInSamples / this.sampleRate, p.duration / this.sampleRate); + this.nextPacketTimestampInSamples += p.duration; + this.onPacket(packet, this.chunkMetadata); + this.chunkMetadata = {}; + } + } + sendCommand(command, transferables) { + return new Promise((resolve, reject) => { + if (this.workerError) { + reject(this.workerError); + return; + } + const id = this.nextMessageId++; + this.pendingMessages.set(id, { + resolve, + reject + }); + assert(this.worker); + if (transferables) this.worker.postMessage({ + id, + command + }, transferables); + else this.worker.postMessage({ + id, + command + }); + }); + } +}; +var registered = false; +var registerAacEncoder = () => { + if (registered) return; + registered = true; + registerEncoder(AacEncoder); +}; +function assert(x) { + if (!x) throw new Error("Assertion failed."); +} +var AAC_ENCODER_LOADED_SYMBOL = Symbol.for("@mediabunny/aac-encoder loaded"); +if (globalThis[AAC_ENCODER_LOADED_SYMBOL]) Logging._error("[WARNING]\n@mediabunny/aac-encoder was loaded twice. This will likely cause the encoder not to work correctly. Check if multiple dependencies are importing different versions of @mediabunny/aac-encoder, or if something is being bundled incorrectly."); +globalThis[AAC_ENCODER_LOADED_SYMBOL] = true; +//#endregion +export { registerAacEncoder }; diff --git a/dist/assets/src-Bv2YeDx1.js b/dist/assets/src-Bv2YeDx1.js new file mode 100644 index 0000000..cc72b07 --- /dev/null +++ b/dist/assets/src-Bv2YeDx1.js @@ -0,0 +1,35044 @@ +//#region \0rolldown/runtime.js +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports); +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { + key = keys[i]; + if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { + get: ((k) => from[k]).bind(null, key), + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { + value: mod, + enumerable: true +}) : target, mod)); +var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { + if (typeof require !== "undefined") return require.apply(this, arguments); + throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details."); +}); +//#endregion +//#region node_modules/mediabunny/dist/modules/src/misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +function assert(x) { + if (!x) throw new Error("Assertion failed."); +} +var DEG_TO_RAD = Math.PI / 180; +var RAD_TO_DEG = 180 / Math.PI; +var normalizeRotation = (rotation) => { + const mappedRotation = (rotation % 360 + 360) % 360; + if (mappedRotation === 0 || mappedRotation === 90 || mappedRotation === 180 || mappedRotation === 270) return mappedRotation; + else throw new Error(`Invalid rotation ${rotation}.`); +}; +var IDENTITY_MATRIX = [ + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 +]; +/** +* Applies a linear transformation (no translation) to a width-by-height frame about its center, with the result +* sitting in the positive quadrant. +*/ +var centeredTransformationMatrix = (linear, width, height) => { + const [a, b, , c, d] = linear; + const transformedWidth = Math.abs(a) * width + Math.abs(c) * height; + const transformedHeight = Math.abs(b) * width + Math.abs(d) * height; + return multiplyMatrices(multiplyMatrices(translationMatrix(-width / 2, -height / 2), linear), translationMatrix(transformedWidth / 2, transformedHeight / 2)); +}; +/** +* Extracts the rotation from a transformation matrix, assuming the matrix has the form "rotate, then maybe flip +* horizontally", and snapping to the nearest multiple of 90 degrees. +*/ +var extractRotationFromMatrix = (matrix) => { + const [a, b] = matrix; + return normalizeRotation(roundToMultiple(Math.atan2(b, matrixIsFlipped(matrix) ? -a : a) * RAD_TO_DEG, 90)); +}; +/** Whether the transformation matrix flips the frame, i.e. has a negative determinant. */ +var matrixIsFlipped = (matrix) => { + const [a, b, , c, d] = matrix; + return a * d - b * c < 0; +}; +var rotationMatrix = (rotationInDegrees) => { + const theta = rotationInDegrees * DEG_TO_RAD; + const cosTheta = Math.round(Math.cos(theta)); + const sinTheta = Math.round(Math.sin(theta)); + return [ + cosTheta, + sinTheta, + 0, + -sinTheta, + cosTheta, + 0, + 0, + 0, + 1 + ]; +}; +var translationMatrix = (x, y) => { + return [ + 1, + 0, + 0, + 0, + 1, + 0, + x, + y, + 1 + ]; +}; +var scaleMatrix = (x, y) => { + return [ + x, + 0, + 0, + 0, + y, + 0, + 0, + 0, + 1 + ]; +}; +/** Computes a * b. Since points are row vectors, this applies a first, then b. */ +var multiplyMatrices = (a, b) => { + const result = new Array(9); + for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) result[3 * i + j] = a[3 * i] * b[j] + a[3 * i + 1] * b[3 + j] + a[3 * i + 2] * b[6 + j]; + return result; +}; +/** +* Composes two "rotate, then flip" transformations into one. A flip conjugates any rotation that follows it, meaning +* the second rotation flips direction if the first flip is set. +*/ +var composeRotationAndFlip = (rotation1, flip1, rotation2, flip2) => { + return { + rotation: normalizeRotation(rotation1 + (flip1 ? -rotation2 : rotation2)), + flip: flip1 !== flip2 + }; +}; +var last = (arr) => { + return arr && arr[arr.length - 1]; +}; +var isU32 = (value) => { + return value >= 0 && value < 2 ** 32; +}; +var isI32 = (value) => { + return value >= -(2 ** 31) && value < 2 ** 31; +}; +/** Reads an exponential-Golomb universal code from a Bitstream. */ +var readExpGolomb = (bitstream) => { + let leadingZeroBits = 0; + while (bitstream.readBits(1) === 0 && leadingZeroBits < 32) leadingZeroBits++; + if (leadingZeroBits >= 32) throw new Error("Invalid exponential-Golomb code."); + return (1 << leadingZeroBits) - 1 + bitstream.readBits(leadingZeroBits); +}; +var writeExpGolomb = (bitstream, value) => { + const codeNum = value + 1; + const leadingZeroBits = Math.floor(Math.log2(codeNum)); + bitstream.writeBits(leadingZeroBits, 0); + bitstream.writeBits(1, 1); + bitstream.writeBits(leadingZeroBits, codeNum - 2 ** leadingZeroBits); +}; +/** Reads a signed exponential-Golomb universal code from a Bitstream. */ +var readSignedExpGolomb = (bitstream) => { + const codeNum = readExpGolomb(bitstream); + return (codeNum & 1) === 0 ? -(codeNum >> 1) : codeNum + 1 >> 1; +}; +var writeBits = (bytes, start, end, value) => { + for (let i = start; i < end; i++) { + const byteIndex = Math.floor(i / 8); + let byte = bytes[byteIndex]; + const bitIndex = 7 - (i & 7); + byte &= ~(1 << bitIndex); + byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; + bytes[byteIndex] = byte; + } +}; +var toUint8Array = (source) => { + if (source.constructor === Uint8Array) return source; + else if (ArrayBuffer.isView(source)) return new Uint8Array(source.buffer, source.byteOffset, source.byteLength); + else return new Uint8Array(source); +}; +var toDataView = (source) => { + if (source.constructor === DataView) return source; + else if (ArrayBuffer.isView(source)) return new DataView(source.buffer, source.byteOffset, source.byteLength); + else return new DataView(source); +}; +var TextEncoder = typeof globalThis.TextEncoder !== "undefined" ? globalThis.TextEncoder : class TextEncoder { + constructor() { + this.encoding = "utf-8"; + } + encode(input = "") { + const bytes = new Uint8Array(3 * input.length); + let n = 0; + for (let i = 0; i < input.length; i++) { + let c = input.charCodeAt(i); + if (c < 128) bytes[n++] = c; + else if (c < 2048) { + bytes[n++] = 192 | c >> 6; + bytes[n++] = 128 | c & 63; + } else if (c < 55296 || c > 57343) { + bytes[n++] = 224 | c >> 12; + bytes[n++] = 128 | c >> 6 & 63; + bytes[n++] = 128 | c & 63; + } else { + const next = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if (c < 56320 && next >= 56320 && next <= 57343) { + c = 65536 + (c - 55296 << 10) + (next - 56320); + i++; + bytes[n++] = 240 | c >> 18; + bytes[n++] = 128 | c >> 12 & 63; + bytes[n++] = 128 | c >> 6 & 63; + bytes[n++] = 128 | c & 63; + } else { + bytes[n++] = 239; + bytes[n++] = 191; + bytes[n++] = 189; + } + } + } + return bytes.slice(0, n); + } +}; +var TextDecoder = typeof globalThis.TextDecoder !== "undefined" ? globalThis.TextDecoder : class TextDecoder { + constructor(label = "utf-8") { + const normalized = label.trim().toLowerCase(); + if (normalized === "utf-8" || normalized === "utf8" || normalized === "unicode-1-1-utf-8") this.encoding = "utf-8"; + else if (normalized === "utf-16le" || normalized === "utf-16") this.encoding = "utf-16le"; + else if (normalized === "utf-16be") this.encoding = "utf-16be"; + else throw new RangeError(`The encoding label provided ('${label}') is invalid.`); + } + decode(input) { + const bytes = input ? toUint8Array(input) : new Uint8Array(0); + const units = []; + if (this.encoding === "utf-8") { + let i = bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0; + while (i < bytes.length) { + const lead = bytes[i]; + if (lead < 128) { + units.push(lead); + i++; + continue; + } + let continuationCount; + let codePoint; + if (lead >= 194 && lead < 224) { + continuationCount = 1; + codePoint = lead & 31; + } else if (lead >= 224 && lead < 240) { + continuationCount = 2; + codePoint = lead & 15; + } else if (lead >= 240 && lead < 245) { + continuationCount = 3; + codePoint = lead & 7; + } else { + units.push(65533); + i++; + continue; + } + let lowerBound = lead === 224 ? 160 : lead === 240 ? 144 : 128; + let upperBound = lead === 237 ? 159 : lead === 244 ? 143 : 191; + let j = 1; + for (; j <= continuationCount; j++) { + const byte = i + j < bytes.length ? bytes[i + j] : 0; + if (byte < lowerBound || byte > upperBound) break; + codePoint = codePoint << 6 | byte & 63; + lowerBound = 128; + upperBound = 191; + } + i += j; + if (j <= continuationCount) units.push(65533); + else if (codePoint >= 65536) { + codePoint -= 65536; + units.push(55296 | codePoint >> 10, 56320 | codePoint & 1023); + } else units.push(codePoint); + } + } else { + const littleEndian = this.encoding === "utf-16le"; + const bomLow = littleEndian ? 255 : 254; + const bomHigh = littleEndian ? 254 : 255; + let i = bytes.length >= 2 && bytes[0] === bomLow && bytes[1] === bomHigh ? 2 : 0; + while (i + 1 < bytes.length) { + const unit = littleEndian ? bytes[i] | bytes[i + 1] << 8 : bytes[i] << 8 | bytes[i + 1]; + i += 2; + if (unit >= 55296 && unit <= 56319) { + const next = i + 1 < bytes.length ? littleEndian ? bytes[i] | bytes[i + 1] << 8 : bytes[i] << 8 | bytes[i + 1] : -1; + if (next >= 56320 && next <= 57343) { + units.push(unit, next); + i += 2; + } else units.push(65533); + } else if (unit >= 56320 && unit <= 57343) units.push(65533); + else units.push(unit); + } + if (i < bytes.length) units.push(65533); + } + let result = ""; + for (let i = 0; i < units.length; i += 8192) result += String.fromCharCode(...units.slice(i, i + 8192)); + return result; + } +}; +var textDecoder = /* #__PURE__ */ new TextDecoder(); +var textEncoder = /* #__PURE__ */ new TextEncoder(); +var isIso88591Compatible = (text) => { + for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) > 255) return false; + return true; +}; +var invertObject = (object) => { + return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key])); +}; +var COLOR_PRIMARIES_MAP = { + bt709: 1, + bt470bg: 5, + smpte170m: 6, + bt2020: 9, + smpte432: 12 +}; +var COLOR_PRIMARIES_MAP_INVERSE = /* #__PURE__ */ invertObject(COLOR_PRIMARIES_MAP); +var TRANSFER_CHARACTERISTICS_MAP = { + "bt709": 1, + "smpte170m": 6, + "linear": 8, + "iec61966-2-1": 13, + "pq": 16, + "hlg": 18 +}; +var TRANSFER_CHARACTERISTICS_MAP_INVERSE = /* #__PURE__ */ invertObject(TRANSFER_CHARACTERISTICS_MAP); +var MATRIX_COEFFICIENTS_MAP = { + "rgb": 0, + "bt709": 1, + "bt470bg": 5, + "smpte170m": 6, + "bt2020-ncl": 9 +}; +var MATRIX_COEFFICIENTS_MAP_INVERSE = /* #__PURE__ */ invertObject(MATRIX_COEFFICIENTS_MAP); +var colorSpaceIsComplete = (colorSpace) => { + return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0; +}; +var colorSpaceIsEmpty = (colorSpace) => { + return !colorSpace || colorSpace.primaries == null && colorSpace.transfer == null && colorSpace.matrix == null && colorSpace.fullRange == null; +}; +var EMPTY_COLOR_SPACE = { + primaries: void 0, + transfer: void 0, + matrix: void 0, + fullRange: void 0 +}; +var isAllowSharedBufferSource = (x) => { + return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x); +}; +var AsyncMutex = class { + constructor() { + this.currentPromise = Promise.resolve(); + this.pending = 0; + } + async acquire() { + let resolver; + const nextPromise = new Promise((resolve) => { + let resolved = false; + resolver = () => { + if (resolved) return; + resolve(); + this.pending--; + resolved = true; + }; + }); + const currentPromiseAlias = this.currentPromise; + this.currentPromise = nextPromise; + this.pending++; + await currentPromiseAlias; + return resolver; + } +}; +var HEX_STRING_REGEX = /^[0-9a-fA-F]+$/; +var bytesToHexString = (bytes) => { + return [...bytes].map((x) => x.toString(16).padStart(2, "0")).join(""); +}; +var hexStringToBytes = (hexString) => { + assert(hexString.length % 2 === 0); + const bytes = new Uint8Array(hexString.length / 2); + for (let i = 0; i < hexString.length; i += 2) bytes[i / 2] = parseInt(hexString.slice(i, i + 2), 16); + return bytes; +}; +var reverseBitsU32 = (x) => { + x = x >> 1 & 1431655765 | (x & 1431655765) << 1; + x = x >> 2 & 858993459 | (x & 858993459) << 2; + x = x >> 4 & 252645135 | (x & 252645135) << 4; + x = x >> 8 & 16711935 | (x & 16711935) << 8; + x = x >> 16 & 65535 | (x & 65535) << 16; + return x >>> 0; +}; +/** Returns the smallest index i such that val[i] === key, or -1 if no such index exists. */ +var binarySearchExact = (arr, key, valueGetter) => { + let low = 0; + let high = arr.length - 1; + let ans = -1; + while (low <= high) { + const mid = low + high >> 1; + const midVal = valueGetter(arr[mid]); + if (midVal === key) { + ans = mid; + high = mid - 1; + } else if (midVal < key) low = mid + 1; + else high = mid - 1; + } + return ans; +}; +/** Returns the largest index i such that val[i] <= key, or -1 if no such index exists. */ +var binarySearchLessOrEqual = (arr, key, valueGetter) => { + let low = 0; + let high = arr.length - 1; + let ans = -1; + while (low <= high) { + const mid = low + (high - low + 1) / 2 | 0; + if (valueGetter(arr[mid]) <= key) { + ans = mid; + low = mid + 1; + } else high = mid - 1; + } + return ans; +}; +/** Assumes the array is already sorted. */ +var insertSorted = (arr, item, valueGetter) => { + const insertionIndex = binarySearchLessOrEqual(arr, valueGetter(item), valueGetter); + arr.splice(insertionIndex + 1, 0, item); +}; +var promiseWithResolvers = () => { + let resolve; + let reject; + return { + promise: new Promise((res, rej) => { + resolve = res; + reject = rej; + }), + resolve, + reject + }; +}; +var removeItem = (arr, item) => { + const index = arr.indexOf(item); + if (index !== -1) arr.splice(index, 1); +}; +var findLast = (arr, predicate) => { + for (let i = arr.length - 1; i >= 0; i--) if (predicate(arr[i])) return arr[i]; +}; +var findLastIndex = (arr, predicate) => { + for (let i = arr.length - 1; i >= 0; i--) if (predicate(arr[i])) return i; + return -1; +}; +var toAsyncIterator = async function* (source) { + if (Symbol.iterator in source) yield* source[Symbol.iterator](); + else yield* source[Symbol.asyncIterator](); +}; +var validateAnyIterable = (iterable) => { + if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) throw new TypeError("Argument must be an iterable or async iterable."); +}; +var assertNever = (x) => { + throw new Error(`Unexpected value: ${x}`); +}; +var getUint24 = (view, byteOffset, littleEndian) => { + const byte1 = view.getUint8(byteOffset); + const byte2 = view.getUint8(byteOffset + 1); + const byte3 = view.getUint8(byteOffset + 2); + if (littleEndian) return byte1 | byte2 << 8 | byte3 << 16; + else return byte1 << 16 | byte2 << 8 | byte3; +}; +var getInt24 = (view, byteOffset, littleEndian) => { + return getUint24(view, byteOffset, littleEndian) << 8 >> 8; +}; +var setUint24 = (view, byteOffset, value, littleEndian) => { + value = value >>> 0; + value = value & 16777215; + if (littleEndian) { + view.setUint8(byteOffset, value & 255); + view.setUint8(byteOffset + 1, value >>> 8 & 255); + view.setUint8(byteOffset + 2, value >>> 16 & 255); + } else { + view.setUint8(byteOffset, value >>> 16 & 255); + view.setUint8(byteOffset + 1, value >>> 8 & 255); + view.setUint8(byteOffset + 2, value & 255); + } +}; +var setInt24 = (view, byteOffset, value, littleEndian) => { + value = clamp(value, -8388608, 8388607); + if (value < 0) value = value + 16777216 & 16777215; + setUint24(view, byteOffset, value, littleEndian); +}; +var setInt64 = (view, byteOffset, value, littleEndian) => { + if (littleEndian) { + view.setUint32(byteOffset + 0, value, true); + view.setInt32(byteOffset + 4, Math.floor(value / 2 ** 32), true); + } else { + view.setInt32(byteOffset + 0, Math.floor(value / 2 ** 32), true); + view.setUint32(byteOffset + 4, value, true); + } +}; +/** +* Calls a function on each value spat out by an async generator. The reason for writing this manually instead of +* using a generator function is that the generator function queues return() calls - here, we forward them immediately. +*/ +var mapAsyncGenerator = (generator, map) => { + return { + async next() { + const result = await generator.next(); + if (result.done) return { + value: void 0, + done: true + }; + else return { + value: map(result.value), + done: false + }; + }, + return() { + return generator.return(); + }, + throw(error) { + return generator.throw(error); + }, + [Symbol.asyncIterator]() { + return this; + } + }; +}; +var clamp = (value, min, max) => { + return Math.max(min, Math.min(max, value)); +}; +var lerp = (from, to, t) => { + return from + (to - from) * t; +}; +var modEuclid = (value, modulus) => { + return value - Math.floor(value / modulus) * modulus; +}; +var roundIfAlmostInteger = (value) => { + const rounded = Math.round(value); + if (Math.abs(value / rounded - 1) < 10 * Number.EPSILON) return rounded; + else return value; +}; +var roundToMultiple = (value, multiple) => { + return Math.round(value / multiple) * multiple; +}; +var roundToDivisor = (value, multiple) => { + return Math.round(value * multiple) / multiple; +}; +var floorToMultiple = (value, multiple) => { + return Math.floor(value / multiple) * multiple; +}; +var floorToDivisor = (value, multiple) => { + return Math.floor(value * multiple) / multiple; +}; +var ilog = (x) => { + let ret = 0; + while (x) { + ret++; + x >>= 1; + } + return ret; +}; +var popcount = (value) => { + let count = 0; + while (value !== 0) { + value &= value - 1; + count++; + } + return count; +}; +var ISO_639_2_REGEX = /^[a-z]{3}$/; +var isIso639Dash2LanguageCode = (x) => { + return ISO_639_2_REGEX.test(x); +}; +var SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON); +/** +* Merges two RequestInit objects with special handling for headers. +* Headers are merged case-insensitively, but original casing is preserved. +* init2 headers take precedence and will override case-insensitive matches from init1. +*/ +var mergeRequestInit = (init1, init2) => { + const merged = { + ...init1, + ...init2 + }; + if (init1.headers || init2.headers) { + const headers1 = init1.headers ? normalizeHeaders(init1.headers) : {}; + const headers2 = init2.headers ? normalizeHeaders(init2.headers) : {}; + const mergedHeaders = { ...headers1 }; + Object.entries(headers2).forEach(([key2, value2]) => { + const existingKey = Object.keys(mergedHeaders).find((key1) => key1.toLowerCase() === key2.toLowerCase()); + if (existingKey) delete mergedHeaders[existingKey]; + mergedHeaders[key2] = value2; + }); + merged.headers = mergedHeaders; + } + return merged; +}; +/** Normalizes HeadersInit to a Record format. */ +var normalizeHeaders = (headers) => { + if (headers instanceof Headers) { + const result = {}; + headers.forEach((value, key) => { + result[key] = value; + }); + return result; + } + if (Array.isArray(headers)) { + const result = {}; + headers.forEach(([key, value]) => { + result[key] = value; + }); + return result; + } + return headers; +}; +var retriedFetch = async (fetchFn, url, requestInit, getRetryDelay, shouldStop) => { + let attempts = 0; + while (true) try { + return await fetchFn(url, requestInit); + } catch (error) { + if (shouldStop()) throw error; + attempts++; + const retryDelayInSeconds = getRetryDelay(attempts, error, url); + if (retryDelayInSeconds === null) throw error; + Logging._error("Retrying failed fetch. Error:", error); + if (!Number.isFinite(retryDelayInSeconds) || retryDelayInSeconds < 0) throw new TypeError("Retry delay must be a non-negative finite number."); + if (retryDelayInSeconds > 0) await wait(1e3 * retryDelayInSeconds); + if (shouldStop()) throw error; + } +}; +var computeRationalApproximation = (x, maxDenominator) => { + const sign = x < 0 ? -1 : 1; + x = Math.abs(x); + let prevNumerator = 0, prevDenominator = 1; + let currNumerator = 1, currDenominator = 0; + let remainder = x; + while (true) { + const integer = Math.floor(remainder); + const nextNumerator = integer * currNumerator + prevNumerator; + const nextDenominator = integer * currDenominator + prevDenominator; + if (nextDenominator > maxDenominator) return { + num: sign * currNumerator, + den: currDenominator + }; + prevNumerator = currNumerator; + prevDenominator = currDenominator; + currNumerator = nextNumerator; + currDenominator = nextDenominator; + remainder = 1 / (remainder - integer); + if (!isFinite(remainder)) break; + } + return { + num: sign * currNumerator, + den: currDenominator + }; +}; +var CallSerializer = class { + constructor() { + this.currentPromise = Promise.resolve(); + } + call(fn) { + return this.currentPromise = this.currentPromise.then(fn); + } +}; +var isWebKitCache = null; +var isWebKit = () => { + if (isWebKitCache !== null) return isWebKitCache; + return isWebKitCache = !!(typeof navigator !== "undefined" && (navigator.vendor?.match(/apple/i) || /AppleWebKit/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) || /\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent))); +}; +var isFirefoxCache = null; +var isFirefox = () => { + if (isFirefoxCache !== null) return isFirefoxCache; + return isFirefoxCache = typeof navigator !== "undefined" && navigator.userAgent?.includes("Firefox"); +}; +var isChromiumCache = null; +var isChromium = () => { + if (isChromiumCache !== null) return isChromiumCache; + return isChromiumCache = !!(typeof navigator !== "undefined" && (navigator.vendor?.includes("Google Inc") || /Chrome/.test(navigator.userAgent))); +}; +var missingWebCodecsClassMessage = (className) => { + if (typeof globalThis.isSecureContext !== "undefined" && !globalThis.isSecureContext) return `${className} is not available in this environment; this may be because this page is running in an insecure context. Try serving your page over HTTPS or use localhost.`; + return `${className} is not available in this environment.`; +}; +var isCodecReclaimedError = (error) => { + return error instanceof DOMException && error.name === "QuotaExceededError" && /reclaimed/i.test(error.message); +}; +var NativePromiseConstructor = (/* #__PURE__ */ (async () => {})()).constructor; +/** +* Needed to properly deal with custom Promise implementations and because this is closer to how the JS spec does it. +*/ +var isThenable = (value) => { + if (value instanceof NativePromiseConstructor || value instanceof Promise) return true; + return typeof value?.then === "function"; +}; +/** Acts like `??` except the condition is -1 and not null/undefined. */ +var coalesceIndex = (a, b) => { + return a !== -1 ? a : b; +}; +var closedIntervalsOverlap = (startA, endA, startB, endB) => { + return startA <= endB && startB <= endA; +}; +var keyValueIterator = function* (object) { + for (const key in object) { + const value = object[key]; + if (value === void 0) continue; + yield { + key, + value + }; + } +}; +var imageMimeTypeToExtension = (mimeType) => { + switch (mimeType.toLowerCase()) { + case "image/jpeg": + case "image/jpg": return ".jpg"; + case "image/png": return ".png"; + case "image/gif": return ".gif"; + case "image/webp": return ".webp"; + case "image/bmp": return ".bmp"; + case "image/svg+xml": return ".svg"; + case "image/tiff": return ".tiff"; + case "image/avif": return ".avif"; + case "image/x-icon": + case "image/vnd.microsoft.icon": return ".ico"; + default: return null; + } +}; +var base64ToBytes = (base64) => { + const decoded = atob(base64); + const bytes = new Uint8Array(decoded.length); + for (let i = 0; i < decoded.length; i++) bytes[i] = decoded.charCodeAt(i); + return bytes; +}; +var bytesToBase64 = (bytes) => { + let string = ""; + for (let i = 0; i < bytes.length; i++) string += String.fromCharCode(bytes[i]); + return btoa(string); +}; +var uint8ArraysAreEqual = (a, b) => { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; + return true; +}; +var polyfillSymbolDispose = () => { + Symbol.dispose ??= Symbol("Symbol.dispose"); +}; +var isNumber = (x) => { + return typeof x === "number" && !Number.isNaN(x); +}; +var joinPaths = (basePath, relativePath) => { + if (relativePath.includes("://")) return relativePath; + if (basePath.includes("://")) { + const queryIndex = basePath.indexOf("?"); + if (queryIndex !== -1) basePath = basePath.slice(0, queryIndex); + } + let result; + if (relativePath.startsWith("/")) { + const protocolIndex = basePath.indexOf("://"); + if (protocolIndex === -1) result = relativePath; + else { + const pathStart = basePath.indexOf("/", protocolIndex + 3); + if (pathStart === -1) result = basePath + relativePath; + else result = basePath.slice(0, pathStart) + relativePath; + } + } else { + const lastSlash = basePath.lastIndexOf("/"); + if (lastSlash === -1) result = relativePath; + else result = basePath.slice(0, lastSlash + 1) + relativePath; + } + let prefix = ""; + const protocolIndex = result.indexOf("://"); + if (protocolIndex !== -1) { + const pathStart = result.indexOf("/", protocolIndex + 3); + if (pathStart !== -1) { + prefix = result.slice(0, pathStart); + result = result.slice(pathStart); + } + } + const segments = result.split("/"); + const normalized = []; + for (const segment of segments) if (segment === "..") normalized.pop(); + else if (segment !== ".") normalized.push(segment); + return prefix + normalized.join("/"); +}; +var getQueryParameter = (path, name) => { + const queryIndex = path.indexOf("?"); + if (queryIndex === -1) return null; + const fragmentIndex = path.indexOf("#", queryIndex); + const query = path.slice(queryIndex + 1, fragmentIndex === -1 ? void 0 : fragmentIndex); + for (const parameter of query.split("&")) { + const equalsIndex = parameter.indexOf("="); + const encodedName = equalsIndex === -1 ? parameter : parameter.slice(0, equalsIndex); + if (decodeURIComponent(encodedName) !== name) continue; + const encodedValue = equalsIndex === -1 ? "" : parameter.slice(equalsIndex + 1); + return decodeURIComponent(encodedValue); + } + return null; +}; +var arrayCount = (array, predicate) => { + let count = 0; + for (let i = 0; i < array.length; i++) if (predicate(array[i])) count++; + return count; +}; +var arrayArgmin = (array, getValue) => { + let minIndex = -1; + let minValue = Infinity; + for (let i = 0; i < array.length; i++) { + const value = getValue(array[i]); + if (value < minValue) { + minValue = value; + minIndex = i; + } + } + return minIndex; +}; +var arrayArgmax = (array, getValue) => { + let maxIndex = -1; + let maxValue = -Infinity; + for (let i = 0; i < array.length; i++) { + const value = getValue(array[i]); + if (value > maxValue) { + maxValue = value; + maxIndex = i; + } + } + return maxIndex; +}; +var simplifyRational = (rational) => { + assert(Number.isInteger(rational.num)); + assert(Number.isInteger(rational.den)); + assert(rational.den !== 0); + let a = Math.abs(rational.num); + let b = Math.abs(rational.den); + while (b !== 0) { + const t = a % b; + a = b; + b = t; + } + const gcd = a || 1; + return { + num: rational.num / gcd, + den: rational.den / gcd + }; +}; +var validateRectangle = (rect, propertyPath) => { + if (typeof rect !== "object" || !rect) throw new TypeError(`${propertyPath} must be an object.`); + if (!Number.isInteger(rect.left) || rect.left < 0) throw new TypeError(`${propertyPath}.left must be a non-negative integer.`); + if (!Number.isInteger(rect.top) || rect.top < 0) throw new TypeError(`${propertyPath}.top must be a non-negative integer.`); + if (!Number.isInteger(rect.width) || rect.width < 0) throw new TypeError(`${propertyPath}.width must be a non-negative integer.`); + if (!Number.isInteger(rect.height) || rect.height < 0) throw new TypeError(`${propertyPath}.height must be a non-negative integer.`); +}; +var unthrottledTimerWorker; +var nextUnthrottledTimerId = 1; +var unthrottledTimeoutCallbacks = /* @__PURE__ */ new Map(); +var unthrottledIntervalCallbacks = /* @__PURE__ */ new Map(); +var shouldUseNativeTimers = () => { + return typeof window === "undefined"; +}; +var unthrottledTimerWorkerMain = () => { + const timeoutHandles = /* @__PURE__ */ new Map(); + const intervalHandles = /* @__PURE__ */ new Map(); + self.onmessage = (event) => { + const message = event.data; + switch (message.type) { + case "set-timeout": + { + const handle = setTimeout(() => { + timeoutHandles.delete(message.timerId); + self.postMessage({ + type: "fire", + timerId: message.timerId + }); + }, message.delay); + timeoutHandles.set(message.timerId, handle); + } + break; + case "set-interval": + { + const handle = setInterval(() => { + self.postMessage({ + type: "fire", + timerId: message.timerId + }); + }, message.delay); + intervalHandles.set(message.timerId, handle); + } + break; + case "clear-timeout": + { + const handle = timeoutHandles.get(message.timerId); + if (handle !== void 0) { + clearTimeout(handle); + timeoutHandles.delete(message.timerId); + } + } + break; + case "clear-interval": + { + const handle = intervalHandles.get(message.timerId); + if (handle !== void 0) { + clearInterval(handle); + intervalHandles.delete(message.timerId); + } + } + break; + } + }; +}; +var getUnthrottledTimerWorker = () => { + if (unthrottledTimerWorker) return unthrottledTimerWorker; + const workerSource = `(${unthrottledTimerWorkerMain.toString()})();`; + const workerURL = URL.createObjectURL(new Blob([workerSource], { type: "text/javascript" })); + unthrottledTimerWorker = new Worker(workerURL); + URL.revokeObjectURL(workerURL); + unthrottledTimerWorker.onmessage = (event) => { + const message = event.data; + const timeoutCallback = unthrottledTimeoutCallbacks.get(message.timerId); + if (timeoutCallback) { + unthrottledTimeoutCallbacks.delete(message.timerId); + timeoutCallback(); + return; + } + const intervalCallback = unthrottledIntervalCallbacks.get(message.timerId); + if (intervalCallback) intervalCallback(); + }; + return unthrottledTimerWorker; +}; +var setIntervalUnthrottled = (callback, delay) => { + if (shouldUseNativeTimers()) return { id: setInterval(callback, delay) }; + const timerId = nextUnthrottledTimerId++; + unthrottledIntervalCallbacks.set(timerId, () => { + callback(); + }); + getUnthrottledTimerWorker().postMessage({ + type: "set-interval", + timerId, + delay + }); + return { id: timerId }; +}; +var clearIntervalUnthrottled = (timer) => { + if (shouldUseNativeTimers()) { + clearInterval(timer.id); + return; + } + assert(typeof timer.id === "number"); + unthrottledIntervalCallbacks.delete(timer.id); + getUnthrottledTimerWorker().postMessage({ + type: "clear-interval", + timerId: timer.id + }); +}; +var wait = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; +var toArray = (x) => { + if (Array.isArray(x)) return x; + else return [x]; +}; +/** +* A class that manages event listeners and dispatches events to them. +* +* @group Miscellaneous +* @public +*/ +var EventEmitter = class { + constructor() { + /** @internal */ + this._listeners = /* @__PURE__ */ new Map(); + } + /** Registers a listener for the given event. Returns a function that, when called, removes the listener again. */ + on(event, listener, options) { + if (!this._listeners.has(event)) this._listeners.set(event, /* @__PURE__ */ new Set()); + const entry = { + fn: listener, + once: options?.once ?? false + }; + this._listeners.get(event).add(entry); + return () => { + this._listeners.get(event)?.delete(entry); + }; + } + /** @internal */ + _emit(...args) { + const [event, data] = args; + const listeners = this._listeners.get(event); + if (!listeners) return; + for (const entry of listeners) { + try { + entry.fn(data); + } catch (error) { + console.error(error); + } + if (entry.once) listeners.delete(entry); + } + } +}; +var ceilToMultipleOfTwo = (value) => Math.ceil(value / 2) * 2; +/** +* Utility class for running async functions in parallel up to a certain level of parallelism. Can be used to apply +* backpressure only if the concurrency level would be exceeded. +* +* @group Miscellaneous +* @public +*/ +var ConcurrentRunner = class { + constructor(parallelism) { + /** @internal */ + this._queue = []; + /** @internal */ + this._errored = false; + this.parallelism = parallelism; + } + /** Whether any function has errored. The runner is effectively bricked if this is `true`, by design. */ + get errored() { + return this._errored; + } + /** The number of tasks currently running. */ + get inFlightCount() { + return this._queue.length; + } + /** + * Schedules an async function to be run. If the maximum allowed level of parallelism has not yet been reached, + * the function will be executed immediately and `run()` will resolve immediately. Otherwise, the function will be + * called as soon as any currently-running function finishes, and `run()` will only resolve then. + * + * Throws if the runner is errored. + */ + async run(fn) { + if (this._errored) await Promise.race(this._queue); + while (this._queue.length >= this.parallelism) await Promise.race(this._queue); + const promise = fn(); + this._queue.push(promise); + promise.then(() => removeItem(this._queue, promise)).catch(() => this._errored = true); + } + /** Waits for all currently running functions to finish. Throws if the runner is errored. */ + async flush() { + await Promise.all(this._queue); + } +}; +var isRecordStringString = (value) => { + return value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype && Object.values(value).every((x) => typeof x === "string"); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/logging.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Controls how much information Mediabunny prints to the console. Higher levels include all lower levels. +* +* @group Logging +* @public +*/ +var LogLevel; +(function(LogLevel) { + /** Nothing is printed to the console. */ + LogLevel[LogLevel["Silent"] = 0] = "Silent"; + /** Only errors are printed. */ + LogLevel[LogLevel["Errors"] = 1] = "Errors"; + /** Errors and warnings are printed. */ + LogLevel[LogLevel["Warnings"] = 2] = "Warnings"; + /** Errors, warnings, and informational messages are printed. */ + LogLevel[LogLevel["Info"] = 3] = "Info"; +})(LogLevel || (LogLevel = {})); +/** +* Mediabunny's central logging singleton. Use {@link Logging.level} to control how much is printed to the console, +* and subscribe to log events using {@link Logging.on}. +* +* Having manual control over logging is useful for command-line applications where you want full say over the output. +* +* @group Logging +* @public +*/ +var Logging = class Logging { + constructor() {} + /** The current log level. Defaults to {@link LogLevel.Info}. */ + static get level() { + return Logging._level; + } + static set level(value) { + if (value !== LogLevel.Silent && value !== LogLevel.Errors && value !== LogLevel.Warnings && value !== LogLevel.Info) throw new TypeError("Invalid log level. Use one of the values of the LogLevel enum."); + Logging._level = value; + } + /** @internal */ + static get _emitter() { + return Logging._emitterInstance ??= new EventEmitter(); + } + /** Registers a listener for a log event. Returns a function that, when called, removes the listener again. */ + static on(event, listener, options) { + return Logging._emitter.on(event, listener, options); + } + /** @internal */ + static _error(...args) { + Logging._emitter._emit("error", args); + if (Logging._level >= LogLevel.Errors) console.error(...args); + } + /** @internal */ + static _warn(...args) { + Logging._emitter._emit("warn", args); + if (Logging._level >= LogLevel.Warnings) console.warn(...args); + } + /** @internal */ + static _info(...args) { + Logging._emitter._emit("info", args); + if (Logging._level >= LogLevel.Info) console.info(...args); + } +}; +/** @internal */ +Logging._level = LogLevel.Info; +/** @internal */ +Logging._emitterInstance = null; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/metadata.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Image data with additional metadata. +* +* @group Metadata tags +* @public +*/ +var RichImageData = class { + /** Creates a new {@link RichImageData}. */ + constructor(data, mimeType) { + this.data = data; + this.mimeType = mimeType; + if (!(data instanceof Uint8Array)) throw new TypeError("data must be a Uint8Array."); + if (typeof mimeType !== "string") throw new TypeError("mimeType must be a string."); + } +}; +/** +* A file attached to a media file. +* +* @group Metadata tags +* @public +*/ +var AttachedFile = class { + /** Creates a new {@link AttachedFile}. */ + constructor(data, mimeType, name, description) { + this.data = data; + this.mimeType = mimeType; + this.name = name; + this.description = description; + if (!(data instanceof Uint8Array)) throw new TypeError("data must be a Uint8Array."); + if (mimeType !== void 0 && typeof mimeType !== "string") throw new TypeError("mimeType, when provided, must be a string."); + if (name !== void 0 && typeof name !== "string") throw new TypeError("name, when provided, must be a string."); + if (description !== void 0 && typeof description !== "string") throw new TypeError("description, when provided, must be a string."); + } +}; +var validateMetadataTags = (tags) => { + if (!tags || typeof tags !== "object") throw new TypeError("tags must be an object."); + if (tags.title !== void 0 && typeof tags.title !== "string") throw new TypeError("tags.title, when provided, must be a string."); + if (tags.description !== void 0 && typeof tags.description !== "string") throw new TypeError("tags.description, when provided, must be a string."); + if (tags.artist !== void 0 && typeof tags.artist !== "string") throw new TypeError("tags.artist, when provided, must be a string."); + if (tags.album !== void 0 && typeof tags.album !== "string") throw new TypeError("tags.album, when provided, must be a string."); + if (tags.albumArtist !== void 0 && typeof tags.albumArtist !== "string") throw new TypeError("tags.albumArtist, when provided, must be a string."); + if (tags.trackNumber !== void 0 && (!Number.isInteger(tags.trackNumber) || tags.trackNumber <= 0)) throw new TypeError("tags.trackNumber, when provided, must be a positive integer."); + if (tags.tracksTotal !== void 0 && (!Number.isInteger(tags.tracksTotal) || tags.tracksTotal <= 0)) throw new TypeError("tags.tracksTotal, when provided, must be a positive integer."); + if (tags.discNumber !== void 0 && (!Number.isInteger(tags.discNumber) || tags.discNumber <= 0)) throw new TypeError("tags.discNumber, when provided, must be a positive integer."); + if (tags.discsTotal !== void 0 && (!Number.isInteger(tags.discsTotal) || tags.discsTotal <= 0)) throw new TypeError("tags.discsTotal, when provided, must be a positive integer."); + if (tags.genre !== void 0 && typeof tags.genre !== "string") throw new TypeError("tags.genre, when provided, must be a string."); + if (tags.date !== void 0 && (!(tags.date instanceof Date) || Number.isNaN(tags.date.getTime()))) throw new TypeError("tags.date, when provided, must be a valid Date."); + if (tags.beatsPerMinute !== void 0 && (!Number.isInteger(tags.beatsPerMinute) || tags.beatsPerMinute <= 0)) throw new TypeError("tags.beatsPerMinute, when provided, must be a positive integer."); + if (tags.lyrics !== void 0 && typeof tags.lyrics !== "string") throw new TypeError("tags.lyrics, when provided, must be a string."); + if (tags.images !== void 0) { + if (!Array.isArray(tags.images)) throw new TypeError("tags.images, when provided, must be an array."); + for (const image of tags.images) { + if (!image || typeof image !== "object") throw new TypeError("Each image in tags.images must be an object."); + if (!(image.data instanceof Uint8Array)) throw new TypeError("Each image.data must be a Uint8Array."); + if (typeof image.mimeType !== "string") throw new TypeError("Each image.mimeType must be a string."); + if (![ + "coverFront", + "coverBack", + "unknown" + ].includes(image.kind)) throw new TypeError("Each image.kind must be 'coverFront', 'coverBack', or 'unknown'."); + } + } + if (tags.comment !== void 0 && typeof tags.comment !== "string") throw new TypeError("tags.comment, when provided, must be a string."); + if (tags.raw !== void 0) { + if (!tags.raw || typeof tags.raw !== "object") throw new TypeError("tags.raw, when provided, must be an object."); + for (const value of Object.values(tags.raw)) if (value !== null && typeof value !== "string" && !(Array.isArray(value) && value.every((x) => typeof x === "string")) && !(value instanceof Uint8Array) && !(value instanceof RichImageData) && !(value instanceof AttachedFile) && !isRecordStringString(value)) throw new TypeError("Each value in tags.raw must be a string, string array, Uint8Array, RichImageData, AttachedFile, Record , or null."); + } +}; +var metadataTagsAreEmpty = (tags) => { + return tags.title === void 0 && tags.description === void 0 && tags.artist === void 0 && tags.album === void 0 && tags.albumArtist === void 0 && tags.trackNumber === void 0 && tags.tracksTotal === void 0 && tags.discNumber === void 0 && tags.discsTotal === void 0 && tags.genre === void 0 && tags.beatsPerMinute === void 0 && tags.date === void 0 && tags.lyrics === void 0 && (!tags.images || tags.images.length === 0) && tags.comment === void 0 && (tags.raw === void 0 || Object.keys(tags.raw).length === 0); +}; +var DEFAULT_TRACK_DISPOSITION = { + default: true, + primary: true, + forced: false, + original: false, + commentary: false, + hearingImpaired: false, + visuallyImpaired: false +}; +var validateTrackDisposition = (disposition) => { + if (!disposition || typeof disposition !== "object") throw new TypeError("disposition must be an object."); + if (disposition.default !== void 0 && typeof disposition.default !== "boolean") throw new TypeError("disposition.default must be a boolean."); + if (disposition.primary !== void 0 && typeof disposition.primary !== "boolean") throw new TypeError("disposition.primary must be a boolean."); + if (disposition.forced !== void 0 && typeof disposition.forced !== "boolean") throw new TypeError("disposition.forced must be a boolean."); + if (disposition.original !== void 0 && typeof disposition.original !== "boolean") throw new TypeError("disposition.original must be a boolean."); + if (disposition.commentary !== void 0 && typeof disposition.commentary !== "boolean") throw new TypeError("disposition.commentary must be a boolean."); + if (disposition.hearingImpaired !== void 0 && typeof disposition.hearingImpaired !== "boolean") throw new TypeError("disposition.hearingImpaired must be a boolean."); + if (disposition.visuallyImpaired !== void 0 && typeof disposition.visuallyImpaired !== "boolean") throw new TypeError("disposition.visuallyImpaired must be a boolean."); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/shared/bitstream.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Bitstream = class Bitstream { + constructor(bytes) { + this.bytes = bytes; + /** Current offset in bits. */ + this.pos = 0; + } + seekToByte(byteOffset) { + this.pos = 8 * byteOffset; + } + readBit() { + const byteIndex = Math.floor(this.pos / 8); + const byte = this.bytes[byteIndex] ?? 0; + const bitIndex = 7 - (this.pos & 7); + const bit = (byte & 1 << bitIndex) >> bitIndex; + this.pos++; + return bit; + } + readBits(n) { + if (n === 1) return this.readBit(); + let result = 0; + for (let i = 0; i < n; i++) { + result <<= 1; + result |= this.readBit(); + } + return result; + } + writeBits(n, value) { + const end = this.pos + n; + for (let i = this.pos; i < end; i++) { + const byteIndex = Math.floor(i / 8); + let byte = this.bytes[byteIndex]; + const bitIndex = 7 - (i & 7); + byte &= ~(1 << bitIndex); + byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; + this.bytes[byteIndex] = byte; + } + this.pos = end; + } + copyBits(n, other) { + let i = 0; + for (; i < n - 7; i += 8) this.writeBits(8, other.readBits(8)); + const leftover = n - i; + if (leftover > 0) this.writeBits(leftover, other.readBits(leftover)); + } + readAlignedByte() { + if (this.pos % 8 !== 0) throw new Error("Bitstream is not byte-aligned."); + const byteIndex = this.pos / 8; + const byte = this.bytes[byteIndex] ?? 0; + this.pos += 8; + return byte; + } + skipBits(n) { + this.pos += n; + } + getBitsLeft() { + return this.bytes.length * 8 - this.pos; + } + clone() { + const clone = new Bitstream(this.bytes); + clone.pos = this.pos; + return clone; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/shared/aac-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var aacFrequencyTable = [ + 96e3, + 88200, + 64e3, + 48e3, + 44100, + 32e3, + 24e3, + 22050, + 16e3, + 12e3, + 11025, + 8e3, + 7350 +]; +var aacChannelMap = [ + -1, + 1, + 2, + 3, + 4, + 5, + 6, + 8 +]; +var parseAacAudioSpecificConfig = (bytes) => { + if (!bytes || bytes.byteLength < 2) throw new TypeError("AAC description must be at least 2 bytes long."); + const bitstream = new Bitstream(bytes); + const objectType = readAacObjectType(bitstream); + const { frequencyIndex, sampleRate } = readAacSamplingFrequency(bitstream); + const channelConfiguration = bitstream.readBits(4); + let numberOfChannels = null; + if (channelConfiguration >= 1 && channelConfiguration <= 7) numberOfChannels = aacChannelMap[channelConfiguration]; + let coreObjectType = objectType; + let psPresent = false; + let outputSampleRate = sampleRate; + if (objectType === 5 || objectType === 29) { + psPresent = objectType === 29; + outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate; + coreObjectType = readAacObjectType(bitstream); + if (coreObjectType === 22) bitstream.skipBits(4); + } else while (bitstream.getBitsLeft() > 15) { + const searchStart = bitstream.pos; + if (bitstream.readBits(11) !== 695) { + bitstream.pos = searchStart + 1; + continue; + } + if (readAacObjectType(bitstream) === 5 && bitstream.readBits(1)) { + outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate; + if (bitstream.getBitsLeft() > 11 && bitstream.readBits(11) === 1352) psPresent = !!bitstream.readBits(1); + } + break; + } + if (numberOfChannels !== null && numberOfChannels > 1) psPresent = false; + return { + objectType, + coreObjectType, + frequencyIndex, + channelConfiguration, + outputSampleRate, + outputNumberOfChannels: psPresent && numberOfChannels === 1 ? 2 : numberOfChannels + }; +}; +var readAacObjectType = (bitstream) => { + const objectType = bitstream.readBits(5); + return objectType === 31 ? 32 + bitstream.readBits(6) : objectType; +}; +var readAacSamplingFrequency = (bitstream) => { + const frequencyIndex = bitstream.readBits(4); + if (frequencyIndex === 15) return { + frequencyIndex, + sampleRate: bitstream.readBits(24) + }; + return { + frequencyIndex, + sampleRate: frequencyIndex < aacFrequencyTable.length ? aacFrequencyTable[frequencyIndex] : null + }; +}; +var buildAacAudioSpecificConfig = (config) => { + const usesSbr = config.objectType === 5 || config.objectType === 29; + const usesPs = config.objectType === 29; + const coreSampleRate = usesSbr ? config.outputSampleRate / 2 : config.outputSampleRate; + const coreNumberOfChannels = usesPs ? 1 : config.outputNumberOfChannels; + const channelConfiguration = aacChannelMap.indexOf(coreNumberOfChannels); + if (channelConfiguration === -1) throw new TypeError(`Unsupported number of channels: ${config.outputNumberOfChannels}`); + let bitCount = 16; + if (config.objectType >= 32) bitCount += 6; + if (findAacFrequencyIndex(coreSampleRate) === 15) bitCount += 24; + if (usesSbr) { + bitCount += 9; + if (findAacFrequencyIndex(config.outputSampleRate) === 15) bitCount += 24; + } + const byteCount = Math.ceil(bitCount / 8); + const bytes = new Uint8Array(byteCount); + const bitstream = new Bitstream(bytes); + writeAacObjectType(bitstream, config.objectType); + writeAacSamplingFrequency(bitstream, coreSampleRate); + bitstream.writeBits(4, channelConfiguration); + if (usesSbr) { + writeAacSamplingFrequency(bitstream, config.outputSampleRate); + writeAacObjectType(bitstream, 2); + } + bitstream.writeBits(3, 0); + return bytes; +}; +var writeAacObjectType = (bitstream, objectType) => { + if (objectType < 32) bitstream.writeBits(5, objectType); + else { + bitstream.writeBits(5, 31); + bitstream.writeBits(6, objectType - 32); + } +}; +var writeAacSamplingFrequency = (bitstream, sampleRate) => { + const frequencyIndex = findAacFrequencyIndex(sampleRate); + bitstream.writeBits(4, frequencyIndex); + if (frequencyIndex === 15) bitstream.writeBits(24, sampleRate); +}; +var findAacFrequencyIndex = (sampleRate) => { + const index = aacFrequencyTable.indexOf(sampleRate); + return index === -1 ? 15 : index; +}; +var buildAdtsHeaderTemplate = (config) => { + const header = new Uint8Array(7); + const bitstream = new Bitstream(header); + const { coreObjectType, frequencyIndex, channelConfiguration } = config; + const profile = coreObjectType - 1; + bitstream.writeBits(12, 4095); + bitstream.writeBits(1, 0); + bitstream.writeBits(2, 0); + bitstream.writeBits(1, 1); + bitstream.writeBits(2, profile); + bitstream.writeBits(4, frequencyIndex); + bitstream.writeBits(1, 0); + bitstream.writeBits(3, channelConfiguration); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.skipBits(13); + bitstream.writeBits(11, 2047); + bitstream.writeBits(2, 0); + return { + header, + bitstream + }; +}; +var writeAdtsFrameLength = (bitstream, frameLength) => { + bitstream.pos = 30; + bitstream.writeBits(13, frameLength); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/shared/ac3-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** Sample rates indexed by fscod (Table 4.1) */ +var AC3_SAMPLE_RATES = [ + 48e3, + 44100, + 32e3 +]; +/** E-AC-3 reduced sample rates for fscod2 per ATSC A/52:2018 */ +var EAC3_REDUCED_SAMPLE_RATES = [ + 24e3, + 22050, + 16e3 +]; +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var VERSIONED_NAME = `Mediabunny v1.61.3`; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/codec-data.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var AvcNalUnitType; +(function(AvcNalUnitType) { + AvcNalUnitType[AvcNalUnitType["NON_IDR_SLICE"] = 1] = "NON_IDR_SLICE"; + AvcNalUnitType[AvcNalUnitType["SLICE_DPA"] = 2] = "SLICE_DPA"; + AvcNalUnitType[AvcNalUnitType["SLICE_DPB"] = 3] = "SLICE_DPB"; + AvcNalUnitType[AvcNalUnitType["SLICE_DPC"] = 4] = "SLICE_DPC"; + AvcNalUnitType[AvcNalUnitType["IDR"] = 5] = "IDR"; + AvcNalUnitType[AvcNalUnitType["SEI"] = 6] = "SEI"; + AvcNalUnitType[AvcNalUnitType["SPS"] = 7] = "SPS"; + AvcNalUnitType[AvcNalUnitType["PPS"] = 8] = "PPS"; + AvcNalUnitType[AvcNalUnitType["AUD"] = 9] = "AUD"; + AvcNalUnitType[AvcNalUnitType["SPS_EXT"] = 13] = "SPS_EXT"; +})(AvcNalUnitType || (AvcNalUnitType = {})); +var HevcNalUnitType; +(function(HevcNalUnitType) { + HevcNalUnitType[HevcNalUnitType["RASL_N"] = 8] = "RASL_N"; + HevcNalUnitType[HevcNalUnitType["RASL_R"] = 9] = "RASL_R"; + HevcNalUnitType[HevcNalUnitType["BLA_W_LP"] = 16] = "BLA_W_LP"; + HevcNalUnitType[HevcNalUnitType["RSV_IRAP_VCL23"] = 23] = "RSV_IRAP_VCL23"; + HevcNalUnitType[HevcNalUnitType["VPS_NUT"] = 32] = "VPS_NUT"; + HevcNalUnitType[HevcNalUnitType["SPS_NUT"] = 33] = "SPS_NUT"; + HevcNalUnitType[HevcNalUnitType["PPS_NUT"] = 34] = "PPS_NUT"; + HevcNalUnitType[HevcNalUnitType["AUD_NUT"] = 35] = "AUD_NUT"; + HevcNalUnitType[HevcNalUnitType["PREFIX_SEI_NUT"] = 39] = "PREFIX_SEI_NUT"; + HevcNalUnitType[HevcNalUnitType["SUFFIX_SEI_NUT"] = 40] = "SUFFIX_SEI_NUT"; +})(HevcNalUnitType || (HevcNalUnitType = {})); +var iterateNalUnitsInAnnexB = function* (packetData) { + let i = 0; + let nalStart = -1; + while (i < packetData.length - 2) { + const zeroIndex = packetData.indexOf(0, i); + if (zeroIndex === -1 || zeroIndex >= packetData.length - 2) break; + i = zeroIndex; + let startCodeLength = 0; + if (i + 3 < packetData.length && packetData[i + 1] === 0 && packetData[i + 2] === 0 && packetData[i + 3] === 1) startCodeLength = 4; + else if (packetData[i + 1] === 0 && packetData[i + 2] === 1) startCodeLength = 3; + if (startCodeLength === 0) { + i++; + continue; + } + if (nalStart !== -1 && i > nalStart) yield { + offset: nalStart, + length: i - nalStart + }; + nalStart = i + startCodeLength; + i = nalStart; + } + if (nalStart !== -1 && nalStart < packetData.length) yield { + offset: nalStart, + length: packetData.length - nalStart + }; +}; +var iterateNalUnitsInLengthPrefixed = function* (packetData, lengthSize) { + let offset = 0; + const dataView = new DataView(packetData.buffer, packetData.byteOffset, packetData.byteLength); + while (offset + lengthSize <= packetData.length) { + let nalUnitLength; + if (lengthSize === 1) nalUnitLength = dataView.getUint8(offset); + else if (lengthSize === 2) nalUnitLength = dataView.getUint16(offset, false); + else if (lengthSize === 3) nalUnitLength = getUint24(dataView, offset, false); + else { + assert(lengthSize === 4); + nalUnitLength = dataView.getUint32(offset, false); + } + offset += lengthSize; + yield { + offset, + length: nalUnitLength + }; + offset += nalUnitLength; + } +}; +var iterateAvcNalUnits = (packetData, decoderConfig) => { + if (decoderConfig.description) return iterateNalUnitsInLengthPrefixed(packetData, (toUint8Array(decoderConfig.description)[4] & 3) + 1); + else return iterateNalUnitsInAnnexB(packetData); +}; +var extractNalUnitTypeForAvc = (byte) => { + return byte & 31; +}; +var removeEmulationPreventionBytes = (data) => { + const result = []; + const len = data.length; + for (let i = 0; i < len; i++) if (i + 2 < len && data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) { + result.push(0, 0); + i += 2; + } else result.push(data[i]); + return new Uint8Array(result); +}; +var addEmulationPreventionBytes = (data) => { + const result = []; + let zeroCount = 0; + for (const byte of data) { + if (zeroCount === 2 && byte <= 3) { + result.push(3); + zeroCount = 0; + } + result.push(byte); + zeroCount = byte === 0 ? zeroCount + 1 : 0; + } + return new Uint8Array(result); +}; +var ANNEX_B_START_CODE = new Uint8Array([ + 0, + 0, + 0, + 1 +]); +var concatNalUnitsInAnnexB = (nalUnits) => { + const totalLength = nalUnits.reduce((a, b) => a + ANNEX_B_START_CODE.byteLength + b.byteLength, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const nalUnit of nalUnits) { + result.set(ANNEX_B_START_CODE, offset); + offset += ANNEX_B_START_CODE.byteLength; + result.set(nalUnit, offset); + offset += nalUnit.byteLength; + } + return result; +}; +var concatNalUnitsInLengthPrefixed = (nalUnits, lengthSize) => { + const totalLength = nalUnits.reduce((a, b) => a + lengthSize + b.byteLength, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const nalUnit of nalUnits) { + const dataView = new DataView(result.buffer, result.byteOffset, result.byteLength); + switch (lengthSize) { + case 1: + dataView.setUint8(offset, nalUnit.byteLength); + break; + case 2: + dataView.setUint16(offset, nalUnit.byteLength, false); + break; + case 3: + setUint24(dataView, offset, nalUnit.byteLength, false); + break; + case 4: + dataView.setUint32(offset, nalUnit.byteLength, false); + break; + } + offset += lengthSize; + result.set(nalUnit, offset); + offset += nalUnit.byteLength; + } + return result; +}; +var concatAvcNalUnits = (nalUnits, decoderConfig) => { + if (decoderConfig.description) return concatNalUnitsInLengthPrefixed(nalUnits, (toUint8Array(decoderConfig.description)[4] & 3) + 1); + else return concatNalUnitsInAnnexB(nalUnits); +}; +/** Builds an AvcDecoderConfigurationRecord from an AVC packet in Annex B format. */ +var extractAvcDecoderConfigurationRecord = (packetData) => { + try { + const spsUnits = []; + const ppsUnits = []; + const spsExtUnits = []; + for (const loc of iterateNalUnitsInAnnexB(packetData)) { + const nalUnit = packetData.subarray(loc.offset, loc.offset + loc.length); + const type = extractNalUnitTypeForAvc(nalUnit[0]); + if (type === AvcNalUnitType.SPS) spsUnits.push(nalUnit); + else if (type === AvcNalUnitType.PPS) ppsUnits.push(nalUnit); + else if (type === AvcNalUnitType.SPS_EXT) spsExtUnits.push(nalUnit); + } + if (spsUnits.length === 0) return null; + if (ppsUnits.length === 0) return null; + const spsData = spsUnits[0]; + const spsInfo = parseAvcSps(spsData); + assert(spsInfo !== null); + const hasExtendedData = spsInfo.profileIdc === 100 || spsInfo.profileIdc === 110 || spsInfo.profileIdc === 122 || spsInfo.profileIdc === 144; + return { + configurationVersion: 1, + avcProfileIndication: spsInfo.profileIdc, + profileCompatibility: spsInfo.constraintFlags, + avcLevelIndication: spsInfo.levelIdc, + lengthSizeMinusOne: 3, + sequenceParameterSets: spsUnits, + pictureParameterSets: ppsUnits, + chromaFormat: hasExtendedData ? spsInfo.chromaFormatIdc : null, + bitDepthLumaMinus8: hasExtendedData ? spsInfo.bitDepthLumaMinus8 : null, + bitDepthChromaMinus8: hasExtendedData ? spsInfo.bitDepthChromaMinus8 : null, + sequenceParameterSetExt: hasExtendedData ? spsExtUnits : null + }; + } catch (error) { + Logging._error("Error building AVC Decoder Configuration Record:", error); + return null; + } +}; +/** Serializes an AvcDecoderConfigurationRecord into the format specified in Section 5.3.3.1 of ISO 14496-15. */ +var serializeAvcDecoderConfigurationRecord = (record) => { + const bytes = []; + bytes.push(record.configurationVersion); + bytes.push(record.avcProfileIndication); + bytes.push(record.profileCompatibility); + bytes.push(record.avcLevelIndication); + bytes.push(252 | record.lengthSizeMinusOne & 3); + bytes.push(224 | record.sequenceParameterSets.length & 31); + for (const sps of record.sequenceParameterSets) { + const length = sps.byteLength; + bytes.push(length >> 8); + bytes.push(length & 255); + for (let i = 0; i < length; i++) bytes.push(sps[i]); + } + bytes.push(record.pictureParameterSets.length); + for (const pps of record.pictureParameterSets) { + const length = pps.byteLength; + bytes.push(length >> 8); + bytes.push(length & 255); + for (let i = 0; i < length; i++) bytes.push(pps[i]); + } + if ((record.avcProfileIndication === 100 || record.avcProfileIndication === 110 || record.avcProfileIndication === 122 || record.avcProfileIndication === 144) && record.chromaFormat !== null) { + assert(record.bitDepthLumaMinus8 !== null); + assert(record.bitDepthChromaMinus8 !== null); + assert(record.sequenceParameterSetExt !== null); + bytes.push(252 | record.chromaFormat & 3); + bytes.push(248 | record.bitDepthLumaMinus8 & 7); + bytes.push(248 | record.bitDepthChromaMinus8 & 7); + bytes.push(record.sequenceParameterSetExt.length); + for (const spsExt of record.sequenceParameterSetExt) { + const length = spsExt.byteLength; + bytes.push(length >> 8); + bytes.push(length & 255); + for (let i = 0; i < length; i++) bytes.push(spsExt[i]); + } + } + return new Uint8Array(bytes); +}; +/** Deserializes an AvcDecoderConfigurationRecord from the format specified in Section 5.3.3.1 of ISO 14496-15. */ +var deserializeAvcDecoderConfigurationRecord = (data) => { + try { + const view = toDataView(data); + let offset = 0; + const configurationVersion = view.getUint8(offset++); + const avcProfileIndication = view.getUint8(offset++); + const profileCompatibility = view.getUint8(offset++); + const avcLevelIndication = view.getUint8(offset++); + const lengthSizeMinusOne = view.getUint8(offset++) & 3; + const numOfSequenceParameterSets = view.getUint8(offset++) & 31; + const sequenceParameterSets = []; + for (let i = 0; i < numOfSequenceParameterSets; i++) { + const length = view.getUint16(offset, false); + offset += 2; + sequenceParameterSets.push(data.subarray(offset, offset + length)); + offset += length; + } + const numOfPictureParameterSets = view.getUint8(offset++); + const pictureParameterSets = []; + for (let i = 0; i < numOfPictureParameterSets; i++) { + const length = view.getUint16(offset, false); + offset += 2; + pictureParameterSets.push(data.subarray(offset, offset + length)); + offset += length; + } + const record = { + configurationVersion, + avcProfileIndication, + profileCompatibility, + avcLevelIndication, + lengthSizeMinusOne, + sequenceParameterSets, + pictureParameterSets, + chromaFormat: null, + bitDepthLumaMinus8: null, + bitDepthChromaMinus8: null, + sequenceParameterSetExt: null + }; + if ((avcProfileIndication === 100 || avcProfileIndication === 110 || avcProfileIndication === 122 || avcProfileIndication === 144) && offset + 4 <= data.length) { + const chromaFormat = view.getUint8(offset++) & 3; + const bitDepthLumaMinus8 = view.getUint8(offset++) & 7; + const bitDepthChromaMinus8 = view.getUint8(offset++) & 7; + const numOfSequenceParameterSetExt = view.getUint8(offset++); + record.chromaFormat = chromaFormat; + record.bitDepthLumaMinus8 = bitDepthLumaMinus8; + record.bitDepthChromaMinus8 = bitDepthChromaMinus8; + const sequenceParameterSetExt = []; + for (let i = 0; i < numOfSequenceParameterSetExt; i++) { + const length = view.getUint16(offset, false); + offset += 2; + sequenceParameterSetExt.push(data.subarray(offset, offset + length)); + offset += length; + } + record.sequenceParameterSetExt = sequenceParameterSetExt; + } + return record; + } catch (error) { + Logging._error("Error deserializing AVC Decoder Configuration Record:", error); + return null; + } +}; +var AVC_HEVC_ASPECT_RATIO_IDC_TABLE = { + 1: { + num: 1, + den: 1 + }, + 2: { + num: 12, + den: 11 + }, + 3: { + num: 10, + den: 11 + }, + 4: { + num: 16, + den: 11 + }, + 5: { + num: 40, + den: 33 + }, + 6: { + num: 24, + den: 11 + }, + 7: { + num: 20, + den: 11 + }, + 8: { + num: 32, + den: 11 + }, + 9: { + num: 80, + den: 33 + }, + 10: { + num: 18, + den: 11 + }, + 11: { + num: 15, + den: 11 + }, + 12: { + num: 64, + den: 33 + }, + 13: { + num: 160, + den: 99 + }, + 14: { + num: 4, + den: 3 + }, + 15: { + num: 3, + den: 2 + }, + 16: { + num: 2, + den: 1 + } +}; +/** Parses an AVC SPS (Sequence Parameter Set) to extract basic information. */ +var parseAvcSps = (sps) => { + try { + const emulationUnpreventedBytes = removeEmulationPreventionBytes(sps); + const bitstream = new Bitstream(emulationUnpreventedBytes); + bitstream.skipBits(1); + bitstream.skipBits(2); + if (bitstream.readBits(5) !== 7) return null; + const profileIdc = bitstream.readAlignedByte(); + const constraintFlags = bitstream.readAlignedByte(); + const levelIdc = bitstream.readAlignedByte(); + readExpGolomb(bitstream); + let chromaFormatIdc = 1; + let bitDepthLumaMinus8 = 0; + let bitDepthChromaMinus8 = 0; + let separateColourPlaneFlag = 0; + if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) { + chromaFormatIdc = readExpGolomb(bitstream); + if (chromaFormatIdc === 3) separateColourPlaneFlag = bitstream.readBits(1); + bitDepthLumaMinus8 = readExpGolomb(bitstream); + bitDepthChromaMinus8 = readExpGolomb(bitstream); + bitstream.skipBits(1); + if (bitstream.readBits(1)) { + for (let i = 0; i < (chromaFormatIdc !== 3 ? 8 : 12); i++) if (bitstream.readBits(1)) { + const sizeOfScalingList = i < 6 ? 16 : 64; + let lastScale = 8; + let nextScale = 8; + for (let j = 0; j < sizeOfScalingList; j++) { + if (nextScale !== 0) { + const deltaScale = readSignedExpGolomb(bitstream); + nextScale = (lastScale + deltaScale + 256) % 256; + } + lastScale = nextScale === 0 ? lastScale : nextScale; + } + } + } + } + readExpGolomb(bitstream); + const picOrderCntType = readExpGolomb(bitstream); + if (picOrderCntType === 0) readExpGolomb(bitstream); + else if (picOrderCntType === 1) { + bitstream.skipBits(1); + readSignedExpGolomb(bitstream); + readSignedExpGolomb(bitstream); + const numRefFramesInPicOrderCntCycle = readExpGolomb(bitstream); + for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) readSignedExpGolomb(bitstream); + } + readExpGolomb(bitstream); + bitstream.skipBits(1); + const picWidthInMbsMinus1 = readExpGolomb(bitstream); + const picHeightInMapUnitsMinus1 = readExpGolomb(bitstream); + const codedWidth = 16 * (picWidthInMbsMinus1 + 1); + const codedHeight = 16 * (picHeightInMapUnitsMinus1 + 1); + let displayWidth = codedWidth; + let displayHeight = codedHeight; + const frameMbsOnlyFlag = bitstream.readBits(1); + if (!frameMbsOnlyFlag) bitstream.skipBits(1); + bitstream.skipBits(1); + if (bitstream.readBits(1)) { + const frameCropLeftOffset = readExpGolomb(bitstream); + const frameCropRightOffset = readExpGolomb(bitstream); + const frameCropTopOffset = readExpGolomb(bitstream); + const frameCropBottomOffset = readExpGolomb(bitstream); + let cropUnitX; + let cropUnitY; + if ((separateColourPlaneFlag === 0 ? chromaFormatIdc : 0) === 0) { + cropUnitX = 1; + cropUnitY = 2 - frameMbsOnlyFlag; + } else { + const subWidthC = chromaFormatIdc === 3 ? 1 : 2; + const subHeightC = chromaFormatIdc === 1 ? 2 : 1; + cropUnitX = subWidthC; + cropUnitY = subHeightC * (2 - frameMbsOnlyFlag); + } + displayWidth -= cropUnitX * (frameCropLeftOffset + frameCropRightOffset); + displayHeight -= cropUnitY * (frameCropTopOffset + frameCropBottomOffset); + } + let colourPrimaries = 2; + let transferCharacteristics = 2; + let matrixCoefficients = 2; + let fullRangeFlag = 0; + let pixelAspectRatio = { + num: 1, + den: 1 + }; + let numReorderFrames = null; + let maxDecFrameBuffering = null; + let bitstreamRestrictionFlagBitOffset = null; + let bitstreamRestrictionFlag = null; + const vuiParametersFlagBitOffset = bitstream.pos; + if (bitstream.readBits(1)) { + if (bitstream.readBits(1)) { + const aspectRatioIdc = bitstream.readBits(8); + if (aspectRatioIdc === 255) pixelAspectRatio = { + num: bitstream.readBits(16), + den: bitstream.readBits(16) + }; + else { + const aspectRatio = AVC_HEVC_ASPECT_RATIO_IDC_TABLE[aspectRatioIdc]; + if (aspectRatio) pixelAspectRatio = aspectRatio; + } + } + if (bitstream.readBits(1)) bitstream.skipBits(1); + if (bitstream.readBits(1)) { + bitstream.skipBits(3); + fullRangeFlag = bitstream.readBits(1); + if (bitstream.readBits(1)) { + colourPrimaries = bitstream.readBits(8); + transferCharacteristics = bitstream.readBits(8); + matrixCoefficients = bitstream.readBits(8); + } + } + if (bitstream.readBits(1)) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + if (bitstream.readBits(1)) { + bitstream.skipBits(32); + bitstream.skipBits(32); + bitstream.skipBits(1); + } + const nalHrdParametersPresentFlag = bitstream.readBits(1); + if (nalHrdParametersPresentFlag) skipAvcHrdParameters(bitstream); + const vclHrdParametersPresentFlag = bitstream.readBits(1); + if (vclHrdParametersPresentFlag) skipAvcHrdParameters(bitstream); + if (nalHrdParametersPresentFlag || vclHrdParametersPresentFlag) bitstream.skipBits(1); + bitstream.skipBits(1); + bitstreamRestrictionFlagBitOffset = bitstream.pos; + bitstreamRestrictionFlag = bitstream.readBits(1); + if (bitstreamRestrictionFlag) { + bitstream.skipBits(1); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + numReorderFrames = readExpGolomb(bitstream); + maxDecFrameBuffering = readExpGolomb(bitstream); + } + } + if (numReorderFrames === null) { + assert(maxDecFrameBuffering === null); + const constraintSet3Flag = constraintFlags & 16; + if ((profileIdc === 44 || profileIdc === 86 || profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244) && constraintSet3Flag) { + numReorderFrames = 0; + maxDecFrameBuffering = 0; + } else { + const picWidthInMbs = picWidthInMbsMinus1 + 1; + const picHeightInMapUnits = picHeightInMapUnitsMinus1 + 1; + const frameHeightInMbs = (2 - frameMbsOnlyFlag) * picHeightInMapUnits; + const levelInfo = AVC_LEVEL_TABLE.find((x) => x.level >= levelIdc) ?? last(AVC_LEVEL_TABLE); + const maxDpbFrames = Math.min(Math.floor(levelInfo.maxDpbMbs / (picWidthInMbs * frameHeightInMbs)), 16); + numReorderFrames = maxDpbFrames; + maxDecFrameBuffering = maxDpbFrames; + } + } + assert(maxDecFrameBuffering !== null); + return { + emulationUnpreventedBytes, + profileIdc, + constraintFlags, + levelIdc, + frameMbsOnlyFlag, + chromaFormatIdc, + bitDepthLumaMinus8, + bitDepthChromaMinus8, + codedWidth, + codedHeight, + displayWidth, + displayHeight, + pixelAspectRatio, + colourPrimaries, + matrixCoefficients, + transferCharacteristics, + fullRangeFlag, + numReorderFrames, + maxDecFrameBuffering, + vuiParametersFlagBitOffset, + bitstreamRestrictionFlagBitOffset, + bitstreamRestrictionFlag + }; + } catch (error) { + Logging._error("Error parsing AVC SPS:", error); + return null; + } +}; +var skipAvcHrdParameters = (bitstream) => { + const cpb_cnt_minus1 = readExpGolomb(bitstream); + bitstream.skipBits(4); + bitstream.skipBits(4); + for (let i = 0; i <= cpb_cnt_minus1; i++) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + bitstream.skipBits(1); + } + bitstream.skipBits(5); + bitstream.skipBits(5); + bitstream.skipBits(5); + bitstream.skipBits(5); +}; +/** +* Adds the missing "bitstream restriction" section within the VUI parameter section. This is done to communicate +* frame reorder buffer size to the decoder, which may otherwise, in its absence, assume no B-frames and drop or skip +* them. +* See https://github.com/Vanilagy/mediabunny/issues/488 +*/ +var addAvcBitstreamRestriction = (sps) => { + assert(sps.bitstreamRestrictionFlag !== 1); + const modifiedBytes = new Uint8Array(sps.emulationUnpreventedBytes.byteLength + 64); + const oldBitstream = new Bitstream(sps.emulationUnpreventedBytes); + const newBitstream = new Bitstream(modifiedBytes); + if (sps.bitstreamRestrictionFlag === null) { + newBitstream.copyBits(sps.vuiParametersFlagBitOffset, oldBitstream); + newBitstream.writeBits(1, 1); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + newBitstream.writeBits(1, 0); + } else { + assert(sps.bitstreamRestrictionFlagBitOffset !== null); + newBitstream.copyBits(sps.bitstreamRestrictionFlagBitOffset, oldBitstream); + } + newBitstream.writeBits(1, 1); + newBitstream.writeBits(1, 1); + writeExpGolomb(newBitstream, 2); + writeExpGolomb(newBitstream, 1); + writeExpGolomb(newBitstream, 16); + writeExpGolomb(newBitstream, 16); + writeExpGolomb(newBitstream, sps.numReorderFrames); + writeExpGolomb(newBitstream, sps.maxDecFrameBuffering); + newBitstream.writeBits(1, 1); + newBitstream.writeBits((8 - newBitstream.pos % 8) % 8, 0); + const byteLength = newBitstream.pos / 8; + assert(Number.isInteger(byteLength)); + return addEmulationPreventionBytes(modifiedBytes.subarray(0, byteLength)); +}; +var concatHevcNalUnits = (nalUnits, decoderConfig) => { + if (decoderConfig.description) return concatNalUnitsInLengthPrefixed(nalUnits, (toUint8Array(decoderConfig.description)[21] & 3) + 1); + else return concatNalUnitsInAnnexB(nalUnits); +}; +var iterateHevcNalUnits = (packetData, decoderConfig) => { + if (decoderConfig.description) return iterateNalUnitsInLengthPrefixed(packetData, (toUint8Array(decoderConfig.description)[21] & 3) + 1); + else return iterateNalUnitsInAnnexB(packetData); +}; +var extractNalUnitTypeForHevc = (byte) => { + return byte >> 1 & 63; +}; +/** Parses an HEVC SPS (Sequence Parameter Set) to extract video information. */ +var parseHevcSps = (sps) => { + try { + const bitstream = new Bitstream(removeEmulationPreventionBytes(sps)); + bitstream.skipBits(16); + bitstream.readBits(4); + const spsMaxSubLayersMinus1 = bitstream.readBits(3); + const spsTemporalIdNestingFlag = bitstream.readBits(1); + const { general_profile_space, general_tier_flag, general_profile_idc, general_profile_compatibility_flags, general_constraint_indicator_flags, general_level_idc } = parseProfileTierLevel(bitstream, spsMaxSubLayersMinus1); + readExpGolomb(bitstream); + const chromaFormatIdc = readExpGolomb(bitstream); + let separateColourPlaneFlag = 0; + if (chromaFormatIdc === 3) separateColourPlaneFlag = bitstream.readBits(1); + const picWidthInLumaSamples = readExpGolomb(bitstream); + const picHeightInLumaSamples = readExpGolomb(bitstream); + let displayWidth = picWidthInLumaSamples; + let displayHeight = picHeightInLumaSamples; + if (bitstream.readBits(1)) { + const confWinLeftOffset = readExpGolomb(bitstream); + const confWinRightOffset = readExpGolomb(bitstream); + const confWinTopOffset = readExpGolomb(bitstream); + const confWinBottomOffset = readExpGolomb(bitstream); + let subWidthC = 1; + let subHeightC = 1; + const chromaArrayType = separateColourPlaneFlag === 0 ? chromaFormatIdc : 0; + if (chromaArrayType === 1) { + subWidthC = 2; + subHeightC = 2; + } else if (chromaArrayType === 2) { + subWidthC = 2; + subHeightC = 1; + } + displayWidth -= (confWinLeftOffset + confWinRightOffset) * subWidthC; + displayHeight -= (confWinTopOffset + confWinBottomOffset) * subHeightC; + } + const bitDepthLumaMinus8 = readExpGolomb(bitstream); + const bitDepthChromaMinus8 = readExpGolomb(bitstream); + readExpGolomb(bitstream); + const startI = bitstream.readBits(1) ? 0 : spsMaxSubLayersMinus1; + let spsMaxNumReorderPics = 0; + for (let i = startI; i <= spsMaxSubLayersMinus1; i++) { + readExpGolomb(bitstream); + spsMaxNumReorderPics = readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + if (bitstream.readBits(1)) { + if (bitstream.readBits(1)) skipScalingListData(bitstream); + } + bitstream.skipBits(1); + bitstream.skipBits(1); + if (bitstream.readBits(1)) { + bitstream.skipBits(4); + bitstream.skipBits(4); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + bitstream.skipBits(1); + } + skipAllStRefPicSets(bitstream, readExpGolomb(bitstream)); + if (bitstream.readBits(1)) { + const numLongTermRefPicsSps = readExpGolomb(bitstream); + for (let i = 0; i < numLongTermRefPicsSps; i++) { + readExpGolomb(bitstream); + bitstream.skipBits(1); + } + } + bitstream.skipBits(1); + bitstream.skipBits(1); + let colourPrimaries = 2; + let transferCharacteristics = 2; + let matrixCoefficients = 2; + let fullRangeFlag = 0; + let minSpatialSegmentationIdc = 0; + let pixelAspectRatio = { + num: 1, + den: 1 + }; + if (bitstream.readBits(1)) { + const vui = parseHevcVui(bitstream, spsMaxSubLayersMinus1); + pixelAspectRatio = vui.pixelAspectRatio; + colourPrimaries = vui.colourPrimaries; + transferCharacteristics = vui.transferCharacteristics; + matrixCoefficients = vui.matrixCoefficients; + fullRangeFlag = vui.fullRangeFlag; + minSpatialSegmentationIdc = vui.minSpatialSegmentationIdc; + } + return { + displayWidth, + displayHeight, + pixelAspectRatio, + colourPrimaries, + transferCharacteristics, + matrixCoefficients, + fullRangeFlag, + maxDecFrameBuffering: spsMaxNumReorderPics + 1, + spsMaxSubLayersMinus1, + spsTemporalIdNestingFlag, + generalProfileSpace: general_profile_space, + generalTierFlag: general_tier_flag, + generalProfileIdc: general_profile_idc, + generalProfileCompatibilityFlags: general_profile_compatibility_flags, + generalConstraintIndicatorFlags: general_constraint_indicator_flags, + generalLevelIdc: general_level_idc, + chromaFormatIdc, + bitDepthLumaMinus8, + bitDepthChromaMinus8, + minSpatialSegmentationIdc + }; + } catch (error) { + Logging._error("Error parsing HEVC SPS:", error); + return null; + } +}; +/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */ +var extractHevcDecoderConfigurationRecord = (packetData) => { + try { + const vpsUnits = []; + const spsUnits = []; + const ppsUnits = []; + const seiUnits = []; + for (const loc of iterateNalUnitsInAnnexB(packetData)) { + const nalUnit = packetData.subarray(loc.offset, loc.offset + loc.length); + const type = extractNalUnitTypeForHevc(nalUnit[0]); + if (type === HevcNalUnitType.VPS_NUT) vpsUnits.push(nalUnit); + else if (type === HevcNalUnitType.SPS_NUT) spsUnits.push(nalUnit); + else if (type === HevcNalUnitType.PPS_NUT) ppsUnits.push(nalUnit); + else if (type === HevcNalUnitType.PREFIX_SEI_NUT || type === HevcNalUnitType.SUFFIX_SEI_NUT) seiUnits.push(nalUnit); + } + if (spsUnits.length === 0 || ppsUnits.length === 0) return null; + const spsInfo = parseHevcSps(spsUnits[0]); + if (!spsInfo) return null; + let parallelismType = 0; + if (ppsUnits.length > 0) { + const pps = ppsUnits[0]; + const ppsBitstream = new Bitstream(removeEmulationPreventionBytes(pps)); + ppsBitstream.skipBits(16); + readExpGolomb(ppsBitstream); + readExpGolomb(ppsBitstream); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(3); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + readExpGolomb(ppsBitstream); + readExpGolomb(ppsBitstream); + readSignedExpGolomb(ppsBitstream); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + if (ppsBitstream.readBits(1)) readExpGolomb(ppsBitstream); + readSignedExpGolomb(ppsBitstream); + readSignedExpGolomb(ppsBitstream); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + ppsBitstream.skipBits(1); + const tiles_enabled_flag = ppsBitstream.readBits(1); + const entropy_coding_sync_enabled_flag = ppsBitstream.readBits(1); + if (!tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 0; + else if (tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 2; + else if (!tiles_enabled_flag && entropy_coding_sync_enabled_flag) parallelismType = 3; + else parallelismType = 0; + } + const arrays = [ + ...vpsUnits.length ? [{ + arrayCompleteness: 1, + nalUnitType: HevcNalUnitType.VPS_NUT, + nalUnits: vpsUnits + }] : [], + ...spsUnits.length ? [{ + arrayCompleteness: 1, + nalUnitType: HevcNalUnitType.SPS_NUT, + nalUnits: spsUnits + }] : [], + ...ppsUnits.length ? [{ + arrayCompleteness: 1, + nalUnitType: HevcNalUnitType.PPS_NUT, + nalUnits: ppsUnits + }] : [], + ...seiUnits.length ? [{ + arrayCompleteness: 1, + nalUnitType: extractNalUnitTypeForHevc(seiUnits[0][0]), + nalUnits: seiUnits + }] : [] + ]; + return { + configurationVersion: 1, + generalProfileSpace: spsInfo.generalProfileSpace, + generalTierFlag: spsInfo.generalTierFlag, + generalProfileIdc: spsInfo.generalProfileIdc, + generalProfileCompatibilityFlags: spsInfo.generalProfileCompatibilityFlags, + generalConstraintIndicatorFlags: spsInfo.generalConstraintIndicatorFlags, + generalLevelIdc: spsInfo.generalLevelIdc, + minSpatialSegmentationIdc: spsInfo.minSpatialSegmentationIdc, + parallelismType, + chromaFormatIdc: spsInfo.chromaFormatIdc, + bitDepthLumaMinus8: spsInfo.bitDepthLumaMinus8, + bitDepthChromaMinus8: spsInfo.bitDepthChromaMinus8, + avgFrameRate: 0, + constantFrameRate: 0, + numTemporalLayers: spsInfo.spsMaxSubLayersMinus1 + 1, + temporalIdNested: spsInfo.spsTemporalIdNestingFlag, + lengthSizeMinusOne: 3, + arrays + }; + } catch (error) { + Logging._error("Error building HEVC Decoder Configuration Record:", error); + return null; + } +}; +var parseProfileTierLevel = (bitstream, maxNumSubLayersMinus1) => { + const general_profile_space = bitstream.readBits(2); + const general_tier_flag = bitstream.readBits(1); + const general_profile_idc = bitstream.readBits(5); + let general_profile_compatibility_flags = 0; + for (let i = 0; i < 32; i++) general_profile_compatibility_flags = general_profile_compatibility_flags << 1 | bitstream.readBits(1); + const general_constraint_indicator_flags = new Uint8Array(6); + for (let i = 0; i < 6; i++) general_constraint_indicator_flags[i] = bitstream.readBits(8); + const general_level_idc = bitstream.readBits(8); + const sub_layer_profile_present_flag = []; + const sub_layer_level_present_flag = []; + for (let i = 0; i < maxNumSubLayersMinus1; i++) { + sub_layer_profile_present_flag.push(bitstream.readBits(1)); + sub_layer_level_present_flag.push(bitstream.readBits(1)); + } + if (maxNumSubLayersMinus1 > 0) for (let i = maxNumSubLayersMinus1; i < 8; i++) bitstream.skipBits(2); + for (let i = 0; i < maxNumSubLayersMinus1; i++) { + if (sub_layer_profile_present_flag[i]) bitstream.skipBits(88); + if (sub_layer_level_present_flag[i]) bitstream.skipBits(8); + } + return { + general_profile_space, + general_tier_flag, + general_profile_idc, + general_profile_compatibility_flags, + general_constraint_indicator_flags, + general_level_idc + }; +}; +var skipScalingListData = (bitstream) => { + for (let sizeId = 0; sizeId < 4; sizeId++) for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) if (!bitstream.readBits(1)) readExpGolomb(bitstream); + else { + const coefNum = Math.min(64, 1 << 4 + (sizeId << 1)); + if (sizeId > 1) readSignedExpGolomb(bitstream); + for (let i = 0; i < coefNum; i++) readSignedExpGolomb(bitstream); + } +}; +var skipAllStRefPicSets = (bitstream, num_short_term_ref_pic_sets) => { + const NumDeltaPocs = []; + for (let stRpsIdx = 0; stRpsIdx < num_short_term_ref_pic_sets; stRpsIdx++) NumDeltaPocs[stRpsIdx] = skipStRefPicSet(bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs); +}; +var skipStRefPicSet = (bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs) => { + let NumDeltaPocsThis = 0; + let inter_ref_pic_set_prediction_flag = 0; + let RefRpsIdx = 0; + if (stRpsIdx !== 0) inter_ref_pic_set_prediction_flag = bitstream.readBits(1); + if (inter_ref_pic_set_prediction_flag) { + if (stRpsIdx === num_short_term_ref_pic_sets) RefRpsIdx = stRpsIdx - (readExpGolomb(bitstream) + 1); + else RefRpsIdx = stRpsIdx - 1; + bitstream.readBits(1); + readExpGolomb(bitstream); + const numDelta = NumDeltaPocs[RefRpsIdx] ?? 0; + for (let j = 0; j <= numDelta; j++) if (!bitstream.readBits(1)) bitstream.readBits(1); + NumDeltaPocsThis = NumDeltaPocs[RefRpsIdx]; + } else { + const num_negative_pics = readExpGolomb(bitstream); + const num_positive_pics = readExpGolomb(bitstream); + for (let i = 0; i < num_negative_pics; i++) { + readExpGolomb(bitstream); + bitstream.readBits(1); + } + for (let i = 0; i < num_positive_pics; i++) { + readExpGolomb(bitstream); + bitstream.readBits(1); + } + NumDeltaPocsThis = num_negative_pics + num_positive_pics; + } + return NumDeltaPocsThis; +}; +var parseHevcVui = (bitstream, sps_max_sub_layers_minus1) => { + let colourPrimaries = 2; + let transferCharacteristics = 2; + let matrixCoefficients = 2; + let fullRangeFlag = 0; + let minSpatialSegmentationIdc = 0; + let pixelAspectRatio = { + num: 1, + den: 1 + }; + if (bitstream.readBits(1)) { + const aspect_ratio_idc = bitstream.readBits(8); + if (aspect_ratio_idc === 255) pixelAspectRatio = { + num: bitstream.readBits(16), + den: bitstream.readBits(16) + }; + else { + const aspectRatio = AVC_HEVC_ASPECT_RATIO_IDC_TABLE[aspect_ratio_idc]; + if (aspectRatio) pixelAspectRatio = aspectRatio; + } + } + if (bitstream.readBits(1)) bitstream.readBits(1); + if (bitstream.readBits(1)) { + bitstream.readBits(3); + fullRangeFlag = bitstream.readBits(1); + if (bitstream.readBits(1)) { + colourPrimaries = bitstream.readBits(8); + transferCharacteristics = bitstream.readBits(8); + matrixCoefficients = bitstream.readBits(8); + } + } + if (bitstream.readBits(1)) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + bitstream.readBits(1); + bitstream.readBits(1); + bitstream.readBits(1); + if (bitstream.readBits(1)) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + if (bitstream.readBits(1)) { + bitstream.readBits(32); + bitstream.readBits(32); + if (bitstream.readBits(1)) readExpGolomb(bitstream); + if (bitstream.readBits(1)) skipHevcHrdParameters(bitstream, true, sps_max_sub_layers_minus1); + } + if (bitstream.readBits(1)) { + bitstream.readBits(1); + bitstream.readBits(1); + bitstream.readBits(1); + minSpatialSegmentationIdc = readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + return { + pixelAspectRatio, + colourPrimaries, + transferCharacteristics, + matrixCoefficients, + fullRangeFlag, + minSpatialSegmentationIdc + }; +}; +var skipHevcHrdParameters = (bitstream, commonInfPresentFlag, maxNumSubLayersMinus1) => { + let nal_hrd_parameters_present_flag = false; + let vcl_hrd_parameters_present_flag = false; + let sub_pic_hrd_params_present_flag = false; + if (commonInfPresentFlag) { + nal_hrd_parameters_present_flag = bitstream.readBits(1) === 1; + vcl_hrd_parameters_present_flag = bitstream.readBits(1) === 1; + if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) { + sub_pic_hrd_params_present_flag = bitstream.readBits(1) === 1; + if (sub_pic_hrd_params_present_flag) { + bitstream.readBits(8); + bitstream.readBits(5); + bitstream.readBits(1); + bitstream.readBits(5); + } + bitstream.readBits(4); + bitstream.readBits(4); + if (sub_pic_hrd_params_present_flag) bitstream.readBits(4); + bitstream.readBits(5); + bitstream.readBits(5); + bitstream.readBits(5); + } + } + for (let i = 0; i <= maxNumSubLayersMinus1; i++) { + const fixed_pic_rate_general_flag = bitstream.readBits(1) === 1; + let fixed_pic_rate_within_cvs_flag = true; + if (!fixed_pic_rate_general_flag) fixed_pic_rate_within_cvs_flag = bitstream.readBits(1) === 1; + let low_delay_hrd_flag = false; + if (fixed_pic_rate_within_cvs_flag) readExpGolomb(bitstream); + else low_delay_hrd_flag = bitstream.readBits(1) === 1; + let CpbCnt = 1; + if (!low_delay_hrd_flag) CpbCnt = readExpGolomb(bitstream) + 1; + if (nal_hrd_parameters_present_flag) skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag); + if (vcl_hrd_parameters_present_flag) skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag); + } +}; +var skipSubLayerHrdParameters = (bitstream, CpbCnt, sub_pic_hrd_params_present_flag) => { + for (let i = 0; i < CpbCnt; i++) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + if (sub_pic_hrd_params_present_flag) { + readExpGolomb(bitstream); + readExpGolomb(bitstream); + } + bitstream.readBits(1); + } +}; +/** Serializes an HevcDecoderConfigurationRecord into the format specified in Section 8.3.3.1 of ISO 14496-15. */ +var serializeHevcDecoderConfigurationRecord = (record) => { + const bytes = []; + bytes.push(record.configurationVersion); + bytes.push((record.generalProfileSpace & 3) << 6 | (record.generalTierFlag & 1) << 5 | record.generalProfileIdc & 31); + bytes.push(record.generalProfileCompatibilityFlags >>> 24 & 255); + bytes.push(record.generalProfileCompatibilityFlags >>> 16 & 255); + bytes.push(record.generalProfileCompatibilityFlags >>> 8 & 255); + bytes.push(record.generalProfileCompatibilityFlags & 255); + bytes.push(...record.generalConstraintIndicatorFlags); + bytes.push(record.generalLevelIdc & 255); + bytes.push(240 | record.minSpatialSegmentationIdc >> 8 & 15); + bytes.push(record.minSpatialSegmentationIdc & 255); + bytes.push(252 | record.parallelismType & 3); + bytes.push(252 | record.chromaFormatIdc & 3); + bytes.push(248 | record.bitDepthLumaMinus8 & 7); + bytes.push(248 | record.bitDepthChromaMinus8 & 7); + bytes.push(record.avgFrameRate >> 8 & 255); + bytes.push(record.avgFrameRate & 255); + bytes.push((record.constantFrameRate & 3) << 6 | (record.numTemporalLayers & 7) << 3 | (record.temporalIdNested & 1) << 2 | record.lengthSizeMinusOne & 3); + bytes.push(record.arrays.length & 255); + for (const arr of record.arrays) { + bytes.push((arr.arrayCompleteness & 1) << 7 | 0 | arr.nalUnitType & 63); + bytes.push(arr.nalUnits.length >> 8 & 255); + bytes.push(arr.nalUnits.length & 255); + for (const nal of arr.nalUnits) { + bytes.push(nal.length >> 8 & 255); + bytes.push(nal.length & 255); + for (let i = 0; i < nal.length; i++) bytes.push(nal[i]); + } + } + return new Uint8Array(bytes); +}; +/** Deserializes an HevcDecoderConfigurationRecord from the format specified in Section 8.3.3.1 of ISO 14496-15. */ +var deserializeHevcDecoderConfigurationRecord = (data) => { + try { + const view = toDataView(data); + let offset = 0; + const configurationVersion = view.getUint8(offset++); + const byte1 = view.getUint8(offset++); + const generalProfileSpace = byte1 >> 6 & 3; + const generalTierFlag = byte1 >> 5 & 1; + const generalProfileIdc = byte1 & 31; + const generalProfileCompatibilityFlags = view.getUint32(offset, false); + offset += 4; + const generalConstraintIndicatorFlags = data.subarray(offset, offset + 6); + offset += 6; + const generalLevelIdc = view.getUint8(offset++); + const minSpatialSegmentationIdc = (view.getUint8(offset++) & 15) << 8 | view.getUint8(offset++); + const parallelismType = view.getUint8(offset++) & 3; + const chromaFormatIdc = view.getUint8(offset++) & 3; + const bitDepthLumaMinus8 = view.getUint8(offset++) & 7; + const bitDepthChromaMinus8 = view.getUint8(offset++) & 7; + const avgFrameRate = view.getUint16(offset, false); + offset += 2; + const byte21 = view.getUint8(offset++); + const constantFrameRate = byte21 >> 6 & 3; + const numTemporalLayers = byte21 >> 3 & 7; + const temporalIdNested = byte21 >> 2 & 1; + const lengthSizeMinusOne = byte21 & 3; + const numOfArrays = view.getUint8(offset++); + const arrays = []; + for (let i = 0; i < numOfArrays; i++) { + const arrByte = view.getUint8(offset++); + const arrayCompleteness = arrByte >> 7 & 1; + const nalUnitType = arrByte & 63; + const numNalus = view.getUint16(offset, false); + offset += 2; + const nalUnits = []; + for (let j = 0; j < numNalus; j++) { + const nalUnitLength = view.getUint16(offset, false); + offset += 2; + nalUnits.push(data.subarray(offset, offset + nalUnitLength)); + offset += nalUnitLength; + } + arrays.push({ + arrayCompleteness, + nalUnitType, + nalUnits + }); + } + return { + configurationVersion, + generalProfileSpace, + generalTierFlag, + generalProfileIdc, + generalProfileCompatibilityFlags, + generalConstraintIndicatorFlags, + generalLevelIdc, + minSpatialSegmentationIdc, + parallelismType, + chromaFormatIdc, + bitDepthLumaMinus8, + bitDepthChromaMinus8, + avgFrameRate, + constantFrameRate, + numTemporalLayers, + temporalIdNested, + lengthSizeMinusOne, + arrays + }; + } catch (error) { + Logging._error("Error deserializing HEVC Decoder Configuration Record:", error); + return null; + } +}; +var HevcNaluOrderState; +(function(HevcNaluOrderState) { + HevcNaluOrderState[HevcNaluOrderState["audAllowed"] = 0] = "audAllowed"; + HevcNaluOrderState[HevcNaluOrderState["beforeFirstVcl"] = 1] = "beforeFirstVcl"; + HevcNaluOrderState[HevcNaluOrderState["afterFirstVcl"] = 2] = "afterFirstVcl"; + HevcNaluOrderState[HevcNaluOrderState["eoBitstreamAllowed"] = 3] = "eoBitstreamAllowed"; + HevcNaluOrderState[HevcNaluOrderState["noMoreDataAllowed"] = 4] = "noMoreDataAllowed"; +})(HevcNaluOrderState || (HevcNaluOrderState = {})); +var sanitizeHevcPacketForChromium = (packetData, decoderConfig) => { + const removedNalUnits = /* @__PURE__ */ new Set(); + let orderState = HevcNaluOrderState.audAllowed; + for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) { + if (orderState === HevcNaluOrderState.noMoreDataAllowed) { + removedNalUnits.add(loc.offset); + continue; + } + const type = extractNalUnitTypeForHevc(packetData[loc.offset]); + if (orderState === HevcNaluOrderState.eoBitstreamAllowed && type !== 37) { + removedNalUnits.add(loc.offset); + continue; + } + let remove = false; + if (type === 35) if (orderState > HevcNaluOrderState.audAllowed) remove = true; + else orderState = HevcNaluOrderState.beforeFirstVcl; + else if (type <= 31) if (orderState > HevcNaluOrderState.afterFirstVcl) remove = true; + else orderState = HevcNaluOrderState.afterFirstVcl; + else if (type === 36) if (orderState !== HevcNaluOrderState.afterFirstVcl) remove = true; + else orderState = HevcNaluOrderState.eoBitstreamAllowed; + else if (type === 37) if (orderState < HevcNaluOrderState.afterFirstVcl) remove = true; + else orderState = HevcNaluOrderState.noMoreDataAllowed; + else if (type === 32 || type === 33 || type === 34 || type === 39 || type >= 41 && type <= 44 || type >= 48 && type <= 55) if (orderState > HevcNaluOrderState.beforeFirstVcl) remove = true; + else orderState = HevcNaluOrderState.beforeFirstVcl; + else if (type === 38 || type === 40 || type >= 45 && type <= 47 || type >= 56 && type <= 63) { + if (orderState < HevcNaluOrderState.afterFirstVcl) remove = true; + } + if (remove) removedNalUnits.add(loc.offset); + } + if (removedNalUnits.size === 0) return null; + const filteredNalUnits = []; + for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) if (!removedNalUnits.has(loc.offset)) filteredNalUnits.push(packetData.subarray(loc.offset, loc.offset + loc.length)); + return concatHevcNalUnits(filteredNalUnits, decoderConfig); +}; +var VP9_COLOR_SPACE_TABLE = { + 1: { + colourPrimaries: 5, + transferCharacteristics: 6, + matrixCoefficients: 5 + }, + 2: { + colourPrimaries: 1, + transferCharacteristics: 1, + matrixCoefficients: 1 + }, + 3: { + colourPrimaries: 6, + transferCharacteristics: 6, + matrixCoefficients: 6 + }, + 4: { + colourPrimaries: 7, + transferCharacteristics: 7, + matrixCoefficients: 7 + }, + 5: { + colourPrimaries: 9, + transferCharacteristics: 14, + matrixCoefficients: 9 + }, + 7: { + colourPrimaries: 1, + transferCharacteristics: 13, + matrixCoefficients: 0 + } +}; +var extractVp9CodecInfoFromPacket = (packet) => { + const bitstream = new Bitstream(packet); + if (bitstream.readBits(2) !== 2) return null; + const profileLowBit = bitstream.readBits(1); + const profile = (bitstream.readBits(1) << 1) + profileLowBit; + if (profile === 3) bitstream.skipBits(1); + if (bitstream.readBits(1) === 1) return null; + if (bitstream.readBits(1) !== 0) return null; + bitstream.skipBits(2); + if (bitstream.readBits(24) !== 4817730) return null; + let bitDepth = 8; + if (profile >= 2) bitDepth = bitstream.readBits(1) ? 12 : 10; + const colorSpace = bitstream.readBits(3); + let chromaSubsampling = 0; + let videoFullRangeFlag = 0; + if (colorSpace !== 7) { + videoFullRangeFlag = bitstream.readBits(1); + if (profile === 1 || profile === 3) { + const subsamplingX = bitstream.readBits(1); + const subsamplingY = bitstream.readBits(1); + chromaSubsampling = !subsamplingX && !subsamplingY ? 3 : subsamplingX && !subsamplingY ? 2 : 1; + bitstream.skipBits(1); + } else chromaSubsampling = 1; + } else { + chromaSubsampling = 3; + videoFullRangeFlag = 1; + } + const widthMinusOne = bitstream.readBits(16); + const heightMinusOne = bitstream.readBits(16); + const pictureSize = (widthMinusOne + 1) * (heightMinusOne + 1); + let level = last(VP9_LEVEL_TABLE).level; + for (const entry of VP9_LEVEL_TABLE) if (pictureSize <= entry.maxPictureSize) { + level = entry.level; + break; + } + const colorSpaceValues = VP9_COLOR_SPACE_TABLE[colorSpace]; + const colourPrimaries = colorSpaceValues?.colourPrimaries ?? 2; + const transferCharacteristics = colorSpaceValues?.transferCharacteristics ?? 2; + const matrixCoefficients = colorSpaceValues?.matrixCoefficients ?? 2; + return { + profile, + level, + bitDepth, + chromaSubsampling, + videoFullRangeFlag, + colourPrimaries, + transferCharacteristics, + matrixCoefficients + }; +}; +var vp9CodecInfoHasColorInfo = (info) => { + return info.colourPrimaries !== 2 || info.transferCharacteristics !== 2 || info.matrixCoefficients !== 2; +}; +/** Iterates over all OBUs in an AV1 packet bitstream. */ +var iterateAv1PacketObus = function* (packet) { + const bitstream = new Bitstream(packet); + const readLeb128 = () => { + let value = 0; + for (let i = 0; i < 8; i++) { + const byte = bitstream.readAlignedByte(); + value += (byte & 127) * 2 ** (i * 7); + if (!(byte & 128)) break; + if (i === 7 && byte & 128) return null; + } + if (value > 2 ** 32 - 1) return null; + return value; + }; + while (bitstream.getBitsLeft() >= 8) { + bitstream.skipBits(1); + const obuType = bitstream.readBits(4); + const obuExtension = bitstream.readBits(1); + const obuHasSizeField = bitstream.readBits(1); + bitstream.skipBits(1); + if (obuExtension) bitstream.skipBits(8); + let obuSize; + if (obuHasSizeField) { + const obuSizeValue = readLeb128(); + if (obuSizeValue === null) return; + obuSize = obuSizeValue; + } else obuSize = Math.floor(bitstream.getBitsLeft() / 8); + assert(bitstream.pos % 8 === 0); + yield { + type: obuType, + data: packet.subarray(bitstream.pos / 8, bitstream.pos / 8 + obuSize) + }; + bitstream.skipBits(obuSize * 8); + } +}; +/** +* When AV1 codec information is not provided by the container, we can still try to extract the information by digging +* into the AV1 bitstream. +*/ +var extractAv1CodecInfoFromPacket = (packet) => { + for (const { type, data } of iterateAv1PacketObus(packet)) { + if (type !== 1) continue; + const bitstream = new Bitstream(data); + const seqProfile = bitstream.readBits(3); + bitstream.readBits(1); + const reducedStillPictureHeader = bitstream.readBits(1); + let seqLevel = 0; + let seqTier = 0; + let bufferDelayLengthMinus1 = 0; + if (reducedStillPictureHeader) seqLevel = bitstream.readBits(5); + else { + const timingInfoPresentFlag = bitstream.readBits(1); + let decoderModelInfoPresentFlag = 0; + if (timingInfoPresentFlag) { + bitstream.skipBits(32); + bitstream.skipBits(32); + if (bitstream.readBits(1)) { + let leadingZeros = 0; + while (leadingZeros < 32 && !bitstream.readBits(1)) leadingZeros++; + if (leadingZeros < 32) bitstream.skipBits(leadingZeros); + } + decoderModelInfoPresentFlag = bitstream.readBits(1); + if (decoderModelInfoPresentFlag) { + bufferDelayLengthMinus1 = bitstream.readBits(5); + bitstream.skipBits(32); + bitstream.skipBits(5); + bitstream.skipBits(5); + } + } + const initialDisplayDelayPresentFlag = bitstream.readBits(1); + const operatingPointsCntMinus1 = bitstream.readBits(5); + for (let i = 0; i <= operatingPointsCntMinus1; i++) { + bitstream.skipBits(12); + const seqLevelIdx = bitstream.readBits(5); + if (i === 0) seqLevel = seqLevelIdx; + if (seqLevelIdx > 7) { + const seqTierTemp = bitstream.readBits(1); + if (i === 0) seqTier = seqTierTemp; + } + if (decoderModelInfoPresentFlag) { + if (bitstream.readBits(1)) { + const n = bufferDelayLengthMinus1 + 1; + bitstream.skipBits(n); + bitstream.skipBits(n); + bitstream.skipBits(1); + } + } + if (initialDisplayDelayPresentFlag) { + if (bitstream.readBits(1)) bitstream.skipBits(4); + } + } + } + const frameWidthBitsMinus1 = bitstream.readBits(4); + const frameHeightBitsMinus1 = bitstream.readBits(4); + const n1 = frameWidthBitsMinus1 + 1; + bitstream.skipBits(n1); + const n2 = frameHeightBitsMinus1 + 1; + bitstream.skipBits(n2); + let frameIdNumbersPresentFlag = 0; + if (reducedStillPictureHeader) frameIdNumbersPresentFlag = 0; + else frameIdNumbersPresentFlag = bitstream.readBits(1); + if (frameIdNumbersPresentFlag) { + bitstream.skipBits(4); + bitstream.skipBits(3); + } + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + if (!reducedStillPictureHeader) { + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + const enableOrderHint = bitstream.readBits(1); + if (enableOrderHint) { + bitstream.skipBits(1); + bitstream.skipBits(1); + } + const seqChooseScreenContentTools = bitstream.readBits(1); + let seqForceScreenContentTools = 0; + if (seqChooseScreenContentTools) seqForceScreenContentTools = 2; + else seqForceScreenContentTools = bitstream.readBits(1); + if (seqForceScreenContentTools > 0) { + if (!bitstream.readBits(1)) bitstream.skipBits(1); + } + if (enableOrderHint) bitstream.skipBits(3); + } + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + const highBitdepth = bitstream.readBits(1); + let bitDepth = 8; + if (seqProfile === 2 && highBitdepth) bitDepth = bitstream.readBits(1) ? 12 : 10; + else if (seqProfile <= 2) bitDepth = highBitdepth ? 10 : 8; + let monochrome = 0; + if (seqProfile !== 1) monochrome = bitstream.readBits(1); + let colourPrimaries = 2; + let transferCharacteristics = 2; + let matrixCoefficients = 2; + if (bitstream.readBits(1)) { + colourPrimaries = bitstream.readBits(8); + transferCharacteristics = bitstream.readBits(8); + matrixCoefficients = bitstream.readBits(8); + } + let videoFullRangeFlag = 0; + let chromaSubsamplingX = 1; + let chromaSubsamplingY = 1; + let chromaSamplePosition = 0; + if (monochrome) videoFullRangeFlag = bitstream.readBits(1); + else if (colourPrimaries === 1 && transferCharacteristics === 13 && matrixCoefficients === 0) { + videoFullRangeFlag = 1; + chromaSubsamplingX = 0; + chromaSubsamplingY = 0; + } else { + videoFullRangeFlag = bitstream.readBits(1); + if (seqProfile === 0) { + chromaSubsamplingX = 1; + chromaSubsamplingY = 1; + } else if (seqProfile === 1) { + chromaSubsamplingX = 0; + chromaSubsamplingY = 0; + } else if (bitDepth === 12) { + chromaSubsamplingX = bitstream.readBits(1); + chromaSubsamplingY = chromaSubsamplingX ? bitstream.readBits(1) : 0; + } else { + chromaSubsamplingX = 1; + chromaSubsamplingY = 0; + } + if (chromaSubsamplingX && chromaSubsamplingY) chromaSamplePosition = bitstream.readBits(2); + } + return { + profile: seqProfile, + level: seqLevel, + tier: seqTier, + bitDepth, + monochrome, + chromaSubsamplingX, + chromaSubsamplingY, + chromaSamplePosition, + videoFullRangeFlag, + colourPrimaries, + transferCharacteristics, + matrixCoefficients + }; + } + return null; +}; +var av1CodecInfoHasColorInfo = (info) => { + return info.colourPrimaries !== 2 || info.transferCharacteristics !== 2 || info.matrixCoefficients !== 2; +}; +var extractProresCodecInfoFromPacket = (packet) => { + const frameHeaderStart = 8; + if (packet.length < 36) return null; + const view = toDataView(packet); + if (view.getUint32(4) !== 1768124518) return null; + if (view.getUint16(frameHeaderStart) < 28) return null; + return { + fullRange: false, + colourPrimaries: view.getUint8(22), + transferCharacteristics: view.getUint8(23), + matrixCoefficients: view.getUint8(24) + }; +}; +var parseOpusIdentificationHeader = (bytes) => { + const view = toDataView(bytes); + const outputChannelCount = view.getUint8(9); + const preSkip = view.getUint16(10, true); + const inputSampleRate = view.getUint32(12, true); + const outputGain = view.getInt16(16, true); + const channelMappingFamily = view.getUint8(18); + let channelMappingTable = null; + if (channelMappingFamily) channelMappingTable = bytes.subarray(19, 21 + outputChannelCount); + return { + outputChannelCount, + preSkip, + inputSampleRate, + outputGain, + channelMappingFamily, + channelMappingTable + }; +}; +var OPUS_FRAME_DURATION_TABLE = [ + 480, + 960, + 1920, + 2880, + 480, + 960, + 1920, + 2880, + 480, + 960, + 1920, + 2880, + 480, + 960, + 480, + 960, + 120, + 240, + 480, + 960, + 120, + 240, + 480, + 960, + 120, + 240, + 480, + 960, + 120, + 240, + 480, + 960 +]; +var parseOpusTocByte = (packet) => { + const config = packet[0] >> 3; + const code = packet[0] & 3; + let frameCount; + if (code === 0) frameCount = 1; + else if (code === 1 || code === 2) frameCount = 2; + else frameCount = packet[1] & 63; + return { durationInSamples: OPUS_FRAME_DURATION_TABLE[config] * frameCount }; +}; +var parseModesFromVorbisSetupPacket = (setupHeader) => { + if (setupHeader.length < 7) throw new Error("Setup header is too short."); + if (setupHeader[0] !== 5) throw new Error("Wrong packet type in Setup header."); + if (String.fromCharCode(...setupHeader.slice(1, 7)) !== "vorbis") throw new Error("Invalid packet signature in Setup header."); + const bufSize = setupHeader.length; + const revBuffer = new Uint8Array(bufSize); + for (let i = 0; i < bufSize; i++) revBuffer[i] = setupHeader[bufSize - 1 - i]; + const bitstream = new Bitstream(revBuffer); + let gotFramingBit = 0; + while (bitstream.getBitsLeft() > 97) if (bitstream.readBits(1) === 1) { + gotFramingBit = bitstream.pos; + break; + } + if (gotFramingBit === 0) throw new Error("Invalid Setup header: framing bit not found."); + let modeCount = 0; + let gotModeHeader = false; + let lastModeCount = 0; + while (bitstream.getBitsLeft() >= 97) { + const tempPos = bitstream.pos; + const a = bitstream.readBits(8); + const b = bitstream.readBits(16); + const c = bitstream.readBits(16); + if (a > 63 || b !== 0 || c !== 0) { + bitstream.pos = tempPos; + break; + } + bitstream.skipBits(1); + modeCount++; + if (modeCount > 64) break; + if (bitstream.clone().readBits(6) + 1 === modeCount) { + gotModeHeader = true; + lastModeCount = modeCount; + } + } + if (!gotModeHeader) throw new Error("Invalid Setup header: mode header not found."); + if (lastModeCount > 63) throw new Error(`Unsupported mode count: ${lastModeCount}.`); + const finalModeCount = lastModeCount; + bitstream.pos = 0; + bitstream.skipBits(gotFramingBit); + const modeBlockflags = Array(finalModeCount).fill(0); + for (let i = finalModeCount - 1; i >= 0; i--) { + bitstream.skipBits(40); + modeBlockflags[i] = bitstream.readBits(1); + } + return { modeBlockflags }; +}; +/** Determines a packet's type (key or delta) by digging into the packet bitstream. */ +var determineVideoPacketType = (codec, decoderConfig, packetData) => { + switch (codec) { + case "avc": + for (const loc of iterateAvcNalUnits(packetData, decoderConfig)) { + const nalTypeByte = packetData[loc.offset]; + const type = extractNalUnitTypeForAvc(nalTypeByte); + if (type >= AvcNalUnitType.NON_IDR_SLICE && type <= AvcNalUnitType.SLICE_DPC) return "delta"; + if (type === AvcNalUnitType.IDR) return "key"; + if (type === AvcNalUnitType.SEI && !isChromium()) { + const bytes = removeEmulationPreventionBytes(packetData.subarray(loc.offset, loc.offset + loc.length)); + let pos = 1; + do { + let payloadType = 0; + while (true) { + const nextByte = bytes[pos++]; + if (nextByte === void 0) break; + payloadType += nextByte; + if (nextByte < 255) break; + } + let payloadSize = 0; + while (true) { + const nextByte = bytes[pos++]; + if (nextByte === void 0) break; + payloadSize += nextByte; + if (nextByte < 255) break; + } + if (payloadType === 6) { + const bitstream = new Bitstream(bytes); + bitstream.pos = 8 * pos; + const recoveryFrameCount = readExpGolomb(bitstream); + const exactMatchFlag = bitstream.readBits(1); + if (recoveryFrameCount === 0 && exactMatchFlag === 1) return "key"; + } + pos += payloadSize; + } while (pos < bytes.length - 1); + } + } + return "delta"; + case "hevc": + for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) { + const type = extractNalUnitTypeForHevc(packetData[loc.offset]); + if (type < HevcNalUnitType.BLA_W_LP) return "delta"; + if (type <= HevcNalUnitType.RSV_IRAP_VCL23) return "key"; + } + return "delta"; + case "vp8": return (packetData[0] & 1) === 0 ? "key" : "delta"; + case "vp9": { + const bitstream = new Bitstream(packetData); + if (bitstream.readBits(2) !== 2) return null; + const profileLowBit = bitstream.readBits(1); + if ((bitstream.readBits(1) << 1) + profileLowBit === 3) bitstream.skipBits(1); + if (bitstream.readBits(1)) return null; + return bitstream.readBits(1) === 0 ? "key" : "delta"; + } + case "av1": { + let reducedStillPictureHeader = false; + for (const { type, data } of iterateAv1PacketObus(packetData)) if (type === 1) { + const bitstream = new Bitstream(data); + bitstream.skipBits(4); + reducedStillPictureHeader = !!bitstream.readBits(1); + } else if (type === 3 || type === 6 || type === 7) { + if (reducedStillPictureHeader) return "key"; + const bitstream = new Bitstream(data); + if (bitstream.readBits(1)) return null; + return bitstream.readBits(2) === 0 ? "key" : "delta"; + } + return null; + } + case "prores": return "key"; + default: + assertNever(codec); + assert(false); + } +}; +var FlacBlockType; +(function(FlacBlockType) { + FlacBlockType[FlacBlockType["STREAMINFO"] = 0] = "STREAMINFO"; + FlacBlockType[FlacBlockType["VORBIS_COMMENT"] = 4] = "VORBIS_COMMENT"; + FlacBlockType[FlacBlockType["PICTURE"] = 6] = "PICTURE"; +})(FlacBlockType || (FlacBlockType = {})); +var readVorbisComments = (bytes, metadataTags) => { + const commentView = toDataView(bytes); + let commentPos = 0; + const vendorStringLength = commentView.getUint32(commentPos, true); + commentPos += 4; + const vendorString = textDecoder.decode(bytes.subarray(commentPos, commentPos + vendorStringLength)); + commentPos += vendorStringLength; + if (vendorStringLength > 0) { + metadataTags.raw ??= {}; + metadataTags.raw["vendor"] ??= vendorString; + } + const listLength = commentView.getUint32(commentPos, true); + commentPos += 4; + for (let i = 0; i < listLength; i++) { + const stringLength = commentView.getUint32(commentPos, true); + commentPos += 4; + const string = textDecoder.decode(bytes.subarray(commentPos, commentPos + stringLength)); + commentPos += stringLength; + const separatorIndex = string.indexOf("="); + if (separatorIndex === -1) continue; + const key = string.slice(0, separatorIndex).toUpperCase(); + const value = string.slice(separatorIndex + 1); + metadataTags.raw ??= {}; + if (Array.isArray(metadataTags.raw[key])) metadataTags.raw[key] = [...metadataTags.raw[key], value]; + else if (typeof metadataTags.raw[key] === "string") metadataTags.raw[key] = [metadataTags.raw[key], value]; + else metadataTags.raw[key] ??= value; + switch (key) { + case "TITLE": + metadataTags.title ??= value; + break; + case "DESCRIPTION": + metadataTags.description ??= value; + break; + case "ARTIST": + metadataTags.artist ??= value; + break; + case "ALBUM": + metadataTags.album ??= value; + break; + case "ALBUMARTIST": + metadataTags.albumArtist ??= value; + break; + case "COMMENT": + metadataTags.comment ??= value; + break; + case "LYRICS": + metadataTags.lyrics ??= value; + break; + case "TRACKNUMBER": + { + const parts = value.split("/"); + const trackNum = Number.parseInt(parts[0], 10); + const tracksTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(trackNum) && trackNum > 0) metadataTags.trackNumber ??= trackNum; + if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "TRACKTOTAL": + { + const tracksTotal = Number.parseInt(value, 10); + if (Number.isInteger(tracksTotal) && tracksTotal > 0) metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "DISCNUMBER": + { + const parts = value.split("/"); + const discNum = Number.parseInt(parts[0], 10); + const discsTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(discNum) && discNum > 0) metadataTags.discNumber ??= discNum; + if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) metadataTags.discsTotal ??= discsTotal; + } + break; + case "DISCTOTAL": + { + const discsTotal = Number.parseInt(value, 10); + if (Number.isInteger(discsTotal) && discsTotal > 0) metadataTags.discsTotal ??= discsTotal; + } + break; + case "DATE": + { + const date = new Date(value); + if (!Number.isNaN(date.getTime())) metadataTags.date ??= date; + } + break; + case "BPM": + { + const bpm = Number.parseInt(value, 10); + if (Number.isInteger(bpm) && bpm > 0) metadataTags.beatsPerMinute ??= bpm; + } + break; + case "GENRE": + metadataTags.genre ??= value; + break; + case "METADATA_BLOCK_PICTURE": + { + const decoded = base64ToBytes(value); + const view = toDataView(decoded); + const pictureType = view.getUint32(0, false); + const mediaTypeLength = view.getUint32(4, false); + const mediaType = String.fromCharCode(...decoded.subarray(8, 8 + mediaTypeLength)); + const descriptionLength = view.getUint32(8 + mediaTypeLength, false); + const description = textDecoder.decode(decoded.subarray(12 + mediaTypeLength, 12 + mediaTypeLength + descriptionLength)); + const dataLength = view.getUint32(mediaTypeLength + descriptionLength + 28); + const data = decoded.subarray(mediaTypeLength + descriptionLength + 32, mediaTypeLength + descriptionLength + 32 + dataLength); + metadataTags.images ??= []; + metadataTags.images.push({ + data, + mimeType: mediaType, + kind: pictureType === 3 ? "coverFront" : pictureType === 4 ? "coverBack" : "unknown", + name: void 0, + description: description || void 0 + }); + } + break; + } + } +}; +var createVorbisComments = (headerBytes, tags, writeImages) => { + const commentHeaderParts = [headerBytes]; + const encodedVendorString = textEncoder.encode(VERSIONED_NAME); + let currentBuffer = new Uint8Array(4 + encodedVendorString.length); + let currentView = new DataView(currentBuffer.buffer); + currentView.setUint32(0, encodedVendorString.length, true); + currentBuffer.set(encodedVendorString, 4); + commentHeaderParts.push(currentBuffer); + const writtenTags = []; + const addCommentTag = (key, value) => { + const joined = `${key}=${value}`; + const encoded = textEncoder.encode(joined); + currentBuffer = new Uint8Array(4 + encoded.length); + currentView = new DataView(currentBuffer.buffer); + currentView.setUint32(0, encoded.length, true); + currentBuffer.set(encoded, 4); + commentHeaderParts.push(currentBuffer); + writtenTags.push(key); + }; + for (const { key, value } of keyValueIterator(tags)) switch (key) { + case "title": + addCommentTag("TITLE", value); + break; + case "description": + addCommentTag("DESCRIPTION", value); + break; + case "artist": + addCommentTag("ARTIST", value); + break; + case "album": + addCommentTag("ALBUM", value); + break; + case "albumArtist": + addCommentTag("ALBUMARTIST", value); + break; + case "genre": + addCommentTag("GENRE", value); + break; + case "date": + addCommentTag("DATE", value.toISOString().slice(0, 10)); + break; + case "comment": + addCommentTag("COMMENT", value); + break; + case "lyrics": + addCommentTag("LYRICS", value); + break; + case "trackNumber": + addCommentTag("TRACKNUMBER", value.toString()); + break; + case "tracksTotal": + addCommentTag("TRACKTOTAL", value.toString()); + break; + case "discNumber": + addCommentTag("DISCNUMBER", value.toString()); + break; + case "discsTotal": + addCommentTag("DISCTOTAL", value.toString()); + break; + case "beatsPerMinute": + addCommentTag("BPM", value.toString()); + break; + case "images": + if (!writeImages) break; + for (const image of value) { + const pictureType = image.kind === "coverFront" ? 3 : image.kind === "coverBack" ? 4 : 0; + const encodedMediaType = new Uint8Array(image.mimeType.length); + for (let i = 0; i < image.mimeType.length; i++) encodedMediaType[i] = image.mimeType.charCodeAt(i); + const encodedDescription = textEncoder.encode(image.description ?? ""); + const buffer = new Uint8Array(8 + encodedMediaType.length + 4 + encodedDescription.length + 16 + 4 + image.data.length); + const view = toDataView(buffer); + view.setUint32(0, pictureType, false); + view.setUint32(4, encodedMediaType.length, false); + buffer.set(encodedMediaType, 8); + view.setUint32(8 + encodedMediaType.length, encodedDescription.length, false); + buffer.set(encodedDescription, 12 + encodedMediaType.length); + view.setUint32(28 + encodedMediaType.length + encodedDescription.length, image.data.length, false); + buffer.set(image.data, 32 + encodedMediaType.length + encodedDescription.length); + addCommentTag("METADATA_BLOCK_PICTURE", bytesToBase64(buffer)); + } + break; + case "raw": break; + default: assertNever(key); + } + if (tags.raw) for (const key in tags.raw) { + const value = tags.raw[key] ?? tags.raw[key.toLowerCase()]; + if (key === "vendor" || value == null || writtenTags.includes(key)) continue; + if (typeof value === "string") addCommentTag(key, value); + else if (Array.isArray(value)) { + if (value.every((x) => typeof x === "string")) for (const elem of value) addCommentTag(key, elem); + } + } + const listLengthBuffer = new Uint8Array(4); + toDataView(listLengthBuffer).setUint32(0, writtenTags.length, true); + commentHeaderParts.splice(2, 0, listLengthBuffer); + const commentHeaderLength = commentHeaderParts.reduce((a, b) => a + b.length, 0); + const commentHeader = new Uint8Array(commentHeaderLength); + let pos = 0; + for (const part of commentHeaderParts) { + commentHeader.set(part, pos); + pos += part.length; + } + return commentHeader; +}; +/** +* Channel counts indexed by acmod (Table 4.3). +* Does NOT include LFE - add lfeon to get total channel count. +*/ +var AC3_ACMOD_CHANNEL_COUNTS = [ + 2, + 1, + 2, + 3, + 3, + 4, + 4, + 5 +]; +/** +* Parse an AC-3 syncframe to extract BSI (Bit Stream Information) fields. +* Section 4.3 +*/ +var parseAc3SyncFrame = (data) => { + if (data.length < 7) return null; + if (data[0] !== 11 || data[1] !== 119) return null; + const bitstream = new Bitstream(data); + bitstream.skipBits(16); + bitstream.skipBits(16); + const fscod = bitstream.readBits(2); + if (fscod === 3) return null; + const frmsizecod = bitstream.readBits(6); + const bsid = bitstream.readBits(5); + if (bsid > 8) return null; + const bsmod = bitstream.readBits(3); + const acmod = bitstream.readBits(3); + if ((acmod & 1) !== 0 && acmod !== 1) bitstream.skipBits(2); + if ((acmod & 4) !== 0) bitstream.skipBits(2); + if (acmod === 2) bitstream.skipBits(2); + return { + fscod, + bsid, + bsmod, + acmod, + lfeon: bitstream.readBits(1), + bitRateCode: Math.floor(frmsizecod / 2) + }; +}; +/** +* AC-3 frame sizes in bytes, indexed by [3 * frmsizecod + fscod]. +* fscod: 0=48kHz, 1=44.1kHz, 2=32kHz +* Values are 16-bit words * 2 (to convert to bytes). +* Table 4.13 +*/ +var AC3_FRAME_SIZES = [ + 128, + 138, + 192, + 128, + 140, + 192, + 160, + 174, + 240, + 160, + 176, + 240, + 192, + 208, + 288, + 192, + 210, + 288, + 224, + 242, + 336, + 224, + 244, + 336, + 256, + 278, + 384, + 256, + 280, + 384, + 320, + 348, + 480, + 320, + 350, + 480, + 384, + 416, + 288 * 2, + 384, + 418, + 288 * 2, + 448, + 486, + 336 * 2, + 448, + 488, + 336 * 2, + 256 * 2, + 278 * 2, + 384 * 2, + 256 * 2, + 279 * 2, + 384 * 2, + 320 * 2, + 348 * 2, + 480 * 2, + 320 * 2, + 349 * 2, + 480 * 2, + 384 * 2, + 417 * 2, + 576 * 2, + 384 * 2, + 418 * 2, + 576 * 2, + 448 * 2, + 487 * 2, + 672 * 2, + 448 * 2, + 488 * 2, + 672 * 2, + 512 * 2, + 557 * 2, + 768 * 2, + 512 * 2, + 558 * 2, + 768 * 2, + 640 * 2, + 696 * 2, + 960 * 2, + 640 * 2, + 697 * 2, + 960 * 2, + 768 * 2, + 835 * 2, + 1152 * 2, + 768 * 2, + 836 * 2, + 1152 * 2, + 896 * 2, + 975 * 2, + 1344 * 2, + 896 * 2, + 976 * 2, + 1344 * 2, + 1024 * 2, + 1114 * 2, + 1536 * 2, + 1024 * 2, + 1115 * 2, + 1536 * 2, + 1152 * 2, + 1253 * 2, + 1728 * 2, + 1152 * 2, + 1254 * 2, + 1728 * 2, + 1280 * 2, + 1393 * 2, + 1920 * 2, + 1280 * 2, + 1394 * 2, + 1920 * 2 +]; +/** Number of samples per AC-3 syncframe (always 1536) */ +var AC3_SAMPLES_PER_FRAME = 1536; +/** +* AC-3 registration_descriptor for MPEG-TS. +* Section A.2.3 +*/ +var AC3_REGISTRATION_DESCRIPTOR = new Uint8Array([ + 5, + 4, + 65, + 67, + 45, + 51 +]); +/** E-AC-3 registration_descriptor for MPEG-TS/ */ +var EAC3_REGISTRATION_DESCRIPTOR = new Uint8Array([ + 5, + 4, + 69, + 65, + 67, + 51 +]); +/** Number of audio blocks per syncframe, indexed by numblkscod */ +var EAC3_NUMBLKS_TABLE = [ + 1, + 2, + 3, + 6 +]; +/** +* Parse an E-AC-3 syncframe to extract BSI fields. +* Section E.1.2 +*/ +var parseEac3SyncFrame = (data) => { + if (data.length < 6) return null; + if (data[0] !== 11 || data[1] !== 119) return null; + const bitstream = new Bitstream(data); + bitstream.skipBits(16); + const strmtyp = bitstream.readBits(2); + bitstream.skipBits(3); + if (strmtyp !== 0 && strmtyp !== 2) return null; + const frmsiz = bitstream.readBits(11); + const fscod = bitstream.readBits(2); + let fscod2 = 0; + let numblkscod; + if (fscod === 3) { + fscod2 = bitstream.readBits(2); + numblkscod = 3; + } else numblkscod = bitstream.readBits(2); + const acmod = bitstream.readBits(3); + const lfeon = bitstream.readBits(1); + const bsid = bitstream.readBits(5); + if (bsid < 11 || bsid > 16) return null; + const numblks = EAC3_NUMBLKS_TABLE[numblkscod]; + let fs; + if (fscod < 3) fs = AC3_SAMPLE_RATES[fscod] / 1e3; + else fs = EAC3_REDUCED_SAMPLE_RATES[fscod2] / 1e3; + return { + dataRate: Math.round((frmsiz + 1) * fs / (numblks * 16)), + substreams: [{ + fscod, + fscod2, + bsid, + bsmod: 0, + acmod, + lfeon, + numDepSub: 0, + chanLoc: 0 + }] + }; +}; +/** +* Parse a dec3 box to extract E-AC-3 parameters. +* Section F.6 +*/ +var parseEac3Config = (data) => { + if (data.length < 2) return null; + const bitstream = new Bitstream(data); + const dataRate = bitstream.readBits(13); + const numIndSub = bitstream.readBits(3); + const substreams = []; + for (let i = 0; i <= numIndSub; i++) { + if (Math.ceil(bitstream.pos / 8) + 3 > data.length) break; + const fscod = bitstream.readBits(2); + const bsid = bitstream.readBits(5); + bitstream.skipBits(1); + bitstream.skipBits(1); + const bsmod = bitstream.readBits(3); + const acmod = bitstream.readBits(3); + const lfeon = bitstream.readBits(1); + bitstream.skipBits(3); + const numDepSub = bitstream.readBits(4); + let chanLoc = 0; + if (numDepSub > 0) chanLoc = bitstream.readBits(9); + else bitstream.skipBits(1); + substreams.push({ + fscod, + fscod2: null, + bsid, + bsmod, + acmod, + lfeon, + numDepSub, + chanLoc + }); + } + if (substreams.length === 0) return null; + return { + dataRate, + substreams + }; +}; +/** +* Get sample rate from E-AC-3 config. +* See ATSC A/52:2018 for handling fscod2. +*/ +var getEac3SampleRate = (config) => { + const sub = config.substreams[0]; + assert(sub); + if (sub.fscod < 3) return AC3_SAMPLE_RATES[sub.fscod]; + else if (sub.fscod2 !== null && sub.fscod2 < 3) return EAC3_REDUCED_SAMPLE_RATES[sub.fscod2]; + return null; +}; +/** +* Get channel count from E-AC-3 config (first independent substream only). +*/ +var getEac3ChannelCount = (config) => { + const sub = config.substreams[0]; + assert(sub); + let channels = AC3_ACMOD_CHANNEL_COUNTS[sub.acmod] + sub.lfeon; + if (sub.numDepSub > 0) { + const CHAN_LOC_COUNTS = [ + 2, + 2, + 1, + 1, + 2, + 2, + 2, + 1, + 1 + ]; + for (let bit = 0; bit < 9; bit++) if (sub.chanLoc & 1 << 8 - bit) channels += CHAN_LOC_COUNTS[bit]; + } + return channels; +}; +/** The largest nuExtSSHeaderSize can get, and therefore how far the asset descriptors can reach. */ +var DTS_EXSS_MAX_HEADER_SIZE = 4096; +/** Number of PCM blocks that a core frame's length must be a multiple of. */ +var DTS_SUBBAND_SAMPLES = 8; +/** Core sample rates indexed by SFREQ. Zeroes mark invalid codes. Table 5-4 */ +var DTS_CORE_SAMPLE_RATES = [ + 0, + 8e3, + 16e3, + 32e3, + 0, + 0, + 11025, + 22050, + 44100, + 0, + 0, + 12e3, + 24e3, + 48e3, + 96e3, + 192e3 +]; +/** +* Core bit rates in bps indexed by RATE, where a zero means the code isn't a constant rate. Table 5-7 +* +* Note that FFmpeg's ff_dca_bit_rates has 896000 where the spec has 960, and defines rates for codes 25 to 28 +* which this revision of the spec calls invalid. We keep the latter, since they cost nothing and some content +* predating the spec revision uses them. +*/ +var DTS_CORE_BIT_RATES = [ + 32e3, + 56e3, + 64e3, + 96e3, + 112e3, + 128e3, + 192e3, + 224e3, + 256e3, + 32e4, + 384e3, + 448e3, + 512e3, + 576e3, + 64e4, + 768e3, + 96e4, + 1024e3, + 1152e3, + 128e4, + 1344e3, + 1408e3, + 1411200, + 1472e3, + 1536e3, + 192e4, + 2048e3, + 3072e3, + 384e4, + 0, + 0, + 0 +]; +/** Source PCM resolutions in bits indexed by PCMR. Zeroes mark invalid codes. */ +var DTS_PCM_RESOLUTIONS = [ + 16, + 16, + 20, + 20, + 0, + 24, + 24, + 0 +]; +/** Channel counts indexed by AMODE, not counting LFE. */ +var DTS_AMODE_CHANNEL_COUNTS = [ + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 6, + 6, + 6, + 7, + 8, + 8 +]; +/** +* Speaker layout masks indexed by AMODE, expressed with the same bits as `ChannelLayout` in the DTSSpecificBox +* and `nuSpkrActivityMask` in an extension substream asset descriptor. +*/ +var DTS_AMODE_CHANNEL_LAYOUTS = [ + 1, + 2, + 2, + 2, + 2, + 3, + 18, + 19, + 6, + 7, + 518, + 323, + 83, + 519, + 582, + 535 +]; +/** The LFE1 speaker bit in a channel layout mask. */ +var DTS_CHANNEL_LAYOUT_LFE1 = 8; +/** The channel layout bits that stand for a pair of speakers rather than a single one. */ +var DTS_CHANNEL_LAYOUT_PAIR_MASK = 44646; +/** Reference clock rates indexed by nuRefClockCode. The last code is unused. Table 7-3 */ +var DTS_EXSS_REF_CLOCKS = [ + 32e3, + 44100, + 48e3, + 0 +]; +/** Sample rates used by extension substream assets, indexed by nuMaxSampleRate. */ +var DTS_EXSS_SAMPLE_RATES = [ + 8e3, + 16e3, + 32e3, + 64e3, + 128e3, + 22050, + 44100, + 88200, + 176400, + 352800, + 12e3, + 24e3, + 48e3, + 96e3, + 192e3, + 384e3 +]; +/** Frame durations that the DTSSpecificBox can express, indexed by FrameDuration. */ +var DTS_SPECIFIC_BOX_FRAME_DURATIONS = [ + 512, + 1024, + 2048, + 4096 +]; +/** +* Parse one complete DTS frame, being a core substream frame followed by any number of extension substreams, +* or an extension substream on its own. Section 5 and Section 7.4.1 +*/ +var parseDtsFrame = (data) => { + const core = parseDtsCoreFrameHeader(data); + const view = toDataView(data); + let offset = core ? Math.ceil(core.frameSize / 4) * 4 : 0; + let firstExss = null; + while (offset + 4 <= data.length && view.getUint32(offset) === 1683496997) { + const exss = parseDtsExssHeader(data.subarray(offset)); + if (!exss) break; + firstExss ??= exss; + offset += exss.frameSize; + } + if (core) return { + frameSize: firstExss ? offset : core.frameSize, + sampleRate: core.sampleRate, + numberOfChannels: core.numberOfChannels, + sampleCount: core.sampleCount, + channelLayout: core.channelLayout, + pcmResolution: core.pcmResolution, + bitRate: core.bitRate, + core, + hasExtensions: firstExss !== null + }; + if (!firstExss?.asset) return null; + const { asset } = firstExss; + return { + frameSize: offset, + sampleRate: asset.sampleRate, + numberOfChannels: asset.numberOfChannels, + sampleCount: asset.sampleCount, + channelLayout: asset.channelLayout, + pcmResolution: asset.pcmResolution, + bitRate: 0, + core: null, + hasExtensions: true + }; +}; +/** +* Works out which four-character code describes a packet, or null when the packet doesn't say. Telling 'dtsl' +* from 'dtse' would mean working out whether the asset holds XLL or LBR data, which sits behind the speaker +* remapping and mixing metadata deep in the asset descriptor, so we don't do it. +*/ +var extractDtsFourCcFromPacket = (data) => { + const frameInfo = parseDtsFrame(data); + if (!frameInfo?.core) return null; + return frameInfo.hasExtensions ? "dtsh" : "dtsc"; +}; +/** Parse the header of a core substream frame. Section 5.3 */ +var parseDtsCoreFrameHeader = (data) => { + if (data.length < 18) return null; + if (data[0] !== 127 || data[1] !== 254 || data[2] !== 128 || data[3] !== 1) return null; + const bitstream = new Bitstream(data); + bitstream.skipBits(32); + bitstream.skipBits(1); + if (bitstream.readBits(5) !== 31) return null; + const cpf = bitstream.readBits(1); + const npcmblocks = bitstream.readBits(7) + 1; + if (npcmblocks % DTS_SUBBAND_SAMPLES !== 0) return null; + const frameSize = bitstream.readBits(14) + 1; + if (frameSize < 96) return null; + const amode = bitstream.readBits(6); + if (amode >= DTS_AMODE_CHANNEL_COUNTS.length) return null; + const sampleRate = DTS_CORE_SAMPLE_RATES[bitstream.readBits(4)]; + if (sampleRate === 0) return null; + const bitRate = DTS_CORE_BIT_RATES[bitstream.readBits(5)]; + if (bitstream.readBits(1) !== 0) return null; + bitstream.skipBits(4); + bitstream.skipBits(5); + const lff = bitstream.readBits(2); + if (lff === 3) return null; + bitstream.skipBits(1); + if (cpf) bitstream.skipBits(16); + bitstream.skipBits(7); + const pcmResolution = DTS_PCM_RESOLUTIONS[bitstream.readBits(3)]; + if (pcmResolution === 0) return null; + const lfePresent = lff !== 0; + return { + frameSize, + sampleRate, + numberOfChannels: DTS_AMODE_CHANNEL_COUNTS[amode] + (lfePresent ? 1 : 0), + sampleCount: npcmblocks * 32, + channelLayout: DTS_AMODE_CHANNEL_LAYOUTS[amode] | (lfePresent ? DTS_CHANNEL_LAYOUT_LFE1 : 0), + amode, + lfePresent, + bitRate, + pcmResolution + }; +}; +/** Parse the header of an extension substream, along with its first audio asset descriptor. Section 7.4.1 */ +var parseDtsExssHeader = (data) => { + if (data.length < 10) return null; + if (data[0] !== 100 || data[1] !== 88 || data[2] !== 32 || data[3] !== 37) return null; + const bitstream = new Bitstream(data); + bitstream.skipBits(32); + bitstream.skipBits(8); + const extSsIndex = bitstream.readBits(2); + const wideHeader = bitstream.readBits(1); + const headerSizeBits = 8 + 4 * wideHeader; + const frameSizeBits = 16 + 4 * wideHeader; + bitstream.skipBits(headerSizeBits); + const frameSize = bitstream.readBits(frameSizeBits) + 1; + const incomplete = { + frameSize, + asset: null + }; + if (!bitstream.readBits(1)) return incomplete; + const refClock = DTS_EXSS_REF_CLOCKS[bitstream.readBits(2)]; + const frameDurationCycles = 512 * (bitstream.readBits(3) + 1); + if (bitstream.readBits(1)) bitstream.skipBits(36); + const numAudioPresentations = bitstream.readBits(3) + 1; + const numAssets = bitstream.readBits(3) + 1; + const activeExssMasks = []; + for (let i = 0; i < numAudioPresentations; i++) activeExssMasks.push(bitstream.readBits(extSsIndex + 1)); + for (const mask of activeExssMasks) bitstream.skipBits(8 * popcount(mask)); + if (bitstream.readBits(1)) { + bitstream.skipBits(2); + const spkrMaskBits = bitstream.readBits(2) + 1 << 2; + const numMixOutConfigs = bitstream.readBits(2) + 1; + bitstream.skipBits(numMixOutConfigs * spkrMaskBits); + } + for (let i = 0; i < numAssets; i++) bitstream.skipBits(frameSizeBits); + bitstream.skipBits(9); + bitstream.skipBits(3); + if (bitstream.readBits(1)) bitstream.skipBits(4); + if (bitstream.readBits(1)) bitstream.skipBits(24); + if (bitstream.readBits(1)) bitstream.skipBits(8 * (bitstream.readBits(10) + 1)); + const pcmResolution = bitstream.readBits(5) + 1; + const sampleRate = DTS_EXSS_SAMPLE_RATES[bitstream.readBits(4)]; + const numberOfChannels = bitstream.readBits(8) + 1; + let channelLayout = 0; + if (bitstream.readBits(1)) { + if (numberOfChannels > 2) bitstream.skipBits(1); + if (numberOfChannels > 6) bitstream.skipBits(1); + if (bitstream.readBits(1)) { + const spkrMaskBits = bitstream.readBits(2) + 1 << 2; + channelLayout = bitstream.readBits(spkrMaskBits); + } + } + if (refClock === 0 || bitstream.getBitsLeft() < 0) return incomplete; + return { + frameSize, + asset: { + sampleRate, + numberOfChannels, + sampleCount: Math.round(frameDurationCycles * sampleRate / refClock), + channelLayout, + pcmResolution + } + }; +}; +/** Parse a DTSSpecificBox (ddts). */ +var parseDtsSpecificBox = (data) => { + if (data.length < 20) return null; + const view = toDataView(data); + const sampleRate = view.getUint32(0); + if (sampleRate === 0) return null; + const bitstream = new Bitstream(data); + bitstream.seekToByte(13); + const frameDuration = bitstream.readBits(2); + bitstream.skipBits(5); + const coreLfePresent = bitstream.readBits(1); + const coreLayout = bitstream.readBits(6); + bitstream.skipBits(14); + bitstream.skipBits(1); + bitstream.skipBits(3); + const channelLayout = bitstream.readBits(16); + let numberOfChannels = null; + if (channelLayout !== 0) numberOfChannels = getDtsChannelCount(channelLayout); + else if (coreLayout < DTS_AMODE_CHANNEL_COUNTS.length) numberOfChannels = DTS_AMODE_CHANNEL_COUNTS[coreLayout] + coreLfePresent; + return { + sampleRate, + maxBitrate: view.getUint32(4), + avgBitrate: view.getUint32(8), + pcmSampleDepth: data[12], + sampleCount: DTS_SPECIFIC_BOX_FRAME_DURATIONS[frameDuration], + channelLayout, + numberOfChannels + }; +}; +/** Build the payload of a DTSSpecificBox (ddts) from a frame of the stream it describes. */ +var buildDtsSpecificBox = (frameInfo) => { + const bytes = new Uint8Array(20); + const view = toDataView(bytes); + view.setUint32(0, frameInfo.sampleRate); + view.setUint32(4, frameInfo.bitRate); + view.setUint32(8, frameInfo.bitRate); + bytes[12] = frameInfo.pcmResolution; + const streamConstruction = frameInfo.core && !frameInfo.hasExtensions ? 1 : 0; + const bitstream = new Bitstream(bytes); + bitstream.seekToByte(13); + bitstream.writeBits(2, Math.max(DTS_SPECIFIC_BOX_FRAME_DURATIONS.indexOf(frameInfo.sampleCount), 0)); + bitstream.writeBits(5, streamConstruction); + bitstream.writeBits(1, frameInfo.core?.lfePresent ? 1 : 0); + bitstream.writeBits(6, frameInfo.core?.amode ?? 0); + bitstream.writeBits(14, frameInfo.core ? frameInfo.core.frameSize - 1 : 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(3, 0); + bitstream.writeBits(16, frameInfo.channelLayout); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(5, 0); + return bytes; +}; +/** Count the channels in a DTS speaker layout mask, where some bits stand for a pair of speakers. */ +var getDtsChannelCount = (channelLayout) => { + return popcount(channelLayout) + popcount(channelLayout & DTS_CHANNEL_LAYOUT_PAIR_MASK); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/codec.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* List of known video codecs, ordered by encoding preference. +* @group Codecs +* @public +*/ +var VIDEO_CODECS = [ + "avc", + "hevc", + "vp9", + "av1", + "vp8", + "prores" +]; +/** +* List of known PCM (uncompressed) audio codecs, ordered by encoding preference. +* @group Codecs +* @public +*/ +var PCM_AUDIO_CODECS = [ + "pcm-s16", + "pcm-s16be", + "pcm-s24", + "pcm-s24be", + "pcm-s32", + "pcm-s32be", + "pcm-f32", + "pcm-f32be", + "pcm-f64", + "pcm-f64be", + "pcm-u8", + "pcm-s8", + "ulaw", + "alaw" +]; +/** +* List of known compressed audio codecs, ordered by encoding preference. +* @group Codecs +* @public +*/ +var NON_PCM_AUDIO_CODECS = [ + "aac", + "opus", + "mp3", + "vorbis", + "flac", + "ac3", + "eac3", + "dts" +]; +/** +* List of known audio codecs, ordered by encoding preference. +* @group Codecs +* @public +*/ +var AUDIO_CODECS = [...NON_PCM_AUDIO_CODECS, ...PCM_AUDIO_CODECS]; +/** +* List of known subtitle codecs, ordered by encoding preference. +* @group Codecs +* @public +*/ +var SUBTITLE_CODECS = ["webvtt"]; +var AVC_LEVEL_TABLE = [ + { + maxMacroblocks: 99, + maxBitrate: 64e3, + maxDpbMbs: 396, + level: 10 + }, + { + maxMacroblocks: 396, + maxBitrate: 192e3, + maxDpbMbs: 900, + level: 11 + }, + { + maxMacroblocks: 396, + maxBitrate: 384e3, + maxDpbMbs: 2376, + level: 12 + }, + { + maxMacroblocks: 396, + maxBitrate: 768e3, + maxDpbMbs: 2376, + level: 13 + }, + { + maxMacroblocks: 396, + maxBitrate: 2e6, + maxDpbMbs: 2376, + level: 20 + }, + { + maxMacroblocks: 792, + maxBitrate: 4e6, + maxDpbMbs: 4752, + level: 21 + }, + { + maxMacroblocks: 1620, + maxBitrate: 4e6, + maxDpbMbs: 8100, + level: 22 + }, + { + maxMacroblocks: 1620, + maxBitrate: 1e7, + maxDpbMbs: 8100, + level: 30 + }, + { + maxMacroblocks: 3600, + maxBitrate: 14e6, + maxDpbMbs: 18e3, + level: 31 + }, + { + maxMacroblocks: 5120, + maxBitrate: 2e7, + maxDpbMbs: 20480, + level: 32 + }, + { + maxMacroblocks: 8192, + maxBitrate: 2e7, + maxDpbMbs: 32768, + level: 40 + }, + { + maxMacroblocks: 8192, + maxBitrate: 5e7, + maxDpbMbs: 32768, + level: 41 + }, + { + maxMacroblocks: 8704, + maxBitrate: 5e7, + maxDpbMbs: 34816, + level: 42 + }, + { + maxMacroblocks: 22080, + maxBitrate: 135e6, + maxDpbMbs: 110400, + level: 50 + }, + { + maxMacroblocks: 36864, + maxBitrate: 24e7, + maxDpbMbs: 184320, + level: 51 + }, + { + maxMacroblocks: 36864, + maxBitrate: 24e7, + maxDpbMbs: 184320, + level: 52 + }, + { + maxMacroblocks: 139264, + maxBitrate: 24e7, + maxDpbMbs: 696320, + level: 60 + }, + { + maxMacroblocks: 139264, + maxBitrate: 48e7, + maxDpbMbs: 696320, + level: 61 + }, + { + maxMacroblocks: 139264, + maxBitrate: 8e8, + maxDpbMbs: 696320, + level: 62 + } +]; +var HEVC_LEVEL_TABLE = [ + { + maxPictureSize: 36864, + maxBitrate: 128e3, + tier: "L", + level: 30 + }, + { + maxPictureSize: 122880, + maxBitrate: 15e5, + tier: "L", + level: 60 + }, + { + maxPictureSize: 245760, + maxBitrate: 3e6, + tier: "L", + level: 63 + }, + { + maxPictureSize: 552960, + maxBitrate: 6e6, + tier: "L", + level: 90 + }, + { + maxPictureSize: 983040, + maxBitrate: 1e7, + tier: "L", + level: 93 + }, + { + maxPictureSize: 2228224, + maxBitrate: 12e6, + tier: "L", + level: 120 + }, + { + maxPictureSize: 2228224, + maxBitrate: 3e7, + tier: "H", + level: 120 + }, + { + maxPictureSize: 2228224, + maxBitrate: 2e7, + tier: "L", + level: 123 + }, + { + maxPictureSize: 2228224, + maxBitrate: 5e7, + tier: "H", + level: 123 + }, + { + maxPictureSize: 8912896, + maxBitrate: 25e6, + tier: "L", + level: 150 + }, + { + maxPictureSize: 8912896, + maxBitrate: 1e8, + tier: "H", + level: 150 + }, + { + maxPictureSize: 8912896, + maxBitrate: 4e7, + tier: "L", + level: 153 + }, + { + maxPictureSize: 8912896, + maxBitrate: 16e7, + tier: "H", + level: 153 + }, + { + maxPictureSize: 8912896, + maxBitrate: 6e7, + tier: "L", + level: 156 + }, + { + maxPictureSize: 8912896, + maxBitrate: 24e7, + tier: "H", + level: 156 + }, + { + maxPictureSize: 35651584, + maxBitrate: 6e7, + tier: "L", + level: 180 + }, + { + maxPictureSize: 35651584, + maxBitrate: 24e7, + tier: "H", + level: 180 + }, + { + maxPictureSize: 35651584, + maxBitrate: 12e7, + tier: "L", + level: 183 + }, + { + maxPictureSize: 35651584, + maxBitrate: 48e7, + tier: "H", + level: 183 + }, + { + maxPictureSize: 35651584, + maxBitrate: 24e7, + tier: "L", + level: 186 + }, + { + maxPictureSize: 35651584, + maxBitrate: 8e8, + tier: "H", + level: 186 + } +]; +var VP9_LEVEL_TABLE = [ + { + maxPictureSize: 36864, + maxBitrate: 2e5, + level: 10 + }, + { + maxPictureSize: 73728, + maxBitrate: 8e5, + level: 11 + }, + { + maxPictureSize: 122880, + maxBitrate: 18e5, + level: 20 + }, + { + maxPictureSize: 245760, + maxBitrate: 36e5, + level: 21 + }, + { + maxPictureSize: 552960, + maxBitrate: 72e5, + level: 30 + }, + { + maxPictureSize: 983040, + maxBitrate: 12e6, + level: 31 + }, + { + maxPictureSize: 2228224, + maxBitrate: 18e6, + level: 40 + }, + { + maxPictureSize: 2228224, + maxBitrate: 3e7, + level: 41 + }, + { + maxPictureSize: 8912896, + maxBitrate: 6e7, + level: 50 + }, + { + maxPictureSize: 8912896, + maxBitrate: 12e7, + level: 51 + }, + { + maxPictureSize: 8912896, + maxBitrate: 18e7, + level: 52 + }, + { + maxPictureSize: 35651584, + maxBitrate: 18e7, + level: 60 + }, + { + maxPictureSize: 35651584, + maxBitrate: 24e7, + level: 61 + }, + { + maxPictureSize: 35651584, + maxBitrate: 48e7, + level: 62 + } +]; +var AV1_LEVEL_TABLE = [ + { + maxPictureSize: 147456, + maxBitrate: 15e5, + tier: "M", + level: 0 + }, + { + maxPictureSize: 278784, + maxBitrate: 3e6, + tier: "M", + level: 1 + }, + { + maxPictureSize: 665856, + maxBitrate: 6e6, + tier: "M", + level: 4 + }, + { + maxPictureSize: 1065024, + maxBitrate: 1e7, + tier: "M", + level: 5 + }, + { + maxPictureSize: 2359296, + maxBitrate: 12e6, + tier: "M", + level: 8 + }, + { + maxPictureSize: 2359296, + maxBitrate: 3e7, + tier: "H", + level: 8 + }, + { + maxPictureSize: 2359296, + maxBitrate: 2e7, + tier: "M", + level: 9 + }, + { + maxPictureSize: 2359296, + maxBitrate: 5e7, + tier: "H", + level: 9 + }, + { + maxPictureSize: 8912896, + maxBitrate: 3e7, + tier: "M", + level: 12 + }, + { + maxPictureSize: 8912896, + maxBitrate: 1e8, + tier: "H", + level: 12 + }, + { + maxPictureSize: 8912896, + maxBitrate: 4e7, + tier: "M", + level: 13 + }, + { + maxPictureSize: 8912896, + maxBitrate: 16e7, + tier: "H", + level: 13 + }, + { + maxPictureSize: 8912896, + maxBitrate: 6e7, + tier: "M", + level: 14 + }, + { + maxPictureSize: 8912896, + maxBitrate: 24e7, + tier: "H", + level: 14 + }, + { + maxPictureSize: 35651584, + maxBitrate: 6e7, + tier: "M", + level: 15 + }, + { + maxPictureSize: 35651584, + maxBitrate: 24e7, + tier: "H", + level: 15 + }, + { + maxPictureSize: 35651584, + maxBitrate: 6e7, + tier: "M", + level: 16 + }, + { + maxPictureSize: 35651584, + maxBitrate: 24e7, + tier: "H", + level: 16 + }, + { + maxPictureSize: 35651584, + maxBitrate: 1e8, + tier: "M", + level: 17 + }, + { + maxPictureSize: 35651584, + maxBitrate: 48e7, + tier: "H", + level: 17 + }, + { + maxPictureSize: 35651584, + maxBitrate: 16e7, + tier: "M", + level: 18 + }, + { + maxPictureSize: 35651584, + maxBitrate: 8e8, + tier: "H", + level: 18 + }, + { + maxPictureSize: 35651584, + maxBitrate: 16e7, + tier: "M", + level: 19 + }, + { + maxPictureSize: 35651584, + maxBitrate: 8e8, + tier: "H", + level: 19 + } +]; +var VP9_DEFAULT_SUFFIX = ".01.01.01.01.00"; +var AV1_DEFAULT_SUFFIX = ".0.110.01.01.01.0"; +var PRORES_FOURCCS = [ + "ap4x", + "ap4h", + "apch", + "apcn", + "apcs", + "apco" +]; +var DTS_FOURCCS = [ + "dtsc", + "dtsh", + "dtsl", + "dtse" +]; +var PRORES_PROFILE_TARGET_BITRATES = [ + { + fourCc: "apco", + bitrate: 45e6, + alpha: false + }, + { + fourCc: "apcs", + bitrate: 102e6, + alpha: false + }, + { + fourCc: "apcn", + bitrate: 147e6, + alpha: false + }, + { + fourCc: "apch", + bitrate: 22e7, + alpha: false + }, + { + fourCc: "ap4h", + bitrate: 33e7, + alpha: true + }, + { + fourCc: "ap4x", + bitrate: 5e8, + alpha: true + } +]; +var buildVideoCodecString = (codec, width, height, bitrate, alpha) => { + if (codec === "avc") { + const profileIndication = 100; + const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16); + const levelInfo = AVC_LEVEL_TABLE.find((level) => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate) ?? last(AVC_LEVEL_TABLE); + const levelIndication = levelInfo ? levelInfo.level : 0; + return `avc1.${profileIndication.toString(16).padStart(2, "0")}00${levelIndication.toString(16).padStart(2, "0")}`; + } else if (codec === "hevc") { + const profilePrefix = ""; + const profileIdc = 1; + const compatibilityFlags = "6"; + const pictureSize = width * height; + const levelInfo = HEVC_LEVEL_TABLE.find((level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(HEVC_LEVEL_TABLE); + return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.B0`; + } else if (codec === "vp8") return "vp8"; + else if (codec === "vp9") { + const profile = "00"; + const pictureSize = width * height; + return `vp09.${profile}.${(VP9_LEVEL_TABLE.find((level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(VP9_LEVEL_TABLE)).level.toString().padStart(2, "0")}.08`; + } else if (codec === "av1") { + const profile = 0; + const pictureSize = width * height; + const levelInfo = AV1_LEVEL_TABLE.find((level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(AV1_LEVEL_TABLE); + return `av01.${profile}.${levelInfo.level.toString().padStart(2, "0")}${levelInfo.tier}.08`; + } else if (codec === "prores") { + const scaleFactor = Math.pow(width * height / (1920 * 1080), .95); + const candidates = PRORES_PROFILE_TARGET_BITRATES.filter((x) => x.alpha === alpha); + let bestFourCc = candidates[0].fourCc; + let smallestDifference = Infinity; + for (const { fourCc, bitrate: targetBitrate } of candidates) { + const difference = Math.abs(targetBitrate * scaleFactor - bitrate); + if (difference < smallestDifference) { + smallestDifference = difference; + bestFourCc = fourCc; + } + } + return bestFourCc; + } else assertNever(codec); + throw new TypeError(`Unhandled codec '${String(codec)}'.`); +}; +var generateVp9CodecConfigurationFromCodecString = (codecString) => { + const parts = codecString.split("."); + return [ + 1, + 1, + Number(parts[1]), + 2, + 1, + Number(parts[2]), + 3, + 1, + Number(parts[3]), + 4, + 1, + parts[4] ? Number(parts[4]) : 1 + ]; +}; +var generateAv1CodecConfigurationFromCodecString = (codecString) => { + const parts = codecString.split("."); + const firstByte = 129; + const profile = Number(parts[1]); + const levelAndTier = parts[2]; + const level = Number(levelAndTier.slice(0, -1)); + const secondByte = (profile << 5) + level; + const tier = levelAndTier.slice(-1) === "H" ? 1 : 0; + const bitDepth = Number(parts[3]); + const highBitDepth = bitDepth === 8 ? 0 : 1; + const twelveBit = bitDepth === 12 ? 1 : 0; + const monochrome = parts[4] ? Number(parts[4]) : 0; + const chromaSubsamplingX = parts[5] ? Number(parts[5][0]) : 1; + const chromaSubsamplingY = parts[5] ? Number(parts[5][1]) : 1; + const chromaSamplePosition = parts[5] ? Number(parts[5][2]) : 0; + return [ + firstByte, + secondByte, + (tier << 7) + (highBitDepth << 6) + (twelveBit << 5) + (monochrome << 4) + (chromaSubsamplingX << 3) + (chromaSubsamplingY << 2) + chromaSamplePosition, + 0 + ]; +}; +var extractVideoCodecString = (trackInfo) => { + const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo, proresFormat } = trackInfo; + if (codec === "avc") { + assert(trackInfo.avcType !== null); + if (avcCodecInfo) { + const bytes = new Uint8Array([ + avcCodecInfo.avcProfileIndication, + avcCodecInfo.profileCompatibility, + avcCodecInfo.avcLevelIndication + ]); + return `avc${trackInfo.avcType}.${bytesToHexString(bytes)}`; + } + if (!codecDescription || codecDescription.byteLength < 4) throw new TypeError("AVC decoder description is not provided or is not at least 4 bytes long."); + return `avc${trackInfo.avcType}.${bytesToHexString(codecDescription.subarray(1, 4))}`; + } else if (codec === "hevc") { + let generalProfileSpace; + let generalProfileIdc; + let compatibilityFlags; + let generalTierFlag; + let generalLevelIdc; + let constraintFlags; + if (hevcCodecInfo) { + generalProfileSpace = hevcCodecInfo.generalProfileSpace; + generalProfileIdc = hevcCodecInfo.generalProfileIdc; + compatibilityFlags = reverseBitsU32(hevcCodecInfo.generalProfileCompatibilityFlags); + generalTierFlag = hevcCodecInfo.generalTierFlag; + generalLevelIdc = hevcCodecInfo.generalLevelIdc; + constraintFlags = [...hevcCodecInfo.generalConstraintIndicatorFlags]; + } else { + if (!codecDescription || codecDescription.byteLength < 23) throw new TypeError("HEVC decoder description is not provided or is not at least 23 bytes long."); + const view = toDataView(codecDescription); + const profileByte = view.getUint8(1); + generalProfileSpace = profileByte >> 6 & 3; + generalProfileIdc = profileByte & 31; + compatibilityFlags = reverseBitsU32(view.getUint32(2)); + generalTierFlag = profileByte >> 5 & 1; + generalLevelIdc = view.getUint8(12); + constraintFlags = []; + for (let i = 0; i < 6; i++) constraintFlags.push(view.getUint8(6 + i)); + } + let codecString = "hev1."; + codecString += [ + "", + "A", + "B", + "C" + ][generalProfileSpace] + generalProfileIdc; + codecString += "."; + codecString += compatibilityFlags.toString(16).toUpperCase(); + codecString += "."; + codecString += generalTierFlag === 0 ? "L" : "H"; + codecString += generalLevelIdc; + while (constraintFlags.length > 0 && constraintFlags[constraintFlags.length - 1] === 0) constraintFlags.pop(); + if (constraintFlags.length > 0) { + codecString += "."; + codecString += constraintFlags.map((x) => x.toString(16).toUpperCase()).join("."); + } + return codecString; + } else if (codec === "vp8") return "vp8"; + else if (codec === "vp9") { + if (!vp9CodecInfo) { + const pictureSize = trackInfo.width * trackInfo.height; + let level = last(VP9_LEVEL_TABLE).level; + for (const entry of VP9_LEVEL_TABLE) if (pictureSize <= entry.maxPictureSize) { + level = entry.level; + break; + } + return `vp09.00.${level.toString().padStart(2, "0")}.08`; + } + const profile = vp9CodecInfo.profile.toString().padStart(2, "0"); + const level = vp9CodecInfo.level.toString().padStart(2, "0"); + const bitDepth = vp9CodecInfo.bitDepth.toString().padStart(2, "0"); + const chromaSubsampling = vp9CodecInfo.chromaSubsampling.toString().padStart(2, "0"); + const colourPrimaries = vp9CodecInfo.colourPrimaries.toString().padStart(2, "0"); + const transferCharacteristics = vp9CodecInfo.transferCharacteristics.toString().padStart(2, "0"); + const matrixCoefficients = vp9CodecInfo.matrixCoefficients.toString().padStart(2, "0"); + const videoFullRangeFlag = vp9CodecInfo.videoFullRangeFlag.toString().padStart(2, "0"); + let string = `vp09.${profile}.${level}.${bitDepth}.${chromaSubsampling}`; + string += `.${colourPrimaries}.${transferCharacteristics}.${matrixCoefficients}.${videoFullRangeFlag}`; + if (string.endsWith(VP9_DEFAULT_SUFFIX)) string = string.slice(0, -15); + return string; + } else if (codec === "av1") { + if (!av1CodecInfo) { + const pictureSize = trackInfo.width * trackInfo.height; + let level = last(VP9_LEVEL_TABLE).level; + for (const entry of VP9_LEVEL_TABLE) if (pictureSize <= entry.maxPictureSize) { + level = entry.level; + break; + } + return `av01.0.${level.toString().padStart(2, "0")}M.08`; + } + const profile = av1CodecInfo.profile; + const level = av1CodecInfo.level.toString().padStart(2, "0"); + const tier = av1CodecInfo.tier ? "H" : "M"; + const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, "0"); + const monochrome = av1CodecInfo.monochrome ? "1" : "0"; + const chromaSubsampling = 100 * av1CodecInfo.chromaSubsamplingX + 10 * av1CodecInfo.chromaSubsamplingY + 1 * (av1CodecInfo.chromaSubsamplingX && av1CodecInfo.chromaSubsamplingY ? av1CodecInfo.chromaSamplePosition : 0); + let colorPrimaries; + let transferCharacteristics; + let matrixCoefficients; + let videoFullRangeFlag; + if (av1CodecInfoHasColorInfo(av1CodecInfo)) { + colorPrimaries = av1CodecInfo.colourPrimaries; + transferCharacteristics = av1CodecInfo.transferCharacteristics; + matrixCoefficients = av1CodecInfo.matrixCoefficients; + videoFullRangeFlag = av1CodecInfo.videoFullRangeFlag; + } else { + colorPrimaries = colorSpace?.primaries ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 1; + transferCharacteristics = colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 1; + matrixCoefficients = colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 1; + videoFullRangeFlag = colorSpace?.fullRange ? 1 : 0; + } + let string = `av01.${profile}.${level}${tier}.${bitDepth}`; + string += `.${monochrome}.${chromaSubsampling.toString().padStart(3, "0")}`; + string += `.${colorPrimaries.toString().padStart(2, "0")}`; + string += `.${transferCharacteristics.toString().padStart(2, "0")}`; + string += `.${matrixCoefficients.toString().padStart(2, "0")}`; + string += `.${videoFullRangeFlag}`; + if (string.endsWith(AV1_DEFAULT_SUFFIX)) string = string.slice(0, -17); + return string; + } else if (codec === "prores") return proresFormat ?? "apch"; + else if (codec !== null) assertNever(codec); + throw new TypeError(`Unhandled codec '${codec}'.`); +}; +var extractColorSpace = (info) => { + switch (info.codec) { + case "avc": + { + let spsData = info.avcCodecInfo?.sequenceParameterSets[0]; + if (!spsData && info.codecDescription) spsData = deserializeAvcDecoderConfigurationRecord(info.codecDescription)?.sequenceParameterSets[0]; + if (spsData) { + const spsInfo = parseAvcSps(spsData); + if (spsInfo) return { + primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[spsInfo.matrixCoefficients], + fullRange: !!spsInfo.fullRangeFlag + }; + } + } + break; + case "hevc": + { + let spsData = info.hevcCodecInfo?.arrays.find((x) => x.nalUnitType === HevcNalUnitType.SPS_NUT)?.nalUnits[0]; + if (!spsData && info.codecDescription) spsData = deserializeHevcDecoderConfigurationRecord(info.codecDescription)?.arrays.find((x) => x.nalUnitType === HevcNalUnitType.SPS_NUT)?.nalUnits[0]; + if (spsData) { + const spsInfo = parseHevcSps(spsData); + if (spsInfo) return { + primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[spsInfo.matrixCoefficients], + fullRange: !!spsInfo.fullRangeFlag + }; + } + } + break; + case "vp8": break; + case "vp9": + if (info.vp9CodecInfo) return { + primaries: COLOR_PRIMARIES_MAP_INVERSE[info.vp9CodecInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[info.vp9CodecInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[info.vp9CodecInfo.matrixCoefficients], + fullRange: !!info.vp9CodecInfo.videoFullRangeFlag + }; + break; + case "av1": + if (info.av1CodecInfo) return { + primaries: COLOR_PRIMARIES_MAP_INVERSE[info.av1CodecInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[info.av1CodecInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[info.av1CodecInfo.matrixCoefficients], + fullRange: !!info.av1CodecInfo.videoFullRangeFlag + }; + break; + case "prores": + if (info.proresCodecInfo) return { + primaries: COLOR_PRIMARIES_MAP_INVERSE[info.proresCodecInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[info.proresCodecInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[info.proresCodecInfo.matrixCoefficients], + fullRange: info.proresCodecInfo.fullRange + }; + break; + } + return { + primaries: void 0, + transfer: void 0, + matrix: void 0, + fullRange: void 0 + }; +}; +var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => { + if (codec === "aac") { + if (numberOfChannels >= 2 && sampleRate <= 24e3) return "mp4a.40.29"; + if (sampleRate <= 24e3) return "mp4a.40.5"; + return "mp4a.40.2"; + } else if (codec === "mp3") return "mp3"; + else if (codec === "opus") return "opus"; + else if (codec === "vorbis") return "vorbis"; + else if (codec === "flac") return "flac"; + else if (codec === "ac3") return "ac-3"; + else if (codec === "eac3") return "ec-3"; + else if (codec === "dts") return "dtsc"; + else if (PCM_AUDIO_CODECS.includes(codec)) return codec; + throw new TypeError(`Unhandled codec '${codec}'.`); +}; +var extractAudioCodecString = (trackInfo) => { + const { codec, codecDescription, aacCodecInfo, dtsFormat } = trackInfo; + if (codec === "aac") { + if (!aacCodecInfo) throw new TypeError("AAC codec info must be provided."); + if (aacCodecInfo.isMpeg2) return "mp4a.67"; + else { + let objectType; + if (aacCodecInfo.objectType !== null) objectType = aacCodecInfo.objectType; + else objectType = parseAacAudioSpecificConfig(codecDescription).objectType; + return `mp4a.40.${objectType}`; + } + } else if (codec === "mp3") return "mp3"; + else if (codec === "opus") return "opus"; + else if (codec === "vorbis") return "vorbis"; + else if (codec === "flac") return "flac"; + else if (codec === "ac3") return "ac-3"; + else if (codec === "eac3") return "ec-3"; + else if (codec === "dts") return dtsFormat ?? "dtsc"; + else if (codec && PCM_AUDIO_CODECS.includes(codec)) return codec; + throw new TypeError(`Unhandled codec '${codec}'.`); +}; +var guessDescriptionForAudio = (decoderConfig) => { + switch (decoderConfig.codec) { + case "flac": { + const referenceDescription = base64ToBytes("ZkxhQ4AAACIQABAAAAYtACWtCsRC8AANRBhVFucAcYu5ASE2m1Dxv8tw"); + if (decoderConfig.sampleRate >= 1 << 20 || decoderConfig.numberOfChannels > 8) return false; + referenceDescription[18] = decoderConfig.sampleRate >>> 12; + referenceDescription[19] = decoderConfig.sampleRate >>> 4; + referenceDescription[20] = (decoderConfig.sampleRate & 15) << 4 | decoderConfig.numberOfChannels - 1 << 1; + return referenceDescription; + } + case "vorbis": { + const referenceDescription = base64ToBytes("Ah7/AgF2b3JiaXMAAAAAAoC7AAAAAAAAgLUBAAAAAAC4AQN2b3JiaXMNAAAATGF2ZjU4Ljc2LjEwMAgAAAAMAAAAbGFuZ3VhZ2U9dW5kGQAAAGhhbmRsZXJfbmFtZT1Tb3VuZEhhbmRsZXIWAAAAdmVuZG9yX2lkPVswXVswXVswXVswXSAAAABlbmNvZGVyPUxhdmM1OC4xMzQuMTAwIGxpYnZvcmJpcxAAAABtYWpvcl9icmFuZD1pc29tEQAAAG1pbm9yX3ZlcnNpb249NTEyIgAAAGNvbXBhdGlibGVfYnJhbmRzPWlzb21pc28yYXZjMW1wNDEmAAAAREVTQ1JJUFRJT049TWFkZSB3aXRoIFJlbW90aW9uIDQuMC4yNzgBBXZvcmJpcyVCQ1YBAEAAACRzGCpGpXMWhBAaQlAZ4xxCzmvsGUJMEYIcMkxbyyVzkCGkoEKIWyiB0JBVAABAAACHQXgUhIpBCCGEJT1YkoMnPQghhIg5eBSEaUEIIYQQQgghhBBCCCGERTlokoMnQQgdhOMwOAyD5Tj4HIRFOVgQgydB6CCED0K4moOsOQghhCQ1SFCDBjnoHITCLCiKgsQwuBaEBDUojILkMMjUgwtCiJqDSTX4GoRnQXgWhGlBCCGEJEFIkIMGQcgYhEZBWJKDBjm4FITLQagahCo5CB+EIDRkFQCQAACgoiiKoigKEBqyCgDIAAAQQFEUx3EcyZEcybEcCwgNWQUAAAEACAAAoEiKpEiO5EiSJFmSJVmSJVmS5omqLMuyLMuyLMsyEBqyCgBIAABQUQxFcRQHCA1ZBQBkAAAIoDiKpViKpWiK54iOCISGrAIAgAAABAAAEDRDUzxHlETPVFXXtm3btm3btm3btm3btm1blmUZCA1ZBQBAAAAQ0mlmqQaIMAMZBkJDVgEACAAAgBGKMMSA0JBVAABAAACAGEoOogmtOd+c46BZDppKsTkdnEi1eZKbirk555xzzsnmnDHOOeecopxZDJoJrTnnnMSgWQqaCa0555wnsXnQmiqtOeeccc7pYJwRxjnnnCateZCajbU555wFrWmOmkuxOeecSLl5UptLtTnnnHPOOeecc84555zqxekcnBPOOeecqL25lpvQxTnnnE/G6d6cEM4555xzzjnnnHPOOeecIDRkFQAABABAEIaNYdwpCNLnaCBGEWIaMulB9+gwCRqDnELq0ehopJQ6CCWVcVJKJwgNWQUAAAIAQAghhRRSSCGFFFJIIYUUYoghhhhyyimnoIJKKqmooowyyyyzzDLLLLPMOuyssw47DDHEEEMrrcRSU2011lhr7jnnmoO0VlprrbVSSimllFIKQkNWAQAgAAAEQgYZZJBRSCGFFGKIKaeccgoqqIDQkFUAACAAgAAAAABP8hzRER3RER3RER3RER3R8RzPESVREiVREi3TMjXTU0VVdWXXlnVZt31b2IVd933d933d+HVhWJZlWZZlWZZlWZZlWZZlWZYgNGQVAAACAAAghBBCSCGFFFJIKcYYc8w56CSUEAgNWQUAAAIACAAAAHAUR3EcyZEcSbIkS9IkzdIsT/M0TxM9URRF0zRV0RVdUTdtUTZl0zVdUzZdVVZtV5ZtW7Z125dl2/d93/d93/d93/d93/d9XQdCQ1YBABIAADqSIymSIimS4ziOJElAaMgqAEAGAEAAAIriKI7jOJIkSZIlaZJneZaomZrpmZ4qqkBoyCoAABAAQAAAAAAAAIqmeIqpeIqoeI7oiJJomZaoqZoryqbsuq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq4LhIasAgAkAAB0JEdyJEdSJEVSJEdygNCQVQCADACAAAAcwzEkRXIsy9I0T/M0TxM90RM901NFV3SB0JBVAAAgAIAAAAAAAAAMybAUy9EcTRIl1VItVVMt1VJF1VNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVN0zRNEwgNWQkAkAEAkBBTLS3GmgmLJGLSaqugYwxS7KWxSCpntbfKMYUYtV4ah5RREHupJGOKQcwtpNApJq3WVEKFFKSYYyoVUg5SIDRkhQAQmgHgcBxAsixAsiwAAAAAAAAAkDQN0DwPsDQPAAAAAAAAACRNAyxPAzTPAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAA0DwP8DwR8EQRAAAAAAAAACzPAzTRAzxRBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAAsDwP8EQR0DwRAAAAAAAAACzPAzxRBDzRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEOAAABBgIRQasiIAiBMAcEgSJAmSBM0DSJYFTYOmwTQBkmVB06BpME0AAAAAAAAAAAAAJE2DpkHTIIoASdOgadA0iCIAAAAAAAAAAAAAkqZB06BpEEWApGnQNGgaRBEAAAAAAAAAAAAAzzQhihBFmCbAM02IIkQRpgkAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrIiAIgTAHA4imUBAIDjOJYFAACO41gWAABYliWKAABgWZooAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAYcAAACDChDBQashIAiAIAcCiKZQHHsSzgOJYFJMmyAJYF0DyApgFEEQAIAAAocAAACLBBU2JxgEJDVgIAUQAABsWxLE0TRZKkaZoniiRJ0zxPFGma53meacLzPM80IYqiaJoQRVE0TZimaaoqME1VFQAAUOAAABBgg6bE4gCFhqwEAEICAByKYlma5nmeJ4qmqZokSdM8TxRF0TRNU1VJkqZ5niiKommapqqyLE3zPFEURdNUVVWFpnmeKIqiaaqq6sLzPE8URdE0VdV14XmeJ4qiaJqq6roQRVE0TdNUTVV1XSCKpmmaqqqqrgtETxRNU1Vd13WB54miaaqqq7ouEE3TVFVVdV1ZBpimaaqq68oyQFVV1XVdV5YBqqqqruu6sgxQVdd1XVmWZQCu67qyLMsCAAAOHAAAAoygk4wqi7DRhAsPQKEhKwKAKAAAwBimFFPKMCYhpBAaxiSEFEImJaXSUqogpFJSKRWEVEoqJaOUUmopVRBSKamUCkIqJZVSAADYgQMA2IGFUGjISgAgDwCAMEYpxhhzTiKkFGPOOScRUoox55yTSjHmnHPOSSkZc8w556SUzjnnnHNSSuacc845KaVzzjnnnJRSSuecc05KKSWEzkEnpZTSOeecEwAAVOAAABBgo8jmBCNBhYasBABSAQAMjmNZmuZ5omialiRpmud5niiapiZJmuZ5nieKqsnzPE8URdE0VZXneZ4oiqJpqirXFUXTNE1VVV2yLIqmaZqq6rowTdNUVdd1XZimaaqq67oubFtVVdV1ZRm2raqq6rqyDFzXdWXZloEsu67s2rIAAPAEBwCgAhtWRzgpGgssNGQlAJABAEAYg5BCCCFlEEIKIYSUUggJAAAYcAAACDChDBQashIASAUAAIyx1lprrbXWQGettdZaa62AzFprrbXWWmuttdZaa6211lJrrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmstpZRSSimllFJKKaWUUkoppZRSSgUA+lU4APg/2LA6wknRWGChISsBgHAAAMAYpRhzDEIppVQIMeacdFRai7FCiDHnJKTUWmzFc85BKCGV1mIsnnMOQikpxVZjUSmEUlJKLbZYi0qho5JSSq3VWIwxqaTWWoutxmKMSSm01FqLMRYjbE2ptdhqq7EYY2sqLbQYY4zFCF9kbC2m2moNxggjWywt1VprMMYY3VuLpbaaizE++NpSLDHWXAAAd4MDAESCjTOsJJ0VjgYXGrISAAgJACAQUooxxhhzzjnnpFKMOeaccw5CCKFUijHGnHMOQgghlIwx5pxzEEIIIYRSSsaccxBCCCGEkFLqnHMQQgghhBBKKZ1zDkIIIYQQQimlgxBCCCGEEEoopaQUQgghhBBCCKmklEIIIYRSQighlZRSCCGEEEIpJaSUUgohhFJCCKGElFJKKYUQQgillJJSSimlEkoJJYQSUikppRRKCCGUUkpKKaVUSgmhhBJKKSWllFJKIYQQSikFAAAcOAAABBhBJxlVFmGjCRcegEJDVgIAZAAAkKKUUiktRYIipRikGEtGFXNQWoqocgxSzalSziDmJJaIMYSUk1Qy5hRCDELqHHVMKQYtlRhCxhik2HJLoXMOAAAAQQCAgJAAAAMEBTMAwOAA4XMQdAIERxsAgCBEZohEw0JweFAJEBFTAUBigkIuAFRYXKRdXECXAS7o4q4DIQQhCEEsDqCABByccMMTb3jCDU7QKSp1IAAAAAAADADwAACQXAAREdHMYWRobHB0eHyAhIiMkAgAAAAAABcAfAAAJCVAREQ0cxgZGhscHR4fICEiIyQBAIAAAgAAAAAggAAEBAQAAAAAAAIAAAAEBA=="); + const view = toDataView(referenceDescription); + view.setUint8(15, decoderConfig.numberOfChannels); + view.setUint32(16, decoderConfig.sampleRate, true); + return referenceDescription; + } + default: return; + } +}; +var OPUS_SAMPLE_RATE = 48e3; +var PCM_CODEC_REGEX = /^pcm-([usf])(\d+)(be)?$/; +var parsePcmCodec = (codec) => { + assert(PCM_AUDIO_CODECS.includes(codec)); + if (codec === "ulaw") return { + dataType: "ulaw", + sampleSize: 1, + littleEndian: true, + silentValue: 255 + }; + else if (codec === "alaw") return { + dataType: "alaw", + sampleSize: 1, + littleEndian: true, + silentValue: 213 + }; + const match = PCM_CODEC_REGEX.exec(codec); + assert(match); + let dataType; + if (match[1] === "u") dataType = "unsigned"; + else if (match[1] === "s") dataType = "signed"; + else dataType = "float"; + const sampleSize = Number(match[2]) / 8; + const littleEndian = match[3] !== "be"; + return { + dataType, + sampleSize, + littleEndian, + silentValue: codec === "pcm-u8" ? 2 ** 7 : 0 + }; +}; +var inferCodecFromCodecString = (codecString) => { + if (codecString.startsWith("avc1") || codecString.startsWith("avc3")) return "avc"; + else if (codecString.startsWith("hev1") || codecString.startsWith("hvc1")) return "hevc"; + else if (codecString === "vp8") return "vp8"; + else if (codecString.startsWith("vp09")) return "vp9"; + else if (codecString.startsWith("av01")) return "av1"; + else if (PRORES_FOURCCS.includes(codecString)) return "prores"; + if (codecString === "mp3" || codecString === "mp4a.69" || codecString === "mp4a.6B" || codecString === "mp4a.6b" || codecString === "mp4a.40.34") return "mp3"; + else if (codecString.startsWith("mp4a.40.") || codecString === "mp4a.67") return "aac"; + else if (codecString === "opus" || codecString === "Opus") return "opus"; + else if (codecString === "vorbis") return "vorbis"; + else if (codecString === "flac") return "flac"; + else if (codecString === "ac-3" || codecString === "ac3") return "ac3"; + else if (codecString === "ec-3" || codecString === "eac3") return "eac3"; + else if (DTS_FOURCCS.includes(codecString)) return "dts"; + else if (codecString === "ulaw") return "ulaw"; + else if (codecString === "alaw") return "alaw"; + else if (PCM_CODEC_REGEX.test(codecString)) return codecString; + if (codecString === "webvtt") return "webvtt"; + return null; +}; +var getVideoEncoderConfigExtension = (codec) => { + if (codec === "avc") return { avc: { format: "avc" } }; + else if (codec === "hevc") return { hevc: { format: "hevc" } }; + return {}; +}; +var getAudioEncoderConfigExtension = (codec) => { + if (codec === "aac") return { aac: { format: "aac" } }; + else if (codec === "opus") return { opus: { format: "opus" } }; + return {}; +}; +var VALID_VIDEO_CODEC_STRING_PREFIXES = [ + "avc1", + "avc3", + "hev1", + "hvc1", + "vp8", + "vp09", + "av01", + ...PRORES_FOURCCS +]; +var AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/; +var HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/; +var VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/; +var AV1_CODEC_STRING_REGEX = /^av01\.\d\.\d{2}[MH]\.\d{2}(?:\.\d\.\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d)?$/; +var validateVideoChunkMetadata = (metadata, trackCodec) => { + if (!metadata) throw new TypeError("Video chunk metadata must be provided."); + if (typeof metadata !== "object") throw new TypeError("Video chunk metadata must be an object."); + if (!metadata.decoderConfig) throw new TypeError("Video chunk metadata must include a decoder configuration."); + if (typeof metadata.decoderConfig !== "object") throw new TypeError("Video chunk metadata decoder configuration must be an object."); + if (typeof metadata.decoderConfig.codec !== "string") throw new TypeError("Video chunk metadata decoder configuration must specify a codec string."); + if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some((prefix) => metadata.decoderConfig.codec.startsWith(prefix))) throw new TypeError("Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in the Mediabunny Codec Registry."); + if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth <= 0) throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)."); + if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight <= 0) throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)."); + if (metadata.decoderConfig.displayAspectWidth !== void 0 && (!Number.isInteger(metadata.decoderConfig.displayAspectWidth) || metadata.decoderConfig.displayAspectWidth <= 0)) throw new TypeError("Video chunk metadata decoder configuration displayAspectWidth, when defined, must be a positive integer."); + if (metadata.decoderConfig.displayAspectHeight !== void 0 && (!Number.isInteger(metadata.decoderConfig.displayAspectHeight) || metadata.decoderConfig.displayAspectHeight <= 0)) throw new TypeError("Video chunk metadata decoder configuration displayAspectHeight, when defined, must be a positive integer."); + if (metadata.decoderConfig.displayAspectWidth !== void 0 !== (metadata.decoderConfig.displayAspectHeight !== void 0)) throw new TypeError("Video chunk metadata decoder configuration must specify both displayAspectWidth and displayAspectHeight, or neither."); + if (metadata.decoderConfig.description !== void 0) { + if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + } + if (metadata.decoderConfig.colorSpace !== void 0) { + const { colorSpace } = metadata.decoderConfig; + if (typeof colorSpace !== "object") throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object."); + const primariesValues = Object.keys(COLOR_PRIMARIES_MAP); + if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(", ")}.`); + const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); + if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(", ")}.`); + const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); + if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(", ")}.`); + if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== "boolean") throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean."); + } + if (metadata.decoderConfig.codec.startsWith("avc1") || metadata.decoderConfig.codec.startsWith("avc3")) { + if (!AVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) throw new TypeError("Video chunk metadata decoder configuration codec string for AVC must be a valid AVC codec string as specified in Section 3.4 of RFC 6381."); + } else if (metadata.decoderConfig.codec.startsWith("hev1") || metadata.decoderConfig.codec.startsWith("hvc1")) { + if (!HEVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) throw new TypeError("Video chunk metadata decoder configuration codec string for HEVC must be a valid HEVC codec string as specified in Section E.3 of ISO 14496-15."); + } else if (metadata.decoderConfig.codec.startsWith("vp8")) { + if (metadata.decoderConfig.codec !== "vp8") throw new TypeError("Video chunk metadata decoder configuration codec string for VP8 must be \"vp8\"."); + } else if (metadata.decoderConfig.codec.startsWith("vp09")) { + if (!VP9_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) throw new TypeError("Video chunk metadata decoder configuration codec string for VP9 must be a valid VP9 codec string as specified in Section \"Codecs Parameter String\" of https://www.webmproject.org/vp9/mp4/."); + } else if (metadata.decoderConfig.codec.startsWith("av01")) { + if (!AV1_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) throw new TypeError("Video chunk metadata decoder configuration codec string for AV1 must be a valid AV1 codec string as specified in Section \"Codecs Parameter String\" of https://aomediacodec.github.io/av1-isobmff/."); + } else if (PRORES_FOURCCS.some((x) => metadata.decoderConfig.codec.startsWith(x))) { + if (!PRORES_FOURCCS.some((x) => metadata.decoderConfig.codec === x)) throw new TypeError(`Video chunk metadata decoder configuration codec string for ProRes must be one of the valid ProRes four-character codes: ${PRORES_FOURCCS.join(", ")}.`); + } + if (trackCodec !== null && inferCodecFromCodecString(metadata.decoderConfig.codec) !== trackCodec) throw new TypeError(`Video chunk metadata decoder configuration codec string '${metadata.decoderConfig.codec}' does not fit to the track codec '${trackCodec}'.`); +}; +var VALID_AUDIO_CODEC_STRING_PREFIXES = [ + "mp4a", + "mp3", + "opus", + "vorbis", + "flac", + "ulaw", + "alaw", + "pcm", + "ac-3", + "ec-3", + "dts" +]; +var validateAudioChunkMetadata = (metadata, trackCodec) => { + if (!metadata) throw new TypeError("Audio chunk metadata must be provided."); + if (typeof metadata !== "object") throw new TypeError("Audio chunk metadata must be an object."); + if (!metadata.decoderConfig) throw new TypeError("Audio chunk metadata must include a decoder configuration."); + if (typeof metadata.decoderConfig !== "object") throw new TypeError("Audio chunk metadata decoder configuration must be an object."); + if (typeof metadata.decoderConfig.codec !== "string") throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string."); + if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some((prefix) => metadata.decoderConfig.codec.startsWith(prefix))) throw new TypeError("Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in the Mediabunny Codec Registry."); + if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)."); + if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)."); + if (metadata.decoderConfig.description !== void 0) { + if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + } + if (metadata.decoderConfig.codec.startsWith("mp4a") && metadata.decoderConfig.codec !== "mp4a.69" && metadata.decoderConfig.codec !== "mp4a.6B" && metadata.decoderConfig.codec !== "mp4a.6b") { + if (![ + "mp4a.40.2", + "mp4a.40.02", + "mp4a.40.5", + "mp4a.40.05", + "mp4a.40.29", + "mp4a.67" + ].includes(metadata.decoderConfig.codec)) throw new TypeError("Audio chunk metadata decoder configuration codec string for AAC must be a valid AAC codec string as specified in https://www.w3.org/TR/webcodecs-aac-codec-registration/."); + } else if (metadata.decoderConfig.codec.startsWith("mp3") || metadata.decoderConfig.codec.startsWith("mp4a")) { + if (metadata.decoderConfig.codec !== "mp3" && metadata.decoderConfig.codec !== "mp4a.69" && metadata.decoderConfig.codec !== "mp4a.6B" && metadata.decoderConfig.codec !== "mp4a.6b") throw new TypeError("Audio chunk metadata decoder configuration codec string for MP3 must be \"mp3\", \"mp4a.69\" or \"mp4a.6B\"."); + } else if (metadata.decoderConfig.codec.startsWith("opus")) { + if (metadata.decoderConfig.codec !== "opus") throw new TypeError("Audio chunk metadata decoder configuration codec string for Opus must be \"opus\"."); + if (metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) throw new TypeError("Audio chunk metadata decoder configuration description, when specified, is expected to be an Identification Header as specified in Section 5.1 of RFC 7845."); + } else if (metadata.decoderConfig.codec.startsWith("vorbis")) { + if (metadata.decoderConfig.codec !== "vorbis") throw new TypeError("Audio chunk metadata decoder configuration codec string for Vorbis must be \"vorbis\"."); + if (!metadata.decoderConfig.description) throw new TypeError("Audio chunk metadata decoder configuration for Vorbis must include a description, which is expected to adhere to the format described in https://www.w3.org/TR/webcodecs-vorbis-codec-registration/."); + } else if (metadata.decoderConfig.codec.startsWith("flac")) { + if (metadata.decoderConfig.codec !== "flac") throw new TypeError("Audio chunk metadata decoder configuration codec string for FLAC must be \"flac\"."); + if (!metadata.decoderConfig.description || metadata.decoderConfig.description.byteLength < 42) throw new TypeError("Audio chunk metadata decoder configuration for FLAC must include a description, which is expected to adhere to the format described in https://www.w3.org/TR/webcodecs-flac-codec-registration/."); + } else if (metadata.decoderConfig.codec.startsWith("ac-3") || metadata.decoderConfig.codec.startsWith("ac3")) { + if (metadata.decoderConfig.codec !== "ac-3") throw new TypeError("Audio chunk metadata decoder configuration codec string for AC-3 must be \"ac-3\"."); + } else if (metadata.decoderConfig.codec.startsWith("ec-3") || metadata.decoderConfig.codec.startsWith("eac3")) { + if (metadata.decoderConfig.codec !== "ec-3") throw new TypeError("Audio chunk metadata decoder configuration codec string for EC-3 must be \"ec-3\"."); + } else if (metadata.decoderConfig.codec.startsWith("dts")) { + if (!DTS_FOURCCS.includes(metadata.decoderConfig.codec)) throw new TypeError(`Audio chunk metadata decoder configuration codec string for DTS must be one of the following four-character codes: ${DTS_FOURCCS.join(", ")}.`); + } else if (metadata.decoderConfig.codec.startsWith("pcm") || metadata.decoderConfig.codec.startsWith("ulaw") || metadata.decoderConfig.codec.startsWith("alaw")) { + if (!PCM_AUDIO_CODECS.includes(metadata.decoderConfig.codec)) throw new TypeError(`Audio chunk metadata decoder configuration codec string for PCM must be one of the supported PCM codecs (${PCM_AUDIO_CODECS.join(", ")}).`); + } + if (trackCodec !== null && inferCodecFromCodecString(metadata.decoderConfig.codec) !== trackCodec) throw new TypeError(`Audio chunk metadata decoder configuration codec string '${metadata.decoderConfig.codec}' does not fit to the track codec '${trackCodec}'.`); +}; +var validateSubtitleMetadata = (metadata) => { + if (!metadata) throw new TypeError("Subtitle metadata must be provided."); + if (typeof metadata !== "object") throw new TypeError("Subtitle metadata must be an object."); + if (!metadata.config) throw new TypeError("Subtitle metadata must include a config object."); + if (typeof metadata.config !== "object") throw new TypeError("Subtitle metadata config must be an object."); + if (typeof metadata.config.description !== "string") throw new TypeError("Subtitle metadata config description must be a string."); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/shared/mp3-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var SAMPLING_RATES = [ + 44100, + 48e3, + 32e3 +]; +var KILOBIT_RATES = [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 160, + 192, + 224, + 256, + 320, + -1, + -1, + 32, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 160, + 192, + 224, + 256, + 320, + 384, + -1, + -1, + 32, + 64, + 96, + 128, + 160, + 192, + 224, + 256, + 288, + 320, + 352, + 384, + 416, + 448, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 8, + 16, + 24, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + -1, + -1, + 8, + 16, + 24, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + -1, + -1, + 32, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 224, + 256, + -1 +]; +/** 'Xing' */ +var XING = 1483304551; +var computeMp3FrameSize = (lowSamplingFrequency, layer, bitrate, sampleRate, padding) => { + if (layer === 0) return 0; + else if (layer === 1) return Math.floor(144 * bitrate / (sampleRate << lowSamplingFrequency)) + padding; + else if (layer === 2) return Math.floor(144 * bitrate / sampleRate) + padding; + else return (Math.floor(12 * bitrate / sampleRate) + padding) * 4; +}; +var computeAverageMp3FrameSize = (lowSamplingFrequency, layer, bitrate, sampleRate) => { + if (layer === 0) return 0; + else if (layer === 1) return 144 * bitrate / (sampleRate << lowSamplingFrequency); + else if (layer === 2) return 144 * bitrate / sampleRate; + else return 12 * bitrate / sampleRate * 4; +}; +var getXingOffset = (mpegVersionId, channel) => { + return mpegVersionId === 3 ? channel === 3 ? 21 : 36 : channel === 3 ? 13 : 21; +}; +var readMp3FrameHeader = (word, remainingBytes) => { + const firstByte = word >>> 24; + const secondByte = word >>> 16 & 255; + const thirdByte = word >>> 8 & 255; + const fourthByte = word & 255; + if (firstByte !== 255 && secondByte !== 255 && thirdByte !== 255 && fourthByte !== 255) return { + header: null, + bytesAdvanced: 4 + }; + if (firstByte !== 255) return { + header: null, + bytesAdvanced: 1 + }; + if ((secondByte & 224) !== 224) return { + header: null, + bytesAdvanced: 1 + }; + let lowSamplingFrequency = 0; + let mpeg25 = 0; + if (secondByte & 16) lowSamplingFrequency = secondByte & 8 ? 0 : 1; + else { + lowSamplingFrequency = 1; + mpeg25 = 1; + } + const mpegVersionId = secondByte >> 3 & 3; + const layer = secondByte >> 1 & 3; + const bitrateIndex = thirdByte >> 4 & 15; + const frequencyIndex = (thirdByte >> 2 & 3) % 3; + const padding = thirdByte >> 1 & 1; + const channel = fourthByte >> 6 & 3; + const modeExtension = fourthByte >> 4 & 3; + const copyright = fourthByte >> 3 & 1; + const original = fourthByte >> 2 & 1; + const emphasis = fourthByte & 3; + const kilobitRate = KILOBIT_RATES[lowSamplingFrequency * 16 * 4 + layer * 16 + bitrateIndex]; + if (kilobitRate === -1) return { + header: null, + bytesAdvanced: 1 + }; + const bitrate = kilobitRate * 1e3; + const sampleRate = SAMPLING_RATES[frequencyIndex] >> lowSamplingFrequency + mpeg25; + const frameLength = computeMp3FrameSize(lowSamplingFrequency, layer, bitrate, sampleRate, padding); + if (remainingBytes !== null && remainingBytes < frameLength) return { + header: null, + bytesAdvanced: 1 + }; + let audioSamplesInFrame; + if (mpegVersionId === 3) audioSamplesInFrame = layer === 3 ? 384 : 1152; + else if (layer === 3) audioSamplesInFrame = 384; + else if (layer === 2) audioSamplesInFrame = 1152; + else audioSamplesInFrame = 576; + return { + header: { + totalSize: frameLength, + mpegVersionId, + lowSamplingFrequency, + layer, + bitrate, + frequencyIndex, + sampleRate, + channel, + modeExtension, + copyright, + original, + emphasis, + audioSamplesInFrame + }, + bytesAdvanced: 1 + }; +}; +var encodeSynchsafe = (unsynchsafed) => { + let mask = 127; + let synchsafed = 0; + let unsynchsafedRest = unsynchsafed; + while ((mask ^ 2147483647) !== 0) { + synchsafed = unsynchsafedRest & ~mask; + synchsafed <<= 1; + synchsafed |= unsynchsafedRest & mask; + mask = (mask + 1 << 8) - 1; + unsynchsafedRest = synchsafed; + } + return synchsafed; +}; +var decodeSynchsafe = (synchsafed) => { + let mask = 2130706432; + let unsynchsafed = 0; + while (mask !== 0) { + unsynchsafed >>= 1; + unsynchsafed |= synchsafed & mask; + mask >>= 8; + } + return unsynchsafed; +}; +var XingFlags; +(function(XingFlags) { + XingFlags[XingFlags["FrameCount"] = 1] = "FrameCount"; + XingFlags[XingFlags["FileSize"] = 2] = "FileSize"; + XingFlags[XingFlags["Toc"] = 4] = "Toc"; +})(XingFlags || (XingFlags = {})); +var getMp3ChannelCount = (channel) => { + return channel === 3 ? 1 : 2; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Demuxer = class { + constructor(input) { + this.input = input; + } + dispose() {} +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/packet.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var PLACEHOLDER_DATA = /* #__PURE__ */ new Uint8Array(0); +/** +* Represents an encoded chunk of media. Mainly used as an expressive wrapper around WebCodecs API's +* [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) and +* [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk), but can also be used +* standalone. +* @group Packets +* @public +*/ +var EncodedPacket = class EncodedPacket { + /** Creates a new {@link EncodedPacket} from raw bytes and timing information. */ + constructor(data, type, timestamp, duration, sequenceNumber = -1, byteLength, sideData) { + this.data = data; + this.type = type; + this.timestamp = timestamp; + this.duration = duration; + this.sequenceNumber = sequenceNumber; + if (data === PLACEHOLDER_DATA && byteLength === void 0) throw new Error("Internal error: byteLength must be explicitly provided when constructing metadata-only packets."); + if (byteLength === void 0) byteLength = data.byteLength; + if (!(data instanceof Uint8Array)) throw new TypeError("data must be a Uint8Array."); + if (type !== "key" && type !== "delta") throw new TypeError("type must be either \"key\" or \"delta\"."); + if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be a number."); + if (!Number.isFinite(duration) || duration < 0) throw new TypeError("duration must be a non-negative number."); + if (!Number.isFinite(sequenceNumber)) throw new TypeError("sequenceNumber must be a number."); + if (!Number.isInteger(byteLength) || byteLength < 0) throw new TypeError("byteLength must be a non-negative integer."); + if (sideData !== void 0 && (typeof sideData !== "object" || !sideData)) throw new TypeError("sideData, when provided, must be an object."); + if (sideData?.alpha !== void 0 && !(sideData.alpha instanceof Uint8Array)) throw new TypeError("sideData.alpha, when provided, must be a Uint8Array."); + if (sideData?.alphaByteLength !== void 0 && (!Number.isInteger(sideData.alphaByteLength) || sideData.alphaByteLength < 0)) throw new TypeError("sideData.alphaByteLength, when provided, must be a non-negative integer."); + this.byteLength = byteLength; + this.sideData = sideData ?? {}; + if (this.sideData.alpha && this.sideData.alphaByteLength === void 0) this.sideData.alphaByteLength = this.sideData.alpha.byteLength; + } + /** + * If this packet is a metadata-only packet. Metadata-only packets don't contain their packet data. They are the + * result of retrieving packets with {@link PacketRetrievalOptions.metadataOnly} set to `true`. + */ + get isMetadataOnly() { + return this.data === PLACEHOLDER_DATA; + } + /** The timestamp of this packet in microseconds. */ + get microsecondTimestamp() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp); + } + /** The duration of this packet in microseconds. */ + get microsecondDuration() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration); + } + /** Converts this packet to an + * [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) for use with the + * WebCodecs API. */ + toEncodedVideoChunk() { + if (this.isMetadataOnly) throw new TypeError("Metadata-only packets cannot be converted to a video chunk."); + if (typeof EncodedVideoChunk === "undefined") throw new Error("EncodedVideoChunk is not available in this environment."); + return new EncodedVideoChunk({ + data: this.data, + type: this.type, + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration + }); + } + /** + * Converts this packet to an + * [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) for use with the + * WebCodecs API, using the alpha side data instead of the color data. Throws if no alpha side data is defined. + */ + alphaToEncodedVideoChunk(type = this.type) { + if (!this.sideData.alpha) throw new TypeError("This packet does not contain alpha side data."); + if (this.isMetadataOnly) throw new TypeError("Metadata-only packets cannot be converted to a video chunk."); + if (typeof EncodedVideoChunk === "undefined") throw new Error("EncodedVideoChunk is not available in this environment."); + return new EncodedVideoChunk({ + data: this.sideData.alpha, + type, + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration + }); + } + /** Converts this packet to an + * [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk) for use with the + * WebCodecs API. */ + toEncodedAudioChunk() { + if (this.isMetadataOnly) throw new TypeError("Metadata-only packets cannot be converted to an audio chunk."); + if (typeof EncodedAudioChunk === "undefined") throw new Error("EncodedAudioChunk is not available in this environment."); + return new EncodedAudioChunk({ + data: this.data, + type: this.type, + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration + }); + } + /** + * Creates an {@link EncodedPacket} from an + * [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) or + * [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk). This method is useful + * for converting chunks from the WebCodecs API to `EncodedPacket` instances. + */ + static fromEncodedChunk(chunk, sideData) { + if (!(chunk instanceof EncodedVideoChunk || chunk instanceof EncodedAudioChunk)) throw new TypeError("chunk must be an EncodedVideoChunk or EncodedAudioChunk."); + const data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + return new EncodedPacket(data, chunk.type, chunk.timestamp / 1e6, (chunk.duration ?? 0) / 1e6, void 0, void 0, sideData); + } + /** Clones this packet while optionally modifying the new packet's data. */ + clone(options) { + if (options !== void 0 && (typeof options !== "object" || options === null)) throw new TypeError("options, when provided, must be an object."); + if (options?.data !== void 0 && !(options.data instanceof Uint8Array)) throw new TypeError("options.data, when provided, must be a Uint8Array."); + if (options?.type !== void 0 && options.type !== "key" && options.type !== "delta") throw new TypeError("options.type, when provided, must be either \"key\" or \"delta\"."); + if (options?.timestamp !== void 0 && !Number.isFinite(options.timestamp)) throw new TypeError("options.timestamp, when provided, must be a number."); + if (options?.duration !== void 0 && !Number.isFinite(options.duration)) throw new TypeError("options.duration, when provided, must be a number."); + if (options?.sequenceNumber !== void 0 && !Number.isFinite(options.sequenceNumber)) throw new TypeError("options.sequenceNumber, when provided, must be a number."); + if (options?.sideData !== void 0 && (typeof options.sideData !== "object" || options.sideData === null)) throw new TypeError("options.sideData, when provided, must be an object."); + return new EncodedPacket(options?.data ?? this.data, options?.type ?? this.type, options?.timestamp ?? this.timestamp, options?.duration ?? this.duration, options?.sequenceNumber ?? this.sequenceNumber, this.byteLength, options?.sideData ?? this.sideData); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/isobmff/isobmff-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var buildIsobmffMimeType = (info) => { + let string = (info.hasVideo ? "video/" : info.hasAudio ? "audio/" : "application/") + (info.isQuickTime ? "quicktime" : "mp4"); + if (info.codecStrings.length > 0) { + const uniqueCodecMimeTypes = [...new Set(info.codecStrings)]; + string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`; + } + return string; +}; +var parsePsshBoxContents = (contents) => { + const view = toDataView(contents); + let pos = 0; + const version = view.getUint8(pos); + pos += 1; + pos += 3; + const systemId = bytesToHexString(contents.subarray(pos, pos + 16)); + pos += 16; + let keyIds = null; + if (version > 0) { + const kidCount = view.getUint32(pos); + pos += 4; + if (kidCount > 0) { + keyIds = []; + for (let i = 0; i < kidCount; i++) { + keyIds.push(bytesToHexString(contents.subarray(pos, pos + 16))); + pos += 16; + } + } + } + const dataSize = view.getUint32(pos); + pos += 4; + return { + systemId, + keyIds, + data: contents.slice(pos, pos + dataSize) + }; +}; +var psshBoxesAreEqual = (a, b) => a.systemId === b.systemId && uint8ArraysAreEqual(a.data, b.data); +//#endregion +//#region node_modules/mediabunny/dist/modules/src/isobmff/isobmff-reader.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var readBoxHeader = (slice) => { + let totalSize = readU32Be(slice); + const name = readAscii(slice, 4); + let headerSize = 8; + if (totalSize === 1) { + totalSize = readU64Be(slice); + headerSize = 16; + } + const contentSize = totalSize - headerSize; + if (contentSize < 0) return null; + return { + name, + totalSize, + headerSize, + contentSize + }; +}; +var readFixed_16_16 = (slice) => { + return readI32Be(slice) / 65536; +}; +var readFixed_2_30 = (slice) => { + return readI32Be(slice) / 1073741824; +}; +var readIsomVariableInteger = (slice) => { + let result = 0; + for (let i = 0; i < 4; i++) { + result <<= 7; + const nextByte = readU8(slice); + result |= nextByte & 127; + if ((nextByte & 128) === 0) break; + } + return result; +}; +var readMetadataStringShort = (slice) => { + let stringLength = readU16Be(slice); + slice.skip(2); + stringLength = Math.min(stringLength, slice.remainingLength); + return textDecoder.decode(readBytes(slice, stringLength)); +}; +var readDataBox = (slice) => { + const header = readBoxHeader(slice); + if (!header || header.name !== "data") return null; + if (slice.remainingLength < 8) return null; + const typeIndicator = readU32Be(slice); + slice.skip(4); + const data = readBytes(slice, header.contentSize - 8); + switch (typeIndicator) { + case 1: return textDecoder.decode(data); + case 2: return new TextDecoder("utf-16be").decode(data); + case 13: return new RichImageData(data, "image/jpeg"); + case 14: return new RichImageData(data, "image/png"); + case 27: return new RichImageData(data, "image/bmp"); + default: return data; + } +}; +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Te4 = new Uint32Array(256); +var Td0 = new Uint32Array(256); +var Td1 = new Uint32Array(256); +var Td2 = new Uint32Array(256); +var Td3 = new Uint32Array(256); +var Td4 = new Uint32Array(256); +var rcon = new Uint32Array(10); +var tablesGenerated = false; +var generateAesTables = () => { + const sbox = new Uint8Array(256); + const log = new Uint8Array(256); + const pow = new Uint8Array(256); + for (let i = 0, p = 1; i < 256; i++) { + pow[i] = p; + log[p] = i; + p = p ^ p << 1 ^ (p & 128 ? 283 : 0); + } + const mul = (a, b) => a && b ? pow[(log[a] + log[b]) % 255] : 0; + sbox[0] = 99; + for (let i = 1; i < 256; i++) { + const x = pow[255 - log[i]]; + let s = x ^ x << 1 ^ x << 2 ^ x << 3 ^ x << 4; + s = s >>> 8 ^ s & 255 ^ 99; + sbox[i] = s; + } + for (let i = 0; i < 256; i++) { + const s = sbox[i]; + const is = sbox.indexOf(i); + Te4[i] = s << 24 | s << 16 | s << 8 | s; + Td4[i] = is << 24 | is << 16 | is << 8 | is; + const b0 = mul(is, 14); + const b1 = mul(is, 9); + const b2 = mul(is, 13); + const b3 = mul(is, 11); + const w = b0 << 24 | b1 << 16 | b2 << 8 | b3; + Td0[i] = w; + Td1[i] = w >>> 8 | w << 24; + Td2[i] = w >>> 16 | w << 16; + Td3[i] = w >>> 24 | w << 8; + } + let r = 1; + for (let i = 0; i < 10; i++) { + rcon[i] = r << 24; + r = r << 1 ^ (r & 128 ? 283 : 0); + } + tablesGenerated = true; +}; +/** A context for doing AES-128-CBC operations. Better than the Web Crypto API since we can stream it. */ +var Aes128CbcContext = class { + constructor() { + this.roundkey = new Uint32Array(44); + this.iv = new Uint32Array(16 / Uint32Array.BYTES_PER_ELEMENT); + this.in = new Uint8Array(16); + this.out = new Uint8Array(16); + this.inView = new DataView(this.in.buffer); + this.outView = new DataView(this.out.buffer); + } + init({ key, iv }) { + assert(key.byteLength === 16); + assert(iv.byteLength === 16); + if (!tablesGenerated) generateAesTables(); + const keyView = new DataView(key.buffer, key.byteOffset, key.byteLength); + const ivView = new DataView(iv.buffer, iv.byteOffset, iv.byteLength); + this.roundkey[0] = keyView.getUint32(0, false); + this.roundkey[1] = keyView.getUint32(4, false); + this.roundkey[2] = keyView.getUint32(8, false); + this.roundkey[3] = keyView.getUint32(12, false); + this.iv[0] = ivView.getUint32(0, false); + this.iv[1] = ivView.getUint32(4, false); + this.iv[2] = ivView.getUint32(8, false); + this.iv[3] = ivView.getUint32(12, false); + for (let index = 4; index < 44; index += 4) { + const temp = this.roundkey[index - 1]; + this.roundkey[index] = this.roundkey[index - 4] ^ Te4[temp >>> 16 & 255] & 4278190080 ^ Te4[temp >>> 8 & 255] & 16711680 ^ Te4[temp >>> 0 & 255] & 65280 ^ Te4[temp >>> 24 & 255] & 255 ^ rcon[index / 4 - 1]; + this.roundkey[index + 1] = this.roundkey[index - 3] ^ this.roundkey[index]; + this.roundkey[index + 2] = this.roundkey[index - 2] ^ this.roundkey[index + 1]; + this.roundkey[index + 3] = this.roundkey[index - 1] ^ this.roundkey[index + 2]; + } + for (let i = 0, j = 40; i < j; i += 4, j -= 4) for (let k = 0; k < 4; k++) { + const temp = this.roundkey[i + k]; + this.roundkey[i + k] = this.roundkey[j + k]; + this.roundkey[j + k] = temp; + } + for (let index = 4; index < 40; index += 4) for (let k = 0; k < 4; k++) { + const rk = this.roundkey[index + k]; + this.roundkey[index + k] = Td0[Te4[rk >>> 24 & 255] & 255] ^ Td1[Te4[rk >>> 16 & 255] & 255] ^ Td2[Te4[rk >>> 8 & 255] & 255] ^ Td3[Te4[rk >>> 0 & 255] & 255]; + } + } + decrypt() { + let s0 = this.inView.getUint32(0, false) ^ this.roundkey[0]; + let s1 = this.inView.getUint32(4, false) ^ this.roundkey[1]; + let s2 = this.inView.getUint32(8, false) ^ this.roundkey[2]; + let s3 = this.inView.getUint32(12, false) ^ this.roundkey[3]; + const temp0 = this.inView.getUint32(0, false); + const temp1 = this.inView.getUint32(4, false); + const temp2 = this.inView.getUint32(8, false); + const temp3 = this.inView.getUint32(12, false); + let t0, t1, t2, t3; + for (let round = 1; round < 10; round++) { + const offset = round * 4; + t0 = Td0[s0 >>> 24] ^ Td1[s3 >>> 16 & 255] ^ Td2[s2 >>> 8 & 255] ^ Td3[s1 & 255] ^ this.roundkey[offset]; + t1 = Td0[s1 >>> 24] ^ Td1[s0 >>> 16 & 255] ^ Td2[s3 >>> 8 & 255] ^ Td3[s2 & 255] ^ this.roundkey[offset + 1]; + t2 = Td0[s2 >>> 24] ^ Td1[s1 >>> 16 & 255] ^ Td2[s0 >>> 8 & 255] ^ Td3[s3 & 255] ^ this.roundkey[offset + 2]; + t3 = Td0[s3 >>> 24] ^ Td1[s2 >>> 16 & 255] ^ Td2[s1 >>> 8 & 255] ^ Td3[s0 & 255] ^ this.roundkey[offset + 3]; + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + const f0 = Td4[s0 >>> 24 & 255] & 4278190080 ^ Td4[s3 >>> 16 & 255] & 16711680 ^ Td4[s2 >>> 8 & 255] & 65280 ^ Td4[s1 >>> 0 & 255] & 255 ^ this.roundkey[40]; + const f1 = Td4[s1 >>> 24 & 255] & 4278190080 ^ Td4[s0 >>> 16 & 255] & 16711680 ^ Td4[s3 >>> 8 & 255] & 65280 ^ Td4[s2 >>> 0 & 255] & 255 ^ this.roundkey[41]; + const f2 = Td4[s2 >>> 24 & 255] & 4278190080 ^ Td4[s1 >>> 16 & 255] & 16711680 ^ Td4[s0 >>> 8 & 255] & 65280 ^ Td4[s3 >>> 0 & 255] & 255 ^ this.roundkey[42]; + const f3 = Td4[s3 >>> 24 & 255] & 4278190080 ^ Td4[s2 >>> 16 & 255] & 16711680 ^ Td4[s1 >>> 8 & 255] & 65280 ^ Td4[s0 >>> 0 & 255] & 255 ^ this.roundkey[43]; + this.outView.setUint32(0, f0 ^ this.iv[0], false); + this.outView.setUint32(4, f1 ^ this.iv[1], false); + this.outView.setUint32(8, f2 ^ this.iv[2], false); + this.outView.setUint32(12, f3 ^ this.iv[3], false); + this.iv[0] = temp0; + this.iv[1] = temp1; + this.iv[2] = temp2; + this.iv[3] = temp3; + } +}; +var createAes128CbcDecryptStream = (reader, getInit, close) => { + let initted = false; + let pos = 0; + const CHUNK_SIZE = 2 ** 16; + const BLOCK_SIZE = 16; + const aesContext = new Aes128CbcContext(); + return new ReadableStream({ + pull: async (controller) => { + if (!initted) { + aesContext.init(await getInit()); + initted = true; + } + const requestedLength = CHUNK_SIZE + BLOCK_SIZE; + let nextSlice = reader.requestSliceRange(pos, 0, requestedLength); + if (isThenable(nextSlice)) nextSlice = await nextSlice; + if (!nextSlice || nextSlice.length === 0) throw new Error("Invalid ciphertext."); + const sliceLength = nextSlice.length; + if (sliceLength % 16 !== 0) throw new Error("Invalid ciphertext."); + const bytesToRead = sliceLength === requestedLength ? sliceLength - BLOCK_SIZE : sliceLength; + const input = readBytes(nextSlice, bytesToRead); + const output = new Uint8Array(bytesToRead); + for (let i = 0; i < bytesToRead; i += 16) { + aesContext.in.set(input.subarray(i, i + 16)); + aesContext.decrypt(); + output.set(aesContext.out, i); + } + if (bytesToRead < sliceLength) { + controller.enqueue(output); + pos += bytesToRead; + } else { + const paddingLength = output[bytesToRead - 1]; + if (paddingLength === 0 || paddingLength > 16) throw new Error("Invalid PKCS#7 padding. Incorrect key or corrupted data."); + const trimmedOutput = output.subarray(0, bytesToRead - paddingLength); + controller.enqueue(trimmedOutput); + controller.close(); + close(); + } + }, + cancel: () => { + close(); + } + }); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/isobmff/isobmff-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var IsobmffDemuxer = class IsobmffDemuxer extends Demuxer { + constructor(input) { + super(input); + this.moovSlice = null; + this.currentTrack = null; + this.tracks = []; + this.metadataPromise = null; + this.movieTimescale = -1; + this.movieDurationInTimescale = -1; + this.movieMatrix = IDENTITY_MATRIX; + this.isQuickTime = false; + this.metadataTags = {}; + this.currentMetadataKeys = null; + this.isFragmented = false; + this.fragmentTrackDefaults = []; + this.psshBoxes = []; + this.currentFragment = null; + /** + * Caches the last fragment that was read. Based on the assumption that there will be multiple reads to the + * same fragment in quick succession. + */ + this.lastReadFragment = null; + this.decryptionKeyCache = /* @__PURE__ */ new Map(); + this.reader = input._reader; + } + async getTrackBackings() { + await this.readMetadata(); + return this.tracks.map((track) => track.trackBacking); + } + async getMimeType() { + await this.readMetadata(); + const backings = await this.getTrackBackings(); + const codecStrings = await Promise.all(backings.map((x) => x.getDecoderConfig().then((c) => c?.codec ?? null))); + return buildIsobmffMimeType({ + isQuickTime: this.isQuickTime, + hasVideo: this.tracks.some((x) => x.info?.type === "video"), + hasAudio: this.tracks.some((x) => x.info?.type === "audio"), + codecStrings: codecStrings.filter(Boolean) + }); + } + async getMetadataTags() { + await this.readMetadata(); + return this.metadataTags; + } + readMetadata() { + return this.metadataPromise ??= (async () => { + let currentPos = 0; + let lookForMfraBox = false; + let foundMovieBoxes = false; + while (true) { + let slice = this.reader.requestSliceRange(currentPos, 8, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const startPos = currentPos; + const boxInfo = readBoxHeader(slice); + if (!boxInfo) break; + if (boxInfo.name === "ftyp" || boxInfo.name === "styp") { + const majorBrand = readAscii(slice, 4); + this.isQuickTime = majorBrand === "qt "; + } else if (boxInfo.name === "moov") { + let moovSlice = this.reader.requestSlice(slice.filePos, boxInfo.contentSize); + if (isThenable(moovSlice)) moovSlice = await moovSlice; + if (!moovSlice) break; + this.moovSlice = moovSlice; + this.readContiguousBoxes(this.moovSlice); + for (const track of this.tracks) { + const previousSegmentDurationsInSeconds = track.editListPreviousSegmentDurations / this.movieTimescale; + track.editListOffset -= Math.round(previousSegmentDurationsInSeconds * track.timescale); + } + lookForMfraBox = this.isFragmented && this.reader.fileSize !== null && this.reader.fileSize > startPos + boxInfo.totalSize; + foundMovieBoxes = true; + break; + } else if (boxInfo.name === "moof") { + if (!this.input._initInput) throw new Error("\"moof\" box encountered with no \"moov\" box present; this file is likely a Segment as described in ISO/IEC 14496-12 Section 8.16. A separate init file that contains a \"moov\" box is required to read this file, please provide it using InputOptions.initInput."); + await this.copyMetadataFromInitInput(this.input._initInput); + lookForMfraBox = false; + foundMovieBoxes = true; + break; + } + currentPos = startPos + boxInfo.totalSize; + } + if (!foundMovieBoxes && this.input._initInput) await this.copyMetadataFromInitInput(this.input._initInput); + if (lookForMfraBox) { + assert(this.reader.fileSize !== null); + let lastWordSlice = this.reader.requestSlice(this.reader.fileSize - 4, 4); + if (isThenable(lastWordSlice)) lastWordSlice = await lastWordSlice; + assert(lastWordSlice); + const lastWord = readU32Be(lastWordSlice); + const potentialMfraPos = this.reader.fileSize - lastWord; + if (potentialMfraPos >= 0 && potentialMfraPos <= this.reader.fileSize - 16) { + let mfraHeaderSlice = this.reader.requestSliceRange(potentialMfraPos, 8, 16); + if (isThenable(mfraHeaderSlice)) mfraHeaderSlice = await mfraHeaderSlice; + if (mfraHeaderSlice) { + const boxInfo = readBoxHeader(mfraHeaderSlice); + if (boxInfo && boxInfo.name === "mfra") { + let mfraSlice = this.reader.requestSlice(mfraHeaderSlice.filePos, boxInfo.contentSize); + if (isThenable(mfraSlice)) mfraSlice = await mfraSlice; + if (mfraSlice) this.readContiguousBoxes(mfraSlice); + } + } + } + } + })(); + } + async copyMetadataFromInitInput(initInput) { + const initDemuxer = await initInput._getDemuxer(); + if (initDemuxer.constructor !== IsobmffDemuxer) throw new Error("Init input must match the input's format."); + await initDemuxer.readMetadata(); + this.movieTimescale = initDemuxer.movieTimescale; + this.movieDurationInTimescale = initDemuxer.movieDurationInTimescale; + this.movieMatrix = initDemuxer.movieMatrix; + this.metadataTags = initDemuxer.metadataTags; + this.isFragmented = true; + this.fragmentTrackDefaults = initDemuxer.fragmentTrackDefaults; + this.psshBoxes = initDemuxer.psshBoxes; + for (const foreignTrack of initDemuxer.tracks) { + const track = { + id: foreignTrack.id, + demuxer: this, + trackBacking: null, + disposition: foreignTrack.disposition, + timescale: foreignTrack.timescale, + durationInMediaTimescale: foreignTrack.durationInMediaTimescale, + durationInMovieTimescale: foreignTrack.durationInMovieTimescale, + matrix: foreignTrack.matrix, + internalCodecId: foreignTrack.internalCodecId, + name: foreignTrack.name, + languageCode: foreignTrack.languageCode, + sampleTableByteOffset: null, + sampleTable: null, + fragmentLookupTable: [], + currentFragmentState: null, + fragmentPositionCache: [], + editListPreviousSegmentDurations: foreignTrack.editListPreviousSegmentDurations, + editListOffset: foreignTrack.editListOffset, + encryptionInfo: foreignTrack.encryptionInfo, + encryptionAuxInfo: null, + frmaCodecString: null, + maxBitrate: foreignTrack.maxBitrate, + avgBitrate: foreignTrack.avgBitrate, + info: foreignTrack.info + }; + if (foreignTrack.trackBacking) { + assert(track.info); + if (track.info.type === "video" && track.info.width !== -1) { + track.trackBacking = new IsobmffVideoTrackBacking(track); + this.tracks.push(track); + } else if (track.info.type === "audio" && track.info.numberOfChannels !== -1) { + track.trackBacking = new IsobmffAudioTrackBacking(track); + this.tracks.push(track); + } + } + } + } + getSampleTableForTrack(internalTrack) { + if (internalTrack.sampleTable) return internalTrack.sampleTable; + const sampleTable = { + sampleTimingEntries: [], + sampleCompositionTimeOffsets: [], + sampleSizes: [], + keySampleIndices: null, + chunkOffsets: [], + sampleToChunk: [], + presentationTimestamps: null, + presentationTimestampIndexMap: null + }; + internalTrack.sampleTable = sampleTable; + if (internalTrack.sampleTableByteOffset === null) return sampleTable; + assert(this.moovSlice); + const stblContainerSlice = this.moovSlice.slice(internalTrack.sampleTableByteOffset); + this.currentTrack = internalTrack; + this.traverseBox(stblContainerSlice); + this.currentTrack = null; + if (internalTrack.info?.type === "audio" && internalTrack.info.codec && PCM_AUDIO_CODECS.includes(internalTrack.info.codec) && sampleTable.sampleCompositionTimeOffsets.length === 0) { + assert(internalTrack.info?.type === "audio"); + const pcmInfo = parsePcmCodec(internalTrack.info.codec); + const newSampleTimingEntries = []; + const newSampleSizes = []; + for (let i = 0; i < sampleTable.sampleToChunk.length; i++) { + const chunkEntry = sampleTable.sampleToChunk[i]; + const nextEntry = sampleTable.sampleToChunk[i + 1]; + const chunkCount = (nextEntry ? nextEntry.startChunkIndex : sampleTable.chunkOffsets.length) - chunkEntry.startChunkIndex; + for (let j = 0; j < chunkCount; j++) { + const startSampleIndex = chunkEntry.startSampleIndex + j * chunkEntry.samplesPerChunk; + const endSampleIndex = startSampleIndex + chunkEntry.samplesPerChunk; + const startTimingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, startSampleIndex, (x) => x.startIndex); + const startTimingEntry = sampleTable.sampleTimingEntries[startTimingEntryIndex]; + const endTimingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, endSampleIndex, (x) => x.startIndex); + const endTimingEntry = sampleTable.sampleTimingEntries[endTimingEntryIndex]; + const firstSampleTimestamp = startTimingEntry.startDecodeTimestamp + (startSampleIndex - startTimingEntry.startIndex) * startTimingEntry.delta; + const delta = endTimingEntry.startDecodeTimestamp + (endSampleIndex - endTimingEntry.startIndex) * endTimingEntry.delta - firstSampleTimestamp; + const lastSampleTimingEntry = last(newSampleTimingEntries); + if (lastSampleTimingEntry && lastSampleTimingEntry.delta === delta) lastSampleTimingEntry.count++; + else newSampleTimingEntries.push({ + startIndex: chunkEntry.startChunkIndex + j, + startDecodeTimestamp: firstSampleTimestamp, + count: 1, + delta + }); + const chunkSize = chunkEntry.samplesPerChunk * pcmInfo.sampleSize * internalTrack.info.numberOfChannels; + newSampleSizes.push(chunkSize); + } + chunkEntry.startSampleIndex = chunkEntry.startChunkIndex; + chunkEntry.samplesPerChunk = 1; + } + sampleTable.sampleTimingEntries = newSampleTimingEntries; + sampleTable.sampleSizes = newSampleSizes; + } + if (sampleTable.sampleCompositionTimeOffsets.length > 0) { + sampleTable.presentationTimestamps = []; + for (const entry of sampleTable.sampleTimingEntries) for (let i = 0; i < entry.count; i++) sampleTable.presentationTimestamps.push({ + presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta, + sampleIndex: entry.startIndex + i + }); + for (const entry of sampleTable.sampleCompositionTimeOffsets) for (let i = 0; i < entry.count; i++) { + const sampleIndex = entry.startIndex + i; + const sample = sampleTable.presentationTimestamps[sampleIndex]; + if (!sample) continue; + sample.presentationTimestamp += entry.offset; + } + sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); + sampleTable.presentationTimestampIndexMap = Array(sampleTable.presentationTimestamps.length).fill(-1); + for (let i = 0; i < sampleTable.presentationTimestamps.length; i++) sampleTable.presentationTimestampIndexMap[sampleTable.presentationTimestamps[i].sampleIndex] = i; + } + return sampleTable; + } + async readFragment(startPos) { + if (this.lastReadFragment?.moofOffset === startPos) return this.lastReadFragment; + let headerSlice = this.reader.requestSliceRange(startPos, 8, 16); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + assert(headerSlice); + const moofBoxInfo = readBoxHeader(headerSlice); + assert(moofBoxInfo?.name === "moof"); + let entireSlice = this.reader.requestSlice(startPos, moofBoxInfo.totalSize); + if (isThenable(entireSlice)) entireSlice = await entireSlice; + assert(entireSlice); + this.traverseBox(entireSlice); + const fragment = this.lastReadFragment; + assert(fragment && fragment.moofOffset === startPos); + for (const [, trackData] of fragment.trackData) { + const track = trackData.track; + const { fragmentPositionCache } = track; + if (!trackData.startTimestampIsFinal) { + const lookupEntry = track.fragmentLookupTable.find((x) => x.moofOffset === fragment.moofOffset); + if (lookupEntry) offsetFragmentTrackDataByTimestamp(trackData, lookupEntry.timestamp); + else { + const lastCacheIndex = binarySearchLessOrEqual(fragmentPositionCache, fragment.moofOffset - 1, (x) => x.moofOffset); + if (lastCacheIndex !== -1) { + const lastCache = fragmentPositionCache[lastCacheIndex]; + offsetFragmentTrackDataByTimestamp(trackData, lastCache.endTimestamp); + } + } + trackData.startTimestampIsFinal = true; + } + const insertionIndex = binarySearchLessOrEqual(fragmentPositionCache, trackData.startTimestamp, (x) => x.startTimestamp); + if (insertionIndex === -1 || fragmentPositionCache[insertionIndex].moofOffset !== fragment.moofOffset) fragmentPositionCache.splice(insertionIndex + 1, 0, { + moofOffset: fragment.moofOffset, + startTimestamp: trackData.startTimestamp, + endTimestamp: trackData.endTimestamp + }); + if (trackData.encryptionAuxInfo && track.encryptionInfo) { + const entries = await resolveEncryptionAuxInfo(this.reader, track.encryptionInfo, trackData.encryptionAuxInfo); + for (let i = 0; i < Math.min(trackData.samples.length, entries.length); i++) { + const entry = entries[i]; + trackData.samples[i].encryption = entry; + } + } + } + return fragment; + } + readContiguousBoxes(slice) { + const startIndex = slice.filePos; + while (slice.filePos - startIndex <= slice.length - 8) if (!this.traverseBox(slice)) break; + } + *iterateContiguousBoxes(slice) { + const startIndex = slice.filePos; + while (slice.filePos - startIndex <= slice.length - 8) { + const startPos = slice.filePos; + const boxInfo = readBoxHeader(slice); + if (!boxInfo) break; + yield { + boxInfo, + slice + }; + slice.filePos = startPos + boxInfo.totalSize; + } + } + traverseBox(slice) { + const startPos = slice.filePos; + const boxInfo = readBoxHeader(slice); + if (!boxInfo) return false; + const contentStartPos = slice.filePos; + const boxEndPos = startPos + boxInfo.totalSize; + switch (boxInfo.name) { + case "mdia": + case "minf": + case "dinf": + case "mfra": + case "edts": + case "sinf": + case "schi": + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + break; + case "mvhd": + { + const version = readU8(slice); + slice.skip(3); + if (version === 1) { + slice.skip(16); + this.movieTimescale = readU32Be(slice); + this.movieDurationInTimescale = readU64Be(slice); + } else { + slice.skip(8); + this.movieTimescale = readU32Be(slice); + this.movieDurationInTimescale = readU32Be(slice); + } + slice.skip(16); + this.movieMatrix = readMatrix(slice); + } + break; + case "trak": + { + const track = { + id: -1, + demuxer: this, + trackBacking: null, + disposition: { + ...DEFAULT_TRACK_DISPOSITION, + primary: false + }, + info: null, + timescale: -1, + durationInMovieTimescale: -1, + durationInMediaTimescale: -1, + matrix: IDENTITY_MATRIX, + internalCodecId: null, + name: null, + languageCode: "und", + sampleTableByteOffset: -1, + sampleTable: null, + fragmentLookupTable: [], + currentFragmentState: null, + fragmentPositionCache: [], + editListPreviousSegmentDurations: 0, + editListOffset: 0, + encryptionInfo: null, + encryptionAuxInfo: null, + frmaCodecString: null, + maxBitrate: null, + avgBitrate: null + }; + this.currentTrack = track; + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + if (track.id !== -1 && track.timescale !== -1 && track.info !== null) { + if (track.info.type === "video" && track.info.width !== -1) { + track.trackBacking = new IsobmffVideoTrackBacking(track); + this.tracks.push(track); + } else if (track.info.type === "audio" && track.info.numberOfChannels !== -1) { + track.trackBacking = new IsobmffAudioTrackBacking(track); + this.tracks.push(track); + } + } + this.currentTrack = null; + } + break; + case "tkhd": + { + const track = this.currentTrack; + if (!track) break; + const version = readU8(slice); + const trackEnabled = !!(readU24Be(slice) & 1); + track.disposition.default = trackEnabled; + if (version === 0) { + slice.skip(8); + track.id = readU32Be(slice); + slice.skip(4); + track.durationInMovieTimescale = readU32Be(slice); + } else if (version === 1) { + slice.skip(16); + track.id = readU32Be(slice); + slice.skip(4); + track.durationInMovieTimescale = readU64Be(slice); + } else throw new Error(`Incorrect track header version ${version}.`); + slice.skip(16); + track.matrix = multiplyMatrices(readMatrix(slice), this.movieMatrix); + } + break; + case "elst": + { + const track = this.currentTrack; + if (!track) break; + const version = readU8(slice); + slice.skip(3); + let relevantEntryFound = false; + let previousSegmentDurations = 0; + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const segmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice); + const mediaTime = version === 1 ? readI64Be(slice) : readI32Be(slice); + const mediaRate = readFixed_16_16(slice); + if (relevantEntryFound) { + Logging._warn("Unsupported edit list: multiple edits are not currently supported. Only using first edit."); + break; + } + if (mediaTime === -1) { + previousSegmentDurations += segmentDuration; + continue; + } + if (mediaRate !== 1) { + Logging._warn("Unsupported edit list entry: media rate must be 1."); + break; + } + track.editListPreviousSegmentDurations = previousSegmentDurations; + track.editListOffset = mediaTime; + relevantEntryFound = true; + } + } + break; + case "mdhd": + { + const track = this.currentTrack; + if (!track) break; + const version = readU8(slice); + slice.skip(3); + if (version === 0) { + slice.skip(8); + track.timescale = readU32Be(slice); + track.durationInMediaTimescale = readU32Be(slice); + } else if (version === 1) { + slice.skip(16); + track.timescale = readU32Be(slice); + track.durationInMediaTimescale = readU64Be(slice); + } + let language = readU16Be(slice); + if (language > 0) { + track.languageCode = ""; + for (let i = 0; i < 3; i++) { + track.languageCode = String.fromCharCode(96 + (language & 31)) + track.languageCode; + language >>= 5; + } + if (!isIso639Dash2LanguageCode(track.languageCode)) track.languageCode = "und"; + } + } + break; + case "hdlr": + { + const track = this.currentTrack; + if (!track) break; + slice.skip(8); + const handlerType = readAscii(slice, 4); + if (handlerType === "vide") track.info = { + type: "video", + width: -1, + height: -1, + squarePixelWidth: -1, + squarePixelHeight: -1, + codec: null, + codecDescription: null, + colorSpace: { ...EMPTY_COLOR_SPACE }, + avcType: null, + avcCodecInfo: null, + hevcCodecInfo: null, + vp9CodecInfo: null, + av1CodecInfo: null, + proresCodecInfo: null, + proresFormat: null + }; + else if (handlerType === "soun") track.info = { + type: "audio", + numberOfChannels: -1, + sampleRate: -1, + codec: null, + codecDescription: null, + aacCodecInfo: null, + dtsFormat: null, + pcmLittleEndian: false, + pcmSampleSize: null + }; + } + break; + case "stbl": + { + const track = this.currentTrack; + if (!track) break; + track.sampleTableByteOffset = startPos; + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + } + break; + case "stsd": + { + const track = this.currentTrack; + if (!track) break; + if (track.info === null || track.sampleTable) break; + const stsdVersion = readU8(slice); + slice.skip(3); + const entries = readU32Be(slice); + for (let i = 0; i < entries; i++) { + const sampleBoxStartPos = slice.filePos; + const sampleBoxInfo = readBoxHeader(slice); + if (!sampleBoxInfo) break; + track.internalCodecId = sampleBoxInfo.name; + const lowercaseBoxName = sampleBoxInfo.name.toLowerCase(); + if (track.info.type === "video") { + slice.skip(24); + track.info.width = readU16Be(slice); + track.info.height = readU16Be(slice); + track.info.squarePixelWidth = track.info.width; + track.info.squarePixelHeight = track.info.height; + slice.skip(50); + track.frmaCodecString = null; + this.readContiguousBoxes(slice.slice(slice.filePos, sampleBoxStartPos + sampleBoxInfo.totalSize - slice.filePos)); + const codecName = lowercaseBoxName === "encv" ? track.frmaCodecString : lowercaseBoxName; + track.frmaCodecString = null; + if (codecName === "avc1" || codecName === "avc3") { + track.info.codec = "avc"; + track.info.avcType = codecName === "avc1" ? 1 : 3; + } else if (codecName === "hvc1" || codecName === "hev1") track.info.codec = "hevc"; + else if (codecName === "vp08") track.info.codec = "vp8"; + else if (codecName === "vp09") track.info.codec = "vp9"; + else if (codecName === "av01") track.info.codec = "av1"; + else if (PRORES_FOURCCS.includes(lowercaseBoxName)) { + track.info.codec = "prores"; + track.info.proresFormat = lowercaseBoxName; + } else if (codecName === null) Logging._warn(`Unknown encrypted video codec due to missing frma box.`); + else Logging._warn(`Unsupported video codec (sample entry type '${sampleBoxInfo.name}').`); + } else { + slice.skip(8); + const version = readU16Be(slice); + slice.skip(6); + let channelCount = readU16Be(slice); + let sampleSize = readU16Be(slice); + slice.skip(4); + let sampleRate = readU32Be(slice) / 65536; + let lpcmFlags = null; + if (stsdVersion === 0 && version > 0) { + if (version === 1) { + slice.skip(4); + sampleSize = 8 * readU32Be(slice); + slice.skip(8); + } else if (version === 2) { + slice.skip(4); + sampleRate = readF64Be(slice); + channelCount = readU32Be(slice); + slice.skip(4); + sampleSize = readU32Be(slice); + lpcmFlags = readU32Be(slice); + slice.skip(8); + } + } + track.info.numberOfChannels = channelCount; + track.info.sampleRate = sampleRate; + track.frmaCodecString = null; + this.readContiguousBoxes(slice.slice(slice.filePos, sampleBoxStartPos + sampleBoxInfo.totalSize - slice.filePos)); + const codecName = lowercaseBoxName === "enca" ? track.frmaCodecString : lowercaseBoxName; + track.frmaCodecString = null; + if (codecName === "mp4a") {} else if (codecName === "opus") { + track.info.codec = "opus"; + track.info.sampleRate = OPUS_SAMPLE_RATE; + } else if (codecName === "flac") track.info.codec = "flac"; + else if (codecName === "ulaw") track.info.codec = "ulaw"; + else if (codecName === "alaw") track.info.codec = "alaw"; + else if (codecName === "ac-3") track.info.codec = "ac3"; + else if (codecName === "ec-3") track.info.codec = "eac3"; + else if (DTS_FOURCCS.includes(codecName)) { + track.info.codec = "dts"; + track.info.dtsFormat = codecName; + } else if (codecName === "twos") if (sampleSize === 8) track.info.codec = "pcm-s8"; + else if (sampleSize === 16) track.info.codec = track.info.pcmLittleEndian ? "pcm-s16" : "pcm-s16be"; + else { + Logging._warn(`Unsupported sample size ${sampleSize} for codec 'twos'.`); + track.info.codec = null; + } + else if (codecName === "sowt") if (sampleSize === 8) track.info.codec = "pcm-s8"; + else if (sampleSize === 16) track.info.codec = "pcm-s16"; + else { + Logging._warn(`Unsupported sample size ${sampleSize} for codec 'sowt'.`); + track.info.codec = null; + } + else if (codecName === "raw ") track.info.codec = "pcm-u8"; + else if (codecName === "in24") track.info.codec = track.info.pcmLittleEndian ? "pcm-s24" : "pcm-s24be"; + else if (codecName === "in32") track.info.codec = track.info.pcmLittleEndian ? "pcm-s32" : "pcm-s32be"; + else if (codecName === "fl32") track.info.codec = track.info.pcmLittleEndian ? "pcm-f32" : "pcm-f32be"; + else if (codecName === "fl64") track.info.codec = track.info.pcmLittleEndian ? "pcm-f64" : "pcm-f64be"; + else if (codecName === "ipcm") { + const pcmSampleSize = track.info.pcmSampleSize; + if (track.info.pcmLittleEndian) if (pcmSampleSize === 16) track.info.codec = "pcm-s16"; + else if (pcmSampleSize === 24) track.info.codec = "pcm-s24"; + else if (pcmSampleSize === 32) track.info.codec = "pcm-s32"; + else { + Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`); + track.info.codec = null; + } + else if (pcmSampleSize === 16) track.info.codec = "pcm-s16be"; + else if (pcmSampleSize === 24) track.info.codec = "pcm-s24be"; + else if (pcmSampleSize === 32) track.info.codec = "pcm-s32be"; + else { + Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`); + track.info.codec = null; + } + } else if (codecName === "fpcm") { + const pcmSampleSize = track.info.pcmSampleSize; + if (track.info.pcmLittleEndian) if (pcmSampleSize === 32) track.info.codec = "pcm-f32"; + else if (pcmSampleSize === 64) track.info.codec = "pcm-f64"; + else { + Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`); + track.info.codec = null; + } + else if (pcmSampleSize === 32) track.info.codec = "pcm-f32be"; + else if (pcmSampleSize === 64) track.info.codec = "pcm-f64be"; + else { + Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`); + track.info.codec = null; + } + } else if (codecName === "lpcm" && lpcmFlags !== null) { + const bytesPerSample = sampleSize + 7 >> 3; + const isFloat = Boolean(lpcmFlags & 1); + const isBigEndian = Boolean(lpcmFlags & 2); + const sFlags = lpcmFlags & 4 ? -1 : 0; + if (sampleSize > 0 && sampleSize <= 64) { + if (isFloat) { + if (sampleSize === 32) track.info.codec = isBigEndian ? "pcm-f32be" : "pcm-f32"; + } else if (sFlags & 1 << bytesPerSample - 1) { + if (bytesPerSample === 1) track.info.codec = "pcm-s8"; + else if (bytesPerSample === 2) track.info.codec = isBigEndian ? "pcm-s16be" : "pcm-s16"; + else if (bytesPerSample === 3) track.info.codec = isBigEndian ? "pcm-s24be" : "pcm-s24"; + else if (bytesPerSample === 4) track.info.codec = isBigEndian ? "pcm-s32be" : "pcm-s32"; + } else if (bytesPerSample === 1) track.info.codec = "pcm-u8"; + } + if (track.info.codec === null) Logging._warn("Unsupported PCM format."); + } else if (codecName === null) Logging._warn(`Unknown encrypted audio codec due to missing frma box.`); + else Logging._warn(`Unsupported audio codec (sample entry type '${sampleBoxInfo.name}').`); + } + slice.filePos = sampleBoxStartPos + sampleBoxInfo.totalSize; + } + } + break; + case "frma": + { + const track = this.currentTrack; + if (!track) break; + track.frmaCodecString = readAscii(slice, 4).toLowerCase(); + } + break; + case "schm": + { + const track = this.currentTrack; + if (!track) break; + slice.skip(4); + const schemeType = readAscii(slice, 4); + if (schemeType === "cenc" || schemeType === "cens" || schemeType === "cbcs") track.encryptionInfo = { + scheme: schemeType, + defaultKid: null, + defaultIsProtected: null, + defaultPerSampleIvSize: null, + defaultConstantIv: null, + defaultCryptByteBlock: null, + defaultSkipByteBlock: null + }; + else Logging._warn(`Unsupported encryption scheme '${schemeType}'.`); + } + break; + case "tenc": + { + const track = this.currentTrack; + if (!track || !track.encryptionInfo) break; + const version = readU8(slice); + slice.skip(3); + slice.skip(1); + const patternByte = readU8(slice); + if (version > 0) { + track.encryptionInfo.defaultCryptByteBlock = patternByte >> 4; + track.encryptionInfo.defaultSkipByteBlock = patternByte & 15; + } else { + track.encryptionInfo.defaultCryptByteBlock = 0; + track.encryptionInfo.defaultSkipByteBlock = 0; + } + track.encryptionInfo.defaultIsProtected = readU8(slice) !== 0; + track.encryptionInfo.defaultPerSampleIvSize = readU8(slice); + track.encryptionInfo.defaultKid = bytesToHexString(readBytes(slice, 16)); + if (track.encryptionInfo.defaultIsProtected && track.encryptionInfo.defaultPerSampleIvSize === 0) { + const constantIvSize = readU8(slice); + const constantIv = new Uint8Array(16); + constantIv.set(readBytes(slice, constantIvSize), 0); + track.encryptionInfo.defaultConstantIv = constantIv; + } + } + break; + case "avcC": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info); + if (boxInfo.contentSize === 0) break; + track.info.codecDescription = readBytes(slice, boxInfo.contentSize); + const sps = deserializeAvcDecoderConfigurationRecord(track.info.codecDescription)?.sequenceParameterSets[0]; + const spsInfo = sps && parseAvcSps(sps); + if (spsInfo) applySpsPixelAspectRatio(track, spsInfo.pixelAspectRatio); + } + break; + case "hvcC": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info); + if (boxInfo.contentSize === 0) break; + track.info.codecDescription = readBytes(slice, boxInfo.contentSize); + const sps = deserializeHevcDecoderConfigurationRecord(track.info.codecDescription)?.arrays.find((x) => x.nalUnitType === HevcNalUnitType.SPS_NUT)?.nalUnits[0]; + const spsInfo = sps && parseHevcSps(sps); + if (spsInfo) applySpsPixelAspectRatio(track, spsInfo.pixelAspectRatio); + } + break; + case "vpcC": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "video"); + slice.skip(4); + const profile = readU8(slice); + const level = readU8(slice); + const thirdByte = readU8(slice); + const bitDepth = thirdByte >> 4; + const chromaSubsampling = thirdByte >> 1 & 7; + const videoFullRangeFlag = thirdByte & 1; + const colourPrimaries = readU8(slice); + const transferCharacteristics = readU8(slice); + const matrixCoefficients = readU8(slice); + track.info.vp9CodecInfo = { + profile, + level, + bitDepth, + chromaSubsampling, + videoFullRangeFlag, + colourPrimaries, + transferCharacteristics, + matrixCoefficients + }; + } + break; + case "av1C": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "video"); + slice.skip(1); + const secondByte = readU8(slice); + const profile = secondByte >> 5; + const level = secondByte & 31; + const thirdByte = readU8(slice); + const tier = thirdByte >> 7; + const highBitDepth = thirdByte >> 6 & 1; + const twelveBit = thirdByte >> 5 & 1; + const monochrome = thirdByte >> 4 & 1; + const chromaSubsamplingX = thirdByte >> 3 & 1; + const chromaSubsamplingY = thirdByte >> 2 & 1; + const chromaSamplePosition = thirdByte & 3; + const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8; + slice.skip(1); + const configObuInfo = extractAv1CodecInfoFromPacket(readBytes(slice, boxInfo.contentSize - 4)); + track.info.av1CodecInfo = { + profile, + level, + tier, + bitDepth, + monochrome, + chromaSubsamplingX, + chromaSubsamplingY, + chromaSamplePosition, + videoFullRangeFlag: configObuInfo?.videoFullRangeFlag ?? 0, + colourPrimaries: configObuInfo?.colourPrimaries ?? 2, + transferCharacteristics: configObuInfo?.transferCharacteristics ?? 2, + matrixCoefficients: configObuInfo?.matrixCoefficients ?? 2 + }; + } + break; + case "colr": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "video"); + const colourType = readAscii(slice, 4); + if (colourType !== "nclx" && colourType !== "nclc") break; + const colourPrimaries = readU16Be(slice); + const transferCharacteristics = readU16Be(slice); + const matrixCoefficients = readU16Be(slice); + let fullRange = void 0; + if (colourType === "nclx") fullRange = Boolean(readU8(slice) & 128); + track.info.colorSpace = { + primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients], + fullRange + }; + } + break; + case "pasp": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "video"); + const num = readU32Be(slice); + const den = readU32Be(slice); + if (num > 0 && den > 0) { + track.info.squarePixelWidth = track.info.width; + track.info.squarePixelHeight = track.info.height; + if (num > den) track.info.squarePixelWidth = Math.round(track.info.width * num / den); + else track.info.squarePixelHeight = Math.round(track.info.height * den / num); + } + } + break; + case "btrt": + { + const track = this.currentTrack; + if (!track) break; + slice.skip(4); + const maxBitrate = readU32Be(slice); + const avgBitrate = readU32Be(slice); + track.maxBitrate = maxBitrate > 0 ? maxBitrate : null; + track.avgBitrate = avgBitrate > 0 ? avgBitrate : null; + } + break; + case "wave": + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + break; + case "esds": + { + const track = this.currentTrack; + if (!track || track.info?.type !== "audio") break; + slice.skip(4); + assert(readU8(slice) === 3); + readIsomVariableInteger(slice); + slice.skip(2); + const mixed = readU8(slice); + const streamDependenceFlag = (mixed & 128) !== 0; + const urlFlag = (mixed & 64) !== 0; + const ocrStreamFlag = (mixed & 32) !== 0; + if (streamDependenceFlag) slice.skip(2); + if (urlFlag) { + const urlLength = readU8(slice); + slice.skip(urlLength); + } + if (ocrStreamFlag) slice.skip(2); + assert(readU8(slice) === 4); + const decoderConfigDescriptorLength = readIsomVariableInteger(slice); + const payloadStart = slice.filePos; + const objectTypeIndication = readU8(slice); + if (objectTypeIndication === 64 || objectTypeIndication === 103) { + track.info.codec = "aac"; + track.info.aacCodecInfo = { + isMpeg2: objectTypeIndication === 103, + objectType: null + }; + } else if (objectTypeIndication === 105 || objectTypeIndication === 107) track.info.codec = "mp3"; + else if (objectTypeIndication === 221) track.info.codec = "vorbis"; + else if (objectTypeIndication === 169) track.info.codec = "dts"; + else Logging._warn(`Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`); + slice.skip(12); + if (decoderConfigDescriptorLength > slice.filePos - payloadStart) { + assert(readU8(slice) === 5); + const decoderSpecificInfoLength = readIsomVariableInteger(slice); + track.info.codecDescription = readBytes(slice, decoderSpecificInfoLength); + if (track.info.codec === "aac") { + const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription); + if (audioSpecificConfig.outputNumberOfChannels !== null) track.info.numberOfChannels = audioSpecificConfig.outputNumberOfChannels; + if (audioSpecificConfig.outputSampleRate !== null) track.info.sampleRate = audioSpecificConfig.outputSampleRate; + } + } + } + break; + case "enda": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + track.info.pcmLittleEndian = !!(readU16Be(slice) & 255); + } + break; + case "pcmC": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + slice.skip(4); + const formatFlags = readU8(slice); + track.info.pcmLittleEndian = Boolean(formatFlags & 1); + track.info.pcmSampleSize = readU8(slice); + } + break; + case "dOps": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + slice.skip(1); + const outputChannelCount = readU8(slice); + const preSkip = readU16Be(slice); + const inputSampleRate = readU32Be(slice); + const outputGain = readI16Be(slice); + const channelMappingFamily = readU8(slice); + let channelMappingTable; + if (channelMappingFamily !== 0) channelMappingTable = readBytes(slice, 2 + outputChannelCount); + else channelMappingTable = new Uint8Array(0); + const description = new Uint8Array(19 + channelMappingTable.byteLength); + const view = new DataView(description.buffer); + view.setUint32(0, 1332770163, false); + view.setUint32(4, 1214603620, false); + view.setUint8(8, 1); + view.setUint8(9, outputChannelCount); + view.setUint16(10, preSkip, true); + view.setUint32(12, inputSampleRate, true); + view.setInt16(16, outputGain, true); + view.setUint8(18, channelMappingFamily); + description.set(channelMappingTable, 19); + track.info.codecDescription = description; + track.info.numberOfChannels = outputChannelCount; + } + break; + case "dfLa": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + slice.skip(4); + const BLOCK_TYPE_MASK = 127; + const LAST_METADATA_BLOCK_FLAG_MASK = 128; + const startPos = slice.filePos; + while (slice.filePos < boxEndPos) { + const flagAndType = readU8(slice); + const metadataBlockLength = readU24Be(slice); + if ((flagAndType & BLOCK_TYPE_MASK) === FlacBlockType.STREAMINFO) { + slice.skip(10); + const word = readU32Be(slice); + const sampleRate = word >>> 12; + const numberOfChannels = (word >> 9 & 7) + 1; + track.info.sampleRate = sampleRate; + track.info.numberOfChannels = numberOfChannels; + slice.skip(20); + } else slice.skip(metadataBlockLength); + if (flagAndType & LAST_METADATA_BLOCK_FLAG_MASK) break; + } + const endPos = slice.filePos; + slice.filePos = startPos; + const bytes = readBytes(slice, endPos - startPos); + const description = new Uint8Array(4 + bytes.byteLength); + new DataView(description.buffer).setUint32(0, 1716281667, false); + description.set(bytes, 4); + track.info.codecDescription = description; + } + break; + case "dac3": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + const bitstream = new Bitstream(readBytes(slice, 3)); + const fscod = bitstream.readBits(2); + bitstream.skipBits(8); + const acmod = bitstream.readBits(3); + const lfeon = bitstream.readBits(1); + if (fscod < 3) track.info.sampleRate = AC3_SAMPLE_RATES[fscod]; + track.info.numberOfChannels = AC3_ACMOD_CHANNEL_COUNTS[acmod] + lfeon; + } + break; + case "dec3": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + const config = parseEac3Config(readBytes(slice, boxInfo.contentSize)); + if (!config) { + Logging._warn("Invalid dec3 box contents, ignoring."); + break; + } + const sampleRate = getEac3SampleRate(config); + if (sampleRate !== null) track.info.sampleRate = sampleRate; + track.info.numberOfChannels = getEac3ChannelCount(config); + } + break; + case "ddts": + { + const track = this.currentTrack; + if (!track) break; + assert(track.info?.type === "audio"); + const config = parseDtsSpecificBox(readBytes(slice, Math.min(boxInfo.contentSize, 20))); + if (!config) { + Logging._warn("Invalid ddts box contents, ignoring."); + break; + } + track.info.sampleRate = config.sampleRate; + if (config.numberOfChannels !== null) track.info.numberOfChannels = config.numberOfChannels; + } + break; + case "stts": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const entryCount = readU32Be(slice); + let currentIndex = 0; + let currentTimestamp = 0; + for (let i = 0; i < entryCount; i++) { + const sampleCount = readU32Be(slice); + const sampleDelta = readU32Be(slice); + track.sampleTable.sampleTimingEntries.push({ + startIndex: currentIndex, + startDecodeTimestamp: currentTimestamp, + count: sampleCount, + delta: sampleDelta + }); + currentIndex += sampleCount; + currentTimestamp += sampleCount * sampleDelta; + } + } + break; + case "ctts": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const entryCount = readU32Be(slice); + let sampleIndex = 0; + for (let i = 0; i < entryCount; i++) { + const sampleCount = readU32Be(slice); + const sampleOffset = readI32Be(slice); + track.sampleTable.sampleCompositionTimeOffsets.push({ + startIndex: sampleIndex, + count: sampleCount, + offset: sampleOffset + }); + sampleIndex += sampleCount; + } + } + break; + case "stsz": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const sampleSize = readU32Be(slice); + const sampleCount = readU32Be(slice); + if (sampleSize === 0) for (let i = 0; i < sampleCount; i++) { + const sampleSize = readU32Be(slice); + track.sampleTable.sampleSizes.push(sampleSize); + } + else track.sampleTable.sampleSizes.push(sampleSize); + } + break; + case "stz2": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + slice.skip(3); + const fieldSize = readU8(slice); + const sampleCount = readU32Be(slice); + const bitstream = new Bitstream(readBytes(slice, Math.ceil(sampleCount * fieldSize / 8))); + for (let i = 0; i < sampleCount; i++) { + const sampleSize = bitstream.readBits(fieldSize); + track.sampleTable.sampleSizes.push(sampleSize); + } + } + break; + case "stss": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + track.sampleTable.keySampleIndices = []; + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const sampleIndex = readU32Be(slice) - 1; + track.sampleTable.keySampleIndices.push(sampleIndex); + } + if (track.sampleTable.keySampleIndices[0] !== 0) track.sampleTable.keySampleIndices.unshift(0); + } + break; + case "stsc": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const startChunkIndex = readU32Be(slice) - 1; + const samplesPerChunk = readU32Be(slice); + const sampleDescriptionIndex = readU32Be(slice); + track.sampleTable.sampleToChunk.push({ + startSampleIndex: -1, + startChunkIndex, + samplesPerChunk, + sampleDescriptionIndex + }); + } + let startSampleIndex = 0; + for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) { + track.sampleTable.sampleToChunk[i].startSampleIndex = startSampleIndex; + if (i < track.sampleTable.sampleToChunk.length - 1) { + const chunkCount = track.sampleTable.sampleToChunk[i + 1].startChunkIndex - track.sampleTable.sampleToChunk[i].startChunkIndex; + startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i].samplesPerChunk; + } + } + } + break; + case "stco": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const chunkOffset = readU32Be(slice); + track.sampleTable.chunkOffsets.push(chunkOffset); + } + } + break; + case "co64": + { + const track = this.currentTrack; + if (!track) break; + if (!track.sampleTable) break; + slice.skip(4); + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const chunkOffset = readU64Be(slice); + track.sampleTable.chunkOffsets.push(chunkOffset); + } + } + break; + case "mvex": + this.isFragmented = true; + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + break; + case "mehd": + { + const version = readU8(slice); + slice.skip(3); + const fragmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice); + this.movieDurationInTimescale = fragmentDuration; + } + break; + case "trex": + { + slice.skip(4); + const trackId = readU32Be(slice); + const defaultSampleDescriptionIndex = readU32Be(slice); + const defaultSampleDuration = readU32Be(slice); + const defaultSampleSize = readU32Be(slice); + const defaultSampleFlags = readU32Be(slice); + this.fragmentTrackDefaults.push({ + trackId, + defaultSampleDescriptionIndex, + defaultSampleDuration, + defaultSampleSize, + defaultSampleFlags + }); + } + break; + case "tfra": + { + const version = readU8(slice); + slice.skip(3); + const trackId = readU32Be(slice); + const track = this.tracks.find((x) => x.id === trackId); + if (!track) break; + const word = readU32Be(slice); + const lengthSizeOfTrafNum = (word & 48) >> 4; + const lengthSizeOfTrunNum = (word & 12) >> 2; + const lengthSizeOfSampleNum = word & 3; + const functions = [ + readU8, + readU16Be, + readU24Be, + readU32Be + ]; + const readTrafNum = functions[lengthSizeOfTrafNum]; + const readTrunNum = functions[lengthSizeOfTrunNum]; + const readSampleNum = functions[lengthSizeOfSampleNum]; + const numberOfEntries = readU32Be(slice); + for (let i = 0; i < numberOfEntries; i++) { + const time = version === 1 ? readU64Be(slice) : readU32Be(slice); + const moofOffset = version === 1 ? readU64Be(slice) : readU32Be(slice); + readTrafNum(slice); + readTrunNum(slice); + readSampleNum(slice); + track.fragmentLookupTable.push({ + timestamp: time, + moofOffset + }); + } + track.fragmentLookupTable.sort((a, b) => a.timestamp - b.timestamp); + for (let i = 0; i < track.fragmentLookupTable.length - 1; i++) { + const entry1 = track.fragmentLookupTable[i]; + const entry2 = track.fragmentLookupTable[i + 1]; + if (entry1.timestamp === entry2.timestamp) { + track.fragmentLookupTable.splice(i + 1, 1); + i--; + } + } + } + break; + case "moof": + this.currentFragment = { + moofOffset: startPos, + moofSize: boxInfo.totalSize, + implicitBaseDataOffset: startPos, + trackData: /* @__PURE__ */ new Map(), + psshBoxes: [] + }; + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + this.lastReadFragment = this.currentFragment; + this.currentFragment = null; + break; + case "traf": + assert(this.currentFragment); + this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + if (this.currentTrack) { + const trackData = this.currentFragment.trackData.get(this.currentTrack.id); + cond: if (trackData) { + if (trackData.samples.length === 0) { + this.currentFragment.trackData.delete(this.currentTrack.id); + break cond; + } + trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ + presentationTimestamp: x.presentationTimestamp, + sampleIndex: i + })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); + for (let i = 0; i < trackData.presentationTimestamps.length; i++) { + const currentEntry = trackData.presentationTimestamps[i]; + const currentSample = trackData.samples[currentEntry.sampleIndex]; + if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp; + if (i < trackData.presentationTimestamps.length - 1) currentSample.duration = trackData.presentationTimestamps[i + 1].presentationTimestamp - currentEntry.presentationTimestamp; + } + const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex]; + const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex]; + trackData.startTimestamp = firstSample.presentationTimestamp; + trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration; + const { currentFragmentState } = this.currentTrack; + assert(currentFragmentState); + if (currentFragmentState.startTimestamp !== null) { + offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp); + trackData.startTimestampIsFinal = true; + } + if (currentFragmentState.encryptionAuxInfo && !trackData.samples[0].encryption) trackData.encryptionAuxInfo = currentFragmentState.encryptionAuxInfo; + } + this.currentTrack.currentFragmentState = null; + this.currentTrack = null; + } + break; + case "pssh": + { + if (this.input._formatOptions.isobmff?._suppressPsshParsing) break; + const psshBox = parsePsshBoxContents(readBytes(slice, boxInfo.contentSize)); + if (this.currentFragment) this.currentFragment.psshBoxes.push(psshBox); + else if (!this.currentTrack) this.psshBoxes.push(psshBox); + } + break; + case "tfhd": + { + assert(this.currentFragment); + slice.skip(1); + const flags = readU24Be(slice); + const baseDataOffsetPresent = Boolean(flags & 1); + const sampleDescriptionIndexPresent = Boolean(flags & 2); + const defaultSampleDurationPresent = Boolean(flags & 8); + const defaultSampleSizePresent = Boolean(flags & 16); + const defaultSampleFlagsPresent = Boolean(flags & 32); + const durationIsEmpty = Boolean(flags & 65536); + const defaultBaseIsMoof = Boolean(flags & 131072); + const trackId = readU32Be(slice); + const track = this.tracks.find((x) => x.id === trackId); + if (!track) break; + const defaults = this.fragmentTrackDefaults.find((x) => x.trackId === trackId); + this.currentTrack = track; + track.currentFragmentState = { + baseDataOffset: this.currentFragment.implicitBaseDataOffset, + sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null, + defaultSampleDuration: defaults?.defaultSampleDuration ?? null, + defaultSampleSize: defaults?.defaultSampleSize ?? null, + defaultSampleFlags: defaults?.defaultSampleFlags ?? null, + startTimestamp: null, + encryptionAuxInfo: null + }; + if (baseDataOffsetPresent) track.currentFragmentState.baseDataOffset = readU64Be(slice); + else if (defaultBaseIsMoof) track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset; + if (sampleDescriptionIndexPresent) track.currentFragmentState.sampleDescriptionIndex = readU32Be(slice); + if (defaultSampleDurationPresent) track.currentFragmentState.defaultSampleDuration = readU32Be(slice); + if (defaultSampleSizePresent) track.currentFragmentState.defaultSampleSize = readU32Be(slice); + if (defaultSampleFlagsPresent) track.currentFragmentState.defaultSampleFlags = readU32Be(slice); + if (durationIsEmpty) track.currentFragmentState.defaultSampleDuration = 0; + } + break; + case "tfdt": + { + const track = this.currentTrack; + if (!track) break; + assert(track.currentFragmentState); + const version = readU8(slice); + slice.skip(3); + const baseMediaDecodeTime = version === 0 ? readU32Be(slice) : readU64Be(slice); + track.currentFragmentState.startTimestamp = baseMediaDecodeTime; + } + break; + case "trun": + { + const track = this.currentTrack; + if (!track) break; + assert(this.currentFragment); + assert(track.currentFragmentState); + slice.skip(1); + const flags = readU24Be(slice); + const dataOffsetPresent = Boolean(flags & 1); + const firstSampleFlagsPresent = Boolean(flags & 4); + const sampleDurationPresent = Boolean(flags & 256); + const sampleSizePresent = Boolean(flags & 512); + const sampleFlagsPresent = Boolean(flags & 1024); + const sampleCompositionTimeOffsetsPresent = Boolean(flags & 2048); + const sampleCount = readU32Be(slice); + let dataOffset = null; + if (dataOffsetPresent) dataOffset = readI32Be(slice); + let firstSampleFlags = null; + if (firstSampleFlagsPresent) firstSampleFlags = readU32Be(slice); + let trackData; + if (this.currentFragment.trackData.has(track.id)) { + trackData = this.currentFragment.trackData.get(track.id); + if (dataOffset !== null) trackData.currentOffset = track.currentFragmentState.baseDataOffset + dataOffset; + } else { + trackData = { + track, + currentTimestamp: 0, + currentOffset: track.currentFragmentState.baseDataOffset + (dataOffset ?? 0), + startTimestamp: 0, + endTimestamp: 0, + firstKeyFrameTimestamp: null, + samples: [], + presentationTimestamps: [], + startTimestampIsFinal: false, + encryptionAuxInfo: null + }; + this.currentFragment.trackData.set(track.id, trackData); + } + for (let i = 0; i < sampleCount; i++) { + let sampleDuration; + if (sampleDurationPresent) sampleDuration = readU32Be(slice); + else { + assert(track.currentFragmentState.defaultSampleDuration !== null); + sampleDuration = track.currentFragmentState.defaultSampleDuration; + } + let sampleSize; + if (sampleSizePresent) sampleSize = readU32Be(slice); + else { + assert(track.currentFragmentState.defaultSampleSize !== null); + sampleSize = track.currentFragmentState.defaultSampleSize; + } + let sampleFlags; + if (sampleFlagsPresent) sampleFlags = readU32Be(slice); + else { + assert(track.currentFragmentState.defaultSampleFlags !== null); + sampleFlags = track.currentFragmentState.defaultSampleFlags; + } + if (i === 0 && firstSampleFlags !== null) sampleFlags = firstSampleFlags; + let sampleCompositionTimeOffset = 0; + if (sampleCompositionTimeOffsetsPresent) sampleCompositionTimeOffset = readI32Be(slice); + const isKeyFrame = !(sampleFlags & 65536); + trackData.samples.push({ + presentationTimestamp: trackData.currentTimestamp + sampleCompositionTimeOffset, + duration: sampleDuration, + byteOffset: trackData.currentOffset, + byteSize: sampleSize, + isKeyFrame, + encryption: null + }); + trackData.currentOffset += sampleSize; + trackData.currentTimestamp += sampleDuration; + } + this.currentFragment.implicitBaseDataOffset = trackData.currentOffset; + } + break; + case "saiz": + { + const track = this.currentTrack; + if (!track || !track.encryptionInfo) break; + slice.skip(1); + if (readU24Be(slice) & 1) { + const auxInfoType = readAscii(slice, 4); + const auxInfoTypeParam = readU32Be(slice); + if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) break; + } + const defaultSampleInfoSize = readU8(slice); + const sampleCount = readU32Be(slice); + let sampleSizes = null; + if (defaultSampleInfoSize === 0 && sampleCount > 0) sampleSizes = readBytes(slice, sampleCount); + const aux = getOrCreateEncryptionAuxInfo(track); + aux.defaultSampleInfoSize = defaultSampleInfoSize; + aux.sampleSizes = sampleSizes; + aux.sampleCount = sampleCount; + } + break; + case "saio": + { + const track = this.currentTrack; + if (!track || !track.encryptionInfo) break; + const version = readU8(slice); + if (readU24Be(slice) & 1) { + const auxInfoType = readAscii(slice, 4); + const auxInfoTypeParam = readU32Be(slice); + if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) break; + } + const entryCount = readU32Be(slice); + if (entryCount === 0) break; + if (entryCount > 1) Logging._warn("Multiple saio entries are not supported; using the first offset only."); + let offset = version === 0 ? readU32Be(slice) : Number(readU64Be(slice)); + if (this.currentFragment) offset += this.currentFragment.moofOffset; + const aux = getOrCreateEncryptionAuxInfo(track); + aux.offset = offset; + } + break; + case "senc": + { + const track = this.currentTrack; + if (!track || !track.encryptionInfo) break; + assert(this.currentFragment); + const trackData = this.currentFragment.trackData.get(track.id); + if (!trackData) break; + slice.skip(1); + const flags = readU24Be(slice); + const useSubsamples = Boolean(flags & 2); + const sampleCount = readU32Be(slice); + const ivSize = track.encryptionInfo.defaultPerSampleIvSize; + assert(ivSize !== null); + for (let i = 0; i < Math.min(sampleCount, trackData.samples.length); i++) { + const iv = new Uint8Array(16); + if (ivSize > 0) iv.set(readBytes(slice, ivSize), 0); + else iv.set(track.encryptionInfo.defaultConstantIv, 0); + let subsamples = null; + if (useSubsamples) { + const subsampleCount = readU16Be(slice); + subsamples = []; + for (let j = 0; j < subsampleCount; j++) { + const clearLen = readU16Be(slice); + const protectedLen = readU32Be(slice); + subsamples.push({ + clearLen, + protectedLen + }); + } + } + const sample = trackData.samples[i]; + sample.encryption = { + iv, + subsamples + }; + } + } + break; + case "udta": + { + const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + for (const { boxInfo, slice } of iterator) { + if (boxInfo.name !== "meta" && !this.currentTrack) { + const startPos = slice.filePos; + this.metadataTags.raw ??= {}; + if (boxInfo.name[0] === "©") this.metadataTags.raw[boxInfo.name] ??= readMetadataStringShort(slice); + else this.metadataTags.raw[boxInfo.name] ??= readBytes(slice, boxInfo.contentSize); + slice.filePos = startPos; + } + switch (boxInfo.name) { + case "meta": + slice.skip(-boxInfo.headerSize); + this.traverseBox(slice); + break; + case "©nam": + case "name": + if (this.currentTrack) this.currentTrack.name = textDecoder.decode(readBytes(slice, boxInfo.contentSize)); + else this.metadataTags.title ??= readMetadataStringShort(slice); + break; + case "©des": + if (!this.currentTrack) this.metadataTags.description ??= readMetadataStringShort(slice); + break; + case "©ART": + if (!this.currentTrack) this.metadataTags.artist ??= readMetadataStringShort(slice); + break; + case "©alb": + if (!this.currentTrack) this.metadataTags.album ??= readMetadataStringShort(slice); + break; + case "albr": + if (!this.currentTrack) this.metadataTags.albumArtist ??= readMetadataStringShort(slice); + break; + case "©gen": + if (!this.currentTrack) this.metadataTags.genre ??= readMetadataStringShort(slice); + break; + case "©day": + if (!this.currentTrack) { + const date = new Date(readMetadataStringShort(slice)); + if (!Number.isNaN(date.getTime())) this.metadataTags.date ??= date; + } + break; + case "©cmt": + if (!this.currentTrack) this.metadataTags.comment ??= readMetadataStringShort(slice); + break; + case "©lyr": + if (!this.currentTrack) this.metadataTags.lyrics ??= readMetadataStringShort(slice); + break; + } + } + } + break; + case "meta": + { + if (this.currentTrack) break; + const isQuickTime = readU32Be(slice) !== 0; + this.currentMetadataKeys = /* @__PURE__ */ new Map(); + if (isQuickTime) this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + else this.readContiguousBoxes(slice.slice(contentStartPos + 4, boxInfo.contentSize - 4)); + this.currentMetadataKeys = null; + } + break; + case "keys": + { + if (!this.currentMetadataKeys) break; + slice.skip(4); + const entryCount = readU32Be(slice); + for (let i = 0; i < entryCount; i++) { + const keySize = readU32Be(slice); + slice.skip(4); + const keyName = textDecoder.decode(readBytes(slice, keySize - 8)); + this.currentMetadataKeys.set(i + 1, keyName); + } + } + break; + case "ilst": + { + if (!this.currentMetadataKeys) break; + const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize)); + for (const { boxInfo, slice } of iterator) { + let metadataKey = boxInfo.name; + const nameAsNumber = (metadataKey.charCodeAt(0) << 24) + (metadataKey.charCodeAt(1) << 16) + (metadataKey.charCodeAt(2) << 8) + metadataKey.charCodeAt(3); + if (this.currentMetadataKeys.has(nameAsNumber)) metadataKey = this.currentMetadataKeys.get(nameAsNumber); + const data = readDataBox(slice); + this.metadataTags.raw ??= {}; + this.metadataTags.raw[metadataKey] ??= data; + switch (metadataKey) { + case "©nam": + case "titl": + case "com.apple.quicktime.title": + case "title": + if (typeof data === "string") this.metadataTags.title ??= data; + break; + case "©des": + case "desc": + case "dscp": + case "com.apple.quicktime.description": + case "description": + if (typeof data === "string") this.metadataTags.description ??= data; + break; + case "©ART": + case "com.apple.quicktime.artist": + case "artist": + if (typeof data === "string") this.metadataTags.artist ??= data; + break; + case "©alb": + case "albm": + case "com.apple.quicktime.album": + case "album": + if (typeof data === "string") this.metadataTags.album ??= data; + break; + case "aART": + case "album_artist": + if (typeof data === "string") this.metadataTags.albumArtist ??= data; + break; + case "©cmt": + case "com.apple.quicktime.comment": + case "comment": + if (typeof data === "string") this.metadataTags.comment ??= data; + break; + case "©gen": + case "gnre": + case "com.apple.quicktime.genre": + case "genre": + if (typeof data === "string") this.metadataTags.genre ??= data; + break; + case "©lyr": + case "lyrics": + if (typeof data === "string") this.metadataTags.lyrics ??= data; + break; + case "©day": + case "rldt": + case "com.apple.quicktime.creationdate": + case "date": + if (typeof data === "string") { + const date = new Date(data); + if (!Number.isNaN(date.getTime())) this.metadataTags.date ??= date; + } + break; + case "tmpo": + if (data instanceof Uint8Array && data.length >= 2) { + const bpm = toDataView(data).getInt16(0, false); + if (bpm > 0) this.metadataTags.beatsPerMinute ??= bpm; + } + break; + case "covr": + case "com.apple.quicktime.artwork": + if (data instanceof RichImageData) { + this.metadataTags.images ??= []; + this.metadataTags.images.push({ + data: data.data, + kind: "coverFront", + mimeType: data.mimeType + }); + } else if (data instanceof Uint8Array) { + this.metadataTags.images ??= []; + this.metadataTags.images.push({ + data, + kind: "coverFront", + mimeType: "image/*" + }); + } + break; + case "track": + if (typeof data === "string") { + const parts = data.split("/"); + const trackNum = Number.parseInt(parts[0], 10); + const tracksTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(trackNum) && trackNum > 0) this.metadataTags.trackNumber ??= trackNum; + if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) this.metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "trkn": + if (data instanceof Uint8Array && data.length >= 6) { + const view = toDataView(data); + const trackNumber = view.getUint16(2, false); + const tracksTotal = view.getUint16(4, false); + if (trackNumber > 0) this.metadataTags.trackNumber ??= trackNumber; + if (tracksTotal > 0) this.metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "disc": + case "disk": + if (data instanceof Uint8Array && data.length >= 6) { + const view = toDataView(data); + const discNumber = view.getUint16(2, false); + const discNumberMax = view.getUint16(4, false); + if (discNumber > 0) this.metadataTags.discNumber ??= discNumber; + if (discNumberMax > 0) this.metadataTags.discsTotal ??= discNumberMax; + } + break; + } + } + } + break; + } + slice.filePos = boxEndPos; + return true; + } +}; +var IsobmffTrackBacking = class { + constructor(internalTrack) { + this.internalTrack = internalTrack; + this.packetToSampleIndex = /* @__PURE__ */ new WeakMap(); + this.packetToFragmentLocation = /* @__PURE__ */ new WeakMap(); + } + getId() { + return this.internalTrack.id; + } + getNumber() { + const demuxer = this.internalTrack.demuxer; + const trackType = this.internalTrack.trackBacking.getType(); + let number = 0; + for (const track of demuxer.tracks) { + if (track.trackBacking.getType() === trackType) number++; + if (track === this.internalTrack) break; + } + return number; + } + getCodec() { + throw new Error("Not implemented on base class."); + } + getInternalCodecId() { + return this.internalTrack.internalCodecId; + } + getName() { + return this.internalTrack.name; + } + getLanguageCode() { + return this.internalTrack.languageCode; + } + getTimeResolution() { + return this.internalTrack.timescale; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getDisposition() { + return this.internalTrack.disposition; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return this.internalTrack.maxBitrate; + } + getAverageBitrate() { + return this.internalTrack.avgBitrate; + } + async getDurationFromMetadata() { + const track = this.internalTrack; + if (track.durationInMediaTimescale <= 0) return null; + assert(track.trackBacking); + return ((await track.trackBacking.getFirstPacket({ metadataOnly: true }))?.timestamp ?? 0) + track.durationInMediaTimescale / track.timescale; + } + async getLiveRefreshInterval() { + return null; + } + async getFirstPacket(options) { + const regularPacket = await this.fetchPacketForSampleIndex(0, options); + if (regularPacket || !this.internalTrack.demuxer.isFragmented) return regularPacket; + return this.performFragmentedLookup(null, (fragment) => { + if (fragment.trackData.get(this.internalTrack.id)) return { + sampleIndex: 0, + correctSampleFound: true + }; + return { + sampleIndex: -1, + correctSampleFound: false + }; + }, -Infinity, Infinity, options); + } + mapTimestampIntoTimescale(timestamp) { + return roundIfAlmostInteger(timestamp * this.internalTrack.timescale) + this.internalTrack.editListOffset; + } + async getPacket(timestamp, options) { + const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp); + const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); + const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale); + const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options); + if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) return regularPacket; + return this.performFragmentedLookup(null, (fragment) => { + const trackData = fragment.trackData.get(this.internalTrack.id); + if (!trackData) return { + sampleIndex: -1, + correctSampleFound: false + }; + const index = binarySearchLessOrEqual(trackData.presentationTimestamps, timestampInTimescale, (x) => x.presentationTimestamp); + return { + sampleIndex: index !== -1 ? trackData.presentationTimestamps[index].sampleIndex : -1, + correctSampleFound: index !== -1 && timestampInTimescale < trackData.endTimestamp + }; + }, timestampInTimescale, timestampInTimescale, options); + } + async getNextPacket(packet, options) { + const regularSampleIndex = this.packetToSampleIndex.get(packet); + if (regularSampleIndex !== void 0) return this.fetchPacketForSampleIndex(regularSampleIndex + 1, options); + const locationInFragment = this.packetToFragmentLocation.get(packet); + if (locationInFragment === void 0) throw new Error("Packet was not created from this track."); + return this.performFragmentedLookup(locationInFragment.fragment, (fragment) => { + if (fragment === locationInFragment.fragment) { + const trackData = fragment.trackData.get(this.internalTrack.id); + if (locationInFragment.sampleIndex + 1 < trackData.samples.length) return { + sampleIndex: locationInFragment.sampleIndex + 1, + correctSampleFound: true + }; + } else if (fragment.trackData.get(this.internalTrack.id)) return { + sampleIndex: 0, + correctSampleFound: true + }; + return { + sampleIndex: -1, + correctSampleFound: false + }; + }, -Infinity, Infinity, options); + } + async getKeyPacket(timestamp, options) { + const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp); + const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); + const sampleIndex = getKeyframeSampleIndexForTimestamp(sampleTable, timestampInTimescale); + const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options); + if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) return regularPacket; + return this.performFragmentedLookup(null, (fragment) => { + const trackData = fragment.trackData.get(this.internalTrack.id); + if (!trackData) return { + sampleIndex: -1, + correctSampleFound: false + }; + const index = findLastIndex(trackData.presentationTimestamps, (x) => { + return trackData.samples[x.sampleIndex].isKeyFrame && x.presentationTimestamp <= timestampInTimescale; + }); + return { + sampleIndex: index !== -1 ? trackData.presentationTimestamps[index].sampleIndex : -1, + correctSampleFound: index !== -1 && timestampInTimescale < trackData.endTimestamp + }; + }, timestampInTimescale, timestampInTimescale, options); + } + async getNextKeyPacket(packet, options) { + const regularSampleIndex = this.packetToSampleIndex.get(packet); + if (regularSampleIndex !== void 0) { + const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack), regularSampleIndex); + return this.fetchPacketForSampleIndex(nextKeyFrameSampleIndex, options); + } + const locationInFragment = this.packetToFragmentLocation.get(packet); + if (locationInFragment === void 0) throw new Error("Packet was not created from this track."); + return this.performFragmentedLookup(locationInFragment.fragment, (fragment) => { + if (fragment === locationInFragment.fragment) { + const nextKeyFrameIndex = fragment.trackData.get(this.internalTrack.id).samples.findIndex((x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex); + if (nextKeyFrameIndex !== -1) return { + sampleIndex: nextKeyFrameIndex, + correctSampleFound: true + }; + } else { + const trackData = fragment.trackData.get(this.internalTrack.id); + if (trackData && trackData.firstKeyFrameTimestamp !== null) { + const keyFrameIndex = trackData.samples.findIndex((x) => x.isKeyFrame); + assert(keyFrameIndex !== -1); + return { + sampleIndex: keyFrameIndex, + correctSampleFound: true + }; + } + } + return { + sampleIndex: -1, + correctSampleFound: false + }; + }, -Infinity, Infinity, options); + } + async fetchPacketForSampleIndex(sampleIndex, options) { + if (sampleIndex === -1) return null; + const sampleInfo = getSampleInfo(this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack), sampleIndex); + if (!sampleInfo) return null; + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.internalTrack.demuxer.reader.requestSlice(sampleInfo.sampleOffset, sampleInfo.sampleSize); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + data = readBytes(slice, sampleInfo.sampleSize); + if (this.internalTrack.encryptionInfo) { + let sampleEncryption = null; + if (this.internalTrack.encryptionAuxInfo) { + const entries = await resolveEncryptionAuxInfo(this.internalTrack.demuxer.reader, this.internalTrack.encryptionInfo, this.internalTrack.encryptionAuxInfo); + if (sampleIndex < entries.length) sampleEncryption = entries[sampleIndex]; + } + sampleEncryption ??= getDefaultSampleEncryption(this.internalTrack.encryptionInfo); + if (sampleEncryption) data = await decryptSample(this.internalTrack, sampleEncryption, data, null); + } + } + const timestamp = (sampleInfo.presentationTimestamp - this.internalTrack.editListOffset) / this.internalTrack.timescale; + const duration = sampleInfo.duration / this.internalTrack.timescale; + const packet = new EncodedPacket(data, sampleInfo.isKeyFrame ? "key" : "delta", timestamp, duration, sampleIndex, sampleInfo.sampleSize); + this.packetToSampleIndex.set(packet, sampleIndex); + return packet; + } + async fetchPacketInFragment(fragment, sampleIndex, options) { + if (sampleIndex === -1) return null; + const fragmentSample = fragment.trackData.get(this.internalTrack.id).samples[sampleIndex]; + assert(fragmentSample); + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.internalTrack.demuxer.reader.requestSlice(fragmentSample.byteOffset, fragmentSample.byteSize); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + data = readBytes(slice, fragmentSample.byteSize); + if (this.internalTrack.encryptionInfo) { + const sampleEncryption = fragmentSample.encryption ?? getDefaultSampleEncryption(this.internalTrack.encryptionInfo); + if (sampleEncryption) data = await decryptSample(this.internalTrack, sampleEncryption, data, fragment); + } + } + const timestamp = (fragmentSample.presentationTimestamp - this.internalTrack.editListOffset) / this.internalTrack.timescale; + const duration = fragmentSample.duration / this.internalTrack.timescale; + const packet = new EncodedPacket(data, fragmentSample.isKeyFrame ? "key" : "delta", timestamp, duration, fragment.moofOffset + sampleIndex, fragmentSample.byteSize); + this.packetToFragmentLocation.set(packet, { + fragment, + sampleIndex + }); + return packet; + } + /** Looks for a packet in the fragments while trying to load as few fragments as possible to retrieve it. */ + async performFragmentedLookup(startFragment, getMatchInFragment, searchTimestamp, latestTimestamp, options) { + const demuxer = this.internalTrack.demuxer; + let currentFragment = null; + let bestFragment = null; + let bestSampleIndex = -1; + if (startFragment) { + const { sampleIndex, correctSampleFound } = getMatchInFragment(startFragment); + if (correctSampleFound) return this.fetchPacketInFragment(startFragment, sampleIndex, options); + if (sampleIndex !== -1) { + bestFragment = startFragment; + bestSampleIndex = sampleIndex; + } + } + const lookupEntryIndex = binarySearchLessOrEqual(this.internalTrack.fragmentLookupTable, searchTimestamp, (x) => x.timestamp); + const lookupEntry = lookupEntryIndex !== -1 ? this.internalTrack.fragmentLookupTable[lookupEntryIndex] : null; + const positionCacheIndex = binarySearchLessOrEqual(this.internalTrack.fragmentPositionCache, searchTimestamp, (x) => x.startTimestamp); + const positionCacheEntry = positionCacheIndex !== -1 ? this.internalTrack.fragmentPositionCache[positionCacheIndex] : null; + const lookupEntryPosition = Math.max(lookupEntry?.moofOffset ?? 0, positionCacheEntry?.moofOffset ?? 0) || null; + let currentPos; + if (!startFragment) currentPos = lookupEntryPosition ?? 0; + else if (lookupEntryPosition === null || startFragment.moofOffset >= lookupEntryPosition) { + currentPos = startFragment.moofOffset + startFragment.moofSize; + currentFragment = startFragment; + } else currentPos = lookupEntryPosition; + while (true) { + if (currentFragment) { + const trackData = currentFragment.trackData.get(this.internalTrack.id); + if (trackData && trackData.startTimestamp > latestTimestamp) break; + } + let slice = demuxer.reader.requestSliceRange(currentPos, 8, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const boxStartPos = currentPos; + const boxInfo = readBoxHeader(slice); + if (!boxInfo) break; + if (boxInfo.name === "moof") { + currentFragment = await demuxer.readFragment(boxStartPos); + const { sampleIndex, correctSampleFound } = getMatchInFragment(currentFragment); + if (correctSampleFound) return this.fetchPacketInFragment(currentFragment, sampleIndex, options); + if (sampleIndex !== -1) { + bestFragment = currentFragment; + bestSampleIndex = sampleIndex; + } + } + currentPos = boxStartPos + boxInfo.totalSize; + } + if (lookupEntry && (!bestFragment || bestFragment.moofOffset < lookupEntry.moofOffset)) { + const previousLookupEntry = this.internalTrack.fragmentLookupTable[lookupEntryIndex - 1]; + assert(!previousLookupEntry || previousLookupEntry.timestamp < lookupEntry.timestamp); + const newSearchTimestamp = previousLookupEntry?.timestamp ?? -Infinity; + return this.performFragmentedLookup(null, getMatchInFragment, newSearchTimestamp, latestTimestamp, options); + } + if (bestFragment) return this.fetchPacketInFragment(bestFragment, bestSampleIndex, options); + return null; + } +}; +var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking { + constructor(internalTrack) { + super(internalTrack); + this.decoderConfigPromise = null; + } + getType() { + return "video"; + } + getCodec() { + return this.internalTrack.info.codec; + } + getCodedWidth() { + return this.internalTrack.info.width; + } + getCodedHeight() { + return this.internalTrack.info.height; + } + getSquarePixelWidth() { + return this.internalTrack.info.squarePixelWidth; + } + getSquarePixelHeight() { + return this.internalTrack.info.squarePixelHeight; + } + getTransformationMatrix() { + return [...this.internalTrack.matrix]; + } + async getColorSpace() { + const decoderConfig = await this.getDecoderConfig(); + if (!decoderConfig) return this.internalTrack.info.colorSpace; + return { + primaries: decoderConfig.colorSpace?.primaries, + transfer: decoderConfig.colorSpace?.transfer, + matrix: decoderConfig.colorSpace?.matrix, + fullRange: decoderConfig.colorSpace?.fullRange + }; + } + async canBeTransparent() { + return this.internalTrack.info.codec === "prores" && (this.internalTrack.info.proresFormat === "ap4h" || this.internalTrack.info.proresFormat === "ap4x"); + } + async getDecoderConfig() { + if (!this.internalTrack.info.codec) return null; + return this.decoderConfigPromise ??= (async () => { + if (this.internalTrack.info.codec === "avc" && !this.internalTrack.info.codecDescription) { + const firstPacket = await this.getFirstPacket({}); + this.internalTrack.info.avcCodecInfo = firstPacket && extractAvcDecoderConfigurationRecord(firstPacket.data); + } else if (this.internalTrack.info.codec === "hevc" && !this.internalTrack.info.codecDescription) { + const firstPacket = await this.getFirstPacket({}); + this.internalTrack.info.hevcCodecInfo = firstPacket && extractHevcDecoderConfigurationRecord(firstPacket.data); + } else if (this.internalTrack.info.codec === "vp9" && (!this.internalTrack.info.vp9CodecInfo || !vp9CodecInfoHasColorInfo(this.internalTrack.info.vp9CodecInfo))) { + const firstPacket = await this.getFirstPacket({}); + const packetInfo = firstPacket && extractVp9CodecInfoFromPacket(firstPacket.data); + if (packetInfo) this.internalTrack.info.vp9CodecInfo = { + ...this.internalTrack.info.vp9CodecInfo ?? packetInfo, + videoFullRangeFlag: packetInfo.videoFullRangeFlag, + colourPrimaries: packetInfo.colourPrimaries, + transferCharacteristics: packetInfo.transferCharacteristics, + matrixCoefficients: packetInfo.matrixCoefficients + }; + } else if (this.internalTrack.info.codec === "av1" && (!this.internalTrack.info.av1CodecInfo || !av1CodecInfoHasColorInfo(this.internalTrack.info.av1CodecInfo))) { + const firstPacket = await this.getFirstPacket({}); + const packetInfo = firstPacket && extractAv1CodecInfoFromPacket(firstPacket.data); + if (packetInfo) this.internalTrack.info.av1CodecInfo = packetInfo; + } else if (this.internalTrack.info.codec === "prores" && !this.internalTrack.info.proresCodecInfo) { + const firstPacket = await this.getFirstPacket({}); + this.internalTrack.info.proresCodecInfo = firstPacket && extractProresCodecInfoFromPacket(firstPacket.data); + } + if (!colorSpaceIsComplete(this.internalTrack.info.colorSpace)) { + const colorSpace = extractColorSpace(this.internalTrack.info); + this.internalTrack.info.colorSpace.primaries ??= colorSpace.primaries; + this.internalTrack.info.colorSpace.transfer ??= colorSpace.transfer; + this.internalTrack.info.colorSpace.matrix ??= colorSpace.matrix; + this.internalTrack.info.colorSpace.fullRange ??= colorSpace.fullRange; + } + const config = { + codec: extractVideoCodecString(this.internalTrack.info), + codedWidth: this.internalTrack.info.width, + codedHeight: this.internalTrack.info.height, + description: this.internalTrack.info.codecDescription ?? void 0, + colorSpace: this.internalTrack.info.colorSpace + }; + if (this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth || this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight) { + config.displayAspectWidth = this.internalTrack.info.squarePixelWidth; + config.displayAspectHeight = this.internalTrack.info.squarePixelHeight; + } + return config; + })(); + } +}; +var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking { + constructor(internalTrack) { + super(internalTrack); + this.decoderConfigPromise = null; + } + getType() { + return "audio"; + } + getCodec() { + return this.internalTrack.info.codec; + } + getNumberOfChannels() { + return this.internalTrack.info.numberOfChannels; + } + getSampleRate() { + return this.internalTrack.info.sampleRate; + } + async getDecoderConfig() { + if (!this.internalTrack.info.codec) return null; + return this.decoderConfigPromise ??= (async () => { + if (this.internalTrack.info.codec === "dts" && !this.internalTrack.info.dtsFormat) { + const firstPacket = await this.getFirstPacket({}); + this.internalTrack.info.dtsFormat = firstPacket && extractDtsFourCcFromPacket(firstPacket.data); + } + return { + codec: extractAudioCodecString(this.internalTrack.info), + numberOfChannels: this.internalTrack.info.numberOfChannels, + sampleRate: this.internalTrack.info.sampleRate, + description: this.internalTrack.info.codecDescription ?? void 0 + }; + })(); + } +}; +var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => { + if (sampleTable.presentationTimestamps) { + const index = binarySearchLessOrEqual(sampleTable.presentationTimestamps, timescaleUnits, (x) => x.presentationTimestamp); + if (index === -1) return -1; + return sampleTable.presentationTimestamps[index].sampleIndex; + } else { + const index = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, timescaleUnits, (x) => x.startDecodeTimestamp); + if (index === -1) return -1; + const entry = sampleTable.sampleTimingEntries[index]; + return entry.startIndex + Math.min(Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta), entry.count - 1); + } +}; +var getKeyframeSampleIndexForTimestamp = (sampleTable, timescaleUnits) => { + if (!sampleTable.keySampleIndices) return getSampleIndexForTimestamp(sampleTable, timescaleUnits); + if (sampleTable.presentationTimestamps) { + const index = binarySearchLessOrEqual(sampleTable.presentationTimestamps, timescaleUnits, (x) => x.presentationTimestamp); + if (index === -1) return -1; + for (let i = index; i >= 0; i--) { + const sampleIndex = sampleTable.presentationTimestamps[i].sampleIndex; + if (binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1) return sampleIndex; + } + return -1; + } else { + const sampleIndex = getSampleIndexForTimestamp(sampleTable, timescaleUnits); + const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); + return sampleTable.keySampleIndices[index] ?? -1; + } +}; +var getSampleInfo = (sampleTable, sampleIndex) => { + const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, (x) => x.startIndex); + const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex]; + if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) return null; + let presentationTimestamp = timingEntry.startDecodeTimestamp + (sampleIndex - timingEntry.startIndex) * timingEntry.delta; + const offsetEntryIndex = binarySearchLessOrEqual(sampleTable.sampleCompositionTimeOffsets, sampleIndex, (x) => x.startIndex); + const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex]; + if (offsetEntry && sampleIndex - offsetEntry.startIndex < offsetEntry.count) presentationTimestamp += offsetEntry.offset; + const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]; + const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, (x) => x.startSampleIndex); + const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex]; + assert(chunkEntry); + const chunkIndex = chunkEntry.startChunkIndex + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk); + const chunkOffset = sampleTable.chunkOffsets[chunkIndex]; + const startSampleIndexOfChunk = chunkEntry.startSampleIndex + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk; + let chunkSize = 0; + let sampleOffset = chunkOffset; + if (sampleTable.sampleSizes.length === 1) { + sampleOffset += sampleSize * (sampleIndex - startSampleIndexOfChunk); + chunkSize += sampleSize * chunkEntry.samplesPerChunk; + } else for (let i = startSampleIndexOfChunk; i < startSampleIndexOfChunk + chunkEntry.samplesPerChunk; i++) { + const sampleSize = sampleTable.sampleSizes[i]; + if (i < sampleIndex) sampleOffset += sampleSize; + chunkSize += sampleSize; + } + let duration = timingEntry.delta; + if (sampleTable.presentationTimestamps) { + const presentationIndex = sampleTable.presentationTimestampIndexMap[sampleIndex]; + assert(presentationIndex !== void 0); + if (presentationIndex < sampleTable.presentationTimestamps.length - 1) duration = sampleTable.presentationTimestamps[presentationIndex + 1].presentationTimestamp - presentationTimestamp; + } + return { + presentationTimestamp, + duration, + sampleOffset, + sampleSize, + chunkOffset, + chunkSize, + isKeyFrame: sampleTable.keySampleIndices ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1 : true + }; +}; +var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => { + if (!sampleTable.keySampleIndices) return sampleIndex + 1; + const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); + return sampleTable.keySampleIndices[index + 1] ?? -1; +}; +var offsetFragmentTrackDataByTimestamp = (trackData, timestamp) => { + trackData.startTimestamp += timestamp; + trackData.endTimestamp += timestamp; + for (const sample of trackData.samples) sample.presentationTimestamp += timestamp; + for (const entry of trackData.presentationTimestamps) entry.presentationTimestamp += timestamp; +}; +var readMatrix = (slice) => { + return [ + readFixed_16_16(slice), + readFixed_16_16(slice), + readFixed_2_30(slice), + readFixed_16_16(slice), + readFixed_16_16(slice), + readFixed_2_30(slice), + readFixed_16_16(slice), + readFixed_16_16(slice), + readFixed_2_30(slice) + ]; +}; +var applySpsPixelAspectRatio = (track, pixelAspectRatio) => { + const info = track.info; + if (info.type !== "video") return; + const { num, den } = pixelAspectRatio; + if (!(num > 0 && den > 0) || num === den) return; + if (num > den) { + info.squarePixelWidth = Math.round(info.width * num / den); + info.squarePixelHeight = info.height; + } else { + info.squarePixelWidth = info.width; + info.squarePixelHeight = Math.round(info.height * den / num); + } +}; +var sampleTableIsEmpty = (sampleTable) => { + return sampleTable.sampleSizes.length === 0; +}; +var getOrCreateEncryptionAuxInfo = (track) => { + if (track.currentFragmentState) return track.currentFragmentState.encryptionAuxInfo ??= { + defaultSampleInfoSize: 0, + sampleSizes: null, + sampleCount: 0, + offset: null, + resolved: null + }; + else return track.encryptionAuxInfo ??= { + defaultSampleInfoSize: 0, + sampleSizes: null, + sampleCount: 0, + offset: null, + resolved: null + }; +}; +var resolveEncryptionAuxInfo = async (reader, encryptionInfo, aux) => { + if (aux.resolved) return aux.resolved; + if (aux.offset === null || aux.sampleCount === 0) throw new Error("Incomplete saiz/saio info; cannot resolve encryption data."); + let totalSize = 0; + if (aux.defaultSampleInfoSize > 0) totalSize = aux.defaultSampleInfoSize * aux.sampleCount; + else { + assert(aux.sampleSizes); + for (let i = 0; i < aux.sampleCount; i++) totalSize += aux.sampleSizes[i]; + } + let slice = reader.requestSlice(aux.offset, totalSize); + if (isThenable(slice)) slice = await slice; + if (!slice) throw new Error("Failed to read auxiliary encryption info."); + const ivSize = encryptionInfo.defaultPerSampleIvSize; + assert(ivSize !== null); + const entries = []; + for (let i = 0; i < aux.sampleCount; i++) { + const entrySize = aux.defaultSampleInfoSize > 0 ? aux.defaultSampleInfoSize : aux.sampleSizes[i]; + const iv = new Uint8Array(16); + if (ivSize > 0) iv.set(readBytes(slice, ivSize), 0); + else iv.set(encryptionInfo.defaultConstantIv, 0); + let subsamples = null; + if (entrySize > ivSize) { + const subsampleCount = readU16Be(slice); + subsamples = []; + for (let j = 0; j < subsampleCount; j++) { + const clearLen = readU16Be(slice); + const protectedLen = readU32Be(slice); + subsamples.push({ + clearLen, + protectedLen + }); + } + } + entries.push({ + iv, + subsamples + }); + } + aux.resolved = entries; + return entries; +}; +var getDefaultSampleEncryption = (encryptionInfo) => { + if (!encryptionInfo.defaultConstantIv) return null; + return { + iv: encryptionInfo.defaultConstantIv, + subsamples: null + }; +}; +var decryptSample = async (track, sampleEncryption, data, fragment) => { + assert(track.encryptionInfo); + const encryptionInfo = track.encryptionInfo; + assert(encryptionInfo.defaultKid !== null); + const keyId = encryptionInfo.defaultKid; + let keyBytes; + const cacheEntry = track.demuxer.decryptionKeyCache.get(keyId); + if (cacheEntry) keyBytes = await cacheEntry; + else { + if (!track.demuxer.input._formatOptions.isobmff?.resolveKeyId) throw new Error("Encrypted media samples encountered. To decrypt them, please provide a callback for InputOptions.formatOptions.isobmff.resolveKeyId."); + const promise = (async () => { + let psshBoxes = track.demuxer.psshBoxes; + if (fragment) { + psshBoxes = [...psshBoxes, ...fragment.psshBoxes].filter((x) => x.keyIds === null || x.keyIds.includes(keyId)); + for (let i = 0; i < psshBoxes.length - 1; i++) for (let j = i + 1; j < psshBoxes.length; j++) if (psshBoxesAreEqual(psshBoxes[i], psshBoxes[j])) { + psshBoxes.splice(j, 1); + j--; + } + } + const keyResult = await track.demuxer.input._formatOptions.isobmff.resolveKeyId({ + keyId, + psshBoxes + }); + if (!(typeof keyResult === "string" && keyResult.length === 32 && HEX_STRING_REGEX.test(keyResult) || keyResult instanceof Uint8Array && keyResult.byteLength === 16)) throw new TypeError("resolveKeyId must return a 32-character hex string or a 16-byte Uint8Array containing the decryption key."); + return keyResult instanceof Uint8Array ? keyResult : hexStringToBytes(keyResult); + })(); + track.demuxer.decryptionKeyCache.set(keyId, promise); + keyBytes = await promise; + } + if (encryptionInfo.scheme === "cenc" || encryptionInfo.scheme === "cens") return decryptCtr(keyBytes, encryptionInfo, sampleEncryption, data); + else return decryptCbcs(keyBytes, encryptionInfo, sampleEncryption, data); +}; +var decryptCtr = async (key, encryptionInfo, sampleEncryption, data) => { + const counter = new Uint8Array(16); + counter.set(sampleEncryption.iv, 0); + const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CTR" }, false, ["decrypt"]); + const cryptApply = async (input) => { + const plaintext = await crypto.subtle.decrypt({ + name: "AES-CTR", + counter, + length: 64 + }, cryptoKey, input); + return new Uint8Array(plaintext); + }; + if (!sampleEncryption.subsamples) return cryptApply(data); + assert(encryptionInfo.defaultCryptByteBlock !== null && encryptionInfo.defaultSkipByteBlock !== null); + const cryptRanges = collectCryptRanges(sampleEncryption.subsamples, encryptionInfo.defaultCryptByteBlock, encryptionInfo.defaultSkipByteBlock); + let totalCryptLen = 0; + for (const range of cryptRanges) for (const seg of range.perSubsample) totalCryptLen += seg.length; + const cryptBuffer = new Uint8Array(totalCryptLen); + let writePos = 0; + for (const range of cryptRanges) for (const seg of range.perSubsample) { + cryptBuffer.set(data.subarray(seg.offset, seg.offset + seg.length), writePos); + writePos += seg.length; + } + const plain = await cryptApply(cryptBuffer); + const output = new Uint8Array(data); + let readPos = 0; + for (const range of cryptRanges) for (const seg of range.perSubsample) { + output.set(plain.subarray(readPos, readPos + seg.length), seg.offset); + readPos += seg.length; + } + return output; +}; +var decryptCbcs = (key, encryptionInfo, sampleEncryption, data) => { + const ctx = new Aes128CbcContext(); + ctx.init({ + key, + iv: sampleEncryption.iv + }); + const cryptByteBlock = encryptionInfo.defaultCryptByteBlock; + const skipByteBlock = encryptionInfo.defaultSkipByteBlock; + assert(cryptByteBlock !== null && skipByteBlock !== null); + if (!sampleEncryption.subsamples) { + const output = new Uint8Array(data); + const numBlocks = Math.floor(data.length / 16); + for (let b = 0; b < numBlocks; b++) { + const off = b * 16; + ctx.in.set(data.subarray(off, off + 16)); + ctx.decrypt(); + output.set(ctx.out, off); + } + return output; + } + if (cryptByteBlock === 0 && skipByteBlock === 0) throw new Error("cbcs with subsamples requires pattern encryption."); + const output = new Uint8Array(data); + const cryptRanges = collectCryptRanges(sampleEncryption.subsamples, cryptByteBlock, skipByteBlock); + const ivView = new DataView(sampleEncryption.iv.buffer, sampleEncryption.iv.byteOffset, 16); + for (const range of cryptRanges) { + ctx.iv[0] = ivView.getUint32(0, false); + ctx.iv[1] = ivView.getUint32(4, false); + ctx.iv[2] = ivView.getUint32(8, false); + ctx.iv[3] = ivView.getUint32(12, false); + for (const seg of range.perSubsample) { + const numBlocks = seg.length / 16; + for (let b = 0; b < numBlocks; b++) { + const offset = seg.offset + b * 16; + ctx.in.set(data.subarray(offset, offset + 16)); + ctx.decrypt(); + output.set(ctx.out, offset); + } + } + } + return output; +}; +var collectCryptRanges = (subsamples, cryptByteBlock, skipByteBlock) => { + const ranges = []; + const hasPattern = cryptByteBlock !== 0 || skipByteBlock !== 0; + let cursor = 0; + for (const subsample of subsamples) { + cursor += subsample.clearLen; + const perSubsample = []; + if (!hasPattern) { + if (subsample.protectedLen > 0) perSubsample.push({ + offset: cursor, + length: subsample.protectedLen + }); + cursor += subsample.protectedLen; + } else { + let remaining = subsample.protectedLen; + let pos = cursor; + while (remaining > 0) { + if (remaining < 16 * cryptByteBlock) break; + const cryptBytes = 16 * cryptByteBlock; + perSubsample.push({ + offset: pos, + length: cryptBytes + }); + pos += cryptBytes; + remaining -= cryptBytes; + const skipBytes = Math.min(16 * skipByteBlock, remaining); + pos += skipBytes; + remaining -= skipBytes; + } + cursor += subsample.protectedLen; + } + ranges.push({ perSubsample }); + } + return ranges; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/matroska/ebml.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** Wrapper around a number to be able to differentiate it in the writer. */ +var EBMLFloat32 = class { + constructor(value) { + this.value = value; + } +}; +/** Wrapper around a number to be able to differentiate it in the writer. */ +var EBMLFloat64 = class { + constructor(value) { + this.value = value; + } +}; +/** Wrapper around a number to be able to differentiate it in the writer. */ +var EBMLSignedInt = class { + constructor(value) { + this.value = value; + } +}; +var EBMLUnicodeString = class { + constructor(value) { + this.value = value; + } +}; +/** Defines some of the EBML IDs used by Matroska files. */ +var EBMLId; +(function(EBMLId) { + EBMLId[EBMLId["EBML"] = 440786851] = "EBML"; + EBMLId[EBMLId["EBMLVersion"] = 17030] = "EBMLVersion"; + EBMLId[EBMLId["EBMLReadVersion"] = 17143] = "EBMLReadVersion"; + EBMLId[EBMLId["EBMLMaxIDLength"] = 17138] = "EBMLMaxIDLength"; + EBMLId[EBMLId["EBMLMaxSizeLength"] = 17139] = "EBMLMaxSizeLength"; + EBMLId[EBMLId["DocType"] = 17026] = "DocType"; + EBMLId[EBMLId["DocTypeVersion"] = 17031] = "DocTypeVersion"; + EBMLId[EBMLId["DocTypeReadVersion"] = 17029] = "DocTypeReadVersion"; + EBMLId[EBMLId["Void"] = 236] = "Void"; + EBMLId[EBMLId["Segment"] = 408125543] = "Segment"; + EBMLId[EBMLId["SeekHead"] = 290298740] = "SeekHead"; + EBMLId[EBMLId["Seek"] = 19899] = "Seek"; + EBMLId[EBMLId["SeekID"] = 21419] = "SeekID"; + EBMLId[EBMLId["SeekPosition"] = 21420] = "SeekPosition"; + EBMLId[EBMLId["Duration"] = 17545] = "Duration"; + EBMLId[EBMLId["Info"] = 357149030] = "Info"; + EBMLId[EBMLId["TimestampScale"] = 2807729] = "TimestampScale"; + EBMLId[EBMLId["MuxingApp"] = 19840] = "MuxingApp"; + EBMLId[EBMLId["WritingApp"] = 22337] = "WritingApp"; + EBMLId[EBMLId["Tracks"] = 374648427] = "Tracks"; + EBMLId[EBMLId["TrackEntry"] = 174] = "TrackEntry"; + EBMLId[EBMLId["TrackNumber"] = 215] = "TrackNumber"; + EBMLId[EBMLId["TrackUID"] = 29637] = "TrackUID"; + EBMLId[EBMLId["TrackType"] = 131] = "TrackType"; + EBMLId[EBMLId["FlagEnabled"] = 185] = "FlagEnabled"; + EBMLId[EBMLId["FlagDefault"] = 136] = "FlagDefault"; + EBMLId[EBMLId["FlagForced"] = 21930] = "FlagForced"; + EBMLId[EBMLId["FlagOriginal"] = 21934] = "FlagOriginal"; + EBMLId[EBMLId["FlagHearingImpaired"] = 21931] = "FlagHearingImpaired"; + EBMLId[EBMLId["FlagVisualImpaired"] = 21932] = "FlagVisualImpaired"; + EBMLId[EBMLId["FlagCommentary"] = 21935] = "FlagCommentary"; + EBMLId[EBMLId["FlagLacing"] = 156] = "FlagLacing"; + EBMLId[EBMLId["Name"] = 21358] = "Name"; + EBMLId[EBMLId["Language"] = 2274716] = "Language"; + EBMLId[EBMLId["LanguageBCP47"] = 2274717] = "LanguageBCP47"; + EBMLId[EBMLId["CodecID"] = 134] = "CodecID"; + EBMLId[EBMLId["CodecPrivate"] = 25506] = "CodecPrivate"; + EBMLId[EBMLId["CodecDelay"] = 22186] = "CodecDelay"; + EBMLId[EBMLId["SeekPreRoll"] = 22203] = "SeekPreRoll"; + EBMLId[EBMLId["DefaultDuration"] = 2352003] = "DefaultDuration"; + EBMLId[EBMLId["Video"] = 224] = "Video"; + EBMLId[EBMLId["PixelWidth"] = 176] = "PixelWidth"; + EBMLId[EBMLId["PixelHeight"] = 186] = "PixelHeight"; + EBMLId[EBMLId["DisplayWidth"] = 21680] = "DisplayWidth"; + EBMLId[EBMLId["DisplayHeight"] = 21690] = "DisplayHeight"; + EBMLId[EBMLId["DisplayUnit"] = 21682] = "DisplayUnit"; + EBMLId[EBMLId["AlphaMode"] = 21440] = "AlphaMode"; + EBMLId[EBMLId["Audio"] = 225] = "Audio"; + EBMLId[EBMLId["SamplingFrequency"] = 181] = "SamplingFrequency"; + EBMLId[EBMLId["Channels"] = 159] = "Channels"; + EBMLId[EBMLId["BitDepth"] = 25188] = "BitDepth"; + EBMLId[EBMLId["SimpleBlock"] = 163] = "SimpleBlock"; + EBMLId[EBMLId["BlockGroup"] = 160] = "BlockGroup"; + EBMLId[EBMLId["Block"] = 161] = "Block"; + EBMLId[EBMLId["BlockAdditions"] = 30113] = "BlockAdditions"; + EBMLId[EBMLId["BlockMore"] = 166] = "BlockMore"; + EBMLId[EBMLId["BlockAdditional"] = 165] = "BlockAdditional"; + EBMLId[EBMLId["BlockAddID"] = 238] = "BlockAddID"; + EBMLId[EBMLId["BlockDuration"] = 155] = "BlockDuration"; + EBMLId[EBMLId["ReferenceBlock"] = 251] = "ReferenceBlock"; + EBMLId[EBMLId["Cluster"] = 524531317] = "Cluster"; + EBMLId[EBMLId["Timestamp"] = 231] = "Timestamp"; + EBMLId[EBMLId["Cues"] = 475249515] = "Cues"; + EBMLId[EBMLId["CuePoint"] = 187] = "CuePoint"; + EBMLId[EBMLId["CueTime"] = 179] = "CueTime"; + EBMLId[EBMLId["CueTrackPositions"] = 183] = "CueTrackPositions"; + EBMLId[EBMLId["CueTrack"] = 247] = "CueTrack"; + EBMLId[EBMLId["CueClusterPosition"] = 241] = "CueClusterPosition"; + EBMLId[EBMLId["Colour"] = 21936] = "Colour"; + EBMLId[EBMLId["MatrixCoefficients"] = 21937] = "MatrixCoefficients"; + EBMLId[EBMLId["TransferCharacteristics"] = 21946] = "TransferCharacteristics"; + EBMLId[EBMLId["Primaries"] = 21947] = "Primaries"; + EBMLId[EBMLId["Range"] = 21945] = "Range"; + EBMLId[EBMLId["Projection"] = 30320] = "Projection"; + EBMLId[EBMLId["ProjectionType"] = 30321] = "ProjectionType"; + EBMLId[EBMLId["ProjectionPoseYaw"] = 30323] = "ProjectionPoseYaw"; + EBMLId[EBMLId["ProjectionPosePitch"] = 30324] = "ProjectionPosePitch"; + EBMLId[EBMLId["ProjectionPoseRoll"] = 30325] = "ProjectionPoseRoll"; + EBMLId[EBMLId["Attachments"] = 423732329] = "Attachments"; + EBMLId[EBMLId["AttachedFile"] = 24999] = "AttachedFile"; + EBMLId[EBMLId["FileDescription"] = 18046] = "FileDescription"; + EBMLId[EBMLId["FileName"] = 18030] = "FileName"; + EBMLId[EBMLId["FileMediaType"] = 18016] = "FileMediaType"; + EBMLId[EBMLId["FileData"] = 18012] = "FileData"; + EBMLId[EBMLId["FileUID"] = 18094] = "FileUID"; + EBMLId[EBMLId["Chapters"] = 272869232] = "Chapters"; + EBMLId[EBMLId["Tags"] = 307544935] = "Tags"; + EBMLId[EBMLId["Tag"] = 29555] = "Tag"; + EBMLId[EBMLId["Targets"] = 25536] = "Targets"; + EBMLId[EBMLId["TargetTypeValue"] = 26826] = "TargetTypeValue"; + EBMLId[EBMLId["TargetType"] = 25546] = "TargetType"; + EBMLId[EBMLId["TagTrackUID"] = 25541] = "TagTrackUID"; + EBMLId[EBMLId["TagEditionUID"] = 25545] = "TagEditionUID"; + EBMLId[EBMLId["TagChapterUID"] = 25540] = "TagChapterUID"; + EBMLId[EBMLId["TagAttachmentUID"] = 25542] = "TagAttachmentUID"; + EBMLId[EBMLId["SimpleTag"] = 26568] = "SimpleTag"; + EBMLId[EBMLId["TagName"] = 17827] = "TagName"; + EBMLId[EBMLId["TagLanguage"] = 17530] = "TagLanguage"; + EBMLId[EBMLId["TagString"] = 17543] = "TagString"; + EBMLId[EBMLId["TagBinary"] = 17541] = "TagBinary"; + EBMLId[EBMLId["ContentEncodings"] = 28032] = "ContentEncodings"; + EBMLId[EBMLId["ContentEncoding"] = 25152] = "ContentEncoding"; + EBMLId[EBMLId["ContentEncodingOrder"] = 20529] = "ContentEncodingOrder"; + EBMLId[EBMLId["ContentEncodingScope"] = 20530] = "ContentEncodingScope"; + EBMLId[EBMLId["ContentCompression"] = 20532] = "ContentCompression"; + EBMLId[EBMLId["ContentCompAlgo"] = 16980] = "ContentCompAlgo"; + EBMLId[EBMLId["ContentCompSettings"] = 16981] = "ContentCompSettings"; + EBMLId[EBMLId["ContentEncryption"] = 20533] = "ContentEncryption"; +})(EBMLId || (EBMLId = {})); +var LEVEL_0_EBML_IDS = [EBMLId.EBML, EBMLId.Segment]; +var LEVEL_1_EBML_IDS = [ + EBMLId.SeekHead, + EBMLId.Info, + EBMLId.Cluster, + EBMLId.Tracks, + EBMLId.Cues, + EBMLId.Attachments, + EBMLId.Chapters, + EBMLId.Tags +]; +var LEVEL_0_AND_1_EBML_IDS = [...LEVEL_0_EBML_IDS, ...LEVEL_1_EBML_IDS]; +var measureUnsignedInt = (value) => { + if (value < 256) return 1; + else if (value < 65536) return 2; + else if (value < 1 << 24) return 3; + else if (value < 2 ** 32) return 4; + else if (value < 2 ** 40) return 5; + else return 6; +}; +var measureUnsignedBigInt = (value) => { + if (value < 1n << 8n) return 1; + else if (value < 1n << 16n) return 2; + else if (value < 1n << 24n) return 3; + else if (value < 1n << 32n) return 4; + else if (value < 1n << 40n) return 5; + else if (value < 1n << 48n) return 6; + else if (value < 1n << 56n) return 7; + else return 8; +}; +var measureSignedInt = (value) => { + if (value >= -64 && value < 64) return 1; + else if (value >= -8192 && value < 8192) return 2; + else if (value >= -1048576 && value < 1 << 20) return 3; + else if (value >= -134217728 && value < 1 << 27) return 4; + else if (value >= -(2 ** 34) && value < 2 ** 34) return 5; + else return 6; +}; +var measureVarInt = (value) => { + if (value < 127) + /** Top bit is set, leaving 7 bits to hold the integer, but we can't store + * 127 because "all bits set to one" is a reserved value. Same thing for the + * other cases below: + */ + return 1; + else if (value < 16383) return 2; + else if (value < (1 << 21) - 1) return 3; + else if (value < (1 << 28) - 1) return 4; + else if (value < 2 ** 35 - 1) return 5; + else if (value < 2 ** 42 - 1) return 6; + else throw new Error("EBML varint size not supported " + value); +}; +var EBMLWriter = class { + constructor(writer) { + this.writer = writer; + this.helper = new Uint8Array(8); + this.helperView = new DataView(this.helper.buffer); + /** + * Stores the position from the start of the file to where EBML elements have been written. This is used to + * rewrite/edit elements that were already added before, and to measure sizes of things. + */ + this.offsets = /* @__PURE__ */ new WeakMap(); + /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */ + this.dataOffsets = /* @__PURE__ */ new WeakMap(); + } + writeByte(value) { + this.helperView.setUint8(0, value); + this.writer.write(this.helper.subarray(0, 1)); + } + writeFloat32(value) { + this.helperView.setFloat32(0, value, false); + this.writer.write(this.helper.subarray(0, 4)); + } + writeFloat64(value) { + this.helperView.setFloat64(0, value, false); + this.writer.write(this.helper); + } + writeUnsignedInt(value, width = measureUnsignedInt(value)) { + let pos = 0; + switch (width) { + case 6: this.helperView.setUint8(pos++, value / 2 ** 40 | 0); + case 5: this.helperView.setUint8(pos++, value / 2 ** 32 | 0); + case 4: this.helperView.setUint8(pos++, value >> 24); + case 3: this.helperView.setUint8(pos++, value >> 16); + case 2: this.helperView.setUint8(pos++, value >> 8); + case 1: + this.helperView.setUint8(pos++, value); + break; + default: throw new Error("Bad unsigned int size " + width); + } + this.writer.write(this.helper.subarray(0, pos)); + } + writeUnsignedBigInt(value, width = measureUnsignedBigInt(value)) { + let pos = 0; + for (let i = width - 1; i >= 0; i--) this.helperView.setUint8(pos++, Number(value >> BigInt(i * 8) & 255n)); + this.writer.write(this.helper.subarray(0, pos)); + } + writeSignedInt(value, width = measureSignedInt(value)) { + if (value < 0) value += 2 ** (width * 8); + this.writeUnsignedInt(value, width); + } + writeVarInt(value, width = measureVarInt(value)) { + let pos = 0; + switch (width) { + case 1: + this.helperView.setUint8(pos++, 128 | value); + break; + case 2: + this.helperView.setUint8(pos++, 64 | value >> 8); + this.helperView.setUint8(pos++, value); + break; + case 3: + this.helperView.setUint8(pos++, 32 | value >> 16); + this.helperView.setUint8(pos++, value >> 8); + this.helperView.setUint8(pos++, value); + break; + case 4: + this.helperView.setUint8(pos++, 16 | value >> 24); + this.helperView.setUint8(pos++, value >> 16); + this.helperView.setUint8(pos++, value >> 8); + this.helperView.setUint8(pos++, value); + break; + case 5: + /** + * JavaScript converts its doubles to 32-bit integers for bitwise + * operations, so we need to do a division by 2^32 instead of a + * right-shift of 32 to retain those top 3 bits + */ + this.helperView.setUint8(pos++, 8 | value / 2 ** 32 & 7); + this.helperView.setUint8(pos++, value >> 24); + this.helperView.setUint8(pos++, value >> 16); + this.helperView.setUint8(pos++, value >> 8); + this.helperView.setUint8(pos++, value); + break; + case 6: + this.helperView.setUint8(pos++, 4 | value / 2 ** 40 & 3); + this.helperView.setUint8(pos++, value / 2 ** 32 | 0); + this.helperView.setUint8(pos++, value >> 24); + this.helperView.setUint8(pos++, value >> 16); + this.helperView.setUint8(pos++, value >> 8); + this.helperView.setUint8(pos++, value); + break; + default: throw new Error("Bad EBML varint size " + width); + } + this.writer.write(this.helper.subarray(0, pos)); + } + writeAsciiString(str) { + this.writer.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); + } + writeEBML(data) { + if (data === null) return; + if (data instanceof Uint8Array) this.writer.write(data); + else if (Array.isArray(data)) for (const elem of data) this.writeEBML(elem); + else { + this.offsets.set(data, this.writer.getPos()); + this.writeUnsignedInt(data.id); + if (Array.isArray(data.data)) { + const sizePos = this.writer.getPos(); + const sizeSize = data.size === -1 ? 1 : data.size ?? 4; + if (data.size === -1) this.writeByte(255); + else this.writer.seek(this.writer.getPos() + sizeSize); + const startPos = this.writer.getPos(); + this.dataOffsets.set(data, startPos); + this.writeEBML(data.data); + if (data.size !== -1) { + const size = this.writer.getPos() - startPos; + const endPos = this.writer.getPos(); + this.writer.seek(sizePos); + this.writeVarInt(size, sizeSize); + this.writer.seek(endPos); + } + } else if (typeof data.data === "number") { + const size = data.size ?? measureUnsignedInt(data.data); + this.writeVarInt(size); + this.writeUnsignedInt(data.data, size); + } else if (typeof data.data === "bigint") { + const size = data.size ?? measureUnsignedBigInt(data.data); + this.writeVarInt(size); + this.writeUnsignedBigInt(data.data, size); + } else if (typeof data.data === "string") { + this.writeVarInt(data.data.length); + this.writeAsciiString(data.data); + } else if (data.data instanceof Uint8Array) { + this.writeVarInt(data.data.byteLength, data.size); + this.writer.write(data.data); + } else if (data.data instanceof EBMLFloat32) { + this.writeVarInt(4); + this.writeFloat32(data.data.value); + } else if (data.data instanceof EBMLFloat64) { + this.writeVarInt(8); + this.writeFloat64(data.data.value); + } else if (data.data instanceof EBMLSignedInt) { + const size = data.size ?? measureSignedInt(data.data.value); + this.writeVarInt(size); + this.writeSignedInt(data.data.value, size); + } else if (data.data instanceof EBMLUnicodeString) { + const bytes = textEncoder.encode(data.data.value); + this.writeVarInt(bytes.length); + this.writer.write(bytes); + } else assertNever(data.data); + } + } +}; +var readVarIntSize = (slice) => { + if (slice.remainingLength < 1) return null; + const firstByte = readU8(slice); + slice.skip(-1); + if (firstByte === 0) return null; + let width = 1; + let mask = 128; + while ((firstByte & mask) === 0) { + width++; + mask >>= 1; + } + if (slice.remainingLength < width) return null; + return width; +}; +var readVarInt = (slice) => { + if (slice.remainingLength < 1) return null; + const firstByte = readU8(slice); + if (firstByte === 0) return null; + let width = 1; + let mask = 128; + while ((firstByte & mask) === 0) { + width++; + mask >>= 1; + } + if (slice.remainingLength < width - 1) return null; + let value = firstByte & mask - 1; + for (let i = 1; i < width; i++) { + value *= 256; + value += readU8(slice); + } + return value; +}; +var readUnsignedInt = (slice, width) => { + if (width < 1 || width > 8) throw new Error("Bad unsigned int size " + width); + let value = 0; + for (let i = 0; i < width; i++) { + value *= 256; + value += readU8(slice); + } + return value; +}; +var readUnsignedBigInt = (slice, width) => { + if (width < 1) throw new Error("Bad unsigned int size " + width); + let value = 0n; + for (let i = 0; i < width; i++) { + value <<= 8n; + value += BigInt(readU8(slice)); + } + return value; +}; +var readElementId = (slice) => { + const size = readVarIntSize(slice); + if (size === null) return null; + if (slice.remainingLength < size) return null; + return readUnsignedInt(slice, size); +}; +/** Returns `undefined` to indicate the EBML undefined size. Returns `null` if the size couldn't be read. */ +var readElementSize = (slice) => { + if (slice.remainingLength < 1) return null; + if (readU8(slice) === 255) return; + slice.skip(-1); + const size = readVarInt(slice); + if (size === null) return null; + if (size === 72057594037927940) return; + return size; +}; +var readElementHeader = (slice) => { + assert(slice.remainingLength >= 2); + const id = readElementId(slice); + if (id === null) return null; + const size = readElementSize(slice); + if (size === null) return null; + return { + id, + size + }; +}; +var readAsciiString = (slice, length) => { + const bytes = readBytes(slice, length); + let strLength = 0; + while (strLength < length && bytes[strLength] !== 0) strLength += 1; + return String.fromCharCode(...bytes.subarray(0, strLength)); +}; +var readUnicodeString = (slice, length) => { + const bytes = readBytes(slice, length); + let strLength = 0; + while (strLength < length && bytes[strLength] !== 0) strLength += 1; + return textDecoder.decode(bytes.subarray(0, strLength)); +}; +var readFloat = (slice, width) => { + if (width === 0) return 0; + if (width !== 4 && width !== 8) throw new Error("Bad float size " + width); + return width === 4 ? readF32Be(slice) : readF64Be(slice); +}; +/** Returns the byte offset in the file of the next element with a matching ID. */ +var searchForNextElementId = async (reader, startPos, ids, until) => { + const idsSet = new Set(ids); + let currentPos = startPos; + while (until === null || currentPos < until) { + let slice = reader.requestSliceRange(currentPos, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const elementHeader = readElementHeader(slice); + if (!elementHeader) break; + if (idsSet.has(elementHeader.id)) return { + pos: currentPos, + found: true + }; + assertDefinedSize(elementHeader.size); + currentPos = slice.filePos + elementHeader.size; + } + return { + pos: until !== null && until > currentPos ? until : currentPos, + found: false + }; +}; +/** Searches for the next occurrence of an element ID using a naive byte-wise search. */ +var resync = async (reader, startPos, ids, until) => { + const CHUNK_SIZE = 2 ** 16; + const idsSet = new Set(ids); + let currentPos = startPos; + while (currentPos < until) { + let slice = reader.requestSliceRange(currentPos, 0, Math.min(CHUNK_SIZE, until - currentPos)); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + if (slice.length < 8) break; + for (let i = 0; i < slice.length - 8; i++) { + slice.filePos = currentPos; + const elementId = readElementId(slice); + if (elementId !== null && idsSet.has(elementId)) return currentPos; + currentPos++; + } + } + return null; +}; +var CODEC_STRING_MAP = { + "avc": "V_MPEG4/ISO/AVC", + "hevc": "V_MPEGH/ISO/HEVC", + "vp8": "V_VP8", + "vp9": "V_VP9", + "av1": "V_AV1", + "prores": "V_PRORES", + "aac": "A_AAC", + "mp3": "A_MPEG/L3", + "opus": "A_OPUS", + "vorbis": "A_VORBIS", + "flac": "A_FLAC", + "ac3": "A_AC3", + "eac3": "A_EAC3", + "dts": "A_DTS", + "pcm-u8": "A_PCM/INT/LIT", + "pcm-s16": "A_PCM/INT/LIT", + "pcm-s16be": "A_PCM/INT/BIG", + "pcm-s24": "A_PCM/INT/LIT", + "pcm-s24be": "A_PCM/INT/BIG", + "pcm-s32": "A_PCM/INT/LIT", + "pcm-s32be": "A_PCM/INT/BIG", + "pcm-f32": "A_PCM/FLOAT/IEEE", + "pcm-f64": "A_PCM/FLOAT/IEEE", + "webvtt": "S_TEXT/WEBVTT" +}; +function assertDefinedSize(size) { + if (size === void 0) throw new Error("Undefined element size is used in a place where it is not supported."); +} +//#endregion +//#region node_modules/mediabunny/dist/modules/src/matroska/matroska-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var buildMatroskaMimeType = (info) => { + let string = (info.hasVideo ? "video/" : info.hasAudio ? "audio/" : "application/") + (info.isWebM ? "webm" : "x-matroska"); + if (info.codecStrings.length > 0) { + const uniqueCodecMimeTypes = [...new Set(info.codecStrings.filter(Boolean))]; + string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`; + } + return string; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/matroska/matroska-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var BlockLacing; +(function(BlockLacing) { + BlockLacing[BlockLacing["None"] = 0] = "None"; + BlockLacing[BlockLacing["Xiph"] = 1] = "Xiph"; + BlockLacing[BlockLacing["FixedSize"] = 2] = "FixedSize"; + BlockLacing[BlockLacing["Ebml"] = 3] = "Ebml"; +})(BlockLacing || (BlockLacing = {})); +var ContentEncodingScope; +(function(ContentEncodingScope) { + ContentEncodingScope[ContentEncodingScope["Block"] = 1] = "Block"; + ContentEncodingScope[ContentEncodingScope["Private"] = 2] = "Private"; + ContentEncodingScope[ContentEncodingScope["Next"] = 4] = "Next"; +})(ContentEncodingScope || (ContentEncodingScope = {})); +var ContentCompAlgo; +(function(ContentCompAlgo) { + ContentCompAlgo[ContentCompAlgo["Zlib"] = 0] = "Zlib"; + ContentCompAlgo[ContentCompAlgo["Bzlib"] = 1] = "Bzlib"; + ContentCompAlgo[ContentCompAlgo["lzo1x"] = 2] = "lzo1x"; + ContentCompAlgo[ContentCompAlgo["HeaderStripping"] = 3] = "HeaderStripping"; +})(ContentCompAlgo || (ContentCompAlgo = {})); +var METADATA_ELEMENTS = [ + { + id: EBMLId.SeekHead, + flag: "seekHeadSeen" + }, + { + id: EBMLId.Info, + flag: "infoSeen" + }, + { + id: EBMLId.Tracks, + flag: "tracksSeen" + }, + { + id: EBMLId.Cues, + flag: "cuesSeen" + } +]; +var MAX_RESYNC_LENGTH = 10 * 2 ** 20; +var MatroskaDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.readMetadataPromise = null; + this.segments = []; + this.currentSegment = null; + this.currentTrack = null; + this.currentCluster = null; + this.currentBlock = null; + this.currentBlockAdditional = null; + this.currentCueTime = null; + this.currentDecodingInstruction = null; + this.currentTagTargetIsMovie = true; + this.currentSimpleTagName = null; + this.currentAttachedFile = null; + this.isWebM = false; + this.reader = input._reader; + } + async getTrackBackings() { + await this.readMetadata(); + return this.segments.flatMap((segment) => segment.tracks.map((track) => track.trackBacking)); + } + async getMimeType() { + await this.readMetadata(); + const backings = await this.getTrackBackings(); + const codecStrings = await Promise.all(backings.map((x) => x.getDecoderConfig().then((c) => c?.codec ?? null))); + return buildMatroskaMimeType({ + isWebM: this.isWebM, + hasVideo: this.segments.some((segment) => segment.tracks.some((x) => x.info?.type === "video")), + hasAudio: this.segments.some((segment) => segment.tracks.some((x) => x.info?.type === "audio")), + codecStrings: codecStrings.filter(Boolean) + }); + } + async getMetadataTags() { + await this.readMetadata(); + for (const segment of this.segments) if (!segment.metadataTagsCollected) { + if (this.reader.fileSize !== null) await this.loadSegmentMetadata(segment); + segment.metadataTagsCollected = true; + } + let metadataTags = {}; + for (const segment of this.segments) metadataTags = { + ...metadataTags, + ...segment.metadataTags + }; + return metadataTags; + } + readMetadata() { + return this.readMetadataPromise ??= (async () => { + let currentPos = 0; + while (true) { + let slice = this.reader.requestSliceRange(currentPos, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const header = readElementHeader(slice); + if (!header) break; + const id = header.id; + let size = header.size; + const dataStartPos = slice.filePos; + if (id === EBMLId.EBML) { + assertDefinedSize(size); + let slice = this.reader.requestSlice(dataStartPos, size); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + this.readContiguousElements(slice); + } else if (id === EBMLId.Segment) { + await this.readSegment(dataStartPos, size); + if (size === void 0) break; + if (this.reader.fileSize === null) break; + } else if (id === EBMLId.Cluster) { + if (this.reader.fileSize === null) break; + if (size === void 0) size = (await searchForNextElementId(this.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, this.reader.fileSize)).pos - dataStartPos; + const lastSegment = last(this.segments); + if (lastSegment) lastSegment.elementEndPos = dataStartPos + size; + } + assertDefinedSize(size); + currentPos = dataStartPos + size; + } + })(); + } + async readSegment(segmentDataStart, dataSize) { + this.currentSegment = { + seekHeadSeen: false, + infoSeen: false, + tracksSeen: false, + cuesSeen: false, + tagsSeen: false, + attachmentsSeen: false, + timestampScale: -1, + timestampFactor: -1, + duration: -1, + seekEntries: [], + tracks: [], + cuePoints: [], + dataStartPos: segmentDataStart, + elementEndPos: dataSize === void 0 ? null : segmentDataStart + dataSize, + clusterSeekStartPos: segmentDataStart, + lastReadCluster: null, + metadataTags: {}, + metadataTagsCollected: false + }; + this.segments.push(this.currentSegment); + let currentPos = segmentDataStart; + const visitedSeekHeadPositions = /* @__PURE__ */ new Set(); + while (this.currentSegment.elementEndPos === null || currentPos < this.currentSegment.elementEndPos) { + let slice = this.reader.requestSliceRange(currentPos, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const elementStartPos = currentPos; + const header = readElementHeader(slice); + if (!header || !LEVEL_1_EBML_IDS.includes(header.id) && header.id !== EBMLId.Void) { + const nextPos = await resync(this.reader, elementStartPos, LEVEL_1_EBML_IDS, Math.min(this.currentSegment.elementEndPos ?? Infinity, elementStartPos + MAX_RESYNC_LENGTH)); + if (nextPos) { + currentPos = nextPos; + continue; + } else break; + } + const { id, size } = header; + const dataStartPos = slice.filePos; + const metadataElementIndex = METADATA_ELEMENTS.findIndex((x) => x.id === id); + if (metadataElementIndex !== -1) { + const field = METADATA_ELEMENTS[metadataElementIndex].flag; + this.currentSegment[field] = true; + if (id === EBMLId.SeekHead) visitedSeekHeadPositions.add(elementStartPos - segmentDataStart); + assertDefinedSize(size); + let slice = this.reader.requestSlice(dataStartPos, size); + if (isThenable(slice)) slice = await slice; + if (slice) this.readContiguousElements(slice); + } else if (id === EBMLId.Tags || id === EBMLId.Attachments) { + if (id === EBMLId.Tags) this.currentSegment.tagsSeen = true; + else this.currentSegment.attachmentsSeen = true; + assertDefinedSize(size); + let slice = this.reader.requestSlice(dataStartPos, size); + if (isThenable(slice)) slice = await slice; + if (slice) this.readContiguousElements(slice); + } else if (id === EBMLId.Cluster) { + this.currentSegment.clusterSeekStartPos = elementStartPos; + break; + } + if (size === void 0) break; + else currentPos = dataStartPos + size; + } + if (this.reader.fileSize !== null) while (true) { + const seekEntry = this.currentSegment.seekEntries.find((x) => x.id === EBMLId.SeekHead && !visitedSeekHeadPositions.has(x.segmentPosition)); + if (!seekEntry) break; + visitedSeekHeadPositions.add(seekEntry.segmentPosition); + let slice = this.reader.requestSliceRange(segmentDataStart + seekEntry.segmentPosition, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) continue; + const header = readElementHeader(slice); + if (!header || header.id !== EBMLId.SeekHead) continue; + const { size } = header; + assertDefinedSize(size); + let dataSlice = this.reader.requestSlice(slice.filePos, size); + if (isThenable(dataSlice)) dataSlice = await dataSlice; + if (!dataSlice) continue; + this.readContiguousElements(dataSlice); + } + this.currentSegment.seekEntries.sort((a, b) => a.segmentPosition - b.segmentPosition); + if (this.reader.fileSize !== null) for (const seekEntry of this.currentSegment.seekEntries) { + const target = METADATA_ELEMENTS.find((x) => x.id === seekEntry.id); + if (!target) continue; + if (this.currentSegment[target.flag]) continue; + let slice = this.reader.requestSliceRange(segmentDataStart + seekEntry.segmentPosition, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) continue; + const header = readElementHeader(slice); + if (!header) continue; + const { id, size } = header; + if (id !== target.id) continue; + assertDefinedSize(size); + this.currentSegment[target.flag] = true; + let dataSlice = this.reader.requestSlice(slice.filePos, size); + if (isThenable(dataSlice)) dataSlice = await dataSlice; + if (!dataSlice) continue; + this.readContiguousElements(dataSlice); + } + if (this.currentSegment.timestampScale === -1) { + this.currentSegment.timestampScale = 1e6; + this.currentSegment.timestampFactor = 1e9 / 1e6; + } + for (const track of this.currentSegment.tracks) if (track.defaultDurationNs !== null) track.defaultDuration = this.currentSegment.timestampFactor * track.defaultDurationNs / 1e9; + const idToTrack = new Map(this.currentSegment.tracks.map((x) => [x.id, x])); + for (const cuePoint of this.currentSegment.cuePoints) { + const track = idToTrack.get(cuePoint.trackId); + if (track) track.cuePoints.push(cuePoint); + } + for (const track of this.currentSegment.tracks) { + track.cuePoints.sort((a, b) => a.time - b.time); + for (let i = 0; i < track.cuePoints.length - 1; i++) { + const cuePoint1 = track.cuePoints[i]; + const cuePoint2 = track.cuePoints[i + 1]; + if (cuePoint1.time === cuePoint2.time) { + track.cuePoints.splice(i + 1, 1); + i--; + } + } + } + let trackWithMostCuePoints = null; + let maxCuePointCount = -Infinity; + for (const track of this.currentSegment.tracks) if (track.cuePoints.length > maxCuePointCount) { + maxCuePointCount = track.cuePoints.length; + trackWithMostCuePoints = track; + } + for (const track of this.currentSegment.tracks) if (track.cuePoints.length === 0) track.cuePoints = trackWithMostCuePoints.cuePoints; + this.currentSegment = null; + } + async readCluster(startPos, segment) { + if (segment.lastReadCluster?.elementStartPos === startPos) return segment.lastReadCluster; + let headerSlice = this.reader.requestSliceRange(startPos, 2, 16); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + assert(headerSlice); + const elementStartPos = startPos; + const elementHeader = readElementHeader(headerSlice); + assert(elementHeader); + const id = elementHeader.id; + assert(id === EBMLId.Cluster); + let size = elementHeader.size; + const dataStartPos = headerSlice.filePos; + if (size === void 0) size = (await searchForNextElementId(this.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, segment.elementEndPos)).pos - dataStartPos; + let dataSlice = this.reader.requestSlice(dataStartPos, size); + if (isThenable(dataSlice)) dataSlice = await dataSlice; + const cluster = { + segment, + elementStartPos, + elementEndPos: dataStartPos + size, + dataStartPos, + timestamp: -1, + trackData: /* @__PURE__ */ new Map() + }; + this.currentCluster = cluster; + if (dataSlice) cluster.elementEndPos = this.readContiguousElements(dataSlice, LEVEL_0_AND_1_EBML_IDS); + for (const [, trackData] of cluster.trackData) { + const track = trackData.track; + assert(trackData.blocks.length > 0); + let hasLacedBlocks = false; + for (let i = 0; i < trackData.blocks.length; i++) { + const block = trackData.blocks[i]; + block.timestamp += cluster.timestamp; + hasLacedBlocks ||= block.lacing !== BlockLacing.None; + } + trackData.presentationTimestamps = trackData.blocks.map((block, i) => ({ + timestamp: block.timestamp, + blockIndex: i + })).sort((a, b) => a.timestamp - b.timestamp); + for (let i = 0; i < trackData.presentationTimestamps.length; i++) { + const currentEntry = trackData.presentationTimestamps[i]; + const currentBlock = trackData.blocks[currentEntry.blockIndex]; + if (trackData.firstKeyFrameTimestamp === null && currentBlock.isKeyFrame) trackData.firstKeyFrameTimestamp = currentBlock.timestamp; + if (i < trackData.presentationTimestamps.length - 1) currentBlock.duration = trackData.presentationTimestamps[i + 1].timestamp - currentBlock.timestamp; + else if (currentBlock.duration === 0) { + if (track.defaultDuration != null) { + if (currentBlock.lacing === BlockLacing.None) currentBlock.duration = track.defaultDuration; + } + } + } + if (hasLacedBlocks) { + this.expandLacedBlocks(trackData.blocks, track); + trackData.presentationTimestamps = trackData.blocks.map((block, i) => ({ + timestamp: block.timestamp, + blockIndex: i + })).sort((a, b) => a.timestamp - b.timestamp); + } + const firstBlock = trackData.blocks[trackData.presentationTimestamps[0].blockIndex]; + const lastBlock = trackData.blocks[last(trackData.presentationTimestamps).blockIndex]; + trackData.startTimestamp = firstBlock.timestamp; + trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration; + const insertionIndex = binarySearchLessOrEqual(track.clusterPositionCache, trackData.startTimestamp, (x) => x.startTimestamp); + if (insertionIndex === -1 || track.clusterPositionCache[insertionIndex].elementStartPos !== elementStartPos) track.clusterPositionCache.splice(insertionIndex + 1, 0, { + elementStartPos: cluster.elementStartPos, + startTimestamp: trackData.startTimestamp + }); + } + segment.lastReadCluster = cluster; + return cluster; + } + getTrackDataInCluster(cluster, trackNumber) { + let trackData = cluster.trackData.get(trackNumber); + if (!trackData) { + const track = cluster.segment.tracks.find((x) => x.id === trackNumber); + if (!track) return null; + trackData = { + track, + startTimestamp: 0, + endTimestamp: 0, + firstKeyFrameTimestamp: null, + blocks: [], + presentationTimestamps: [] + }; + cluster.trackData.set(trackNumber, trackData); + } + return trackData; + } + expandLacedBlocks(blocks, track) { + for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) { + const originalBlock = blocks[blockIndex]; + if (originalBlock.lacing === BlockLacing.None) continue; + if (!originalBlock.decoded) { + originalBlock.data = this.decodeBlockData(track, originalBlock.data); + originalBlock.decoded = true; + } + const slice = FileSlice.tempFromBytes(originalBlock.data); + const frameSizes = []; + const frameCount = readU8(slice) + 1; + switch (originalBlock.lacing) { + case BlockLacing.Xiph: + { + let totalUsedSize = 0; + for (let i = 0; i < frameCount - 1; i++) { + let frameSize = 0; + while (slice.bufferPos < slice.length) { + const value = readU8(slice); + frameSize += value; + if (value < 255) { + frameSizes.push(frameSize); + totalUsedSize += frameSize; + break; + } + } + } + frameSizes.push(slice.length - (slice.bufferPos + totalUsedSize)); + } + break; + case BlockLacing.FixedSize: + { + const totalDataSize = slice.length - 1; + const frameSize = Math.floor(totalDataSize / frameCount); + for (let i = 0; i < frameCount; i++) frameSizes.push(frameSize); + } + break; + case BlockLacing.Ebml: + { + const firstResult = readVarInt(slice); + assert(firstResult !== null); + let currentSize = firstResult; + frameSizes.push(currentSize); + let totalUsedSize = currentSize; + for (let i = 1; i < frameCount - 1; i++) { + const startPos = slice.bufferPos; + const diffResult = readVarInt(slice); + assert(diffResult !== null); + const diff = diffResult - ((1 << (slice.bufferPos - startPos) * 7 - 1) - 1); + currentSize += diff; + frameSizes.push(currentSize); + totalUsedSize += currentSize; + } + frameSizes.push(slice.length - (slice.bufferPos + totalUsedSize)); + } + break; + default: assert(false); + } + assert(frameSizes.length === frameCount); + blocks.splice(blockIndex, 1); + const blockDuration = originalBlock.duration || frameCount * (track.defaultDuration ?? 0); + for (let i = 0; i < frameCount; i++) { + const frameSize = frameSizes[i]; + const frameData = readBytes(slice, frameSize); + const frameTimestamp = originalBlock.timestamp + blockDuration * i / frameCount; + const frameDuration = blockDuration / frameCount; + blocks.splice(blockIndex + i, 0, { + timestamp: frameTimestamp, + duration: frameDuration, + isKeyFrame: originalBlock.isKeyFrame, + data: frameData, + lacing: BlockLacing.None, + decoded: true, + postProcessed: false, + mainAdditional: originalBlock.mainAdditional + }); + } + blockIndex += frameCount; + blockIndex--; + } + } + async loadSegmentMetadata(segment) { + for (const seekEntry of segment.seekEntries) { + if (seekEntry.id === EBMLId.Tags && !segment.tagsSeen) {} else if (seekEntry.id === EBMLId.Attachments && !segment.attachmentsSeen) {} else continue; + let slice = this.reader.requestSliceRange(segment.dataStartPos + seekEntry.segmentPosition, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) continue; + const header = readElementHeader(slice); + if (!header || header.id !== seekEntry.id) continue; + const { size } = header; + assertDefinedSize(size); + assert(!this.currentSegment); + this.currentSegment = segment; + let dataSlice = this.reader.requestSlice(slice.filePos, size); + if (isThenable(dataSlice)) dataSlice = await dataSlice; + if (dataSlice) this.readContiguousElements(dataSlice); + this.currentSegment = null; + if (seekEntry.id === EBMLId.Tags) segment.tagsSeen = true; + else if (seekEntry.id === EBMLId.Attachments) segment.attachmentsSeen = true; + } + } + readContiguousElements(slice, stopIds) { + while (slice.remainingLength >= 2) { + const startPos = slice.filePos; + if (!this.traverseElement(slice, stopIds)) return startPos; + } + return slice.filePos; + } + traverseElement(slice, stopIds) { + const header = readElementHeader(slice); + if (!header) return false; + if (stopIds && stopIds.includes(header.id)) return false; + const { id, size } = header; + const dataStartPos = slice.filePos; + assertDefinedSize(size); + switch (id) { + case EBMLId.DocType: + this.isWebM = readAsciiString(slice, size) === "webm"; + break; + case EBMLId.Seek: + { + if (!this.currentSegment) break; + const seekEntry = { + id: -1, + segmentPosition: -1 + }; + this.currentSegment.seekEntries.push(seekEntry); + this.readContiguousElements(slice.slice(dataStartPos, size)); + if (seekEntry.id === -1 || seekEntry.segmentPosition === -1) this.currentSegment.seekEntries.pop(); + } + break; + case EBMLId.SeekID: + { + const lastSeekEntry = this.currentSegment?.seekEntries[this.currentSegment.seekEntries.length - 1]; + if (!lastSeekEntry) break; + lastSeekEntry.id = readUnsignedInt(slice, size); + } + break; + case EBMLId.SeekPosition: + { + const lastSeekEntry = this.currentSegment?.seekEntries[this.currentSegment.seekEntries.length - 1]; + if (!lastSeekEntry) break; + lastSeekEntry.segmentPosition = readUnsignedInt(slice, size); + } + break; + case EBMLId.TimestampScale: + if (!this.currentSegment) break; + this.currentSegment.timestampScale = readUnsignedInt(slice, size); + this.currentSegment.timestampFactor = 1e9 / this.currentSegment.timestampScale; + break; + case EBMLId.Duration: + if (!this.currentSegment) break; + this.currentSegment.duration = readFloat(slice, size); + break; + case EBMLId.TrackEntry: + if (!this.currentSegment) break; + this.currentTrack = { + id: -1, + segment: this.currentSegment, + demuxer: this, + clusterPositionCache: [], + cuePoints: [], + disposition: { + ...DEFAULT_TRACK_DISPOSITION, + primary: false + }, + trackBacking: null, + codecId: null, + codecPrivate: null, + defaultDuration: null, + defaultDurationNs: null, + name: null, + languageCode: "eng", + hasLanguageBcp47: false, + decodingInstructions: [], + info: null + }; + this.readContiguousElements(slice.slice(dataStartPos, size)); + if (!this.currentTrack) break; + if (this.currentTrack.decodingInstructions.some((instruction) => { + return instruction.data?.type !== "decompress" || instruction.scope !== ContentEncodingScope.Block || instruction.data.algorithm !== ContentCompAlgo.HeaderStripping; + })) { + Logging._warn(`Track #${this.currentTrack.id} has an unsupported content encoding; dropping.`); + this.currentTrack = null; + } + if (this.currentTrack && this.currentTrack.id !== -1 && this.currentTrack.codecId && this.currentTrack.info) { + const slashIndex = this.currentTrack.codecId.indexOf("/"); + const codecIdWithoutSuffix = slashIndex === -1 ? this.currentTrack.codecId : this.currentTrack.codecId.slice(0, slashIndex); + if (this.currentTrack.info.type === "video" && this.currentTrack.info.width !== -1 && this.currentTrack.info.height !== -1) { + this.currentTrack.info.squarePixelWidth = this.currentTrack.info.width; + this.currentTrack.info.squarePixelHeight = this.currentTrack.info.height; + if (this.currentTrack.info.displayWidth !== null && this.currentTrack.info.displayHeight !== null) { + const num = this.currentTrack.info.displayWidth * this.currentTrack.info.height; + const den = this.currentTrack.info.displayHeight * this.currentTrack.info.width; + if (num > 0 && den > 0) if (num > den) this.currentTrack.info.squarePixelWidth = Math.round(this.currentTrack.info.width * num / den); + else this.currentTrack.info.squarePixelHeight = Math.round(this.currentTrack.info.height * den / num); + } + if (this.currentTrack.codecId === CODEC_STRING_MAP.avc) { + this.currentTrack.info.codec = "avc"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (this.currentTrack.codecId === CODEC_STRING_MAP.hevc) { + this.currentTrack.info.codec = "hevc"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vp8) this.currentTrack.info.codec = "vp8"; + else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vp9) this.currentTrack.info.codec = "vp9"; + else if (codecIdWithoutSuffix === CODEC_STRING_MAP.av1) this.currentTrack.info.codec = "av1"; + else if (codecIdWithoutSuffix === CODEC_STRING_MAP.prores) { + const format = this.currentTrack.codecPrivate ? textDecoder.decode(this.currentTrack.codecPrivate) : ""; + if (PRORES_FOURCCS.includes(format)) { + this.currentTrack.info.codec = "prores"; + this.currentTrack.info.proresFormat = format; + } + } + const videoTrack = this.currentTrack; + this.currentTrack.trackBacking = new MatroskaVideoTrackBacking(videoTrack); + this.currentSegment.tracks.push(this.currentTrack); + } else if (this.currentTrack.info.type === "audio") { + if (codecIdWithoutSuffix === CODEC_STRING_MAP.aac) { + this.currentTrack.info.codec = "aac"; + this.currentTrack.info.aacCodecInfo = { + isMpeg2: this.currentTrack.codecId.includes("MPEG2"), + objectType: null + }; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (this.currentTrack.codecId === CODEC_STRING_MAP.mp3) this.currentTrack.info.codec = "mp3"; + else if (codecIdWithoutSuffix === CODEC_STRING_MAP.opus) { + this.currentTrack.info.codec = "opus"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + this.currentTrack.info.sampleRate = OPUS_SAMPLE_RATE; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vorbis) { + this.currentTrack.info.codec = "vorbis"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.flac) { + this.currentTrack.info.codec = "flac"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.ac3) { + this.currentTrack.info.codec = "ac3"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.eac3) { + this.currentTrack.info.codec = "eac3"; + this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate; + } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.dts) { + this.currentTrack.info.codec = "dts"; + if (this.currentTrack.codecId === "A_DTS/EXPRESS") this.currentTrack.info.dtsFormat = "dtse"; + else if (this.currentTrack.codecId === "A_DTS/LOSSLESS") this.currentTrack.info.dtsFormat = "dtsl"; + } else if (this.currentTrack.codecId === "A_PCM/INT/LIT") { + if (this.currentTrack.info.bitDepth === 8) this.currentTrack.info.codec = "pcm-u8"; + else if (this.currentTrack.info.bitDepth === 16) this.currentTrack.info.codec = "pcm-s16"; + else if (this.currentTrack.info.bitDepth === 24) this.currentTrack.info.codec = "pcm-s24"; + else if (this.currentTrack.info.bitDepth === 32) this.currentTrack.info.codec = "pcm-s32"; + } else if (this.currentTrack.codecId === "A_PCM/INT/BIG") { + if (this.currentTrack.info.bitDepth === 8) this.currentTrack.info.codec = "pcm-u8"; + else if (this.currentTrack.info.bitDepth === 16) this.currentTrack.info.codec = "pcm-s16be"; + else if (this.currentTrack.info.bitDepth === 24) this.currentTrack.info.codec = "pcm-s24be"; + else if (this.currentTrack.info.bitDepth === 32) this.currentTrack.info.codec = "pcm-s32be"; + } else if (this.currentTrack.codecId === "A_PCM/FLOAT/IEEE") { + if (this.currentTrack.info.bitDepth === 32) this.currentTrack.info.codec = "pcm-f32"; + else if (this.currentTrack.info.bitDepth === 64) this.currentTrack.info.codec = "pcm-f64"; + } + const audioTrack = this.currentTrack; + this.currentTrack.trackBacking = new MatroskaAudioTrackBacking(audioTrack); + this.currentSegment.tracks.push(this.currentTrack); + } + } + this.currentTrack = null; + break; + case EBMLId.TrackNumber: + if (!this.currentTrack) break; + this.currentTrack.id = readUnsignedInt(slice, size); + break; + case EBMLId.TrackType: + { + if (!this.currentTrack) break; + const type = readUnsignedInt(slice, size); + if (type === 1) this.currentTrack.info = { + type: "video", + width: -1, + height: -1, + displayWidth: null, + displayHeight: null, + displayUnit: null, + squarePixelWidth: -1, + squarePixelHeight: -1, + horizontalScale: 1, + verticalScale: 1, + rotation: 0, + codec: null, + codecDescription: null, + colorSpace: { ...EMPTY_COLOR_SPACE }, + alphaMode: false, + proresFormat: null + }; + else if (type === 2) this.currentTrack.info = { + type: "audio", + numberOfChannels: 1, + sampleRate: 8e3, + bitDepth: -1, + codec: null, + codecDescription: null, + aacCodecInfo: null, + dtsFormat: null + }; + } + break; + case EBMLId.FlagEnabled: + if (!this.currentTrack) break; + if (!readUnsignedInt(slice, size)) this.currentTrack = null; + break; + case EBMLId.FlagDefault: + if (!this.currentTrack) break; + this.currentTrack.disposition.default = !!readUnsignedInt(slice, size); + break; + case EBMLId.FlagForced: + if (!this.currentTrack) break; + this.currentTrack.disposition.forced = !!readUnsignedInt(slice, size); + break; + case EBMLId.FlagOriginal: + if (!this.currentTrack) break; + this.currentTrack.disposition.original = !!readUnsignedInt(slice, size); + break; + case EBMLId.FlagHearingImpaired: + if (!this.currentTrack) break; + this.currentTrack.disposition.hearingImpaired = !!readUnsignedInt(slice, size); + break; + case EBMLId.FlagVisualImpaired: + if (!this.currentTrack) break; + this.currentTrack.disposition.visuallyImpaired = !!readUnsignedInt(slice, size); + break; + case EBMLId.FlagCommentary: + if (!this.currentTrack) break; + this.currentTrack.disposition.commentary = !!readUnsignedInt(slice, size); + break; + case EBMLId.CodecID: + if (!this.currentTrack) break; + this.currentTrack.codecId = readAsciiString(slice, size); + break; + case EBMLId.CodecPrivate: + if (!this.currentTrack) break; + this.currentTrack.codecPrivate = readBytes(slice, size); + break; + case EBMLId.DefaultDuration: + if (!this.currentTrack) break; + this.currentTrack.defaultDurationNs = readUnsignedInt(slice, size); + break; + case EBMLId.Name: + if (!this.currentTrack) break; + this.currentTrack.name = readUnicodeString(slice, size); + break; + case EBMLId.Language: + if (!this.currentTrack) break; + if (this.currentTrack.hasLanguageBcp47) break; + this.currentTrack.languageCode = readAsciiString(slice, size); + if (!isIso639Dash2LanguageCode(this.currentTrack.languageCode)) this.currentTrack.languageCode = "und"; + break; + case EBMLId.LanguageBCP47: + { + if (!this.currentTrack) break; + const languageSubtag = readAsciiString(slice, size).split("-")[0]; + if (languageSubtag) this.currentTrack.languageCode = languageSubtag; + else this.currentTrack.languageCode = "und"; + this.currentTrack.hasLanguageBcp47 = true; + } + break; + case EBMLId.Video: + if (this.currentTrack?.info?.type !== "video") break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.PixelWidth: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.width = readUnsignedInt(slice, size); + break; + case EBMLId.PixelHeight: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.height = readUnsignedInt(slice, size); + break; + case EBMLId.DisplayWidth: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.displayWidth = readUnsignedInt(slice, size); + break; + case EBMLId.DisplayHeight: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.displayHeight = readUnsignedInt(slice, size); + break; + case EBMLId.DisplayUnit: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.displayUnit = readUnsignedInt(slice, size); + break; + case EBMLId.AlphaMode: + if (this.currentTrack?.info?.type !== "video") break; + this.currentTrack.info.alphaMode = readUnsignedInt(slice, size) === 1; + break; + case EBMLId.Colour: + if (this.currentTrack?.info?.type !== "video") break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.MatrixCoefficients: + { + if (this.currentTrack?.info?.type !== "video") break; + const mapped = MATRIX_COEFFICIENTS_MAP_INVERSE[readUnsignedInt(slice, size)]; + this.currentTrack.info.colorSpace.matrix = mapped; + } + break; + case EBMLId.Range: + { + if (this.currentTrack?.info?.type !== "video") break; + const range = readUnsignedInt(slice, size); + this.currentTrack.info.colorSpace.fullRange = range === 1 || range === 2 ? range === 2 : void 0; + } + break; + case EBMLId.TransferCharacteristics: + { + if (this.currentTrack?.info?.type !== "video") break; + const mapped = TRANSFER_CHARACTERISTICS_MAP_INVERSE[readUnsignedInt(slice, size)]; + this.currentTrack.info.colorSpace.transfer = mapped; + } + break; + case EBMLId.Primaries: + { + if (this.currentTrack?.info?.type !== "video") break; + const mapped = COLOR_PRIMARIES_MAP_INVERSE[readUnsignedInt(slice, size)]; + this.currentTrack.info.colorSpace.primaries = mapped; + } + break; + case EBMLId.Projection: + if (this.currentTrack?.info?.type !== "video") break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.ProjectionPoseYaw: + { + if (this.currentTrack?.info?.type !== "video") break; + const yaw = readFloat(slice, size); + try { + const normalized = normalizeRotation(yaw); + this.currentTrack.info.horizontalScale = Math.round(Math.cos(normalized * DEG_TO_RAD)); + } catch {} + } + break; + case EBMLId.ProjectionPosePitch: + { + if (this.currentTrack?.info?.type !== "video") break; + const pitch = readFloat(slice, size); + try { + const normalized = normalizeRotation(pitch); + this.currentTrack.info.verticalScale = Math.round(Math.cos(normalized * DEG_TO_RAD)); + } catch {} + } + break; + case EBMLId.ProjectionPoseRoll: + { + if (this.currentTrack?.info?.type !== "video") break; + const flippedRotation = -readFloat(slice, size); + try { + this.currentTrack.info.rotation = normalizeRotation(flippedRotation); + } catch {} + } + break; + case EBMLId.Audio: + if (this.currentTrack?.info?.type !== "audio") break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.SamplingFrequency: + if (this.currentTrack?.info?.type !== "audio") break; + this.currentTrack.info.sampleRate = readFloat(slice, size); + break; + case EBMLId.Channels: + if (this.currentTrack?.info?.type !== "audio") break; + this.currentTrack.info.numberOfChannels = readUnsignedInt(slice, size); + break; + case EBMLId.BitDepth: + if (this.currentTrack?.info?.type !== "audio") break; + this.currentTrack.info.bitDepth = readUnsignedInt(slice, size); + break; + case EBMLId.CuePoint: + if (!this.currentSegment) break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + this.currentCueTime = null; + break; + case EBMLId.CueTime: + this.currentCueTime = readUnsignedInt(slice, size); + break; + case EBMLId.CueTrackPositions: + { + if (this.currentCueTime === null) break; + assert(this.currentSegment); + const cuePoint = { + time: this.currentCueTime, + trackId: -1, + clusterPosition: -1 + }; + this.currentSegment.cuePoints.push(cuePoint); + this.readContiguousElements(slice.slice(dataStartPos, size)); + if (cuePoint.trackId === -1 || cuePoint.clusterPosition === -1) this.currentSegment.cuePoints.pop(); + } + break; + case EBMLId.CueTrack: + { + const lastCuePoint = this.currentSegment?.cuePoints[this.currentSegment.cuePoints.length - 1]; + if (!lastCuePoint) break; + lastCuePoint.trackId = readUnsignedInt(slice, size); + } + break; + case EBMLId.CueClusterPosition: + { + const lastCuePoint = this.currentSegment?.cuePoints[this.currentSegment.cuePoints.length - 1]; + if (!lastCuePoint) break; + assert(this.currentSegment); + lastCuePoint.clusterPosition = this.currentSegment.dataStartPos + readUnsignedInt(slice, size); + } + break; + case EBMLId.Timestamp: + if (!this.currentCluster) break; + this.currentCluster.timestamp = readUnsignedInt(slice, size); + break; + case EBMLId.SimpleBlock: + { + if (!this.currentCluster) break; + const trackNumber = readVarInt(slice); + if (trackNumber === null) break; + const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber); + if (!trackData) break; + const relativeTimestamp = readI16Be(slice); + const flags = readU8(slice); + const lacing = flags >> 1 & 3; + let isKeyFrame = !!(flags & 128); + if (trackData.track.info?.type === "audio" && trackData.track.info.codec) isKeyFrame = true; + const blockData = readBytes(slice, size - (slice.filePos - dataStartPos)); + const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0; + trackData.blocks.push({ + timestamp: relativeTimestamp, + duration: 0, + isKeyFrame, + data: blockData, + lacing, + decoded: !hasDecodingInstructions, + postProcessed: false, + mainAdditional: null + }); + } + break; + case EBMLId.BlockGroup: + if (!this.currentCluster) break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + this.currentBlock = null; + break; + case EBMLId.Block: + { + if (!this.currentCluster) break; + const trackNumber = readVarInt(slice); + if (trackNumber === null) break; + const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber); + if (!trackData) break; + const relativeTimestamp = readI16Be(slice); + const lacing = readU8(slice) >> 1 & 3; + const blockData = readBytes(slice, size - (slice.filePos - dataStartPos)); + const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0; + this.currentBlock = { + timestamp: relativeTimestamp, + duration: 0, + isKeyFrame: true, + data: blockData, + lacing, + decoded: !hasDecodingInstructions, + postProcessed: false, + mainAdditional: null + }; + trackData.blocks.push(this.currentBlock); + } + break; + case EBMLId.BlockAdditions: + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.BlockMore: + if (!this.currentBlock) break; + this.currentBlockAdditional = { + addId: 1, + data: null + }; + this.readContiguousElements(slice.slice(dataStartPos, size)); + if (this.currentBlockAdditional.data && this.currentBlockAdditional.addId === 1) this.currentBlock.mainAdditional = this.currentBlockAdditional.data; + this.currentBlockAdditional = null; + break; + case EBMLId.BlockAdditional: + if (!this.currentBlockAdditional) break; + this.currentBlockAdditional.data = readBytes(slice, size); + break; + case EBMLId.BlockAddID: + if (!this.currentBlockAdditional) break; + this.currentBlockAdditional.addId = readUnsignedInt(slice, size); + break; + case EBMLId.BlockDuration: + if (!this.currentBlock) break; + this.currentBlock.duration = readUnsignedInt(slice, size); + break; + case EBMLId.ReferenceBlock: + if (!this.currentBlock) break; + this.currentBlock.isKeyFrame = false; + break; + case EBMLId.Tag: + this.currentTagTargetIsMovie = true; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.Targets: + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.TargetTypeValue: + if (readUnsignedInt(slice, size) !== 50) this.currentTagTargetIsMovie = false; + break; + case EBMLId.TagTrackUID: + case EBMLId.TagEditionUID: + case EBMLId.TagChapterUID: + case EBMLId.TagAttachmentUID: + this.currentTagTargetIsMovie = false; + break; + case EBMLId.SimpleTag: + if (!this.currentTagTargetIsMovie) break; + this.currentSimpleTagName = null; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.TagName: + this.currentSimpleTagName = readUnicodeString(slice, size); + break; + case EBMLId.TagString: + { + if (!this.currentSimpleTagName) break; + const value = readUnicodeString(slice, size); + this.processTagValue(this.currentSimpleTagName, value); + } + break; + case EBMLId.TagBinary: + { + if (!this.currentSimpleTagName) break; + const value = readBytes(slice, size); + this.processTagValue(this.currentSimpleTagName, value); + } + break; + case EBMLId.AttachedFile: + { + if (!this.currentSegment) break; + this.currentAttachedFile = { + fileUid: null, + fileName: null, + fileMediaType: null, + fileData: null, + fileDescription: null + }; + this.readContiguousElements(slice.slice(dataStartPos, size)); + const tags = this.currentSegment.metadataTags; + if (this.currentAttachedFile.fileUid && this.currentAttachedFile.fileData) { + tags.raw ??= {}; + tags.raw[this.currentAttachedFile.fileUid.toString()] = new AttachedFile(this.currentAttachedFile.fileData, this.currentAttachedFile.fileMediaType ?? void 0, this.currentAttachedFile.fileName ?? void 0, this.currentAttachedFile.fileDescription ?? void 0); + } + if (this.currentAttachedFile.fileMediaType?.startsWith("image/") && this.currentAttachedFile.fileData) { + const fileName = this.currentAttachedFile.fileName; + let kind = "unknown"; + if (fileName) { + const lowerName = fileName.toLowerCase(); + if (lowerName.startsWith("cover.")) kind = "coverFront"; + else if (lowerName.startsWith("back.")) kind = "coverBack"; + } + tags.images ??= []; + tags.images.push({ + data: this.currentAttachedFile.fileData, + mimeType: this.currentAttachedFile.fileMediaType, + kind, + name: this.currentAttachedFile.fileName ?? void 0, + description: this.currentAttachedFile.fileDescription ?? void 0 + }); + } + this.currentAttachedFile = null; + } + break; + case EBMLId.FileUID: + if (!this.currentAttachedFile) break; + this.currentAttachedFile.fileUid = readUnsignedBigInt(slice, size); + break; + case EBMLId.FileName: + if (!this.currentAttachedFile) break; + this.currentAttachedFile.fileName = readUnicodeString(slice, size); + break; + case EBMLId.FileMediaType: + if (!this.currentAttachedFile) break; + this.currentAttachedFile.fileMediaType = readAsciiString(slice, size); + break; + case EBMLId.FileData: + if (!this.currentAttachedFile) break; + this.currentAttachedFile.fileData = readBytes(slice, size); + break; + case EBMLId.FileDescription: + if (!this.currentAttachedFile) break; + this.currentAttachedFile.fileDescription = readUnicodeString(slice, size); + break; + case EBMLId.ContentEncodings: + if (!this.currentTrack) break; + this.readContiguousElements(slice.slice(dataStartPos, size)); + this.currentTrack.decodingInstructions.sort((a, b) => b.order - a.order); + break; + case EBMLId.ContentEncoding: + this.currentDecodingInstruction = { + order: 0, + scope: ContentEncodingScope.Block, + data: null + }; + this.readContiguousElements(slice.slice(dataStartPos, size)); + if (this.currentDecodingInstruction.data) this.currentTrack.decodingInstructions.push(this.currentDecodingInstruction); + this.currentDecodingInstruction = null; + break; + case EBMLId.ContentEncodingOrder: + if (!this.currentDecodingInstruction) break; + this.currentDecodingInstruction.order = readUnsignedInt(slice, size); + break; + case EBMLId.ContentEncodingScope: + if (!this.currentDecodingInstruction) break; + this.currentDecodingInstruction.scope = readUnsignedInt(slice, size); + break; + case EBMLId.ContentCompression: + if (!this.currentDecodingInstruction) break; + this.currentDecodingInstruction.data = { + type: "decompress", + algorithm: ContentCompAlgo.Zlib, + settings: null + }; + this.readContiguousElements(slice.slice(dataStartPos, size)); + break; + case EBMLId.ContentCompAlgo: + if (this.currentDecodingInstruction?.data?.type !== "decompress") break; + this.currentDecodingInstruction.data.algorithm = readUnsignedInt(slice, size); + break; + case EBMLId.ContentCompSettings: + if (this.currentDecodingInstruction?.data?.type !== "decompress") break; + this.currentDecodingInstruction.data.settings = readBytes(slice, size); + break; + case EBMLId.ContentEncryption: + if (!this.currentDecodingInstruction) break; + this.currentDecodingInstruction.data = { type: "decrypt" }; + break; + } + slice.filePos = dataStartPos + size; + return true; + } + decodeBlockData(track, rawData) { + assert(track.decodingInstructions.length > 0); + let currentData = rawData; + for (const instruction of track.decodingInstructions) { + assert(instruction.data); + switch (instruction.data.type) { + case "decompress": + switch (instruction.data.algorithm) { + case ContentCompAlgo.HeaderStripping: + if (instruction.data.settings && instruction.data.settings.length > 0) { + const prefix = instruction.data.settings; + const newData = new Uint8Array(prefix.length + currentData.length); + newData.set(prefix, 0); + newData.set(currentData, prefix.length); + currentData = newData; + } + break; + default: + } + break; + default: + } + } + return currentData; + } + processTagValue(name, value) { + if (!this.currentSegment?.metadataTags) return; + const metadataTags = this.currentSegment.metadataTags; + metadataTags.raw ??= {}; + metadataTags.raw[name] ??= value; + if (typeof value === "string") switch (name.toLowerCase()) { + case "title": + metadataTags.title ??= value; + break; + case "description": + metadataTags.description ??= value; + break; + case "artist": + metadataTags.artist ??= value; + break; + case "album": + metadataTags.album ??= value; + break; + case "album_artist": + metadataTags.albumArtist ??= value; + break; + case "genre": + metadataTags.genre ??= value; + break; + case "bpm": + { + const bpm = Number.parseInt(value, 10); + if (Number.isInteger(bpm) && bpm > 0) metadataTags.beatsPerMinute ??= bpm; + } + break; + case "comment": + metadataTags.comment ??= value; + break; + case "lyrics": + metadataTags.lyrics ??= value; + break; + case "date": + { + const date = new Date(value); + if (!Number.isNaN(date.getTime())) metadataTags.date ??= date; + } + break; + case "track_number": + case "part_number": + { + const parts = value.split("/"); + const trackNum = Number.parseInt(parts[0], 10); + const tracksTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(trackNum) && trackNum > 0) metadataTags.trackNumber ??= trackNum; + if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "disc_number": + case "disc": + { + const discParts = value.split("/"); + const discNum = Number.parseInt(discParts[0], 10); + const discsTotal = discParts[1] && Number.parseInt(discParts[1], 10); + if (Number.isInteger(discNum) && discNum > 0) metadataTags.discNumber ??= discNum; + if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) metadataTags.discsTotal ??= discsTotal; + } + break; + } + } + getDurationFromMetadata(segment) { + if (segment.duration <= 0) return null; + return segment.duration / segment.timestampFactor; + } +}; +var MatroskaTrackBacking = class { + constructor(internalTrack) { + this.internalTrack = internalTrack; + this.packetToClusterLocation = /* @__PURE__ */ new WeakMap(); + } + getId() { + return this.internalTrack.id; + } + getNumber() { + const demuxer = this.internalTrack.demuxer; + const trackType = this.internalTrack.trackBacking.getType(); + let number = 0; + for (const segment of demuxer.segments) for (const track of segment.tracks) { + if (track.trackBacking.getType() === trackType) number++; + if (track === this.internalTrack) break; + } + return number; + } + getCodec() { + throw new Error("Not implemented on base class."); + } + getInternalCodecId() { + return this.internalTrack.codecId; + } + getName() { + return this.internalTrack.name; + } + getLanguageCode() { + return this.internalTrack.languageCode; + } + getTimeResolution() { + return this.internalTrack.segment.timestampFactor; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getDisposition() { + return this.internalTrack.disposition; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + return this.internalTrack.demuxer.getDurationFromMetadata(this.internalTrack.segment); + } + async getLiveRefreshInterval() { + return null; + } + async getFirstPacket(options) { + return this.performClusterLookup(null, (cluster) => { + if (cluster.trackData.get(this.internalTrack.id)) return { + blockIndex: 0, + correctBlockFound: true + }; + return { + blockIndex: -1, + correctBlockFound: false + }; + }, -Infinity, Infinity, options); + } + intoTimescale(timestamp) { + return roundIfAlmostInteger(timestamp * this.internalTrack.segment.timestampFactor); + } + async getPacket(timestamp, options) { + const timestampInTimescale = this.intoTimescale(timestamp); + return this.performClusterLookup(null, (cluster) => { + const trackData = cluster.trackData.get(this.internalTrack.id); + if (!trackData) return { + blockIndex: -1, + correctBlockFound: false + }; + const index = binarySearchLessOrEqual(trackData.presentationTimestamps, timestampInTimescale, (x) => x.timestamp); + return { + blockIndex: index !== -1 ? trackData.presentationTimestamps[index].blockIndex : -1, + correctBlockFound: index !== -1 && timestampInTimescale < trackData.endTimestamp + }; + }, timestampInTimescale, timestampInTimescale, options); + } + async getNextPacket(packet, options) { + const locationInCluster = this.packetToClusterLocation.get(packet); + if (locationInCluster === void 0) throw new Error("Packet was not created from this track."); + return this.performClusterLookup(locationInCluster.cluster, (cluster) => { + if (cluster === locationInCluster.cluster) { + const trackData = cluster.trackData.get(this.internalTrack.id); + if (locationInCluster.blockIndex + 1 < trackData.blocks.length) return { + blockIndex: locationInCluster.blockIndex + 1, + correctBlockFound: true + }; + } else if (cluster.trackData.get(this.internalTrack.id)) return { + blockIndex: 0, + correctBlockFound: true + }; + return { + blockIndex: -1, + correctBlockFound: false + }; + }, -Infinity, Infinity, options); + } + async getKeyPacket(timestamp, options) { + const timestampInTimescale = this.intoTimescale(timestamp); + return this.performClusterLookup(null, (cluster) => { + const trackData = cluster.trackData.get(this.internalTrack.id); + if (!trackData) return { + blockIndex: -1, + correctBlockFound: false + }; + const index = findLastIndex(trackData.presentationTimestamps, (x) => { + return trackData.blocks[x.blockIndex].isKeyFrame && x.timestamp <= timestampInTimescale; + }); + return { + blockIndex: index !== -1 ? trackData.presentationTimestamps[index].blockIndex : -1, + correctBlockFound: index !== -1 && timestampInTimescale < trackData.endTimestamp + }; + }, timestampInTimescale, timestampInTimescale, options); + } + async getNextKeyPacket(packet, options) { + const locationInCluster = this.packetToClusterLocation.get(packet); + if (locationInCluster === void 0) throw new Error("Packet was not created from this track."); + return this.performClusterLookup(locationInCluster.cluster, (cluster) => { + if (cluster === locationInCluster.cluster) { + const nextKeyFrameIndex = cluster.trackData.get(this.internalTrack.id).blocks.findIndex((x, i) => x.isKeyFrame && i > locationInCluster.blockIndex); + if (nextKeyFrameIndex !== -1) return { + blockIndex: nextKeyFrameIndex, + correctBlockFound: true + }; + } else { + const trackData = cluster.trackData.get(this.internalTrack.id); + if (trackData && trackData.firstKeyFrameTimestamp !== null) { + const keyFrameIndex = trackData.blocks.findIndex((x) => x.isKeyFrame); + assert(keyFrameIndex !== -1); + return { + blockIndex: keyFrameIndex, + correctBlockFound: true + }; + } + } + return { + blockIndex: -1, + correctBlockFound: false + }; + }, -Infinity, Infinity, options); + } + async fetchPacketInCluster(cluster, blockIndex, options) { + if (blockIndex === -1) return null; + const block = cluster.trackData.get(this.internalTrack.id).blocks[blockIndex]; + assert(block); + if (!block.decoded) { + block.data = this.internalTrack.demuxer.decodeBlockData(this.internalTrack, block.data); + block.decoded = true; + } + if (!block.postProcessed) { + if (this.internalTrack.info?.codec === "prores") { + if (!(block.data.length >= 8 && block.data[4] === 105 && block.data[5] === 99 && block.data[6] === 112 && block.data[7] === 102)) { + const newData = new Uint8Array(block.data.length + 8); + toDataView(newData).setUint32(0, newData.length, false); + newData[4] = 105; + newData[5] = 99; + newData[6] = 112; + newData[7] = 102; + newData.set(block.data, 8); + block.data = newData; + } + } + block.postProcessed = true; + } + const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data; + const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor; + const duration = block.duration / this.internalTrack.segment.timestampFactor; + const sideData = {}; + if (block.mainAdditional && this.internalTrack.info?.type === "video" && this.internalTrack.info.alphaMode) { + sideData.alpha = options.metadataOnly ? PLACEHOLDER_DATA : block.mainAdditional; + sideData.alphaByteLength = block.mainAdditional.byteLength; + } + const packet = new EncodedPacket(data, block.isKeyFrame ? "key" : "delta", timestamp, duration, cluster.dataStartPos + blockIndex, block.data.byteLength, sideData); + this.packetToClusterLocation.set(packet, { + cluster, + blockIndex + }); + return packet; + } + /** Looks for a packet in the clusters while trying to load as few clusters as possible to retrieve it. */ + async performClusterLookup(startCluster, getMatchInCluster, searchTimestamp, latestTimestamp, options) { + const { demuxer, segment } = this.internalTrack; + let currentCluster = null; + let bestCluster = null; + let bestBlockIndex = -1; + if (startCluster) { + const { blockIndex, correctBlockFound } = getMatchInCluster(startCluster); + if (correctBlockFound) return this.fetchPacketInCluster(startCluster, blockIndex, options); + if (blockIndex !== -1) { + bestCluster = startCluster; + bestBlockIndex = blockIndex; + } + } + const cuePointIndex = binarySearchLessOrEqual(this.internalTrack.cuePoints, searchTimestamp, (x) => x.time); + const cuePoint = cuePointIndex !== -1 ? this.internalTrack.cuePoints[cuePointIndex] : null; + const positionCacheIndex = binarySearchLessOrEqual(this.internalTrack.clusterPositionCache, searchTimestamp, (x) => x.startTimestamp); + const positionCacheEntry = positionCacheIndex !== -1 ? this.internalTrack.clusterPositionCache[positionCacheIndex] : null; + const lookupEntryPosition = Math.max(cuePoint?.clusterPosition ?? 0, positionCacheEntry?.elementStartPos ?? 0) || null; + let currentPos; + if (!startCluster) currentPos = lookupEntryPosition ?? segment.clusterSeekStartPos; + else if (lookupEntryPosition === null || startCluster.elementStartPos >= lookupEntryPosition) { + currentPos = startCluster.elementEndPos; + currentCluster = startCluster; + } else currentPos = lookupEntryPosition; + while (segment.elementEndPos === null || currentPos <= segment.elementEndPos - 2) { + if (currentCluster) { + const trackData = currentCluster.trackData.get(this.internalTrack.id); + if (trackData && trackData.startTimestamp > latestTimestamp) break; + } + let slice = demuxer.reader.requestSliceRange(currentPos, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const elementStartPos = currentPos; + const elementHeader = readElementHeader(slice); + if (!elementHeader || !LEVEL_1_EBML_IDS.includes(elementHeader.id) && elementHeader.id !== EBMLId.Void) { + const nextPos = await resync(demuxer.reader, elementStartPos, LEVEL_1_EBML_IDS, Math.min(segment.elementEndPos ?? Infinity, elementStartPos + MAX_RESYNC_LENGTH)); + if (nextPos) { + currentPos = nextPos; + continue; + } else break; + } + const id = elementHeader.id; + let size = elementHeader.size; + const dataStartPos = slice.filePos; + if (id === EBMLId.Cluster) { + currentCluster = await demuxer.readCluster(elementStartPos, segment); + size = currentCluster.elementEndPos - dataStartPos; + const { blockIndex, correctBlockFound } = getMatchInCluster(currentCluster); + if (correctBlockFound) return this.fetchPacketInCluster(currentCluster, blockIndex, options); + if (blockIndex !== -1) { + bestCluster = currentCluster; + bestBlockIndex = blockIndex; + } + } + if (size === void 0) { + assert(id !== EBMLId.Cluster); + size = (await searchForNextElementId(demuxer.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, segment.elementEndPos)).pos - dataStartPos; + } + const endPos = dataStartPos + size; + if (segment.elementEndPos === null) { + let slice = demuxer.reader.requestSliceRange(endPos, 2, 16); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + if (readElementId(slice) === EBMLId.Segment) { + segment.elementEndPos = endPos; + break; + } + } + currentPos = endPos; + } + if (cuePoint && (!bestCluster || bestCluster.elementStartPos < cuePoint.clusterPosition)) { + const previousCuePoint = this.internalTrack.cuePoints[cuePointIndex - 1]; + assert(!previousCuePoint || previousCuePoint.time < cuePoint.time); + const newSearchTimestamp = previousCuePoint?.time ?? -Infinity; + return this.performClusterLookup(null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options); + } + if (bestCluster) return this.fetchPacketInCluster(bestCluster, bestBlockIndex, options); + return null; + } +}; +var MatroskaVideoTrackBacking = class extends MatroskaTrackBacking { + constructor(internalTrack) { + super(internalTrack); + this.decoderConfigPromise = null; + } + getType() { + return "video"; + } + getCodec() { + return this.internalTrack.info.codec; + } + getCodedWidth() { + return this.internalTrack.info.width; + } + getCodedHeight() { + return this.internalTrack.info.height; + } + getSquarePixelWidth() { + return this.internalTrack.info.squarePixelWidth; + } + getSquarePixelHeight() { + return this.internalTrack.info.squarePixelHeight; + } + getTransformationMatrix() { + const info = this.internalTrack.info; + return centeredTransformationMatrix(multiplyMatrices(scaleMatrix(info.horizontalScale, info.verticalScale), rotationMatrix(info.rotation)), info.width, info.height); + } + async getColorSpace() { + const decoderConfig = await this.getDecoderConfig(); + if (!decoderConfig) return this.internalTrack.info.colorSpace; + return { + primaries: decoderConfig.colorSpace?.primaries, + transfer: decoderConfig.colorSpace?.transfer, + matrix: decoderConfig.colorSpace?.matrix, + fullRange: decoderConfig.colorSpace?.fullRange + }; + } + async canBeTransparent() { + return this.internalTrack.info.alphaMode || this.internalTrack.info.codec === "prores" && (this.internalTrack.info.proresFormat === "ap4h" || this.internalTrack.info.proresFormat === "ap4x"); + } + async getDecoderConfig() { + if (!this.internalTrack.info.codec) return null; + return this.decoderConfigPromise ??= (async () => { + let firstPacket = null; + if (this.internalTrack.info.codec === "vp9" || this.internalTrack.info.codec === "av1" || this.internalTrack.info.codec === "prores" || this.internalTrack.info.codec === "avc" && !this.internalTrack.info.codecDescription || this.internalTrack.info.codec === "hevc" && !this.internalTrack.info.codecDescription) firstPacket = await this.getFirstPacket({}); + const codecInfo = { + width: this.internalTrack.info.width, + height: this.internalTrack.info.height, + codec: this.internalTrack.info.codec, + codecDescription: this.internalTrack.info.codecDescription, + colorSpace: this.internalTrack.info.colorSpace, + avcType: 1, + avcCodecInfo: this.internalTrack.info.codec === "avc" && firstPacket ? extractAvcDecoderConfigurationRecord(firstPacket.data) : null, + hevcCodecInfo: this.internalTrack.info.codec === "hevc" && firstPacket ? extractHevcDecoderConfigurationRecord(firstPacket.data) : null, + vp9CodecInfo: this.internalTrack.info.codec === "vp9" && firstPacket ? extractVp9CodecInfoFromPacket(firstPacket.data) : null, + av1CodecInfo: this.internalTrack.info.codec === "av1" && firstPacket ? extractAv1CodecInfoFromPacket(firstPacket.data) : null, + proresCodecInfo: this.internalTrack.info.codec === "prores" && firstPacket ? extractProresCodecInfoFromPacket(firstPacket.data) : null, + proresFormat: this.internalTrack.info.proresFormat + }; + if (!colorSpaceIsComplete(this.internalTrack.info.colorSpace)) { + const colorSpace = extractColorSpace(codecInfo); + this.internalTrack.info.colorSpace.primaries ??= colorSpace.primaries; + this.internalTrack.info.colorSpace.transfer ??= colorSpace.transfer; + this.internalTrack.info.colorSpace.matrix ??= colorSpace.matrix; + this.internalTrack.info.colorSpace.fullRange ??= colorSpace.fullRange; + } + const config = { + codec: extractVideoCodecString(codecInfo), + codedWidth: this.internalTrack.info.width, + codedHeight: this.internalTrack.info.height, + description: this.internalTrack.info.codecDescription ?? void 0, + colorSpace: this.internalTrack.info.colorSpace + }; + if (this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth || this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight) { + config.displayAspectWidth = this.internalTrack.info.squarePixelWidth; + config.displayAspectHeight = this.internalTrack.info.squarePixelHeight; + } + return config; + })(); + } +}; +var MatroskaAudioTrackBacking = class extends MatroskaTrackBacking { + constructor(internalTrack) { + super(internalTrack); + this.decoderConfigPromise = null; + } + getType() { + return "audio"; + } + getCodec() { + return this.internalTrack.info.codec; + } + getNumberOfChannels() { + return this.internalTrack.info.numberOfChannels; + } + getSampleRate() { + return this.internalTrack.info.sampleRate; + } + async getDecoderConfig() { + if (!this.internalTrack.info.codec) return null; + return this.decoderConfigPromise ??= (async () => { + if (this.internalTrack.info.codec === "dts" && !this.internalTrack.info.dtsFormat) { + const firstPacket = await this.getFirstPacket({}); + this.internalTrack.info.dtsFormat = firstPacket && extractDtsFourCcFromPacket(firstPacket.data); + } + return { + codec: extractAudioCodecString({ + codec: this.internalTrack.info.codec, + codecDescription: this.internalTrack.info.codecDescription, + aacCodecInfo: this.internalTrack.info.aacCodecInfo, + dtsFormat: this.internalTrack.info.dtsFormat + }), + numberOfChannels: this.internalTrack.info.numberOfChannels, + sampleRate: this.internalTrack.info.sampleRate, + description: this.internalTrack.info.codecDescription ?? void 0 + }; + })(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mp3/mp3-reader.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var readNextMp3FrameHeader = async (reader, startPos, until, ref = null) => { + const CHUNK_SIZE = 2 ** 16; + let currentPos = startPos; + while (until === null || currentPos < until) { + const maxLength = until !== null ? Math.min(CHUNK_SIZE, until - currentPos) : CHUNK_SIZE; + let slice = reader.requestSliceRange(currentPos, 4, maxLength); + if (isThenable(slice)) slice = await slice; + if (!slice || slice.length < 4) break; + while (slice.remainingLength >= 4) { + const posBeforeRead = slice.filePos; + const result = readMp3FrameHeader(readU32Be(slice), reader.fileSize !== null ? reader.fileSize - currentPos : null); + if (result.header && (!ref || result.header.sampleRate === ref.sampleRate && result.header.mpegVersionId === ref.mpegVersionId && result.header.layer === ref.layer && getMp3ChannelCount(result.header.channel) === getMp3ChannelCount(ref.channel))) return { + header: result.header, + startPos: currentPos + }; + slice.filePos = posBeforeRead + result.bytesAdvanced; + currentPos = slice.filePos; + } + } + return null; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mp3/mp3-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Mp3Demuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.firstFrameHeader = null; + this.firstFrameHeaderPos = null; + this.xingFrameHeader = null; + this.xingFrameHeaderPos = null; + this.loadedSamples = []; + this.metadataTags = null; + this.xingData = null; + this.trackBackings = []; + this.readingMutex = new AsyncMutex(); + this.lastSampleLoaded = false; + this.lastLoadedPos = 0; + this.nextTimestampInSamples = 0; + this.reader = input._reader; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + while (!this.firstFrameHeader && !this.lastSampleLoaded) await this.advanceReader(); + if (!this.firstFrameHeader && this.xingFrameHeader) { + this.firstFrameHeader = this.xingFrameHeader; + this.firstFrameHeaderPos = this.xingFrameHeaderPos; + } + if (!this.firstFrameHeader) throw new Error("No valid MP3 frame found."); + this.trackBackings = [new Mp3AudioTrackBacking(this)]; + })(); + } + async advanceReader() { + if (this.lastLoadedPos === 0) while (true) { + let slice = this.reader.requestSlice(this.lastLoadedPos, 10); + if (isThenable(slice)) slice = await slice; + if (!slice) { + this.lastSampleLoaded = true; + return; + } + const id3V2Header = readId3V2Header(slice); + if (!id3V2Header) break; + this.lastLoadedPos = slice.filePos + id3V2Header.size; + } + const result = await readNextMp3FrameHeader(this.reader, this.lastLoadedPos, this.reader.fileSize, this.firstFrameHeader); + if (!result) { + this.lastSampleLoaded = true; + return; + } + const header = result.header; + this.lastLoadedPos = result.startPos + header.totalSize - 1; + const xingOffset = getXingOffset(header.mpegVersionId, header.channel); + let slice = this.reader.requestSlice(result.startPos + xingOffset, 4); + if (isThenable(slice)) slice = await slice; + if (slice) { + const word = readU32Be(slice); + if (word === 1483304551 || word === 1231971951) { + if (!this.xingFrameHeader) { + this.xingFrameHeader = header; + this.xingFrameHeaderPos = result.startPos; + } + if (!this.xingData) { + let xingDataSlice = this.reader.requestSlice(result.startPos + xingOffset + 4, 12); + if (isThenable(xingDataSlice)) xingDataSlice = await xingDataSlice; + if (xingDataSlice) { + const view = toDataView(readBytes(xingDataSlice, 12)); + const flags = view.getUint32(0, false); + this.xingData = { + frameCount: flags & XingFlags.FrameCount ? view.getUint32(4, false) : null, + fileSize: flags & XingFlags.FileSize ? view.getUint32(8, false) : null + }; + } + } + return; + } + } + if (!this.firstFrameHeader) { + this.firstFrameHeader = header; + this.firstFrameHeaderPos = result.startPos; + } + const sampleDuration = header.audioSamplesInFrame / this.firstFrameHeader.sampleRate; + const sample = { + timestamp: this.nextTimestampInSamples / this.firstFrameHeader.sampleRate, + duration: sampleDuration, + dataStart: result.startPos, + dataSize: header.totalSize + }; + this.loadedSamples.push(sample); + this.nextTimestampInSamples += header.audioSamplesInFrame; + } + async getMimeType() { + return "audio/mpeg"; + } + async getTrackBackings() { + await this.readMetadata(); + return this.trackBackings; + } + async getMetadataTags() { + const release = await this.readingMutex.acquire(); + try { + await this.readMetadata(); + if (this.metadataTags) return this.metadataTags; + this.metadataTags = {}; + let currentPos = 0; + let id3V2HeaderFound = false; + while (true) { + let headerSlice = this.reader.requestSlice(currentPos, 10); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + if (!headerSlice) break; + const id3V2Header = readId3V2Header(headerSlice); + if (!id3V2Header) break; + id3V2HeaderFound = true; + let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size); + if (isThenable(contentSlice)) contentSlice = await contentSlice; + if (!contentSlice) break; + parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags); + currentPos = headerSlice.filePos + id3V2Header.size; + } + if (!id3V2HeaderFound && this.reader.fileSize !== null && this.reader.fileSize >= 128) { + let slice = this.reader.requestSlice(this.reader.fileSize - 128, 128); + if (isThenable(slice)) slice = await slice; + assert(slice); + if (readAscii(slice, 3) === "TAG") parseId3V1Tag(slice, this.metadataTags); + } + return this.metadataTags; + } finally { + release(); + } + } +}; +var Mp3AudioTrackBacking = class { + constructor(demuxer) { + this.demuxer = demuxer; + } + getType() { + return "audio"; + } + getId() { + return 1; + } + getNumber() { + return 1; + } + getTimeResolution() { + assert(this.demuxer.firstFrameHeader); + return this.demuxer.firstFrameHeader.sampleRate / this.demuxer.firstFrameHeader.audioSamplesInFrame; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + const demuxer = this.demuxer; + assert(demuxer.firstFrameHeader !== null); + assert(demuxer.firstFrameHeaderPos !== null); + if (demuxer.xingData) { + if (demuxer.xingData.frameCount !== null) return demuxer.xingData.frameCount * demuxer.firstFrameHeader.audioSamplesInFrame / demuxer.firstFrameHeader.sampleRate; + } else if (demuxer.reader.fileSize !== null) { + const averageFrameSize = computeAverageMp3FrameSize(demuxer.firstFrameHeader.lowSamplingFrequency, demuxer.firstFrameHeader.layer, demuxer.firstFrameHeader.bitrate, demuxer.firstFrameHeader.sampleRate); + const frameCount = (demuxer.reader.fileSize - demuxer.firstFrameHeaderPos) / averageFrameSize; + return Math.round(frameCount) * demuxer.firstFrameHeader.audioSamplesInFrame / demuxer.firstFrameHeader.sampleRate; + } + return null; + } + async getLiveRefreshInterval() { + return null; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getCodec() { + return "mp3"; + } + getInternalCodecId() { + return null; + } + getNumberOfChannels() { + assert(this.demuxer.firstFrameHeader); + return getMp3ChannelCount(this.demuxer.firstFrameHeader.channel); + } + getSampleRate() { + assert(this.demuxer.firstFrameHeader); + return this.demuxer.firstFrameHeader.sampleRate; + } + getDisposition() { + return { ...DEFAULT_TRACK_DISPOSITION }; + } + async getDecoderConfig() { + assert(this.demuxer.firstFrameHeader); + return { + codec: "mp3", + numberOfChannels: getMp3ChannelCount(this.demuxer.firstFrameHeader.channel), + sampleRate: this.demuxer.firstFrameHeader.sampleRate + }; + } + async getPacketAtIndex(sampleIndex, options) { + if (sampleIndex === -1) return null; + const rawSample = this.demuxer.loadedSamples[sampleIndex]; + if (!rawSample) return null; + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.demuxer.reader.requestSlice(rawSample.dataStart, rawSample.dataSize); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + data = readBytes(slice, rawSample.dataSize); + } + return new EncodedPacket(data, "key", rawSample.timestamp, rawSample.duration, sampleIndex, rawSample.dataSize); + } + getFirstPacket(options) { + return this.getPacketAtIndex(0, options); + } + async getNextPacket(packet, options) { + const release = await this.demuxer.readingMutex.acquire(); + try { + const sampleIndex = binarySearchExact(this.demuxer.loadedSamples, packet.timestamp, (x) => x.timestamp); + if (sampleIndex === -1) throw new Error("Packet was not created from this track."); + const nextIndex = sampleIndex + 1; + while (nextIndex >= this.demuxer.loadedSamples.length && !this.demuxer.lastSampleLoaded) await this.demuxer.advanceReader(); + return this.getPacketAtIndex(nextIndex, options); + } finally { + release(); + } + } + async getPacket(timestamp, options) { + const release = await this.demuxer.readingMutex.acquire(); + try { + while (true) { + const index = binarySearchLessOrEqual(this.demuxer.loadedSamples, timestamp, (x) => x.timestamp); + if (index === -1 && this.demuxer.loadedSamples.length > 0) return null; + if (this.demuxer.lastSampleLoaded) return this.getPacketAtIndex(index, options); + if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) return this.getPacketAtIndex(index, options); + await this.demuxer.advanceReader(); + } + } finally { + release(); + } + } + getKeyPacket(timestamp, options) { + return this.getPacket(timestamp, options); + } + getNextKeyPacket(packet, options) { + return this.getNextPacket(packet, options); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/ogg/ogg-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var OGGS = 1399285583; +var OGG_CRC_POLYNOMIAL = 79764919; +var OGG_CRC_TABLE = new Uint32Array(256); +for (let n = 0; n < 256; n++) { + let crc = n << 24; + for (let k = 0; k < 8; k++) crc = crc & 2147483648 ? crc << 1 ^ OGG_CRC_POLYNOMIAL : crc << 1; + OGG_CRC_TABLE[n] = crc >>> 0 & 4294967295; +} +var computeOggPageCrc = (bytes) => { + const view = toDataView(bytes); + const originalChecksum = view.getUint32(22, true); + view.setUint32(22, 0, true); + let crc = 0; + for (let i = 0; i < bytes.length; i++) { + const byte = bytes[i]; + crc = (crc << 8 ^ OGG_CRC_TABLE[crc >>> 24 ^ byte]) >>> 0; + } + view.setUint32(22, originalChecksum, true); + return crc; +}; +var extractSampleMetadata = (data, codecInfo, vorbisLastBlocksize) => { + let durationInSamples = 0; + let currentBlocksize = null; + if (data.length > 0) { + if (codecInfo.codec === "vorbis") { + assert(codecInfo.vorbisInfo); + const vorbisModeCount = codecInfo.vorbisInfo.modeBlockflags.length; + const modeMask = (1 << ilog(vorbisModeCount - 1)) - 1 << 1; + const modeNumber = (data[0] & modeMask) >> 1; + if (modeNumber >= codecInfo.vorbisInfo.modeBlockflags.length) throw new Error("Invalid mode number."); + let prevBlocksize = vorbisLastBlocksize; + const blockflag = codecInfo.vorbisInfo.modeBlockflags[modeNumber]; + currentBlocksize = codecInfo.vorbisInfo.blocksizes[blockflag]; + if (blockflag === 1) { + const prevMask = (modeMask | 1) + 1; + const flag = data[0] & prevMask ? 1 : 0; + prevBlocksize = codecInfo.vorbisInfo.blocksizes[flag]; + } + durationInSamples = prevBlocksize !== null ? prevBlocksize + currentBlocksize >> 2 : 0; + } else if (codecInfo.codec === "opus") durationInSamples = parseOpusTocByte(data).durationInSamples; + } + return { + durationInSamples, + vorbisBlockSize: currentBlocksize + }; +}; +var buildOggMimeType = (info) => { + let string = "audio/ogg"; + if (info.codecStrings) { + const uniqueCodecMimeTypes = [...new Set(info.codecStrings)]; + string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`; + } + return string; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/ogg/ogg-reader.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var MAX_PAGE_SIZE = 65307; +var readPageHeader = (slice) => { + const startPos = slice.filePos; + if (readU32Le(slice) !== 1399285583) return null; + slice.skip(1); + const headerType = readU8(slice); + const granulePosition = readI64Le(slice); + const serialNumber = readU32Le(slice); + const sequenceNumber = readU32Le(slice); + const checksum = readU32Le(slice); + const numberPageSegments = readU8(slice); + const lacingValues = new Uint8Array(numberPageSegments); + for (let i = 0; i < numberPageSegments; i++) lacingValues[i] = readU8(slice); + const headerSize = 27 + numberPageSegments; + const dataSize = lacingValues.reduce((a, b) => a + b, 0); + return { + headerStartPos: startPos, + totalSize: headerSize + dataSize, + dataStartPos: startPos + headerSize, + dataSize, + headerType, + granulePosition, + serialNumber, + sequenceNumber, + checksum, + lacingValues + }; +}; +var findNextPageHeader = (slice, until) => { + while (slice.filePos < until - 3) { + const word = readU32Le(slice); + const firstByte = word & 255; + const secondByte = word >>> 8 & 255; + const thirdByte = word >>> 16 & 255; + const fourthByte = word >>> 24 & 255; + const O = 79; + if (firstByte !== O && secondByte !== O && thirdByte !== O && fourthByte !== O) continue; + slice.skip(-4); + if (word === 1399285583) return true; + slice.skip(1); + } + return false; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/ogg/ogg-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var OggDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.bitstreams = []; + this.trackBackings = []; + this.metadataTags = {}; + this.reader = input._reader; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + let currentPos = 0; + while (true) { + let slice = this.reader.requestSliceRange(currentPos, 27, 282); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const page = readPageHeader(slice); + if (!page) break; + if (!!!(page.headerType & 2)) break; + this.bitstreams.push({ + serialNumber: page.serialNumber, + bosPage: page, + description: null, + numberOfChannels: -1, + sampleRate: -1, + codecInfo: { + codec: null, + vorbisInfo: null, + opusInfo: null + }, + lastMetadataPacket: null + }); + currentPos = page.headerStartPos + page.totalSize; + } + for (const bitstream of this.bitstreams) { + const firstPacket = await this.readPacket(bitstream.bosPage, 0); + if (!firstPacket) continue; + if (firstPacket.data.byteLength >= 7 && firstPacket.data[0] === 1 && firstPacket.data[1] === 118 && firstPacket.data[2] === 111 && firstPacket.data[3] === 114 && firstPacket.data[4] === 98 && firstPacket.data[5] === 105 && firstPacket.data[6] === 115) await this.readVorbisMetadata(firstPacket, bitstream); + else if (firstPacket.data.byteLength >= 8 && firstPacket.data[0] === 79 && firstPacket.data[1] === 112 && firstPacket.data[2] === 117 && firstPacket.data[3] === 115 && firstPacket.data[4] === 72 && firstPacket.data[5] === 101 && firstPacket.data[6] === 97 && firstPacket.data[7] === 100) await this.readOpusMetadata(firstPacket, bitstream); + if (bitstream.codecInfo.codec !== null) this.trackBackings.push(new OggAudioTrackBacking(bitstream, this)); + } + })(); + } + async readVorbisMetadata(firstPacket, bitstream) { + let nextPacketPosition = await this.findNextPacketStart(firstPacket); + if (!nextPacketPosition) return; + const secondPacket = await this.readPacket(nextPacketPosition.startPage, nextPacketPosition.startSegmentIndex); + if (!secondPacket) return; + nextPacketPosition = await this.findNextPacketStart(secondPacket); + if (!nextPacketPosition) return; + const thirdPacket = await this.readPacket(nextPacketPosition.startPage, nextPacketPosition.startSegmentIndex); + if (!thirdPacket) return; + if (secondPacket.data[0] !== 3 || thirdPacket.data[0] !== 5) return; + const lacingValues = []; + const addBytesToSegmentTable = (bytes) => { + while (true) { + lacingValues.push(Math.min(255, bytes)); + if (bytes < 255) break; + bytes -= 255; + } + }; + addBytesToSegmentTable(firstPacket.data.length); + addBytesToSegmentTable(secondPacket.data.length); + const description = new Uint8Array(1 + lacingValues.length + firstPacket.data.length + secondPacket.data.length + thirdPacket.data.length); + description[0] = 2; + description.set(lacingValues, 1); + description.set(firstPacket.data, 1 + lacingValues.length); + description.set(secondPacket.data, 1 + lacingValues.length + firstPacket.data.length); + description.set(thirdPacket.data, 1 + lacingValues.length + firstPacket.data.length + secondPacket.data.length); + bitstream.codecInfo.codec = "vorbis"; + bitstream.description = description; + bitstream.lastMetadataPacket = thirdPacket; + const view = toDataView(firstPacket.data); + bitstream.numberOfChannels = view.getUint8(11); + bitstream.sampleRate = view.getUint32(12, true); + const blockSizeByte = view.getUint8(28); + bitstream.codecInfo.vorbisInfo = { + blocksizes: [1 << (blockSizeByte & 15), 1 << (blockSizeByte >> 4)], + modeBlockflags: parseModesFromVorbisSetupPacket(thirdPacket.data).modeBlockflags + }; + readVorbisComments(secondPacket.data.subarray(7), this.metadataTags); + } + async readOpusMetadata(firstPacket, bitstream) { + const nextPacketPosition = await this.findNextPacketStart(firstPacket); + if (!nextPacketPosition) return; + const secondPacket = await this.readPacket(nextPacketPosition.startPage, nextPacketPosition.startSegmentIndex); + if (!secondPacket) return; + bitstream.codecInfo.codec = "opus"; + bitstream.description = firstPacket.data; + bitstream.lastMetadataPacket = secondPacket; + const header = parseOpusIdentificationHeader(firstPacket.data); + bitstream.numberOfChannels = header.outputChannelCount; + bitstream.sampleRate = OPUS_SAMPLE_RATE; + bitstream.codecInfo.opusInfo = { preSkip: header.preSkip }; + readVorbisComments(secondPacket.data.subarray(8), this.metadataTags); + } + async readPacket(startPage, startSegmentIndex) { + assert(startSegmentIndex < startPage.lacingValues.length); + let startDataOffset = 0; + for (let i = 0; i < startSegmentIndex; i++) startDataOffset += startPage.lacingValues[i]; + let currentPage = startPage; + let currentDataOffset = startDataOffset; + let currentSegmentIndex = startSegmentIndex; + const chunks = []; + outer: while (true) { + let pageSlice = this.reader.requestSlice(currentPage.dataStartPos, currentPage.dataSize); + if (isThenable(pageSlice)) pageSlice = await pageSlice; + assert(pageSlice); + const pageData = readBytes(pageSlice, currentPage.dataSize); + while (true) { + if (currentSegmentIndex === currentPage.lacingValues.length) { + chunks.push(pageData.subarray(startDataOffset, currentDataOffset)); + break; + } + const lacingValue = currentPage.lacingValues[currentSegmentIndex]; + currentDataOffset += lacingValue; + if (lacingValue < 255) { + chunks.push(pageData.subarray(startDataOffset, currentDataOffset)); + break outer; + } + currentSegmentIndex++; + } + let currentPos = currentPage.headerStartPos + currentPage.totalSize; + while (true) { + let headerSlice = this.reader.requestSliceRange(currentPos, 27, 282); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + if (!headerSlice) return null; + const nextPage = readPageHeader(headerSlice); + if (!nextPage) return null; + currentPage = nextPage; + if (currentPage.serialNumber === startPage.serialNumber) break; + currentPos = currentPage.headerStartPos + currentPage.totalSize; + } + startDataOffset = 0; + currentDataOffset = 0; + currentSegmentIndex = 0; + } + const totalPacketSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + if (totalPacketSize === 0) return null; + const packetData = new Uint8Array(totalPacketSize); + let offset = 0; + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + packetData.set(chunk, offset); + offset += chunk.length; + } + return { + data: packetData, + endPage: currentPage, + endSegmentIndex: currentSegmentIndex + }; + } + async findNextPacketStart(lastPacket) { + if (lastPacket.endSegmentIndex < lastPacket.endPage.lacingValues.length - 1) return { + startPage: lastPacket.endPage, + startSegmentIndex: lastPacket.endSegmentIndex + 1 + }; + if (!!(lastPacket.endPage.headerType & 4)) return null; + let currentPos = lastPacket.endPage.headerStartPos + lastPacket.endPage.totalSize; + while (true) { + let slice = this.reader.requestSliceRange(currentPos, 27, 282); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + const nextPage = readPageHeader(slice); + if (!nextPage) return null; + if (nextPage.serialNumber === lastPacket.endPage.serialNumber) return { + startPage: nextPage, + startSegmentIndex: 0 + }; + currentPos = nextPage.headerStartPos + nextPage.totalSize; + } + } + async getMimeType() { + await this.readMetadata(); + return buildOggMimeType({ codecStrings: (await Promise.all(this.trackBackings.map((x) => x.getDecoderConfig().then((c) => c?.codec ?? null)))).filter(Boolean) }); + } + async getTrackBackings() { + await this.readMetadata(); + return this.trackBackings; + } + async getMetadataTags() { + await this.readMetadata(); + return this.metadataTags; + } +}; +var OggAudioTrackBacking = class { + constructor(bitstream, demuxer) { + this.bitstream = bitstream; + this.demuxer = demuxer; + this.encodedPacketToMetadata = /* @__PURE__ */ new WeakMap(); + this.sequentialScanCache = []; + this.sequentialScanMutex = new AsyncMutex(); + this.firstTimestampInSamplesPromise = null; + this.internalSampleRate = bitstream.codecInfo.codec === "opus" ? OPUS_SAMPLE_RATE : bitstream.sampleRate; + } + getType() { + return "audio"; + } + getId() { + return this.bitstream.serialNumber; + } + getNumber() { + const index = this.demuxer.trackBackings.findIndex((x) => x.bitstream === this.bitstream); + assert(index !== -1); + return index + 1; + } + getNumberOfChannels() { + return this.bitstream.numberOfChannels; + } + getSampleRate() { + return this.bitstream.sampleRate; + } + getTimeResolution() { + return this.bitstream.sampleRate; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + return null; + } + async getLiveRefreshInterval() { + return null; + } + getCodec() { + return this.bitstream.codecInfo.codec; + } + getInternalCodecId() { + return null; + } + async getDecoderConfig() { + assert(this.bitstream.codecInfo.codec); + return { + codec: this.bitstream.codecInfo.codec, + numberOfChannels: this.bitstream.numberOfChannels, + sampleRate: this.bitstream.sampleRate, + description: this.bitstream.description ?? void 0 + }; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getDisposition() { + return { + ...DEFAULT_TRACK_DISPOSITION, + primary: false + }; + } + granulePositionToTimestampInSamples(granulePosition) { + if (this.bitstream.codecInfo.codec === "opus") { + assert(this.bitstream.codecInfo.opusInfo); + return granulePosition - this.bitstream.codecInfo.opusInfo.preSkip; + } + return granulePosition; + } + createEncodedPacketFromOggPacket(packet, additional, options) { + if (!packet) return null; + const sampleMetadata = extractSampleMetadata(packet.data, this.bitstream.codecInfo, additional.vorbisLastBlocksize); + const { vorbisBlockSize } = sampleMetadata; + let { durationInSamples } = sampleMetadata; + if (!!(packet.endPage.headerType & 4) && packet.endSegmentIndex === packet.endPage.lacingValues.length - 1) durationInSamples = clamp(this.granulePositionToTimestampInSamples(packet.endPage.granulePosition) - additional.timestampInSamples, 0, durationInSamples); + const startTimestampInSamples = Math.max(additional.timestampInSamples, 0); + const endTimestampInSamples = Math.max(additional.timestampInSamples + durationInSamples, 0); + const encodedPacket = new EncodedPacket(options.metadataOnly ? PLACEHOLDER_DATA : packet.data, "key", startTimestampInSamples / this.internalSampleRate, (endTimestampInSamples - startTimestampInSamples) / this.internalSampleRate, packet.endPage.headerStartPos + packet.endSegmentIndex, packet.data.byteLength); + this.encodedPacketToMetadata.set(encodedPacket, { + packet, + timestampInSamples: additional.timestampInSamples, + durationInSamples, + vorbisLastBlockSize: additional.vorbisLastBlocksize, + vorbisBlockSize + }); + return encodedPacket; + } + getFirstTimestampInSamples() { + return this.firstTimestampInSamplesPromise ??= (async () => { + const defaultTimestampInSamples = this.granulePositionToTimestampInSamples(0); + assert(this.bitstream.lastMetadataPacket); + let position = await this.demuxer.findNextPacketStart(this.bitstream.lastMetadataPacket); + if (!position) return defaultTimestampInSamples; + let firstEndPage = null; + let totalDurationInSamples = 0; + let vorbisLastBlocksize = null; + while (position) { + const packet = await this.demuxer.readPacket(position.startPage, position.startSegmentIndex); + if (!packet) break; + firstEndPage ??= packet.endPage; + if (packet.endPage.headerStartPos !== firstEndPage.headerStartPos) break; + const { durationInSamples, vorbisBlockSize } = extractSampleMetadata(packet.data, this.bitstream.codecInfo, vorbisLastBlocksize); + totalDurationInSamples += durationInSamples; + vorbisLastBlocksize = vorbisBlockSize; + position = await this.demuxer.findNextPacketStart(packet); + } + if (!firstEndPage) return defaultTimestampInSamples; + return defaultTimestampInSamples + Math.max(firstEndPage.granulePosition - totalDurationInSamples, 0); + })(); + } + async getFirstPacket(options) { + assert(this.bitstream.lastMetadataPacket); + const packetPosition = await this.demuxer.findNextPacketStart(this.bitstream.lastMetadataPacket); + if (!packetPosition) return null; + const timestampInSamples = await this.getFirstTimestampInSamples(); + const packet = await this.demuxer.readPacket(packetPosition.startPage, packetPosition.startSegmentIndex); + return this.createEncodedPacketFromOggPacket(packet, { + timestampInSamples, + vorbisLastBlocksize: null + }, options); + } + async getNextPacket(prevPacket, options) { + const prevMetadata = this.encodedPacketToMetadata.get(prevPacket); + if (!prevMetadata) throw new Error("Packet was not created from this track."); + const packetPosition = await this.demuxer.findNextPacketStart(prevMetadata.packet); + if (!packetPosition) return null; + const timestampInSamples = prevMetadata.timestampInSamples + prevMetadata.durationInSamples; + const packet = await this.demuxer.readPacket(packetPosition.startPage, packetPosition.startSegmentIndex); + return this.createEncodedPacketFromOggPacket(packet, { + timestampInSamples, + vorbisLastBlocksize: prevMetadata.vorbisBlockSize + }, options); + } + async getPacket(timestamp, options) { + if (this.demuxer.reader.fileSize === null) return this.getPacketSequential(timestamp, options); + const timestampInSamples = roundIfAlmostInteger(timestamp * this.internalSampleRate); + const firstTimestampInSamples = await this.getFirstTimestampInSamples(); + if (timestampInSamples === Math.max(firstTimestampInSamples, 0)) return this.getFirstPacket(options); + if (timestampInSamples < Math.max(firstTimestampInSamples, 0)) return null; + assert(this.bitstream.lastMetadataPacket); + const startPosition = await this.demuxer.findNextPacketStart(this.bitstream.lastMetadataPacket); + if (!startPosition) return null; + let lowPage = startPosition.startPage; + let high = this.demuxer.reader.fileSize; + const lowPages = [lowPage]; + outer: while (lowPage.headerStartPos + lowPage.totalSize < high) { + const low = lowPage.headerStartPos; + const mid = Math.floor((low + high) / 2); + let searchStartPos = mid; + while (true) { + const until = Math.min(searchStartPos + MAX_PAGE_SIZE, high - 27); + let searchSlice = this.demuxer.reader.requestSlice(searchStartPos, until - searchStartPos); + if (isThenable(searchSlice)) searchSlice = await searchSlice; + assert(searchSlice); + if (!findNextPageHeader(searchSlice, until)) { + if (high <= mid + 27) break outer; + high = mid + 27; + continue outer; + } + let headerSlice = this.demuxer.reader.requestSliceRange(searchSlice.filePos, 27, 282); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + assert(headerSlice); + const page = readPageHeader(headerSlice); + assert(page); + let pageValid = false; + if (page.serialNumber === this.bitstream.serialNumber) pageValid = true; + else { + let pageSlice = this.demuxer.reader.requestSlice(page.headerStartPos, page.totalSize); + if (isThenable(pageSlice)) pageSlice = await pageSlice; + assert(pageSlice); + pageValid = computeOggPageCrc(readBytes(pageSlice, page.totalSize)) === page.checksum; + } + if (!pageValid) { + searchStartPos = page.headerStartPos + 4; + continue; + } + if (pageValid && page.serialNumber !== this.bitstream.serialNumber) { + searchStartPos = page.headerStartPos + page.totalSize; + continue; + } + if (page.granulePosition === -1) { + searchStartPos = page.headerStartPos + page.totalSize; + continue; + } + if (this.granulePositionToTimestampInSamples(page.granulePosition) > timestampInSamples) high = page.headerStartPos; + else { + lowPage = page; + lowPages.push(page); + } + continue outer; + } + } + let lowerPage = startPosition.startPage; + for (const otherLowPage of lowPages) { + if (otherLowPage.granulePosition === lowPage.granulePosition) break; + if (!lowerPage || otherLowPage.headerStartPos > lowerPage.headerStartPos) lowerPage = otherLowPage; + } + let currentPage = lowerPage; + const previousPages = [currentPage]; + while (true) { + if (currentPage.serialNumber === this.bitstream.serialNumber && currentPage.granulePosition === lowPage.granulePosition) break; + const nextPos = currentPage.headerStartPos + currentPage.totalSize; + let slice = this.demuxer.reader.requestSliceRange(nextPos, 27, 282); + if (isThenable(slice)) slice = await slice; + assert(slice); + const nextPage = readPageHeader(slice); + assert(nextPage); + currentPage = nextPage; + if (currentPage.serialNumber === this.bitstream.serialNumber) previousPages.push(currentPage); + } + assert(currentPage.granulePosition !== -1); + let currentSegmentIndex = null; + let currentTimestampInSamples; + let currentTimestampIsCorrect; + let endPage = currentPage; + let endSegmentIndex = 0; + if (currentPage.headerStartPos === startPosition.startPage.headerStartPos) { + currentTimestampInSamples = firstTimestampInSamples; + currentTimestampIsCorrect = true; + currentSegmentIndex = 0; + } else { + currentTimestampInSamples = 0; + currentTimestampIsCorrect = false; + for (let i = currentPage.lacingValues.length - 1; i >= 0; i--) if (currentPage.lacingValues[i] < 255) { + currentSegmentIndex = i + 1; + break; + } + if (currentSegmentIndex === null) throw new Error("Invalid page with granule position: no packets end on this page."); + endSegmentIndex = currentSegmentIndex - 1; + const pseudopacket = { + data: PLACEHOLDER_DATA, + endPage, + endSegmentIndex + }; + if (await this.demuxer.findNextPacketStart(pseudopacket)) { + const endPosition = findPreviousPacketEndPosition(previousPages, currentPage, currentSegmentIndex); + assert(endPosition); + const startPosition = findPacketStartPosition(previousPages, endPosition.page, endPosition.segmentIndex); + if (startPosition) { + currentPage = startPosition.page; + currentSegmentIndex = startPosition.segmentIndex; + } + } else while (true) { + const endPosition = findPreviousPacketEndPosition(previousPages, currentPage, currentSegmentIndex); + if (!endPosition) break; + const startPosition = findPacketStartPosition(previousPages, endPosition.page, endPosition.segmentIndex); + if (!startPosition) break; + currentPage = startPosition.page; + currentSegmentIndex = startPosition.segmentIndex; + if (endPosition.page.headerStartPos !== endPage.headerStartPos) { + endPage = endPosition.page; + endSegmentIndex = endPosition.segmentIndex; + break; + } + } + } + let lastEncodedPacket = null; + let lastEncodedPacketMetadata = null; + while (currentPage !== null) { + assert(currentSegmentIndex !== null); + const packet = await this.demuxer.readPacket(currentPage, currentSegmentIndex); + if (!packet) break; + if (!(currentPage.headerStartPos === startPosition.startPage.headerStartPos && currentSegmentIndex < startPosition.startSegmentIndex)) { + let encodedPacket = this.createEncodedPacketFromOggPacket(packet, { + timestampInSamples: currentTimestampInSamples, + vorbisLastBlocksize: lastEncodedPacketMetadata?.vorbisBlockSize ?? null + }, options); + assert(encodedPacket); + let encodedPacketMetadata = this.encodedPacketToMetadata.get(encodedPacket); + assert(encodedPacketMetadata); + if (!currentTimestampIsCorrect && packet.endPage.headerStartPos === endPage.headerStartPos && packet.endSegmentIndex === endSegmentIndex) { + currentTimestampInSamples = this.granulePositionToTimestampInSamples(currentPage.granulePosition); + currentTimestampIsCorrect = true; + encodedPacket = this.createEncodedPacketFromOggPacket(packet, { + timestampInSamples: currentTimestampInSamples - encodedPacketMetadata.durationInSamples, + vorbisLastBlocksize: lastEncodedPacketMetadata?.vorbisBlockSize ?? null + }, options); + assert(encodedPacket); + encodedPacketMetadata = this.encodedPacketToMetadata.get(encodedPacket); + assert(encodedPacketMetadata); + } else currentTimestampInSamples += encodedPacketMetadata.durationInSamples; + lastEncodedPacket = encodedPacket; + lastEncodedPacketMetadata = encodedPacketMetadata; + if (currentTimestampIsCorrect && (Math.max(currentTimestampInSamples, 0) > timestampInSamples || Math.max(encodedPacketMetadata.timestampInSamples, 0) === timestampInSamples)) break; + } + const nextPosition = await this.demuxer.findNextPacketStart(packet); + if (!nextPosition) break; + currentPage = nextPosition.startPage; + currentSegmentIndex = nextPosition.startSegmentIndex; + } + return lastEncodedPacket; + } + async getPacketSequential(timestamp, options) { + const release = await this.sequentialScanMutex.acquire(); + try { + const timestampInSamples = roundIfAlmostInteger(timestamp * this.internalSampleRate); + timestamp = timestampInSamples / this.internalSampleRate; + const index = binarySearchLessOrEqual(this.sequentialScanCache, timestampInSamples, (x) => x.timestampInSamples); + let currentPacket; + if (index !== -1) { + const cacheEntry = this.sequentialScanCache[index]; + currentPacket = this.createEncodedPacketFromOggPacket(cacheEntry.packet, { + timestampInSamples: cacheEntry.timestampInSamples, + vorbisLastBlocksize: cacheEntry.vorbisLastBlockSize + }, options); + } else { + currentPacket = await this.getFirstPacket(options); + if (currentPacket && currentPacket.timestamp > timestamp) return null; + } + let i = 0; + while (currentPacket && currentPacket.timestamp < timestamp) { + const nextPacket = await this.getNextPacket(currentPacket, options); + if (!nextPacket || nextPacket.timestamp > timestamp) break; + currentPacket = nextPacket; + i++; + if (i === 100) { + i = 0; + const metadata = this.encodedPacketToMetadata.get(currentPacket); + assert(metadata); + if (this.sequentialScanCache.length > 0) assert(last(this.sequentialScanCache).timestampInSamples <= metadata.timestampInSamples); + this.sequentialScanCache.push(metadata); + } + } + return currentPacket; + } finally { + release(); + } + } + getKeyPacket(timestamp, options) { + return this.getPacket(timestamp, options); + } + getNextKeyPacket(packet, options) { + return this.getNextPacket(packet, options); + } +}; +/** Finds the start position of a packet given its end position. */ +var findPacketStartPosition = (pageList, endPage, endSegmentIndex) => { + let page = endPage; + let segmentIndex = endSegmentIndex; + outer: while (true) { + segmentIndex--; + for (; segmentIndex >= 0; segmentIndex--) if (page.lacingValues[segmentIndex] < 255) { + segmentIndex++; + break outer; + } + assert(segmentIndex === -1); + if (!(page.headerType & 1)) { + segmentIndex = 0; + break; + } + const previousPage = findLast(pageList, (x) => x.headerStartPos < page.headerStartPos); + if (!previousPage) return null; + page = previousPage; + segmentIndex = page.lacingValues.length; + } + assert(segmentIndex !== -1); + if (segmentIndex === page.lacingValues.length) { + const nextPage = pageList[pageList.indexOf(page) + 1]; + assert(nextPage); + page = nextPage; + segmentIndex = 0; + } + return { + page, + segmentIndex + }; +}; +/** Finds the end position of a packet given the start position of the following packet. */ +var findPreviousPacketEndPosition = (pageList, startPage, startSegmentIndex) => { + if (startSegmentIndex > 0) return { + page: startPage, + segmentIndex: startSegmentIndex - 1 + }; + const previousPage = findLast(pageList, (x) => x.headerStartPos < startPage.headerStartPos); + if (!previousPage) return null; + return { + page: previousPage, + segmentIndex: previousPage.lacingValues.length - 1 + }; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/wave/wave-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var WaveFormat; +(function(WaveFormat) { + WaveFormat[WaveFormat["PCM"] = 1] = "PCM"; + WaveFormat[WaveFormat["IEEE_FLOAT"] = 3] = "IEEE_FLOAT"; + WaveFormat[WaveFormat["ALAW"] = 6] = "ALAW"; + WaveFormat[WaveFormat["MULAW"] = 7] = "MULAW"; + WaveFormat[WaveFormat["EXTENSIBLE"] = 65534] = "EXTENSIBLE"; +})(WaveFormat || (WaveFormat = {})); +var WaveDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.dataStart = -1; + this.dataSize = -1; + this.audioInfo = null; + this.trackBackings = []; + this.lastKnownPacketIndex = 0; + this.metadataTags = {}; + this.reader = input._reader; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + let slice = this.reader.requestSlice(0, 12); + if (isThenable(slice)) slice = await slice; + assert(slice); + const riffType = readAscii(slice, 4); + const littleEndian = riffType !== "RIFX"; + const isRf64 = riffType === "RF64"; + const outerChunkSize = readU32(slice, littleEndian); + let totalFileSize = isRf64 ? this.reader.fileSize : Math.min(outerChunkSize + 8, this.reader.fileSize ?? Infinity); + if (readAscii(slice, 4) !== "WAVE") throw new Error("Invalid WAVE file - wrong format"); + let chunksRead = 0; + let dataChunkSize = null; + let currentPos = slice.filePos; + while (totalFileSize === null || currentPos < totalFileSize) { + let slice = this.reader.requestSlice(currentPos, 8); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const chunkId = readAscii(slice, 4); + const chunkSize = readU32(slice, littleEndian); + const startPos = slice.filePos; + if (isRf64 && chunksRead === 0 && chunkId !== "ds64") throw new Error("Invalid RF64 file: First chunk must be \"ds64\"."); + if (chunkId === "fmt ") await this.parseFmtChunk(startPos, chunkSize, littleEndian); + else if (chunkId === "data") { + dataChunkSize ??= chunkSize; + this.dataStart = slice.filePos; + this.dataSize = Math.min(dataChunkSize, (totalFileSize ?? Infinity) - this.dataStart); + if (this.reader.fileSize === null) break; + } else if (chunkId === "ds64") { + let ds64Slice = this.reader.requestSlice(startPos, chunkSize); + if (isThenable(ds64Slice)) ds64Slice = await ds64Slice; + if (!ds64Slice) break; + const riffChunkSize = readU64(ds64Slice, littleEndian); + dataChunkSize = readU64(ds64Slice, littleEndian); + totalFileSize = Math.min(riffChunkSize + 8, this.reader.fileSize ?? Infinity); + } else if (chunkId === "LIST") await this.parseListChunk(startPos, chunkSize, littleEndian); + else if (chunkId === "ID3 " || chunkId === "id3 ") await this.parseId3Chunk(startPos, chunkSize); + currentPos = startPos + chunkSize + (chunkSize & 1); + chunksRead++; + } + if (!this.audioInfo) throw new Error("Invalid WAVE file - missing \"fmt \" chunk"); + if (this.dataStart === -1) throw new Error("Invalid WAVE file - missing \"data\" chunk"); + const blockSize = this.audioInfo.blockSizeInBytes; + this.dataSize = Math.floor(this.dataSize / blockSize) * blockSize; + this.trackBackings.push(new WaveAudioTrackBacking(this)); + })(); + } + async parseFmtChunk(startPos, size, littleEndian) { + let slice = this.reader.requestSlice(startPos, size); + if (isThenable(slice)) slice = await slice; + if (!slice) return; + let formatTag = readU16(slice, littleEndian); + const numChannels = readU16(slice, littleEndian); + const sampleRate = readU32(slice, littleEndian); + slice.skip(4); + const blockAlign = readU16(slice, littleEndian); + let bitsPerSample; + if (size === 14) bitsPerSample = 8; + else bitsPerSample = readU16(slice, littleEndian); + if (size >= 18 && formatTag !== 357) { + const cbSize = readU16(slice, littleEndian); + const remainingSize = size - 18; + if (Math.min(remainingSize, cbSize) >= 22 && formatTag === WaveFormat.EXTENSIBLE) { + slice.skip(6); + const subFormat = readBytes(slice, 16); + formatTag = subFormat[0] | subFormat[1] << 8; + } + } + if (formatTag === WaveFormat.MULAW || formatTag === WaveFormat.ALAW) bitsPerSample = 8; + if (formatTag !== WaveFormat.PCM && formatTag !== WaveFormat.IEEE_FLOAT && formatTag !== WaveFormat.ALAW && formatTag !== WaveFormat.MULAW) throw new Error(`Unsupported WAVE codec (format tag ${formatTag}). Only integer/float PCM, A-law, and μ-law are supported.`); + if (formatTag === WaveFormat.PCM && ![ + 8, + 16, + 24, + 32 + ].includes(bitsPerSample)) throw new Error(`Unsupported WAVE PCM bit depth (${bitsPerSample}). Only 8, 16, 24, and 32 bits are supported.`); + if (formatTag === WaveFormat.IEEE_FLOAT && ![32, 64].includes(bitsPerSample)) throw new Error(`Unsupported WAVE float bit depth (${bitsPerSample}). Only 32 and 64 bits are supported.`); + this.audioInfo = { + format: formatTag, + numberOfChannels: numChannels, + sampleRate, + sampleSizeInBytes: Math.ceil(bitsPerSample / 8), + blockSizeInBytes: blockAlign + }; + } + async parseListChunk(startPos, size, littleEndian) { + let slice = this.reader.requestSlice(startPos, size); + if (isThenable(slice)) slice = await slice; + if (!slice) return; + const infoType = readAscii(slice, 4); + if (infoType !== "INFO" && infoType !== "INF0") return; + let currentPos = slice.filePos; + while (currentPos <= startPos + size - 8) { + slice.filePos = currentPos; + const chunkName = readAscii(slice, 4); + const chunkSize = readU32(slice, littleEndian); + const bytes = readBytes(slice, chunkSize); + let stringLength = 0; + for (let i = 0; i < bytes.length; i++) { + if (bytes[i] === 0) break; + stringLength++; + } + const value = String.fromCharCode(...bytes.subarray(0, stringLength)); + this.metadataTags.raw ??= {}; + this.metadataTags.raw[chunkName] = value; + switch (chunkName) { + case "INAM": + case "TITL": + this.metadataTags.title ??= value; + break; + case "TIT3": + this.metadataTags.description ??= value; + break; + case "IART": + this.metadataTags.artist ??= value; + break; + case "IPRD": + this.metadataTags.album ??= value; + break; + case "IPRT": + case "ITRK": + case "TRCK": + { + const parts = value.split("/"); + const trackNum = Number.parseInt(parts[0], 10); + const tracksTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(trackNum) && trackNum > 0) this.metadataTags.trackNumber ??= trackNum; + if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) this.metadataTags.tracksTotal ??= tracksTotal; + } + break; + case "ICRD": + case "IDIT": + { + const date = new Date(value); + if (!Number.isNaN(date.getTime())) this.metadataTags.date ??= date; + } + break; + case "YEAR": + { + const year = Number.parseInt(value, 10); + if (Number.isInteger(year) && year > 0) this.metadataTags.date ??= new Date(year, 0, 1); + } + break; + case "IGNR": + case "GENR": + this.metadataTags.genre ??= value; + break; + case "ICMT": + case "CMNT": + case "COMM": + this.metadataTags.comment ??= value; + break; + } + currentPos += 8 + chunkSize + (chunkSize & 1); + } + } + async parseId3Chunk(startPos, size) { + let slice = this.reader.requestSlice(startPos, size); + if (isThenable(slice)) slice = await slice; + if (!slice) return; + const id3V2Header = readId3V2Header(slice); + if (id3V2Header) { + const availableSize = size - 10; + id3V2Header.size = Math.min(id3V2Header.size, availableSize); + if (id3V2Header.size > 0) parseId3V2Tag(slice.slice(startPos + 10, id3V2Header.size), id3V2Header, this.metadataTags); + } + } + getCodec() { + assert(this.audioInfo); + if (this.audioInfo.format === WaveFormat.MULAW) return "ulaw"; + if (this.audioInfo.format === WaveFormat.ALAW) return "alaw"; + if (this.audioInfo.format === WaveFormat.PCM) { + if (this.audioInfo.sampleSizeInBytes === 1) return "pcm-u8"; + else if (this.audioInfo.sampleSizeInBytes === 2) return "pcm-s16"; + else if (this.audioInfo.sampleSizeInBytes === 3) return "pcm-s24"; + else if (this.audioInfo.sampleSizeInBytes === 4) return "pcm-s32"; + } + if (this.audioInfo.format === WaveFormat.IEEE_FLOAT) { + if (this.audioInfo.sampleSizeInBytes === 4) return "pcm-f32"; + else if (this.audioInfo.sampleSizeInBytes === 8) return "pcm-f64"; + } + assert(false); + } + async getMimeType() { + return "audio/wav"; + } + async getTrackBackings() { + await this.readMetadata(); + return this.trackBackings; + } + async getMetadataTags() { + await this.readMetadata(); + return this.metadataTags; + } +}; +var PACKET_SIZE_IN_FRAMES = 2048; +var WaveAudioTrackBacking = class { + constructor(demuxer) { + this.demuxer = demuxer; + } + getType() { + return "audio"; + } + getId() { + return 1; + } + getNumber() { + return 1; + } + getCodec() { + return this.demuxer.getCodec(); + } + getInternalCodecId() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.format; + } + async getDecoderConfig() { + const codec = this.demuxer.getCodec(); + if (!codec) return null; + assert(this.demuxer.audioInfo); + return { + codec, + numberOfChannels: this.demuxer.audioInfo.numberOfChannels, + sampleRate: this.demuxer.audioInfo.sampleRate + }; + } + getNumberOfChannels() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.numberOfChannels; + } + getSampleRate() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.sampleRate; + } + getTimeResolution() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.sampleRate; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + assert(this.demuxer.dataSize !== -1); + return this.demuxer.dataSize / this.demuxer.audioInfo.blockSizeInBytes / this.demuxer.audioInfo.sampleRate; + } + async getLiveRefreshInterval() { + return null; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getDisposition() { + return { ...DEFAULT_TRACK_DISPOSITION }; + } + async getPacketAtIndex(packetIndex, options) { + assert(packetIndex >= 0); + assert(this.demuxer.audioInfo); + const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes; + if (startOffset >= this.demuxer.dataSize) return null; + const sizeInBytes = Math.min(PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes, this.demuxer.dataSize - startOffset); + if (this.demuxer.reader.fileSize === null) { + let slice = this.demuxer.reader.requestSlice(this.demuxer.dataStart + startOffset, sizeInBytes); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + } + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.demuxer.reader.requestSlice(this.demuxer.dataStart + startOffset, sizeInBytes); + if (isThenable(slice)) slice = await slice; + assert(slice); + data = readBytes(slice, sizeInBytes); + } + const timestamp = packetIndex * PACKET_SIZE_IN_FRAMES / this.demuxer.audioInfo.sampleRate; + const duration = sizeInBytes / this.demuxer.audioInfo.blockSizeInBytes / this.demuxer.audioInfo.sampleRate; + this.demuxer.lastKnownPacketIndex = Math.max(packetIndex, this.demuxer.lastKnownPacketIndex); + return new EncodedPacket(data, "key", timestamp, duration, packetIndex, sizeInBytes); + } + getFirstPacket(options) { + return this.getPacketAtIndex(0, options); + } + async getPacket(timestamp, options) { + assert(this.demuxer.audioInfo); + const packetIndex = Math.floor(Math.min(timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES, (this.demuxer.dataSize - 1) / (PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes))); + if (packetIndex < 0) return null; + const packet = await this.getPacketAtIndex(packetIndex, options); + if (packet) return packet; + if (packetIndex === 0) return null; + assert(this.demuxer.reader.fileSize === null); + let currentPacket = await this.getPacketAtIndex(this.demuxer.lastKnownPacketIndex, options); + while (currentPacket) { + const nextPacket = await this.getNextPacket(currentPacket, options); + if (!nextPacket) break; + currentPacket = nextPacket; + } + return currentPacket; + } + getNextPacket(packet, options) { + assert(this.demuxer.audioInfo); + const packetIndex = Math.round(packet.timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES); + return this.getPacketAtIndex(packetIndex + 1, options); + } + getKeyPacket(timestamp, options) { + return this.getPacket(timestamp, options); + } + getNextKeyPacket(packet, options) { + return this.getNextPacket(packet, options); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/adts/adts-reader.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var readAdtsFrameHeader = (slice) => { + const startPos = slice.filePos; + const bitstream = new Bitstream(readBytes(slice, 9)); + if (bitstream.readBits(12) !== 4095) return null; + bitstream.skipBits(1); + if (bitstream.readBits(2) !== 0) return null; + const protectionAbsence = bitstream.readBits(1); + const objectType = bitstream.readBits(2) + 1; + const samplingFrequencyIndex = bitstream.readBits(4); + if (samplingFrequencyIndex === 15) return null; + bitstream.skipBits(1); + const channelConfiguration = bitstream.readBits(3); + if (channelConfiguration === 0) throw new Error("ADTS frames with channel configuration 0 are not supported."); + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + bitstream.skipBits(1); + const frameLength = bitstream.readBits(13); + bitstream.skipBits(11); + const numberOfAacFrames = bitstream.readBits(2) + 1; + if (numberOfAacFrames !== 1) throw new Error("ADTS frames with more than one AAC frame are not supported."); + let crcCheck = null; + if (protectionAbsence === 1) slice.filePos -= 2; + else crcCheck = bitstream.readBits(16); + return { + objectType, + samplingFrequencyIndex, + channelConfiguration, + frameLength, + numberOfAacFrames, + crcCheck, + startPos + }; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/adts/adts-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var SAMPLES_PER_AAC_FRAME = 1024; +var AdtsDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.firstFrameHeader = null; + this.loadedSamples = []; + this.metadataTags = null; + this.trackBackings = []; + this.readingMutex = new AsyncMutex(); + this.lastSampleLoaded = false; + this.lastLoadedPos = 0; + this.nextTimestampInSamples = 0; + this.reader = input._reader; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + while (!this.firstFrameHeader && !this.lastSampleLoaded) await this.advanceReader(); + assert(this.firstFrameHeader); + this.trackBackings = [new AdtsAudioTrackBacking(this)]; + })(); + } + async advanceReader() { + if (this.lastLoadedPos === 0) while (true) { + let slice = this.reader.requestSlice(this.lastLoadedPos, 10); + if (isThenable(slice)) slice = await slice; + if (!slice) { + this.lastSampleLoaded = true; + return; + } + const id3V2Header = readId3V2Header(slice); + if (!id3V2Header) break; + this.lastLoadedPos = slice.filePos + id3V2Header.size; + } + let slice = this.reader.requestSliceRange(this.lastLoadedPos, 7, 9); + if (isThenable(slice)) slice = await slice; + if (!slice) { + this.lastSampleLoaded = true; + return; + } + const header = readAdtsFrameHeader(slice); + if (!header) { + this.lastSampleLoaded = true; + return; + } + if (this.reader.fileSize !== null && header.startPos + header.frameLength > this.reader.fileSize) { + this.lastSampleLoaded = true; + return; + } + if (!this.firstFrameHeader) this.firstFrameHeader = header; + const sampleRate = aacFrequencyTable[header.samplingFrequencyIndex]; + assert(sampleRate !== void 0); + const sampleDuration = SAMPLES_PER_AAC_FRAME / sampleRate; + const sample = { + timestamp: this.nextTimestampInSamples / sampleRate, + duration: sampleDuration, + dataStart: header.startPos, + dataSize: header.frameLength + }; + this.loadedSamples.push(sample); + this.nextTimestampInSamples += SAMPLES_PER_AAC_FRAME; + this.lastLoadedPos = header.startPos + header.frameLength; + } + async getMimeType() { + return "audio/aac"; + } + async getTrackBackings() { + await this.readMetadata(); + return this.trackBackings; + } + async getMetadataTags() { + const release = await this.readingMutex.acquire(); + try { + await this.readMetadata(); + if (this.metadataTags) return this.metadataTags; + this.metadataTags = {}; + let currentPos = 0; + while (true) { + let headerSlice = this.reader.requestSlice(currentPos, 10); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + if (!headerSlice) break; + const id3V2Header = readId3V2Header(headerSlice); + if (!id3V2Header) break; + let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size); + if (isThenable(contentSlice)) contentSlice = await contentSlice; + if (!contentSlice) break; + parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags); + currentPos = headerSlice.filePos + id3V2Header.size; + } + return this.metadataTags; + } finally { + release(); + } + } +}; +var AdtsAudioTrackBacking = class { + constructor(demuxer) { + this.demuxer = demuxer; + } + getType() { + return "audio"; + } + getId() { + return 1; + } + getNumber() { + return 1; + } + getTimeResolution() { + return this.getSampleRate() / SAMPLES_PER_AAC_FRAME; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + return null; + } + async getLiveRefreshInterval() { + return null; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getCodec() { + return "aac"; + } + getInternalCodecId() { + assert(this.demuxer.firstFrameHeader); + return this.demuxer.firstFrameHeader.objectType; + } + getNumberOfChannels() { + assert(this.demuxer.firstFrameHeader); + const numberOfChannels = aacChannelMap[this.demuxer.firstFrameHeader.channelConfiguration]; + assert(numberOfChannels !== void 0); + return numberOfChannels; + } + getSampleRate() { + assert(this.demuxer.firstFrameHeader); + const sampleRate = aacFrequencyTable[this.demuxer.firstFrameHeader.samplingFrequencyIndex]; + assert(sampleRate !== void 0); + return sampleRate; + } + getDisposition() { + return { ...DEFAULT_TRACK_DISPOSITION }; + } + async getDecoderConfig() { + assert(this.demuxer.firstFrameHeader); + return { + codec: `mp4a.40.${this.demuxer.firstFrameHeader.objectType}`, + numberOfChannels: this.getNumberOfChannels(), + sampleRate: this.getSampleRate() + }; + } + async getPacketAtIndex(sampleIndex, options) { + if (sampleIndex === -1) return null; + const rawSample = this.demuxer.loadedSamples[sampleIndex]; + if (!rawSample) return null; + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.demuxer.reader.requestSlice(rawSample.dataStart, rawSample.dataSize); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + data = readBytes(slice, rawSample.dataSize); + } + return new EncodedPacket(data, "key", rawSample.timestamp, rawSample.duration, sampleIndex, rawSample.dataSize); + } + getFirstPacket(options) { + return this.getPacketAtIndex(0, options); + } + async getNextPacket(packet, options) { + const release = await this.demuxer.readingMutex.acquire(); + try { + const sampleIndex = binarySearchExact(this.demuxer.loadedSamples, packet.timestamp, (x) => x.timestamp); + if (sampleIndex === -1) throw new Error("Packet was not created from this track."); + const nextIndex = sampleIndex + 1; + while (nextIndex >= this.demuxer.loadedSamples.length && !this.demuxer.lastSampleLoaded) await this.demuxer.advanceReader(); + return this.getPacketAtIndex(nextIndex, options); + } finally { + release(); + } + } + async getPacket(timestamp, options) { + const release = await this.demuxer.readingMutex.acquire(); + try { + while (true) { + const index = binarySearchLessOrEqual(this.demuxer.loadedSamples, timestamp, (x) => x.timestamp); + if (index === -1 && this.demuxer.loadedSamples.length > 0) return null; + if (this.demuxer.lastSampleLoaded) return this.getPacketAtIndex(index, options); + if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) return this.getPacketAtIndex(index, options); + await this.demuxer.advanceReader(); + } + } finally { + release(); + } + } + getKeyPacket(timestamp, options) { + return this.getPacket(timestamp, options); + } + getNextKeyPacket(packet, options) { + return this.getNextPacket(packet, options); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/flac/flac-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var getBlockSizeOrUncommon = (bits) => { + if (bits === 0) return null; + else if (bits === 1) return 192; + else if (bits >= 2 && bits <= 5) return 144 * 2 ** bits; + else if (bits === 6) return "uncommon-u8"; + else if (bits === 7) return "uncommon-u16"; + else if (bits >= 8 && bits <= 15) return 2 ** bits; + else return null; +}; +var getSampleRateOrUncommon = (sampleRateBits, streamInfoSampleRate) => { + switch (sampleRateBits) { + case 0: return streamInfoSampleRate; + case 1: return 88200; + case 2: return 176400; + case 3: return 192e3; + case 4: return 8e3; + case 5: return 16e3; + case 6: return 22050; + case 7: return 24e3; + case 8: return 32e3; + case 9: return 44100; + case 10: return 48e3; + case 11: return 96e3; + case 12: return "uncommon-u8"; + case 13: return "uncommon-u16"; + case 14: return "uncommon-u16-10"; + default: return null; + } +}; +var readCodedNumber = (fileSlice) => { + let ones = 0; + const bitstream1 = new Bitstream(readBytes(fileSlice, 1)); + while (bitstream1.readBits(1) === 1) ones++; + if (ones === 0) return bitstream1.readBits(7); + const bitArray = []; + const extraBytes = ones - 1; + const bitstream2 = new Bitstream(readBytes(fileSlice, extraBytes)); + const firstByteBits = 8 - ones - 1; + for (let i = 0; i < firstByteBits; i++) bitArray.unshift(bitstream1.readBits(1)); + for (let i = 0; i < extraBytes; i++) for (let j = 0; j < 8; j++) { + const val = bitstream2.readBits(1); + if (j < 2) continue; + bitArray.unshift(val); + } + return bitArray.reduce((acc, bit, index) => { + return acc | bit << index; + }, 0); +}; +var readBlockSize = (slice, blockSizeBits) => { + if (blockSizeBits === "uncommon-u16") return readU16Be(slice) + 1; + else if (blockSizeBits === "uncommon-u8") return readU8(slice) + 1; + else if (typeof blockSizeBits === "number") return blockSizeBits; + else { + assertNever(blockSizeBits); + assert(false); + } +}; +var readSampleRate = (slice, sampleRateOrUncommon) => { + if (sampleRateOrUncommon === "uncommon-u16") return readU16Be(slice); + if (sampleRateOrUncommon === "uncommon-u16-10") return readU16Be(slice) * 10; + if (sampleRateOrUncommon === "uncommon-u8") return readU8(slice); + if (typeof sampleRateOrUncommon === "number") return sampleRateOrUncommon; + return null; +}; +var calculateCrc8 = (data) => { + const polynomial = 7; + let crc = 0; + for (const byte of data) { + crc ^= byte; + for (let i = 0; i < 8; i++) { + if ((crc & 128) !== 0) crc = crc << 1 ^ polynomial; + else crc <<= 1; + crc &= 255; + } + } + return crc; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/flac/flac-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var FlacDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.loadedSamples = []; + this.metadataPromise = null; + this.trackBacking = null; + this.metadataTags = {}; + this.audioInfo = null; + this.lastLoadedPos = null; + this.blockingBit = null; + this.readingMutex = new AsyncMutex(); + this.lastSampleLoaded = false; + this.reader = input._reader; + } + async getMetadataTags() { + await this.readMetadata(); + return this.metadataTags; + } + async getTrackBackings() { + await this.readMetadata(); + assert(this.trackBacking); + return [this.trackBacking]; + } + async getMimeType() { + return "audio/flac"; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + let currentPos = 0; + while (true) { + let headerSlice = this.reader.requestSlice(currentPos, 10); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + if (!headerSlice) { + this.lastSampleLoaded = true; + return; + } + const id3V2Header = readId3V2Header(headerSlice); + if (!id3V2Header) break; + let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size); + if (isThenable(contentSlice)) contentSlice = await contentSlice; + assert(contentSlice); + parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags); + currentPos = headerSlice.filePos + id3V2Header.size; + } + currentPos += 4; + while (this.reader.fileSize === null || currentPos < this.reader.fileSize) { + let sizeSlice = this.reader.requestSlice(currentPos, 4); + if (isThenable(sizeSlice)) sizeSlice = await sizeSlice; + currentPos += 4; + if (sizeSlice === null) throw new Error(`Metadata block at position ${currentPos} is too small! Corrupted file.`); + assert(sizeSlice); + const byte = readU8(sizeSlice); + const size = readU24Be(sizeSlice); + const isLastMetadata = (byte & 128) !== 0; + switch (byte & 127) { + case FlacBlockType.STREAMINFO: { + let streamInfoBlock = this.reader.requestSlice(currentPos, size); + if (isThenable(streamInfoBlock)) streamInfoBlock = await streamInfoBlock; + assert(streamInfoBlock); + if (streamInfoBlock === null) throw new Error(`StreamInfo block at position ${currentPos} is too small! Corrupted file.`); + const streamInfoBytes = readBytes(streamInfoBlock, 34); + const bitstream = new Bitstream(streamInfoBytes); + const minimumBlockSize = bitstream.readBits(16); + const maximumBlockSize = bitstream.readBits(16); + const minimumFrameSize = bitstream.readBits(24); + const maximumFrameSize = bitstream.readBits(24); + const sampleRate = bitstream.readBits(20); + const numberOfChannels = bitstream.readBits(3) + 1; + bitstream.readBits(5); + const totalSamples = bitstream.readBits(36); + bitstream.skipBits(128); + const description = new Uint8Array(42); + description.set(new Uint8Array([ + 102, + 76, + 97, + 67 + ]), 0); + description.set(new Uint8Array([ + 128, + 0, + 0, + 34 + ]), 4); + description.set(streamInfoBytes, 8); + this.audioInfo = { + numberOfChannels, + sampleRate, + totalSamples, + minimumBlockSize, + maximumBlockSize, + minimumFrameSize, + maximumFrameSize, + description + }; + this.trackBacking = new FlacAudioTrackBacking(this); + break; + } + case FlacBlockType.VORBIS_COMMENT: { + let vorbisCommentBlock = this.reader.requestSlice(currentPos, size); + if (isThenable(vorbisCommentBlock)) vorbisCommentBlock = await vorbisCommentBlock; + assert(vorbisCommentBlock); + readVorbisComments(readBytes(vorbisCommentBlock, size), this.metadataTags); + break; + } + case FlacBlockType.PICTURE: { + let pictureBlock = this.reader.requestSlice(currentPos, size); + if (isThenable(pictureBlock)) pictureBlock = await pictureBlock; + assert(pictureBlock); + const pictureType = readU32Be(pictureBlock); + const mediaTypeLength = readU32Be(pictureBlock); + const mediaType = textDecoder.decode(readBytes(pictureBlock, mediaTypeLength)); + const descriptionLength = readU32Be(pictureBlock); + const description = textDecoder.decode(readBytes(pictureBlock, descriptionLength)); + pictureBlock.skip(16); + const dataLength = readU32Be(pictureBlock); + const data = readBytes(pictureBlock, dataLength); + this.metadataTags.images ??= []; + this.metadataTags.images.push({ + data, + mimeType: mediaType, + kind: pictureType === 3 ? "coverFront" : pictureType === 4 ? "coverBack" : "unknown", + description + }); + break; + } + default: break; + } + currentPos += size; + if (isLastMetadata) { + this.lastLoadedPos = currentPos; + break; + } + } + if (!this.audioInfo) throw new Error("Missing STREAMINFO metadata block! Corrupted FLAC file."); + })(); + } + async readNextFlacFrame({ startPos, isFirstPacket }) { + assert(this.audioInfo); + const minimumHeaderLength = 6; + const maximumHeaderLength = 16; + const minimumFrameLength = 10; + const maximumFrameLength = this.audioInfo.maximumBlockSize * this.audioInfo.numberOfChannels * 4 + maximumHeaderLength + 2; + const effectiveMinFrameSize = this.audioInfo.minimumFrameSize || minimumFrameLength; + const maximumSliceLength = (this.audioInfo.maximumFrameSize || maximumFrameLength) + maximumHeaderLength; + const slice = await this.reader.requestSliceRange(startPos, maximumHeaderLength, maximumSliceLength); + if (!slice) return null; + const frameHeader = this.readFlacFrameHeader({ + slice, + isFirstPacket + }); + if (!frameHeader) return null; + slice.filePos = startPos + effectiveMinFrameSize; + while (true) { + if (slice.filePos > slice.end - minimumHeaderLength) return { + num: frameHeader.num, + blockSize: frameHeader.blockSize, + sampleRate: frameHeader.sampleRate, + size: slice.end - startPos, + isLastFrame: true + }; + if (readU8(slice) === 255) { + const positionBeforeReading = slice.filePos; + if (readU8(slice) !== (this.blockingBit === 1 ? 249 : 248)) { + slice.filePos = positionBeforeReading; + continue; + } + slice.skip(-2); + const lengthIfNextFlacFrameHeaderIsLegit = slice.filePos - startPos; + const nextFrameHeader = this.readFlacFrameHeader({ + slice, + isFirstPacket: false + }); + if (!nextFrameHeader) { + slice.filePos = positionBeforeReading; + continue; + } + if (this.blockingBit === 0) { + if (nextFrameHeader.num - frameHeader.num !== 1) { + slice.filePos = positionBeforeReading; + continue; + } + } else if (nextFrameHeader.num - frameHeader.num !== frameHeader.blockSize) { + slice.filePos = positionBeforeReading; + continue; + } + return { + num: frameHeader.num, + blockSize: frameHeader.blockSize, + sampleRate: frameHeader.sampleRate, + size: lengthIfNextFlacFrameHeaderIsLegit, + isLastFrame: false + }; + } + } + } + readFlacFrameHeader({ slice, isFirstPacket }) { + const startOffset = slice.filePos; + const bitstream = new Bitstream(readBytes(slice, 4)); + if (bitstream.readBits(15) !== 32764) return null; + if (this.blockingBit === null) { + assert(isFirstPacket); + const newBlockingBit = bitstream.readBits(1); + this.blockingBit = newBlockingBit; + } else if (this.blockingBit === 1) { + assert(!isFirstPacket); + if (bitstream.readBits(1) !== 1) return null; + } else if (this.blockingBit === 0) { + assert(!isFirstPacket); + if (bitstream.readBits(1) !== 0) return null; + } else throw new Error("Invalid blocking bit"); + const blockSizeOrUncommon = getBlockSizeOrUncommon(bitstream.readBits(4)); + if (!blockSizeOrUncommon) return null; + assert(this.audioInfo); + const sampleRateOrUncommon = getSampleRateOrUncommon(bitstream.readBits(4), this.audioInfo.sampleRate); + if (!sampleRateOrUncommon) return null; + bitstream.readBits(4); + bitstream.readBits(3); + if (bitstream.readBits(1) !== 0) return null; + const num = readCodedNumber(slice); + const blockSize = readBlockSize(slice, blockSizeOrUncommon); + const sampleRate = readSampleRate(slice, sampleRateOrUncommon); + if (sampleRate === null) return null; + if (sampleRate !== this.audioInfo.sampleRate) return null; + const size = slice.filePos - startOffset; + const crc = readU8(slice); + slice.skip(-size); + slice.skip(-1); + if (crc !== calculateCrc8(readBytes(slice, size))) return null; + return { + num, + blockSize, + sampleRate + }; + } + async advanceReader() { + await this.readMetadata(); + assert(this.lastLoadedPos !== null); + assert(this.audioInfo); + const startPos = this.lastLoadedPos; + const frame = await this.readNextFlacFrame({ + startPos, + isFirstPacket: this.loadedSamples.length === 0 + }); + if (!frame) { + this.lastSampleLoaded = true; + return; + } + const lastSample = this.loadedSamples[this.loadedSamples.length - 1]; + const sample = { + blockOffset: lastSample ? lastSample.blockOffset + lastSample.blockSize : 0, + blockSize: frame.blockSize, + byteOffset: startPos, + byteSize: frame.size + }; + this.lastLoadedPos = this.lastLoadedPos + frame.size; + this.loadedSamples.push(sample); + if (frame.isLastFrame) { + this.lastSampleLoaded = true; + return; + } + } +}; +var FlacAudioTrackBacking = class { + constructor(demuxer) { + this.demuxer = demuxer; + } + getType() { + return "audio"; + } + getId() { + return 1; + } + getNumber() { + return 1; + } + getCodec() { + return "flac"; + } + getInternalCodecId() { + return null; + } + getNumberOfChannels() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.numberOfChannels; + } + getSampleRate() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.sampleRate; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getTimeResolution() { + assert(this.demuxer.audioInfo); + return this.demuxer.audioInfo.sampleRate; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + assert(this.demuxer.audioInfo); + if (this.demuxer.audioInfo.totalSamples === 0) return null; + return this.demuxer.audioInfo.totalSamples / this.demuxer.audioInfo.sampleRate; + } + async getLiveRefreshInterval() { + return null; + } + getDisposition() { + return { ...DEFAULT_TRACK_DISPOSITION }; + } + async getDecoderConfig() { + assert(this.demuxer.audioInfo); + return { + codec: "flac", + numberOfChannels: this.demuxer.audioInfo.numberOfChannels, + sampleRate: this.demuxer.audioInfo.sampleRate, + description: this.demuxer.audioInfo.description + }; + } + async getPacket(timestamp, options) { + assert(this.demuxer.audioInfo); + if (timestamp < 0) return null; + const release = await this.demuxer.readingMutex.acquire(); + try { + while (true) { + const packetIndex = binarySearchLessOrEqual(this.demuxer.loadedSamples, timestamp, (x) => x.blockOffset / this.demuxer.audioInfo.sampleRate); + if (packetIndex === -1) { + await this.demuxer.advanceReader(); + continue; + } + const packet = this.demuxer.loadedSamples[packetIndex]; + if (packet.blockOffset / this.demuxer.audioInfo.sampleRate + packet.blockSize / this.demuxer.audioInfo.sampleRate <= timestamp) { + if (this.demuxer.lastSampleLoaded) return this.getPacketAtIndex(this.demuxer.loadedSamples.length - 1, options); + await this.demuxer.advanceReader(); + continue; + } + return this.getPacketAtIndex(packetIndex, options); + } + } finally { + release(); + } + } + async getNextPacket(packet, options) { + const release = await this.demuxer.readingMutex.acquire(); + try { + const nextIndex = packet.sequenceNumber + 1; + if (this.demuxer.lastSampleLoaded && nextIndex >= this.demuxer.loadedSamples.length) return null; + while (nextIndex >= this.demuxer.loadedSamples.length && !this.demuxer.lastSampleLoaded) await this.demuxer.advanceReader(); + return this.getPacketAtIndex(nextIndex, options); + } finally { + release(); + } + } + getKeyPacket(timestamp, options) { + return this.getPacket(timestamp, options); + } + getNextKeyPacket(packet, options) { + return this.getNextPacket(packet, options); + } + async getPacketAtIndex(sampleIndex, options) { + const rawSample = this.demuxer.loadedSamples[sampleIndex]; + if (!rawSample) return null; + let data; + if (options.metadataOnly) data = PLACEHOLDER_DATA; + else { + let slice = this.demuxer.reader.requestSlice(rawSample.byteOffset, rawSample.byteSize); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + data = readBytes(slice, rawSample.byteSize); + } + assert(this.demuxer.audioInfo); + const timestamp = rawSample.blockOffset / this.demuxer.audioInfo.sampleRate; + const duration = rawSample.blockSize / this.demuxer.audioInfo.sampleRate; + return new EncodedPacket(data, "key", timestamp, duration, sampleIndex, rawSample.byteSize); + } + async getFirstPacket(options) { + while (this.demuxer.loadedSamples.length === 0 && !this.demuxer.lastSampleLoaded) await this.demuxer.advanceReader(); + return this.getPacketAtIndex(0, options); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mpeg-ts/mpeg-ts-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var TIMESCALE = 9e4; +var TIMESTAMP_MODULUS = 2 ** 33; +var buildMpegTsMimeType = (codecStrings) => { + let string = "video/MP2T"; + const uniqueCodecStrings = [...new Set(codecStrings.filter(Boolean))]; + if (uniqueCodecStrings.length > 0) string += `; codecs="${uniqueCodecStrings.join(", ")}"`; + return string; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var MISSING_PTS_ERROR_MESSAGE = "PES packet is missing PTS where it was expected. PES packets without PTS are not currently supported. If you think this file should be supported, please report it."; +var REGISTRATION_DESCRIPTOR_TAG = 5; +var HDMV_FORMAT_IDENTIFIER = 1212435798; +var HDPR_FORMAT_IDENTIFIER = 1212436562; +var DTS_FORMAT_IDENTIFIER_PREFIX = 1146376960; +var BLU_RAY_DTS_STREAM_TYPES = new Set([ + 133, + 134, + 162 +]); +var ignoredStreamTypes = /* @__PURE__ */ new Set(); +var MpegTsDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.elementaryStreams = []; + this.trackBackingEntries = []; + this.packetOffset = 0; + this.packetStride = -1; + this.sectionEndPositions = []; + this.seekChunkSize = 5 * 1024 * 1024; + this.minReferencePointByteDistance = -1; + this.timestampWrapInfo = null; + this.reader = input._reader; + } + async readMetadata() { + return this.metadataPromise ??= (async () => { + const lengthToCheck = 205; + let startingSlice = this.reader.requestSlice(0, lengthToCheck); + if (isThenable(startingSlice)) startingSlice = await startingSlice; + assert(startingSlice); + const startingBytes = readBytes(startingSlice, lengthToCheck); + if (startingBytes[0] === 71 && startingBytes[188] === 71) { + this.packetOffset = 0; + this.packetStride = 188; + } else if (startingBytes[0] === 71 && startingBytes[204] === 71) { + this.packetOffset = 0; + this.packetStride = 204; + } else if (startingBytes[4] === 71 && startingBytes[196] === 71) { + this.packetOffset = 4; + this.packetStride = 192; + } else throw new Error("Unreachable."); + const MIN_REFERENCE_POINT_PACKET_DISTANCE = 256; + this.minReferencePointByteDistance = MIN_REFERENCE_POINT_PACKET_DISTANCE * this.packetStride; + let currentPos = this.packetOffset; + let programMapPid = null; + let hasProgramAssociationTable = false; + let hasProgramMap = false; + while (true) { + const packetHeader = await this.readPacketHeader(currentPos); + if (!packetHeader) break; + if (packetHeader.payloadUnitStartIndicator === 0) { + currentPos += this.packetStride; + continue; + } + if (hasProgramMap && !this.elementaryStreams.some((x) => x.pid === packetHeader.pid)) { + currentPos += this.packetStride; + continue; + } + const section = await this.readSection(currentPos, true, !hasProgramMap); + if (!section) break; + const BYTES_BEFORE_SECTION_LENGTH = 3; + const BITS_IN_CRC_32 = 32; + let isProbablyProgramMap = false; + if (!hasProgramMap && section.pid !== 0) { + if (!(section.payload[0] === 0 && section.payload[1] === 0 && section.payload[2] === 1)) { + const bitstream = new Bitstream(section.payload); + const pointerField = bitstream.readAlignedByte(); + bitstream.skipBits(8 * pointerField); + isProbablyProgramMap = bitstream.readBits(8) === 2; + } + } + if (section.pid === 0 && !hasProgramAssociationTable) { + const bitstream = new Bitstream(section.payload); + const pointerField = bitstream.readAlignedByte(); + bitstream.skipBits(8 * pointerField); + bitstream.skipBits(14); + const sectionLength = bitstream.readBits(10); + bitstream.skipBits(40); + while (8 * (sectionLength + BYTES_BEFORE_SECTION_LENGTH) - bitstream.pos > BITS_IN_CRC_32) { + const programNumber = bitstream.readBits(16); + bitstream.skipBits(3); + const id = bitstream.readBits(13); + if (programNumber !== 0) if (programMapPid !== null) throw new Error("Only files with a single program are supported."); + else programMapPid = id; + } + if (programMapPid === null) throw new Error("Program Association Table must link to a Program Map Table."); + hasProgramAssociationTable = true; + } else if ((section.pid === programMapPid || isProbablyProgramMap) && !hasProgramMap) { + const bitstream = new Bitstream(section.payload); + const pointerField = bitstream.readAlignedByte(); + bitstream.skipBits(8 * pointerField); + bitstream.skipBits(12); + const sectionLength = bitstream.readBits(12); + bitstream.skipBits(43); + bitstream.readBits(13); + bitstream.skipBits(6); + const programInfoLength = bitstream.readBits(10); + const programInfoEndPos = bitstream.pos + 8 * programInfoLength; + let isBluRayProgram = false; + while (bitstream.pos < programInfoEndPos) { + const descriptorTag = bitstream.readBits(8); + const descriptorLength = bitstream.readBits(8); + const descriptorEndPos = bitstream.pos + 8 * descriptorLength; + if (descriptorTag === REGISTRATION_DESCRIPTOR_TAG && descriptorLength >= 4) { + const formatIdentifier = bitstream.readBits(32); + isBluRayProgram ||= formatIdentifier === HDMV_FORMAT_IDENTIFIER || formatIdentifier === HDPR_FORMAT_IDENTIFIER; + } + bitstream.pos = descriptorEndPos; + } + bitstream.pos = programInfoEndPos; + while (8 * (sectionLength + BYTES_BEFORE_SECTION_LENGTH) - bitstream.pos > BITS_IN_CRC_32) { + const streamType = bitstream.readBits(8); + bitstream.skipBits(3); + const elementaryPid = bitstream.readBits(13); + bitstream.skipBits(6); + const esInfoLength = bitstream.readBits(10); + const esInfoEndPos = bitstream.pos + 8 * esInfoLength; + let hasAc3Descriptor = false; + let hasEac3Descriptor = false; + let hasDtsDescriptor = false; + while (bitstream.pos < esInfoEndPos) { + const descriptorTag = bitstream.readBits(8); + const descriptorLength = bitstream.readBits(8); + const descriptorEndPos = bitstream.pos + 8 * descriptorLength; + if (descriptorTag === 106) hasAc3Descriptor = true; + else if (descriptorTag === 122 || descriptorTag === 204) hasEac3Descriptor = true; + else if (descriptorTag === 123) hasDtsDescriptor = true; + else if (descriptorTag === REGISTRATION_DESCRIPTOR_TAG && descriptorLength >= 4) { + const formatIdentifier = bitstream.readBits(32); + hasDtsDescriptor ||= (formatIdentifier & 4294967040) === DTS_FORMAT_IDENTIFIER_PREFIX; + } + bitstream.pos = descriptorEndPos; + } + let info = null; + const effectiveStreamType = isBluRayProgram && BLU_RAY_DTS_STREAM_TYPES.has(streamType) ? 130 : streamType; + switch (effectiveStreamType) { + case 27: + case 36: + info = { + type: "video", + codec: streamType === 27 ? "avc" : "hevc", + decoderConfig: null, + avcCodecInfo: null, + hevcCodecInfo: null, + colorSpace: { + primaries: null, + transfer: null, + matrix: null, + fullRange: null + }, + width: -1, + height: -1, + squarePixelWidth: -1, + squarePixelHeight: -1, + reorderSize: -1 + }; + break; + case 3: + case 4: + case 15: + case 129: + case 135: + case 130: + case 138: + { + let codec; + if (effectiveStreamType === 3 || effectiveStreamType === 4) codec = "mp3"; + else if (effectiveStreamType === 15) codec = "aac"; + else if (effectiveStreamType === 129) codec = "ac3"; + else if (effectiveStreamType === 135) codec = "eac3"; + else codec = "dts"; + info = { + type: "audio", + codec, + decoderConfig: null, + aacCodecInfo: null, + dtsFormat: null, + numberOfChannels: -1, + sampleRate: -1 + }; + } + break; + case 6: + if (hasEac3Descriptor) info = { + type: "audio", + codec: "eac3", + decoderConfig: null, + aacCodecInfo: null, + dtsFormat: null, + numberOfChannels: -1, + sampleRate: -1 + }; + else if (hasAc3Descriptor) info = { + type: "audio", + codec: "ac3", + decoderConfig: null, + aacCodecInfo: null, + dtsFormat: null, + numberOfChannels: -1, + sampleRate: -1 + }; + else if (hasDtsDescriptor) info = { + type: "audio", + codec: "dts", + decoderConfig: null, + aacCodecInfo: null, + dtsFormat: null, + numberOfChannels: -1, + sampleRate: -1 + }; + break; + default: if (!ignoredStreamTypes.has(streamType)) { + Logging._warn(`Note: MPEG-TS streams with stream_type 0x${streamType.toString(16)} are not currently supported.`); + ignoredStreamTypes.add(streamType); + } + } + if (info) this.elementaryStreams.push({ + demuxer: this, + pid: elementaryPid, + streamType, + initialized: false, + firstSection: null, + canBeTrustedWithKeyPackets: false, + info, + referencePesPackets: [] + }); + } + hasProgramMap = true; + } else { + const elementaryStream = this.elementaryStreams.find((x) => x.pid === section.pid); + outer: if (elementaryStream && !elementaryStream.initialized) { + const pesPacket = readPesPacket(this, section, true); + if (!pesPacket) throw new Error(`Couldn't read first PES packet for Elementary Stream with PID ${elementaryStream.pid}`); + elementaryStream.firstSection = section; + elementaryStream.canBeTrustedWithKeyPackets = section.randomAccessIndicator === 1; + if (this.input._initInput) { + const matchingStream = (await this.input._initInput._getDemuxer()).elementaryStreams.find((x) => x.pid === section.pid && x.info.codec === elementaryStream.info.codec); + if (matchingStream) { + elementaryStream.info = matchingStream.info; + elementaryStream.initialized = true; + break outer; + } + } + const context = new PacketReadingContext(elementaryStream, pesPacket); + if (elementaryStream.info.type === "video") { + while (true) { + const contextAlias = context; + contextAlias.suppliedPacket = null; + await context.markNextPacket(); + if (elementaryStream.info.codec === "avc") { + if (!context.suppliedPacket) throw new Error("Invalid AVC video stream; could not extract AVCDecoderConfigurationRecord from any packet."); + elementaryStream.info.avcCodecInfo = extractAvcDecoderConfigurationRecord(context.suppliedPacket.data); + if (!elementaryStream.info.avcCodecInfo) continue; + const spsUnit = elementaryStream.info.avcCodecInfo.sequenceParameterSets[0]; + assert(spsUnit); + const spsInfo = parseAvcSps(spsUnit); + elementaryStream.info.width = spsInfo.displayWidth; + elementaryStream.info.height = spsInfo.displayHeight; + const num = spsInfo.pixelAspectRatio.num; + const den = spsInfo.pixelAspectRatio.den; + elementaryStream.info.squarePixelWidth = elementaryStream.info.width; + elementaryStream.info.squarePixelHeight = elementaryStream.info.height; + if (num > 0 && den > 0) if (num > den) elementaryStream.info.squarePixelWidth = Math.round(elementaryStream.info.width * num / den); + else elementaryStream.info.squarePixelHeight = Math.round(elementaryStream.info.height * den / num); + elementaryStream.info.colorSpace = { + primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[spsInfo.matrixCoefficients], + fullRange: !!spsInfo.fullRangeFlag + }; + elementaryStream.info.reorderSize = spsInfo.maxDecFrameBuffering; + break; + } else if (elementaryStream.info.codec === "hevc") { + if (!context.suppliedPacket) throw new Error("Invalid HEVC video stream; could not extract HVCDecoderConfigurationRecord from first packet."); + elementaryStream.info.hevcCodecInfo = extractHevcDecoderConfigurationRecord(context.suppliedPacket.data); + if (!elementaryStream.info.hevcCodecInfo) continue; + const spsUnit = elementaryStream.info.hevcCodecInfo.arrays.find((a) => a.nalUnitType === HevcNalUnitType.SPS_NUT).nalUnits[0]; + assert(spsUnit); + const spsInfo = parseHevcSps(spsUnit); + elementaryStream.info.width = spsInfo.displayWidth; + elementaryStream.info.height = spsInfo.displayHeight; + const num = spsInfo.pixelAspectRatio.num; + const den = spsInfo.pixelAspectRatio.den; + elementaryStream.info.squarePixelWidth = elementaryStream.info.width; + elementaryStream.info.squarePixelHeight = elementaryStream.info.height; + if (num > 0 && den > 0) if (num > den) elementaryStream.info.squarePixelWidth = Math.round(elementaryStream.info.width * num / den); + else elementaryStream.info.squarePixelHeight = Math.round(elementaryStream.info.height * den / num); + elementaryStream.info.colorSpace = { + primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries], + transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[spsInfo.transferCharacteristics], + matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[spsInfo.matrixCoefficients], + fullRange: !!spsInfo.fullRangeFlag + }; + elementaryStream.info.reorderSize = spsInfo.maxDecFrameBuffering; + break; + } else throw new Error("Unhandled."); + } + elementaryStream.info.decoderConfig = { + codec: extractVideoCodecString({ + width: elementaryStream.info.width, + height: elementaryStream.info.height, + codec: elementaryStream.info.codec, + codecDescription: null, + colorSpace: elementaryStream.info.colorSpace, + avcType: 1, + avcCodecInfo: elementaryStream.info.avcCodecInfo, + hevcCodecInfo: elementaryStream.info.hevcCodecInfo, + vp9CodecInfo: null, + av1CodecInfo: null, + proresFormat: null + }), + codedWidth: elementaryStream.info.width, + codedHeight: elementaryStream.info.height, + colorSpace: elementaryStream.info.colorSpace + }; + if (elementaryStream.info.width !== elementaryStream.info.squarePixelWidth || elementaryStream.info.height !== elementaryStream.info.squarePixelHeight) { + elementaryStream.info.decoderConfig.displayAspectWidth = elementaryStream.info.squarePixelWidth; + elementaryStream.info.decoderConfig.displayAspectHeight = elementaryStream.info.squarePixelHeight; + } + elementaryStream.initialized = true; + } else { + await context.markNextPacket(); + if (!context.suppliedPacket) throw new Error(`Couldn't parse first media packet for Elementary Stream with PID ${elementaryStream.pid}`); + if (elementaryStream.info.codec === "aac") { + const header = readAdtsFrameHeader(FileSlice.tempFromBytes(context.suppliedPacket.data)); + if (!header) throw new Error("Invalid AAC audio stream; could not read ADTS frame header from first packet."); + elementaryStream.info.aacCodecInfo = { + isMpeg2: false, + objectType: header.objectType + }; + elementaryStream.info.numberOfChannels = aacChannelMap[header.channelConfiguration]; + elementaryStream.info.sampleRate = aacFrequencyTable[header.samplingFrequencyIndex]; + } else if (elementaryStream.info.codec === "mp3") { + const result = readMp3FrameHeader(readU32Be(FileSlice.tempFromBytes(context.suppliedPacket.data)), context.suppliedPacket.data.byteLength); + if (!result.header) throw new Error("Invalid MP3 audio stream; could not read frame header from first packet."); + elementaryStream.info.numberOfChannels = getMp3ChannelCount(result.header.channel); + elementaryStream.info.sampleRate = result.header.sampleRate; + } else if (elementaryStream.info.codec === "ac3") { + const frameInfo = parseAc3SyncFrame(context.suppliedPacket.data); + if (!frameInfo) throw new Error("Invalid AC-3 audio stream; could not read sync frame from first packet."); + if (frameInfo.fscod === 3) throw new Error("Invalid AC-3 audio stream; reserved sample rate code found in first packet."); + elementaryStream.info.numberOfChannels = AC3_ACMOD_CHANNEL_COUNTS[frameInfo.acmod] + frameInfo.lfeon; + elementaryStream.info.sampleRate = AC3_SAMPLE_RATES[frameInfo.fscod]; + } else if (elementaryStream.info.codec === "eac3") { + const frameInfo = parseEac3SyncFrame(context.suppliedPacket.data); + if (!frameInfo) throw new Error("Invalid E-AC-3 audio stream; could not read sync frame from first packet."); + const sampleRate = getEac3SampleRate(frameInfo); + if (sampleRate === null) throw new Error("Invalid E-AC-3 audio stream; reserved sample rate code found in first packet."); + elementaryStream.info.numberOfChannels = getEac3ChannelCount(frameInfo); + elementaryStream.info.sampleRate = sampleRate; + } else if (elementaryStream.info.codec === "dts") { + const frameInfo = parseDtsFrame(context.suppliedPacket.data); + if (!frameInfo) throw new Error("Invalid DTS audio stream; could not read frame header from first packet."); + elementaryStream.info.numberOfChannels = frameInfo.numberOfChannels; + elementaryStream.info.sampleRate = frameInfo.sampleRate; + if (frameInfo.core) elementaryStream.info.dtsFormat = frameInfo.hasExtensions ? "dtsh" : "dtsc"; + } else throw new Error("Unhandled."); + elementaryStream.info.decoderConfig = { + codec: extractAudioCodecString({ + codec: elementaryStream.info.codec, + codecDescription: null, + aacCodecInfo: elementaryStream.info.aacCodecInfo, + dtsFormat: elementaryStream.info.dtsFormat + }), + numberOfChannels: elementaryStream.info.numberOfChannels, + sampleRate: elementaryStream.info.sampleRate + }; + elementaryStream.initialized = true; + } + } + } + if (hasProgramMap && this.elementaryStreams.every((x) => x.initialized)) break; + currentPos += this.packetStride; + } + if (!hasProgramMap) { + if (!hasProgramAssociationTable) throw new Error("No Program Association Table found in the file."); + throw new Error("No Program Map Table found in the file."); + } + for (const stream of this.elementaryStreams) { + if (!stream.initialized) continue; + if (stream.info.type === "video") this.trackBackingEntries.push(new MpegTsVideoTrackBacking(stream)); + else this.trackBackingEntries.push(new MpegTsAudioTrackBacking(stream)); + } + })(); + } + async getTrackBackings() { + await this.readMetadata(); + return this.trackBackingEntries; + } + async getMetadataTags() { + return {}; + } + async getMimeType() { + await this.readMetadata(); + return buildMpegTsMimeType(await Promise.all(this.trackBackingEntries.map((x) => x.getDecoderConfig().then((c) => c?.codec ?? null)))); + } + async readSection(startPos, full, contiguous = false) { + let endPos = startPos; + let currentPos = startPos; + const chunks = []; + let chunksByteLength = 0; + let firstPacket = null; + let mustAddSectionEnd = true; + let randomAccessIndicator = 0; + while (true) { + const packet = await this.readPacket(currentPos); + currentPos += this.packetStride; + if (!packet) break; + if (!firstPacket) { + if (packet.payloadUnitStartIndicator === 0) break; + firstPacket = packet; + } else { + if (packet.pid !== firstPacket.pid) if (contiguous) break; + else continue; + if (packet.payloadUnitStartIndicator === 1) break; + } + const hasAdaptationField = !!(packet.adaptationFieldControl & 2); + const hasPayload = !!(packet.adaptationFieldControl & 1); + let adaptationFieldLength = 0; + if (hasAdaptationField) { + adaptationFieldLength = 1 + packet.body[0]; + if (packet === firstPacket && adaptationFieldLength > 1) randomAccessIndicator = packet.body[1] >> 6 & 1; + } + if (hasPayload) if (adaptationFieldLength === 0) { + chunks.push(packet.body); + chunksByteLength += packet.body.byteLength; + } else { + chunks.push(packet.body.subarray(adaptationFieldLength)); + chunksByteLength += packet.body.byteLength - adaptationFieldLength; + } + endPos = currentPos; + if (!full && chunksByteLength >= 64) { + mustAddSectionEnd = false; + break; + } + if (binarySearchExact(this.sectionEndPositions, endPos, (x) => x) !== -1) { + mustAddSectionEnd = false; + break; + } + } + if (mustAddSectionEnd) { + const index = binarySearchLessOrEqual(this.sectionEndPositions, endPos, (x) => x); + this.sectionEndPositions.splice(index + 1, 0, endPos); + } + if (!firstPacket) return null; + let merged; + if (chunks.length === 1) merged = chunks[0]; + else { + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + merged = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + merged.set(chunk, offset); + offset += chunk.length; + } + } + return { + startPos, + endPos: full ? endPos : null, + pid: firstPacket.pid, + payload: merged, + randomAccessIndicator + }; + } + async readPacketHeader(pos) { + let slice = this.reader.requestSlice(pos, 4); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + if (readU8(slice) !== 71) throw new Error("Invalid TS packet sync byte. Likely an internal bug, please report this file."); + const nextTwoBytes = readU16Be(slice); + nextTwoBytes >> 15; + const payloadUnitStartIndicator = nextTwoBytes >> 14 & 1; + nextTwoBytes >> 13 & 1; + const pid = nextTwoBytes & 8191; + const nextByte = readU8(slice); + nextByte >> 6; + const adaptationFieldControl = nextByte >> 4 & 3; + nextByte & 15; + return { + payloadUnitStartIndicator, + pid, + adaptationFieldControl + }; + } + async readPacket(pos) { + let slice = this.reader.requestSlice(pos, 188); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + const bytes = readBytes(slice, 188); + if (bytes[0] !== 71) throw new Error("Invalid TS packet sync byte. Likely an internal bug, please report this file."); + const nextTwoBytes = (bytes[1] << 8) + bytes[2]; + nextTwoBytes >> 15; + const payloadUnitStartIndicator = nextTwoBytes >> 14 & 1; + nextTwoBytes >> 13 & 1; + const pid = nextTwoBytes & 8191; + const nextByte = bytes[3]; + nextByte >> 6; + const adaptationFieldControl = nextByte >> 4 & 3; + nextByte & 15; + return { + payloadUnitStartIndicator, + pid, + adaptationFieldControl, + body: bytes.subarray(4) + }; + } + normalizeTimestamp(timestamp) { + if (!this.timestampWrapInfo) { + const tolerance = 60 * TIMESCALE; + this.timestampWrapInfo = { + reference: timestamp - tolerance, + offset: timestamp >= TIMESTAMP_MODULUS - tolerance ? -TIMESTAMP_MODULUS : TIMESTAMP_MODULUS + }; + } + const { reference, offset } = this.timestampWrapInfo; + if (offset < 0 && timestamp >= reference || offset > 0 && timestamp < reference) return timestamp + offset; + return timestamp; + } +}; +var readPesPacketHeader = (demuxer, section, expectPts) => { + if (section.payload.byteLength < 3) return null; + const bitstream = new Bitstream(section.payload); + if (bitstream.readBits(24) !== 1) return null; + const streamId = bitstream.readBits(8); + bitstream.skipBits(16); + if (streamId === 188 || streamId === 190 || streamId === 191 || streamId === 240 || streamId === 241 || streamId === 255 || streamId === 242 || streamId === 248) return null; + bitstream.skipBits(8); + const ptsDtsFlags = bitstream.readBits(2); + bitstream.skipBits(14); + let pts = null; + if (ptsDtsFlags === 2 || ptsDtsFlags === 3) { + pts = 0; + bitstream.skipBits(4); + pts += bitstream.readBits(3) * (1 << 30); + bitstream.skipBits(1); + pts += bitstream.readBits(15) * 32768; + bitstream.skipBits(1); + pts += bitstream.readBits(15); + pts = demuxer.normalizeTimestamp(pts); + } else if (expectPts) throw new Error(MISSING_PTS_ERROR_MESSAGE); + return { + sectionStartPos: section.startPos, + sectionEndPos: section.endPos, + pts, + randomAccessIndicator: section.randomAccessIndicator + }; +}; +var readPesPacket = (demuxer, section, expectPts) => { + assert(section.endPos !== null); + const header = readPesPacketHeader(demuxer, section, expectPts); + if (!header) return null; + const bitstream = new Bitstream(section.payload); + bitstream.skipBits(32); + const pesPacketLength = bitstream.readBits(16); + const BYTES_UNTIL_END_OF_PES_PACKET_LENGTH = 6; + bitstream.skipBits(16); + const pesHeaderDataLength = bitstream.readBits(8); + const pesHeaderEndPos = bitstream.pos + 8 * pesHeaderDataLength; + bitstream.pos = pesHeaderEndPos; + const bytePos = pesHeaderEndPos / 8; + assert(Number.isInteger(bytePos)); + const data = section.payload.subarray(bytePos, pesPacketLength > 0 ? BYTES_UNTIL_END_OF_PES_PACKET_LENGTH + pesPacketLength : section.payload.byteLength); + return { + ...header, + data + }; +}; +var MpegTsTrackBacking = class MpegTsTrackBacking { + constructor(elementaryStream) { + this.elementaryStream = elementaryStream; + this.packetBuffers = /* @__PURE__ */ new WeakMap(); + /** Used for recreating PacketBuffers if necessary. */ + this.packetSectionStarts = /* @__PURE__ */ new WeakMap(); + } + getId() { + return this.elementaryStream.pid; + } + getNumber() { + const demuxer = this.elementaryStream.demuxer; + const trackType = this.elementaryStream.info.type; + let number = 0; + for (const backing of demuxer.trackBackingEntries) { + if (backing.getType() === trackType) number++; + assert(backing instanceof MpegTsTrackBacking); + if (backing.elementaryStream === this.elementaryStream) break; + } + return number; + } + getCodec() { + throw new Error("Not implemented on base class."); + } + getInternalCodecId() { + return this.elementaryStream.streamType; + } + getName() { + return null; + } + getLanguageCode() { + return "und"; + } + getDisposition() { + return { + ...DEFAULT_TRACK_DISPOSITION, + primary: false + }; + } + getTimeResolution() { + return TIMESCALE; + } + isRelativeToUnixEpoch() { + return false; + } + getUnixTimeForTimestamp() { + return null; + } + getPairingMask() { + return 1n; + } + getBitrate() { + return null; + } + getAverageBitrate() { + return null; + } + async getDurationFromMetadata() { + return null; + } + async getLiveRefreshInterval() { + return null; + } + createEncodedPacket(suppliedPacket, duration, options) { + let packetType; + if (this.allPacketsAreKeyPackets()) packetType = "key"; + else packetType = suppliedPacket.randomAccessIndicator === 1 ? "key" : "delta"; + return new EncodedPacket(options.metadataOnly ? PLACEHOLDER_DATA : suppliedPacket.data, packetType, suppliedPacket.pts / TIMESCALE, Math.max(duration / TIMESCALE, 0), suppliedPacket.sequenceNumber, suppliedPacket.data.byteLength); + } + async getFirstPacket(options) { + const section = this.elementaryStream.firstSection; + assert(section); + const pesPacket = readPesPacket(this.elementaryStream.demuxer, section, true); + assert(pesPacket); + const context = new PacketReadingContext(this.elementaryStream, pesPacket); + const buffer = new PacketBuffer(this, context); + const result = await buffer.readNext(); + if (!result) return null; + const packet = this.createEncodedPacket(result.packet, result.duration, options); + this.packetBuffers.set(packet, buffer); + this.packetSectionStarts.set(packet, result.packet.sectionStartPos); + return packet; + } + async getNextPacket(packet, options) { + let buffer = this.packetBuffers.get(packet); + if (buffer) { + const result = await buffer.readNext(); + if (!result) return null; + this.packetBuffers.delete(packet); + const newPacket = this.createEncodedPacket(result.packet, result.duration, options); + this.packetBuffers.set(newPacket, buffer); + this.packetSectionStarts.set(newPacket, result.packet.sectionStartPos); + return newPacket; + } + const sectionStartPos = this.packetSectionStarts.get(packet); + if (sectionStartPos === void 0) throw new Error("Packet was not created from this track."); + const demuxer = this.elementaryStream.demuxer; + const section = await demuxer.readSection(sectionStartPos, true); + assert(section); + const pesPacket = readPesPacket(demuxer, section, true); + assert(pesPacket); + const context = new PacketReadingContext(this.elementaryStream, pesPacket); + buffer = new PacketBuffer(this, context); + const targetSequenceNumber = packet.sequenceNumber; + while (true) { + const result = await buffer.readNext(); + if (!result) return null; + if (result.packet.sequenceNumber > targetSequenceNumber) { + const newPacket = this.createEncodedPacket(result.packet, result.duration, options); + this.packetBuffers.set(newPacket, buffer); + this.packetSectionStarts.set(newPacket, result.packet.sectionStartPos); + return newPacket; + } + } + } + async getNextKeyPacket(packet, options) { + let currentPacket = packet; + while (true) { + currentPacket = await this.getNextPacket(currentPacket, options); + if (!currentPacket) return null; + if (currentPacket.type === "key") return currentPacket; + } + } + getPacket(timestamp, options) { + return this.doPacketLookup(timestamp, false, options); + } + getKeyPacket(timestamp, options) { + return this.doPacketLookup(timestamp, true, options); + } + /** + * Searches for the packet with the largest timestamp not larger than `timestamp` in the file, using a combination + * of chunk-based binary search and linear refinement. The reason the coarse search is done in large chunks is to + * make it more performant for small files and over high-latency readers such as the network. + */ + async doPacketLookup(timestamp, keyframesOnly, options) { + const searchPts = roundIfAlmostInteger(timestamp * TIMESCALE); + const demuxer = this.elementaryStream.demuxer; + const { reader, seekChunkSize } = demuxer; + const pid = this.elementaryStream.pid; + const findFirstPesPacketHeaderInChunk = async (startPos, endPos, readSectionInFull) => { + let currentPos = startPos; + while (currentPos < endPos) { + const packetHeader = await demuxer.readPacketHeader(currentPos); + if (!packetHeader) return null; + if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) { + const section = await demuxer.readSection(currentPos, readSectionInFull); + if (!section) return null; + const pesPacketHeader = readPesPacketHeader(demuxer, section, false); + if (pesPacketHeader && pesPacketHeader.pts !== null) return { + pesPacketHeader, + section + }; + } + currentPos += demuxer.packetStride; + } + return null; + }; + const firstSection = this.elementaryStream.firstSection; + assert(firstSection); + const firstPesPacketHeader = readPesPacketHeader(demuxer, firstSection, true); + assert(firstPesPacketHeader); + if (searchPts < firstPesPacketHeader.pts) return null; + let scanStartPos; + const referencePesPackets = this.elementaryStream.referencePesPackets; + const referencePointIndex = binarySearchLessOrEqual(referencePesPackets, searchPts, (x) => x.pts); + const referencePoint = referencePointIndex !== -1 ? referencePesPackets[referencePointIndex] : null; + if (referencePoint && searchPts - referencePoint.pts < 9e4 / 2) scanStartPos = referencePoint.sectionStartPos; + else { + let startChunkIndex = 0; + if (reader.fileSize !== null) { + const numChunks = Math.ceil(reader.fileSize / seekChunkSize); + if (numChunks > 1) { + let low = 0; + let high = numChunks - 1; + startChunkIndex = low; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + const chunkStartPos = floorToMultiple(mid * seekChunkSize, demuxer.packetStride) + firstPesPacketHeader.sectionStartPos; + const result = await findFirstPesPacketHeaderInChunk(chunkStartPos, chunkStartPos + seekChunkSize, false); + if (!result) { + high = mid - 1; + continue; + } + if (result.pesPacketHeader.pts <= searchPts) { + startChunkIndex = mid; + low = mid + 1; + } else high = mid - 1; + } + } + } + scanStartPos = floorToMultiple(startChunkIndex * seekChunkSize, demuxer.packetStride) + firstPesPacketHeader.sectionStartPos; + } + let currentPesHeader = (await findFirstPesPacketHeaderInChunk(scanStartPos, reader.fileSize ?? Infinity, false))?.pesPacketHeader ?? null; + if (!currentPesHeader) currentPesHeader = firstPesPacketHeader; + const reorderSize = this.getReorderSize(); + const retrieveEncodedPacket = async (sectionStartPos, predicate) => { + const section = await demuxer.readSection(sectionStartPos, true); + assert(section); + const pesPacket = readPesPacket(demuxer, section, true); + assert(pesPacket); + const context = new PacketReadingContext(this.elementaryStream, pesPacket); + const buffer = new PacketBuffer(this, context); + while (true) { + if ((last(buffer.presentationOrderPackets)?.pts ?? -Infinity) >= searchPts) break; + if (!await buffer.readNextPacket()) break; + } + const targetIndex = findLastIndex(buffer.presentationOrderPackets, predicate); + if (targetIndex === -1) return null; + const targetPacket = buffer.presentationOrderPackets[targetIndex]; + const lastDuration = targetIndex === 0 ? 0 : targetPacket.pts - buffer.presentationOrderPackets[targetIndex - 1].pts; + while (buffer.decodeOrderPackets[0] !== targetPacket) buffer.decodeOrderPackets.shift(); + buffer.lastDuration = lastDuration; + const result = await buffer.readNext(); + assert(result); + const packet = this.createEncodedPacket(result.packet, result.duration, options); + this.packetBuffers.set(packet, buffer); + this.packetSectionStarts.set(packet, result.packet.sectionStartPos); + return packet; + }; + if (!keyframesOnly || this.allPacketsAreKeyPackets()) { + outer: while (true) { + let currentPos = currentPesHeader.sectionStartPos + demuxer.packetStride; + while (true) { + const packetHeader = await demuxer.readPacketHeader(currentPos); + if (!packetHeader) break outer; + if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) { + const section = await demuxer.readSection(currentPos, false); + if (section) { + const nextPesHeader = readPesPacketHeader(demuxer, section, false); + if (nextPesHeader && nextPesHeader.pts !== null) { + if (nextPesHeader.pts > searchPts) break outer; + currentPesHeader = nextPesHeader; + maybeInsertReferencePacket(this.elementaryStream, currentPesHeader); + break; + } + } + } + currentPos += demuxer.packetStride; + } + } + outer: for (let i = 0; i < reorderSize + 1; i++) { + let pos = currentPesHeader.sectionStartPos - demuxer.packetStride; + while (pos >= demuxer.packetOffset) { + const packetHeader = await demuxer.readPacketHeader(pos); + if (!packetHeader) break outer; + if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) { + const section = await demuxer.readSection(pos, false); + if (section) { + const header = readPesPacketHeader(demuxer, section, false); + if (header && header.pts !== null) { + currentPesHeader = header; + break; + } + } + } + pos -= demuxer.packetStride; + } + } + return retrieveEncodedPacket(currentPesHeader.sectionStartPos, (p) => p.pts <= searchPts); + } else { + let currentChunkStartPos = scanStartPos; + let nextChunkStartPos = null; + const readSectionsInFull = !this.elementaryStream.canBeTrustedWithKeyPackets; + while (true) { + let bestKeyPesHeader = null; + const isFirstChunk = currentChunkStartPos <= firstPesPacketHeader.sectionStartPos; + let pesHeader; + let pesHeaderSection = null; + if (isFirstChunk) { + pesHeader = firstPesPacketHeader; + pesHeaderSection = firstSection; + } else { + const result = await findFirstPesPacketHeaderInChunk(currentChunkStartPos, reader.fileSize ?? Infinity, readSectionsInFull); + pesHeader = result?.pesPacketHeader ?? null; + pesHeaderSection = result?.section ?? null; + } + let passedSearchPts = false; + let lookaheadCount = 0; + outer: while (pesHeader) { + if (nextChunkStartPos !== null && pesHeader.sectionStartPos >= nextChunkStartPos) break; + if (pesHeader.pts <= searchPts) { + let isKeyPacket; + if (this.elementaryStream.canBeTrustedWithKeyPackets) isKeyPacket = pesHeader.randomAccessIndicator === 1; + else { + assert(pesHeaderSection); + const pesPacket = readPesPacket(demuxer, pesHeaderSection, true); + assert(pesPacket); + const context = new PacketReadingContext(this.elementaryStream, pesPacket); + await context.markNextPacket(); + isKeyPacket = context.suppliedPacket?.randomAccessIndicator === 1; + } + if (isKeyPacket) bestKeyPesHeader = pesHeader; + } + if (pesHeader.pts > searchPts) passedSearchPts = true; + if (passedSearchPts) { + lookaheadCount++; + if (lookaheadCount > reorderSize) break; + } + let currentPos = pesHeader.sectionStartPos + demuxer.packetStride; + while (true) { + const packetHeader = await demuxer.readPacketHeader(currentPos); + if (!packetHeader) break outer; + if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) { + const section = await demuxer.readSection(currentPos, readSectionsInFull); + if (section) { + const nextPesHeader = readPesPacketHeader(demuxer, section, false); + if (nextPesHeader && nextPesHeader.pts !== null) { + pesHeader = nextPesHeader; + pesHeaderSection = section; + maybeInsertReferencePacket(this.elementaryStream, pesHeader); + break; + } + } + } + currentPos += demuxer.packetStride; + } + } + if (bestKeyPesHeader) { + let startPesHeader = bestKeyPesHeader; + if (lookaheadCount === 0) outer: for (let i = 0; i < reorderSize; i++) { + let pos = startPesHeader.sectionStartPos - demuxer.packetStride; + while (pos >= demuxer.packetOffset) { + const packetHeader = await demuxer.readPacketHeader(pos); + if (!packetHeader) break outer; + if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) { + const section = await demuxer.readSection(pos, readSectionsInFull); + if (section) { + const header = readPesPacketHeader(demuxer, section, false); + if (header && header.pts !== null) { + startPesHeader = header; + break; + } + } + } + pos -= demuxer.packetStride; + } + } + const encodedPacket = await retrieveEncodedPacket(startPesHeader.sectionStartPos, (p) => p.pts <= searchPts && p.randomAccessIndicator === 1); + assert(encodedPacket); + return encodedPacket; + } + if (isFirstChunk) return null; + nextChunkStartPos = currentChunkStartPos; + currentChunkStartPos = Math.max(floorToMultiple(currentChunkStartPos - firstPesPacketHeader.sectionStartPos - seekChunkSize, demuxer.packetStride) + firstPesPacketHeader.sectionStartPos, firstPesPacketHeader.sectionStartPos); + } + } + } +}; +var MpegTsVideoTrackBacking = class extends MpegTsTrackBacking { + getType() { + return "video"; + } + getCodec() { + return this.elementaryStream.info.codec; + } + getCodedWidth() { + return this.elementaryStream.info.width; + } + getCodedHeight() { + return this.elementaryStream.info.height; + } + getSquarePixelWidth() { + return this.elementaryStream.info.squarePixelWidth; + } + getSquarePixelHeight() { + return this.elementaryStream.info.squarePixelHeight; + } + getTransformationMatrix() { + return [...IDENTITY_MATRIX]; + } + async getColorSpace() { + return this.elementaryStream.info.colorSpace; + } + async canBeTransparent() { + return false; + } + async getDecoderConfig() { + assert(this.elementaryStream.info.decoderConfig); + return this.elementaryStream.info.decoderConfig; + } + allPacketsAreKeyPackets() { + return false; + } + getReorderSize() { + return this.elementaryStream.info.reorderSize; + } +}; +var MpegTsAudioTrackBacking = class extends MpegTsTrackBacking { + getType() { + return "audio"; + } + getCodec() { + return this.elementaryStream.info.codec; + } + getNumberOfChannels() { + return this.elementaryStream.info.numberOfChannels; + } + getSampleRate() { + return this.elementaryStream.info.sampleRate; + } + async getDecoderConfig() { + assert(this.elementaryStream.info.decoderConfig); + return this.elementaryStream.info.decoderConfig; + } + allPacketsAreKeyPackets() { + return true; + } + getReorderSize() { + return 0; + } +}; +var maybeInsertReferencePacket = (elementaryStream, pesPacketHeader) => { + const referencePesPackets = elementaryStream.referencePesPackets; + const index = binarySearchLessOrEqual(referencePesPackets, pesPacketHeader.sectionStartPos, (x) => x.sectionStartPos); + if (index >= 0) { + const entry = referencePesPackets[index]; + if (pesPacketHeader.pts <= entry.pts) return false; + const minByteDistance = elementaryStream.demuxer.minReferencePointByteDistance; + if (pesPacketHeader.sectionStartPos - entry.sectionStartPos < minByteDistance) return false; + if (index < referencePesPackets.length - 1) { + const nextEntry = referencePesPackets[index + 1]; + if (nextEntry.pts < pesPacketHeader.pts) return false; + if (nextEntry.sectionStartPos - pesPacketHeader.sectionStartPos < minByteDistance) return false; + } + } + referencePesPackets.splice(index + 1, 0, pesPacketHeader); + return true; +}; +/** Stateful context used to extract exact encoded packets from the underlying data stream. */ +var PacketReadingContext = class { + constructor(elementaryStream, startingPesPacket) { + this.currentPos = 0; + this.pesPackets = []; + this.currentPesPacketIndex = 0; + this.currentPesPacketPos = 0; + this.endPos = 0; + this.lastSuppliedPesPacket = null; + this.nextPts = null; + this.suppliedPacket = null; + this.elementaryStream = elementaryStream; + this.pid = elementaryStream.pid; + this.demuxer = elementaryStream.demuxer; + this.startingPesPacket = startingPesPacket; + } + ensureBuffered(length) { + const remaining = this.endPos - this.currentPos; + if (remaining >= length) return length; + return this.bufferData(length - remaining).then(() => Math.min(this.endPos - this.currentPos, length)); + } + getCurrentPesPacket() { + const packet = this.pesPackets[this.currentPesPacketIndex]; + assert(packet); + return packet; + } + async bufferData(length) { + const targetEndPos = this.endPos + length; + while (this.endPos < targetEndPos) { + let pesPacket; + if (this.pesPackets.length === 0) pesPacket = this.startingPesPacket; + else { + let currentPos = last(this.pesPackets).sectionEndPos; + assert(currentPos !== null); + while (true) { + const packetHeader = await this.demuxer.readPacketHeader(currentPos); + if (!packetHeader) return; + if (packetHeader.pid === this.pid) { + const nextSection = await this.demuxer.readSection(currentPos, true); + if (!nextSection) return; + const nextPesPacket = readPesPacket(this.demuxer, nextSection, false); + if (nextPesPacket) { + pesPacket = nextPesPacket; + break; + } + } + currentPos += this.demuxer.packetStride; + } + } + this.pesPackets.push(pesPacket); + this.endPos += pesPacket.data.byteLength; + } + } + readBytes(length) { + const currentPesPacket = this.getCurrentPesPacket(); + const relativeStartOffset = this.currentPos - this.currentPesPacketPos; + const relativeEndOffset = relativeStartOffset + length; + this.currentPos += length; + if (relativeEndOffset <= currentPesPacket.data.byteLength) return currentPesPacket.data.subarray(relativeStartOffset, relativeEndOffset); + const result = new Uint8Array(length); + result.set(currentPesPacket.data.subarray(relativeStartOffset)); + let offset = currentPesPacket.data.byteLength - relativeStartOffset; + while (true) { + this.advanceCurrentPacket(); + const currentPesPacket = this.getCurrentPesPacket(); + const relativeEndOffset = length - offset; + if (relativeEndOffset <= currentPesPacket.data.byteLength) { + result.set(currentPesPacket.data.subarray(0, relativeEndOffset), offset); + break; + } + result.set(currentPesPacket.data, offset); + offset += currentPesPacket.data.byteLength; + } + return result; + } + readU8() { + let currentPesPacket = this.getCurrentPesPacket(); + const relativeOffset = this.currentPos - this.currentPesPacketPos; + this.currentPos++; + if (relativeOffset < currentPesPacket.data.byteLength) return currentPesPacket.data[relativeOffset]; + this.advanceCurrentPacket(); + currentPesPacket = this.getCurrentPesPacket(); + return currentPesPacket.data[0]; + } + seekTo(pos) { + if (pos === this.currentPos) return; + if (pos < this.currentPos) while (pos < this.currentPesPacketPos) { + this.currentPesPacketIndex--; + const currentPacket = this.getCurrentPesPacket(); + this.currentPesPacketPos -= currentPacket.data.byteLength; + } + else while (true) { + const currentPesPacket = this.getCurrentPesPacket(); + if (pos < this.currentPesPacketPos + currentPesPacket.data.byteLength) break; + this.currentPesPacketPos += currentPesPacket.data.byteLength; + this.currentPesPacketIndex++; + } + this.currentPos = pos; + } + skip(n) { + this.seekTo(this.currentPos + n); + } + advanceCurrentPacket() { + this.currentPesPacketPos += this.getCurrentPesPacket().data.byteLength; + this.currentPesPacketIndex++; + } + async markNextPacket() { + assert(!this.suppliedPacket); + const elementaryStream = this.elementaryStream; + if (elementaryStream.info.type === "video") { + const codec = elementaryStream.info.codec; + const CHUNK_SIZE = 1024; + if (codec !== "avc" && codec !== "hevc") throw new Error("Unhandled."); + const nalHeaderSize = codec === "avc" ? 1 : 2; + let packetStartPos = null; + let frameStartFound = false; + let lastFirstMacroblockInSlice = 0; + while (true) { + let remaining = this.ensureBuffered(CHUNK_SIZE); + if (isThenable(remaining)) remaining = await remaining; + if (remaining === 0) break; + const chunkStartPos = this.currentPos; + const chunk = this.readBytes(remaining); + const length = chunk.byteLength; + let i = 0; + while (i < length) { + const zeroIndex = chunk.indexOf(0, i); + if (zeroIndex === -1 || zeroIndex >= length) break; + i = zeroIndex; + const posBeforeZero = chunkStartPos + i; + if (i + 3 >= length) { + this.seekTo(posBeforeZero); + break; + } + const b1 = chunk[i + 1]; + const b2 = chunk[i + 2]; + const b3 = chunk[i + 3]; + let startCodeLength = 0; + if (b1 === 0 && b2 === 0 && b3 === 1) startCodeLength = 4; + else if (b1 === 0 && b2 === 1) startCodeLength = 3; + if (startCodeLength === 0) { + i++; + continue; + } + const startCodePos = posBeforeZero; + packetStartPos ??= startCodePos; + const nalHeaderStart = i + startCodeLength; + const payloadStart = nalHeaderStart + nalHeaderSize; + const AVC_SLICE_HEADER_PEEK_SIZE = 6; + if (payloadStart + (codec === "avc" ? AVC_SLICE_HEADER_PEEK_SIZE : 1) > length) { + this.seekTo(posBeforeZero); + break; + } + const headerByte0 = chunk[nalHeaderStart]; + let nalUnitType; + let isSlice; + let isAccessUnitStart; + if (codec === "avc") { + nalUnitType = extractNalUnitTypeForAvc(headerByte0); + isSlice = nalUnitType === AvcNalUnitType.NON_IDR_SLICE || nalUnitType === AvcNalUnitType.SLICE_DPA || nalUnitType === AvcNalUnitType.IDR; + isAccessUnitStart = nalUnitType === AvcNalUnitType.SEI || nalUnitType === AvcNalUnitType.SPS || nalUnitType === AvcNalUnitType.PPS || nalUnitType === AvcNalUnitType.AUD; + } else { + nalUnitType = extractNalUnitTypeForHevc(headerByte0); + if (((headerByte0 & 1) << 5 | chunk[nalHeaderStart + 1] >> 3) > 0) { + i += startCodeLength; + continue; + } + isSlice = nalUnitType <= HevcNalUnitType.RASL_R || nalUnitType >= HevcNalUnitType.BLA_W_LP && nalUnitType <= 21; + isAccessUnitStart = nalUnitType >= HevcNalUnitType.VPS_NUT && nalUnitType <= 37 || nalUnitType === HevcNalUnitType.PREFIX_SEI_NUT || nalUnitType >= 41 && nalUnitType <= 44 || nalUnitType >= 48 && nalUnitType <= 55; + } + let isFrameBoundary = false; + if (isSlice) { + let startsNewPicture; + if (codec === "avc") { + const firstMacroblockInSlice = readExpGolomb(new Bitstream(chunk.subarray(payloadStart, payloadStart + AVC_SLICE_HEADER_PEEK_SIZE))); + startsNewPicture = !frameStartFound || firstMacroblockInSlice <= lastFirstMacroblockInSlice; + lastFirstMacroblockInSlice = firstMacroblockInSlice; + } else startsNewPicture = chunk[payloadStart] >> 7 === 1; + if (startsNewPicture) if (frameStartFound) isFrameBoundary = true; + else frameStartFound = true; + } else if (isAccessUnitStart && frameStartFound) isFrameBoundary = true; + if (isFrameBoundary) { + const packetLength = startCodePos - packetStartPos; + this.seekTo(packetStartPos); + return this.supplyPacket(packetLength, 0); + } + i += startCodeLength; + } + if (remaining < CHUNK_SIZE) break; + } + if (packetStartPos !== null && this.endPos > packetStartPos) { + const packetLength = this.endPos - packetStartPos; + this.seekTo(packetStartPos); + return this.supplyPacket(packetLength, 0); + } + } else { + const codec = elementaryStream.info.codec; + const CHUNK_SIZE = 128; + while (true) { + let remaining = this.ensureBuffered(CHUNK_SIZE); + if (isThenable(remaining)) remaining = await remaining; + const startPos = this.currentPos; + while (this.currentPos - startPos < remaining) { + const byte = this.readU8(); + if (codec === "aac") { + if (byte !== 255) continue; + this.skip(-1); + const possibleHeaderStartPos = this.currentPos; + let remaining = this.ensureBuffered(9); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < 9) return; + const headerBytes = this.readBytes(9); + const header = readAdtsFrameHeader(FileSlice.tempFromBytes(headerBytes)); + if (header) { + this.seekTo(possibleHeaderStartPos); + let remaining = this.ensureBuffered(header.frameLength); + if (isThenable(remaining)) remaining = await remaining; + return this.supplyPacket(remaining, Math.round(SAMPLES_PER_AAC_FRAME * TIMESCALE / elementaryStream.info.sampleRate)); + } else this.seekTo(possibleHeaderStartPos + 1); + } else if (codec === "mp3") { + if (byte !== 255) continue; + this.skip(-1); + const possibleHeaderStartPos = this.currentPos; + let remaining = this.ensureBuffered(4); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < 4) return; + const result = readMp3FrameHeader(toDataView(this.readBytes(4)).getUint32(0), null); + if (result.header) { + this.seekTo(possibleHeaderStartPos); + let remaining = this.ensureBuffered(result.header.totalSize); + if (isThenable(remaining)) remaining = await remaining; + const duration = result.header.audioSamplesInFrame * TIMESCALE / elementaryStream.info.sampleRate; + return this.supplyPacket(remaining, Math.round(duration)); + } else this.seekTo(possibleHeaderStartPos + 1); + } else if (codec === "ac3") { + if (byte !== 11) continue; + this.skip(-1); + const possibleSyncPos = this.currentPos; + let remaining = this.ensureBuffered(5); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < 5) return; + const headerBytes = this.readBytes(5); + if (headerBytes[0] !== 11 || headerBytes[1] !== 119) { + this.seekTo(possibleSyncPos + 1); + continue; + } + const fscod = headerBytes[4] >> 6; + const frmsizecod = headerBytes[4] & 63; + if (fscod === 3 || frmsizecod > 37) { + this.seekTo(possibleSyncPos + 1); + continue; + } + const frameSize = AC3_FRAME_SIZES[3 * frmsizecod + fscod]; + assert(frameSize !== void 0); + this.seekTo(possibleSyncPos); + remaining = this.ensureBuffered(frameSize); + if (isThenable(remaining)) remaining = await remaining; + const duration = Math.round(AC3_SAMPLES_PER_FRAME * TIMESCALE / elementaryStream.info.sampleRate); + return this.supplyPacket(remaining, duration); + } else if (codec === "eac3") { + if (byte !== 11) continue; + this.skip(-1); + const possibleSyncPos = this.currentPos; + let remaining = this.ensureBuffered(5); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < 5) return; + const headerBytes = this.readBytes(5); + if (headerBytes[0] !== 11 || headerBytes[1] !== 119) { + this.seekTo(possibleSyncPos + 1); + continue; + } + const frameSize = (((headerBytes[2] & 7) << 8 | headerBytes[3]) + 1) * 2; + const numblks = EAC3_NUMBLKS_TABLE[headerBytes[4] >> 6 === 3 ? 3 : headerBytes[4] >> 4 & 3]; + this.seekTo(possibleSyncPos); + remaining = this.ensureBuffered(frameSize); + if (isThenable(remaining)) remaining = await remaining; + const samplesPerFrame = numblks * 256; + const duration = Math.round(samplesPerFrame * TIMESCALE / elementaryStream.info.sampleRate); + return this.supplyPacket(remaining, duration); + } else if (codec === "dts") { + if (byte !== 127 && byte !== 100) continue; + this.skip(-1); + const possibleSyncPos = this.currentPos; + let remaining = this.ensureBuffered(18); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < 18) return; + const headerBytes = this.readBytes(18); + const core = parseDtsCoreFrameHeader(headerBytes); + let leadingExss = core ? null : parseDtsExssHeader(headerBytes); + if (!core && !leadingExss) { + this.seekTo(possibleSyncPos + 1); + continue; + } + if (leadingExss && !leadingExss.asset) { + this.seekTo(possibleSyncPos); + const headerBound = Math.min(leadingExss.frameSize, DTS_EXSS_MAX_HEADER_SIZE); + let remaining = this.ensureBuffered(headerBound); + if (isThenable(remaining)) remaining = await remaining; + leadingExss = parseDtsExssHeader(this.readBytes(remaining)) ?? leadingExss; + } + let frameSize = core ? core.frameSize : leadingExss.frameSize; + if (core) { + let nextSubstreamPos = Math.ceil(core.frameSize / 4) * 4; + while (true) { + this.seekTo(possibleSyncPos); + const neededBytes = nextSubstreamPos + 10; + let remaining = this.ensureBuffered(neededBytes); + if (isThenable(remaining)) remaining = await remaining; + if (remaining < neededBytes) break; + this.seekTo(possibleSyncPos + nextSubstreamPos); + const exss = parseDtsExssHeader(this.readBytes(10)); + if (!exss) break; + nextSubstreamPos += exss.frameSize; + frameSize = nextSubstreamPos; + } + } + const sampleCount = core?.sampleCount ?? leadingExss.asset?.sampleCount; + if (sampleCount === void 0) { + this.seekTo(possibleSyncPos + 1); + continue; + } + this.seekTo(possibleSyncPos); + remaining = this.ensureBuffered(frameSize); + if (isThenable(remaining)) remaining = await remaining; + const duration = Math.round(sampleCount * TIMESCALE / elementaryStream.info.sampleRate); + return this.supplyPacket(remaining, duration); + } else throw new Error("Unhandled."); + } + if (remaining < CHUNK_SIZE) break; + } + } + } + /** Supplies the context with a new encoded packet, beginning at the current position. */ + supplyPacket(packetLength, intrinsicDuration) { + const currentPesPacket = this.getCurrentPesPacket(); + let pts; + if (this.lastSuppliedPesPacket === currentPesPacket) { + assert(this.nextPts !== null); + pts = this.nextPts; + } else { + if (currentPesPacket.pts === null) throw new Error(MISSING_PTS_ERROR_MESSAGE); + pts = currentPesPacket.pts; + maybeInsertReferencePacket(this.elementaryStream, currentPesPacket); + } + this.lastSuppliedPesPacket = currentPesPacket; + this.nextPts = pts + intrinsicDuration; + const sectionStartPos = currentPesPacket.sectionStartPos; + const sequenceNumber = sectionStartPos + (this.currentPos - this.currentPesPacketPos); + const data = this.readBytes(packetLength); + let randomAccessIndicator = currentPesPacket.randomAccessIndicator; + if (randomAccessIndicator === 0 && !this.elementaryStream.canBeTrustedWithKeyPackets) { + if (this.elementaryStream.info.type === "audio") randomAccessIndicator = 1; + else if (this.elementaryStream.info.decoderConfig) { + const isKey = determineVideoPacketType(this.elementaryStream.info.codec, this.elementaryStream.info.decoderConfig, data) === "key"; + randomAccessIndicator = Number(isKey); + } + } + this.suppliedPacket = { + pts, + data, + sequenceNumber, + sectionStartPos, + randomAccessIndicator + }; + this.pesPackets.splice(0, this.currentPesPacketIndex); + this.currentPesPacketIndex = 0; + } +}; +/** +* A buffer that simulates decoder frame reordering to compute packet durations. Packets arrive in decode order but +* durations are based on presentation order. +*/ +var PacketBuffer = class { + constructor(backing, context) { + this.decodeOrderPackets = []; + this.reorderBuffer = []; + this.presentationOrderPackets = []; + this.reachedEnd = false; + this.lastDuration = 0; + this.backing = backing; + this.context = context; + this.reorderSize = backing.getReorderSize(); + assert(this.reorderSize >= 0); + } + async readNext() { + if (this.decodeOrderPackets.length === 0) { + if (!await this.readNextPacket()) return null; + } + await this.ensureCurrentPacketHasNext(); + const packet = this.decodeOrderPackets[0]; + const presentationIndex = this.presentationOrderPackets.indexOf(packet); + assert(presentationIndex !== -1); + let duration; + if (presentationIndex === this.presentationOrderPackets.length - 1) duration = this.lastDuration; + else { + duration = this.presentationOrderPackets[presentationIndex + 1].pts - packet.pts; + this.lastDuration = duration; + } + this.decodeOrderPackets.shift(); + while (this.presentationOrderPackets.length > 0) { + const first = this.presentationOrderPackets[0]; + if (this.decodeOrderPackets.includes(first)) break; + this.presentationOrderPackets.shift(); + } + return { + packet, + duration + }; + } + async readNextPacket() { + if (this.reachedEnd) return false; + let suppliedPacket; + if (this.context.suppliedPacket) suppliedPacket = this.context.suppliedPacket; + else { + await this.context.markNextPacket(); + suppliedPacket = this.context.suppliedPacket; + } + this.context.suppliedPacket = null; + if (!suppliedPacket) { + this.reachedEnd = true; + this.flushReorderBuffer(); + return false; + } + this.decodeOrderPackets.push(suppliedPacket); + this.processPacketThroughReorderBuffer(suppliedPacket); + return true; + } + async ensureCurrentPacketHasNext() { + const current = this.decodeOrderPackets[0]; + assert(current); + while (true) { + const presentationIndex = this.presentationOrderPackets.indexOf(current); + if (presentationIndex !== -1 && presentationIndex <= this.presentationOrderPackets.length - 2) break; + if (!await this.readNextPacket()) break; + } + } + processPacketThroughReorderBuffer(packet) { + this.reorderBuffer.push(packet); + if (this.reorderBuffer.length > this.reorderSize) { + let minIndex = 0; + for (let i = 1; i < this.reorderBuffer.length; i++) if (this.reorderBuffer[i].pts < this.reorderBuffer[minIndex].pts) minIndex = i; + const packet = this.reorderBuffer[minIndex]; + this.presentationOrderPackets.push(packet); + this.reorderBuffer.splice(minIndex, 1); + } + } + flushReorderBuffer() { + this.reorderBuffer.sort((a, b) => a.pts - b.pts); + this.presentationOrderPackets.push(...this.reorderBuffer); + this.reorderBuffer.length = 0; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/hls/hls-misc.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var HLS_MIME_TYPE = "application/vnd.apple.mpegurl"; +var TAG_STREAM_INF = "#EXT-X-STREAM-INF:"; +var TAG_I_FRAME_STREAM_INF = "#EXT-X-I-FRAME-STREAM-INF:"; +var TAG_MEDIA = "#EXT-X-MEDIA:"; +var TAG_DEFINE = "#EXT-X-DEFINE:"; +var TAG_EXTINF = "#EXTINF:"; +var TAG_MAP = "#EXT-X-MAP:"; +var TAG_KEY = "#EXT-X-KEY:"; +var TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE:"; +var TAG_BYTERANGE = "#EXT-X-BYTERANGE:"; +var TAG_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME:"; +var TAG_TARGETDURATION = "#EXT-X-TARGETDURATION:"; +var canIgnoreLine = (line) => line.length === 0 || line.startsWith("#") && !line.startsWith("#EXT"); +var AttributeList = class { + constructor(str, variables) { + this._attributes = {}; + let key = ""; + let value = ""; + let inValue = false; + let inQuotes = false; + let quotedValue = false; + const flushAttribute = () => { + if (!key) return; + const attributeName = key.trim().toLowerCase(); + if (Object.prototype.hasOwnProperty.call(this._attributes, attributeName)) Logging._warn(`Duplicate AttributeName "${attributeName}"; using last value`); + const shouldSubstitute = quotedValue || value.startsWith("0x") || value.startsWith("0X"); + this._attributes[attributeName] = shouldSubstitute && variables ? variables.substitute(value) : value; + key = ""; + value = ""; + inValue = false; + quotedValue = false; + }; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + if (char === "\"") { + if (inValue && !inQuotes && value.length === 0) quotedValue = true; + inQuotes = !inQuotes; + } else if (char === "=" && !inValue && !inQuotes) inValue = true; + else if (char === "," && !inQuotes) flushAttribute(); + else if (inValue) value += char; + else key += char; + } + if (inQuotes) throw new Error("Invalid M3U8 file; unterminated quoted-string AttributeValue."); + flushAttribute(); + } + get(name) { + return this._attributes[name.toLowerCase()] ?? null; + } + getAsNumber(name) { + const value = this.get(name); + if (value === null) return null; + const num = Number(value); + return Number.isFinite(num) ? num : null; + } + merge(other) { + Object.assign(this._attributes, other._attributes); + } +}; +var VARIABLE_NAME_PATTERN = /^[A-Za-z0-9_-]+$/; +var VARIABLE_REFERENCE_PATTERN = /\{\$([A-Za-z0-9_-]+)\}/g; +var HlsPlaylistVariables = class { + constructor(playlistPath, importedVariables) { + this.playlistPath = playlistPath; + this.importedVariables = importedVariables; + this.variables = /* @__PURE__ */ new Map(); + } + define(str) { + const attributes = new AttributeList(str, this); + const name = attributes.get("name"); + const importedName = attributes.get("import"); + const queryParameterName = attributes.get("queryparam"); + if ([ + name, + importedName, + queryParameterName + ].filter((value) => value !== null).length !== 1) throw new Error("Invalid #EXT-X-DEFINE tag; exactly one of NAME, IMPORT, or QUERYPARAM must be present."); + if (name !== null) { + const value = attributes.get("value"); + if (value === null) throw new Error("Invalid #EXT-X-DEFINE tag; NAME requires VALUE."); + this.set(name, value); + } else if (importedName !== null) { + const value = this.importedVariables?.get(importedName); + if (value === void 0) throw new Error(`Invalid #EXT-X-DEFINE tag; cannot import undefined variable "${importedName}".`); + this.set(importedName, value); + } else if (queryParameterName !== null) { + const value = getQueryParameter(this.playlistPath, queryParameterName); + if (value === null) throw new Error(`Invalid #EXT-X-DEFINE tag; query parameter "${queryParameterName}" is missing.`); + this.set(queryParameterName, value); + } + } + substitute(value) { + return value.replaceAll(VARIABLE_REFERENCE_PATTERN, (_reference, name) => { + const replacement = this.variables.get(name); + if (replacement === void 0) throw new Error(`Invalid M3U8 file; variable "${name}" is referenced before being defined.`); + return replacement; + }); + } + get(name) { + return this.variables.get(name); + } + set(name, value) { + if (!VARIABLE_NAME_PATTERN.test(name)) throw new Error(`Invalid #EXT-X-DEFINE tag; invalid variable name "${name}".`); + if (this.variables.has(name)) throw new Error(`Invalid #EXT-X-DEFINE tag; variable "${name}" is already defined.`); + this.variables.set(name, value); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/segmented-input.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var SegmentedInput = class { + constructor(input, path, trackDeclarations) { + this.nextInputCacheAge = 0; + this.inputCache = []; + this.trackBackingsPromise = null; + this.firstSegment = null; + this.firstSegmentFirstTimestamps = /* @__PURE__ */ new WeakMap(); + this.firstTimestampCache = /* @__PURE__ */ new WeakMap(); + this.input = input; + this.path = path; + this.trackDeclarations = trackDeclarations; + } + async getDurationFromMetadata(options) { + const lastSegment = await this.getSegmentAt(Infinity, { skipLiveWait: options.skipLiveWait }); + if (!lastSegment) return null; + return lastSegment.timestamp + lastSegment.duration; + } + async getUnixTimeForTimestamp(timestamp) { + let segment = await this.getSegmentAt(timestamp, {}); + segment ??= await this.getFirstSegment({}); + if (!segment || segment.unixEpochTimestamp === null) return null; + const elapsed = timestamp - segment.timestamp; + return segment.unixEpochTimestamp + elapsed; + } + async getTrackBackings() { + return this.trackBackingsPromise ??= (async () => { + const backings = []; + if (this.trackDeclarations) { + for (const decl of this.trackDeclarations) if (decl.type === "video") { + const number = arrayCount(backings, (x) => x.getType() === "video") + 1; + backings.push(new SegmentedInputInputVideoTrackBacking(this, decl, number)); + } else if (decl.type === "audio") { + const number = arrayCount(backings, (x) => x.getType() === "audio") + 1; + backings.push(new SegmentedInputInputAudioTrackBacking(this, decl, number)); + } + } else { + this.firstSegment = await this.getFirstSegment({}); + if (!this.firstSegment) return []; + const inputTracks = await this.getInputForSegment(this.firstSegment).getTracks(); + for (const track of inputTracks) if (track.type === "video") { + const number = arrayCount(backings, (x) => x.getType() === "video") + 1; + backings.push(new SegmentedInputInputVideoTrackBacking(this, { + id: backings.length + 1, + type: "video" + }, number)); + } else if (track.type === "audio") { + const number = arrayCount(backings, (x) => x.getType() === "audio") + 1; + backings.push(new SegmentedInputInputAudioTrackBacking(this, { + id: backings.length + 1, + type: "audio" + }, number)); + } + } + return backings; + })(); + } + async getFirstTimestampForInput(input) { + const existing = this.firstTimestampCache.get(input); + if (existing !== void 0) return existing; + const firstTimestamp = await input.getFirstTimestamp(); + this.firstTimestampCache.set(input, firstTimestamp); + return firstTimestamp; + } + async getMediaOffset(segment, input) { + const firstSegment = segment.firstSegment ?? segment; + let firstSegmentFirstTimestamp; + if (this.firstSegmentFirstTimestamps.has(firstSegment)) firstSegmentFirstTimestamp = this.firstSegmentFirstTimestamps.get(firstSegment); + else { + const firstInput = this.getInputForSegment(firstSegment); + firstSegmentFirstTimestamp = await this.getFirstTimestampForInput(firstInput); + this.firstSegmentFirstTimestamps.set(firstSegment, firstSegmentFirstTimestamp); + } + if (firstSegment === segment) return firstSegment.timestamp - firstSegmentFirstTimestamp; + const segmentFirstTimestamp = await this.getFirstTimestampForInput(input); + const segmentElapsed = segment.timestamp - firstSegment.timestamp; + const difference = segmentFirstTimestamp - firstSegmentFirstTimestamp - segmentElapsed; + if (Math.abs(difference) <= Math.min(.25, segmentElapsed)) return firstSegment.timestamp - firstSegmentFirstTimestamp; + else return segment.timestamp - segmentFirstTimestamp; + } + dispose() { + for (const entry of this.inputCache) entry.input.dispose(); + this.inputCache.length = 0; + } +}; +var SegmentedInputInputTrackBacking = class { + constructor(segmentedInput, decl, number) { + this.packetInfos = /* @__PURE__ */ new WeakMap(); + this.hydrationPromise = null; + this.firstInputTrack = null; + this.firstSegment = null; + this.segmentedInput = segmentedInput; + this.decl = decl; + this.number = number; + } + hydrate() { + return this.hydrationPromise ??= (async () => { + this.segmentedInput.firstSegment ??= await this.segmentedInput.getFirstSegment({}); + if (!this.segmentedInput.firstSegment) throw new Error("Missing first segment, can't retrieve track."); + let currentSegment = this.segmentedInput.firstSegment; + let track = null; + while (currentSegment) { + track = (await this.segmentedInput.getInputForSegment(currentSegment).getTracks()).find((x) => x.type === this.decl.type && x.number === this.number) ?? null; + if (track) break; + currentSegment = await this.segmentedInput.getNextSegment(currentSegment, {}); + } + if (!track) throw new Error("No matching track found in underlying media data."); + this.firstInputTrack = track; + this.firstSegment = currentSegment; + })(); + } + getId() { + return this.decl.id; + } + getType() { + return this.decl.type; + } + getNumber() { + return this.number; + } + /** If the backing track is already present, delegate synchronously; otherwise, hydrate first. */ + delegate(fn) { + if (this.firstInputTrack) return fn(); + return this.hydrate().then(fn); + } + async getDecoderConfig() { + return this.delegate(() => this.firstInputTrack._backing.getDecoderConfig()); + } + getHasOnlyKeyPackets() { + return this.delegate(() => this.firstInputTrack._backing.getHasOnlyKeyPackets?.() ?? null); + } + getPairingMask() { + return 1n; + } + getCodec() { + return this.delegate(() => this.firstInputTrack._backing.getCodec()); + } + getInternalCodecId() { + return this.delegate(() => this.firstInputTrack._backing.getInternalCodecId()); + } + getDisposition() { + return this.delegate(() => this.firstInputTrack._backing.getDisposition()); + } + getLanguageCode() { + return this.delegate(() => this.firstInputTrack._backing.getLanguageCode()); + } + getName() { + return this.delegate(() => this.firstInputTrack._backing.getName()); + } + getTimeResolution() { + return this.delegate(() => this.firstInputTrack._backing.getTimeResolution()); + } + async isRelativeToUnixEpoch() { + await this.hydrate(); + assert(this.segmentedInput.firstSegment); + return this.segmentedInput.firstSegment.unixEpochTimestamp === this.segmentedInput.firstSegment.timestamp; + } + getUnixTimeForTimestamp(timestamp) { + return this.segmentedInput.getUnixTimeForTimestamp(timestamp); + } + getBitrate() { + return this.delegate(() => this.firstInputTrack._backing.getBitrate()); + } + getAverageBitrate() { + return this.delegate(() => this.firstInputTrack._backing.getAverageBitrate()); + } + getDurationFromMetadata(options) { + return this.segmentedInput.getDurationFromMetadata(options); + } + getLiveRefreshInterval() { + return this.segmentedInput.getLiveRefreshInterval(); + } + async createAdjustedPacket(packet, segment, track) { + assert(packet.sequenceNumber >= 0); + assert(this.segmentedInput.firstSegment); + const mediaOffset = await this.segmentedInput.getMediaOffset(segment, track.input); + const segmentTimestampRelativeToFirst = segment.timestamp - this.segmentedInput.firstSegment.timestamp; + const modified = packet.clone({ + timestamp: roundToDivisor(packet.timestamp + mediaOffset, await track.getTimeResolution()), + sequenceNumber: Math.floor(1e8 * segmentTimestampRelativeToFirst) + packet.sequenceNumber + }); + this.packetInfos.set(modified, { + segment, + track, + sourcePacket: packet + }); + return modified; + } + async getFirstPacket(options) { + await this.hydrate(); + assert(this.firstInputTrack); + assert(this.firstSegment); + let currentTrack = this.firstInputTrack; + let currentSegment = this.firstSegment; + while (true) { + if (currentTrack) { + const packet = await currentTrack._backing.getFirstPacket(options); + if (packet) return this.createAdjustedPacket(packet, currentSegment, currentTrack); + } + currentSegment = await this.segmentedInput.getNextSegment(currentSegment, { skipLiveWait: options.skipLiveWait }); + if (!currentSegment) break; + currentTrack = (await this.segmentedInput.getInputForSegment(currentSegment).getTracks()).find((t) => t.type === this.firstInputTrack.type && t.number === this.firstInputTrack.number) ?? null; + } + return null; + } + getNextPacket(packet, options) { + return this._getNextInternal(packet, options, false); + } + getNextKeyPacket(packet, options) { + return this._getNextInternal(packet, options, true); + } + async _getNextInternal(packet, options, keyframesOnly) { + const info = this.packetInfos.get(packet); + if (!info) throw new Error("Packet was not created from this track."); + const nextPacket = keyframesOnly ? await info.track._backing.getNextKeyPacket(info.sourcePacket, options) : await info.track._backing.getNextPacket(info.sourcePacket, options); + if (nextPacket) return this.createAdjustedPacket(nextPacket, info.segment, info.track); + let currentSegment = info.segment; + while (true) { + const nextSegment = await this.segmentedInput.getNextSegment(currentSegment, { skipLiveWait: options.skipLiveWait }); + if (!nextSegment) return null; + const nextTrack = (await this.segmentedInput.getInputForSegment(nextSegment).getTracks()).find((t) => t.type === info.track.type && t.number === info.track.number); + if (!nextTrack) { + currentSegment = nextSegment; + continue; + } + const firstPacket = await nextTrack._backing.getFirstPacket(options); + if (!firstPacket) return null; + return this.createAdjustedPacket(firstPacket, nextSegment, nextTrack); + } + } + getPacket(timestamp, options) { + return this._getPacketInternal(timestamp, options, false); + } + getKeyPacket(timestamp, options) { + return this._getPacketInternal(timestamp, options, true); + } + async _getPacketInternal(timestamp, options, keyframesOnly) { + let currentSegment = await this.segmentedInput.getSegmentAt(timestamp, { skipLiveWait: options.skipLiveWait }); + if (!currentSegment) return null; + await this.hydrate(); + while (currentSegment) { + const input = this.segmentedInput.getInputForSegment(currentSegment); + const track = (await input.getTracks()).find((t) => t.type === this.firstInputTrack.type && t.number === this.firstInputTrack.number); + if (!track) { + currentSegment = await this.segmentedInput.getPreviousSegment(currentSegment, { skipLiveWait: options.skipLiveWait }); + continue; + } + const offsetTimestamp = timestamp - await this.segmentedInput.getMediaOffset(currentSegment, input); + const packet = keyframesOnly ? await track._backing.getKeyPacket(offsetTimestamp, options) : await track._backing.getPacket(offsetTimestamp, options); + if (!packet) { + currentSegment = await this.segmentedInput.getPreviousSegment(currentSegment, { skipLiveWait: options.skipLiveWait }); + continue; + } + return this.createAdjustedPacket(packet, currentSegment, track); + } + return null; + } +}; +var SegmentedInputInputVideoTrackBacking = class extends SegmentedInputInputTrackBacking { + getType() { + return "video"; + } + getCodec() { + return this.delegate(() => this.firstInputTrack._backing.getCodec()); + } + getCodedWidth() { + return this.delegate(() => this.firstInputTrack._backing.getCodedWidth()); + } + getCodedHeight() { + return this.delegate(() => this.firstInputTrack._backing.getCodedHeight()); + } + getSquarePixelWidth() { + return this.delegate(() => this.firstInputTrack._backing.getSquarePixelWidth()); + } + getSquarePixelHeight() { + return this.delegate(() => this.firstInputTrack._backing.getSquarePixelHeight()); + } + getTransformationMatrix() { + return this.delegate(() => this.firstInputTrack._backing.getTransformationMatrix()); + } + async getColorSpace() { + return this.delegate(() => this.firstInputTrack._backing.getColorSpace()); + } + async canBeTransparent() { + return this.delegate(() => this.firstInputTrack._backing.canBeTransparent()); + } + async getDecoderConfig() { + return this.delegate(() => this.firstInputTrack._backing.getDecoderConfig()); + } +}; +var SegmentedInputInputAudioTrackBacking = class extends SegmentedInputInputTrackBacking { + getType() { + return "audio"; + } + getCodec() { + return this.delegate(() => this.firstInputTrack._backing.getCodec()); + } + getNumberOfChannels() { + return this.delegate(() => this.firstInputTrack._backing.getNumberOfChannels()); + } + getSampleRate() { + return this.delegate(() => this.firstInputTrack._backing.getSampleRate()); + } + async getDecoderConfig() { + return this.delegate(() => this.firstInputTrack._backing.getDecoderConfig()); + } +}; +//#endregion +//#region __vite-browser-external +var require___vite_browser_external = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = {}; +})); +//#endregion +//#region node_modules/mediabunny/dist/modules/src/source.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1); +polyfillSymbolDispose(); +var node$1 = typeof import___vite_browser_external !== "undefined" ? import___vite_browser_external : void 0; +var DEFAULT_MAX_READ_POSITION = Infinity; +var sourceFinalizationRegistry = null; +if (typeof FinalizationRegistry !== "undefined") sourceFinalizationRegistry = new FinalizationRegistry((cleanup) => { + cleanup(); +}); +/** +* The source base class, representing a resource from which bytes can be read. +* @group Input sources +* @public +*/ +var Source = class extends EventEmitter { + constructor() { + super(); + /** @internal */ + this._disposed = false; + /** @internal */ + this._refCount = 0; + /** + * Used internally to mark if a source stems from an HLS reading operation. Used to suppress certain warnings. + * @internal + */ + this._usedForHls = false; + /** + * FinalizationRegistry for rogue refs to this source that didn't get freed. It lives on the Source itself so that + * in case the Source transitively points back to itself and forms a cycle (for example through a custom + * CustomSource callback) that we're not leaking memory. + * @internal + */ + this._refFinalizationRegistry = null; + /** @internal */ + this._sizePromise = null; + /** + * Called each time data is retrieved from the source. Will be called with the retrieved range (end exclusive). + * + * @deprecated Use `source.on('read', ({ start, end }) => ...)` instead. + */ + this.onread = null; + if (typeof FinalizationRegistry !== "undefined") this._refFinalizationRegistry = new FinalizationRegistry((source) => { + source._decrementRefCount(); + }); + } + /** + * Resolves with the total size of the file in bytes. This function is memoized, meaning only the first call + * will retrieve the size. + * + * Returns null if the source is unsized. + */ + async getSizeOrNull() { + if (this._disposed) throw new InputDisposedError(); + return this._sizePromise ??= (async () => { + let size = this._getFileSize(); + if (size !== void 0) return size; + await this._read(0, 1, 0, DEFAULT_MAX_READ_POSITION); + size = this._getFileSize(); + assert(size !== void 0); + return size; + })(); + } + /** + * Resolves with the total size of the file in bytes. This function is memoized, meaning only the first call + * will retrieve the size. + * + * Throws an error if the source is unsized. + */ + async getSize() { + if (this._disposed) throw new InputDisposedError(); + const result = await this.getSizeOrNull(); + if (result === null) throw new Error("Cannot determine the size of an unsized source."); + return result; + } + /** + * Returns a new {@link RangedSource} that maps data onto this source using the given offset and length. If a length + * is not provided, the ranged source spans until the end of this source's data. + * + * Useful for reading files that are embedded within larger files. + */ + slice(offset, length) { + if (!Number.isInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative integer."); + if (length !== void 0 && (!Number.isInteger(length) || length < 0)) throw new TypeError("length, when provided, must be a non-negative integer."); + return new RangedSource(this, offset, length); + } + /** @internal */ + _dispatchRead(start, end) { + this.onread?.(start, end); + this._emit("read", { + start, + end + }); + } + /** + * Creates a new `SourceRef` pointing to this source. You are expected to call `.free()` on said `SourceRef` when + * you're done with it. + */ + ref() { + return new SourceRef(this); + } + /** @internal */ + _incrementRefCount() { + this._refCount++; + } + /** @internal */ + _decrementRefCount() { + this._refCount--; + if (this._refCount === 0) { + this._dispose(); + this._disposed = true; + } + } +}; +/** +* A reference to a {@link Source}, used to manage a source's lifecycle. Creating a `SourceRef` via {@link Source.ref} +* increases that source's internal reference count. As long as a source has a non-zero reference count, it is assumed +* to still be in use. Once all references are freed via {@link SourceRef.free}, the source gets disposed. +* +* @group Input sources +* @public +*/ +var SourceRef = class { + /** @internal */ + constructor(source) { + /** @internal */ + this._freed = false; + if (source._disposed) throw new Error("Cannot ref a disposed source."); + source._incrementRefCount(); + source._refFinalizationRegistry?.register(this, source, this); + this._source = source; + } + /** The {@link Source} this ref references. Accessing this field throws an error after having freed the ref. */ + get source() { + if (!this._source) throw new Error("Can't get source; ref has already been freed."); + return this._source; + } + /** Whether or not this reference has been freed via {@link SourceRef.free}. */ + get freed() { + return this._freed; + } + /** + * Frees the ref, decrementing the source's internal reference count. If the source's internal reference count + * reaches zero, it gets disposed. To catch bugs, this method throws if the ref is already freed. + */ + free() { + if (this._freed) throw new Error("Illegal operation: double free on SourceRef."); + const source = this.source; + assert(source._refCount > 0); + source._decrementRefCount(); + source._refFinalizationRegistry?.unregister(this); + this._freed = true; + this._source = null; + } + /** + * Calls {@link SourceRef.free}. + */ + [Symbol.dispose]() { + if (!this.freed) this.free(); + } +}; +/** +* A source which can create new sources from file paths. Required for multi-file inputs such as HLS playlists. +* @public +* @group Input sources +*/ +var PathedSource = class extends Source { + constructor(rootPath, requestHandler) { + if (typeof rootPath !== "string") throw new TypeError("rootPath must be a string."); + if (typeof requestHandler !== "function") throw new TypeError("requestHandler must be a function."); + super(); + this.rootPath = rootPath; + this.requestHandler = requestHandler; + } + /** @internal */ + _resolveRequest(request) { + const result = this.requestHandler(request); + const handle = (result) => { + if (!(result instanceof Source || result instanceof SourceRef)) throw new TypeError("requestHandler must return or resolve to a Source or SourceRef."); + const ref = result instanceof Source ? result.ref() : result; + ref.source._usedForHls ||= this._usedForHls; + return ref; + }; + if (isThenable(result)) return result.then(handle); + else return handle(result); + } +}; +var sourceRequestsAreEqual = (a, b) => { + return a.path === b.path; +}; +/** +* A custom multi-file source where each file is uniquely identified by a {@link FilePath} and can be resolved to +* an arbitrary {@link Source}. +* +* @public +* @group Input sources +*/ +var CustomPathedSource = class extends PathedSource { + constructor() { + super(...arguments); + /** @internal */ + this._root = null; + /** @internal */ + this._rootRequest = null; + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + if (!this._root) { + if (!this._rootRequest) { + const result = this._resolveRequest({ + path: this.rootPath, + isRoot: true + }); + const handle = (result) => { + const ref = result instanceof Source ? result.ref() : result; + this._root = ref; + this._rootRequest = null; + return ref; + }; + if (isThenable(result)) this._rootRequest = result.then(handle); + else { + handle(result); + assert(this._root); + } + } + if (this._rootRequest) return this._rootRequest.then((ref) => ref.source._read(start, end, minReadPosition, maxReadPosition)); + } + return this._root.source._read(start, end, minReadPosition, maxReadPosition); + } + /** @internal */ + _getFileSize() { + if (this._root) return this._root.source._getFileSize(); + } + /** @internal */ + _dispose() { + if (this._root) this._root.free(); + else if (this._rootRequest) this._rootRequest.then((ref) => ref.free()); + } +}; +/** +* A source backed by an ArrayBuffer or ArrayBufferView, with the entire file held in memory. +* @group Input sources +* @public +*/ +var BufferSource = class extends Source { + /** + * Creates a new {@link BufferSource} backed by the specified `ArrayBuffer`, `SharedArrayBuffer`, + * or `ArrayBufferView`. + */ + constructor(buffer) { + if (!(buffer instanceof ArrayBuffer) && !(typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer) && !ArrayBuffer.isView(buffer)) throw new TypeError("buffer must be an ArrayBuffer, SharedArrayBuffer, or ArrayBufferView."); + super(); + /** @internal */ + this._onreadCalled = false; + this._bytes = toUint8Array(buffer); + this._view = toDataView(buffer); + } + /** @internal */ + _getFileSize() { + return this._bytes.byteLength; + } + /** @internal */ + _read() { + if (!this._onreadCalled) { + this._dispatchRead(0, this._bytes.byteLength); + this._onreadCalled = true; + } + return { + bytes: this._bytes, + view: this._view, + offset: 0 + }; + } + /** @internal */ + _dispose() {} +}; +var blobReaderRegistry = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((reader) => { + reader.cancel().catch(() => {}); +}) : null; +/** +* A source backed by a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). Since a +* [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is also a `Blob`, this is the source to use when +* reading files off the disk. +* @group Input sources +* @public +*/ +var BlobSource = class extends Source { + /** + * Creates a new {@link BlobSource} backed by the specified + * [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). + */ + constructor(blob, options = {}) { + if (!(blob instanceof Blob)) throw new TypeError("blob must be a Blob."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number."); + if (options.useStreamReader !== void 0 && typeof options.useStreamReader !== "boolean") throw new TypeError("options.useStreamReader, when provided, must be a boolean."); + if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") throw new TypeError("options.handleUnhandledError, when provided, must be a function."); + super(); + /** @internal */ + this._readers = /* @__PURE__ */ new WeakMap(); + this._blob = blob; + this._options = options; + this._orchestrator = new ReadOrchestrator({ + maxCacheSize: options.maxCacheSize ?? 8 * 2 ** 20, + maxWorkerCount: 4, + runWorker: this._runWorker.bind(this), + onIdleWorkerRemoved: (worker) => { + const reader = this._readers.get(worker); + if (reader) { + this._readers.delete(worker); + blobReaderRegistry?.unregister(worker); + reader.cancel().catch(() => {}); + } + }, + prefetchProfile: PREFETCH_PROFILES.fileSystem, + handleUnhandledError: options.handleUnhandledError + }); + this._orchestrator.fileSize = blob.size; + } + /** @internal */ + _getFileSize() { + return this._orchestrator.fileSize; + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + return this._orchestrator.read(start, end, minReadPosition, maxReadPosition); + } + /** @internal */ + async _runWorker(worker) { + assert(worker.strictTarget); + let reader = this._readers.get(worker); + if (reader === void 0) { + if ("stream" in this._blob && !isWebKit() && this._options.useStreamReader !== false) { + reader = this._blob.slice(worker.currentPos).stream().getReader(); + blobReaderRegistry?.register(worker, reader, worker); + } else reader = null; + this._readers.set(worker, reader); + } + while (worker.currentPos < worker.targetPos && !worker.aborted) if (reader) { + const { done, value } = await reader.read(); + if (done) { + this._orchestrator.onWorkerFinished(worker); + throw new Error("Blob reader stopped unexpectedly before all requested data was read."); + } + if (worker.aborted) break; + this._dispatchRead(worker.currentPos, worker.currentPos + value.length); + this._orchestrator.supplyWorkerData(worker, value); + } else { + const data = await this._blob.slice(worker.currentPos, worker.targetPos).arrayBuffer(); + if (worker.aborted) break; + this._dispatchRead(worker.currentPos, worker.currentPos + data.byteLength); + this._orchestrator.supplyWorkerData(worker, new Uint8Array(data)); + } + this._orchestrator.signalWorkerStoppedRunning(worker); + if (worker.aborted) await reader?.cancel().catch(() => {}); + } + /** @internal */ + _dispose() { + this._orchestrator.dispose(); + } +}; +var URL_SOURCE_MIN_LOAD_AMOUNT = .5 * 2 ** 20; +var DEFAULT_RETRY_DELAY = ((previousAttempts, error, src) => { + if (error instanceof Error && (error.message.includes("Failed to fetch") || error.message.includes("Load failed") || error.message.includes("NetworkError when attempting to fetch resource")) && typeof window !== "undefined") { + let originOfSrc = null; + try { + if (typeof window !== "undefined" && typeof window.location !== "undefined") originOfSrc = new URL(src instanceof Request ? src.url : src, window.location.href).origin; + } catch {} + if ((typeof navigator !== "undefined" && typeof navigator.onLine === "boolean" ? navigator.onLine : true) && originOfSrc !== null && originOfSrc !== window.location.origin) { + Logging._warn("Request will not be retried because a CORS error was suspected due to different origins. You can modify this behavior by providing your own function for the 'getRetryDelay' option."); + return null; + } + } + return Math.min(2 ** (previousAttempts - 2), 16); +}); +var warnedOrigins = /* @__PURE__ */ new Set(); +/** +* A source backed by a URL. This is useful for reading data from the network. Requests will be made using an optimized +* reading and prefetching pattern to minimize request count and latency. Works best with servers that support HTTP +* range requests; otherwise, resources must be streamed and read sequentially. +* @group Input sources +* @public +*/ +var UrlSource = class UrlSource extends PathedSource { + /** + * Creates a new {@link UrlSource} backed by the resource at the specified URL. + * + * When passing a `Request` instance, note that its `signal` will be overridden by Mediabunny; if you want to cancel + * ongoing requests, use {@link Input.dispose}. + */ + constructor(url, options = {}) { + if (typeof url !== "string" && !(url instanceof URL) && !(typeof Request !== "undefined" && url instanceof Request)) throw new TypeError("url must be a string, URL or Request."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.requestInit !== void 0 && (!options.requestInit || typeof options.requestInit !== "object")) throw new TypeError("options.requestInit, when provided, must be an object."); + if (options.getRetryDelay !== void 0 && typeof options.getRetryDelay !== "function") throw new TypeError("options.getRetryDelay, when provided, must be a function."); + if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number."); + if (options.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) throw new TypeError("options.parallelism, when provided, must be a positive number."); + if (options.fetchFn !== void 0 && typeof options.fetchFn !== "function") throw new TypeError("options.fetchFn, when provided, must be a function."); + if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") throw new TypeError("options.handleUnhandledError, when provided, must be a function."); + const urlString = url instanceof Request ? url.url : url instanceof URL ? url.href : url; + super(urlString, (request) => new UrlSource(request.path, this._options)); + /** @internal */ + this._offset = 0; + /** @internal */ + this._length = null; + /** + * Note that this value being true does NOT mean the file size can't change anymore; it just signals that we have at + * least checked if we know the file size or not. + * @internal + */ + this._fileSizeDetermined = false; + /** + * When the server doesn't support range requests, we abandon the orchestrator and instead defer to an internal + * ReadableStreamSource wrapping the response body, which pulls new data only when reads demand it. + * @internal + */ + this._sequentialBacking = null; + /** @internal */ + this._abortControllers = /* @__PURE__ */ new Map(); + /** @internal */ + this._sequentialAbortController = null; + this._url = url; + this._options = options; + this._getRetryDelay = options.getRetryDelay ?? DEFAULT_RETRY_DELAY; + this._requestInit = { ...options.requestInit }; + let rangeHeaderValue = null; + if (options.requestInit?.headers) { + const headers = { ...normalizeHeaders(options.requestInit.headers) }; + const rangeKey = Object.keys(headers).find((key) => key.toLowerCase() === "range"); + if (rangeKey !== void 0) { + rangeHeaderValue = headers[rangeKey]; + delete headers[rangeKey]; + this._requestInit.headers = headers; + } + } + if (url instanceof Request) { + const requestRange = url.headers.get("Range"); + if (requestRange !== null) { + rangeHeaderValue ??= requestRange; + const strippedRequest = new Request(url); + strippedRequest.headers.delete("Range"); + this._url = strippedRequest; + } + } + if (rangeHeaderValue !== null) { + const parsed = parseByteRangeHeader(rangeHeaderValue); + if (parsed) { + this._offset = parsed.offset; + this._length = parsed.length; + } + } + const DEFAULT_PARALLELISM = 2; + this._orchestrator = new ReadOrchestrator({ + maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20, + maxWorkerCount: options.parallelism ?? DEFAULT_PARALLELISM, + runWorker: this._runWorker.bind(this), + onIdleWorkerRemoved: (worker) => this._abortControllers.delete(worker), + prefetchProfile: PREFETCH_PROFILES.network, + handleUnhandledError: options.handleUnhandledError + }); + } + /** @internal */ + _getFileSize() { + if (!this._fileSizeDetermined) return this._length !== null ? this._length : void 0; + const baseSize = this._sequentialBacking ? this._sequentialBacking._endIndex : this._orchestrator.fileSize; + if (baseSize === null) return this._length !== null ? this._length : null; + return clamp(baseSize - this._offset, 0, this._length ?? Infinity); + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + if (this._length !== null && end > this._length) return null; + const offset = this._offset; + const result = this._sequentialBacking ? this._sequentialBacking._read(offset + start, offset + end) : this._orchestrator.read(offset + start, offset + end, Math.max(offset + minReadPosition, offset), offset + Math.min(maxReadPosition, this._length ?? Infinity)); + const processResult = (result) => { + if (!result) return null; + result.offset -= this._offset; + return result; + }; + if (isThenable(result)) return result.then(processResult); + else return processResult(result); + } + /** @internal */ + async _runWorker(worker) { + while (true) { + if (worker.aborted) { + this._orchestrator.signalWorkerStoppedRunning(worker); + return; + } + const abortController = new AbortController(); + this._abortControllers.set(worker, abortController); + const response = await retriedFetch(this._options.fetchFn ?? fetch, this._url, mergeRequestInit(this._requestInit, { + headers: { Range: `bytes=${worker.currentPos}-` }, + signal: abortController.signal + }), this._getRetryDelay, () => abortController.signal.aborted); + if (!response.ok) throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`); + if (response.redirected) this.rootPath = response.url; + outer: if (this._orchestrator.fileSize === null && !response.headers.has("Content-Encoding") && (response.status === 206 || response.type === "basic")) { + const contentRange = response.headers.get("Content-Range"); + if (contentRange) { + const match = /\/(\d+)/.exec(contentRange); + if (match) { + this._orchestrator.supplyFileSize(Number(match[1])); + break outer; + } + } + const contentLength = response.headers.get("Content-Length"); + if (contentLength) { + const basePos = response.status === 206 ? worker.currentPos : 0; + this._orchestrator.supplyFileSize(basePos + Number(contentLength)); + } + } + this._fileSizeDetermined = true; + if (!response.body) throw new Error("Missing HTTP response body stream. The used fetch function must provide the response body as a ReadableStream."); + if (response.status !== 206) { + if (this._sequentialBacking) { + response.body.cancel().catch(() => {}); + return; + } + if (!this._usedForHls) { + const url = new URL(this._url instanceof Request ? this._url.url : this._url, typeof window !== "undefined" ? window.location.href : void 0); + if (url.origin !== "null" && !(url.pathname.endsWith(".m3u8") || url.pathname.endsWith(".m3u"))) { + if (!warnedOrigins.has(url.origin)) { + Logging._warn(`HTTP server (origin ${url.origin}) did not respond to a range request with 206 Partial Content, meaning the resource will now be streamed sequentially, with old data being evicted from the cache. Reads into evicted regions will throw. To enable efficient media file streaming across a network, please make sure your server supports range requests. Alternatively, set maxCacheSize to Infinity in the UrlSource options to keep the entire resource in memory.`); + warnedOrigins.add(url.origin); + } + } + } + this._abortControllers.delete(worker); + this._transitionToSequentialMode(response.body, abortController); + return; + } + const reader = response.body.getReader(); + while (true) { + if (worker.currentPos >= worker.targetPos || worker.aborted) { + abortController.abort(); + this._orchestrator.signalWorkerStoppedRunning(worker); + return; + } + let readResult; + try { + readResult = await reader.read(); + } catch (error) { + if (abortController.signal.aborted) throw error; + const retryDelayInSeconds = this._getRetryDelay(1, error, this._url); + if (retryDelayInSeconds !== null) { + Logging._error("Error while reading response stream. Attempting to resume.", error); + await wait(1e3 * retryDelayInSeconds); + break; + } else throw error; + } + if (worker.aborted) continue; + const { done, value } = readResult; + if (done) { + if (worker.currentPos >= worker.targetPos) { + this._orchestrator.onWorkerFinished(worker); + return; + } + if (worker.strictTarget) break; + else { + this._orchestrator.onWorkerFinished(worker); + return; + } + } + this._dispatchRead(worker.currentPos, worker.currentPos + value.length); + this._orchestrator.supplyWorkerData(worker, value); + } + } + } + /** @internal */ + _transitionToSequentialMode(body, abortController) { + this._sequentialAbortController = abortController; + let currentReader = body.getReader(); + let streamPosition = 0; + let skipRemaining = 0; + const backing = new ReadableStreamSource(new ReadableStream({ + pull: async (controller) => { + while (true) { + let readResult; + try { + readResult = await currentReader.read(); + } catch (error) { + if (this._disposed) throw error; + const retryDelayInSeconds = this._getRetryDelay(1, error, this._url); + if (retryDelayInSeconds === null) throw error; + Logging._error("Error while reading response stream. Attempting to resume.", error); + await wait(1e3 * retryDelayInSeconds); + this._sequentialAbortController = new AbortController(); + if (this._disposed) this._sequentialAbortController.abort(); + const newResponse = await retriedFetch(this._options.fetchFn ?? fetch, this._url, mergeRequestInit(this._requestInit, { + headers: { Range: `bytes=${streamPosition}-` }, + signal: this._sequentialAbortController.signal + }), this._getRetryDelay, () => this._disposed); + if (!newResponse.ok) throw new Error(`Error fetching ${String(this._url)}: ${newResponse.status} ${newResponse.statusText}`); + if (!newResponse.body) throw new Error("Missing HTTP response body stream. The used fetch function must provide the response body as a ReadableStream."); + currentReader = newResponse.body.getReader(); + skipRemaining = newResponse.status === 206 ? 0 : streamPosition; + continue; + } + if (readResult.done) { + controller.close(); + return; + } + let chunk = readResult.value; + if (skipRemaining > 0) { + const skippedAmount = Math.min(skipRemaining, chunk.length); + skipRemaining -= skippedAmount; + chunk = chunk.subarray(skippedAmount); + } + if (chunk.length === 0) continue; + streamPosition += chunk.length; + controller.enqueue(chunk); + return; + } + }, + cancel: () => currentReader.cancel().catch(() => {}) + }), { + maxCacheSize: this._orchestrator.options.maxCacheSize, + handleUnhandledError: this._options.handleUnhandledError + }); + backing._endIndex = this._orchestrator.fileSize; + backing._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. Because the HTTP server did not honor the range request, data can only be read sequentially, with old data being evicted from the cache. To fix this issue, either ensure your server responds to range requests with 206 Partial Content, or set maxCacheSize to Infinity in the UrlSource options. Note that the latter will store the entire file in the cache if needed, no matter how large."; + backing.on("read", ({ start, end }) => this._dispatchRead(start, end)); + this._sequentialBacking = backing; + const uniqueSlices = /* @__PURE__ */ new Set(); + for (const otherWorker of this._orchestrator.workers) { + for (const slice of otherWorker.pendingSlices) uniqueSlices.add(slice); + otherWorker.aborted = true; + otherWorker.pendingSlices.length = 0; + } + for (const queuedRead of this._orchestrator.queuedReads) for (const slice of queuedRead.pendingSlices) uniqueSlices.add(slice); + this._orchestrator.workers.length = 0; + this._orchestrator.queuedReads.length = 0; + for (const [, otherAbortController] of this._abortControllers) otherAbortController.abort(); + this._abortControllers.clear(); + for (const slice of uniqueSlices) { + const result = backing._read(slice.start, slice.start + slice.bytes.length); + if (isThenable(result)) result.then((readResult) => { + if (readResult) { + assert(readResult.offset === slice.start); + slice.resolve(readResult.bytes); + } else slice.resolve(null); + }, (error) => slice.reject(error)); + else { + assert(result === null); + slice.resolve(null); + } + } + } + /** @internal */ + _dispose() { + this._orchestrator.dispose(); + for (const [, abortController] of this._abortControllers) abortController.abort(); + this._abortControllers.clear(); + this._sequentialAbortController?.abort(); + if (this._sequentialBacking) { + this._sequentialBacking._disposed = true; + this._sequentialBacking._dispose(); + } + } +}; +var BYTE_RANGE_REGEX = /^bytes=(\d+)-(\d*)$/; +var parseByteRangeHeader = (value) => { + const match = BYTE_RANGE_REGEX.exec(value.trim()); + if (!match) return null; + const offset = Number(match[1]); + const end = match[2] === "" ? null : Number(match[2]); + if (end !== null && end < offset) return null; + return { + offset, + length: end !== null ? end - offset + 1 : null + }; +}; +/** +* A source backed by a path to a file. Intended for server-side usage in Node, Bun, or Deno. +* +* Make sure to call `.dispose()` on the corresponding {@link Input} when done to explicitly free the internal file +* handle acquired by this source. +* @group Input sources +* @public +*/ +var FilePathSource = class FilePathSource extends PathedSource { + /** Creates a new {@link FilePathSource} backed by the file at the specified file path. */ + constructor(filePath, options = {}) { + if (typeof filePath !== "string") throw new TypeError("filePath must be a string."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number."); + if (!node$1.fs) throw new Error("FilePathSource is only available in server-side environments (Node.js, Bun, Deno)."); + super(filePath, (request) => new FilePathSource(request.path, options)); + /** @internal */ + this._fileHandle = null; + this._customSource = new CustomSource({ + getSize: async () => { + const fileHandle = await node$1.fs.open(filePath, "r"); + this._fileHandle = fileHandle; + sourceFinalizationRegistry?.register(this, () => { + fileHandle.close(); + }, this); + return (await fileHandle.stat()).size; + }, + read: async (start, end) => { + assert(this._fileHandle); + const buffer = new Uint8Array(end - start); + await this._fileHandle.read(buffer, 0, end - start, start); + return buffer; + }, + maxCacheSize: options.maxCacheSize, + prefetchProfile: "fileSystem", + handleUnhandledError: options.handleUnhandledError + }); + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + return this._customSource._read(start, end, minReadPosition, maxReadPosition); + } + /** @internal */ + _getFileSize() { + return this._customSource._getFileSize(); + } + /** @internal */ + _dispose() { + this._customSource._dispose(); + if (this._fileHandle) { + this._fileHandle.close(); + this._fileHandle = null; + sourceFinalizationRegistry?.unregister(this); + } + } +}; +/** +* A general-purpose, callback-driven source that can get its data from anywhere. Use this source to implement your own +* custom source if the other sources don't cover your case. +* @group Input sources +* @public +*/ +var CustomSource = class extends Source { + /** Creates a new {@link CustomSource} whose behavior is specified by `options`. */ + constructor(options) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (typeof options.getSize !== "function") throw new TypeError("options.getSize must be a function."); + if (typeof options.read !== "function") throw new TypeError("options.read must be a function."); + if (options.dispose !== void 0 && typeof options.dispose !== "function") throw new TypeError("options.dispose, when provided, must be a function."); + if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") throw new TypeError("options.handleUnhandledError, when provided, must be a function."); + if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number."); + if (options.prefetchProfile && ![ + "none", + "fileSystem", + "network" + ].includes(options.prefetchProfile)) throw new TypeError("options.prefetchProfile, when provided, must be one of 'none', 'fileSystem' or 'network'."); + super(); + /** @internal */ + this._readers = /* @__PURE__ */ new Set(); + this._options = options; + this._orchestrator = new ReadOrchestrator({ + maxCacheSize: options.maxCacheSize ?? 8 * 2 ** 20, + maxWorkerCount: 2, + prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? "none"], + runWorker: this._runWorker.bind(this), + handleUnhandledError: options.handleUnhandledError + }); + } + /** @internal */ + _getFileSize() { + return this._orchestrator.fileSize ?? void 0; + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + if (this._orchestrator.fileSize !== null) return this._orchestrator.read(start, end, minReadPosition, maxReadPosition); + const result = this._options.getSize(); + if (isThenable(result)) return result.then((size) => { + if (!Number.isInteger(size) || size < 0) throw new TypeError("options.getSize must return or resolve to a non-negative integer."); + this._orchestrator.fileSize = size; + return this._orchestrator.read(start, end, minReadPosition, maxReadPosition); + }); + else { + if (!Number.isInteger(result) || result < 0) throw new TypeError("options.getSize must return or resolve to a non-negative integer."); + this._orchestrator.fileSize = result; + return this._orchestrator.read(start, end, minReadPosition, maxReadPosition); + } + } + /** @internal */ + async _runWorker(worker) { + while (worker.currentPos < worker.targetPos && !worker.aborted) { + const originalCurrentPos = worker.currentPos; + const originalTargetPos = worker.targetPos; + let data = this._options.read(worker.currentPos, originalTargetPos); + if (isThenable(data)) data = await data; + if (worker.aborted) { + if (data instanceof ReadableStream) await data.cancel().catch(() => {}); + break; + } + if (data instanceof Uint8Array) { + data = toUint8Array(data); + if (data.length !== originalTargetPos - worker.currentPos) throw new Error(`options.read returned a Uint8Array with unexpected length: Requested ${originalTargetPos - worker.currentPos} bytes, but got ${data.length}.`); + this._dispatchRead(worker.currentPos, worker.currentPos + data.length); + this._orchestrator.supplyWorkerData(worker, data); + } else if (data instanceof ReadableStream) { + const reader = data.getReader(); + this._readers.add(reader); + try { + while (worker.currentPos < originalTargetPos && !worker.aborted) { + const { done, value } = await reader.read(); + if (done) { + if (worker.currentPos < originalTargetPos) throw new Error(`ReadableStream returned by options.read ended before supplying enough data. Requested ${originalTargetPos - originalCurrentPos} bytes, but got ${worker.currentPos - originalCurrentPos}`); + break; + } + if (!(value instanceof Uint8Array)) throw new TypeError("ReadableStream returned by options.read must yield Uint8Array chunks."); + if (worker.aborted) break; + const data = toUint8Array(value); + this._dispatchRead(worker.currentPos, worker.currentPos + data.length); + this._orchestrator.supplyWorkerData(worker, data); + } + } finally { + this._readers.delete(reader); + reader.releaseLock(); + } + } else throw new TypeError("options.read must return or resolve to a Uint8Array or a ReadableStream."); + } + this._orchestrator.signalWorkerStoppedRunning(worker); + } + /** @internal */ + _dispose() { + this._orchestrator.dispose(); + for (const reader of this._readers) reader.cancel().catch(() => {}); + this._readers.clear(); + this._options.dispose?.(); + } +}; +/** +* An alias for {@link CustomSource}. +* @deprecated This name is misleading and will be removed in a future release. Please use {@link CustomSource} instead. +* +* @group Input sources +* @public +*/ +var StreamSource = CustomSource; +/** +* A source backed by a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) of +* `Uint8Array`, representing an append-only byte stream of unknown length. This is the source to use for incrementally +* streaming in input files that are still being constructed and whose size we don't yet know, like for example the +* output chunks of [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder). +* +* This source is *unsized*, meaning calls to `.getSize()` will throw and readers are more limited due to the +* lack of random file access. You should only use this source with sequential access patterns, such as reading all +* packets from start to end. This source does not work well with random access patterns unless you increase its +* max cache size. +* +* @group Input sources +* @public +*/ +var ReadableStreamSource = class extends Source { + /** Creates a new {@link ReadableStreamSource} backed by the specified `ReadableStream `. */ + constructor(stream, options = {}) { + if (!(stream instanceof ReadableStream)) throw new TypeError("stream must be a ReadableStream."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") throw new TypeError("options.handleUnhandledError, when provided, must be a function."); + if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number."); + super(); + /** @internal */ + this._reader = null; + /** @internal */ + this._cache = []; + /** @internal */ + this._pendingSlices = []; + /** @internal */ + this._currentIndex = 0; + /** @internal */ + this._targetIndex = 0; + /** @internal */ + this._maxRequestedIndex = 0; + /** @internal */ + this._endIndex = null; + /** @internal */ + this._pulling = false; + /** + * Overridable for internal use. + * @internal + */ + this._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. With ReadableStreamSource, you must access the data more sequentially or increase the size of its cache."; + this._stream = stream; + this._maxCacheSize = options.maxCacheSize ?? 32 * 2 ** 20; + this._handleUnhandledError = options.handleUnhandledError; + } + /** @internal */ + _getFileSize() { + return this._endIndex; + } + /** @internal */ + _read(start, end) { + if (this._endIndex !== null && end > this._endIndex) return null; + this._maxRequestedIndex = Math.max(this._maxRequestedIndex, end); + const cacheStartIndex = binarySearchLessOrEqual(this._cache, start, (x) => x.start); + const cacheStartEntry = cacheStartIndex !== -1 ? this._cache[cacheStartIndex] : null; + if (cacheStartEntry && cacheStartEntry.start <= start && end <= cacheStartEntry.end) return { + bytes: cacheStartEntry.bytes, + view: cacheStartEntry.view, + offset: cacheStartEntry.start + }; + let lastEnd = start; + const bytes = new Uint8Array(end - start); + if (cacheStartIndex !== -1) for (let i = cacheStartIndex; i < this._cache.length; i++) { + const cacheEntry = this._cache[i]; + if (cacheEntry.start >= end) break; + const cappedStart = Math.max(start, cacheEntry.start); + if (cappedStart > lastEnd) this._throwDueToCacheMiss(); + const cappedEnd = Math.min(end, cacheEntry.end); + if (cappedStart < cappedEnd) { + bytes.set(cacheEntry.bytes.subarray(cappedStart - cacheEntry.start, cappedEnd - cacheEntry.start), cappedStart - start); + lastEnd = cappedEnd; + } + } + if (lastEnd === end) return { + bytes, + view: toDataView(bytes), + offset: start + }; + if (this._currentIndex > lastEnd) this._throwDueToCacheMiss(); + const { promise, resolve, reject } = promiseWithResolvers(); + this._pendingSlices.push({ + start, + end, + bytes, + resolve, + reject + }); + this._targetIndex = Math.max(this._targetIndex, end); + if (!this._pulling) { + this._pulling = true; + this._pull().catch((error) => { + this._pulling = false; + if (this._pendingSlices.length > 0) { + this._pendingSlices.forEach((x) => x.reject(error)); + this._pendingSlices.length = 0; + } else if (this._handleUnhandledError) this._handleUnhandledError(error); + else throw error; + }); + } + return promise; + } + /** @internal */ + _throwDueToCacheMiss() { + throw new Error(this._cacheMissErrorMessage); + } + /** @internal */ + async _pull() { + this._reader ??= this._stream.getReader(); + while (this._currentIndex < this._targetIndex && !this._disposed) { + const { done, value } = await this._reader.read(); + if (done) { + for (const pendingSlice of this._pendingSlices) pendingSlice.resolve(null); + this._pendingSlices.length = 0; + this._endIndex = this._currentIndex; + break; + } + const startIndex = this._currentIndex; + const endIndex = this._currentIndex + value.byteLength; + this._dispatchRead(startIndex, endIndex); + for (let i = 0; i < this._pendingSlices.length; i++) { + const pendingSlice = this._pendingSlices[i]; + const cappedStart = Math.max(startIndex, pendingSlice.start); + const cappedEnd = Math.min(endIndex, pendingSlice.end); + if (cappedStart < cappedEnd) { + pendingSlice.bytes.set(value.subarray(cappedStart - startIndex, cappedEnd - startIndex), cappedStart - pendingSlice.start); + if (cappedEnd === pendingSlice.end) { + pendingSlice.resolve({ + bytes: pendingSlice.bytes, + view: toDataView(pendingSlice.bytes), + offset: pendingSlice.start + }); + this._pendingSlices.splice(i, 1); + i--; + } + } + } + this._cache.push({ + start: startIndex, + end: endIndex, + bytes: value, + view: toDataView(value), + age: 0 + }); + while (this._cache.length > 0) { + const firstEntry = this._cache[0]; + if (this._maxRequestedIndex - firstEntry.end <= this._maxCacheSize) break; + this._cache.shift(); + } + this._currentIndex += value.byteLength; + } + this._pulling = false; + } + /** @internal */ + _dispose() { + for (const pendingSlice of this._pendingSlices) pendingSlice.reject(new InputDisposedError()); + this._pendingSlices.length = 0; + this._cache.length = 0; + this._reader?.cancel().catch(() => {}); + } +}; +var PREFETCH_PROFILES = { + none: (start, end) => ({ + start, + end + }), + fileSystem: (start, end) => { + const padding = 2 ** 16; + start = Math.floor((start - padding) / padding) * padding; + end = Math.ceil((end + padding) / padding) * padding; + return { + start, + end + }; + }, + network: (start, end, workers) => { + const paddingStart = 2 ** 16; + start = Math.max(0, Math.floor((start - paddingStart) / paddingStart) * paddingStart); + for (const worker of workers) { + const maxExtensionAmount = 8 * 2 ** 20; + const thresholdPoint = Math.max((worker.startPos + worker.targetPos) / 2, worker.targetPos - maxExtensionAmount); + if (closedIntervalsOverlap(start, end, thresholdPoint, worker.targetPos)) { + const size = worker.targetPos - worker.startPos; + const a = Math.ceil((size + 1) / maxExtensionAmount) * maxExtensionAmount; + const b = 2 ** Math.ceil(Math.log2(size + 1)); + const extent = Math.min(b, a); + end = Math.max(end, worker.startPos + extent); + } + } + end = Math.max(end, start + URL_SOURCE_MIN_LOAD_AMOUNT); + return { + start, + end + }; + } +}; +/** +* Godclass for orchestrating complex, cached read operations. The reading model is as follows: Any reading task is +* delegated to a *worker*, which is a sequential reader positioned somewhere along the file. All workers run in +* parallel and can be stopped and resumed in their forward movement. When read requests come in, this orchestrator will +* first try to satisfy the request with only the cached data. If this isn't possible, workers are spun up for all +* missing parts (or existing workers are repurposed), and these workers will then fill the holes in the data as they +* march along the file. +*/ +var ReadOrchestrator = class { + constructor(options) { + this.options = options; + this.fileSize = null; + this.nextAge = 0; + this.workers = []; + this.cache = []; + this.currentCacheSize = 0; + this.disposed = false; + this.queuedReads = []; + } + read(innerStart, innerEnd, minReadPosition, maxReadPosition) { + assert(!this.disposed); + const prefetchRange = this.options.prefetchProfile(innerStart, innerEnd, this.workers); + const outerStart = Math.max(prefetchRange.start, minReadPosition); + const outerEnd = Math.min(prefetchRange.end, this.fileSize ?? Infinity, maxReadPosition); + assert(outerStart <= innerStart && innerEnd <= outerEnd); + let result = null; + const innerCacheStartIndex = binarySearchLessOrEqual(this.cache, innerStart, (x) => x.start); + const innerStartEntry = innerCacheStartIndex !== -1 ? this.cache[innerCacheStartIndex] : null; + if (innerStartEntry && innerStartEntry.start <= innerStart && innerEnd <= innerStartEntry.end) { + innerStartEntry.age = this.nextAge++; + result = { + bytes: innerStartEntry.bytes, + view: innerStartEntry.view, + offset: innerStartEntry.start + }; + } + const outerCacheStartIndex = binarySearchLessOrEqual(this.cache, outerStart, (x) => x.start); + const bytes = result ? null : new Uint8Array(innerEnd - innerStart); + let contiguousBytesWriteEnd = 0; + let lastEnd = outerStart; + const outerHoles = []; + if (outerCacheStartIndex !== -1) { + for (let i = outerCacheStartIndex; i < this.cache.length; i++) { + const entry = this.cache[i]; + if (entry.start >= outerEnd) break; + if (entry.end <= outerStart) continue; + const cappedOuterStart = Math.max(outerStart, entry.start); + const cappedOuterEnd = Math.min(outerEnd, entry.end); + assert(cappedOuterStart <= cappedOuterEnd); + if (lastEnd < cappedOuterStart) outerHoles.push({ + start: lastEnd, + end: cappedOuterStart + }); + lastEnd = cappedOuterEnd; + if (bytes) { + const cappedInnerStart = Math.max(innerStart, entry.start); + const cappedInnerEnd = Math.min(innerEnd, entry.end); + if (cappedInnerStart < cappedInnerEnd) { + const relativeOffset = cappedInnerStart - innerStart; + bytes.set(entry.bytes.subarray(cappedInnerStart - entry.start, cappedInnerEnd - entry.start), relativeOffset); + if (relativeOffset === contiguousBytesWriteEnd) contiguousBytesWriteEnd = cappedInnerEnd - innerStart; + } + } + entry.age = this.nextAge++; + } + if (lastEnd < outerEnd) outerHoles.push({ + start: lastEnd, + end: outerEnd + }); + } else outerHoles.push({ + start: outerStart, + end: outerEnd + }); + if (bytes && contiguousBytesWriteEnd >= bytes.length) result = { + bytes, + view: toDataView(bytes), + offset: innerStart + }; + if (outerHoles.length === 0) { + assert(result); + return result; + } + const { promise, resolve, reject } = promiseWithResolvers(); + const innerHoles = []; + for (const outerHole of outerHoles) { + const cappedStart = Math.max(innerStart, outerHole.start); + const cappedEnd = Math.min(innerEnd, outerHole.end); + if (cappedStart === outerHole.start && cappedEnd === outerHole.end) innerHoles.push(outerHole); + else if (cappedStart < cappedEnd) innerHoles.push({ + start: cappedStart, + end: cappedEnd + }); + } + const pendingSlice = bytes && { + start: innerStart, + bytes, + holes: innerHoles, + resolve, + reject + }; + outer: for (const outerHole of outerHoles) { + for (const worker of this.workers) if (this.checkHoleAgainstWorker(worker, outerHole, pendingSlice ? [pendingSlice] : [])) { + this.checkQueuedReadsAgainstWorker(worker); + continue outer; + } + const strictTarget = outerHole.end < outerEnd || this.fileSize !== null; + const newWorker = this.createWorker(outerHole.start, outerHole.end, strictTarget); + if (newWorker) { + if (pendingSlice) newWorker.pendingSlices = [pendingSlice]; + this.runWorker(newWorker); + } else { + let index = binarySearchLessOrEqual(this.queuedReads, outerHole.start, (x) => x.hole.start); + let entry = index !== -1 ? this.queuedReads[index] : null; + if (entry && outerHole.start <= entry.hole.end) { + entry.hole.end = Math.max(entry.hole.end, outerHole.end); + entry.strictTarget &&= strictTarget; + if (pendingSlice) entry.pendingSlices.push(pendingSlice); + } else { + index++; + entry = { + hole: { + start: outerHole.start, + end: outerHole.end + }, + strictTarget, + pendingSlices: pendingSlice ? [pendingSlice] : [], + age: this.nextAge++ + }; + this.queuedReads.splice(index, 0, entry); + } + while (index + 1 < this.queuedReads.length) { + const nextEntry = this.queuedReads[index + 1]; + if (nextEntry.hole.start > entry.hole.end) break; + entry.hole.end = Math.max(entry.hole.end, nextEntry.hole.end); + entry.pendingSlices.push(...nextEntry.pendingSlices); + entry.strictTarget &&= nextEntry.strictTarget; + entry.age = Math.min(entry.age, nextEntry.age); + this.queuedReads.splice(index + 1, 1); + } + } + } + if (!result) { + assert(bytes); + result = promise.then((bytes) => bytes && { + bytes, + view: toDataView(bytes), + offset: innerStart + }); + } else promise.catch((error) => { + if (this.disposed) return; + if (this.options.handleUnhandledError) this.options.handleUnhandledError(error); + else throw error; + }); + return result; + } + checkHoleAgainstWorker(worker, hole, pendingSlices) { + if (closedIntervalsOverlap(hole.start - 2 ** 17, hole.start, worker.currentPos, worker.targetPos)) { + worker.targetPos = Math.max(worker.targetPos, hole.end); + for (let i = 0; i < pendingSlices.length; i++) { + const pendingSlice = pendingSlices[i]; + if (!worker.pendingSlices.includes(pendingSlice)) worker.pendingSlices.push(pendingSlice); + } + if (!worker.running) this.runWorker(worker); + return true; + } + return false; + } + checkQueuedReadsAgainstWorker(worker) { + let wasTrueOnce = false; + for (let i = 0; i < this.queuedReads.length; i++) { + const queuedRead = this.queuedReads[i]; + if (this.checkHoleAgainstWorker(worker, queuedRead.hole, queuedRead.pendingSlices)) { + this.queuedReads.splice(i, 1); + i--; + wasTrueOnce = true; + } else if (wasTrueOnce) break; + } + } + createWorker(startPos, targetPos, strictTarget) { + if (this.workers.length >= this.options.maxWorkerCount) { + let oldestWorker = null; + let oldestIndex = null; + for (let i = 0; i < this.workers.length; i++) { + const worker = this.workers[i]; + if (!worker.running && worker.pendingSlices.length === 0 && (!oldestWorker || worker.age < oldestWorker.age)) { + oldestIndex = i; + oldestWorker = worker; + } + } + if (oldestWorker) { + assert(oldestIndex !== null); + assert(oldestWorker.pendingSlices.length === 0); + this.workers.splice(oldestIndex, 1); + this.options.onIdleWorkerRemoved?.(oldestWorker); + } else return null; + } + const worker = { + startPos, + currentPos: startPos, + targetPos, + strictTarget, + running: false, + aborted: this.disposed, + pendingSlices: [], + age: this.nextAge++ + }; + this.workers.push(worker); + return worker; + } + runWorker(worker) { + assert(!worker.running); + assert(worker.currentPos < worker.targetPos); + worker.running = true; + worker.age = this.nextAge++; + this.options.runWorker(worker).catch((error) => { + worker.running = false; + if (worker.pendingSlices.length > 0) { + worker.pendingSlices.forEach((x) => x.reject(error)); + worker.pendingSlices.length = 0; + } else if (!worker.aborted && !this.disposed) if (this.options.handleUnhandledError) this.options.handleUnhandledError(error); + else throw error; + }).finally(() => { + if (worker.running) return; + if (this.queuedReads.length > 0) { + let oldestIndex = 0; + for (let i = 1; i < this.queuedReads.length; i++) if (this.queuedReads[i].age < this.queuedReads[oldestIndex].age) oldestIndex = i; + const queuedRead = this.queuedReads[oldestIndex]; + const newWorker = this.createWorker(queuedRead.hole.start, queuedRead.hole.end, queuedRead.strictTarget); + if (!newWorker) return; + this.queuedReads.splice(oldestIndex, 1); + newWorker.pendingSlices = queuedRead.pendingSlices; + this.runWorker(newWorker); + } + }); + } + /** Called by a worker when it has read some data. */ + supplyWorkerData(worker, bytes) { + assert(!worker.aborted); + const start = worker.currentPos; + const end = start + bytes.length; + this.insertIntoCache({ + start, + end, + bytes, + view: toDataView(bytes), + age: this.nextAge++ + }); + worker.currentPos += bytes.length; + if (worker.currentPos > worker.targetPos) { + worker.targetPos = worker.currentPos; + this.checkQueuedReadsAgainstWorker(worker); + } + for (let i = 0; i < worker.pendingSlices.length; i++) { + const pendingSlice = worker.pendingSlices[i]; + const clampedStart = Math.max(start, pendingSlice.start); + const clampedEnd = Math.min(end, pendingSlice.start + pendingSlice.bytes.length); + if (clampedStart < clampedEnd) pendingSlice.bytes.set(bytes.subarray(clampedStart - start, clampedEnd - start), clampedStart - pendingSlice.start); + for (let j = 0; j < pendingSlice.holes.length; j++) { + const hole = pendingSlice.holes[j]; + if (start <= hole.start && end > hole.start) hole.start = end; + if (hole.end <= hole.start) { + pendingSlice.holes.splice(j, 1); + j--; + } + } + if (pendingSlice.holes.length === 0) { + pendingSlice.resolve(pendingSlice.bytes); + worker.pendingSlices.splice(i, 1); + i--; + } + } + for (let i = 0; i < this.workers.length; i++) { + const otherWorker = this.workers[i]; + if (worker === otherWorker || otherWorker.running) continue; + if (closedIntervalsOverlap(start, end, otherWorker.currentPos, otherWorker.targetPos)) { + this.workers.splice(i, 1); + this.options.onIdleWorkerRemoved?.(otherWorker); + i--; + } + } + } + supplyFileSize(size) { + assert(this.fileSize === null); + this.fileSize = size; + for (const worker of this.workers) { + worker.targetPos = Math.min(worker.targetPos, size); + worker.strictTarget = true; + for (let i = 0; i < worker.pendingSlices.length; i++) { + const pendingSlice = worker.pendingSlices[i]; + for (const hole of pendingSlice.holes) if (hole.end > size) { + pendingSlice.resolve(null); + worker.pendingSlices.splice(i, 1); + i--; + break; + } + } + } + for (let i = 0; i < this.queuedReads.length; i++) { + const queuedRead = this.queuedReads[i]; + if (queuedRead.hole.start >= size) { + for (const slice of queuedRead.pendingSlices) slice.resolve(null); + this.queuedReads.splice(i, 1); + i--; + } else if (queuedRead.hole.end > size) { + queuedRead.hole.end = size; + queuedRead.strictTarget = true; + for (let j = 0; j < queuedRead.pendingSlices.length; j++) { + const slice = queuedRead.pendingSlices[j]; + if (slice.start >= size) { + slice.resolve(null); + queuedRead.pendingSlices.splice(j, 1); + j--; + } + } + } + } + } + signalWorkerStoppedRunning(worker) { + worker.running = false; + if (!worker.aborted) worker.pendingSlices.length = 0; + } + /** Called when a worker reaches the end of the underlying data and must be cleaned up. */ + onWorkerFinished(worker) { + const index = this.workers.indexOf(worker); + assert(index !== -1); + worker.running = false; + this.workers.splice(index, 1); + this.options.onIdleWorkerRemoved?.(worker); + if (this.fileSize === null) this.supplyFileSize(worker.currentPos); + for (const pendingSlice of worker.pendingSlices) pendingSlice.resolve(null); + } + insertIntoCache(entry) { + if (this.options.maxCacheSize === 0) return; + let insertionIndex = binarySearchLessOrEqual(this.cache, entry.start, (x) => x.start) + 1; + if (insertionIndex > 0) { + const previous = this.cache[insertionIndex - 1]; + if (previous.end >= entry.end) return; + if (previous.end > entry.start) { + const joined = new Uint8Array(entry.end - previous.start); + joined.set(previous.bytes, 0); + joined.set(entry.bytes, entry.start - previous.start); + this.currentCacheSize += entry.end - previous.end; + previous.bytes = joined; + previous.view = toDataView(joined); + previous.end = entry.end; + insertionIndex--; + entry = previous; + } else { + this.cache.splice(insertionIndex, 0, entry); + this.currentCacheSize += entry.bytes.length; + } + } else { + this.cache.splice(insertionIndex, 0, entry); + this.currentCacheSize += entry.bytes.length; + } + for (let i = insertionIndex + 1; i < this.cache.length; i++) { + const next = this.cache[i]; + if (entry.end <= next.start) break; + if (entry.end >= next.end) { + this.cache.splice(i, 1); + this.currentCacheSize -= next.bytes.length; + i--; + continue; + } + const joined = new Uint8Array(next.end - entry.start); + joined.set(entry.bytes, 0); + joined.set(next.bytes, next.start - entry.start); + this.currentCacheSize -= entry.end - next.start; + entry.bytes = joined; + entry.view = toDataView(joined); + entry.end = next.end; + this.cache.splice(i, 1); + break; + } + while (this.currentCacheSize > this.options.maxCacheSize) { + let oldestIndex = 0; + let oldestEntry = this.cache[0]; + for (let i = 1; i < this.cache.length; i++) { + const entry = this.cache[i]; + if (entry.age < oldestEntry.age) { + oldestIndex = i; + oldestEntry = entry; + } + } + if (this.currentCacheSize - oldestEntry.bytes.length <= this.options.maxCacheSize) break; + this.cache.splice(oldestIndex, 1); + this.currentCacheSize -= oldestEntry.bytes.length; + } + } + dispose() { + for (const worker of this.workers) { + for (const slice of worker.pendingSlices) slice.reject(new InputDisposedError()); + worker.pendingSlices.length = 0; + worker.aborted = true; + if (!worker.running) this.options.onIdleWorkerRemoved?.(worker); + } + for (const queuedRead of this.queuedReads) for (const slice of queuedRead.pendingSlices) slice.reject(new InputDisposedError()); + this.workers.length = 0; + this.cache.length = 0; + this.queuedReads.length = 0; + this.disposed = true; + } +}; +/** +* A source that covers a range (offset + length) of another source. Useful for reading files that are embedded within +* larger files. +* +* @group Input sources +* @public +*/ +var RangedSource = class extends Source { + /** @internal */ + constructor(baseSource, offset, length) { + super(); + /** @internal */ + this._ref = null; + if (baseSource._disposed) throw new Error("Cannot create a slice of a disposed source."); + this._baseSource = baseSource; + this._offset = offset; + this._length = length ?? null; + } + /** @internal */ + _getFileSize() { + const baseSize = this._baseSource._getFileSize(); + if (baseSize === void 0) return this._length !== null ? this._length : void 0; + if (baseSize === null) if (this._length !== null) return this._length; + else return null; + return clamp(baseSize - this._offset, 0, this._length ?? Infinity); + } + /** @internal */ + _read(start, end, minReadPosition, maxReadPosition) { + if (this._length !== null && end > this._length) return null; + const result = this._baseSource._read(this._offset + start, this._offset + end, this._offset + minReadPosition, this._offset + maxReadPosition); + const processResult = (result) => { + if (!result) return null; + result.offset -= this._offset; + return result; + }; + if (isThenable(result)) return result.then(processResult); + else return processResult(result); + } + /** @internal */ + _dispose() { + this._ref?.free(); + } + ref() { + this._ref ??= this._baseSource.ref(); + return super.ref(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/hls/hls-segmented-input.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var __addDisposableResource$3 = function(env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); + var dispose, inner; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + if (async) inner = dispose; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + if (inner) dispose = function() { + try { + inner.call(this); + } catch (e) { + return Promise.reject(e); + } + }; + env.stack.push({ + value, + dispose, + async + }); + } else if (async) env.stack.push({ async: true }); + return value; +}; +var __disposeResources$3 = (function(SuppressedError) { + return function(env) { + function fail(e) { + env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + var r, s = 0; + function next() { + while (r = env.stack.pop()) try { + if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); + if (r.dispose) { + var result = r.dispose.call(r.value); + if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { + fail(e); + return next(); + }); + } else s |= 1; + } catch (e) { + fail(e); + } + if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); + if (env.hasError) throw env.error; + } + return next(); + }; +})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}); +var IV_STRING_REGEX = /^0[xX][0-9a-fA-F]+$/; +var BASE64_DATA_URI_REGEX = /^data:.*;base64,/i; +var HlsSegmentedInput = class extends SegmentedInput { + constructor(demuxer, path, trackDeclarations, lines) { + super(demuxer.input, path, trackDeclarations); + this.segments = []; + this.nextLines = null; + this.currentUpdateSegmentsPromise = null; + this.streamHasEnded = false; + this.lastSegmentUpdateTime = -Infinity; + this.refreshInterval = 5; + this.rootPath = path; + this.demuxer = demuxer; + this.nextLines = lines; + } + runUpdateSegments() { + return this.currentUpdateSegmentsPromise ??= (async () => { + try { + const remainingWaitTimeMs = this.getRemainingWaitTimeMs(); + if (remainingWaitTimeMs > 0) await wait(remainingWaitTimeMs); + this.lastSegmentUpdateTime = performance.now(); + await this.updateSegments(); + } finally { + this.currentUpdateSegmentsPromise = null; + } + })(); + } + getRemainingWaitTimeMs() { + const elapsed = performance.now() - this.lastSegmentUpdateTime; + const result = Math.max(0, 1e3 * this.refreshInterval - elapsed); + if (result <= 50) return 0; + return result; + } + /** + * Reads and parses the segment info from the playlist file. When called more than one, it updates the existing + * segments by appending the new ones. Existing segments are never removed. + */ + async updateSegments() { + let lines = this.nextLines; + this.nextLines = null; + if (!lines) { + const env_1 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const ref = __addDisposableResource$3(env_1, await this.demuxer.input._getSourceUncached({ + path: this.rootPath, + isRoot: false + }), false); + const slice = await new Reader(ref.source).requestEntireFile(); + assert(slice); + lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine }); + if (ref.source instanceof PathedSource) this.rootPath = ref.source.rootPath; + } catch (e_1) { + env_1.error = e_1; + env_1.hasError = true; + } finally { + __disposeResources$3(env_1); + } + } + const offsetTimestampsByDateTime = this.input._formatOptions.hls?.offsetTimestampsByDateTime !== false; + const variables = new HlsPlaylistVariables(this.rootPath, this.demuxer.hasMasterPlaylist ? this.demuxer.masterPlaylistVariables : null); + let headerRead = false; + let accumulatedTime = 0; + let accumulatedUnixTime = null; + let nextSegmentDuration = null; + let currentKey = null; + let nextSequenceNumber = 0; + let currentFirstSegment = null; + let currentInitSegment = null; + let lastByteRangeEnd = null; + let nextByteRange = null; + let lastProgramDateTimeSeconds = null; + let targetDuration = null; + let segmentSeen = false; + let prevLastSegment = last(this.segments) ?? null; + const parseByteRange = (content) => { + const atIndex = content.indexOf("@"); + const length = Number(atIndex === -1 ? content : content.slice(0, atIndex)); + if (!Number.isInteger(length) || length < 0) throw new Error(`Invalid #EXT-X-BYTERANGE length '${content}'.`); + let offset = null; + if (atIndex !== -1) { + offset = Number(content.slice(atIndex + 1)); + if (!Number.isInteger(offset) || offset < 0) throw new Error(`Invalid #EXT-X-BYTERANGE offset '${content}'.`); + } + return { + length, + offset + }; + }; + const setNextSequenceNumber = (number) => { + nextSequenceNumber = number; + if (prevLastSegment) { + assert(prevLastSegment.sequenceNumber !== null); + if (prevLastSegment.sequenceNumber < number) { + accumulatedTime = prevLastSegment.timestamp + prevLastSegment.duration; + currentFirstSegment = prevLastSegment.firstSegment; + currentInitSegment = prevLastSegment.initSegment; + lastProgramDateTimeSeconds = prevLastSegment.lastProgramDateTimeSeconds; + accumulatedUnixTime = prevLastSegment.unixEpochTimestamp !== null ? prevLastSegment.unixEpochTimestamp + prevLastSegment.duration : null; + prevLastSegment = null; + } + } + }; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (!headerRead) { + if (line !== "#EXTM3U") throw new Error("Invalid M3U8 file; expected first line to be #EXTM3U."); + headerRead = true; + continue; + } + if (!line.startsWith("#")) { + if (!prevLastSegment) { + if (nextSegmentDuration === null) throw new Error("Invalid M3U8 file; a segment must be preceded by an #EXTINF tag."); + let key = currentKey; + if (key && key.method === "AES-128" && !key.iv) { + const iv = new Uint8Array(16); + const view = toDataView(iv); + view.setUint32(8, Math.floor(nextSequenceNumber / 2 ** 32)); + view.setUint32(12, nextSequenceNumber); + key = { + ...key, + iv + }; + } + const location = { + path: joinPaths(this.rootPath, variables.substitute(line)), + offset: nextByteRange?.offset ?? 0, + length: nextByteRange?.length ?? null + }; + const segment = { + timestamp: accumulatedTime, + unixEpochTimestamp: accumulatedUnixTime, + firstSegment: currentFirstSegment, + sequenceNumber: nextSequenceNumber, + location, + duration: nextSegmentDuration, + encryption: key, + initSegment: currentInitSegment, + lastProgramDateTimeSeconds + }; + currentFirstSegment ??= segment; + accumulatedTime += nextSegmentDuration; + if (accumulatedUnixTime !== null) accumulatedUnixTime += nextSegmentDuration; + this.segments.push(segment); + } + nextSegmentDuration = null; + if (nextByteRange === null) lastByteRangeEnd = null; + else nextByteRange = null; + setNextSequenceNumber(nextSequenceNumber + 1); + } + if (line.startsWith("#EXTINF:")) { + if (prevLastSegment) { + segmentSeen = true; + continue; + } + if (!segmentSeen) { + if (lastProgramDateTimeSeconds === null && nextSequenceNumber > 0 && targetDuration !== null) accumulatedTime = nextSequenceNumber * targetDuration; + segmentSeen = true; + } + const extinfContent = line.slice(TAG_EXTINF.length); + const commaIndex = extinfContent.indexOf(","); + const durationStr = commaIndex === -1 ? extinfContent : extinfContent.slice(0, commaIndex); + const duration = Number(durationStr); + if (!Number.isFinite(duration) || duration < 0) throw new Error(`Invalid #EXTINF tag duration '${durationStr}'.`); + nextSegmentDuration = duration; + } else if (line.startsWith("#EXT-X-DEFINE:")) variables.define(line.slice(TAG_DEFINE.length)); + else if (line.startsWith("#EXT-X-MAP:")) { + const attributes = new AttributeList(line.slice(TAG_MAP.length), variables); + const uri = attributes.get("uri"); + if (!uri) throw new Error("Invalid #EXT-X-MAP tag; missing URI attribute."); + const byteRange = attributes.get("byterange"); + let parsedByteRange = null; + if (byteRange !== null) parsedByteRange = parseByteRange(byteRange); + if (parsedByteRange && parsedByteRange.offset === null) throw new Error("Invalid #EXT-X-MAP tag; BYTERANGE attribute must have a specified offset."); + if (!prevLastSegment) { + const location = { + path: joinPaths(this.rootPath, uri), + offset: parsedByteRange?.offset ?? 0, + length: parsedByteRange?.length ?? null + }; + if (currentKey?.method === "AES-128" && !currentKey.iv) throw new Error("IV attribute must be set on #EXT-X-KEY tag preceding the #EXT-X-MAP tag."); + currentInitSegment = { + timestamp: accumulatedTime, + unixEpochTimestamp: accumulatedUnixTime, + firstSegment: null, + sequenceNumber: null, + location, + duration: 0, + encryption: currentKey, + initSegment: null, + lastProgramDateTimeSeconds + }; + } + nextSegmentDuration = null; + if (nextByteRange === null) lastByteRangeEnd = null; + else nextByteRange = null; + } else if (line.startsWith("#EXT-X-KEY:")) { + const attributes = new AttributeList(line.slice(TAG_KEY.length), variables); + const method = attributes.get("method"); + if (method === "NONE") currentKey = null; + else if (method === "AES-128") { + const uri = attributes.get("uri"); + if (!uri) throw new Error("Invalid #EXT-X-KEY: AES-128 requires a URI attribute."); + let iv = null; + const ivString = attributes.get("iv"); + if (ivString) { + if (!IV_STRING_REGEX.test(ivString)) throw new Error(`Unsupported IV format '${ivString}'.`); + let hex = ivString.slice(2); + hex = hex.padStart(32, "0"); + hex = hex.slice(-32); + iv = new Uint8Array(16); + for (let i = 0; i < 16; i++) { + const startIndex = 2 * i; + iv[i] = parseInt(hex.slice(startIndex, startIndex + 2), 16); + } + } + const keyFormat = attributes.get("keyformat") ?? "identity"; + if (keyFormat !== "identity") throw new Error("For AES-128 encryption, only the 'identity' KEYFORMAT is currently supported. If you think other formats should be supported, please raise an issue."); + currentKey = { + method: "AES-128", + keyUri: joinPaths(this.rootPath, uri), + iv, + keyFormat + }; + } else if (method === "SAMPLE-AES" || method === "SAMPLE-AES-CTR") { + const uri = attributes.get("uri"); + if (!uri) throw new Error(`Invalid #EXT-X-KEY: ${method} requires a URI attribute.`); + if ((attributes.get("keyformat") ?? "identity") === "identity") throw new Error("For SAMPLE-AES and SAMPLE-AES-CTR encryption, the 'identity' KEYFORMAT is not supported. If you think this format should be supported, please raise an issue."); + let psshBox = null; + if (BASE64_DATA_URI_REGEX.test(uri)) { + const commaIndex = uri.indexOf(","); + const bytes = base64ToBytes(uri.slice(commaIndex + 1)); + if (bytes.length >= 8 && bytes[4] === 112 && bytes[5] === 115 && bytes[6] === 115 && bytes[7] === 104) { + const size = toDataView(bytes).getUint32(0); + psshBox = parsePsshBoxContents(bytes.subarray(8, Math.min(size, bytes.length))); + } + } + currentKey = { + method, + psshBox + }; + } else throw new Error(`Unsupported encryption method '${method}'. If you think this method should be supported, please raise an issue.`); + } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:")) { + const value = line.slice(TAG_MEDIA_SEQUENCE.length); + const number = Number(value); + if (!Number.isInteger(number) || number < 0) throw new Error(`Invalid EXT-X-MEDIA-SEQUENCE value '${value}'.`); + setNextSequenceNumber(number); + } else if (line.startsWith("#EXT-X-BYTERANGE:")) { + const parsed = parseByteRange(line.slice(TAG_BYTERANGE.length)); + if (parsed.offset === null) { + if (lastByteRangeEnd === null) throw new Error("Invalid M3U8 file; #EXT-X-BYTERANGE without offset requires a previous byte range."); + parsed.offset = lastByteRangeEnd; + } + nextByteRange = parsed; + lastByteRangeEnd = parsed.offset + parsed.length; + } else if (line.startsWith("#EXT-X-PROGRAM-DATE-TIME:")) { + if (prevLastSegment) continue; + const dateTime = line.slice(TAG_PROGRAM_DATE_TIME.length); + const dateTimeMs = Date.parse(dateTime); + if (!Number.isFinite(dateTimeMs)) continue; + const dateTimeSeconds = dateTimeMs / 1e3; + if (lastProgramDateTimeSeconds === dateTimeSeconds) continue; + if (lastProgramDateTimeSeconds === null && this.segments.length > 0) { + const lastSegment = last(this.segments); + const offset = dateTimeSeconds - (lastSegment.timestamp + lastSegment.duration); + for (const segment of this.segments) { + segment.unixEpochTimestamp = segment.timestamp + offset; + if (offsetTimestampsByDateTime) segment.timestamp = segment.unixEpochTimestamp; + } + } + lastProgramDateTimeSeconds = dateTimeSeconds; + accumulatedUnixTime = dateTimeSeconds; + if (offsetTimestampsByDateTime) accumulatedTime = dateTimeSeconds; + } else if (line === "#EXT-X-DISCONTINUITY") currentFirstSegment = null; + else if (line.startsWith("#EXT-X-TARGETDURATION:")) { + const value = line.slice(TAG_TARGETDURATION.length); + const duration = Number(value); + if (!Number.isFinite(duration) || duration < 0) throw new Error(`Invalid EXT-X-TARGETDURATION value '${value}'.`); + this.refreshInterval = duration; + targetDuration = duration; + } else if (line === "#EXT-X-ENDLIST") { + this.streamHasEnded = true; + break; + } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:")) { + if (line.slice(21).toLowerCase() === "vod") this.streamHasEnded = true; + } + } + if (!headerRead) throw new Error("Invalid M3U8 file; no #EXTM3U header."); + } + async getFirstSegment() { + if (this.segments.length === 0) await this.runUpdateSegments(); + return this.segments[0] ?? null; + } + async getSegmentAt(timestamp, options) { + if (this.segments.length === 0) await this.runUpdateSegments(); + let isLazy = !!options.skipLiveWait && this.getRemainingWaitTimeMs() > 0; + while (true) { + const index = binarySearchLessOrEqual(this.segments, timestamp, (x) => x.timestamp); + if (index === -1) return null; + if (index < this.segments.length - 1 || this.streamHasEnded || isLazy) return this.segments[index]; + const segment = this.segments[index]; + if (timestamp < segment.timestamp + segment.duration) return segment; + await this.runUpdateSegments(); + if (options.skipLiveWait) isLazy = true; + } + } + async getNextSegment(segment, options) { + const index = this.segments.indexOf(segment); + assert(index !== -1); + const nextIndex = index + 1; + let isLazy = !!options.skipLiveWait && this.getRemainingWaitTimeMs() > 0; + while (true) { + if (nextIndex < this.segments.length) return this.segments[nextIndex]; + if (this.streamHasEnded || isLazy) return null; + await this.runUpdateSegments(); + if (options.skipLiveWait) isLazy = true; + } + } + async getPreviousSegment(segment) { + const index = this.segments.indexOf(segment); + assert(index !== -1); + return this.segments[index - 1] ?? null; + } + getInputForSegment(segment) { + const hlsSegment = segment; + const cacheEntry = this.inputCache.find((x) => x.segment === hlsSegment); + if (cacheEntry) { + cacheEntry.age = this.nextInputCacheAge++; + return cacheEntry.input; + } + let initInput = null; + if (hlsSegment.initSegment || hlsSegment.firstSegment) initInput = this.getInputForSegment(hlsSegment.initSegment ?? hlsSegment.firstSegment); + const formatOptions = { + ...this.input._formatOptions, + isobmff: { + ...this.input._formatOptions.isobmff, + resolveKeyId: this.input._formatOptions.isobmff?.resolveKeyId && ((options) => { + if (!hlsSegment.encryption || !(hlsSegment.encryption.method === "SAMPLE-AES" || hlsSegment.encryption.method === "SAMPLE-AES-CTR") || !hlsSegment.encryption.psshBox) return this.input._formatOptions.isobmff.resolveKeyId(options); + let psshBoxes = options.psshBoxes; + const { psshBox } = hlsSegment.encryption; + if ((psshBox.keyIds === null || psshBox.keyIds.includes(options.keyId)) && !psshBoxes.some((x) => psshBoxesAreEqual(x, psshBox))) psshBoxes = [...psshBoxes, psshBox]; + return this.input._formatOptions.isobmff.resolveKeyId({ + ...options, + psshBoxes + }); + }) + } + }; + const input = new Input({ + source: new CustomPathedSource(hlsSegment.location.path, async (request) => { + assert(request.isRoot); + const proxiedRequest = { + ...request, + isRoot: false + }; + let ref; + const needsSlice = hlsSegment.location.offset > 0 || hlsSegment.location.length !== null; + if (!hlsSegment.encryption || hlsSegment.encryption.method === "SAMPLE-AES" || hlsSegment.encryption.method === "SAMPLE-AES-CTR") { + ref = await this.input._getSourceCached(proxiedRequest); + if (needsSlice) { + const sliceRef = ref.source.slice(hlsSegment.location.offset, hlsSegment.location.length ?? void 0).ref(); + ref.free(); + ref = sliceRef; + } + } else if (hlsSegment.encryption.method === "AES-128") { + const encryption = hlsSegment.encryption; + assert(encryption.iv); + let ciphertextRef = await this.input._getSourceCached(proxiedRequest); + if (needsSlice) { + const sliceRef = ciphertextRef.source.slice(hlsSegment.location.offset, hlsSegment.location.length ?? void 0).ref(); + ciphertextRef.free(); + ciphertextRef = sliceRef; + } + ref = new ReadableStreamSource(createAes128CbcDecryptStream(new Reader(ciphertextRef.source), async () => { + const env_2 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const keySlice = await new Reader(__addDisposableResource$3(env_2, await this.input._getSourceCached({ + path: encryption.keyUri, + isRoot: false + }, 2), false).source).requestSlice(0, 16); + if (!keySlice) throw new Error("Invalid AES-128 key; expected at least 16 bytes of data."); + return { + key: readBytes(keySlice, 16), + iv: encryption.iv + }; + } catch (e_2) { + env_2.error = e_2; + env_2.hasError = true; + } finally { + __disposeResources$3(env_2); + } + }, () => { + ciphertextRef.free(); + })).ref(); + } else assert(false); + return ref; + }), + formats: this.input._formats.filter((x) => !(x instanceof HlsInputFormat)), + initInput: initInput ?? void 0, + formatOptions + }); + input._onFormatDetermined = (format) => { + if ((hlsSegment.encryption?.method === "SAMPLE-AES" || hlsSegment.encryption?.method === "SAMPLE-AES-CTR") && !format._isIsobmff) throw new Error("The SAMPLE-AES and SAMPLE-AES-CTR encryption methods are currently only supported for ISOBMFF files."); + }; + this.inputCache.push({ + segment: hlsSegment, + input, + age: this.nextInputCacheAge++ + }); + if (this.inputCache.length > 4) { + const minAgeIndex = arrayArgmin(this.inputCache, (x) => x.age); + assert(minAgeIndex !== -1); + this.inputCache.splice(minAgeIndex, 1); + } + return input; + } + async getLiveRefreshInterval() { + if (this.getRemainingWaitTimeMs() === 0) await this.runUpdateSegments(); + return this.streamHasEnded ? null : this.refreshInterval; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/hls/hls-demuxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var HlsDemuxer = class extends Demuxer { + constructor(input) { + super(input); + this.metadataPromise = null; + this.trackBackings = null; + this.internalTracks = null; + this.segmentedInputs = []; + this.hasMasterPlaylist = true; + this.masterPlaylistVariables = null; + } + readMetadata() { + return this.metadataPromise ??= (async () => { + assert(this.input._rootSource instanceof PathedSource); + const slice = await this.input._reader.requestEntireFile(); + assert(slice); + const lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine }); + const { rootPath } = this.input._rootSource; + const variables = new HlsPlaylistVariables(rootPath, null); + const variantStreams = []; + const mediaTags = []; + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith("#EXT-X-DEFINE:")) variables.define(line.slice(TAG_DEFINE.length)); + else if (line.startsWith("#EXT-X-STREAM-INF:")) { + const streamInfLineNumber = i; + let playlistPath = lines[++i]; + if (playlistPath === void 0) throw new Error("Incorrect M3U8 file; a line must follow the #EXT-X-STREAM-INF tag."); + playlistPath = variables.substitute(playlistPath); + const fullPath = joinPaths(rootPath, playlistPath); + const attributes = new AttributeList(line.slice(TAG_STREAM_INF.length), variables); + if (attributes.getAsNumber("bandwidth") === null) throw new Error("Invalid M3U8 file; #EXT-X-STREAM-INF tag requires a BANDWIDTH attribute with a valid numerical value."); + variantStreams.push({ + fullPath, + attributes, + lineNumber: streamInfLineNumber, + hasOnlyKeyPackets: false + }); + } else if (line.startsWith("#EXT-X-I-FRAME-STREAM-INF:")) { + const attributes = new AttributeList(line.slice(TAG_I_FRAME_STREAM_INF.length), variables); + const playlistPath = attributes.get("uri"); + if (playlistPath === null) throw new Error("Invalid M3U8 file; #EXT-X-I-FRAME-STREAM-INF tag requires a URI attribute."); + if (attributes.getAsNumber("bandwidth") === null) throw new Error("Invalid M3U8 file; #EXT-X-I-FRAME-STREAM-INF tag requires a BANDWIDTH attribute with a valid numerical value."); + const fullPath = joinPaths(rootPath, playlistPath); + variantStreams.push({ + fullPath, + attributes, + lineNumber: i, + hasOnlyKeyPackets: true + }); + } else if (line.startsWith("#EXT-X-MEDIA:")) { + const attributes = new AttributeList(line.slice(TAG_MEDIA.length), variables); + if (attributes.get("type") === null) throw new Error("Invalid M3U8 file; #EXT-X-MEDIA tag requires a TYPE attribute."); + if (attributes.get("group-id") === null) throw new Error("Invalid M3U8 file; #EXT-X-MEDIA tag requires a GROUP-ID attribute."); + let fullPath = null; + const uri = attributes.get("uri"); + if (uri !== null) fullPath = joinPaths(rootPath, uri); + mediaTags.push({ + fullPath, + attributes, + lineNumber: i + }); + } else if (line === "#EXT-X-I-FRAMES-ONLY") {} else if (line.startsWith("#EXTINF:")) { + const segmentedInput = new HlsSegmentedInput(this, rootPath, null, lines); + this.segmentedInputs = [segmentedInput]; + this.hasMasterPlaylist = false; + this.trackBackings = await segmentedInput.getTrackBackings(); + return; + } + } + this.masterPlaylistVariables = variables; + const internalTracksByVariant = await Promise.all(variantStreams.map(async (variantStream, i) => { + const result = []; + const codecsList = variantStream.attributes.get("codecs"); + let codecStrings; + if (codecsList) codecStrings = codecsList.split(",").map((x) => x.trim()); + else { + const trackBackings = await this.getSegmentedInputForPath(variantStream.fullPath).getTrackBackings(); + const tracksWithCodec = await Promise.all(trackBackings.map(async (t) => ({ + track: t, + codec: await t.getCodec() + }))); + codecStrings = await Promise.all(tracksWithCodec.filter((x) => x.codec !== null).map((x) => x.track.getDecoderConfig().then((x) => x.codec))); + } + const videoGroupId = variantStream.attributes.get("video"); + const audioGroupId = variantStream.attributes.get("audio"); + const containsVideoCodecs = codecStrings.some((x) => VIDEO_CODECS.includes(inferCodecFromCodecString(x))); + const containsAudioCodecs = codecStrings.some((x) => AUDIO_CODECS.includes(inferCodecFromCodecString(x))); + if (videoGroupId !== null && !containsVideoCodecs) { + const matchingVideoMediaTag = mediaTags.find((mediaTag) => { + const groupId = mediaTag.attributes.get("group-id"); + const type = mediaTag.attributes.get("type"); + return groupId === videoGroupId && type.toLowerCase() === "video"; + }); + outer: if (matchingVideoMediaTag) { + const uri = matchingVideoMediaTag.attributes.get("uri"); + if (uri === null) break outer; + const fullPath = joinPaths(rootPath, uri); + const videoTrack = (await this.getSegmentedInputForPath(fullPath).getTrackBackings()).find((x) => x.getType() === "video"); + if (!videoTrack || await videoTrack.getCodec() === null) break outer; + const additionalCodecString = await videoTrack.getDecoderConfig().then((x) => x?.codec ?? null); + assert(additionalCodecString !== null); + codecStrings.push(additionalCodecString); + } + } + if (audioGroupId !== null && !containsAudioCodecs) { + const matchingAudioMediaTag = mediaTags.find((tag) => { + const groupId = tag.attributes.get("group-id"); + const type = tag.attributes.get("type"); + return groupId === audioGroupId && type.toLowerCase() === "audio"; + }); + outer: if (matchingAudioMediaTag) { + const uri = matchingAudioMediaTag.attributes.get("uri"); + if (uri === null) break outer; + const fullPath = joinPaths(rootPath, uri); + const audioTrack = (await this.getSegmentedInputForPath(fullPath).getTrackBackings()).find((x) => x.getType() === "audio"); + if (!audioTrack || await audioTrack.getCodec() === null) break outer; + const additionalCodecString = await audioTrack.getDecoderConfig().then((x) => x?.codec ?? null); + assert(additionalCodecString !== null); + codecStrings.push(additionalCodecString); + } + } + codecStrings = [...new Set(codecStrings)]; + let videoCodecString = null; + let audioCodecString = null; + const bandwidth = variantStream.attributes.getAsNumber("bandwidth"); + assert(bandwidth !== null); + const averageBandwidth = variantStream.attributes.getAsNumber("average-bandwidth"); + const name = variantStream.attributes.get("name"); + for (const codecString of codecStrings) { + const inferredCodec = inferCodecFromCodecString(codecString); + if (inferredCodec === null) continue; + if (VIDEO_CODECS.includes(inferredCodec)) { + if (videoCodecString !== null) throw new Error("Unsupported M3U8 file; multiple video codecs found in the CODECS attribute of a variant stream."); + videoCodecString = codecString; + const videoGroupId = variantStream.attributes.get("video"); + if (videoGroupId === null) { + const resolution = variantStream.attributes.get("resolution"); + let width = null; + let height = null; + if (resolution) { + const match = resolution.match(/^(\d+)x(\d+)$/); + if (match) { + width = Number(match[1]); + height = Number(match[2]); + } + } + result.push({ + id: -1, + demuxer: this, + backingTrack: null, + default: true, + autoselect: true, + languageCode: "und", + lineNumber: variantStream.lineNumber, + fullPath: variantStream.fullPath, + fullCodecString: videoCodecString, + pairingMask: 1n << BigInt(i), + peakBitrate: bandwidth, + averageBitrate: averageBandwidth, + name, + hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets, + info: { + type: "video", + width, + height + } + }); + } else for (const mediaTag of mediaTags) { + const groupId = mediaTag.attributes.get("group-id"); + const type = mediaTag.attributes.get("type"); + if (groupId !== videoGroupId || type.toLowerCase() !== "video") continue; + const resolution = mediaTag.attributes.get("resolution") ?? variantStream.attributes.get("resolution"); + let width = null; + let height = null; + if (resolution) { + const match = resolution.match(/^(\d+)x(\d+)$/); + if (match) { + width = Number(match[1]); + height = Number(match[2]); + } + } + result.push({ + id: -1, + demuxer: this, + backingTrack: null, + default: getMediaTagDefault(mediaTag.attributes), + autoselect: getMediaTagDefault(mediaTag.attributes) || getMediaTagAutoselect(mediaTag.attributes), + languageCode: preprocessLanguageCode(mediaTag.attributes.get("language")), + lineNumber: mediaTag.lineNumber, + fullPath: mediaTag.fullPath ?? variantStream.fullPath, + fullCodecString: videoCodecString, + pairingMask: 1n << BigInt(i), + peakBitrate: mediaTag.fullPath === null ? bandwidth : null, + averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null, + name: mediaTag.attributes.get("name"), + hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets, + info: { + type: "video", + width, + height + } + }); + } + } else if (AUDIO_CODECS.includes(inferredCodec)) { + if (audioCodecString !== null) throw new Error("Unsupported M3U8 file; multiple audio codecs found in the CODECS attribute of a variant stream."); + audioCodecString = codecString; + const audioGroupId = variantStream.attributes.get("audio"); + if (audioGroupId === null) { + const channels = variantStream.attributes.get("channels"); + const parsedChannels = channels !== null ? Number(channels.split("/")[0]) : null; + result.push({ + id: -1, + demuxer: this, + backingTrack: null, + default: true, + autoselect: true, + languageCode: "und", + lineNumber: variantStream.lineNumber, + fullPath: variantStream.fullPath, + fullCodecString: audioCodecString, + pairingMask: 1n << BigInt(i), + peakBitrate: bandwidth, + averageBitrate: averageBandwidth, + name, + hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets, + info: { + type: "audio", + numberOfChannels: parsedChannels !== null && Number.isInteger(parsedChannels) && parsedChannels > 0 ? parsedChannels : null + } + }); + } else for (const mediaTag of mediaTags) { + const groupId = mediaTag.attributes.get("group-id"); + const type = mediaTag.attributes.get("type"); + if (groupId !== audioGroupId || type.toLowerCase() !== "audio") continue; + const channels = mediaTag.attributes.get("channels") ?? variantStream.attributes.get("channels"); + const parsedChannels = channels !== null ? Number(channels.split("/")[0]) : null; + result.push({ + id: -1, + demuxer: this, + backingTrack: null, + default: getMediaTagDefault(mediaTag.attributes), + autoselect: getMediaTagDefault(mediaTag.attributes) || getMediaTagAutoselect(mediaTag.attributes), + languageCode: preprocessLanguageCode(mediaTag.attributes.get("language")), + lineNumber: mediaTag.lineNumber, + fullPath: mediaTag.fullPath ?? variantStream.fullPath, + fullCodecString: audioCodecString, + pairingMask: 1n << BigInt(i), + peakBitrate: mediaTag.fullPath === null ? bandwidth : null, + averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null, + name: mediaTag.attributes.get("name"), + hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets, + info: { + type: "audio", + numberOfChannels: parsedChannels !== null && Number.isInteger(parsedChannels) && parsedChannels > 0 ? parsedChannels : null + } + }); + } + } + } + return result; + })); + const internalTracks = []; + const addInternalTrack = (track) => { + const existingTrack = internalTracks.find((x) => x.fullPath === track.fullPath && x.info.type === track.info.type); + if (existingTrack) { + existingTrack.pairingMask |= track.pairingMask; + existingTrack.default ||= track.default; + existingTrack.autoselect ||= track.autoselect; + existingTrack.lineNumber = Math.min(existingTrack.lineNumber, track.lineNumber); + if (track.peakBitrate !== null) existingTrack.peakBitrate = Math.max(existingTrack.peakBitrate ?? -Infinity, track.peakBitrate); + if (track.averageBitrate !== null) existingTrack.averageBitrate = Math.max(existingTrack.averageBitrate ?? -Infinity, track.averageBitrate); + if (existingTrack.languageCode === "und") existingTrack.languageCode = track.languageCode; + } else { + track.id = internalTracks.length + 1; + internalTracks.push(track); + } + }; + for (const variantInternalTracks of internalTracksByVariant) for (const trackEntry of variantInternalTracks) addInternalTrack(trackEntry); + internalTracks.sort((a, b) => a.lineNumber - b.lineNumber); + this.trackBackings = []; + for (const internalTrack of internalTracks) if (internalTrack.info.type === "video") this.trackBackings.push(new HlsInputVideoTrackBacking(internalTrack)); + else this.trackBackings.push(new HlsInputAudioTrackBacking(internalTrack)); + this.internalTracks = internalTracks; + })(); + } + async getTrackBackings() { + await this.readMetadata(); + assert(this.trackBackings); + return this.trackBackings; + } + getSegmentedInputForPath(path) { + let segmentedInput = this.segmentedInputs.find((x) => x.path === path); + if (segmentedInput) return segmentedInput; + let decls = null; + if (this.internalTracks) decls = this.internalTracks.filter((x) => x.fullPath === path).map((x) => ({ + id: x.id, + type: x.info.type + })); + segmentedInput = new HlsSegmentedInput(this, path, decls, null); + this.segmentedInputs.push(segmentedInput); + return segmentedInput; + } + async getMetadataTags() { + return {}; + } + async getMimeType() { + return HLS_MIME_TYPE; + } + dispose() { + if (this.segmentedInputs) { + for (const segInput of this.segmentedInputs) segInput.dispose(); + this.segmentedInputs.length = 0; + } + } +}; +var HlsInputTrackBacking = class { + constructor(internalTrack) { + this.internalTrack = internalTrack; + this.hydrationPromise = null; + } + hydrate() { + return this.hydrationPromise ??= (async () => { + const segmentedInput = this.internalTrack.demuxer.getSegmentedInputForPath(this.internalTrack.fullPath); + let trackBacking = null; + const matchingType = (await segmentedInput.getTrackBackings()).filter((x) => x.getType() === this.getType()); + if (matchingType.length === 1) trackBacking = matchingType[0]; + else if (this instanceof HlsInputVideoTrackBacking) { + for (const backing of matchingType) if (await backing.getCodec() === this.getCodec()) { + trackBacking = backing; + break; + } + } else { + assert(this instanceof HlsInputAudioTrackBacking); + for (const backing of matchingType) if (await backing.getCodec() === this.getCodec()) { + trackBacking = backing; + break; + } + } + if (!trackBacking) throw new Error("Could not find matching track in underlying media data."); + this.internalTrack.backingTrack = trackBacking; + })(); + } + /** If the backing track is already present, delegate synchronously; otherwise, hydrate first. */ + delegate(fn) { + if (this.internalTrack.backingTrack) return fn(); + return this.hydrate().then(fn); + } + getCodec() { + throw new Error("Not implemented on base class."); + } + getDisposition() { + return { + ...DEFAULT_TRACK_DISPOSITION, + default: this.internalTrack.autoselect, + primary: this.internalTrack.default + }; + } + getId() { + return this.internalTrack.id; + } + getPairingMask() { + return this.internalTrack.pairingMask; + } + getInternalCodecId() { + return null; + } + getLanguageCode() { + return this.internalTrack.languageCode; + } + getName() { + return this.internalTrack.name; + } + getNumber() { + assert(this.internalTrack.demuxer.internalTracks); + const trackType = this.internalTrack.info.type; + let number = 0; + for (const track of this.internalTrack.demuxer.internalTracks) { + if (track.info.type === trackType) number++; + if (track === this.internalTrack) break; + } + return number; + } + getTimeResolution() { + return this.delegate(() => this.internalTrack.backingTrack.getTimeResolution()); + } + isRelativeToUnixEpoch() { + return this.delegate(() => this.internalTrack.backingTrack.isRelativeToUnixEpoch()); + } + getUnixTimeForTimestamp(timestamp) { + return this.delegate(() => this.internalTrack.backingTrack.getUnixTimeForTimestamp(timestamp)); + } + getBitrate() { + return this.internalTrack.peakBitrate; + } + getAverageBitrate() { + return this.internalTrack.averageBitrate; + } + async getDurationFromMetadata(options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getDurationFromMetadata(options); + } + async getLiveRefreshInterval() { + await this.hydrate(); + return this.internalTrack.backingTrack.getLiveRefreshInterval(); + } + getHasOnlyKeyPackets() { + return this.internalTrack.hasOnlyKeyPackets || null; + } + async getFirstPacket(options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getFirstPacket(options); + } + async getPacket(timestamp, options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getPacket(timestamp, options); + } + async getKeyPacket(timestamp, options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getKeyPacket(timestamp, options); + } + async getNextPacket(packet, options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getNextPacket(packet, options); + } + async getNextKeyPacket(packet, options) { + await this.hydrate(); + return this.internalTrack.backingTrack.getNextKeyPacket(packet, options); + } +}; +var HlsInputVideoTrackBacking = class extends HlsInputTrackBacking { + constructor(internalTrack) { + super(internalTrack); + } + get backingVideoTrack() { + return this.internalTrack.backingTrack; + } + getType() { + return "video"; + } + getCodec() { + return inferCodecFromCodecString(this.internalTrack.fullCodecString); + } + getCodedWidth() { + return this.delegate(() => this.backingVideoTrack.getCodedWidth()); + } + getCodedHeight() { + return this.delegate(() => this.backingVideoTrack.getCodedHeight()); + } + getSquarePixelWidth() { + return this.delegate(() => this.backingVideoTrack.getSquarePixelWidth()); + } + getSquarePixelHeight() { + return this.delegate(() => this.backingVideoTrack.getSquarePixelHeight()); + } + getMetadataDisplayWidth() { + if (this.backingVideoTrack) return null; + return this.internalTrack.info.width; + } + getMetadataDisplayHeight() { + if (this.backingVideoTrack) return null; + return this.internalTrack.info.height; + } + getTransformationMatrix() { + return this.delegate(() => this.backingVideoTrack.getTransformationMatrix()); + } + async getColorSpace() { + await this.hydrate(); + return this.backingVideoTrack.getColorSpace(); + } + async canBeTransparent() { + await this.hydrate(); + return this.backingVideoTrack.canBeTransparent(); + } + getMetadataCodecParameterString() { + if (this.backingVideoTrack) return null; + return this.internalTrack.fullCodecString; + } + async getDecoderConfig() { + await this.hydrate(); + return this.backingVideoTrack.getDecoderConfig(); + } +}; +var HlsInputAudioTrackBacking = class extends HlsInputTrackBacking { + constructor(internalTrack) { + super(internalTrack); + } + get backingAudioTrack() { + return this.internalTrack.backingTrack; + } + getType() { + return "audio"; + } + getCodec() { + return inferCodecFromCodecString(this.internalTrack.fullCodecString); + } + getNumberOfChannels() { + if (this.internalTrack.info.numberOfChannels !== null) return this.internalTrack.info.numberOfChannels; + return this.delegate(() => this.backingAudioTrack.getNumberOfChannels()); + } + getSampleRate() { + return this.delegate(() => this.backingAudioTrack.getSampleRate()); + } + getMetadataCodecParameterString() { + if (this.backingAudioTrack) return null; + return this.internalTrack.fullCodecString; + } + async getDecoderConfig() { + await this.hydrate(); + return this.backingAudioTrack.getDecoderConfig(); + } +}; +var getMediaTagDefault = (attributes) => { + const value = attributes.get("default"); + if (value === null) return false; + const normalized = value.toUpperCase(); + if (normalized === "YES") return true; + if (normalized === "NO") return false; + throw new Error(`Invalid M3U8 file; #EXT-X-MEDIA DEFAULT attribute must be YES or NO, got "${value}".`); +}; +var getMediaTagAutoselect = (attributes) => { + const value = attributes.get("autoselect"); + if (value === null) return false; + const normalized = value.toUpperCase(); + if (normalized === "YES") return true; + if (normalized === "NO") return false; + throw new Error(`Invalid M3U8 file; #EXT-X-MEDIA AUTOSELECT attribute must be YES or NO, got "${value}".`); +}; +var preprocessLanguageCode = (code) => { + if (code === null) return "und"; + const languageSubtag = code.split("-")[0]; + if (!languageSubtag) return "und"; + return languageSubtag; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/input-format.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Base class representing an input media file format. +* @group Input formats +* @public +*/ +var InputFormat = class { + constructor() { + /** + * Provided for tree-shakable checking. + * @internal + */ + this._isIsobmff = false; + } +}; +/** +* Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files. +* +* This format can make use of {@link InputOptions.initInput}. When the file contents are fragmented but no track +* initialization info is provided (no `moov` atom), then it must be provided via `initInput`. +* +* @group Input formats +* @public +*/ +var IsobmffInputFormat = class extends InputFormat { + constructor() { + super(...arguments); + /** @internal */ + this._isIsobmff = true; + } + /** @internal */ + async _getMajorBrand(input) { + let slice = input._reader.requestSlice(0, 12); + if (isThenable(slice)) slice = await slice; + if (!slice) return null; + slice.skip(4); + const fourCc = readAscii(slice, 4); + if (fourCc !== "ftyp" && fourCc !== "styp") return null; + return readAscii(slice, 4); + } + /** @internal */ + _createDemuxer(input) { + return new IsobmffDemuxer(input); + } +}; +/** +* MPEG-4 Part 14 (MP4) file format. +* +* Do not instantiate this class; use the {@link MP4} singleton instead. +* +* @group Input formats +* @public +*/ +var Mp4InputFormat = class extends IsobmffInputFormat { + /** @internal */ + async _canReadInput(input) { + const majorBrand = await this._getMajorBrand(input); + if (majorBrand !== null) return majorBrand !== "qt "; + let pos = 0; + for (let iter = 0; iter < 10; iter++) { + let slice = input._reader.requestSlice(pos, 8); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + let size = readU32Be(slice); + let headerSize = 8; + if (size === 1) { + let sizeExtensionSlice = input._reader.requestSlice(pos + 8, 8); + if (isThenable(sizeExtensionSlice)) sizeExtensionSlice = await sizeExtensionSlice; + if (!sizeExtensionSlice) return false; + size = readU64Be(sizeExtensionSlice); + headerSize = 16; + } + if (size < headerSize) return false; + const fourCc = readAscii(slice, 4); + if (fourCc === "moof" || fourCc === "sidx") return true; + else if (fourCc === "emsg" || fourCc === "prft" || fourCc === "free") pos += size; + else return false; + } + return false; + } + get name() { + return "MP4"; + } + get mimeType() { + return "video/mp4"; + } +}; +/** +* QuickTime File Format (QTFF), often called MOV. +* +* Do not instantiate this class; use the {@link QTFF} singleton instead. +* +* @group Input formats +* @public +*/ +var QuickTimeInputFormat = class extends IsobmffInputFormat { + /** @internal */ + async _canReadInput(input) { + return await this._getMajorBrand(input) === "qt "; + } + get name() { + return "QuickTime File Format"; + } + get mimeType() { + return "video/quicktime"; + } +}; +/** +* Matroska file format. +* +* Do not instantiate this class; use the {@link MATROSKA} singleton instead. +* +* @group Input formats +* @public +*/ +var MatroskaInputFormat = class extends InputFormat { + /** @internal */ + async isSupportedEBMLOfDocType(input, desiredDocType) { + let headerSlice = input._reader.requestSlice(0, 16); + if (isThenable(headerSlice)) headerSlice = await headerSlice; + if (!headerSlice) return false; + const varIntSize = readVarIntSize(headerSlice); + if (varIntSize === null) return false; + if (varIntSize < 1 || varIntSize > 8) return false; + if (readUnsignedInt(headerSlice, varIntSize) !== EBMLId.EBML) return false; + const dataSize = readElementSize(headerSlice); + if (typeof dataSize !== "number") return false; + let dataSlice = input._reader.requestSlice(headerSlice.filePos, dataSize); + if (isThenable(dataSlice)) dataSlice = await dataSlice; + if (!dataSlice) return false; + const startPos = headerSlice.filePos; + while (dataSlice.filePos <= startPos + dataSize - 2) { + const header = readElementHeader(dataSlice); + if (!header) break; + const { id, size } = header; + const dataStartPos = dataSlice.filePos; + if (size === void 0) return false; + switch (id) { + case EBMLId.EBMLVersion: + if (readUnsignedInt(dataSlice, size) !== 1) return false; + break; + case EBMLId.EBMLReadVersion: + if (readUnsignedInt(dataSlice, size) !== 1) return false; + break; + case EBMLId.DocType: + if (readAsciiString(dataSlice, size) !== desiredDocType) return false; + break; + case EBMLId.DocTypeVersion: + if (readUnsignedInt(dataSlice, size) > 4) return false; + break; + } + dataSlice.filePos = dataStartPos + size; + } + return true; + } + /** @internal */ + _canReadInput(input) { + return this.isSupportedEBMLOfDocType(input, "matroska"); + } + /** @internal */ + _createDemuxer(input) { + return new MatroskaDemuxer(input); + } + get name() { + return "Matroska"; + } + get mimeType() { + return "video/x-matroska"; + } +}; +/** +* WebM file format, based on Matroska. +* +* Do not instantiate this class; use the {@link WEBM} singleton instead. +* +* @group Input formats +* @public +*/ +var WebMInputFormat = class extends MatroskaInputFormat { + /** @internal */ + _canReadInput(input) { + return this.isSupportedEBMLOfDocType(input, "webm"); + } + get name() { + return "WebM"; + } + get mimeType() { + return "video/webm"; + } +}; +/** +* MP3 file format. +* +* Do not instantiate this class; use the {@link MP3} singleton instead. +* +* @group Input formats +* @public +*/ +var Mp3InputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let currentPos = 0; + while (true) { + let slice = input._reader.requestSlice(currentPos, 10); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const id3V2Header = readId3V2Header(slice); + if (!id3V2Header) break; + currentPos = slice.filePos + id3V2Header.size; + } + const firstResult = await readNextMp3FrameHeader(input._reader, currentPos, currentPos + 4096); + if (!firstResult) return false; + const firstHeader = firstResult.header; + const xingOffset = getXingOffset(firstHeader.mpegVersionId, firstHeader.channel); + let slice = input._reader.requestSlice(firstResult.startPos + xingOffset, 4); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + const word = readU32Be(slice); + if (word === 1483304551 || word === 1231971951) return true; + currentPos = firstResult.startPos + firstResult.header.totalSize; + const secondResult = await readNextMp3FrameHeader(input._reader, currentPos, currentPos + 4); + if (!secondResult) return false; + const secondHeader = secondResult.header; + if (firstHeader.channel !== secondHeader.channel || firstHeader.sampleRate !== secondHeader.sampleRate) return false; + return true; + } + /** @internal */ + _createDemuxer(input) { + return new Mp3Demuxer(input); + } + get name() { + return "MP3"; + } + get mimeType() { + return "audio/mpeg"; + } +}; +/** +* WAVE file format, based on RIFF. +* +* Do not instantiate this class; use the {@link WAVE} singleton instead. +* +* @group Input formats +* @public +*/ +var WaveInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let slice = input._reader.requestSlice(0, 12); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + const riffType = readAscii(slice, 4); + if (riffType !== "RIFF" && riffType !== "RIFX" && riffType !== "RF64") return false; + slice.skip(4); + return readAscii(slice, 4) === "WAVE"; + } + /** @internal */ + _createDemuxer(input) { + return new WaveDemuxer(input); + } + get name() { + return "WAVE"; + } + get mimeType() { + return "audio/wav"; + } +}; +/** +* Ogg file format. +* +* Do not instantiate this class; use the {@link OGG} singleton instead. +* +* @group Input formats +* @public +*/ +var OggInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let slice = input._reader.requestSlice(0, 4); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + return readAscii(slice, 4) === "OggS"; + } + /** @internal */ + _createDemuxer(input) { + return new OggDemuxer(input); + } + get name() { + return "Ogg"; + } + get mimeType() { + return "application/ogg"; + } +}; +/** +* FLAC file format. +* +* Do not instantiate this class; use the {@link FLAC} singleton instead. +* +* @group Input formats +* @public +*/ +var FlacInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let currentPos = 0; + while (true) { + let slice = input._reader.requestSlice(currentPos, 10); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const id3V2Header = readId3V2Header(slice); + if (!id3V2Header) break; + currentPos = slice.filePos + id3V2Header.size; + } + let slice = input._reader.requestSlice(currentPos, 4); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + return readAscii(slice, 4) === "fLaC"; + } + get name() { + return "FLAC"; + } + get mimeType() { + return "audio/flac"; + } + /** @internal */ + _createDemuxer(input) { + return new FlacDemuxer(input); + } +}; +/** +* ADTS file format. +* +* Do not instantiate this class; use the {@link ADTS} singleton instead. +* +* @group Input formats +* @public +*/ +var AdtsInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let currentPos = 0; + while (true) { + let slice = input._reader.requestSlice(currentPos, 10); + if (isThenable(slice)) slice = await slice; + if (!slice) break; + const id3V2Header = readId3V2Header(slice); + if (!id3V2Header) break; + currentPos = slice.filePos + id3V2Header.size; + } + let slice = input._reader.requestSliceRange(currentPos, 7, 9); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + const firstHeader = readAdtsFrameHeader(slice); + if (!firstHeader) return false; + currentPos += firstHeader.frameLength; + slice = input._reader.requestSliceRange(currentPos, 7, 9); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + const secondHeader = readAdtsFrameHeader(slice); + if (!secondHeader) return false; + return firstHeader.objectType === secondHeader.objectType && firstHeader.samplingFrequencyIndex === secondHeader.samplingFrequencyIndex && firstHeader.channelConfiguration === secondHeader.channelConfiguration; + } + /** @internal */ + _createDemuxer(input) { + return new AdtsDemuxer(input); + } + get name() { + return "ADTS"; + } + get mimeType() { + return "audio/aac"; + } +}; +/** +* MPEG Transport Stream (MPEG-TS) file format. +* +* This format can make use of {@link InputOptions.initInput} to initialize track information even when no +* initialization information is provided for the track, for example because it has no key frames. In this case, tracks +* are matched to each other based on their PID. +* +* Do not instantiate this class; use the {@link MPEG_TS} singleton instead. +* +* @group Input formats +* @public +*/ +var MpegTsInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + const lengthToCheck = 205; + let slice = input._reader.requestSlice(0, lengthToCheck); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + const bytes = readBytes(slice, lengthToCheck); + if (bytes[0] === 71 && bytes[188] === 71) return true; + else if (bytes[0] === 71 && bytes[204] === 71) return true; + else if (bytes[4] === 71 && bytes[196] === 71) return true; + return false; + } + /** @internal */ + _createDemuxer(input) { + return new MpegTsDemuxer(input); + } + get name() { + return "MPEG Transport Stream"; + } + get mimeType() { + return "video/MP2T"; + } +}; +/** +* Media described using the HTTP Live Streaming (HLS) protocol, with playlists in the M3U8 format. +* +* Do not instantiate this class; use the {@link HLS} singleton instead. +* +* @group Input formats +* @public +*/ +var HlsInputFormat = class extends InputFormat { + /** @internal */ + async _canReadInput(input) { + let slice = input._reader.requestSlice(0, 7); + if (isThenable(slice)) slice = await slice; + if (!slice) return false; + if (!(readAscii(slice, 7) === "#EXTM3U")) return false; + if (!(input._rootSource instanceof PathedSource)) throw new TypeError("HLS inputs require `InputOptions.source` to be a PathedSource or a ref to one."); + input._rootSource._usedForHls = true; + return true; + } + /** @internal */ + _createDemuxer(input) { + return new HlsDemuxer(input); + } + get name() { + return "HTTP Live Streaming (HLS)"; + } + get mimeType() { + return HLS_MIME_TYPE; + } +}; +/** +* MP4 input format singleton. +* @group Input formats +* @public +*/ +var MP4 = /* #__PURE__ */ new Mp4InputFormat(); +/** +* QuickTime File Format input format singleton. +* @group Input formats +* @public +*/ +var QTFF = /* #__PURE__ */ new QuickTimeInputFormat(); +/** +* Matroska input format singleton. +* @group Input formats +* @public +*/ +var MATROSKA = /* #__PURE__ */ new MatroskaInputFormat(); +/** +* WebM input format singleton. +* @group Input formats +* @public +*/ +var WEBM = /* #__PURE__ */ new WebMInputFormat(); +/** +* MP3 input format singleton. +* @group Input formats +* @public +*/ +var MP3 = /* #__PURE__ */ new Mp3InputFormat(); +/** +* WAVE input format singleton. +* @group Input formats +* @public +*/ +var WAVE = /* #__PURE__ */ new WaveInputFormat(); +/** +* Ogg input format singleton. +* @group Input formats +* @public +*/ +var OGG = /* #__PURE__ */ new OggInputFormat(); +/** +* ADTS input format singleton. +* @group Input formats +* @public +*/ +var ADTS = /* #__PURE__ */ new AdtsInputFormat(); +/** +* FLAC input format singleton. +* @group Input formats +* @public +*/ +var FLAC = /* #__PURE__ */ new FlacInputFormat(); +/** +* MPEG-TS input format singleton. +* @group Input formats +* @public +*/ +var MPEG_TS = /* #__PURE__ */ new MpegTsInputFormat(); +/** +* HLS input format singleton. +* @group Input formats +* @public +*/ +var HLS = /* #__PURE__ */ new HlsInputFormat(); +/** +* List of all input format singletons. If you don't need to support all input formats, you should specify the +* formats individually for better tree shaking. +* @group Input formats +* @public +*/ +var ALL_FORMATS = [ + HLS, + MP4, + QTFF, + MATROSKA, + WEBM, + WAVE, + OGG, + FLAC, + MP3, + ADTS, + MPEG_TS +]; +/** +* List of input formats required for playback of typical HLS manifests. Includes HLS itself as well as the typical +* segment formats: MPEG Transport Stream (.ts), MP4 (CMAF), ADTS (.aac) and MP3. +* @group Input formats +* @public +*/ +var HLS_FORMATS = [ + HLS, + MP4, + QTFF, + MP3, + ADTS, + MPEG_TS +]; +var validateInputFormatOptions = (options, prefix) => { + if (!options || typeof options !== "object") throw new TypeError(`${prefix}, when provided, must be an object.`); + if (options.isobmff !== void 0) { + if (!options.isobmff || typeof options.isobmff !== "object") throw new TypeError(`${prefix}.isobmff, when provided, must be an object.`); + if (options.isobmff.resolveKeyId !== void 0 && typeof options.isobmff.resolveKeyId !== "function") throw new TypeError(`${prefix}.isobmff.resolveKeyId, when provided, must be a function.`); + } + if (options.hls !== void 0) { + if (!options.hls || typeof options.hls !== "object") throw new TypeError(`${prefix}.hls, when provided, must be an object.`); + if (options.hls.offsetTimestampsByDateTime !== void 0 && typeof options.hls.offsetTimestampsByDateTime !== "boolean") throw new TypeError(`${prefix}.hls.offsetTimestampsByDateTime, when provided, must be a boolean.`); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/decode.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var canDecodeVideoMemo = /* @__PURE__ */ new Map(); +var canDecodeAudioMemo = /* @__PURE__ */ new Map(); +var validateVideoDecodingConfig = (codec, options) => { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.codec !== void 0 && typeof options.codec !== "string") throw new TypeError("options.codec, when provided, must be a string."); + if (options.codec !== void 0 && inferCodecFromCodecString(options.codec) !== codec) throw new TypeError(`options.codec, when provided, must match the specified codec (${codec}).`); + if (options.codedWidth !== void 0 && (!Number.isInteger(options.codedWidth) || options.codedWidth <= 0)) throw new TypeError("options.codedWidth, when provided, must be a positive integer."); + if (options.codedHeight !== void 0 && (!Number.isInteger(options.codedHeight) || options.codedHeight <= 0)) throw new TypeError("options.codedHeight, when provided, must be a positive integer."); + if (options.displayAspectWidth !== void 0 && (!Number.isInteger(options.displayAspectWidth) || options.displayAspectWidth <= 0)) throw new TypeError("options.displayAspectWidth, when provided, must be a positive integer."); + if (options.displayAspectHeight !== void 0 && (!Number.isInteger(options.displayAspectHeight) || options.displayAspectHeight <= 0)) throw new TypeError("options.displayAspectHeight, when provided, must be a positive integer."); + if (options.description !== void 0 && !isAllowSharedBufferSource(options.description)) throw new TypeError("options.description, when provided, must be a buffer source."); + if (options.hardwareAcceleration !== void 0 && ![ + "no-preference", + "prefer-hardware", + "prefer-software" + ].includes(options.hardwareAcceleration)) throw new TypeError("options.hardwareAcceleration, when provided, must be 'no-preference', 'prefer-hardware' or 'prefer-software'."); + if (options.optimizeForLatency !== void 0 && typeof options.optimizeForLatency !== "boolean") throw new TypeError("options.optimizeForLatency, when provided, must be a boolean."); +}; +var validateAudioDecodingConfig = (codec, options) => { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.codec !== void 0 && typeof options.codec !== "string") throw new TypeError("options.codec, when provided, must be a string."); + if (options.codec !== void 0 && inferCodecFromCodecString(options.codec) !== codec) throw new TypeError(`options.codec, when provided, must match the specified codec (${codec}).`); + if (options.numberOfChannels !== void 0 && (!Number.isInteger(options.numberOfChannels) || options.numberOfChannels <= 0)) throw new TypeError("options.numberOfChannels, when provided, must be a positive integer."); + if (options.sampleRate !== void 0 && (!Number.isInteger(options.sampleRate) || options.sampleRate <= 0)) throw new TypeError("options.sampleRate, when provided, must be a positive integer."); + if (options.description !== void 0 && !isAllowSharedBufferSource(options.description)) throw new TypeError("options.description, when provided, must be a buffer source."); +}; +/** +* Checks if the browser is able to decode the given codec. +* @group Decoding +* @public +*/ +var canDecode = (codec) => { + if (VIDEO_CODECS.includes(codec)) return canDecodeVideo(codec); + else if (AUDIO_CODECS.includes(codec)) return canDecodeAudio(codec); + return false; +}; +/** +* Checks if the browser is able to decode the given video codec with the given parameters. +* @group Decoding +* @public +*/ +var canDecodeVideo = async (codec, options = {}) => { + if (!VIDEO_CODECS.includes(codec)) return false; + validateVideoDecodingConfig(codec, options); + const resolvedOptions = { + ...options, + codedWidth: options.codedWidth ?? 1280, + codedHeight: options.codedHeight ?? 720, + codec: options.codec ?? buildVideoCodecString(codec, 1280, 720, 1e6, false) + }; + resolvedOptions.description ??= void 0; + const key = JSON.stringify(resolvedOptions); + const memoized = canDecodeVideoMemo.get(key); + if (memoized) return memoized; + const promise = (async () => { + if (customVideoDecoders.some((x) => x.supports(codec, resolvedOptions))) return true; + if (typeof VideoDecoder === "undefined") return false; + return (await VideoDecoder.isConfigSupported(resolvedOptions)).supported === true; + })(); + canDecodeVideoMemo.set(key, promise); + return promise; +}; +/** +* Checks if the browser is able to decode the given audio codec with the given parameters. +* @group Decoding +* @public +*/ +var canDecodeAudio = async (codec, options = {}) => { + if (!AUDIO_CODECS.includes(codec)) return false; + validateAudioDecodingConfig(codec, options); + const resolvedOptions = { + ...options, + numberOfChannels: options.numberOfChannels ?? 2, + sampleRate: options.sampleRate ?? 48e3, + codec: options.codec ?? buildAudioCodecString(codec, 2, 48e3) + }; + if (resolvedOptions.description === void 0) { + const generatedDescription = guessDescriptionForAudio(resolvedOptions); + if (generatedDescription === false) return false; + resolvedOptions.description = generatedDescription; + } + const key = JSON.stringify(resolvedOptions); + const memoized = canDecodeAudioMemo.get(key); + if (memoized) return memoized; + const promise = (async () => { + if (customAudioDecoders.some((x) => x.supports(codec, resolvedOptions))) return true; + if (PCM_AUDIO_CODECS.includes(codec)) return true; + if (typeof AudioDecoder === "undefined") return false; + return (await AudioDecoder.isConfigSupported(resolvedOptions)).supported === true; + })(); + canDecodeAudioMemo.set(key, promise); + return promise; +}; +/** +* Returns the list of all media codecs that can be decoded by the browser. +* @group Decoding +* @public +*/ +var getDecodableCodecs = async () => { + const [videoCodecs, audioCodecs] = await Promise.all([getDecodableVideoCodecs(), getDecodableAudioCodecs()]); + return [...videoCodecs, ...audioCodecs]; +}; +/** +* Returns the list of all video codecs that can be decoded by the browser. +* @group Decoding +* @public +*/ +var getDecodableVideoCodecs = async (checkedCodecs = VIDEO_CODECS, options) => { + const bools = await Promise.all(checkedCodecs.map((codec) => canDecodeVideo(codec, options))); + return checkedCodecs.filter((_, i) => bools[i]); +}; +/** +* Returns the list of all audio codecs that can be decoded by the browser. +* @group Decoding +* @public +*/ +var getDecodableAudioCodecs = async (checkedCodecs = AUDIO_CODECS, options) => { + const bools = await Promise.all(checkedCodecs.map((codec) => canDecodeAudio(codec, options))); + return checkedCodecs.filter((_, i) => bools[i]); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/sample.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var __addDisposableResource$2 = function(env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); + var dispose, inner; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + if (async) inner = dispose; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + if (inner) dispose = function() { + try { + inner.call(this); + } catch (e) { + return Promise.reject(e); + } + }; + env.stack.push({ + value, + dispose, + async + }); + } else if (async) env.stack.push({ async: true }); + return value; +}; +var __disposeResources$2 = (function(SuppressedError) { + return function(env) { + function fail(e) { + env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + var r, s = 0; + function next() { + while (r = env.stack.pop()) try { + if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); + if (r.dispose) { + var result = r.dispose.call(r.value); + if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { + fail(e); + return next(); + }); + } else s |= 1; + } catch (e) { + fail(e); + } + if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); + if (env.hasError) throw env.error; + } + return next(); + }; +})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}); +polyfillSymbolDispose(); +var lastVideoGcErrorLog = -Infinity; +var lastAudioGcErrorLog = -Infinity; +var finalizationRegistry = null; +if (typeof FinalizationRegistry !== "undefined") finalizationRegistry = new FinalizationRegistry((value) => { + const now = performance.now(); + if (value.type === "video") { + if (now - lastVideoGcErrorLog >= 1e3) { + Logging._error("A VideoSample was garbage collected without first being closed. For proper resource management, make sure to call close() on all your VideoSamples as soon as you're done using them."); + lastVideoGcErrorLog = now; + } + if (typeof VideoFrame !== "undefined" && value.data instanceof VideoFrame) value.data.close(); + } else { + if (now - lastAudioGcErrorLog >= 1e3) { + Logging._error("An AudioSample was garbage collected without first being closed. For proper resource management, make sure to call close() on all your AudioSamples as soon as you're done using them."); + lastAudioGcErrorLog = now; + } + if (typeof AudioData !== "undefined" && value.data instanceof AudioData) value.data.close(); + } +}); +/** +* Abstract base class for custom video sample resources. Implement this class to provide custom backing +* for VideoSample instances. +* @group Samples +* @public +*/ +var VideoSampleResource = class { + constructor() { + /** @internal */ + this._referenceCount = 0; + /** @internal */ + this._lastAllocationBuffer = null; + } +}; +/** +* The list of {@link VideoSample} pixel formats. +* @group Samples +* @public +*/ +var VIDEO_SAMPLE_PIXEL_FORMATS = [ + "I420", + "I420P10", + "I420P12", + "I420A", + "I420AP10", + "I420AP12", + "I422", + "I422P10", + "I422P12", + "I422A", + "I422AP10", + "I422AP12", + "I444", + "I444P10", + "I444P12", + "I444A", + "I444AP10", + "I444AP12", + "NV12", + "RGBA", + "RGBX", + "BGRA", + "BGRX" +]; +var VIDEO_SAMPLE_PIXEL_FORMATS_SET = new Set(VIDEO_SAMPLE_PIXEL_FORMATS); +/** +* Represents a raw, unencoded video sample (frame). Mainly used as an expressive wrapper around WebCodecs API's +* [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame), but can also be used standalone. +* @group Samples +* @public +*/ +var VideoSample = class VideoSample { + /** The width of the frame in pixels. */ + get codedWidth() { + return this.visibleRect.width; + } + /** The height of the frame in pixels. */ + get codedHeight() { + return this.visibleRect.height; + } + /** The display width of the frame in pixels, after aspect ratio adjustment, rotation and flip. */ + get displayWidth() { + return this.rotation % 180 === 0 ? this.squarePixelWidth : this.squarePixelHeight; + } + /** The display height of the frame in pixels, after aspect ratio adjustment, rotation and flip. */ + get displayHeight() { + return this.rotation % 180 === 0 ? this.squarePixelHeight : this.squarePixelWidth; + } + /** The presentation timestamp of the frame in microseconds. */ + get microsecondTimestamp() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp); + } + /** The duration of the frame in microseconds. */ + get microsecondDuration() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration); + } + /** + * Whether this sample uses a pixel format that can hold transparency data. Note that this doesn't necessarily mean + * that the sample is transparent. + */ + get hasAlpha() { + return this.format && this.format.includes("A"); + } + constructor(data, init) { + /** @internal */ + this._closed = false; + if (data instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && data instanceof SharedArrayBuffer || ArrayBuffer.isView(data)) { + if (!init || typeof init !== "object") throw new TypeError("init must be an object."); + if (init.format === void 0 || !VIDEO_SAMPLE_PIXEL_FORMATS_SET.has(init.format)) throw new TypeError("init.format must be one of: " + VIDEO_SAMPLE_PIXEL_FORMATS.join(", ")); + if (!Number.isInteger(init.codedWidth) || init.codedWidth <= 0) throw new TypeError("init.codedWidth must be a positive integer."); + if (!Number.isInteger(init.codedHeight) || init.codedHeight <= 0) throw new TypeError("init.codedHeight must be a positive integer."); + if (init.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(init.rotation)) throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270."); + if (init.flip !== void 0 && typeof init.flip !== "boolean") throw new TypeError("init.flip, when provided, must be a boolean."); + if (!Number.isFinite(init.timestamp)) throw new TypeError("init.timestamp must be a number."); + if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) throw new TypeError("init.duration, when provided, must be a non-negative number."); + if (init.layout !== void 0) { + if (!Array.isArray(init.layout)) throw new TypeError("init.layout, when provided, must be an array."); + for (const plane of init.layout) { + if (!plane || typeof plane !== "object" || Array.isArray(plane)) throw new TypeError("Each entry in init.layout must be an object."); + if (!Number.isInteger(plane.offset) || plane.offset < 0) throw new TypeError("plane.offset must be a non-negative integer."); + if (!Number.isInteger(plane.stride) || plane.stride < 0) throw new TypeError("plane.stride must be a non-negative integer."); + } + } + if (init.visibleRect !== void 0) validateRectangle(init.visibleRect, "init.visibleRect"); + if (init.displayWidth !== void 0 && (!Number.isInteger(init.displayWidth) || init.displayWidth <= 0)) throw new TypeError("init.displayWidth, when provided, must be a positive integer."); + if (init.displayHeight !== void 0 && (!Number.isInteger(init.displayHeight) || init.displayHeight <= 0)) throw new TypeError("init.displayHeight, when provided, must be a positive integer."); + if (init.displayWidth !== void 0 !== (init.displayHeight !== void 0)) throw new TypeError("init.displayWidth and init.displayHeight must be either both provided or both omitted."); + this.format = init.format; + this.rotation = init.rotation ?? 0; + this.flip = init.flip ?? false; + this.timestamp = init.timestamp; + this.duration = init.duration ?? 0; + const layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight); + let colorSpaceInit = init.colorSpace ?? null; + if (colorSpaceInit === null) if (this.format === "RGBA" || this.format === "RGBX" || this.format === "BGRA" || this.format === "BGRX") colorSpaceInit = { + primaries: "bt709", + transfer: "iec61966-2-1", + matrix: "rgb", + fullRange: true + }; + else colorSpaceInit = { + primaries: "bt709", + transfer: "bt709", + matrix: "bt709", + fullRange: false + }; + this.visibleRect = { + left: init.visibleRect?.left ?? 0, + top: init.visibleRect?.top ?? 0, + width: init.visibleRect?.width ?? init.codedWidth, + height: init.visibleRect?.height ?? init.codedHeight + }; + if (init.displayWidth !== void 0) { + this.squarePixelWidth = this.rotation % 180 === 0 ? init.displayWidth : init.displayHeight; + this.squarePixelHeight = this.rotation % 180 === 0 ? init.displayHeight : init.displayWidth; + } else { + this.squarePixelWidth = this.visibleRect.width; + this.squarePixelHeight = this.visibleRect.height; + } + this._data = init._doNotCopy ? toUint8Array(data) : toUint8Array(data).slice(); + this._layout = layout; + this.colorSpace = new VideoSampleColorSpace(colorSpaceInit); + } else if (typeof VideoFrame !== "undefined" && data instanceof VideoFrame) { + if (init?.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(init.rotation)) throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270."); + if (init?.flip !== void 0 && typeof init.flip !== "boolean") throw new TypeError("init.flip, when provided, must be a boolean."); + if (init?.timestamp !== void 0 && !Number.isFinite(init?.timestamp)) throw new TypeError("init.timestamp, when provided, must be a number."); + if (init?.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) throw new TypeError("init.duration, when provided, must be a non-negative number."); + if (init?.visibleRect !== void 0) validateRectangle(init.visibleRect, "init.visibleRect"); + this._data = data; + this._layout = null; + this.format = data.format; + this.visibleRect = { + left: data.visibleRect?.x ?? 0, + top: data.visibleRect?.y ?? 0, + width: data.visibleRect?.width ?? data.codedWidth, + height: data.visibleRect?.height ?? data.codedHeight + }; + this.rotation = init?.rotation ?? 0; + this.flip = init?.flip ?? false; + this.squarePixelWidth = data.displayWidth; + this.squarePixelHeight = data.displayHeight; + this.timestamp = init?.timestamp ?? data.timestamp / 1e6; + this.duration = init?.duration ?? (data.duration ?? 0) / 1e6; + this.colorSpace = new VideoSampleColorSpace(data.colorSpace); + } else if (typeof HTMLImageElement !== "undefined" && data instanceof HTMLImageElement || typeof SVGImageElement !== "undefined" && data instanceof SVGImageElement || typeof ImageBitmap !== "undefined" && data instanceof ImageBitmap || typeof HTMLVideoElement !== "undefined" && data instanceof HTMLVideoElement || typeof HTMLCanvasElement !== "undefined" && data instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && data instanceof OffscreenCanvas) { + if (!init || typeof init !== "object") throw new TypeError("init must be an object."); + if (init.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(init.rotation)) throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270."); + if (init.flip !== void 0 && typeof init.flip !== "boolean") throw new TypeError("init.flip, when provided, must be a boolean."); + if (!Number.isFinite(init.timestamp)) throw new TypeError("init.timestamp must be a number."); + if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) throw new TypeError("init.duration, when provided, must be a non-negative number."); + if (init.visibleRect !== void 0) validateRectangle(init.visibleRect, "init.visibleRect"); + if (typeof VideoFrame !== "undefined") return new VideoSample(new VideoFrame(data, { + timestamp: Math.trunc(init.timestamp * SECOND_TO_MICROSECOND_FACTOR), + duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR) || void 0, + visibleRect: init.visibleRect && { + x: init.visibleRect.left, + y: init.visibleRect.top, + width: init.visibleRect.width, + height: init.visibleRect.height + } + }), init); + let width = 0; + let height = 0; + if ("naturalWidth" in data) { + width = data.naturalWidth; + height = data.naturalHeight; + } else if ("videoWidth" in data) { + width = data.videoWidth; + height = data.videoHeight; + } else if ("width" in data) { + width = Number(data.width); + height = Number(data.height); + } + if (!width || !height) throw new TypeError("Could not determine dimensions."); + const visibleRect = init.visibleRect ?? { + left: 0, + top: 0, + width, + height + }; + const canvas = new OffscreenCanvas(visibleRect.width, visibleRect.height); + const context = canvas.getContext("2d", { + alpha: isFirefox(), + willReadFrequently: true + }); + if (!context) throw new Error("OffscreenCanvas must have support for the '2d' context in order to create a VideoSample from this data."); + context.drawImage(data, -visibleRect.left, -visibleRect.top); + this._data = canvas; + this._layout = null; + this.format = "RGBX"; + this.visibleRect = { + left: 0, + top: 0, + width: visibleRect.width, + height: visibleRect.height + }; + this.squarePixelWidth = visibleRect.width; + this.squarePixelHeight = visibleRect.height; + this.rotation = init.rotation ?? 0; + this.flip = init.flip ?? false; + this.timestamp = init.timestamp; + this.duration = init.duration ?? 0; + this.colorSpace = new VideoSampleColorSpace({ + matrix: "rgb", + primaries: "bt709", + transfer: "iec61966-2-1", + fullRange: true + }); + } else if (data instanceof VideoSampleResource) { + if (!init || typeof init !== "object") throw new TypeError("init must be an object."); + if (init.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(init.rotation)) throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270."); + if (init.flip !== void 0 && typeof init.flip !== "boolean") throw new TypeError("init.flip, when provided, must be a boolean."); + if (!Number.isFinite(init.timestamp)) throw new TypeError("init.timestamp must be a number."); + if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) throw new TypeError("init.duration, when provided, must be a non-negative number."); + this._data = data; + data._referenceCount++; + this.format = data.getFormat(); + if (this.format !== null && !VIDEO_SAMPLE_PIXEL_FORMATS.includes(this.format)) throw new TypeError("getFormat() must return a VideoSamplePixelFormat or null."); + this.visibleRect = { + left: 0, + top: 0, + width: data.getCodedWidth(), + height: data.getCodedHeight() + }; + if (!Number.isInteger(this.visibleRect.width) || this.visibleRect.width <= 0) throw new TypeError("getCodedWidth() must return a positive integer."); + if (!Number.isInteger(this.visibleRect.height) || this.visibleRect.height <= 0) throw new TypeError("getCodedHeight() must return a positive integer."); + this.squarePixelWidth = data.getSquarePixelWidth(); + if (!Number.isInteger(this.squarePixelWidth) || this.squarePixelWidth <= 0) throw new TypeError("getSquarePixelWidth() must return a positive integer."); + this.squarePixelHeight = data.getSquarePixelHeight(); + if (!Number.isInteger(this.squarePixelHeight) || this.squarePixelHeight <= 0) throw new TypeError("getSquarePixelHeight() must return a positive integer."); + this.rotation = init.rotation ?? 0; + this.flip = init.flip ?? false; + this.timestamp = init.timestamp; + this.duration = init.duration ?? 0; + this.colorSpace = data.getColorSpace(); + } else throw new TypeError("Invalid data type: Must be a BufferSource, CanvasImageSource, or VideoSampleResource."); + this.encodeOptions = init?.encodeOptions ?? {}; + this.pixelAspectRatio = simplifyRational({ + num: this.squarePixelWidth * this.codedHeight, + den: this.squarePixelHeight * this.codedWidth + }); + finalizationRegistry?.register(this, { + type: "video", + data: this._data + }, this); + } + /** Clones this video sample. */ + clone() { + if (this._closed) throw new Error("VideoSample is closed."); + assert(this._data !== null); + if (this._data instanceof VideoSampleResource) return new VideoSample(this._data, { + timestamp: this.timestamp, + duration: this.duration, + rotation: this.rotation, + flip: this.flip, + encodeOptions: this.encodeOptions + }); + else if (isVideoFrame(this._data)) return new VideoSample(this._data.clone(), { + timestamp: this.timestamp, + duration: this.duration, + rotation: this.rotation, + flip: this.flip, + encodeOptions: this.encodeOptions + }); + else if (this._data instanceof Uint8Array) { + assert(this._layout); + return new VideoSample(this._data, { + format: this.format, + layout: this._layout, + codedWidth: this.codedWidth, + codedHeight: this.codedHeight, + timestamp: this.timestamp, + duration: this.duration, + colorSpace: this.colorSpace, + rotation: this.rotation, + flip: this.flip, + visibleRect: this.visibleRect, + displayWidth: this.displayWidth, + displayHeight: this.displayHeight, + encodeOptions: this.encodeOptions, + _doNotCopy: true + }); + } else return new VideoSample(this._data, { + format: this.format, + codedWidth: this.codedWidth, + codedHeight: this.codedHeight, + timestamp: this.timestamp, + duration: this.duration, + colorSpace: this.colorSpace, + rotation: this.rotation, + flip: this.flip, + visibleRect: this.visibleRect, + displayWidth: this.displayWidth, + displayHeight: this.displayHeight, + encodeOptions: this.encodeOptions + }); + } + /** + * Closes this video sample, releasing held resources. Video samples should be closed as soon as they are not + * needed anymore. + */ + close() { + if (this._closed) return; + finalizationRegistry?.unregister(this); + if (this._data instanceof VideoSampleResource) { + this._data._referenceCount--; + if (this._data._referenceCount === 0) this._data.close(); + } else if (isVideoFrame(this._data)) this._data.close(); + else this._data = null; + this._closed = true; + } + /** + * Returns the number of bytes required to hold this video sample's pixel data. + */ + allocationSize(options = {}) { + validateVideoFrameCopyToOptions(options); + if (this._closed) throw new Error("VideoSample is closed."); + if ((options.format ?? this.format) == null) throw new Error("Cannot get allocation size when format is null."); + if (isVideoFrame(this._data)) return this._data.allocationSize(options); + return ParseVideoFrameCopyToOptions(this, options).allocationSize; + } + /** + * Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView. + * @returns The byte layout of the planes of the copied data. + */ + async copyTo(destination, options = {}) { + if (!isAllowSharedBufferSource(destination)) throw new TypeError("destination must be an ArrayBuffer or an ArrayBuffer view."); + validateVideoFrameCopyToOptions(options); + if (this._closed) throw new Error("VideoSample is closed."); + if ((options.format ?? this.format) == null) throw new Error("Cannot copy video sample data when format is null."); + assert(this._data !== null); + if (isVideoFrame(this._data)) return this._data.copyTo(destination, options); + if (options.format && ![ + "RGBA", + "RGBX", + "BGRA", + "BGRX" + ].includes(this.format) && [ + "RGBA", + "RGBX", + "BGRA", + "BGRX" + ].includes(options.format)) if (this._data instanceof VideoSampleResource) { + const env_1 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const rgbSample = __addDisposableResource$2(env_1, await this._data.toRgbSample({ + timestamp: this.timestamp, + duration: this.duration, + rotation: this.rotation, + flip: this.flip + }, options.colorSpace ?? "srgb"), false); + if (!(rgbSample instanceof VideoSample)) throw new TypeError("toRgbSample() must return a VideoSample."); + if (![ + "RGBA", + "RGBX", + "BGRA", + "BGRX" + ].includes(rgbSample.format)) throw new Error(`Sample returned by toRgbSample was expected to have an RGB format, got '${rgbSample.format}' instead.`); + return await rgbSample.copyTo(destination, options); + } catch (e_1) { + env_1.error = e_1; + env_1.hasError = true; + } finally { + __disposeResources$2(env_1); + } + } else { + if (typeof VideoFrame === "undefined") throw new Error("For this sample, converting from a non-RGB to an RGB format requires VideoFrame to be defined."); + const tempFrame = this.toVideoFrame(); + const result = await tempFrame.copyTo(destination, options); + tempFrame.close(); + return result; + } + const combinedLayout = ParseVideoFrameCopyToOptions(this, options); + assert(this.format); + const destBytes = toUint8Array(destination); + if (destBytes.byteLength < combinedLayout.allocationSize) throw new TypeError(`Destination buffer too small. Required: ${combinedLayout.allocationSize}, Available: ${destBytes.byteLength}`); + const planeConfigs = getPlaneConfigs(this.format); + let dataPlanes; + if (this._data instanceof VideoSampleResource) { + let result = this._data.getDataPlanes(); + if (isThenable(result)) result = await result; + if (!Array.isArray(result) || result.some((x) => !(x.data instanceof Uint8Array) || !Number.isInteger(x.stride) || x.stride < 0)) throw new TypeError("getDataPlanes() must return an array of objects with a Uint8Array \"data\" property and a non-negative integer \"stride\" property."); + dataPlanes = result; + } else if (this._data instanceof Uint8Array) { + assert(this._layout); + assert(this._layout.length === planeConfigs.length); + dataPlanes = this._layout.map((planeLayout, i) => { + const height = Math.ceil(this.codedHeight / planeConfigs[i].heightDivisor); + return { + data: this._data.subarray(planeLayout.offset, planeLayout.offset + planeLayout.stride * height), + stride: planeLayout.stride + }; + }); + } else { + const context = this._data.getContext("2d"); + assert(context); + dataPlanes = [{ + data: toUint8Array(context.getImageData(0, 0, this.codedWidth, this.codedHeight).data), + stride: 4 * this.codedWidth + }]; + } + const planeLayouts = []; + const numPlanes = planeConfigs.length; + for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) { + const computedLayout = combinedLayout.computedLayouts[planeIndex]; + const sourceStride = dataPlanes[planeIndex].stride; + const sourceData = dataPlanes[planeIndex].data; + let sourceOffset = computedLayout.sourceTop * sourceStride; + sourceOffset += computedLayout.sourceLeftBytes; + let destinationOffset = computedLayout.destinationOffset; + const rowBytes = computedLayout.sourceWidthBytes; + const layout = { + offset: destinationOffset, + stride: computedLayout.destinationStride + }; + for (let row = 0; row < computedLayout.sourceHeight; row++) { + if (sourceOffset + rowBytes > sourceData.byteLength) throw new Error(`Source buffer OOB read.`); + if (destinationOffset + rowBytes > destBytes.byteLength) throw new Error(`Destination buffer OOB write.`); + const srcSub = sourceData.subarray(sourceOffset, sourceOffset + rowBytes); + destBytes.set(srcSub, destinationOffset); + sourceOffset += sourceStride; + destinationOffset += computedLayout.destinationStride; + } + planeLayouts.push(layout); + } + if (options.format !== void 0) { + const needsRgbConversion = this.format.startsWith("RGB") !== options.format.startsWith("RGB"); + const needsAlphaConversion = this.format.includes("X") && options.format.includes("A"); + if (needsRgbConversion || needsAlphaConversion) for (let i = 0; i < combinedLayout.allocationSize; i += 4) { + if (needsRgbConversion) { + const r = destBytes[i]; + destBytes[i] = destBytes[i + 2]; + destBytes[i + 2] = r; + } + if (needsAlphaConversion) destBytes[i + 3] = 255; + } + } + return planeLayouts; + } + /** + * Converts this video sample to a VideoFrame for use with the WebCodecs API. The VideoFrame returned by this + * method *must* be closed separately from this video sample. + */ + toVideoFrame() { + if (this._closed) throw new Error("VideoSample is closed."); + assert(this._data !== null); + if (this._data instanceof VideoSampleResource) { + if (this.format === null) throw new Error("Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if format is null."); + const planes = this._data.getDataPlanes(); + if (isThenable(planes)) throw new Error("Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if getDataPlanes() returns a promise."); + const size = planes.reduce((a, b) => a + b.data.byteLength, 0); + const buffer = new Uint8Array(size); + let offset = 0; + const offsets = []; + for (const plane of planes) { + buffer.set(plane.data, offset); + offsets.push(offset); + offset += plane.data.byteLength; + } + return new VideoFrame(buffer, { + format: this.format, + layout: planes.map((x, i) => ({ + offset: offsets[i], + stride: x.stride + })), + codedWidth: this.codedWidth, + codedHeight: this.codedHeight, + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration, + colorSpace: this.colorSpace, + visibleRect: this.visibleRect, + displayWidth: this.squarePixelWidth, + displayHeight: this.squarePixelHeight + }); + } else if (isVideoFrame(this._data)) return new VideoFrame(this._data, { + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration || void 0 + }); + else if (this._data instanceof Uint8Array) { + assert(this._layout); + return new VideoFrame(this._data, { + format: this.format, + codedWidth: this.codedWidth, + codedHeight: this.codedHeight, + layout: this._layout, + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration || void 0, + colorSpace: this.colorSpace, + visibleRect: this.visibleRect, + displayWidth: this.squarePixelWidth, + displayHeight: this.squarePixelHeight + }); + } else return new VideoFrame(this._data, { + timestamp: this.microsecondTimestamp, + duration: this.microsecondDuration || void 0 + }); + } + draw(context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + let sx = 0; + let sy = 0; + let sWidth = this.displayWidth; + let sHeight = this.displayHeight; + let dx = 0; + let dy = 0; + let dWidth = this.displayWidth; + let dHeight = this.displayHeight; + if (arg5 !== void 0) { + sx = arg1; + sy = arg2; + sWidth = arg3; + sHeight = arg4; + dx = arg5; + dy = arg6; + if (arg7 !== void 0) { + dWidth = arg7; + dHeight = arg8; + } else { + dWidth = sWidth; + dHeight = sHeight; + } + } else { + dx = arg1; + dy = arg2; + if (arg3 !== void 0) { + dWidth = arg3; + dHeight = arg4; + } + } + if (!(typeof CanvasRenderingContext2D !== "undefined" && context instanceof CanvasRenderingContext2D || typeof OffscreenCanvasRenderingContext2D !== "undefined" && context instanceof OffscreenCanvasRenderingContext2D)) throw new TypeError("context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D."); + if (!Number.isFinite(sx)) throw new TypeError("sx must be a number."); + if (!Number.isFinite(sy)) throw new TypeError("sy must be a number."); + if (!Number.isFinite(sWidth) || sWidth < 0) throw new TypeError("sWidth must be a non-negative number."); + if (!Number.isFinite(sHeight) || sHeight < 0) throw new TypeError("sHeight must be a non-negative number."); + if (!Number.isFinite(dx)) throw new TypeError("dx must be a number."); + if (!Number.isFinite(dy)) throw new TypeError("dy must be a number."); + if (!Number.isFinite(dWidth) || dWidth < 0) throw new TypeError("dWidth must be a non-negative number."); + if (!Number.isFinite(dHeight) || dHeight < 0) throw new TypeError("dHeight must be a non-negative number."); + if (this._closed) throw new Error("VideoSample is closed."); + ({sx, sy, sWidth, sHeight} = this._unmapSourceRegion(sx, sy, sWidth, sHeight, this.rotation, this.flip)); + const source = this.toCanvasImageSource(); + context.save(); + const centerX = dx + dWidth / 2; + const centerY = dy + dHeight / 2; + context.translate(centerX, centerY); + if (this.flip) context.scale(-1, 1); + context.rotate(this.rotation * Math.PI / 180); + const aspectRatioChange = this.rotation % 180 === 0 ? 1 : dWidth / dHeight; + context.scale(1 / aspectRatioChange, aspectRatioChange); + context.drawImage(source, sx, sy, sWidth, sHeight, -dWidth / 2, -dHeight / 2, dWidth, dHeight); + context.restore(); + } + /** + * Draws the sample in the middle of the canvas corresponding to the context with the specified fit behavior. + */ + drawWithFit(context, options) { + if (!(typeof CanvasRenderingContext2D !== "undefined" && context instanceof CanvasRenderingContext2D || typeof OffscreenCanvasRenderingContext2D !== "undefined" && context instanceof OffscreenCanvasRenderingContext2D)) throw new TypeError("context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (![ + "fill", + "contain", + "cover" + ].includes(options.fit)) throw new TypeError("options.fit must be 'fill', 'contain', or 'cover'."); + if (options.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(options.rotation)) throw new TypeError("options.rotation, when provided, must be 0, 90, 180, or 270."); + if (options.flip !== void 0 && typeof options.flip !== "boolean") throw new TypeError("options.flip, when provided, must be a boolean."); + if (options.crop !== void 0) validateCropRectangle(options.crop, "options."); + const canvasWidth = context.canvas.width; + const canvasHeight = context.canvas.height; + const rotation = options.rotation ?? this.rotation; + const flip = options.flip ?? this.flip; + const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth]; + let finalCrop = options.crop; + if (finalCrop) finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight); + let dx; + let dy; + let newWidth; + let newHeight; + const { sx, sy, sWidth, sHeight } = this._unmapSourceRegion(options.crop?.left ?? 0, options.crop?.top ?? 0, options.crop?.width ?? rotatedWidth, options.crop?.height ?? rotatedHeight, rotation, flip); + if (options.fit === "fill") { + dx = 0; + dy = 0; + newWidth = canvasWidth; + newHeight = canvasHeight; + } else { + const [sampleWidth, sampleHeight] = options.crop ? [options.crop.width, options.crop.height] : [rotatedWidth, rotatedHeight]; + const scale = options.fit === "contain" ? Math.min(canvasWidth / sampleWidth, canvasHeight / sampleHeight) : Math.max(canvasWidth / sampleWidth, canvasHeight / sampleHeight); + newWidth = sampleWidth * scale; + newHeight = sampleHeight * scale; + dx = (canvasWidth - newWidth) / 2; + dy = (canvasHeight - newHeight) / 2; + } + context.save(); + const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight; + context.translate(canvasWidth / 2, canvasHeight / 2); + if (flip) context.scale(-1, 1); + context.rotate(rotation * Math.PI / 180); + context.scale(1 / aspectRatioChange, aspectRatioChange); + context.translate(-canvasWidth / 2, -canvasHeight / 2); + context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight); + context.restore(); + } + /** @internal */ + _unmapSourceRegion(sx, sy, sWidth, sHeight, rotation, flip) { + if (flip) sx = (rotation % 180 === 0 ? this.squarePixelWidth : this.squarePixelHeight) - sx - sWidth; + if (rotation === 90) [sx, sy, sWidth, sHeight] = [ + sy, + this.squarePixelHeight - sx - sWidth, + sHeight, + sWidth + ]; + else if (rotation === 180) [sx, sy] = [this.squarePixelWidth - sx - sWidth, this.squarePixelHeight - sy - sHeight]; + else if (rotation === 270) [sx, sy, sWidth, sHeight] = [ + this.squarePixelWidth - sy - sHeight, + sx, + sHeight, + sWidth + ]; + return { + sx, + sy, + sWidth, + sHeight + }; + } + /** + * Draws the sample onto the target canvas with fit behavior, manually mipmapping on strong downscales for quality. + * @internal + */ + _drawWithFitAndMipmapping(targetCanvas, targetContext, options) { + const targetWidth = targetCanvas.width; + const targetHeight = targetCanvas.height; + const [rotatedWidth, rotatedHeight] = options.rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth]; + const sourceWidth = options.crop ? options.crop.width : rotatedWidth; + const sourceHeight = options.crop ? options.crop.height : rotatedHeight; + let mipLevels = 0; + if (2 * targetWidth < sourceWidth && 2 * targetHeight < sourceHeight) mipLevels = Math.floor(Math.log2(Math.min(sourceWidth / targetWidth, sourceHeight / targetHeight))); + const drawWidth = targetWidth * 2 ** mipLevels; + const drawHeight = targetHeight * 2 ** mipLevels; + const { canvas, context, isNew } = mipLevels > 0 ? getTransformationCanvas(drawWidth, drawHeight) : { + canvas: targetCanvas, + context: targetContext, + isNew: options.targetIsFresh + }; + context.imageSmoothingQuality = "high"; + if (options.fillBlack) { + context.fillStyle = "black"; + context.fillRect(0, 0, drawWidth, drawHeight); + } else if (!isNew) context.clearRect(0, 0, drawWidth, drawHeight); + this.drawWithFit(context, { + fit: options.fit, + rotation: options.rotation, + flip: options.flip, + crop: options.crop + }); + context.globalCompositeOperation = "copy"; + for (let i = mipLevels; i > 1; i--) { + const levelWidth = targetWidth * 2 ** i; + const levelHeight = targetHeight * 2 ** i; + context.drawImage(canvas, 0, 0, levelWidth, levelHeight, 0, 0, levelWidth / 2, levelHeight / 2); + } + context.globalCompositeOperation = "source-over"; + if (mipLevels > 0) { + targetContext.imageSmoothingQuality = "high"; + targetContext.globalCompositeOperation = "copy"; + targetContext.drawImage(canvas, 0, 0, 2 * targetWidth, 2 * targetHeight, 0, 0, targetWidth, targetHeight); + targetContext.globalCompositeOperation = "source-over"; + } + } + /** + * Converts this video sample to a + * [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource) for drawing to a canvas. + * + * You must use the value returned by this method immediately, as any VideoFrame created internally may + * automatically be closed in the next microtask. + */ + toCanvasImageSource() { + if (this._closed) throw new Error("VideoSample is closed."); + assert(this._data !== null); + if (this._data instanceof VideoSampleResource || this._data instanceof Uint8Array) { + const videoFrame = this.toVideoFrame(); + queueMicrotask(() => videoFrame.close()); + return videoFrame; + } else return this._data; + } + /** + * Transform this video sample to a new video sample given the options. Can be used to resize, rotate, flip, and + * crop the sample. + * + * In non-browser environments, this method will not work by default. To make it work, register a custom + * transformer function via {@link registerVideoSampleTransformer}. + */ + async transform(options) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.width !== void 0 && (!Number.isInteger(options.width) || options.width <= 0)) throw new TypeError("options.width, when provided, must be a positive integer."); + if (options.height !== void 0 && (!Number.isInteger(options.height) || options.height <= 0)) throw new TypeError("options.height, when provided, must be a positive integer."); + if (options.roundDimensionsTo !== void 0 && (!Number.isInteger(options.roundDimensionsTo) || options.roundDimensionsTo <= 0)) throw new TypeError("options.roundDimensionsTo, when provided, must be a positive integer."); + if (options.fit !== void 0 && ![ + "fill", + "contain", + "cover" + ].includes(options.fit)) throw new TypeError("options.fit, when provided, must be one of \"fill\", \"contain\", or \"cover\"."); + if (options.width !== void 0 && options.height !== void 0 && options.fit === void 0) throw new TypeError("When both options.width and options.height are provided, options.fit must also be provided."); + if (options.rotate !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(options.rotate)) throw new TypeError("options.rotate, when provided, must be 0, 90, 180 or 270."); + if (options.flip !== void 0 && typeof options.flip !== "boolean") throw new TypeError("options.flip, when provided, must be a boolean."); + if (options.crop !== void 0) validateCropRectangle(options.crop, "options."); + if (options.alpha !== void 0 && !["keep", "discard"].includes(options.alpha)) throw new TypeError("options.alpha, when provided, must be 'keep' or 'discard'."); + const { rotation, flip } = composeRotationAndFlip(this.rotation, this.flip, options.rotate ?? 0, options.flip ?? false); + const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth]; + let finalCrop = options.crop; + if (finalCrop) finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight); + const cropWidth = finalCrop ? finalCrop.width : rotatedWidth; + const cropHeight = finalCrop ? finalCrop.height : rotatedHeight; + const originalAspectRatio = cropWidth / cropHeight; + let targetWidth; + let targetHeight; + if (options.width !== void 0 && options.height === void 0) { + targetWidth = options.width; + targetHeight = targetWidth / originalAspectRatio; + } else if (options.width === void 0 && options.height !== void 0) { + targetHeight = options.height; + targetWidth = targetHeight * originalAspectRatio; + } else if (options.width !== void 0 && options.height !== void 0) { + targetWidth = options.width; + targetHeight = options.height; + } else { + targetWidth = cropWidth; + targetHeight = cropHeight; + } + targetWidth = roundToMultiple(targetWidth, options.roundDimensionsTo ?? 1); + targetHeight = roundToMultiple(targetHeight, options.roundDimensionsTo ?? 1); + const description = { + width: targetWidth, + height: targetHeight, + fit: options.fit ?? "fill", + rotation, + flip, + crop: finalCrop ?? { + left: 0, + top: 0, + width: rotatedWidth, + height: rotatedHeight + }, + alpha: options.alpha ?? "keep" + }; + for (const transformer of registeredVideoSampleTransformers) { + let result = transformer(this, description); + if (isThenable(result)) result = await result; + if (result !== null) return result; + } + const { canvas, context, isNew } = getTransformationCanvas(description.width, description.height); + this._drawWithFitAndMipmapping(canvas, context, { + fit: description.fit, + rotation: description.rotation, + flip: description.flip, + crop: description.crop, + targetIsFresh: isNew, + fillBlack: description.alpha === "discard" + }); + return new VideoSample(canvas, { + timestamp: this.timestamp, + duration: this.duration, + rotation: 0, + flip: false + }); + } + /** Sets the rotation metadata of this video sample. */ + setRotation(newRotation) { + if (![ + 0, + 90, + 180, + 270 + ].includes(newRotation)) throw new TypeError("newRotation must be 0, 90, 180, or 270."); + this.rotation = newRotation; + } + /** Sets the flip metadata of this video sample. */ + setFlip(newFlip) { + if (typeof newFlip !== "boolean") throw new TypeError("newFlip must be a boolean."); + this.flip = newFlip; + } + /** Sets the presentation timestamp of this video sample, in seconds. */ + setTimestamp(newTimestamp) { + if (!Number.isFinite(newTimestamp)) throw new TypeError("newTimestamp must be a number."); + this.timestamp = newTimestamp; + } + /** Sets the duration of this video sample, in seconds. */ + setDuration(newDuration) { + if (!Number.isFinite(newDuration) || newDuration < 0) throw new TypeError("newDuration must be a non-negative number."); + this.duration = newDuration; + } + /** Sets the encode options used when this sample is passed to an encoder. */ + setEncodeOptions(newEncodeOptions) { + if (!newEncodeOptions || typeof newEncodeOptions !== "object") throw new TypeError("newEncodeOptions must be an object."); + this.encodeOptions = newEncodeOptions; + } + /** Calls `.close()`. */ + [Symbol.dispose]() { + this.close(); + } +}; +var registeredVideoSampleTransformers = []; +/** +* Registers a callback to handle the transformation of {@link VideoSample} instances. The callback can either return +* the transformed sample, or `null` to indicate that it doesn't want to handle the given transformation task. +* @group Samples +* @public +*/ +var registerVideoSampleTransformer = (transformer) => { + if (registeredVideoSampleTransformers.includes(transformer)) return; + registeredVideoSampleTransformers.push(transformer); +}; +var TRANSFORMATION_CANVAS_CACHE_MAX_SIZE = 3; +var transformationCanvasCache = []; +var transformationCanvasCacheNextAge = 0; +var getTransformationCanvas = (width, height) => { + for (const entry of transformationCanvasCache) if (entry.canvas.width === width && entry.canvas.height === height) { + entry.age = transformationCanvasCacheNextAge++; + return { + canvas: entry.canvas, + context: entry.context, + isNew: false + }; + } + let canvas; + if (typeof OffscreenCanvas !== "undefined") canvas = new OffscreenCanvas(width, height); + else { + if (typeof window === "undefined" || typeof document === "undefined") throw new Error("Cannot transform VideoSamples in this environment. Either run in an environment with OffscreenCanvas or HTMLCanvasElement, or supply a custom VideoSample transformer using registerVideoSampleTransformer()."); + canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + } + const context = canvas.getContext("2d", { + alpha: true, + willReadFrequently: false + }); + if (!context) throw new Error("The '2d' canvas context is required to transform VideoSamples. Register a custom transformer using registerVideoSampleTransformer to work around this limitation."); + if (transformationCanvasCache.length >= TRANSFORMATION_CANVAS_CACHE_MAX_SIZE) transformationCanvasCache.splice(arrayArgmin(transformationCanvasCache, (x) => x.age), 1); + transformationCanvasCache.push({ + canvas, + context, + age: transformationCanvasCacheNextAge++ + }); + return { + canvas, + context, + isNew: true + }; +}; +/** +* Describes the color space of a {@link VideoSample}. Corresponds to the WebCodecs API's VideoColorSpace. +* @group Samples +* @public +*/ +var VideoSampleColorSpace = class { + /** Creates a new VideoSampleColorSpace. */ + constructor(init) { + if (init !== void 0) { + if (!init || typeof init !== "object") throw new TypeError("init.colorSpace, when provided, must be an object."); + const primariesValues = Object.keys(COLOR_PRIMARIES_MAP); + if (init.primaries != null && !primariesValues.includes(init.primaries)) throw new TypeError(`init.colorSpace.primaries, when provided, must be one of ${primariesValues.join(", ")}.`); + const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); + if (init.transfer != null && !transferValues.includes(init.transfer)) throw new TypeError(`init.colorSpace.transfer, when provided, must be one of ${transferValues.join(", ")}.`); + const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); + if (init.matrix != null && !matrixValues.includes(init.matrix)) throw new TypeError(`init.colorSpace.matrix, when provided, must be one of ${matrixValues.join(", ")}.`); + if (init.fullRange != null && typeof init.fullRange !== "boolean") throw new TypeError("init.colorSpace.fullRange, when provided, must be a boolean."); + } + this.primaries = init?.primaries ?? null; + this.transfer = init?.transfer ?? null; + this.matrix = init?.matrix ?? null; + this.fullRange = init?.fullRange ?? null; + } + /** Serializes the color space to a JSON object. */ + toJSON() { + return { + primaries: this.primaries, + transfer: this.transfer, + matrix: this.matrix, + fullRange: this.fullRange + }; + } +}; +var isVideoFrame = (x) => { + return typeof VideoFrame !== "undefined" && x instanceof VideoFrame; +}; +var clampCropRectangle = (crop, outerWidth, outerHeight) => { + const left = Math.min(crop.left, outerWidth); + const top = Math.min(crop.top, outerHeight); + const width = Math.min(crop.width, outerWidth - left); + const height = Math.min(crop.height, outerHeight - top); + assert(width >= 0); + assert(height >= 0); + return { + left, + top, + width, + height + }; +}; +var validateCropRectangle = (crop, prefix) => { + if (!crop || typeof crop !== "object") throw new TypeError(prefix + "crop, when provided, must be an object."); + if (!Number.isInteger(crop.left) || crop.left < 0) throw new TypeError(prefix + "crop.left must be a non-negative integer."); + if (!Number.isInteger(crop.top) || crop.top < 0) throw new TypeError(prefix + "crop.top must be a non-negative integer."); + if (!Number.isInteger(crop.width) || crop.width < 0) throw new TypeError(prefix + "crop.width must be a non-negative integer."); + if (!Number.isInteger(crop.height) || crop.height < 0) throw new TypeError(prefix + "crop.height must be a non-negative integer."); +}; +var validateVideoFrameCopyToOptions = (options) => { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.colorSpace !== void 0 && !["display-p3", "srgb"].includes(options.colorSpace)) throw new TypeError("options.colorSpace, when provided, must be 'display-p3' or 'srgb'."); + if (options.format !== void 0 && typeof options.format !== "string") throw new TypeError("options.format, when provided, must be a string."); + if (options.layout !== void 0) { + if (!Array.isArray(options.layout)) throw new TypeError("options.layout, when provided, must be an array."); + for (const plane of options.layout) { + if (!plane || typeof plane !== "object") throw new TypeError("Each entry in options.layout must be an object."); + if (!Number.isInteger(plane.offset) || plane.offset < 0) throw new TypeError("plane.offset must be a non-negative integer."); + if (!Number.isInteger(plane.stride) || plane.stride < 0) throw new TypeError("plane.stride must be a non-negative integer."); + } + } + if (options.rect !== void 0) { + if (!options.rect || typeof options.rect !== "object") throw new TypeError("options.rect, when provided, must be an object."); + if (options.rect.x !== void 0 && (!Number.isInteger(options.rect.x) || options.rect.x < 0)) throw new TypeError("options.rect.x, when provided, must be a non-negative integer."); + if (options.rect.y !== void 0 && (!Number.isInteger(options.rect.y) || options.rect.y < 0)) throw new TypeError("options.rect.y, when provided, must be a non-negative integer."); + if (options.rect.width !== void 0 && (!Number.isInteger(options.rect.width) || options.rect.width < 0)) throw new TypeError("options.rect.width, when provided, must be a non-negative integer."); + if (options.rect.height !== void 0 && (!Number.isInteger(options.rect.height) || options.rect.height < 0)) throw new TypeError("options.rect.height, when provided, must be a non-negative integer."); + } +}; +/** Implements logic from WebCodecs § 9.4.6 "Compute Layout and Allocation Size" */ +var createDefaultPlaneLayout = (format, codedWidth, codedHeight) => { + const planes = getPlaneConfigs(format); + const layouts = []; + let currentOffset = 0; + for (const plane of planes) { + const planeWidth = Math.ceil(codedWidth / plane.widthDivisor); + const planeHeight = Math.ceil(codedHeight / plane.heightDivisor); + const stride = planeWidth * plane.sampleBytes; + const planeSize = stride * planeHeight; + layouts.push({ + offset: currentOffset, + stride + }); + currentOffset += planeSize; + } + return layouts; +}; +/** Helper to retrieve plane configurations based on WebCodecs § 9.8 Pixel Format definitions. */ +var getPlaneConfigs = (format) => { + const yuv = (yBytes, uvBytes, subX, subY, hasAlpha) => { + const configs = [ + { + sampleBytes: yBytes, + widthDivisor: 1, + heightDivisor: 1 + }, + { + sampleBytes: uvBytes, + widthDivisor: subX, + heightDivisor: subY + }, + { + sampleBytes: uvBytes, + widthDivisor: subX, + heightDivisor: subY + } + ]; + if (hasAlpha) configs.push({ + sampleBytes: yBytes, + widthDivisor: 1, + heightDivisor: 1 + }); + return configs; + }; + switch (format) { + case "I420": return yuv(1, 1, 2, 2, false); + case "I420P10": + case "I420P12": return yuv(2, 2, 2, 2, false); + case "I420A": return yuv(1, 1, 2, 2, true); + case "I420AP10": + case "I420AP12": return yuv(2, 2, 2, 2, true); + case "I422": return yuv(1, 1, 2, 1, false); + case "I422P10": + case "I422P12": return yuv(2, 2, 2, 1, false); + case "I422A": return yuv(1, 1, 2, 1, true); + case "I422AP10": + case "I422AP12": return yuv(2, 2, 2, 1, true); + case "I444": return yuv(1, 1, 1, 1, false); + case "I444P10": + case "I444P12": return yuv(2, 2, 1, 1, false); + case "I444A": return yuv(1, 1, 1, 1, true); + case "I444AP10": + case "I444AP12": return yuv(2, 2, 1, 1, true); + case "NV12": return [{ + sampleBytes: 1, + widthDivisor: 1, + heightDivisor: 1 + }, { + sampleBytes: 2, + widthDivisor: 2, + heightDivisor: 2 + }]; + case "RGBA": + case "RGBX": + case "BGRA": + case "BGRX": return [{ + sampleBytes: 4, + widthDivisor: 1, + heightDivisor: 1 + }]; + default: + assertNever(format); + assert(false); + } +}; +/** Taken from the WebCodecs spec. */ +var ParseVideoFrameCopyToOptions = (sample, options) => { + const defaultRect = { + left: 0, + top: 0, + width: sample.codedWidth, + height: sample.codedHeight + }; + const overrideRect = options.rect; + const parsedRect = ParseVisibleRect(defaultRect, overrideRect, sample.codedWidth, sample.codedHeight, sample.format); + const optLayout = options.layout; + let format; + if (!options.format || options.format === sample.format) format = sample.format; + else if ([ + "RGBA", + "RGBX", + "BGRA", + "BGRX" + ].includes(options.format)) format = options.format; + else throw new Error("NotSupportedError: Invalid destination format."); + return ComputeLayoutAndAllocationSize(parsedRect, format, optLayout); +}; +/** Taken from the WebCodecs spec. */ +var ParseVisibleRect = (defaultRect, overrideRect, codedWidth, codedHeight, format) => { + const sourceRect = { ...defaultRect }; + if (overrideRect !== void 0) { + if (overrideRect.width === 0 || overrideRect.height === 0) throw new TypeError("visibleRect dimensions cannot be zero."); + if ((overrideRect.x || 0) + (overrideRect.width || 0) > codedWidth) throw new TypeError("visibleRect exceeds codedWidth."); + if ((overrideRect.y || 0) + (overrideRect.height || 0) > codedHeight) throw new TypeError("visibleRect exceeds codedHeight."); + sourceRect.x = overrideRect.x || 0; + sourceRect.y = overrideRect.y || 0; + sourceRect.width = overrideRect.width || 0; + sourceRect.height = overrideRect.height || 0; + } + if (!VerifyRectOffsetAlignment(format, sourceRect)) throw new TypeError("visibleRect alignment is invalid for the format."); + return sourceRect; +}; +/** Taken from the WebCodecs spec. */ +var VerifyRectOffsetAlignment = (format, rect) => { + if (format === null) return true; + const planes = getPlaneConfigs(format); + for (let planeIndex = 0; planeIndex < planes.length; planeIndex++) { + const plane = planes[planeIndex]; + const sampleWidth = plane.widthDivisor; + const sampleHeight = plane.heightDivisor; + if ((rect.x || 0) % sampleWidth !== 0) return false; + if ((rect.y || 0) % sampleHeight !== 0) return false; + } + return true; +}; +/** Taken from the WebCodecs spec. */ +var ComputeLayoutAndAllocationSize = (parsedRect, format, layout) => { + const planes = getPlaneConfigs(format); + const numPlanes = planes.length; + if (layout !== void 0 && layout.length !== numPlanes) throw new TypeError(`Layout must have ${numPlanes} planes.`); + let minAllocationSize = 0; + const computedLayouts = []; + const endOffsets = []; + for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) { + const plane = planes[planeIndex]; + const sampleBytes = plane.sampleBytes; + const sampleWidth = plane.widthDivisor; + const sampleHeight = plane.heightDivisor; + const computedLayout = { + destinationOffset: 0, + destinationStride: 0, + sourceTop: 0, + sourceHeight: 0, + sourceLeftBytes: 0, + sourceWidthBytes: 0 + }; + computedLayout.sourceTop = Math.ceil(Math.trunc(parsedRect.y || 0) / sampleHeight); + computedLayout.sourceHeight = Math.ceil(Math.trunc(parsedRect.height || 0) / sampleHeight); + computedLayout.sourceLeftBytes = Math.floor(Math.trunc(parsedRect.x || 0) / sampleWidth) * sampleBytes; + computedLayout.sourceWidthBytes = Math.floor(Math.trunc(parsedRect.width || 0) / sampleWidth) * sampleBytes; + if (layout !== void 0) { + const planeLayout = layout[planeIndex]; + if (planeLayout.stride < computedLayout.sourceWidthBytes) throw new TypeError(`Stride for plane ${planeIndex} is too small.`); + computedLayout.destinationOffset = planeLayout.offset; + computedLayout.destinationStride = planeLayout.stride; + } else { + computedLayout.destinationOffset = minAllocationSize; + computedLayout.destinationStride = computedLayout.sourceWidthBytes; + } + const planeEnd = computedLayout.destinationStride * computedLayout.sourceHeight + computedLayout.destinationOffset; + if (planeEnd > 4294967295) throw new TypeError("Allocation size exceeds limit."); + endOffsets.push(planeEnd); + minAllocationSize = Math.max(minAllocationSize, planeEnd); + for (let earlierPlaneIndex = 0; earlierPlaneIndex < planeIndex; earlierPlaneIndex++) { + const earlierLayout = computedLayouts[earlierPlaneIndex]; + if (endOffsets[planeIndex] <= earlierLayout.destinationOffset || endOffsets[earlierPlaneIndex] <= computedLayout.destinationOffset) continue; + throw new TypeError("Planes overlap."); + } + computedLayouts.push(computedLayout); + } + return { + allocationSize: minAllocationSize, + computedLayouts + }; +}; +var AUDIO_SAMPLE_FORMATS = new Set([ + "f32", + "f32-planar", + "s16", + "s16-planar", + "s32", + "s32-planar", + "u8", + "u8-planar" +]); +/** +* Abstract base class for custom audio sample resources. Implement this class to provide custom backing +* for AudioSample instances. +* @group Samples +* @public +*/ +var AudioSampleResource = class { + constructor() { + /** @internal */ + this._referenceCount = 0; + } +}; +/** +* Represents a raw, unencoded audio sample. Mainly used as an expressive wrapper around WebCodecs API's +* [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData), but can also be used standalone. +* @group Samples +* @public +*/ +var AudioSample = class AudioSample { + /** The presentation timestamp of the sample in microseconds. */ + get microsecondTimestamp() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp); + } + /** The duration of the sample in microseconds. */ + get microsecondDuration() { + return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration); + } + constructor(init) { + /** @internal */ + this._closed = false; + if (isAudioData(init)) { + if (init.format === null) throw new TypeError("AudioData with null format is not supported."); + this._data = init; + this.format = init.format; + this.sampleRate = init.sampleRate; + this.numberOfFrames = init.numberOfFrames; + this.numberOfChannels = init.numberOfChannels; + this.timestamp = init.timestamp / 1e6; + this.duration = init.numberOfFrames / init.sampleRate; + } else if (init instanceof AudioSampleResource) { + this._data = init; + init._referenceCount++; + this.format = init.getFormat(); + if (!AUDIO_SAMPLE_FORMATS.has(this.format)) throw new TypeError("getFormat() must return an AudioSampleFormat."); + this.sampleRate = init.getSampleRate(); + if (!Number.isInteger(this.sampleRate) || this.sampleRate <= 0) throw new TypeError("getSampleRate() must return a positive integer."); + this.numberOfFrames = init.getNumberOfFrames(); + if (!Number.isInteger(this.numberOfFrames) || this.numberOfFrames < 0) throw new TypeError("getNumberOfFrames() must return a non-negative integer."); + this.numberOfChannels = init.getNumberOfChannels(); + if (!Number.isInteger(this.numberOfChannels) || this.numberOfChannels <= 0) throw new TypeError("getNumberOfChannels() must return a positive integer."); + this.timestamp = init.getTimestamp(); + if (!Number.isFinite(this.timestamp)) throw new TypeError("getTimestamp() must return a finite number."); + this.duration = this.numberOfFrames / this.sampleRate; + } else { + if (!init || typeof init !== "object") throw new TypeError("Invalid AudioDataInit: must be an object."); + if (!AUDIO_SAMPLE_FORMATS.has(init.format)) throw new TypeError("Invalid AudioDataInit: invalid format."); + if (!Number.isFinite(init.sampleRate) || init.sampleRate <= 0) throw new TypeError("Invalid AudioDataInit: sampleRate must be > 0."); + if (!Number.isInteger(init.numberOfChannels) || init.numberOfChannels === 0) throw new TypeError("Invalid AudioDataInit: numberOfChannels must be an integer > 0."); + if (!Number.isFinite(init?.timestamp)) throw new TypeError("init.timestamp must be a number."); + const numberOfFrames = init.data.byteLength / (getBytesPerSample(init.format) * init.numberOfChannels); + if (!Number.isInteger(numberOfFrames)) throw new TypeError("Invalid AudioDataInit: data size is not a multiple of frame size."); + this.format = init.format; + this.sampleRate = init.sampleRate; + this.numberOfFrames = numberOfFrames; + this.numberOfChannels = init.numberOfChannels; + this.timestamp = init.timestamp; + this.duration = numberOfFrames / init.sampleRate; + let dataBuffer; + if (init.data instanceof ArrayBuffer) dataBuffer = new Uint8Array(init.data); + else if (ArrayBuffer.isView(init.data)) dataBuffer = new Uint8Array(init.data.buffer, init.data.byteOffset, init.data.byteLength); + else throw new TypeError("Invalid AudioDataInit: data is not a BufferSource."); + const expectedSize = this.numberOfFrames * this.numberOfChannels * getBytesPerSample(this.format); + if (dataBuffer.byteLength < expectedSize) throw new TypeError("Invalid AudioDataInit: insufficient data size."); + this._data = dataBuffer; + } + finalizationRegistry?.register(this, { + type: "audio", + data: this._data + }, this); + } + /** Returns the number of bytes required to hold the audio sample's data as specified by the given options. */ + allocationSize(options) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) throw new TypeError("planeIndex must be a non-negative integer."); + if (options.format !== void 0 && !AUDIO_SAMPLE_FORMATS.has(options.format)) throw new TypeError("Invalid format."); + if (options.frameOffset !== void 0 && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) throw new TypeError("frameOffset must be a non-negative integer."); + if (options.frameCount !== void 0 && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) throw new TypeError("frameCount must be a non-negative integer."); + if (this._closed) throw new Error("AudioSample is closed."); + const destFormat = options.format ?? this.format; + const frameOffset = options.frameOffset ?? 0; + if (frameOffset >= this.numberOfFrames) throw new RangeError("frameOffset out of range"); + const copyFrameCount = options.frameCount !== void 0 ? options.frameCount : this.numberOfFrames - frameOffset; + if (copyFrameCount > this.numberOfFrames - frameOffset) throw new RangeError("frameCount out of range"); + const bytesPerSample = getBytesPerSample(destFormat); + const isPlanar = formatIsPlanar(destFormat); + if (isPlanar && options.planeIndex >= this.numberOfChannels) throw new RangeError("planeIndex out of range"); + if (!isPlanar && options.planeIndex !== 0) throw new RangeError("planeIndex out of range"); + return (isPlanar ? copyFrameCount : copyFrameCount * this.numberOfChannels) * bytesPerSample; + } + /** Copies the audio sample's data to an ArrayBuffer or ArrayBufferView as specified by the given options. */ + copyTo(destination, options) { + if (!isAllowSharedBufferSource(destination)) throw new TypeError("destination must be an ArrayBuffer or an ArrayBuffer view."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) throw new TypeError("planeIndex must be a non-negative integer."); + if (options.format !== void 0 && !AUDIO_SAMPLE_FORMATS.has(options.format)) throw new TypeError("Invalid format."); + if (options.frameOffset !== void 0 && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) throw new TypeError("frameOffset must be a non-negative integer."); + if (options.frameCount !== void 0 && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) throw new TypeError("frameCount must be a non-negative integer."); + if (this._closed) throw new Error("AudioSample is closed."); + const { format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options; + let { planeIndex } = options; + let srcFormat = this.format; + const destFormat = format ?? this.format; + if (!destFormat) throw new Error("Destination format not determined"); + const numFrames = this.numberOfFrames; + const numChannels = this.numberOfChannels; + const frameOffset = optFrameOffset ?? 0; + if (frameOffset >= numFrames) throw new RangeError("frameOffset out of range"); + const copyFrameCount = optFrameCount !== void 0 ? optFrameCount : numFrames - frameOffset; + if (copyFrameCount > numFrames - frameOffset) throw new RangeError("frameCount out of range"); + const destBytesPerSample = getBytesPerSample(destFormat); + const destIsPlanar = formatIsPlanar(destFormat); + if (destIsPlanar && planeIndex >= numChannels) throw new RangeError("planeIndex out of range"); + if (!destIsPlanar && planeIndex !== 0) throw new RangeError("planeIndex out of range"); + const requiredSize = (destIsPlanar ? copyFrameCount : copyFrameCount * numChannels) * destBytesPerSample; + if (destination.byteLength < requiredSize) throw new RangeError("Destination buffer is too small"); + const destView = toDataView(destination); + const writeFn = getWriteFunction(destFormat); + if (isAudioData(this._data)) if (isWebKit() && numChannels > 2 && destFormat !== srcFormat) { + doAudioDataCopyToWebKitWorkaround(this._data, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount); + return; + } else try { + this._data.copyTo(destination, { + planeIndex, + frameOffset, + frameCount: copyFrameCount, + format: destFormat + }); + return; + } catch (error) { + if (destFormat === "f32-planar") throw error; + srcFormat = "f32-planar"; + } + const readFn = getReadFunction(srcFormat); + const srcBytesPerSample = getBytesPerSample(srcFormat); + const srcIsPlanar = formatIsPlanar(srcFormat); + let uint8Data; + if (this._data instanceof AudioSampleResource) { + const getDataPlaneValidated = (index) => { + const result = this._data.getDataPlane(index); + if (!(result instanceof Uint8Array)) throw new TypeError("getDataPlane() must return a Uint8Array."); + const expectedSize = numFrames * srcBytesPerSample * (srcIsPlanar ? 1 : numChannels); + if (result.byteLength !== expectedSize) throw new TypeError(`Data plane ${index} has invalid size. Expected exactly ${expectedSize} bytes, got ${result.byteLength} bytes.`); + return result; + }; + if (srcIsPlanar) if (destIsPlanar) { + uint8Data = getDataPlaneValidated(planeIndex); + planeIndex = 0; + } else { + uint8Data = new Uint8Array(numFrames * srcBytesPerSample * numChannels); + for (let ch = 0; ch < numChannels; ch++) { + const planeData = getDataPlaneValidated(ch); + uint8Data.set(planeData, ch * numFrames * srcBytesPerSample); + } + } + else uint8Data = getDataPlaneValidated(0); + } else if (this._data instanceof Uint8Array) uint8Data = this._data; + else { + assert(srcFormat === "f32-planar"); + if (destIsPlanar) { + uint8Data = new Uint8Array(this._data.allocationSize({ + format: "f32-planar", + planeIndex + })); + this._data.copyTo(uint8Data, { + format: "f32-planar", + planeIndex + }); + planeIndex = 0; + } else { + uint8Data = new Uint8Array(this._data.allocationSize({ + format: "f32-planar", + planeIndex: 0 + }) * numChannels); + for (let ch = 0; ch < numChannels; ch++) this._data.copyTo(uint8Data.subarray(ch * numFrames * srcBytesPerSample, (ch + 1) * numFrames * srcBytesPerSample), { + format: "f32-planar", + planeIndex: ch + }); + } + } + const srcView = toDataView(uint8Data); + for (let i = 0; i < copyFrameCount; i++) if (destIsPlanar) { + const destOffset = i * destBytesPerSample; + let srcOffset; + if (srcIsPlanar) srcOffset = (planeIndex * numFrames + (i + frameOffset)) * srcBytesPerSample; + else srcOffset = ((i + frameOffset) * numChannels + planeIndex) * srcBytesPerSample; + writeFn(destView, destOffset, readFn(srcView, srcOffset)); + } else for (let ch = 0; ch < numChannels; ch++) { + const destOffset = (i * numChannels + ch) * destBytesPerSample; + let srcOffset; + if (srcIsPlanar) srcOffset = (ch * numFrames + (i + frameOffset)) * srcBytesPerSample; + else srcOffset = ((i + frameOffset) * numChannels + ch) * srcBytesPerSample; + writeFn(destView, destOffset, readFn(srcView, srcOffset)); + } + } + /** Clones this audio sample. */ + clone() { + if (this._closed) throw new Error("AudioSample is closed."); + if (this._data instanceof AudioSampleResource) { + const sample = new AudioSample(this._data); + sample.setTimestamp(this.timestamp); + return sample; + } else if (isAudioData(this._data)) { + const sample = new AudioSample(this._data.clone()); + sample.setTimestamp(this.timestamp); + return sample; + } else return new AudioSample({ + format: this.format, + sampleRate: this.sampleRate, + numberOfFrames: this.numberOfFrames, + numberOfChannels: this.numberOfChannels, + timestamp: this.timestamp, + data: this._data + }); + } + /** + * Returns a new {@link AudioSample} containing only the frames in the range [startFrame, endFrame). Both bounds + * must lie within this sample's range of frames. The returned sample's timestamp is shifted to match the start of + * the trimmed section. + */ + trim(startFrame, endFrame = this.numberOfFrames) { + if (!Number.isInteger(startFrame) || startFrame < 0) throw new TypeError("startFrame must be a non-negative integer."); + if (!Number.isInteger(endFrame) || endFrame < 0) throw new TypeError("endFrame must be a non-negative integer."); + if (startFrame > this.numberOfFrames) throw new RangeError("startFrame out of range."); + if (endFrame > this.numberOfFrames) throw new RangeError("endFrame out of range."); + if (endFrame < startFrame) throw new RangeError("endFrame must not be less than startFrame."); + if (this._closed) throw new Error("AudioSample is closed."); + const frameCount = endFrame - startFrame; + const bytesPerSample = getBytesPerSample(this.format); + let data; + if (formatIsPlanar(this.format)) { + const planeSize = frameCount * bytesPerSample; + data = new Uint8Array(planeSize * this.numberOfChannels); + if (frameCount > 0) for (let i = 0; i < this.numberOfChannels; i++) this.copyTo(data.subarray(i * planeSize, (i + 1) * planeSize), { + planeIndex: i, + format: this.format, + frameOffset: startFrame, + frameCount + }); + } else { + data = new Uint8Array(frameCount * this.numberOfChannels * bytesPerSample); + if (frameCount > 0) this.copyTo(data, { + planeIndex: 0, + format: this.format, + frameOffset: startFrame, + frameCount + }); + } + return new AudioSample({ + data, + format: this.format, + sampleRate: this.sampleRate, + numberOfChannels: this.numberOfChannels, + timestamp: this.timestamp + startFrame / this.sampleRate + }); + } + /** + * Closes this audio sample, releasing held resources. Audio samples should be closed as soon as they are not + * needed anymore. + */ + close() { + if (this._closed) return; + finalizationRegistry?.unregister(this); + if (this._data instanceof AudioSampleResource) { + this._data._referenceCount--; + if (this._data._referenceCount === 0) this._data.close(); + } else if (isAudioData(this._data)) this._data.close(); + else this._data = new Uint8Array(0); + this._closed = true; + } + /** + * Converts this audio sample to an AudioData for use with the WebCodecs API. The AudioData returned by this + * method *must* be closed separately from this audio sample. + */ + toAudioData() { + if (this._closed) throw new Error("AudioSample is closed."); + if (this._data instanceof AudioSampleResource) return this._createAudioDataFromData(); + else if (isAudioData(this._data)) if (this._data.timestamp === this.microsecondTimestamp) return this._data.clone(); + else return this._createAudioDataFromData(); + else return new AudioData({ + format: this.format, + sampleRate: this.sampleRate, + numberOfFrames: this.numberOfFrames, + numberOfChannels: this.numberOfChannels, + timestamp: this.microsecondTimestamp, + data: this._data.buffer instanceof ArrayBuffer ? this._data.buffer : this._data.slice() + }); + } + /** @internal */ + _createAudioDataFromData() { + if (formatIsPlanar(this.format)) { + const size = this.allocationSize({ + planeIndex: 0, + format: this.format + }); + const data = new ArrayBuffer(size * this.numberOfChannels); + for (let i = 0; i < this.numberOfChannels; i++) this.copyTo(new Uint8Array(data, i * size, size), { + planeIndex: i, + format: this.format + }); + return new AudioData({ + format: this.format, + sampleRate: this.sampleRate, + numberOfFrames: this.numberOfFrames, + numberOfChannels: this.numberOfChannels, + timestamp: this.microsecondTimestamp, + data + }); + } else { + const data = new ArrayBuffer(this.allocationSize({ + planeIndex: 0, + format: this.format + })); + this.copyTo(data, { + planeIndex: 0, + format: this.format + }); + return new AudioData({ + format: this.format, + sampleRate: this.sampleRate, + numberOfFrames: this.numberOfFrames, + numberOfChannels: this.numberOfChannels, + timestamp: this.microsecondTimestamp, + data + }); + } + } + /** Convert this audio sample to an AudioBuffer for use with the Web Audio API. */ + toAudioBuffer() { + if (this._closed) throw new Error("AudioSample is closed."); + const audioBuffer = new AudioBuffer({ + numberOfChannels: this.numberOfChannels, + length: this.numberOfFrames, + sampleRate: this.sampleRate + }); + const dataBytes = new Float32Array(this.allocationSize({ + planeIndex: 0, + format: "f32-planar" + }) / 4); + for (let i = 0; i < this.numberOfChannels; i++) { + this.copyTo(dataBytes, { + planeIndex: i, + format: "f32-planar" + }); + audioBuffer.copyToChannel(dataBytes, i); + } + return audioBuffer; + } + /** Sets the presentation timestamp of this audio sample, in seconds. */ + setTimestamp(newTimestamp) { + if (!Number.isFinite(newTimestamp)) throw new TypeError("newTimestamp must be a number."); + this.timestamp = newTimestamp; + } + /** Calls `.close()`. */ + [Symbol.dispose]() { + this.close(); + } + /** @internal */ + static *_fromAudioBuffer(audioBuffer, timestamp) { + if (!(audioBuffer instanceof AudioBuffer)) throw new TypeError("audioBuffer must be an AudioBuffer."); + const MAX_FLOAT_COUNT = 48e3 * 5; + const numberOfChannels = audioBuffer.numberOfChannels; + const sampleRate = audioBuffer.sampleRate; + const totalFrames = audioBuffer.length; + const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels); + let currentRelativeFrame = 0; + let remainingFrames = totalFrames; + while (remainingFrames > 0) { + const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames); + const chunkData = new Float32Array(numberOfChannels * framesToCopy); + for (let channel = 0; channel < numberOfChannels; channel++) audioBuffer.copyFromChannel(chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy), channel, currentRelativeFrame); + yield new AudioSample({ + format: "f32-planar", + sampleRate, + numberOfFrames: framesToCopy, + numberOfChannels, + timestamp: timestamp + currentRelativeFrame / sampleRate, + data: chunkData + }); + currentRelativeFrame += framesToCopy; + remainingFrames -= framesToCopy; + } + } + /** + * Creates AudioSamples from an AudioBuffer, starting at the given timestamp in seconds. Typically creates exactly + * one sample, but may create multiple if the AudioBuffer is exceedingly large. + */ + static fromAudioBuffer(audioBuffer, timestamp) { + if (!(audioBuffer instanceof AudioBuffer)) throw new TypeError("audioBuffer must be an AudioBuffer."); + const MAX_FLOAT_COUNT = 48e3 * 5; + const numberOfChannels = audioBuffer.numberOfChannels; + const sampleRate = audioBuffer.sampleRate; + const totalFrames = audioBuffer.length; + const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels); + let currentRelativeFrame = 0; + let remainingFrames = totalFrames; + const result = []; + while (remainingFrames > 0) { + const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames); + const chunkData = new Float32Array(numberOfChannels * framesToCopy); + for (let channel = 0; channel < numberOfChannels; channel++) audioBuffer.copyFromChannel(chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy), channel, currentRelativeFrame); + const audioSample = new AudioSample({ + format: "f32-planar", + sampleRate, + numberOfFrames: framesToCopy, + numberOfChannels, + timestamp: timestamp + currentRelativeFrame / sampleRate, + data: chunkData + }); + result.push(audioSample); + currentRelativeFrame += framesToCopy; + remainingFrames -= framesToCopy; + } + return result; + } +}; +var getBytesPerSample = (format) => { + switch (format) { + case "u8": + case "u8-planar": return 1; + case "s16": + case "s16-planar": return 2; + case "s32": + case "s32-planar": return 4; + case "f32": + case "f32-planar": return 4; + default: throw new Error("Unknown AudioSampleFormat"); + } +}; +var formatIsPlanar = (format) => { + switch (format) { + case "u8-planar": + case "s16-planar": + case "s32-planar": + case "f32-planar": return true; + default: return false; + } +}; +var getReadFunction = (format) => { + switch (format) { + case "u8": + case "u8-planar": return (view, offset) => (view.getUint8(offset) - 128) / 128; + case "s16": + case "s16-planar": return (view, offset) => view.getInt16(offset, true) / 32768; + case "s32": + case "s32-planar": return (view, offset) => view.getInt32(offset, true) / 2147483648; + case "f32": + case "f32-planar": return (view, offset) => view.getFloat32(offset, true); + } +}; +var getWriteFunction = (format) => { + switch (format) { + case "u8": + case "u8-planar": return (view, offset, value) => view.setUint8(offset, clamp(Math.round(value * 128) + 128, 0, 255)); + case "s16": + case "s16-planar": return (view, offset, value) => view.setInt16(offset, clamp(Math.round(value * 32768), -32768, 32767), true); + case "s32": + case "s32-planar": return (view, offset, value) => view.setInt32(offset, clamp(Math.round(value * 2147483648), -2147483648, 2147483647), true); + case "f32": + case "f32-planar": return (view, offset, value) => view.setFloat32(offset, value, true); + } +}; +var isAudioData = (x) => { + return typeof AudioData !== "undefined" && x instanceof AudioData; +}; +var toInterleavedAudioFormat = (format) => { + switch (format) { + case "u8-planar": return "u8"; + case "s16-planar": return "s16"; + case "s32-planar": return "s32"; + case "f32-planar": return "f32"; + default: return format; + } +}; +/** +* WebKit has a bug where calling AudioData.copyTo with a format different from the source format crashes the tab when +* there are more than 2 channels. This function works around that by always copying with the source format and then +* manually converting to the destination format. +* +* See https://bugs.webkit.org/show_bug.cgi?id=302521. +*/ +var doAudioDataCopyToWebKitWorkaround = (audioData, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount) => { + const readFn = getReadFunction(srcFormat); + const writeFn = getWriteFunction(destFormat); + const srcBytesPerSample = getBytesPerSample(srcFormat); + const destBytesPerSample = getBytesPerSample(destFormat); + const srcIsPlanar = formatIsPlanar(srcFormat); + if (formatIsPlanar(destFormat)) if (srcIsPlanar) { + const data = new ArrayBuffer(copyFrameCount * srcBytesPerSample); + const dataView = toDataView(data); + audioData.copyTo(data, { + planeIndex, + frameOffset, + frameCount: copyFrameCount, + format: srcFormat + }); + for (let i = 0; i < copyFrameCount; i++) { + const srcOffset = i * srcBytesPerSample; + writeFn(destView, i * destBytesPerSample, readFn(dataView, srcOffset)); + } + } else { + const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample); + const dataView = toDataView(data); + audioData.copyTo(data, { + planeIndex: 0, + frameOffset, + frameCount: copyFrameCount, + format: srcFormat + }); + for (let i = 0; i < copyFrameCount; i++) { + const srcOffset = (i * numChannels + planeIndex) * srcBytesPerSample; + writeFn(destView, i * destBytesPerSample, readFn(dataView, srcOffset)); + } + } + else if (srcIsPlanar) { + const planeSize = copyFrameCount * srcBytesPerSample; + const data = new ArrayBuffer(planeSize); + const dataView = toDataView(data); + for (let ch = 0; ch < numChannels; ch++) { + audioData.copyTo(data, { + planeIndex: ch, + frameOffset, + frameCount: copyFrameCount, + format: srcFormat + }); + for (let i = 0; i < copyFrameCount; i++) { + const srcOffset = i * srcBytesPerSample; + writeFn(destView, (i * numChannels + ch) * destBytesPerSample, readFn(dataView, srcOffset)); + } + } + } else { + const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample); + const dataView = toDataView(data); + audioData.copyTo(data, { + planeIndex: 0, + frameOffset, + frameCount: copyFrameCount, + format: srcFormat + }); + for (let i = 0; i < copyFrameCount; i++) for (let ch = 0; ch < numChannels; ch++) { + const idx = i * numChannels + ch; + const srcOffset = idx * srcBytesPerSample; + writeFn(destView, idx * destBytesPerSample, readFn(dataView, srcOffset)); + } + } +}; +var audioSampleToInterleavedFormat = (sample, format) => { + const size = sample.allocationSize({ + format, + planeIndex: 0 + }); + const buffer = new ArrayBuffer(size); + sample.copyTo(buffer, { + format, + planeIndex: 0 + }); + return new AudioSample({ + data: buffer, + format, + numberOfChannels: sample.numberOfChannels, + sampleRate: sample.sampleRate, + timestamp: sample.timestamp, + duration: sample.duration + }); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/encode.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var canEncodeVideoMemo = /* @__PURE__ */ new Map(); +var canEncodeAudioMemo = /* @__PURE__ */ new Map(); +var validateVideoEncodingConfig = (config) => { + if (!config || typeof config !== "object") throw new TypeError("Encoding config must be an object."); + if (!VIDEO_CODECS.includes(config.codec)) throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); + const bitrate = config.bitrate; + if (config.quality === void 0 && bitrate === void 0) throw new TypeError("config.quality must be provided."); + if (config.quality !== void 0 && bitrate !== void 0) throw new TypeError("config.quality and config.bitrate cannot both be provided."); + if (config.quality !== void 0 && !(config.quality instanceof Quality)) throw new TypeError("config.quality, when provided, must be a Quality."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality."); + if (config.keyFrameInterval !== void 0 && (!Number.isFinite(config.keyFrameInterval) || config.keyFrameInterval < 0)) throw new TypeError("config.keyFrameInterval, when provided, must be a non-negative number."); + if (config.sizeChangeBehavior !== void 0 && ![ + "deny", + "passThrough", + "fill", + "contain", + "cover" + ].includes(config.sizeChangeBehavior)) throw new TypeError("config.sizeChangeBehavior, when provided, must be 'deny', 'passThrough', 'fill', 'contain' or 'cover'."); + if (config.transform !== void 0) { + if (typeof config.transform !== "object" || !config.transform) throw new TypeError("config.transform, when provided, must be an object."); + if (config.transform.width !== void 0 && (!Number.isInteger(config.transform.width) || config.transform.width <= 0)) throw new TypeError("config.transform.width, when provided, must be a positive integer."); + if (config.transform.height !== void 0 && (!Number.isInteger(config.transform.height) || config.transform.height <= 0)) throw new TypeError("config.transform.height, when provided, must be a positive integer."); + if (config.transform.fit !== void 0 && ![ + "fill", + "contain", + "cover" + ].includes(config.transform.fit)) throw new TypeError("config.transform.fit, when provided, must be one of \"fill\", \"contain\", or \"cover\"."); + if (config.transform.width !== void 0 && config.transform.height !== void 0 && config.transform.fit === void 0 && ![ + "fill", + "contain", + "cover" + ].includes(config.sizeChangeBehavior)) throw new TypeError("When both config.transform.width and config.transform.height are provided, config.transform.fit must also be provided."); + if (config.transform.fit !== void 0 && [ + "fill", + "contain", + "cover" + ].includes(config.sizeChangeBehavior) && config.transform.fit !== config.sizeChangeBehavior) throw new TypeError("config.transform.fit, when provided, cannot differ from config.sizeChangeBehavior when config.sizeChangeBehavior is 'fill', 'contain' or 'cover', as sizeChangeBehavior already determines the fitting algorithm."); + if (config.transform.rotate !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(config.transform.rotate)) throw new TypeError("config.transform.rotate, when provided, must be 0, 90, 180 or 270."); + if (config.transform.flip !== void 0 && typeof config.transform.flip !== "boolean") throw new TypeError("config.transform.flip, when provided, must be a boolean."); + if (config.transform.crop !== void 0) validateCropRectangle(config.transform.crop, "config.transform."); + if (config.transform.process !== void 0 && typeof config.transform.process !== "function") throw new TypeError("config.transform.process, when provided, must be a function."); + if (config.transform.frameRate !== void 0 && (!Number.isFinite(config.transform.frameRate) || config.transform.frameRate <= 0)) throw new TypeError("config.transform.frameRate, when provided, must be a finite positive number."); + if (config.transform.force !== void 0 && typeof config.transform.force !== "boolean") throw new TypeError("config.transform.force, when provided, must be a boolean."); + } + if (config.onEncodedPacket !== void 0 && typeof config.onEncodedPacket !== "function") throw new TypeError("config.onEncodedPacket, when provided, must be a function."); + if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") throw new TypeError("config.onEncoderConfig, when provided, must be a function."); + if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") throw new TypeError("config.onEncodedSample, when provided, must be a function."); + validateVideoEncodingAdditionalOptions(config.codec, config); +}; +var validateVideoEncodingAdditionalOptions = (codec, options) => { + if (!options || typeof options !== "object") throw new TypeError("Encoding options must be an object."); + if (options.alpha !== void 0 && !["discard", "keep"].includes(options.alpha)) throw new TypeError("options.alpha, when provided, must be 'discard' or 'keep'."); + const bitrateMode = options.bitrateMode; + if (bitrateMode !== void 0 && !["constant", "variable"].includes(bitrateMode)) throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'."); + if (options.latencyMode !== void 0 && !["quality", "realtime"].includes(options.latencyMode)) throw new TypeError("latencyMode, when provided, must be 'quality' or 'realtime'."); + if (options.fullCodecString !== void 0 && typeof options.fullCodecString !== "string") throw new TypeError("fullCodecString, when provided, must be a string."); + if (options.fullCodecString !== void 0 && inferCodecFromCodecString(options.fullCodecString) !== codec) throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`); + if (options.hardwareAcceleration !== void 0 && ![ + "no-preference", + "prefer-hardware", + "prefer-software" + ].includes(options.hardwareAcceleration)) throw new TypeError("hardwareAcceleration, when provided, must be 'no-preference', 'prefer-hardware' or 'prefer-software'."); + if (options.scalabilityMode !== void 0 && typeof options.scalabilityMode !== "string") throw new TypeError("scalabilityMode, when provided, must be a string."); + if (options.contentHint !== void 0 && typeof options.contentHint !== "string") throw new TypeError("contentHint, when provided, must be a string."); +}; +/** +* Builds the encoder configs to attempt, in order of preference. Multiple configs are returned when a Quality can be +* satisfied by multiple rate control methods (quantizer-based encoding with a bitrate-based fallback). +*/ +var buildVideoEncoderConfigs = (options) => { + const fallbackBitrateMode = options.bitrateMode; + const rateControl = options.quality._toVideoRateControl(options.codec, options.width, options.height, fallbackBitrateMode); + const buildConfig = (bitrate, bitrateMode, bitrateEstimate) => ({ + codec: options.fullCodecString ?? buildVideoCodecString(options.codec, options.width, options.height, bitrateEstimate, options.alpha === "keep"), + width: options.width, + height: options.height, + displayWidth: options.squarePixelWidth, + displayHeight: options.squarePixelHeight, + bitrate, + bitrateMode, + alpha: options.alpha ?? "discard", + framerate: options.framerate, + latencyMode: options.latencyMode, + hardwareAcceleration: options.hardwareAcceleration, + scalabilityMode: options.scalabilityMode, + contentHint: options.contentHint, + ...getVideoEncoderConfigExtension(options.codec) + }); + const candidates = []; + if (rateControl.quantizer !== null) candidates.push({ + config: buildConfig(void 0, "quantizer", rateControl.bitrate), + quantizer: rateControl.quantizer + }); + if (rateControl.bitrateMode !== "quantizer") candidates.push({ + config: buildConfig(rateControl.bitrate, rateControl.bitrateMode, rateControl.bitrate), + quantizer: null + }); + assert(candidates.length > 0); + return candidates; +}; +var validateAudioEncodingConfig = (config) => { + if (!config || typeof config !== "object") throw new TypeError("Encoding config must be an object."); + if (!AUDIO_CODECS.includes(config.codec)) throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); + const bitrate = config.bitrate; + if (config.quality === void 0 && bitrate === void 0 && !(PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) throw new TypeError("config.quality must be provided for compressed audio codecs."); + if (config.quality !== void 0 && bitrate !== void 0) throw new TypeError("config.quality and config.bitrate cannot both be provided."); + if (config.quality !== void 0 && !(config.quality instanceof Quality)) throw new TypeError("config.quality, when provided, must be a Quality."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality."); + if (config.transform !== void 0) { + if (typeof config.transform !== "object" || !config.transform) throw new TypeError("config.transform, when provided, must be an object."); + if (config.transform.numberOfChannels !== void 0 && (!Number.isInteger(config.transform.numberOfChannels) || config.transform.numberOfChannels <= 0)) throw new TypeError("config.transform.numberOfChannels, when provided, must be a positive integer."); + if (config.transform.sampleRate !== void 0 && (!Number.isInteger(config.transform.sampleRate) || config.transform.sampleRate <= 0)) throw new TypeError("config.transform.sampleRate, when provided, must be a positive integer."); + if (config.transform.sampleFormat !== void 0 && ![ + "u8", + "s16", + "s32", + "f32" + ].includes(config.transform.sampleFormat)) throw new TypeError("config.transform.sampleFormat, when provided, must be one of: u8, s16, s32, f32."); + if (config.transform.process !== void 0 && typeof config.transform.process !== "function") throw new TypeError("config.transform.process, when provided, must be a function."); + } + if (config.onEncodedPacket !== void 0 && typeof config.onEncodedPacket !== "function") throw new TypeError("config.onEncodedPacket, when provided, must be a function."); + if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") throw new TypeError("config.onEncoderConfig, when provided, must be a function."); + if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") throw new TypeError("config.onEncodedSample, when provided, must be a function."); + validateAudioEncodingAdditionalOptions(config.codec, config); +}; +var validateAudioEncodingAdditionalOptions = (codec, options) => { + if (!options || typeof options !== "object") throw new TypeError("Encoding options must be an object."); + const bitrateMode = options.bitrateMode; + if (bitrateMode !== void 0 && !["constant", "variable"].includes(bitrateMode)) throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'."); + if (options.fullCodecString !== void 0 && typeof options.fullCodecString !== "string") throw new TypeError("fullCodecString, when provided, must be a string."); + if (options.fullCodecString !== void 0 && inferCodecFromCodecString(options.fullCodecString) !== codec) throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`); +}; +var buildAudioEncoderConfig = (options) => { + const fallbackBitrateMode = options.bitrateMode; + return { + codec: options.fullCodecString ?? buildAudioCodecString(options.codec, options.numberOfChannels, options.sampleRate), + numberOfChannels: options.numberOfChannels, + sampleRate: options.sampleRate, + bitrate: options.quality?._toAudioBitrate(options.codec), + bitrateMode: options.quality?._bitrateMode ?? fallbackBitrateMode, + ...getAudioEncoderConfigExtension(options.codec) + }; +}; +/** +* Represents a desired encoding quality. Can express a qualitative quality level, an explicit bitrate, an explicit +* quantizer value, or a combination thereof. +* @group Encoding +* @public +*/ +var Quality = class { + constructor(options) { + if (typeof options === "number" || typeof options === "string") options = { quality: options }; + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.bitrateMode !== void 0 && !["constant", "variable"].includes(options.bitrateMode)) throw new TypeError("options.bitrateMode, when provided, must be 'constant' or 'variable'."); + if ("quality" in options) { + if (typeof options.quality === "string" ? !(options.quality in QUALITY_LEVELS) : typeof options.quality !== "number" || Number.isNaN(options.quality)) throw new TypeError("options.quality must be a number, or one of 'very-low', 'low', 'medium', 'high' or 'very-high'."); + if (options.preferBitrate !== void 0 && typeof options.preferBitrate !== "boolean") throw new TypeError("options.preferBitrate, when provided, must be a boolean."); + if ("bitrate" in options || "quantizer" in options) throw new TypeError("options.quality cannot be combined with options.bitrate or options.quantizer."); + this._quality = typeof options.quality === "string" ? QUALITY_LEVELS[options.quality] : options.quality; + this._preferBitrate = options.preferBitrate ?? false; + this._bitrate = void 0; + this._quantizer = void 0; + } else { + if (options.bitrate !== void 0 && (!Number.isInteger(options.bitrate) || options.bitrate <= 0)) throw new TypeError("options.bitrate, when provided, must be a positive integer."); + if (options.quantizer !== void 0 && (!Number.isInteger(options.quantizer) || options.quantizer < 0)) throw new TypeError("options.quantizer, when provided, must be a non-negative integer."); + if (options.bitrate === void 0 && options.quantizer === void 0) throw new TypeError("At least one of options.bitrate or options.quantizer must be set."); + if ("preferBitrate" in options) throw new TypeError("options.preferBitrate can only be combined with options.quality."); + this._quality = void 0; + this._preferBitrate = false; + this._bitrate = options.bitrate; + this._quantizer = options.quantizer; + } + this._bitrateMode = options.bitrateMode; + } + /** + * Determines the rate control methods usable for the given codec. + * @internal + */ + _toVideoRateControl(codec, width, height, fallbackBitrateMode) { + const quantizerSupport = VIDEO_CODEC_QUANTIZER_SUPPORT[codec]; + let quantizer = null; + let bitrateMode = this._bitrateMode ?? fallbackBitrateMode ?? "variable"; + if (this._quantizer !== void 0) if (!quantizerSupport) { + if (this._bitrate === void 0) throw new Error(`Codec '${codec}' does not support quantizer-based encoding. Provide a bitrate in the Quality to define a fallback.`); + } else if (this._quantizer < quantizerSupport.min || this._quantizer > quantizerSupport.max) { + if (this._bitrate === void 0) throw new Error(`Quantizer ${this._quantizer} is out of range for codec '${codec}'; must be between ${quantizerSupport.min} and ${quantizerSupport.max}.`); + } else { + quantizer = this._quantizer; + if (this._bitrate === void 0) bitrateMode = "quantizer"; + } + else if (this._bitrate === void 0 && quantizerSupport && !this._preferBitrate) { + assert(this._quality !== void 0); + quantizer = clamp(Math.round(lerp(quantizerSupport.worst, quantizerSupport.best, this._quality)), quantizerSupport.min, quantizerSupport.max); + } + let bitrate; + if (this._bitrate !== void 0) bitrate = this._bitrate; + else { + let quality = this._quality; + if (quality === void 0) { + assert(quantizer !== null && quantizerSupport); + quality = clamp((quantizer - quantizerSupport.worst) / (quantizerSupport.best - quantizerSupport.worst), 0, 1); + } + bitrate = computeVideoBitrate(codec, width, height, qualityToBitrateFactor(quality)); + } + return { + quantizer, + bitrate, + bitrateMode + }; + } + /** @internal */ + _toVideoBitrate(codec, width, height) { + if (this._bitrate !== void 0) return this._bitrate; + assert(this._quality !== void 0); + return computeVideoBitrate(codec, width, height, qualityToBitrateFactor(this._quality)); + } + /** @internal */ + _toAudioBitrate(codec) { + if (PCM_AUDIO_CODECS.includes(codec) || codec === "flac") return; + if (this._bitrate !== void 0) return this._bitrate; + if (this._quality === void 0) throw new Error("This Quality defines neither a quality level nor a bitrate and therefore cannot be used for audio encoding."); + const factor = qualityToBitrateFactor(this._quality); + const baseBitrate = { + aac: 128e3, + opus: 64e3, + mp3: 16e4, + vorbis: 64e3, + ac3: 384e3, + eac3: 192e3, + dts: 768e3 + }[codec]; + if (!baseBitrate) throw new Error(`Unhandled codec: ${codec}`); + let finalBitrate = baseBitrate * factor; + if (codec === "aac") finalBitrate = [ + 96e3, + 128e3, + 16e4, + 192e3 + ].reduce((prev, curr) => Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev); + else if (codec === "opus" || codec === "vorbis") finalBitrate = Math.max(6e3, finalBitrate); + else if (codec === "mp3") finalBitrate = [ + 8e3, + 16e3, + 24e3, + 32e3, + 4e4, + 48e3, + 64e3, + 8e4, + 96e3, + 112e3, + 128e3, + 16e4, + 192e3, + 224e3, + 256e3, + 32e4 + ].reduce((prev, curr) => Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev); + return Math.round(finalBitrate / 1e3) * 1e3; + } +}; +var QUALITY_LEVELS = { + "very-low": 0, + "low": .25, + "medium": .5, + "high": .75, + "very-high": 1 +}; +var VIDEO_CODEC_QUANTIZER_SUPPORT = { + avc: { + min: 0, + max: 51, + worst: 41, + best: 16 + }, + hevc: { + min: 0, + max: 51, + worst: 41, + best: 16 + }, + vp9: { + min: 0, + max: 63, + worst: 52, + best: 20 + }, + av1: { + min: 0, + max: 255, + worst: 208, + best: 80 + } +}; +/** +* Maps the qualitative 0-1 quality scale to a bitrate multiplier. The curve is a least-squares exponential fit through +* the multipliers historically used by the predefined quality levels (0.3, 0.6, 1, 2, 4). +*/ +var qualityToBitrateFactor = (quality) => .3 * Math.exp(2.5538 * quality); +var computeVideoBitrate = (codec, width, height, factor) => { + const pixels = width * height; + const referencePixels = 1920 * 1080; + const referenceBitrate = 3e6; + const finalBitrate = referenceBitrate * Math.pow(pixels / referencePixels, .95) * { + avc: 1, + hevc: .6, + vp9: .6, + av1: .4, + vp8: 1.2, + prores: 22e7 / referenceBitrate + }[codec] * factor; + return Math.ceil(finalBitrate / 1e3) * 1e3; +}; +/** Builds the per-frame encode options that carry the quantizer value for the given codec. */ +var buildQuantizerEncodeOptions = (codec, quantizer) => { + if (codec === "avc") return { avc: { quantizer } }; + else if (codec === "hevc") return { hevc: { quantizer } }; + else if (codec === "vp9") return { vp9: { quantizer } }; + else if (codec === "av1") return { av1: { quantizer } }; + assert(false); +}; +/** +* Represents a very low media quality. +* @deprecated Use `new Quality('very-low')` instead. +* @group Encoding +* @public +*/ +var QUALITY_VERY_LOW = /* #__PURE__ */ new Quality("very-low"); +/** +* Represents a low media quality. +* @deprecated Use `new Quality('low')` instead. +* @group Encoding +* @public +*/ +var QUALITY_LOW = /* #__PURE__ */ new Quality("low"); +/** +* Represents a medium media quality. +* @deprecated Use `new Quality('medium')` instead. +* @group Encoding +* @public +*/ +var QUALITY_MEDIUM = /* #__PURE__ */ new Quality("medium"); +/** +* Represents a high media quality. +* @deprecated Use `new Quality('high')` instead. +* @group Encoding +* @public +*/ +var QUALITY_HIGH = /* #__PURE__ */ new Quality("high"); +/** +* Represents a very high media quality. +* @deprecated Use `new Quality('very-high')` instead. +* @group Encoding +* @public +*/ +var QUALITY_VERY_HIGH = /* #__PURE__ */ new Quality("very-high"); +/** +* Checks if the browser is able to encode the given codec. +* @group Encoding +* @public +*/ +var canEncode = (codec) => { + if (VIDEO_CODECS.includes(codec)) return canEncodeVideo(codec); + else if (AUDIO_CODECS.includes(codec)) return canEncodeAudio(codec); + else if (SUBTITLE_CODECS.includes(codec)) return canEncodeSubtitles(codec); + throw new TypeError(`Unknown codec '${codec}'.`); +}; +/** +* Checks if the browser is able to encode the given video codec with the given parameters. +* @group Encoding +* @public +*/ +var canEncodeVideo = async (codec, options = {}) => { + const { width = 1280, height = 720, quality, bitrate, frameRate, ...restOptions } = options; + if (!VIDEO_CODECS.includes(codec)) return false; + if (!Number.isInteger(width) || width <= 0) throw new TypeError("width must be a positive integer."); + if (!Number.isInteger(height) || height <= 0) throw new TypeError("height must be a positive integer."); + if (quality !== void 0 && !(quality instanceof Quality)) throw new TypeError("quality, when provided, must be a Quality."); + if (quality !== void 0 && bitrate !== void 0) throw new TypeError("quality and bitrate cannot both be provided."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("bitrate must be a positive integer or a quality."); + if (frameRate !== void 0 && (!Number.isFinite(frameRate) || frameRate <= 0)) throw new TypeError("frameRate, when provided, must be a finite positive number."); + validateVideoEncodingAdditionalOptions(codec, restOptions); + const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality("medium"); + let candidates; + try { + candidates = buildVideoEncoderConfigs({ + codec, + width, + height, + quality: resolvedQuality, + framerate: frameRate, + ...restOptions, + alpha: "discard" + }); + } catch { + return false; + } + const key = JSON.stringify(candidates); + const memoized = canEncodeVideoMemo.get(key); + if (memoized) return memoized; + const promise = (async () => { + for (const { config } of candidates) if (customVideoEncoders.some((x) => x.supports(codec, config))) return true; + if (typeof VideoEncoder === "undefined") return false; + if ((width % 2 === 1 || height % 2 === 1) && (codec === "avc" || codec === "hevc")) return false; + for (const { config, quantizer } of candidates) { + try { + if (!(await VideoEncoder.isConfigSupported(config)).supported) continue; + } catch { + continue; + } + if (!isFirefox()) return true; + if (await new Promise(async (resolve) => { + try { + const encoder = new VideoEncoder({ + output: () => {}, + error: () => resolve(false) + }); + encoder.configure(config); + const frameData = new Uint8Array(width * height * 4); + const frame = new VideoFrame(frameData, { + format: "RGBA", + codedWidth: width, + codedHeight: height, + timestamp: 0 + }); + encoder.encode(frame, quantizer !== null ? buildQuantizerEncodeOptions(codec, quantizer) : void 0); + frame.close(); + await encoder.flush(); + resolve(true); + } catch { + resolve(false); + } + })) return true; + } + return false; + })(); + canEncodeVideoMemo.set(key, promise); + return promise; +}; +/** +* Checks if the browser is able to encode the given audio codec with the given parameters. +* @group Encoding +* @public +*/ +var canEncodeAudio = async (codec, options = {}) => { + const { numberOfChannels = 2, sampleRate = 48e3, quality, bitrate, ...restOptions } = options; + if (!AUDIO_CODECS.includes(codec)) return false; + if (!Number.isInteger(numberOfChannels) || numberOfChannels <= 0) throw new TypeError("numberOfChannels must be a positive integer."); + if (!Number.isInteger(sampleRate) || sampleRate <= 0) throw new TypeError("sampleRate must be a positive integer."); + if (quality !== void 0 && !(quality instanceof Quality)) throw new TypeError("quality, when provided, must be a Quality."); + if (quality !== void 0 && bitrate !== void 0) throw new TypeError("quality and bitrate cannot both be provided."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("bitrate must be a positive integer."); + validateAudioEncodingAdditionalOptions(codec, restOptions); + const encoderConfig = buildAudioEncoderConfig({ + codec, + numberOfChannels, + sampleRate, + quality: resolveQuality(quality, bitrate) ?? new Quality("medium"), + ...restOptions + }); + const key = JSON.stringify(encoderConfig); + const memoized = canEncodeAudioMemo.get(key); + if (memoized) return memoized; + const promise = (async () => { + if (customAudioEncoders.some((x) => x.supports(codec, encoderConfig))) return true; + if (PCM_AUDIO_CODECS.includes(codec)) return true; + if (typeof AudioEncoder === "undefined") return false; + try { + return (await AudioEncoder.isConfigSupported(encoderConfig)).supported === true; + } catch { + return false; + } + })(); + canEncodeAudioMemo.set(key, promise); + return promise; +}; +/** +* Resolves the `quality` and deprecated `bitrate` fields from the public API into a {@link Quality}, the norm used +* internally. +*/ +var resolveQuality = (quality, bitrate) => { + if (quality !== void 0) return quality; + if (bitrate === void 0) return; + return bitrate instanceof Quality ? bitrate : new Quality({ bitrate }); +}; +/** +* Checks if the browser is able to encode the given subtitle codec. +* @group Encoding +* @public +*/ +var canEncodeSubtitles = async (codec) => { + if (!SUBTITLE_CODECS.includes(codec)) return false; + return true; +}; +/** +* Returns the list of all media codecs that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getEncodableCodecs = async () => { + const [videoCodecs, audioCodecs, subtitleCodecs] = await Promise.all([ + getEncodableVideoCodecs(), + getEncodableAudioCodecs(), + getEncodableSubtitleCodecs() + ]); + return [ + ...videoCodecs, + ...audioCodecs, + ...subtitleCodecs + ]; +}; +/** +* Returns the list of all video codecs that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getEncodableVideoCodecs = async (checkedCodecs = VIDEO_CODECS, options) => { + const bools = await Promise.all(checkedCodecs.map((codec) => canEncodeVideo(codec, options))); + return checkedCodecs.filter((_, i) => bools[i]); +}; +/** +* Returns the list of all audio codecs that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getEncodableAudioCodecs = async (checkedCodecs = AUDIO_CODECS, options) => { + const bools = await Promise.all(checkedCodecs.map((codec) => canEncodeAudio(codec, options))); + return checkedCodecs.filter((_, i) => bools[i]); +}; +/** +* Returns the list of all subtitle codecs that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getEncodableSubtitleCodecs = async (checkedCodecs = SUBTITLE_CODECS) => { + const bools = await Promise.all(checkedCodecs.map(canEncodeSubtitles)); + return checkedCodecs.filter((_, i) => bools[i]); +}; +/** +* Returns the first video codec from the given list that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getFirstEncodableVideoCodec = async (checkedCodecs, options) => { + for (const codec of checkedCodecs) if (await canEncodeVideo(codec, options)) return codec; + return null; +}; +/** +* Returns the first audio codec from the given list that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getFirstEncodableAudioCodec = async (checkedCodecs, options) => { + for (const codec of checkedCodecs) if (await canEncodeAudio(codec, options)) return codec; + return null; +}; +/** +* Returns the first subtitle codec from the given list that can be encoded by the browser. +* @group Encoding +* @public +*/ +var getFirstEncodableSubtitleCodec = async (checkedCodecs) => { + for (const codec of checkedCodecs) if (await canEncodeSubtitles(codec)) return codec; + return null; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/custom-coder.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Base class for custom video decoders. To add your own custom video decoder, extend this class, implement the +* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}. +* @group Custom coders +* @public +*/ +var CustomVideoDecoder = class { + /** Returns true if and only if the decoder can decode the given codec configuration. */ + static supports(codec, config) { + return false; + } +}; +/** +* Base class for custom audio decoders. To add your own custom audio decoder, extend this class, implement the +* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}. +* @group Custom coders +* @public +*/ +var CustomAudioDecoder = class { + /** Returns true if and only if the decoder can decode the given codec configuration. */ + static supports(codec, config) { + return false; + } +}; +/** +* Base class for custom video encoders. To add your own custom video encoder, extend this class, implement the +* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}. +* @group Custom coders +* @public +*/ +var CustomVideoEncoder = class { + /** Returns true if and only if the encoder can encode the given codec configuration. */ + static supports(codec, config) { + return false; + } +}; +/** +* Base class for custom audio encoders. To add your own custom audio encoder, extend this class, implement the +* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}. +* @group Custom coders +* @public +*/ +var CustomAudioEncoder = class { + /** Returns true if and only if the encoder can encode the given codec configuration. */ + static supports(codec, config) { + return false; + } +}; +var customVideoDecoders = []; +var customAudioDecoders = []; +var customVideoEncoders = []; +var customAudioEncoders = []; +/** +* Registers a custom video or audio decoder. Registered decoders will automatically be used for decoding whenever +* possible. +* @group Custom coders +* @public +*/ +var registerDecoder = (decoder) => { + if (decoder.prototype instanceof CustomVideoDecoder) { + const casted = decoder; + if (customVideoDecoders.includes(casted)) { + Logging._warn("Video decoder already registered."); + return; + } + customVideoDecoders.push(casted); + canDecodeVideoMemo.clear(); + } else if (decoder.prototype instanceof CustomAudioDecoder) { + const casted = decoder; + if (customAudioDecoders.includes(casted)) { + Logging._warn("Audio decoder already registered."); + return; + } + customAudioDecoders.push(casted); + canDecodeAudioMemo.clear(); + } else throw new TypeError("Decoder must be a CustomVideoDecoder or CustomAudioDecoder."); +}; +/** +* Registers a custom video or audio encoder. Registered encoders will automatically be used for encoding whenever +* possible. +* @group Custom coders +* @public +*/ +var registerEncoder = (encoder) => { + if (encoder.prototype instanceof CustomVideoEncoder) { + const casted = encoder; + if (customVideoEncoders.includes(casted)) { + Logging._warn("Video encoder already registered."); + return; + } + customVideoEncoders.push(casted); + canEncodeVideoMemo.clear(); + } else if (encoder.prototype instanceof CustomAudioEncoder) { + const casted = encoder; + if (customAudioEncoders.includes(casted)) { + Logging._warn("Audio encoder already registered."); + return; + } + customAudioEncoders.push(casted); + canEncodeAudioMemo.clear(); + } else throw new TypeError("Encoder must be a CustomVideoEncoder or CustomAudioEncoder."); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/pcm.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var toUlaw = (s16) => { + const MULAW_MAX = 8191; + const MULAW_BIAS = 33; + let number = s16 >> 2; + let mask = 4096; + let sign = 0; + let position = 12; + let lsb = 0; + if (number < 0) { + number = -number; + sign = 128; + } + number += MULAW_BIAS; + if (number > MULAW_MAX) number = MULAW_MAX; + while ((number & mask) !== mask && position >= 5) { + mask >>= 1; + position--; + } + lsb = number >> position - 4 & 15; + return ~(sign | position - 5 << 4 | lsb) & 255; +}; +var fromUlaw = (u8) => { + const MULAW_BIAS = 33; + let sign = 0; + let position = 0; + let number = ~u8; + if (number & 128) { + number &= -129; + sign = -1; + } + position = ((number & 240) >> 4) + 5; + const decoded = (1 << position | (number & 15) << position - 4 | 1 << position - 5) - MULAW_BIAS; + return (sign === 0 ? decoded : -decoded) << 2; +}; +var toAlaw = (s16) => { + const ALAW_MAX = 4095; + let mask = 2048; + let sign = 0; + let position = 11; + let lsb = 0; + let number = s16 >> 3; + if (number < 0) number = -number - 1; + else sign = 128; + if (number > ALAW_MAX) number = ALAW_MAX; + while ((number & mask) !== mask && position >= 5) { + mask >>= 1; + position--; + } + lsb = number >> (position === 4 ? 1 : position - 4) & 15; + return (sign | position - 4 << 4 | lsb) ^ 85; +}; +var fromAlaw = (u8) => { + let sign = 0; + let position = 0; + let number = u8 ^ 85; + if (number & 128) { + number &= -129; + sign = -1; + } + position = ((number & 240) >> 4) + 4; + let decoded = 0; + if (position !== 4) decoded = 1 << position | (number & 15) << position - 4 | 1 << position - 5; + else decoded = number << 1 | 1; + return (sign === 0 ? -decoded : decoded) << 3; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/media-sink.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var validatePacketRetrievalOptions = (options) => { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.metadataOnly !== void 0 && typeof options.metadataOnly !== "boolean") throw new TypeError("options.metadataOnly, when defined, must be a boolean."); + if (options.verifyKeyPackets !== void 0 && typeof options.verifyKeyPackets !== "boolean") throw new TypeError("options.verifyKeyPackets, when defined, must be a boolean."); + if (options.verifyKeyPackets && options.metadataOnly) throw new TypeError("options.verifyKeyPackets and options.metadataOnly cannot be enabled together."); + if (options.skipLiveWait !== void 0 && typeof options.skipLiveWait !== "boolean") throw new TypeError("options.skipLiveWait, when defined, must be a boolean."); +}; +var validateTimestamp = (timestamp) => { + if (!isNumber(timestamp)) throw new TypeError("timestamp must be a number."); +}; +var maybeFixPacketType = (track, promise, options) => { + if (options.verifyKeyPackets) return promise.then(async (packet) => { + if (!packet || packet.type === "delta") return packet; + const determinedType = await track.determinePacketType(packet); + if (determinedType) packet.type = determinedType; + return packet; + }); + else return promise; +}; +/** +* Sink for retrieving encoded packets from an input track. +* @group Media sinks +* @public +*/ +var EncodedPacketSink = class { + /** Creates a new {@link EncodedPacketSink} for the given {@link InputTrack}. */ + constructor(track) { + if (!(track instanceof InputTrack)) throw new TypeError("track must be an InputTrack."); + this._track = track; + } + /** + * Retrieves the track's first packet (in decode order), or null if it has no packets. The first packet is very + * likely to be a key packet, but it doesn't have to be. + */ + async getFirstPacket(options = {}) { + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + return maybeFixPacketType(this._track, this._track._backing.getFirstPacket(options), options); + } + /** Retrieves the track's first key packet (in decode order), or null if it has no key packets. */ + async getFirstKeyPacket(options = {}) { + validatePacketRetrievalOptions(options); + const firstPacket = await this.getFirstPacket(options); + if (!firstPacket) return null; + if (firstPacket.type === "key") return firstPacket; + return this.getNextKeyPacket(firstPacket, options); + } + /** + * Retrieves the packet corresponding to the given timestamp, in seconds. More specifically, returns the last packet + * (in presentation order) with a start timestamp less than or equal to the given timestamp. This method can be + * used to retrieve a track's last packet using `getPacket(Infinity)`. The method returns null if the timestamp + * is before the first packet in the track. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + */ + async getPacket(timestamp, options = {}) { + validateTimestamp(timestamp); + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + return maybeFixPacketType(this._track, this._track._backing.getPacket(timestamp, options), options); + } + /** + * Retrieves the packet following the given packet (in decode order), or null if the given packet is the + * last packet. + */ + async getNextPacket(packet, options = {}) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + return maybeFixPacketType(this._track, this._track._backing.getNextPacket(packet, options), options); + } + /** + * Retrieves the key packet corresponding to the given timestamp, in seconds. More specifically, returns the last + * key packet (in presentation order) with a start timestamp less than or equal to the given timestamp. A key packet + * is a packet that doesn't require previous packets to be decoded. This method can be used to retrieve a track's + * last key packet using `getKeyPacket(Infinity)`. The method returns null if the timestamp is before the first + * key packet in the track. + * + * To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + */ + async getKeyPacket(timestamp, options = {}) { + validateTimestamp(timestamp); + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + if (!options.verifyKeyPackets) return this._track._backing.getKeyPacket(timestamp, options); + const packet = await this._track._backing.getKeyPacket(timestamp, options); + if (!packet) return packet; + assert(packet.type === "key"); + if (await this._track.determinePacketType(packet) === "delta") return this.getKeyPacket(packet.timestamp - 1 / await this._track.getTimeResolution(), options); + return packet; + } + /** + * Retrieves the key packet following the given packet (in decode order), or null if the given packet is the last + * key packet. + * + * To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`. + */ + async getNextKeyPacket(packet, options = {}) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + if (!options.verifyKeyPackets) return this._track._backing.getNextKeyPacket(packet, options); + const nextPacket = await this._track._backing.getNextKeyPacket(packet, options); + if (!nextPacket) return nextPacket; + assert(nextPacket.type === "key"); + if (await this._track.determinePacketType(nextPacket) === "delta") return this.getNextKeyPacket(nextPacket, options); + return nextPacket; + } + /** + * Creates an async iterator that yields the packets in this track in decode order. To enable fast iteration, this + * method will intelligently preload packets based on the speed of the consumer. + * + * @param startPacket - (optional) The packet from which iteration should begin. This packet will also be yielded. + * @param endPacket - (optional) The packet at which iteration should end. This packet will _not_ be yielded. + */ + packets(startPacket, endPacket, options = {}) { + if (startPacket !== void 0 && !(startPacket instanceof EncodedPacket)) throw new TypeError("startPacket must be an EncodedPacket."); + if (startPacket !== void 0 && startPacket.isMetadataOnly && !options?.metadataOnly) throw new TypeError("startPacket can only be metadata-only if options.metadataOnly is enabled."); + if (endPacket !== void 0 && !(endPacket instanceof EncodedPacket)) throw new TypeError("endPacket must be an EncodedPacket."); + validatePacketRetrievalOptions(options); + if (this._track.input._disposed) throw new InputDisposedError(); + const packetQueue = []; + let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); + let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); + let ended = false; + let terminated = false; + let outOfBandError = null; + let hasOutOfBandError = false; + const timestamps = []; + const maxQueueSize = () => Math.max(2, timestamps.length); + (async () => { + let packet = startPacket ?? await this.getFirstPacket(options); + while (packet && !terminated && !this._track.input._disposed) { + if (endPacket && packet.sequenceNumber >= endPacket?.sequenceNumber) break; + if (packetQueue.length > maxQueueSize()) { + ({promise: queueDequeue, resolve: onQueueDequeue} = promiseWithResolvers()); + await queueDequeue; + continue; + } + packetQueue.push(packet); + onQueueNotEmpty(); + ({promise: queueNotEmpty, resolve: onQueueNotEmpty} = promiseWithResolvers()); + packet = await this.getNextPacket(packet, options); + } + ended = true; + onQueueNotEmpty(); + })().catch((error) => { + if (!hasOutOfBandError) { + outOfBandError = error; + hasOutOfBandError = true; + onQueueNotEmpty(); + } + }); + const track = this._track; + return { + async next() { + while (true) if (track.input._disposed) throw new InputDisposedError(); + else if (terminated) return { + value: void 0, + done: true + }; + else if (hasOutOfBandError) throw outOfBandError; + else if (packetQueue.length > 0) { + const value = packetQueue.shift(); + const now = performance.now(); + timestamps.push(now); + while (timestamps.length > 0 && now - timestamps[0] >= 1e3) timestamps.shift(); + onQueueDequeue(); + return { + value, + done: false + }; + } else if (ended) return { + value: void 0, + done: true + }; + else await queueNotEmpty; + }, + async return() { + terminated = true; + onQueueDequeue(); + onQueueNotEmpty(); + return { + value: void 0, + done: true + }; + }, + async throw(error) { + throw error; + }, + [Symbol.asyncIterator]() { + return this; + } + }; + } +}; +var DecoderWrapper = class { + constructor(onSample, onError) { + this.onSample = onSample; + this.onError = onError; + } +}; +/** +* Base class for decoded media sample sinks. +* @group Media sinks +* @public +*/ +var BaseMediaSampleSink = class { + /** @internal */ + mediaSamplesInRange(startTimestamp = -Infinity, endTimestamp = Infinity, options) { + validateTimestamp(startTimestamp); + validateTimestamp(endTimestamp); + const sampleQueue = []; + let firstSampleQueued = false; + let lastSample = null; + let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); + let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); + let decoderIsFlushed = false; + let ended = false; + let terminated = false; + let decoder = null; + let outOfBandError = null; + let hasOutOfBandError = false; + const packetRetrievalOptions = { + ...options, + verifyKeyPackets: true, + metadataOnly: false + }; + (async () => { + decoder = await this._createDecoder((sample) => { + onQueueDequeue(); + if (sample.timestamp >= endTimestamp) ended = true; + if (ended) { + sample.close(); + return; + } + if (lastSample) if (sample.timestamp > startTimestamp) { + sampleQueue.push(lastSample); + firstSampleQueued = true; + } else lastSample.close(); + if (sample.timestamp >= startTimestamp) { + sampleQueue.push(sample); + firstSampleQueued = true; + } + lastSample = firstSampleQueued ? null : sample; + if (sampleQueue.length > 0) { + onQueueNotEmpty(); + ({promise: queueNotEmpty, resolve: onQueueNotEmpty} = promiseWithResolvers()); + } + }, (error) => { + if (!hasOutOfBandError) { + outOfBandError = error; + hasOutOfBandError = true; + onQueueNotEmpty(); + } + }); + const packetSink = this._createPacketSink(); + const keyPacket = await packetSink.getKeyPacket(startTimestamp, packetRetrievalOptions) ?? await packetSink.getFirstKeyPacket(packetRetrievalOptions); + let currentPacket = keyPacket; + const packets = packetSink.packets(keyPacket ?? void 0, void 0, packetRetrievalOptions); + await packets.next(); + while (currentPacket && !ended && !this._track.input._disposed) { + const maxQueueSize = computeMaxQueueSize(sampleQueue.length); + if (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize) { + ({promise: queueDequeue, resolve: onQueueDequeue} = promiseWithResolvers()); + await queueDequeue; + continue; + } + decoder.decode(currentPacket); + const packetResult = await packets.next(); + if (packetResult.done) break; + currentPacket = packetResult.value; + } + await packets.return(); + if (!terminated && !this._track.input._disposed) await decoder.flush(); + if (!firstSampleQueued && lastSample) sampleQueue.push(lastSample); + decoderIsFlushed = true; + onQueueNotEmpty(); + })().catch((error) => { + if (!hasOutOfBandError) { + outOfBandError = error; + hasOutOfBandError = true; + onQueueNotEmpty(); + } + }).finally(() => { + decoder?.close(); + }); + const track = this._track; + const closeSamples = () => { + lastSample?.close(); + for (const sample of sampleQueue) sample.close(); + }; + return { + async next() { + while (true) if (track.input._disposed) { + terminated = true; + ended = true; + closeSamples(); + throw new InputDisposedError(); + } else if (terminated) return { + value: void 0, + done: true + }; + else if (hasOutOfBandError) { + terminated = true; + ended = true; + closeSamples(); + throw outOfBandError; + } else if (sampleQueue.length > 0) { + const value = sampleQueue.shift(); + onQueueDequeue(); + return { + value, + done: false + }; + } else if (!decoderIsFlushed) await queueNotEmpty; + else return { + value: void 0, + done: true + }; + }, + async return() { + terminated = true; + ended = true; + onQueueDequeue(); + onQueueNotEmpty(); + closeSamples(); + return { + value: void 0, + done: true + }; + }, + async throw(error) { + throw error; + }, + [Symbol.asyncIterator]() { + return this; + } + }; + } + /** @internal */ + mediaSamplesAtTimestamps(timestamps, options) { + validateAnyIterable(timestamps); + const timestampIterator = toAsyncIterator(timestamps); + const timestampsOfInterest = []; + const sampleQueue = []; + let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); + let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); + let decoderIsFlushed = false; + let terminated = false; + let decoder = null; + let outOfBandError = null; + let hasOutOfBandError = false; + const pushToQueue = (sample) => { + sampleQueue.push(sample); + onQueueNotEmpty(); + ({promise: queueNotEmpty, resolve: onQueueNotEmpty} = promiseWithResolvers()); + }; + const retrievalOptions = { + ...options, + verifyKeyPackets: true, + metadataOnly: false + }; + (async () => { + decoder = await this._createDecoder((sample) => { + onQueueDequeue(); + if (terminated) { + sample.close(); + return; + } + let sampleUses = 0; + while (timestampsOfInterest.length > 0 && sample.timestamp - timestampsOfInterest[0] > -1e-10) { + sampleUses++; + timestampsOfInterest.shift(); + } + if (sampleUses > 0) for (let i = 0; i < sampleUses; i++) pushToQueue(i < sampleUses - 1 ? sample.clone() : sample); + else sample.close(); + }, (error) => { + if (!hasOutOfBandError) { + outOfBandError = error; + hasOutOfBandError = true; + onQueueNotEmpty(); + } + }); + const packetSink = this._createPacketSink(); + let lastPacket = null; + let lastKeyPacket = null; + let maxSequenceNumber = -1; + const decodePackets = async () => { + assert(lastKeyPacket); + assert(decoder); + let currentPacket = lastKeyPacket; + decoder.decode(currentPacket); + while (currentPacket.sequenceNumber < maxSequenceNumber) { + const maxQueueSize = computeMaxQueueSize(sampleQueue.length); + while (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize && !terminated) { + ({promise: queueDequeue, resolve: onQueueDequeue} = promiseWithResolvers()); + await queueDequeue; + } + if (terminated) break; + const nextPacket = await packetSink.getNextPacket(currentPacket, retrievalOptions); + assert(nextPacket); + decoder.decode(nextPacket); + currentPacket = nextPacket; + } + maxSequenceNumber = -1; + }; + const flushDecoder = async () => { + assert(decoder); + await decoder.flush(); + for (let i = 0; i < timestampsOfInterest.length; i++) pushToQueue(null); + timestampsOfInterest.length = 0; + }; + for await (const timestamp of timestampIterator) { + validateTimestamp(timestamp); + if (terminated || this._track.input._disposed) break; + const targetPacket = await packetSink.getPacket(timestamp, retrievalOptions); + const keyPacket = targetPacket && await packetSink.getKeyPacket(timestamp, retrievalOptions); + if (!keyPacket) { + if (maxSequenceNumber !== -1) { + await decodePackets(); + await flushDecoder(); + } + pushToQueue(null); + lastPacket = null; + continue; + } + if (lastPacket && (keyPacket.sequenceNumber !== lastKeyPacket.sequenceNumber || targetPacket.timestamp < lastPacket.timestamp)) { + await decodePackets(); + await flushDecoder(); + } + timestampsOfInterest.push(targetPacket.timestamp); + maxSequenceNumber = Math.max(targetPacket.sequenceNumber, maxSequenceNumber); + lastPacket = targetPacket; + lastKeyPacket = keyPacket; + } + if (!terminated && !this._track.input._disposed) { + if (maxSequenceNumber !== -1) await decodePackets(); + await flushDecoder(); + } + decoderIsFlushed = true; + onQueueNotEmpty(); + })().catch((error) => { + if (!hasOutOfBandError) { + outOfBandError = error; + hasOutOfBandError = true; + onQueueNotEmpty(); + } + }).finally(() => { + decoder?.close(); + }); + const track = this._track; + const closeSamples = () => { + for (const sample of sampleQueue) sample?.close(); + }; + return { + async next() { + while (true) if (track.input._disposed) { + terminated = true; + closeSamples(); + throw new InputDisposedError(); + } else if (terminated) return { + value: void 0, + done: true + }; + else if (hasOutOfBandError) { + terminated = true; + closeSamples(); + throw outOfBandError; + } else if (sampleQueue.length > 0) { + const value = sampleQueue.shift(); + assert(value !== void 0); + onQueueDequeue(); + return { + value, + done: false + }; + } else if (!decoderIsFlushed) await queueNotEmpty; + else return { + value: void 0, + done: true + }; + }, + async return() { + terminated = true; + onQueueDequeue(); + onQueueNotEmpty(); + closeSamples(); + return { + value: void 0, + done: true + }; + }, + async throw(error) { + throw error; + }, + [Symbol.asyncIterator]() { + return this; + } + }; + } +}; +var computeMaxQueueSize = (decodedSampleQueueSize) => { + return decodedSampleQueueSize === 0 ? 40 : 8; +}; +var VideoDecoderWrapper = class extends DecoderWrapper { + constructor(onSample, onError, codec, decoderConfig, rotation, flip, timeResolution) { + super(onSample, onError); + this.codec = codec; + this.decoderConfig = decoderConfig; + this.rotation = rotation; + this.flip = flip; + this.timeResolution = timeResolution; + this.decoder = null; + this.customDecoder = null; + this.customDecoderCallSerializer = new CallSerializer(); + this.customDecoderQueueSize = 0; + this.inputTimestamps = []; + this.sampleQueue = []; + this.currentPacketIndex = 0; + this.raslSkipped = false; + this.alphaDecoder = null; + this.alphaHadKeyframe = false; + this.colorQueue = []; + this.alphaQueue = []; + this.merger = null; + this.decodedAlphaChunkCount = 0; + this.alphaDecoderQueueSize = 0; + /** Each value is the number of decoded alpha chunks at which a null alpha frame should be added. */ + this.nullAlphaFrameQueue = []; + this.currentAlphaPacketIndex = 0; + this.alphaRaslSkipped = false; + this.finalSamples = []; + this.mergeAlphaPromises = []; + const MatchingCustomDecoder = customVideoDecoders.find((x) => x.supports(codec, decoderConfig)); + if (MatchingCustomDecoder) { + this.customDecoder = new MatchingCustomDecoder(); + this.customDecoder.codec = codec; + this.customDecoder.config = decoderConfig; + this.customDecoder.onSample = (sample) => { + if (!(sample instanceof VideoSample)) throw new TypeError("The argument passed to onSample must be a VideoSample."); + this.finalizeAndEmitSample(sample); + }; + this.customDecoder.onError = (error) => { + onError(error); + }; + this.customDecoderCallSerializer.call(() => this.customDecoder.init()).catch((error) => onError(error)); + } else { + const colorHandler = (frame) => { + if (this.alphaQueue.length > 0) { + const alphaFrame = this.alphaQueue.shift(); + assert(alphaFrame !== void 0); + this.mergeAlpha(frame, alphaFrame); + } else this.colorQueue.push(frame); + }; + if (isChromium()) { + if (codec === "avc" && this.decoderConfig.description) { + const record = deserializeAvcDecoderConfigurationRecord(toUint8Array(this.decoderConfig.description)); + if (record && record.sequenceParameterSets.length > 0) { + const sps = parseAvcSps(record.sequenceParameterSets[0]); + if (sps) { + if (sps.frameMbsOnlyFlag === 0) this.decoderConfig = { + ...this.decoderConfig, + hardwareAcceleration: "prefer-software" + }; + if (sps.maxDecFrameBuffering !== 0 && sps.bitstreamRestrictionFlag !== 1) { + record.sequenceParameterSets[0] = addAvcBitstreamRestriction(sps); + this.decoderConfig = { + ...this.decoderConfig, + description: serializeAvcDecoderConfigurationRecord(record) + }; + } + } + } + } + if (!colorSpaceIsComplete(this.decoderConfig.colorSpace)) this.decoderConfig = { + ...this.decoderConfig, + colorSpace: { + primaries: this.decoderConfig.colorSpace?.primaries ?? "bt709", + matrix: this.decoderConfig.colorSpace?.matrix ?? "bt709", + transfer: this.decoderConfig.colorSpace?.transfer ?? "bt709", + fullRange: this.decoderConfig.colorSpace?.fullRange ?? false + } + }; + } + const stack = (/* @__PURE__ */ new Error("Decoding error")).stack; + this.decoder = new VideoDecoder({ + output: (frame) => { + try { + colorHandler(frame); + } catch (error) { + this.onError(error); + } + }, + error: (error) => { + error.stack = stack; + this.onError(error); + } + }); + this.decoder.configure(this.decoderConfig); + } + } + getDecodeQueueSize() { + if (this.customDecoder) return this.customDecoderQueueSize; + else { + assert(this.decoder); + return Math.max(this.decoder.decodeQueueSize, this.alphaDecoder?.decodeQueueSize ?? 0); + } + } + decode(packet) { + if (this.codec === "hevc" && this.currentPacketIndex > 0 && !this.raslSkipped) { + if (this.hasHevcRaslPicture(packet.data)) return; + this.raslSkipped = true; + } + if (this.customDecoder) { + this.customDecoderQueueSize++; + this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--); + } else { + assert(this.decoder); + if (!isWebKit()) insertSorted(this.inputTimestamps, packet.timestamp, (x) => x); + if (isChromium() && this.currentPacketIndex === 0) { + if (this.codec === "avc") { + const filteredNalUnits = []; + let hasFrameData = false; + for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) { + const type = extractNalUnitTypeForAvc(packet.data[loc.offset]); + hasFrameData ||= type >= 1 && type <= 5; + if (type === AvcNalUnitType.AUD) if (hasFrameData) break; + else filteredNalUnits.length = 0; + if (!(type >= 20 && type <= 31)) filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length)); + } + if (!this.decoderConfig.description) for (let i = 0; i < filteredNalUnits.length; i++) { + const nalUnit = filteredNalUnits[i]; + if (extractNalUnitTypeForAvc(nalUnit[0]) !== AvcNalUnitType.SPS) continue; + const sps = parseAvcSps(nalUnit); + if (sps && sps.maxDecFrameBuffering !== 0 && sps.bitstreamRestrictionFlag !== 1) filteredNalUnits[i] = addAvcBitstreamRestriction(sps); + break; + } + packet = new EncodedPacket(concatAvcNalUnits(filteredNalUnits, this.decoderConfig), packet.type, packet.timestamp, packet.duration); + } else if (this.codec === "hevc") { + const sanitizedData = sanitizeHevcPacketForChromium(packet.data, this.decoderConfig); + if (sanitizedData) packet = new EncodedPacket(sanitizedData, packet.type, packet.timestamp, packet.duration); + } + } + this.decoder.decode(packet.toEncodedVideoChunk()); + this.decodeAlphaData(packet); + } + this.currentPacketIndex++; + } + decodeAlphaData(packet) { + if (!packet.sideData.alpha) { + this.pushNullAlphaFrame(); + return; + } + if (!this.merger) this.merger = new ColorAlphaMerger(); + if (!this.alphaDecoder) { + const alphaHandler = (frame) => { + if (this.colorQueue.length > 0) { + const colorFrame = this.colorQueue.shift(); + assert(colorFrame !== void 0); + this.mergeAlpha(colorFrame, frame); + } else this.alphaQueue.push(frame); + this.decodedAlphaChunkCount++; + while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) { + this.nullAlphaFrameQueue.shift(); + if (this.colorQueue.length > 0) { + const colorFrame = this.colorQueue.shift(); + assert(colorFrame !== void 0); + this.mergeAlpha(colorFrame, null); + } else this.alphaQueue.push(null); + } + this.alphaDecoderQueueSize--; + }; + const stack = (/* @__PURE__ */ new Error("Decoding error")).stack; + this.alphaDecoder = new VideoDecoder({ + output: (frame) => { + try { + alphaHandler(frame); + } catch (error) { + this.onError(error); + } + }, + error: (error) => { + error.stack = stack; + this.onError(error); + } + }); + this.alphaDecoder.configure({ + ...this.decoderConfig, + colorSpace: { + fullRange: true, + matrix: "bt709", + primaries: "bt709", + transfer: "bt709" + } + }); + } + const type = determineVideoPacketType(this.codec, this.decoderConfig, packet.sideData.alpha); + if (!this.alphaHadKeyframe) this.alphaHadKeyframe = type === "key"; + if (this.alphaHadKeyframe) { + if (this.codec === "hevc" && this.currentAlphaPacketIndex > 0 && !this.alphaRaslSkipped) { + if (this.hasHevcRaslPicture(packet.sideData.alpha)) { + this.pushNullAlphaFrame(); + return; + } + this.alphaRaslSkipped = true; + } + this.currentAlphaPacketIndex++; + this.alphaDecoder.decode(packet.alphaToEncodedVideoChunk(type ?? packet.type)); + this.alphaDecoderQueueSize++; + } else this.pushNullAlphaFrame(); + } + pushNullAlphaFrame() { + if (this.alphaDecoderQueueSize === 0) this.alphaQueue.push(null); + else this.nullAlphaFrameQueue.push(this.decodedAlphaChunkCount + this.alphaDecoderQueueSize); + } + /** + * If we're using HEVC, we need to make sure to skip any RASL slices that follow a non-IDR key frame such as + * CRA_NUT. This is because RASL slices cannot be decoded without data before the CRA_NUT. Browsers behave + * differently here: Chromium drops the packets, Safari throws a decoder error. Either way, it's not good + * and causes bugs upstream. So, let's take the dropping into our own hands. + */ + hasHevcRaslPicture(packetData) { + for (const loc of iterateHevcNalUnits(packetData, this.decoderConfig)) { + const type = extractNalUnitTypeForHevc(packetData[loc.offset]); + if (type === HevcNalUnitType.RASL_N || type === HevcNalUnitType.RASL_R) return true; + } + return false; + } + /** Handler for the WebCodecs VideoDecoder for ironing out browser differences. */ + sampleHandler(sample) { + if (isWebKit()) { + if (this.sampleQueue.length > 0 && sample.timestamp >= last(this.sampleQueue).timestamp) { + for (const sample of this.sampleQueue) this.finalizeAndEmitSample(sample); + this.sampleQueue.length = 0; + } + insertSorted(this.sampleQueue, sample, (x) => x.timestamp); + } else { + const timestamp = this.inputTimestamps.shift(); + assert(timestamp !== void 0); + sample.setTimestamp(timestamp); + this.finalizeAndEmitSample(sample); + } + } + finalizeAndEmitSample(sample) { + sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution); + sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution); + sample.setRotation(this.rotation); + sample.setFlip(this.flip); + this.onSample(sample); + } + async mergeAlpha(color, alpha) { + const resolver = promiseWithResolvers(); + this.mergeAlphaPromises.push(resolver.promise); + const result = { sample: null }; + this.finalSamples.push(result); + try { + if (!alpha) result.sample = new VideoSample(color); + else { + assert(this.merger); + result.sample = new VideoSample(await this.merger.merge(color, alpha)); + } + while (this.finalSamples.length > 0 && this.finalSamples[0].sample !== null) { + const next = this.finalSamples.shift(); + this.sampleHandler(next.sample); + } + } catch (error) { + removeItem(this.finalSamples, result); + this.onError(error); + } finally { + removeItem(this.mergeAlphaPromises, resolver.promise); + resolver.resolve(); + } + } + async flush() { + if (this.customDecoder) await this.customDecoderCallSerializer.call(() => this.customDecoder.flush()); + else { + assert(this.decoder); + await Promise.all([this.decoder.flush(), this.alphaDecoder?.flush()]); + await Promise.all(this.mergeAlphaPromises); + this.colorQueue.forEach((x) => x.close()); + this.colorQueue.length = 0; + this.alphaQueue.forEach((x) => x?.close()); + this.alphaQueue.length = 0; + this.alphaHadKeyframe = false; + this.decodedAlphaChunkCount = 0; + this.alphaDecoderQueueSize = 0; + this.nullAlphaFrameQueue.length = 0; + this.currentAlphaPacketIndex = 0; + this.alphaRaslSkipped = false; + } + if (isWebKit()) { + for (const sample of this.sampleQueue) this.finalizeAndEmitSample(sample); + this.sampleQueue.length = 0; + } + this.currentPacketIndex = 0; + this.raslSkipped = false; + } + close() { + if (this.customDecoder) this.customDecoderCallSerializer.call(() => this.customDecoder.close()); + else { + assert(this.decoder); + if (this.decoder.state !== "closed") this.decoder.close(); + if (this.alphaDecoder && this.alphaDecoder.state !== "closed") this.alphaDecoder.close(); + this.colorQueue.forEach((x) => x.close()); + this.colorQueue.length = 0; + this.alphaQueue.forEach((x) => x?.close()); + this.alphaQueue.length = 0; + this.merger?.close(); + } + for (const sample of this.sampleQueue) sample.close(); + this.sampleQueue.length = 0; + } +}; +var mergerWorkerUrl = null; +/** Utility class that merges together color and alpha information on the CPU in a pool of workers. */ +var ColorAlphaMerger = class { + constructor() { + this.workers = []; + this.nextWorkerIndex = 0; + this.pendingRequests = /* @__PURE__ */ new Map(); + this.nextRequestId = 0; + } + merge(color, alpha) { + if (this.workers.length === 0) { + if (!mergerWorkerUrl) { + const blob = new Blob([`(${colorAlphaMergerWorkerCode.toString()})()`], { type: "application/javascript" }); + mergerWorkerUrl = URL.createObjectURL(blob); + } + const poolSize = clamp(navigator.hardwareConcurrency, 1, 4); + for (let i = 0; i < poolSize; i++) { + const worker = new Worker(mergerWorkerUrl); + worker.addEventListener("message", (event) => { + const data = event.data; + const pending = this.pendingRequests.get(data.id); + if (!pending) return; + this.pendingRequests.delete(data.id); + if ("error" in data) pending.reject(new Error(data.error)); + else pending.resolve(data.frame); + }); + worker.addEventListener("error", (event) => { + const error = new Error(event.message || "Color/alpha merge worker error."); + for (const pending of this.pendingRequests.values()) pending.reject(error); + this.pendingRequests.clear(); + }); + this.workers.push(worker); + } + } + const id = this.nextRequestId++; + const pending = promiseWithResolvers(); + this.pendingRequests.set(id, pending); + const worker = this.workers[this.nextWorkerIndex]; + this.nextWorkerIndex = (this.nextWorkerIndex + 1) % this.workers.length; + worker.postMessage({ + id, + color, + alpha + }, { transfer: [color, alpha] }); + return pending.promise; + } + close() { + for (const worker of this.workers) worker.terminate(); + this.workers.length = 0; + const error = /* @__PURE__ */ new Error("Color/alpha merger closed."); + for (const pending of this.pendingRequests.values()) pending.reject(error); + this.pendingRequests.clear(); + } +}; +var colorAlphaMergerWorkerCode = () => { + let cpuAlphaBuffer = null; + let cpuColorBuffer = null; + let chain = Promise.resolve(); + self.addEventListener("message", (event) => { + const { id, color, alpha } = event.data; + chain = chain.then(async () => { + try { + const frame = await merge(color, alpha); + self.postMessage({ + id, + frame + }, { transfer: [frame] }); + } catch (error) { + self.postMessage({ + id, + error: error.message + }); + } finally { + color.close(); + alpha.close(); + } + }); + }); + const merge = async (color, alpha) => { + const format = color.format; + const alphaFormat = alpha.format; + if (!format || !alphaFormat) throw new Error("CPU color/alpha merging requires a known VideoFrame format."); + const colorIs10 = format.includes("P10"); + const colorIs12 = format.includes("P12"); + const alphaIs10 = alphaFormat.includes("P10"); + const alphaIs12 = alphaFormat.includes("P12"); + if (alphaIs10 !== colorIs10 || alphaIs12 !== colorIs12) throw new Error(`CPU color/alpha merging requires the alpha frame to have the same bit depth as the color frame (color: '${format}', alpha: '${alphaFormat}').`); + if (format === "RGBX" || format === "RGBA" || format === "BGRX" || format === "BGRA") return await mergeInterleavedRgba(color, alpha, format); + else if (format === "I420" || format === "I420P10" || format === "I420P12" || format === "I422" || format === "I422P10" || format === "I422P12" || format === "I444" || format === "I444P10" || format === "I444P12") return await mergePlanarYuv(color, alpha, format); + else if (format === "NV12") return await mergeNv12(color, alpha); + throw new Error(`CPU color/alpha merging does not support format '${format}'.`); + }; + const mergeInterleavedRgba = async (color, alpha, format) => { + const width = color.visibleRect?.width ?? color.codedWidth; + const height = color.visibleRect?.height ?? color.codedHeight; + const pixelCount = width * height; + const output = new Uint8Array(pixelCount * 4); + await color.copyTo(output); + const alphaY = await readAlpha(alpha, width, height, 1); + for (let i = 0, j = 3; i < pixelCount; i++, j += 4) output[j] = alphaY[i]; + const init = { + format: format === "RGBX" || format === "RGBA" ? "RGBA" : "BGRA", + codedWidth: width, + codedHeight: height, + timestamp: color.timestamp, + duration: color.duration ?? void 0, + transfer: [output.buffer] + }; + return new VideoFrame(output, init); + }; + const mergePlanarYuv = async (color, alpha, format) => { + const width = color.visibleRect?.width ?? color.codedWidth; + const height = color.visibleRect?.height ?? color.codedHeight; + const is10 = format.includes("P10"); + const is12 = format.includes("P12"); + const bytesPerSample = is10 || is12 ? 2 : 1; + let chromaW; + let chromaH; + if (format.startsWith("I420")) { + chromaW = Math.ceil(width / 2); + chromaH = Math.ceil(height / 2); + } else if (format.startsWith("I422")) { + chromaW = Math.ceil(width / 2); + chromaH = height; + } else { + chromaW = width; + chromaH = height; + } + const ySamples = width * height; + const uvSamples = chromaW * chromaH; + const yBytes = ySamples * bytesPerSample; + const uvBytes = uvSamples * bytesPerSample; + const aBytes = ySamples * bytesPerSample; + const outputBytes = yBytes + 2 * uvBytes + aBytes; + const output = new Uint8Array(outputBytes); + await color.copyTo(output); + const alphaY = await readAlpha(alpha, width, height, bytesPerSample); + const aOffset = yBytes + 2 * uvBytes; + output.set(alphaY, aOffset); + const init = { + format: format.slice(0, 4) + "A" + format.slice(4), + codedWidth: width, + codedHeight: height, + timestamp: color.timestamp, + duration: color.duration ?? void 0, + transfer: [output.buffer] + }; + return new VideoFrame(output, init); + }; + const mergeNv12 = async (color, alpha) => { + const width = color.visibleRect?.width ?? color.codedWidth; + const height = color.visibleRect?.height ?? color.codedHeight; + const ySize = width * height; + const uvSize = Math.ceil(width / 2) * Math.ceil(height / 2); + const sourceSize = color.allocationSize(); + if (!cpuColorBuffer || cpuColorBuffer.byteLength !== sourceSize) cpuColorBuffer = new Uint8Array(sourceSize); + await color.copyTo(cpuColorBuffer); + const output = new Uint8Array(ySize + 2 * uvSize + ySize); + output.set(cpuColorBuffer.subarray(0, ySize), 0); + const uOffset = ySize; + const vOffset = ySize + uvSize; + const uvStart = ySize; + for (let i = 0; i < uvSize; i++) { + output[uOffset + i] = cpuColorBuffer[uvStart + i * 2]; + output[vOffset + i] = cpuColorBuffer[uvStart + i * 2 + 1]; + } + const alphaY = await readAlpha(alpha, width, height, 1); + output.set(alphaY, ySize + 2 * uvSize); + const init = { + format: "I420A", + codedWidth: width, + codedHeight: height, + timestamp: color.timestamp, + duration: color.duration ?? void 0, + transfer: [output.buffer] + }; + return new VideoFrame(output, init); + }; + const readAlpha = async (alpha, width, height, bytesPerSample) => { + const size = alpha.allocationSize(); + if (!cpuAlphaBuffer || cpuAlphaBuffer.byteLength !== size) cpuAlphaBuffer = new Uint8Array(size); + await alpha.copyTo(cpuAlphaBuffer); + const format = alpha.format; + if (format === "RGBA" || format === "BGRA" || format === "RGBX" || format === "BGRX") { + const rOffset = format === "RGBA" || format === "RGBX" ? 0 : 2; + const pixelCount = width * height; + for (let i = 0; i < pixelCount; i++) cpuAlphaBuffer[i] = cpuAlphaBuffer[i * 4 + rOffset]; + return cpuAlphaBuffer.subarray(0, pixelCount); + } else { + const yPlane = cpuAlphaBuffer.subarray(0, width * height * bytesPerSample); + if (alpha.colorSpace.fullRange === false) { + const bitDepth = format.includes("P12") ? 12 : format.includes("P10") ? 10 : 8; + const low = 16 << bitDepth - 8; + const high = 235 << bitDepth - 8; + const max = (1 << bitDepth) - 1; + const samples = bytesPerSample === 2 ? new Uint16Array(yPlane.buffer, 0, width * height) : yPlane; + for (let i = 0; i < samples.length; i++) { + const value = Math.round((samples[i] - low) * max / (high - low)); + samples[i] = Math.min(Math.max(value, 0), max); + } + } + return yPlane; + } + }; +}; +var validateVideoSinkDecoderOptions = (decoderOptions) => { + if (!decoderOptions || typeof decoderOptions !== "object") throw new TypeError("decoderOptions must be an object."); + if (decoderOptions.hardwareAcceleration !== void 0 && ![ + "no-preference", + "prefer-hardware", + "prefer-software" + ].includes(decoderOptions.hardwareAcceleration)) throw new TypeError("decoderOptions.hardwareAcceleration, when provided, must be 'no-preference', 'prefer-hardware' or 'prefer-software'."); + if (decoderOptions.optimizeForLatency !== void 0 && typeof decoderOptions.optimizeForLatency !== "boolean") throw new TypeError("decoderOptions.optimizeForLatency, when provided, must be a boolean."); +}; +/** +* A sink that retrieves decoded video samples (video frames) from a video track. +* @group Media sinks +* @public +*/ +var VideoSampleSink = class extends BaseMediaSampleSink { + /** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */ + constructor(videoTrack, decoderOptions = {}) { + if (!(videoTrack instanceof InputVideoTrack)) throw new TypeError("videoTrack must be an InputVideoTrack."); + validateVideoSinkDecoderOptions(decoderOptions); + super(); + this._track = videoTrack; + this._decoderOptions = decoderOptions; + } + /** @internal */ + async _createDecoder(onSample, onError) { + if (!await this._track.canDecode()) { + if (typeof VideoDecoder === "undefined") throw new Error(missingWebCodecsClassMessage("VideoDecoder")); + throw new Error("This video track cannot be decoded in this environment. Make sure to check decodability before using a track."); + } + const codec = await this._track.getCodec(); + const rotation = await this._track.getRotation(); + const flip = await this._track.getFlip(); + let decoderConfig = await this._track.getDecoderConfig(); + const timeResolution = await this._track.getTimeResolution(); + assert(codec && decoderConfig); + decoderConfig = { + ...decoderConfig, + hardwareAcceleration: this._decoderOptions.hardwareAcceleration, + optimizeForLatency: this._decoderOptions.optimizeForLatency + }; + return new VideoDecoderWrapper(onSample, onError, codec, decoderConfig, rotation, flip, timeResolution); + } + /** @internal */ + _createPacketSink() { + return new EncodedPacketSink(this._track); + } + /** + * Retrieves the video sample (frame) corresponding to the given timestamp, in seconds. More specifically, returns + * the last video sample (in presentation order) with a start timestamp less than or equal to the given timestamp. + * Returns null if the timestamp is before the track's first timestamp. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + async getSample(timestamp, options = {}) { + validateTimestamp(timestamp); + for await (const sample of this.mediaSamplesAtTimestamps([timestamp], options)) return sample; + throw new Error("Internal error: Iterator returned nothing."); + } + /** + * Creates an async iterator that yields the video samples (frames) of this track in presentation order. This method + * will intelligently pre-decode a few frames ahead to enable fast iteration. + * + * @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive). + * @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive). + * @param options - Options used for the underlying packet retrieval. + */ + samples(startTimestamp, endTimestamp, options = {}) { + return this.mediaSamplesInRange(startTimestamp, endTimestamp, options); + } + /** + * Creates an async iterator that yields a video sample (frame) for each timestamp in the argument. This method + * uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most + * once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may + * yield null if no frame is available for a given timestamp. + * + * This method is good for sparse access of media data. If you want primarily sequential media access, prefer + * {@link VideoSampleSink.samples} instead. + * + * @param timestamps - An iterable or async iterable of timestamps in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + samplesAtTimestamps(timestamps, options = {}) { + return this.mediaSamplesAtTimestamps(timestamps, options); + } +}; +/** +* A sink that renders video samples (frames) of the given video track to canvases. This is often more useful than +* directly retrieving frames, as it comes with common preprocessing steps such as resizing or applying rotation and +* flip metadata. +* +* This sink will yield `HTMLCanvasElement`s when in a DOM context, and `OffscreenCanvas`es otherwise. +* +* @group Media sinks +* @public +*/ +var CanvasSink = class { + /** Creates a new {@link CanvasSink} for the given {@link InputVideoTrack}. */ + constructor(videoTrack, options = {}) { + /** @internal */ + this._rotation = 0; + /** @internal */ + this._flip = false; + /** @internal */ + this._initPromise = null; + /** @internal */ + this._nextCanvasIndex = 0; + if (!(videoTrack instanceof InputVideoTrack)) throw new TypeError("videoTrack must be an InputVideoTrack."); + if (options && typeof options !== "object") throw new TypeError("options must be an object."); + if (options.alpha !== void 0 && typeof options.alpha !== "boolean") throw new TypeError("options.alpha, when provided, must be a boolean."); + if (options.width !== void 0 && (!Number.isInteger(options.width) || options.width <= 0)) throw new TypeError("options.width, when defined, must be a positive integer."); + if (options.height !== void 0 && (!Number.isInteger(options.height) || options.height <= 0)) throw new TypeError("options.height, when defined, must be a positive integer."); + if (options.fit !== void 0 && ![ + "fill", + "contain", + "cover" + ].includes(options.fit)) throw new TypeError("options.fit, when provided, must be one of \"fill\", \"contain\", or \"cover\"."); + if (options.width !== void 0 && options.height !== void 0 && options.fit === void 0) throw new TypeError("When both options.width and options.height are provided, options.fit must also be provided."); + if (options.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(options.rotation)) throw new TypeError("options.rotation, when provided, must be 0, 90, 180 or 270."); + if (options.flip !== void 0 && typeof options.flip !== "boolean") throw new TypeError("options.flip, when provided, must be a boolean."); + if (options.crop !== void 0) validateCropRectangle(options.crop, "options."); + if (options.poolSize !== void 0 && (typeof options.poolSize !== "number" || !Number.isInteger(options.poolSize) || options.poolSize < 0)) throw new TypeError("poolSize must be a non-negative integer."); + if (options.decoderOptions !== void 0) validateVideoSinkDecoderOptions(options.decoderOptions); + this._videoTrack = videoTrack; + this._alpha = options.alpha ?? false; + this._options = options; + this._fit = options.fit ?? "fill"; + this._videoSampleSink = new VideoSampleSink(videoTrack, options.decoderOptions); + this._canvasPool = Array.from({ length: options.poolSize ?? 0 }, () => null); + } + /** @internal */ + _ensureInit() { + return this._initPromise ??= (async () => { + const options = this._options; + const videoTrack = this._videoTrack; + const rotation = options.rotation ?? await videoTrack.getRotation(); + const flip = options.flip ?? await videoTrack.getFlip(); + const squarePixelWidth = await videoTrack.getSquarePixelWidth(); + const squarePixelHeight = await videoTrack.getSquarePixelHeight(); + const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [squarePixelWidth, squarePixelHeight] : [squarePixelHeight, squarePixelWidth]; + let crop = options.crop; + if (crop) crop = clampCropRectangle(crop, rotatedWidth, rotatedHeight); + let [width, height] = crop ? [crop.width, crop.height] : [rotatedWidth, rotatedHeight]; + const originalAspectRatio = width / height; + if (options.width !== void 0 && options.height === void 0) { + width = options.width; + height = Math.round(width / originalAspectRatio); + } else if (options.width === void 0 && options.height !== void 0) { + height = options.height; + width = Math.round(height * originalAspectRatio); + } else if (options.width !== void 0 && options.height !== void 0) { + width = options.width; + height = options.height; + } + this._width = width; + this._height = height; + this._rotation = rotation; + this._flip = flip; + this._crop = crop; + })(); + } + /** @internal */ + _videoSampleToWrappedCanvas(sample) { + const width = this._width; + const height = this._height; + let canvas = this._canvasPool[this._nextCanvasIndex]; + let canvasIsNew = false; + if (!canvas) { + if (typeof document !== "undefined") { + canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + } else canvas = new OffscreenCanvas(width, height); + if (this._canvasPool.length > 0) this._canvasPool[this._nextCanvasIndex] = canvas; + canvasIsNew = true; + } + if (this._canvasPool.length > 0) this._nextCanvasIndex = (this._nextCanvasIndex + 1) % this._canvasPool.length; + const context = canvas.getContext("2d", { alpha: this._alpha || isFirefox() }); + assert(context); + sample._drawWithFitAndMipmapping(canvas, context, { + fit: this._fit, + rotation: this._rotation, + flip: this._flip, + crop: this._crop, + targetIsFresh: canvasIsNew, + fillBlack: !this._alpha && isFirefox() + }); + const result = { + canvas, + timestamp: sample.timestamp, + duration: sample.duration + }; + sample.close(); + return result; + } + /** + * Retrieves a canvas with the video frame corresponding to the given timestamp, in seconds. More specifically, + * returns the last video frame (in presentation order) with a start timestamp less than or equal to the given + * timestamp. Returns null if the timestamp is before the track's first timestamp. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + async getCanvas(timestamp, options) { + validateTimestamp(timestamp); + await this._ensureInit(); + const sample = await this._videoSampleSink.getSample(timestamp, options); + return sample && this._videoSampleToWrappedCanvas(sample); + } + /** + * Creates an async iterator that yields canvases with the video frames of this track in presentation order. This + * method will intelligently pre-decode a few frames ahead to enable fast iteration. + * + * @param startTimestamp - The timestamp in seconds at which to start yielding canvases (inclusive). + * @param endTimestamp - The timestamp in seconds at which to stop yielding canvases (exclusive). + * @param options - Options used for the underlying packet retrieval. + */ + async *canvases(startTimestamp, endTimestamp, options) { + await this._ensureInit(); + yield* mapAsyncGenerator(this._videoSampleSink.samples(startTimestamp, endTimestamp, options), (sample) => this._videoSampleToWrappedCanvas(sample)); + } + /** + * Creates an async iterator that yields a canvas for each timestamp in the argument. This method uses an optimized + * decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most once, and is + * therefore more efficient than manually getting the canvas for every timestamp. The iterator may yield null if + * no frame is available for a given timestamp. + * + * This method is good for sparse access of media data. If you want primarily sequential media access, prefer + * {@link CanvasSink.canvases} instead. + * + * @param timestamps - An iterable or async iterable of timestamps in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + async *canvasesAtTimestamps(timestamps, options) { + await this._ensureInit(); + yield* mapAsyncGenerator(this._videoSampleSink.samplesAtTimestamps(timestamps, options), (sample) => sample && this._videoSampleToWrappedCanvas(sample)); + } +}; +var AudioDecoderWrapper = class extends DecoderWrapper { + constructor(onSample, onError, codec, decoderConfig) { + super(onSample, onError); + this.decoder = null; + this.customDecoder = null; + this.customDecoderCallSerializer = new CallSerializer(); + this.customDecoderQueueSize = 0; + this.currentTimestamp = null; + this.expectedFirstTimestamp = null; + this.timestampOffset = 0; + const sampleHandler = (sample) => { + let sampleTimestamp = sample.timestamp; + if (this.expectedFirstTimestamp !== null && this.currentTimestamp === null) this.timestampOffset = this.expectedFirstTimestamp - sampleTimestamp; + sampleTimestamp += this.timestampOffset; + if (this.currentTimestamp === null || Math.abs(sampleTimestamp - this.currentTimestamp) >= sample.duration) this.currentTimestamp = sampleTimestamp; + const preciseTimestamp = this.currentTimestamp; + this.currentTimestamp += sample.duration; + if (sample.numberOfFrames === 0) { + sample.close(); + return; + } + const sampleRate = decoderConfig.sampleRate; + sample.setTimestamp(Math.round(preciseTimestamp * sampleRate) / sampleRate); + onSample(sample); + }; + const MatchingCustomDecoder = customAudioDecoders.find((x) => x.supports(codec, decoderConfig)); + if (MatchingCustomDecoder) { + this.customDecoder = new MatchingCustomDecoder(); + this.customDecoder.codec = codec; + this.customDecoder.config = decoderConfig; + this.customDecoder.onSample = (sample) => { + if (!(sample instanceof AudioSample)) throw new TypeError("The argument passed to onSample must be an AudioSample."); + sampleHandler(sample); + }; + this.customDecoder.onError = (error) => { + onError(error); + }; + this.customDecoderCallSerializer.call(() => this.customDecoder.init()).catch((error) => onError(error)); + } else { + const stack = (/* @__PURE__ */ new Error("Decoding error")).stack; + this.decoder = new AudioDecoder({ + output: (data) => { + try { + sampleHandler(new AudioSample(data)); + } catch (error) { + this.onError(error); + } + }, + error: (error) => { + error.stack = stack; + this.onError(error); + } + }); + this.decoder.configure(decoderConfig); + } + } + getDecodeQueueSize() { + if (this.customDecoder) return this.customDecoderQueueSize; + else { + assert(this.decoder); + return this.decoder.decodeQueueSize; + } + } + decode(packet) { + if (this.customDecoder) { + this.customDecoderQueueSize++; + this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--); + } else { + assert(this.decoder); + this.expectedFirstTimestamp ??= packet.timestamp; + this.decoder.decode(packet.toEncodedAudioChunk()); + } + } + async flush() { + if (this.customDecoder) await this.customDecoderCallSerializer.call(() => this.customDecoder.flush()); + else { + assert(this.decoder); + await this.decoder.flush(); + } + this.currentTimestamp = null; + this.expectedFirstTimestamp = null; + this.timestampOffset = 0; + } + close() { + if (this.customDecoder) this.customDecoderCallSerializer.call(() => this.customDecoder.close()); + else { + assert(this.decoder); + if (this.decoder.state !== "closed") this.decoder.close(); + } + } +}; +var PcmAudioDecoderWrapper = class extends DecoderWrapper { + constructor(onSample, onError, decoderConfig) { + super(onSample, onError); + this.decoderConfig = decoderConfig; + this.currentTimestamp = null; + assert(PCM_AUDIO_CODECS.includes(decoderConfig.codec)); + this.codec = decoderConfig.codec; + const { dataType, sampleSize, littleEndian } = parsePcmCodec(this.codec); + this.inputSampleSize = sampleSize; + switch (sampleSize) { + case 1: + if (dataType === "unsigned") this.readInputValue = (view, byteOffset) => view.getUint8(byteOffset) - 2 ** 7; + else if (dataType === "signed") this.readInputValue = (view, byteOffset) => view.getInt8(byteOffset); + else if (dataType === "ulaw") this.readInputValue = (view, byteOffset) => fromUlaw(view.getUint8(byteOffset)); + else if (dataType === "alaw") this.readInputValue = (view, byteOffset) => fromAlaw(view.getUint8(byteOffset)); + else assert(false); + break; + case 2: + if (dataType === "unsigned") this.readInputValue = (view, byteOffset) => view.getUint16(byteOffset, littleEndian) - 2 ** 15; + else if (dataType === "signed") this.readInputValue = (view, byteOffset) => view.getInt16(byteOffset, littleEndian); + else assert(false); + break; + case 3: + if (dataType === "unsigned") this.readInputValue = (view, byteOffset) => getUint24(view, byteOffset, littleEndian) - 2 ** 23; + else if (dataType === "signed") this.readInputValue = (view, byteOffset) => getInt24(view, byteOffset, littleEndian); + else assert(false); + break; + case 4: + if (dataType === "unsigned") this.readInputValue = (view, byteOffset) => view.getUint32(byteOffset, littleEndian) - 2 ** 31; + else if (dataType === "signed") this.readInputValue = (view, byteOffset) => view.getInt32(byteOffset, littleEndian); + else if (dataType === "float") this.readInputValue = (view, byteOffset) => view.getFloat32(byteOffset, littleEndian); + else assert(false); + break; + case 8: + if (dataType === "float") this.readInputValue = (view, byteOffset) => view.getFloat64(byteOffset, littleEndian); + else assert(false); + break; + default: + assertNever(sampleSize); + assert(false); + } + switch (sampleSize) { + case 1: + if (dataType === "ulaw" || dataType === "alaw") { + this.outputSampleSize = 2; + this.outputFormat = "s16"; + this.writeOutputValue = (view, byteOffset, value) => view.setInt16(byteOffset, value, true); + } else { + this.outputSampleSize = 1; + this.outputFormat = "u8"; + this.writeOutputValue = (view, byteOffset, value) => view.setUint8(byteOffset, value + 2 ** 7); + } + break; + case 2: + this.outputSampleSize = 2; + this.outputFormat = "s16"; + this.writeOutputValue = (view, byteOffset, value) => view.setInt16(byteOffset, value, true); + break; + case 3: + this.outputSampleSize = 4; + this.outputFormat = "s32"; + this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value << 8, true); + break; + case 4: + this.outputSampleSize = 4; + if (dataType === "float") { + this.outputFormat = "f32"; + this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, true); + } else { + this.outputFormat = "s32"; + this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value, true); + } + break; + case 8: + this.outputSampleSize = 4; + this.outputFormat = "f32"; + this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, true); + break; + default: + assertNever(sampleSize); + assert(false); + } + } + getDecodeQueueSize() { + return 0; + } + decode(packet) { + const inputView = toDataView(packet.data); + const numberOfFrames = packet.byteLength / this.decoderConfig.numberOfChannels / this.inputSampleSize; + const outputBufferSize = numberOfFrames * this.decoderConfig.numberOfChannels * this.outputSampleSize; + const outputBuffer = new ArrayBuffer(outputBufferSize); + const outputView = new DataView(outputBuffer); + for (let i = 0; i < numberOfFrames * this.decoderConfig.numberOfChannels; i++) { + const inputIndex = i * this.inputSampleSize; + const outputIndex = i * this.outputSampleSize; + const value = this.readInputValue(inputView, inputIndex); + this.writeOutputValue(outputView, outputIndex, value); + } + const preciseDuration = numberOfFrames / this.decoderConfig.sampleRate; + if (this.currentTimestamp === null || Math.abs(packet.timestamp - this.currentTimestamp) >= preciseDuration) this.currentTimestamp = packet.timestamp; + const preciseTimestamp = this.currentTimestamp; + this.currentTimestamp += preciseDuration; + const audioSample = new AudioSample({ + format: this.outputFormat, + data: outputBuffer, + numberOfChannels: this.decoderConfig.numberOfChannels, + sampleRate: this.decoderConfig.sampleRate, + numberOfFrames, + timestamp: preciseTimestamp + }); + this.onSample(audioSample); + } + async flush() {} + close() {} +}; +/** +* Sink for retrieving decoded audio samples from an audio track. +* @group Media sinks +* @public +*/ +var AudioSampleSink = class extends BaseMediaSampleSink { + /** Creates a new {@link AudioSampleSink} for the given {@link InputAudioTrack}. */ + constructor(audioTrack) { + if (!(audioTrack instanceof InputAudioTrack)) throw new TypeError("audioTrack must be an InputAudioTrack."); + super(); + this._track = audioTrack; + } + /** @internal */ + async _createDecoder(onSample, onError) { + if (!await this._track.canDecode()) { + if (typeof AudioDecoder === "undefined") throw new Error(missingWebCodecsClassMessage("AudioDecoder")); + throw new Error("This audio track cannot be decoded in this environment. Make sure to check decodability before using a track."); + } + const codec = await this._track.getCodec(); + const decoderConfig = await this._track.getDecoderConfig(); + assert(codec && decoderConfig); + if (PCM_AUDIO_CODECS.includes(decoderConfig.codec)) return new PcmAudioDecoderWrapper(onSample, onError, decoderConfig); + else return new AudioDecoderWrapper(onSample, onError, codec, decoderConfig); + } + /** @internal */ + _createPacketSink() { + return new EncodedPacketSink(this._track); + } + /** + * Retrieves the audio sample corresponding to the given timestamp, in seconds. More specifically, returns + * the last audio sample (in presentation order) with a start timestamp less than or equal to the given timestamp. + * Returns null if the timestamp is before the track's first timestamp. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + async getSample(timestamp, options = {}) { + validateTimestamp(timestamp); + for await (const sample of this.mediaSamplesAtTimestamps([timestamp], options)) return sample; + throw new Error("Internal error: Iterator returned nothing."); + } + /** + * Creates an async iterator that yields the audio samples of this track in presentation order. This method + * will intelligently pre-decode a few samples ahead to enable fast iteration. + * + * @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive). + * @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive). + * @param options - Options used for the underlying packet retrieval. + */ + samples(startTimestamp, endTimestamp, options = {}) { + return this.mediaSamplesInRange(startTimestamp, endTimestamp, options); + } + /** + * Creates an async iterator that yields an audio sample for each timestamp in the argument. This method + * uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most + * once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may + * yield null if no sample is available for a given timestamp. + * + * This method is good for sparse access of media data. If you want primarily sequential media access, prefer + * {@link AudioSampleSink.samples} instead. + * + * @param timestamps - An iterable or async iterable of timestamps in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + samplesAtTimestamps(timestamps, options = {}) { + return this.mediaSamplesAtTimestamps(timestamps, options); + } +}; +/** +* A sink that retrieves decoded audio samples from an audio track and converts them to `AudioBuffer` instances. This is +* often more useful than directly retrieving audio samples, as audio buffers can be directly used with the +* Web Audio API. +* @group Media sinks +* @public +*/ +var AudioBufferSink = class { + /** Creates a new {@link AudioBufferSink} for the given {@link InputAudioTrack}. */ + constructor(audioTrack) { + if (!(audioTrack instanceof InputAudioTrack)) throw new TypeError("audioTrack must be an InputAudioTrack."); + this._audioSampleSink = new AudioSampleSink(audioTrack); + } + /** @internal */ + _audioSampleToWrappedArrayBuffer(sample) { + const result = { + buffer: sample.toAudioBuffer(), + timestamp: sample.timestamp, + duration: sample.duration + }; + sample.close(); + return result; + } + /** + * Retrieves the audio buffer corresponding to the given timestamp, in seconds. More specifically, returns + * the last audio buffer (in presentation order) with a start timestamp less than or equal to the given timestamp. + * Returns null if the timestamp is before the track's first timestamp. + * + * @param timestamp - The timestamp used for retrieval, in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + async getBuffer(timestamp, options) { + validateTimestamp(timestamp); + const data = await this._audioSampleSink.getSample(timestamp, options); + return data && this._audioSampleToWrappedArrayBuffer(data); + } + /** + * Creates an async iterator that yields audio buffers of this track in presentation order. This method + * will intelligently pre-decode a few buffers ahead to enable fast iteration. + * + * @param startTimestamp - The timestamp in seconds at which to start yielding buffers (inclusive). + * @param endTimestamp - The timestamp in seconds at which to stop yielding buffers (exclusive). + * @param options - Options used for the underlying packet retrieval. + */ + buffers(startTimestamp, endTimestamp, options) { + return mapAsyncGenerator(this._audioSampleSink.samples(startTimestamp, endTimestamp, options), (data) => this._audioSampleToWrappedArrayBuffer(data)); + } + /** + * Creates an async iterator that yields an audio buffer for each timestamp in the argument. This method + * uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most + * once, and is therefore more efficient than manually getting the buffer for every timestamp. The iterator may + * yield null if no buffer is available for a given timestamp. + * + * @param timestamps - An iterable or async iterable of timestamps in seconds. + * @param options - Options used for the underlying packet retrieval. + */ + buffersAtTimestamps(timestamps, options) { + return mapAsyncGenerator(this._audioSampleSink.samplesAtTimestamps(timestamps, options), (data) => data && this._audioSampleToWrappedArrayBuffer(data)); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/input-track.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Represents a media track in an input file. +* @group Input files & tracks +* @public +*/ +var InputTrack = class InputTrack { + /** @internal */ + constructor(input, backing) { + this.input = input; + this._backing = backing; + } + /** Returns true if and only if this track is a video track. */ + isVideoTrack() { + return this instanceof InputVideoTrack; + } + /** Returns true if and only if this track is an audio track. */ + isAudioTrack() { + return this instanceof InputAudioTrack; + } + /** The unique ID of this track in the input file. */ + get id() { + return this._backing.getId(); + } + /** + * The 1-based index of this track among all tracks of the same type in the input file. For example, the first + * video track has number 1, the second video track has number 2, and so on. The index refers to the order in + * which the tracks are returned by {@link Input.getTracks}. + */ + get number() { + return this._backing.getNumber(); + } + /** + * Returns the identifier of the codec used internally by the container. It is not homogenized by Mediabunny + * and depends entirely on the container format. + * + * This method can be used to determine the codec of a track in case Mediabunny doesn't know that codec. + * + * - For ISOBMFF files, this resolves to the name of the Sample Description Box (e.g. `'avc1'`). + * - For Matroska files, this resolves to the value of the `CodecID` element. + * - For WAVE files, this resolves to the value of the format tag in the `'fmt '` chunk. + * - For ADTS files, this resolves to the `MPEG-4 Audio Object Type`. + * - For MPEG-TS files, this resolves to the `streamType` value from the Program Map Table. + * - In all other cases, this resolves to `null`. + */ + async getInternalCodecId() { + return this._backing.getInternalCodecId(); + } + /** + * See {@link InputTrack.getInternalCodecId}. + * @deprecated Use {@link InputTrack.getInternalCodecId} instead. + */ + get internalCodecId() { + return requireSync(this._backing.getInternalCodecId(), "internalCodecId", "getInternalCodecId"); + } + /** + * Returns the ISO 639-2/T language code for this track. If the language is unknown, this resolves to `'und'` + * (undetermined). + */ + async getLanguageCode() { + return this._backing.getLanguageCode(); + } + /** + * The ISO 639-2/T language code for this track. If the language is unknown, this field is `'und'` (undetermined). + * @deprecated Use {@link InputTrack.getLanguageCode} instead. + */ + get languageCode() { + return requireSync(this._backing.getLanguageCode(), "languageCode", "getLanguageCode"); + } + /** Returns the user-defined name for this track. */ + async getName() { + return this._backing.getName(); + } + /** + * A user-defined name for this track. + * @deprecated Use {@link InputTrack.getName} instead. + */ + get name() { + return requireSync(this._backing.getName(), "name", "getName"); + } + /** + * Returns a positive number x such that all timestamps and durations of all packets of this track are + * integer multiples of 1/x. + */ + async getTimeResolution() { + return this._backing.getTimeResolution(); + } + /** + * A positive number x such that all timestamps and durations of all packets of this track are + * integer multiples of 1/x. + * @deprecated Use {@link InputTrack.getTimeResolution} instead. + */ + get timeResolution() { + return requireSync(this._backing.getTimeResolution(), "timeResolution", "getTimeResolution"); + } + /** + * Returns whether the timestamps of this track are relative to the Unix epoch (January 1, 1970 00:00:00 UTC). + * When `true`, each timestamp maps to a definitive point in time. + */ + async isRelativeToUnixEpoch() { + return this._backing.isRelativeToUnixEpoch(); + } + /** + * Returns the Unix time (in seconds since January 1, 1970 00:00:00 UTC) that the given track timestamp (in seconds) + * maps to, or `null` if there is no such mapping. This provides a piecewise-continuous mapping from this track's + * timestamp space into wall-clock time. Such mapping exists, for example, for HLS playlists with + * `#EXT-X-PROGRAM-DATE-TIME` tags present. + * + * This mapping can be available even when {@link InputTrack.isRelativeToUnixEpoch} is `false`, for example for HLS + * streams with program date time information but with {@link HlsInputFormatOptions.offsetTimestampsByDateTime} + * set to `false`. + */ + async getUnixTimeForTimestamp(timestamp) { + return this._backing.getUnixTimeForTimestamp(timestamp); + } + /** + * Whether the track's timestamps can be mapped to Unix wall clock time via + * {@link InputTrack.getUnixTimeForTimestamp}. + */ + async hasUnixTimeMapping() { + return await this._backing.getUnixTimeForTimestamp(await this.getFirstTimestamp()) !== null; + } + /** Returns the track's disposition, i.e. information about its intended usage. */ + async getDisposition() { + return this._backing.getDisposition(); + } + /** + * The track's disposition, i.e. information about its intended usage. + * @deprecated Use {@link InputTrack.getDisposition} instead. + */ + get disposition() { + return requireSync(this._backing.getDisposition(), "disposition", "getDisposition"); + } + /** + * Returns the peak bitrate of the track in bits per second, as specified in the track's metadata. This might not + * match the actual media data's bitrate. + */ + async getBitrate() { + return this._backing.getBitrate(); + } + /** + * Returns the average bitrate of the track in bits per second, as specified in the track's metadata. This might + * not match the actual media data's bitrate. + */ + async getAverageBitrate() { + return this._backing.getAverageBitrate(); + } + /** + * Returns the start timestamp of the first packet of this track, in seconds. While often near zero, this value + * may be positive or even negative. A negative starting timestamp means the track's timing has been offset. Samples + * with a negative timestamp should not be presented. + */ + async getFirstTimestamp() { + return (await this._backing.getFirstPacket({ metadataOnly: true }))?.timestamp ?? 0; + } + /** + * Returns the end timestamp of the last packet of this track, in seconds. + * + * By default, when the underlying media is live, this method will only resolve once the live stream ends. If you + * want to query the current end timestamp of the stream, set {@link PacketRetrievalOptions.skipLiveWait} to `true` + * in the options. + */ + async computeDuration(options) { + const lastPacket = await this._backing.getPacket(Infinity, { + metadataOnly: true, + ...options + }); + return roundToDivisor((lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0), await this.getTimeResolution()); + } + /** + * Gets the duration (end timestamp) in seconds of this track from metadata stored in the file. This value may be + * approximate or diverge from the actual, precise duration returned by `.computeDuration()`, but compared to that + * method, this method is cheaper. When the duration cannot be determined from the file metadata, `null` + * is returned. + * + * By default, when the underlying media is live, this method will only resolve once the live stream + * ends. If you want to query the current duration of the media, set + * {@link DurationMetadataRequestOptions.skipLiveWait} to `true` in the options. + */ + async getDurationFromMetadata(options = {}) { + return this._backing.getDurationFromMetadata(options); + } + /** + * Computes aggregate packet statistics for this track, such as average packet rate or bitrate. + * + * @param targetPacketCount - This optional parameter sets a target for how many packets this method must have + * looked at before it can return early; this means, you can use it to aggregate only a subset (prefix) of all + * packets. This is very useful for getting a great estimate of video frame rate without having to scan through the + * entire file. + * + * By default, when the underlying media is live and `targetPacketCount` is not set, this method will only resolve + * once the live stream ends. If you want to query the current packet statistics of the stream, set + * {@link PacketRetrievalOptions.skipLiveWait} to `true` in the options. + */ + async computePacketStats(targetPacketCount = Infinity, options) { + const sink = new EncodedPacketSink(this); + let startTimestamp = Infinity; + let endTimestamp = -Infinity; + let packetCount = 0; + let totalPacketBytes = 0; + for await (const packet of sink.packets(void 0, void 0, { + metadataOnly: true, + ...options + })) { + if (packetCount >= targetPacketCount && packet.timestamp >= endTimestamp) break; + startTimestamp = Math.min(startTimestamp, packet.timestamp); + endTimestamp = Math.max(endTimestamp, packet.timestamp + packet.duration); + packetCount++; + totalPacketBytes += packet.byteLength; + } + return { + packetCount, + averagePacketRate: packetCount ? Number((packetCount / (endTimestamp - startTimestamp)).toPrecision(16)) : 0, + averageBitrate: packetCount ? Number((8 * totalPacketBytes / (endTimestamp - startTimestamp)).toPrecision(16)) : 0 + }; + } + /** + * Whether or not this track is currently live, meaning the media's end is still unknown. + * + * The value returned by this method may change over time as the track stops being live. To keep track of the + * track's live status, poll this method at the track's refresh interval + * via {@link InputTrack.getLiveRefreshInterval}. + */ + async isLive() { + return await this._backing.getLiveRefreshInterval() !== null; + } + /** + * Returns the track's live refresh interval in seconds, or `null` if the track is not live. This interval describes + * the time it takes, on average, for new live media data to become available. + */ + async getLiveRefreshInterval() { + return this._backing.getLiveRefreshInterval(); + } + /** + * Returns `true` if this track can be paired with the given track. Two tracks being pairable means they can be + * presented (displayed) together. + * + * Returns `false` if `other` equals `this`. + */ + canBePairedWith(other) { + if (!(other instanceof InputTrack)) throw new TypeError("other must be an InputTrack."); + if (this.input !== other.input || this === other) return false; + return (this._backing.getPairingMask() & other._backing.getPairingMask()) !== 0n; + } + /** + * Gets the list of other tracks that can be paired with this track. An optional query can be provided to narrow + * down the results. + */ + async getPairableTracks(query) { + return this.input.getTracks(mergeInputTrackQueries({ filter: (t) => t.canBePairedWith(this) }, query)); + } + /** + * Gets the list of other video tracks that can be paired with this track. An optional query can be provided to + * narrow down the results. + */ + async getPairableVideoTracks(query) { + return this.input.getVideoTracks(mergeInputTrackQueries({ filter: (t) => t.canBePairedWith(this) }, query)); + } + /** + * Gets the list of other audio tracks that can be paired with this track. An optional query can be provided to + * narrow down the results. + */ + async getPairableAudioTracks(query) { + return this.input.getAudioTracks(mergeInputTrackQueries({ filter: (t) => t.canBePairedWith(this) }, query)); + } + /** Returns the primary track that can be paired with this track, optionally steered by the provided query. */ + async getPrimaryPairableVideoTrack(query) { + return this.input.getPrimaryVideoTrack(mergeInputTrackQueries({ filter: (t) => t.canBePairedWith(this) }, query)); + } + /** Returns the primary track that can be paired with this track, optionally steered by the provided query. */ + async getPrimaryPairableAudioTrack(query) { + return this.input.getPrimaryAudioTrack(mergeInputTrackQueries({ filter: (t) => t.canBePairedWith(this) }, query)); + } + /** Returns `true` if there is another track that can be paired with this track. */ + async hasPairableTrack(predicate) { + predicate &&= toValidatedPredicate(predicate); + const tracks = await this.input.getTracks(); + for (const track of tracks) { + if (!this.canBePairedWith(track)) continue; + if (!predicate || await predicate(track)) return true; + } + return false; + } + /** Returns `true` if there is a video track that can be paired with this track. */ + hasPairableVideoTrack(predicate) { + predicate &&= toValidatedPredicate(predicate); + return this.hasPairableTrack(async (x) => x.isVideoTrack() && (!predicate || await predicate(x))); + } + /** Returns `true` if there is an audio track that can be paired with this track. */ + hasPairableAudioTrack(predicate) { + predicate &&= toValidatedPredicate(predicate); + return this.hasPairableTrack(async (x) => x.isAudioTrack() && (!predicate || await predicate(x))); + } +}; +var requireSync = (value, getterName, asyncName) => { + if (isThenable(value)) throw new Error(`'${getterName}' is deprecated and not available synchronously for this track. Use the preferred '${asyncName}()' instead.`); + return value; +}; +var toValidatedPredicate = (predicate) => { + if (predicate !== void 0 && typeof predicate !== "function") throw new TypeError("predicate, when provided, must be a function."); + return predicate ? (track) => { + const handle = (result) => { + if (typeof result !== "boolean") throw new TypeError("predicate must return or resolve to a boolean value."); + return result; + }; + const result = predicate(track); + if (isThenable(result)) return result.then(handle); + return handle(result); + } : void 0; +}; +/** +* Represents a video track in an input file. +* @group Input files & tracks +* @public +*/ +var InputVideoTrack = class extends InputTrack { + /** @internal */ + constructor(input, backing) { + super(input, backing); + /** @internal */ + this._pixelAspectRatioCache = null; + } + get type() { + return "video"; + } + /** The codec of the track's packets. */ + async getCodec() { + return this._backing.getCodec(); + } + /** + * The codec of the track's packets. + * @deprecated Use {@link InputVideoTrack.getCodec} instead. + */ + get codec() { + return requireSync(this._backing.getCodec(), "codec", "getCodec"); + } + async hasOnlyKeyPackets() { + return await this._backing.getHasOnlyKeyPackets?.() ?? await this._backing.getCodec() === "prores"; + } + /** Returns the width in pixels of the track's coded samples, before any transformations or rotations. */ + async getCodedWidth() { + return this._backing.getCodedWidth(); + } + /** + * The width in pixels of the track's coded samples, before any transformations or rotations. + * @deprecated Use {@link InputVideoTrack.getCodedWidth} instead. + */ + get codedWidth() { + return requireSync(this._backing.getCodedWidth(), "codedWidth", "getCodedWidth"); + } + /** Returns the height in pixels of the track's coded samples, before any transformations or rotations. */ + async getCodedHeight() { + return this._backing.getCodedHeight(); + } + /** + * The height in pixels of the track's coded samples, before any transformations or rotations. + * @deprecated Use {@link InputVideoTrack.getCodedHeight} instead. + */ + get codedHeight() { + return requireSync(this._backing.getCodedHeight(), "codedHeight", "getCodedHeight"); + } + /** + * Returns the row-major 3x3 affine transformation matrix that is used to transform the raw video frames before they + * are displayed. The raw frames are assumed to be placed with their top-left corner at the origin (0,0). + * + * Usually, this matrix models a rotation and a flip. Depending on the file, it may also contain a translation + * component. When displaying video, you should typically ignore the translation component and display the center of + * the transformed frame in the center of the viewport. + */ + async getTransformationMatrix() { + return this._backing.getTransformationMatrix(); + } + /** + * Returns the angle in degrees by which the track's frames should be rotated (clockwise). The rotation is + * applied before flipping. + */ + async getRotation() { + return extractRotationFromMatrix(await this._backing.getTransformationMatrix()); + } + /** + * The angle in degrees by which the track's frames should be rotated (clockwise). + * @deprecated Use {@link InputVideoTrack.getRotation} instead. + */ + get rotation() { + return extractRotationFromMatrix(requireSync(this._backing.getTransformationMatrix(), "rotation", "getRotation")); + } + /** + * Returns whether the track's frames should be flipped horizontally (about the vertical axis), after rotation. + */ + async getFlip() { + return matrixIsFlipped(await this._backing.getTransformationMatrix()); + } + /** + * Returns the width of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. + */ + async getSquarePixelWidth() { + return this._backing.getSquarePixelWidth(); + } + /** + * The width of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. + * @deprecated Use {@link InputVideoTrack.getSquarePixelWidth} instead. + */ + get squarePixelWidth() { + return requireSync(this._backing.getSquarePixelWidth(), "squarePixelWidth", "getSquarePixelWidth"); + } + /** + * Returns the height of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. + */ + async getSquarePixelHeight() { + return this._backing.getSquarePixelHeight(); + } + /** + * The height of the track's frames in square pixels, adjusted for pixel aspect ratio but before rotation. + * @deprecated Use {@link InputVideoTrack.getSquarePixelHeight} instead. + */ + get squarePixelHeight() { + return requireSync(this._backing.getSquarePixelHeight(), "squarePixelHeight", "getSquarePixelHeight"); + } + /** + * Returns the pixel aspect ratio of the track's frames as a rational number in its reduced form. Most videos use + * square pixels (1:1). + */ + async getPixelAspectRatio() { + return this._pixelAspectRatioCache ??= simplifyRational({ + num: await this.getSquarePixelWidth() * await this.getCodedHeight(), + den: await this.getSquarePixelHeight() * await this.getCodedWidth() + }); + } + /** + * The pixel aspect ratio of the track's frames, as a rational number in its reduced form. Most videos use + * square pixels (1:1). + * @deprecated Use {@link InputVideoTrack.getPixelAspectRatio} instead. + */ + get pixelAspectRatio() { + return this._pixelAspectRatioCache ??= simplifyRational({ + num: requireSync(this._backing.getSquarePixelWidth(), "pixelAspectRatio", "getPixelAspectRatio") * requireSync(this._backing.getCodedHeight(), "pixelAspectRatio", "getPixelAspectRatio"), + den: requireSync(this._backing.getSquarePixelHeight(), "pixelAspectRatio", "getPixelAspectRatio") * requireSync(this._backing.getCodedWidth(), "pixelAspectRatio", "getPixelAspectRatio") + }); + } + /** Returns the display width of the track's frames in pixels, after aspect ratio adjustment and rotation. */ + async getDisplayWidth() { + const metadata = await this._backing.getMetadataDisplayWidth?.(); + if (metadata != null) return metadata; + return await this.getRotation() % 180 === 0 ? this.getSquarePixelWidth() : this.getSquarePixelHeight(); + } + /** + * The display width of the track's frames in pixels, after aspect ratio adjustment and rotation. + * @deprecated Use {@link InputVideoTrack.getDisplayWidth} instead. + */ + get displayWidth() { + const metadataRaw = this._backing.getMetadataDisplayWidth?.(); + if (metadataRaw !== void 0) { + const metadata = requireSync(metadataRaw, "displayWidth", "getDisplayWidth"); + if (metadata !== null) return metadata; + } + return requireSync(extractRotationFromMatrix(requireSync(this._backing.getTransformationMatrix(), "displayWidth", "getDisplayWidth")) % 180 === 0 ? this._backing.getSquarePixelWidth() : this._backing.getSquarePixelHeight(), "displayWidth", "getDisplayWidth"); + } + /** Returns the display height of the track's frames in pixels, after aspect ratio adjustment and rotation. */ + async getDisplayHeight() { + const metadata = await this._backing.getMetadataDisplayHeight?.(); + if (metadata != null) return metadata; + return await this.getRotation() % 180 === 0 ? this.getSquarePixelHeight() : this.getSquarePixelWidth(); + } + /** + * The display height of the track's frames in pixels, after aspect ratio adjustment and rotation. + * @deprecated Use {@link InputVideoTrack.getDisplayHeight} instead. + */ + get displayHeight() { + const metadataRaw = this._backing.getMetadataDisplayHeight?.(); + if (metadataRaw !== void 0) { + const metadata = requireSync(metadataRaw, "displayHeight", "getDisplayHeight"); + if (metadata !== null) return metadata; + } + return requireSync(extractRotationFromMatrix(requireSync(this._backing.getTransformationMatrix(), "displayHeight", "getDisplayHeight")) % 180 === 0 ? this._backing.getSquarePixelHeight() : this._backing.getSquarePixelWidth(), "displayHeight", "getDisplayHeight"); + } + /** Returns the color space of the track's samples. */ + async getColorSpace() { + return this._backing.getColorSpace(); + } + /** If this method returns true, the track's samples use a high dynamic range (HDR). */ + async hasHighDynamicRange() { + const colorSpace = await this._backing.getColorSpace(); + return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pq" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl"; + } + /** Checks if this track may contain transparent samples with alpha data. */ + async canBeTransparent() { + return this._backing.canBeTransparent(); + } + /** + * Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#video-decoder-config) for decoding the + * track's packets using a [`VideoDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder). Returns + * null if the track's codec is unknown. + */ + async getDecoderConfig() { + return this._backing.getDecoderConfig(); + } + async getCodecParameterString() { + const fromMetadata = await this._backing.getMetadataCodecParameterString?.(); + if (fromMetadata != null) return fromMetadata; + return (await this._backing.getDecoderConfig())?.codec ?? null; + } + async canDecode() { + try { + const decoderConfig = await this._backing.getDecoderConfig(); + if (!decoderConfig) return false; + const codec = await this._backing.getCodec(); + assert(codec !== null); + if (customVideoDecoders.some((x) => x.supports(codec, decoderConfig))) return true; + if (typeof VideoDecoder === "undefined") return false; + return (await VideoDecoder.isConfigSupported(decoderConfig)).supported === true; + } catch (error) { + Logging._error("Error during decodability check:", error); + return false; + } + } + async determinePacketType(packet) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + if (packet.isMetadataOnly) throw new TypeError("packet must not be metadata-only to determine its type."); + const codec = await this.getCodec(); + if (codec === null) return null; + const decoderConfig = await this.getDecoderConfig(); + assert(decoderConfig); + return determineVideoPacketType(codec, decoderConfig, packet.data); + } + /** + * Computes frame rate metrics for this video track, i.e. estimates the video's frame rate. Frame rate is never + * determined from file metadata (which is unreliable) but is always deduced directly from the actual frame + * timestamps. + */ + async computeFrameRateMetrics(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.targetPacketCount !== void 0 && (!isNumber(options.targetPacketCount) || options.targetPacketCount < 0)) throw new TypeError("options.targetPacketCount must be a non-negative number."); + const timeResolution = await this.getTimeResolution(); + const targetPacketCount = options.targetPacketCount ?? 256; + const sink = new EncodedPacketSink(this); + const timestamps = []; + let maxTimestamp = -Infinity; + let probedPacketCount = 0; + for await (const packet of sink.packets(void 0, void 0, { metadataOnly: true })) { + if (timestamps.length >= targetPacketCount && packet.timestamp >= maxTimestamp) break; + timestamps.push(packet.timestamp); + maxTimestamp = Math.max(maxTimestamp, packet.timestamp); + probedPacketCount++; + } + const ticks = new Float64Array(timestamps.length); + for (let i = 0; i < timestamps.length; i++) ticks[i] = Math.round(timestamps[i] * timeResolution); + ticks.sort(); + let n = 1; + for (let i = 1; i < ticks.length; i++) if (ticks[i] !== ticks[n - 1]) ticks[n++] = ticks[i]; + if (n < 2) return { + underlyingFrameRate: null, + bestGuessFrameRate: timeResolution, + minFrameRate: timeResolution, + maxFrameRate: timeResolution, + averageFrameRate: timeResolution, + medianFrameRate: timeResolution, + frameRateIsConstant: true, + probedPacketCount + }; + const activeTicks = ticks.subarray(0, n); + const underlyingFrameRate = findUnderlyingFrameRate(activeTicks, timeResolution); + const unitRate = underlyingFrameRate ?? timeResolution; + const ticksPerFrame = underlyingFrameRate !== null ? timeResolution / underlyingFrameRate : null; + const histogram = /* @__PURE__ */ new Map(); + let minDifference = Infinity; + let maxDifference = -Infinity; + let totalDifference = 0; + for (let i = 1; i < n; i++) { + const tickDifference = activeTicks[i] - activeTicks[i - 1]; + const difference = ticksPerFrame !== null ? Math.max(1, Math.round(tickDifference / ticksPerFrame)) : tickDifference; + histogram.set(difference, (histogram.get(difference) ?? 0) + 1); + minDifference = Math.min(minDifference, difference); + maxDifference = Math.max(maxDifference, difference); + totalDifference += difference; + } + const differenceCount = n - 1; + const sortedDifferences = [...histogram.keys()].sort((a, b) => a - b); + const middleA = differenceCount - 1 >> 1; + const middleB = differenceCount >> 1; + let medianDifferenceA = 0; + let medianDifferenceB = 0; + let cumulativeCount = 0; + for (const difference of sortedDifferences) { + cumulativeCount += histogram.get(difference); + if (medianDifferenceA === 0 && cumulativeCount > middleA) medianDifferenceA = difference; + if (cumulativeCount > middleB) { + medianDifferenceB = difference; + break; + } + } + const medianFrameRate = (unitRate / medianDifferenceA + unitRate / medianDifferenceB) / 2; + return { + underlyingFrameRate, + bestGuessFrameRate: underlyingFrameRate !== null ? underlyingFrameRate : getBestGuessFrameRate(medianFrameRate), + minFrameRate: unitRate / maxDifference, + maxFrameRate: unitRate / minDifference, + averageFrameRate: unitRate * differenceCount / totalDifference, + medianFrameRate, + frameRateIsConstant: underlyingFrameRate !== null && minDifference === 1 && maxDifference === 1, + probedPacketCount + }; + } +}; +/** +* Represents an audio track in an input file. +* @group Input files & tracks +* @public +*/ +var InputAudioTrack = class extends InputTrack { + /** @internal */ + constructor(input, backing) { + super(input, backing); + } + get type() { + return "audio"; + } + /** The codec of the track's packets. */ + async getCodec() { + return this._backing.getCodec(); + } + /** + * The codec of the track's packets. + * @deprecated Use {@link InputAudioTrack.getCodec} instead. + */ + get codec() { + return requireSync(this._backing.getCodec(), "codec", "getCodec"); + } + async hasOnlyKeyPackets() { + return await this._backing.getHasOnlyKeyPackets?.() ?? true; + } + /** Returns the number of audio channels in the track. */ + async getNumberOfChannels() { + return this._backing.getNumberOfChannels(); + } + /** + * The number of audio channels in the track. + * @deprecated Use {@link InputAudioTrack.getNumberOfChannels} instead. + */ + get numberOfChannels() { + return requireSync(this._backing.getNumberOfChannels(), "numberOfChannels", "getNumberOfChannels"); + } + /** Returns the track's audio sample rate in hertz. */ + async getSampleRate() { + return this._backing.getSampleRate(); + } + /** + * The track's audio sample rate in hertz. + * @deprecated Use {@link InputAudioTrack.getSampleRate} instead. + */ + get sampleRate() { + return requireSync(this._backing.getSampleRate(), "sampleRate", "getSampleRate"); + } + /** + * Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#audio-decoder-config) for decoding the + * track's packets using an [`AudioDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/AudioDecoder). Returns + * null if the track's codec is unknown. + */ + async getDecoderConfig() { + return this._backing.getDecoderConfig(); + } + async getCodecParameterString() { + const fromMetadata = await this._backing.getMetadataCodecParameterString?.(); + if (fromMetadata != null) return fromMetadata; + return (await this._backing.getDecoderConfig())?.codec ?? null; + } + async canDecode() { + try { + const decoderConfig = await this._backing.getDecoderConfig(); + if (!decoderConfig) return false; + const codec = await this._backing.getCodec(); + assert(codec !== null); + if (customAudioDecoders.some((x) => x.supports(codec, decoderConfig))) return true; + if (PCM_AUDIO_CODECS.includes(decoderConfig.codec)) return true; + else { + if (typeof AudioDecoder === "undefined") return false; + return (await AudioDecoder.isConfigSupported(decoderConfig)).supported === true; + } + } catch (error) { + Logging._error("Error during decodability check:", error); + return false; + } + } + async determinePacketType(packet) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + if (await this.getCodec() === null) return null; + return "key"; + } +}; +/** +* Helper function for use in {@link InputTrackQuery.sortBy}, used to describe sorting tracks by a numeric property in +* ascending order. `null` and `undefined` are accepted too and are last in the order (sorted to the end). +* +* @group Input files & tracks +* @public +*/ +var asc = (value) => { + return value ?? Infinity; +}; +/** +* Helper function for use in {@link InputTrackQuery.sortBy}, used to describe sorting tracks by a numeric property in +* descending order. `null` and `undefined` are accepted too and are last in the order (sorted to the end). +* +* @group Input files & tracks +* @public +*/ +var desc = (value) => { + return -(value ?? -Infinity); +}; +/** +* Helper function for use in {@link InputTrackQuery.sortBy}, used to sort tracks by boolean properties. `true` is +* sorted to the start, `false` to the end. Useful for expressing soft preferences (e.g., "I'd prefer 1080p, but other +* resolutions are fine too") as opposed to {@link InputTrackQuery.filter} which expresses hard requirements for +* tracks. +* +* @group Input files & tracks +* @public +*/ +var prefer = (value) => { + return -value; +}; +var toValidatedInputTrackQuery = (query) => { + if (typeof query !== "object" || !query) throw new TypeError("query must be an object."); + if (query.filter !== void 0 && typeof query.filter !== "function") throw new TypeError("query.filter, when provided, must be a function."); + if (query.sortBy !== void 0 && typeof query.sortBy !== "function") throw new TypeError("query.sortBy, when provided, must be a function."); + return { + filter: query.filter ? (track) => { + const handle = (bool) => { + if (typeof bool !== "boolean") throw new TypeError("query.filter must return or resolve to a boolean."); + return bool; + }; + const result = query.filter(track); + if (isThenable(result)) return result.then(handle); + else return handle(result); + } : void 0, + sortBy: query.sortBy ? (track) => { + const handle = (value) => { + if (typeof value !== "number" && (!Array.isArray(value) || !value.every((x) => typeof x === "number"))) throw new TypeError("query.sortBy must return or resolve to a number or an array of numbers."); + return value; + }; + const result = query.sortBy(track); + if (isThenable(result)) return result.then(handle); + else return handle(result); + } : void 0 + }; +}; +var mergeInputTrackQueries = (queryA, queryB) => { + return { + filter: queryA?.filter || queryB?.filter ? (track) => { + const resultA = queryA?.filter?.(track) ?? true; + const handleResultA = (resultA) => { + if (resultA === false) return false; + return queryB?.filter?.(track) ?? true; + }; + if (isThenable(resultA)) return resultA.then(handleResultA); + else return handleResultA(resultA); + } : void 0, + sortBy: queryA?.sortBy || queryB?.sortBy ? (track) => { + const resultA = queryA?.sortBy?.(track) ?? []; + const resultB = queryB?.sortBy?.(track) ?? []; + const join = (resultA, resultB) => { + return [...Array.isArray(resultA) ? resultA : [resultA], ...Array.isArray(resultB) ? resultB : [resultB]]; + }; + if (isThenable(resultA) || isThenable(resultB)) return Promise.all([resultA, resultB]).then(([resultA, resultB]) => { + return join(resultA, resultB); + }); + else return join(resultA, resultB); + } : void 0 + }; +}; +var queryInputTracks = async (tracks, query) => { + let matched = tracks; + if (query?.filter) { + const filterMatches = tracks.map((t) => query.filter(t)); + if (filterMatches.some((x) => isThenable(x))) { + const resolvedFilterMatches = await Promise.all(filterMatches); + matched = tracks.filter((_, i) => resolvedFilterMatches[i]); + } else matched = tracks.filter((_, i) => filterMatches[i]); + } + if (!query?.sortBy) return matched; + const sortValues = matched.map((t) => query.sortBy(t)); + const resolvedSortValues = sortValues.some((x) => isThenable(x)) ? await Promise.all(sortValues) : sortValues; + return matched.map((track, i) => ({ + track, + sortValue: resolvedSortValues[i] + })).sort((a, b) => { + const aValues = Array.isArray(a.sortValue) ? a.sortValue : [a.sortValue]; + const bValues = Array.isArray(b.sortValue) ? b.sortValue : [b.sortValue]; + const maxLength = Math.max(aValues.length, bValues.length); + for (let i = 0; i < maxLength; i++) { + const aValue = aValues[i] ?? 0; + const bValue = bValues[i] ?? 0; + if (aValue === bValue) continue; + return aValue - bValue; + } + return 0; + }).map((x) => x.track); +}; +/** +* Estimates the underlying CFR-like frame rate from timestamp deltas. +* +* Each delta is modeled as an integer multiple of one frame period, allowing dropped frames and a small number of +* malformed timestamps. +*/ +var findUnderlyingFrameRate = (ticks, resolution) => { + const MAX_DENOMINATOR = 1e6; + const MIN_INLIER_RATIO = .98; + const DELTA_TOLERANCE = 1.000000001; + const MAX_EFFECTIVE_FRAME_SPAN = 1e3; + const KNOWN_FRAME_RATES = [ + 12, + 15, + 20, + 24e3 / 1001, + 24, + 25, + 3e4 / 1001, + 30, + 48, + 50, + 6e4 / 1001, + 60, + 100, + 12e4 / 1001, + 120, + 144, + 240 + ]; + if (ticks.length < 2) return null; + const gaps = new Float64Array(ticks.length - 1); + for (let i = 1; i < ticks.length; i++) { + const gap = ticks[i] - ticks[i - 1]; + if (!(gap > 0)) return null; + gaps[i - 1] = gap; + } + const sortedGaps = gaps.slice(); + sortedGaps.sort(); + let period = sortedGaps[Math.floor(sortedGaps.length * .05)]; + for (let iteration = 0; iteration < 6; iteration++) { + let totalTicks = 0; + let totalFrames = 0; + for (const gap of gaps) { + const multiple = Math.max(1, Math.round(gap / period)); + if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) continue; + totalTicks += gap; + totalFrames += multiple; + } + if (totalFrames === 0) return null; + const refinedPeriod = totalTicks / totalFrames; + if (Math.abs(refinedPeriod - period) <= 1e-12 * Math.max(1, period)) { + period = refinedPeriod; + break; + } + period = refinedPeriod; + } + let inlierCount = 0; + let totalTicks = 0; + let totalFrames = 0; + for (const gap of gaps) { + const multiple = Math.max(1, Math.round(gap / period)); + if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) continue; + inlierCount++; + totalTicks += gap; + totalFrames += multiple; + } + if (inlierCount / gaps.length < MIN_INLIER_RATIO) return null; + period = totalTicks / totalFrames; + const uncertainty = 1 / Math.min(totalFrames, MAX_EFFECTIVE_FRAME_SPAN); + const periodLo = Math.max(Number.EPSILON, period - uncertainty); + const periodHi = period + uncertainty; + const fpsLo = resolution / periodHi; + const fpsHi = resolution / periodLo; + const fittedFps = resolution / period; + let fps = null; + let bestKnownError = Infinity; + for (const candidate of KNOWN_FRAME_RATES) { + if (candidate < fpsLo || candidate > fpsHi) continue; + const error = Math.abs(candidate / fittedFps - 1); + if (error < bestKnownError) { + fps = candidate; + bestKnownError = error; + } + } + if (fps === null) { + const periodFraction = simplestFractionBetween(periodLo, periodHi, MAX_DENOMINATOR); + const fpsFraction = simplestFractionBetween(fpsLo, fpsHi, MAX_DENOMINATOR); + if (fpsFraction && (!periodFraction || fpsFraction.den < periodFraction.den || fpsFraction.den === periodFraction.den && fpsFraction.num <= periodFraction.num)) fps = fpsFraction.num / fpsFraction.den; + else if (periodFraction) fps = resolution * periodFraction.den / periodFraction.num; + else return null; + } + const finalPeriod = resolution / fps; + let finalInlierCount = 0; + for (const gap of gaps) { + const multiple = Math.max(1, Math.round(gap / finalPeriod)); + if (Math.abs(gap - multiple * finalPeriod) < DELTA_TOLERANCE) finalInlierCount++; + } + if (finalInlierCount / gaps.length < MIN_INLIER_RATIO) return null; + return fps; +}; +var simplestFractionBetween = (lo, hi, maxDenominator) => { + for (let den = 1; den <= maxDenominator; den++) { + const num = Math.floor(lo * den) + 1; + if (num / den < hi) return simplifyRational({ + num, + den + }); + } + return null; +}; +var getBestGuessFrameRate = (frameRate) => { + const SPECIAL_FRAME_RATES = [ + 24 / 1.001, + 30 / 1.001, + 60 / 1.001, + 120 / 1.001 + ]; + const COMMON_FRAME_RATES = [ + 12, + 15, + 20, + 24, + 25, + 30, + 48, + 50, + 60, + 100, + 120, + 144, + 240 + ]; + const SPECIAL_TOLERANCE = 5e-4; + const COMMON_TOLERANCE = .025; + for (const candidate of SPECIAL_FRAME_RATES) if (Math.abs(candidate / frameRate - 1) <= SPECIAL_TOLERANCE) return candidate; + let best = frameRate; + let bestError = Infinity; + for (const candidate of COMMON_FRAME_RATES) { + const error = Math.abs(candidate / frameRate - 1); + if (error <= COMMON_TOLERANCE && error < bestError) { + best = candidate; + bestError = error; + } + } + return best; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/input.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +polyfillSymbolDispose(); +/** +* Represents input media, backed by a single file or multiple files depending on the format. +* +* This is the root object from which all media read operations start. +* @group Input files & tracks +* @public +*/ +var Input = class Input extends EventEmitter { + /** True if the input has been disposed. */ + get disposed() { + return this._disposed; + } + /** + * Creates a new input file from the specified options. No reading operations will be performed until methods are + * called on this instance. + */ + constructor(options) { + super(); + /** @internal */ + this._demuxerPromise = null; + /** @internal */ + this._format = null; + /** @internal */ + this._trackBackingsCache = null; + /** @internal */ + this._backingToTrack = /* @__PURE__ */ new Map(); + /** @internal */ + this._disposed = false; + /** @internal */ + this._nextSourceCacheAge = 0; + /** @internal */ + this._sourceRefs = []; + /** @internal */ + this._sourceCache = []; + /** @internal */ + this._sourceCachePromises = []; + /** @internal */ + this._onFormatDetermined = null; + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!Array.isArray(options.formats) || options.formats.some((x) => !(x instanceof InputFormat))) throw new TypeError("options.formats must be an array of InputFormat."); + if (!(options.source instanceof Source || options.source instanceof SourceRef)) throw new TypeError("options.source must be a Source or SourceRef."); + if (options.source instanceof Source && options.source._disposed) throw new TypeError("options.source must not be a disposed Source."); + if (options.initInput !== void 0 && !(options.initInput instanceof Input)) throw new TypeError("options.initInput, when provided, must be an Input."); + if (options.formatOptions !== void 0) validateInputFormatOptions(options.formatOptions, "formatOptions"); + this._formats = options.formats; + this._initInput = options.initInput ?? null; + this._formatOptions = options.formatOptions ?? {}; + if (options.source instanceof Source) this._rootRef = options.source.ref(); + else this._rootRef = options.source; + this._sourceRefs.push(this._rootRef); + } + /** @internal */ + get _rootSource() { + return this._rootRef.source; + } + /** @internal */ + async _getSourceUncached(request) { + assert(this._rootSource instanceof PathedSource); + const ref = await this._rootSource._resolveRequest(request); + this._emit("source", { + source: ref.source, + request, + isRoot: request.isRoot + }); + return ref; + } + /** @internal */ + _getSourceCached(request, cacheGroup = 1) { + const cachedEntry = this._sourceCache.find((x) => x.cacheGroup === cacheGroup && sourceRequestsAreEqual(x.request, request)); + if (cachedEntry) { + cachedEntry.age++; + return Promise.resolve(cachedEntry.sourceRef.source.ref()); + } + const cachedPromiseEntry = this._sourceCachePromises.find((x) => x.cacheGroup === cacheGroup && sourceRequestsAreEqual(x.request, request)); + if (cachedPromiseEntry) return cachedPromiseEntry.promise.then((x) => x.sourceRef.source.ref()); + const promise = (async () => { + const sourceRef = await this._getSourceUncached(request); + if (arrayCount(this._sourceCache, (x) => x.cacheGroup === cacheGroup && x.sourceRef.source._refCount === 1) >= 4) { + const minAgeIndex = arrayArgmin(this._sourceCache, (x) => x.cacheGroup === cacheGroup && x.sourceRef.source._refCount === 1 ? x.age : Infinity); + assert(minAgeIndex !== -1); + const entry = this._sourceCache[minAgeIndex]; + this._sourceCache.splice(minAgeIndex, 1); + entry.sourceRef.free(); + removeItem(this._sourceRefs, entry.sourceRef); + } + this._sourceRefs.push(sourceRef); + const promiseIndex = this._sourceCachePromises.findIndex((x) => x.request === request); + assert(promiseIndex !== -1); + this._sourceCachePromises.splice(promiseIndex, 1); + return { + request, + sourceRef, + age: this._nextSourceCacheAge++, + cacheGroup + }; + })(); + this._sourceCachePromises.push({ + request, + cacheGroup, + promise + }); + return promise.then((entry) => { + const ref = entry.sourceRef.source.ref(); + this._sourceCache.push(entry); + return ref; + }); + } + /** @internal */ + _getDemuxer() { + return this._demuxerPromise ??= (async () => { + this._reader = new Reader(this._rootSource); + this._emit("source", { + source: this._rootSource, + request: null, + isRoot: true + }); + for (const format of this._formats) if (await format._canReadInput(this)) { + this._format = format; + this._onFormatDetermined?.(format); + return format._createDemuxer(this); + } + throw new UnsupportedInputFormatError(); + })(); + } + /** + * Returns the source from which this input file reads data for the root path. + */ + get source() { + return this._rootSource; + } + /** + * Returns the format of the input file. You can compare this result directly to the {@link InputFormat} singletons + * or use `instanceof` checks for subset-aware logic (for example, `format instanceof MatroskaInputFormat` is true + * for both MKV and WebM). + */ + async getFormat() { + await this._getDemuxer(); + assert(this._format); + return this._format; + } + /** Returns `true` if the format of the input file is known and the file can be read, `false` otherwise. */ + async canRead() { + try { + await this._getDemuxer(); + return true; + } catch (error) { + if (error instanceof UnsupportedInputFormatError) return false; + throw error; + } + } + /** + * Returns the timestamp at which the input file starts. More precisely, returns the smallest starting timestamp + * among all tracks. + * + * Optionally, you can pass in the list of tracks for which you want to compute the starting timestamp. + * + * Note that this method is potentially expensive for inputs with many tracks (such as HLS manifests), since it + * probes every track. + */ + async getFirstTimestamp(tracks) { + tracks ??= await this.getTracks(); + const filtered = tracks.filter((x) => x !== null); + if (filtered.length === 0) return 0; + const firstPackets = await Promise.all(filtered.map((x) => x._backing.getFirstPacket({ metadataOnly: true }))); + const result = Math.min(...firstPackets.map((x) => x?.timestamp ?? Infinity)); + return result === Infinity ? 0 : result; + } + /** + * Computes the duration of the input file, in seconds. More precisely, returns the largest end timestamp among + * all tracks. + * + * Optionally, you can pass in the list of tracks for which you want to compute the duration. + * + * This method can be potentially expensive depending on the underlying file format, because it returns the most + * accurate duration possible and must check all tracks. Use {@link Input.getDurationFromMetadata} for a faster but + * less accurate estimate of duration. + * + * By default, when any track in the underlying media is live, this method will only resolve once the live stream + * ends. If you want to query the current duration of the media, set {@link PacketRetrievalOptions.skipLiveWait} + * to `true` in the options. + */ + async computeDuration(tracks, options) { + tracks ??= await this.getTracks(); + const filtered = tracks.filter((x) => x !== null); + if (filtered.length === 0) return 0; + const tracksDurations = await Promise.all(filtered.map((x) => x.computeDuration(options))); + return Math.max(...tracksDurations); + } + /** + * Gets the duration (end timestamp) in seconds of the input file from metadata stored in the file. This value may + * be approximate or diverge from the actual, precise duration returned by `.computeDuration()`, but compared to + * that method, this method is cheaper. When the duration cannot be determined from the file metadata, `null` + * is returned. + * + * Optionally, you can pass in the list of tracks for which you want to get the duration from metadata. + * + * By default, when the underlying media is live, this method will only resolve once the live stream + * ends. If you want to query the current duration of the media, set + * {@link DurationMetadataRequestOptions.skipLiveWait} to `true` in the options. + */ + async getDurationFromMetadata(tracks, options) { + tracks ??= await this.getTracks(); + const filtered = tracks.filter((x) => x !== null); + const nonNullDurations = (await Promise.all(filtered.map((x) => x.getDurationFromMetadata(options)))).filter((x) => x !== null); + if (nonNullDurations.length === 0) return null; + return Math.max(...nonNullDurations); + } + /** + * Returns the list of all tracks of this input file in the order in which they appear in the file. An optional + * query can be provided. + */ + async getTracks(query) { + query &&= toValidatedInputTrackQuery(query); + return queryInputTracks((await this._getTrackBackings()).map((backing) => this._wrapBackingAsTrack(backing)), query); + } + /** Returns the list of all video tracks of this input file. An optional query can be provided. */ + async getVideoTracks(query) { + query &&= toValidatedInputTrackQuery(query); + return queryInputTracks((await this.getTracks()).filter((x) => x.isVideoTrack()), query); + } + /** Returns the list of all audio tracks of this input file. An optional query can be provided. */ + async getAudioTracks(query) { + query &&= toValidatedInputTrackQuery(query); + return queryInputTracks((await this.getTracks()).filter((x) => x.isAudioTrack()), query); + } + /** + * Returns the primary video track of this input file, or null if there are no video tracks. + * + * Multiple factors determine which track is considered primary, including its position in the file, disposition, + * bitrate (higher bitrate is preferred), and if it can be paired with an audio track. + */ + async getPrimaryVideoTrack(query) { + query &&= toValidatedInputTrackQuery(query); + const merged = mergeInputTrackQueries(query, { sortBy: async (t) => [ + prefer((await t.getDisposition()).default), + prefer(await t.hasPairableAudioTrack()), + prefer(!await t.hasOnlyKeyPackets()), + desc(await t.getBitrate()) + ] }); + return (await this.getVideoTracks(merged))[0] ?? null; + } + /** + * Returns the primary audio track of this input file, or null if there are no audio tracks. + * + * Multiple factors determine which track is considered primary, including its position in the file, disposition, + * bitrate (higher bitrate is preferred), and if it can be paired with the primary video track. + */ + async getPrimaryAudioTrack(query) { + query &&= toValidatedInputTrackQuery(query); + const primaryVideoTrack = await this.getPrimaryVideoTrack(); + const merged = mergeInputTrackQueries(query, { sortBy: async (t) => [ + prefer(!primaryVideoTrack || t.canBePairedWith(primaryVideoTrack)), + prefer((await t.getDisposition()).default), + desc(await t.getBitrate()) + ] }); + return (await this.getAudioTracks(merged))[0] ?? null; + } + /** @internal */ + async _getTrackBackings() { + const demuxer = await this._getDemuxer(); + return this._trackBackingsCache ??= await demuxer.getTrackBackings(); + } + /** @internal */ + _wrapBackingAsTrack(backing) { + const existing = this._backingToTrack.get(backing); + if (existing) return existing; + const track = backing.getType() === "video" ? new InputVideoTrack(this, backing) : new InputAudioTrack(this, backing); + this._backingToTrack.set(backing, track); + return track; + } + /** Returns the full MIME type of this input file, including track codecs. */ + async getMimeType() { + return (await this._getDemuxer()).getMimeType(); + } + /** + * Returns descriptive metadata tags about the media file, such as title, author, date, cover art, or other + * attached files. + */ + async getMetadataTags() { + return (await this._getDemuxer()).getMetadataTags(); + } + /** + * Disposes this input and frees connected resources. When an input is disposed, ongoing read operations will be + * canceled, all future read operations will fail, any open decoders will be closed, and all ongoing media sink + * operations will be canceled. Disallowed and canceled operations will throw an {@link InputDisposedError}. + * + * You are expected not to use an input after disposing it. While some operations may still work, it is not + * specified and may change in any future update. + */ + dispose() { + if (this._disposed) return; + this._disposed = true; + for (const ref of this._sourceRefs) ref.free(); + this._sourceRefs.length = 0; + if (this._demuxerPromise) this._demuxerPromise.then((demuxer) => demuxer.dispose()).catch(() => {}); + } + /** + * Calls `.dispose()` on the input, implementing the `Disposable` interface for use with + * JavaScript Explicit Resource Management features. + */ + [Symbol.dispose]() { + this.dispose(); + } +}; +/** +* Thrown when trying to operate on an input that has an unsupported or unrecognizable format. +* @group Input files & tracks +* @public +*/ +var UnsupportedInputFormatError = class extends Error { + /** Creates a new {@link UnsupportedInputFormatError}. */ + constructor(message = "Input has an unsupported or unrecognizable format.") { + super(message); + this.name = "UnsupportedInputFormatError"; + } +}; +/** +* Thrown when an operation was prevented because the corresponding {@link Input} has been disposed. +* @group Input files & tracks +* @public +*/ +var InputDisposedError = class extends Error { + /** Creates a new {@link InputDisposedError}. */ + constructor(message = "Input has been disposed.") { + super(message); + this.name = "InputDisposedError"; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/reader.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Reader = class { + constructor(source) { + this.source = source; + } + get fileSize() { + const size = this.source._getFileSize(); + if (size === void 0) throw new Error("Reading file size too early; read required first."); + return size; + } + get fileSizeNonStrict() { + return this.source._getFileSize() ?? null; + } + requestSlice(start, length) { + if (this.source._disposed) throw new InputDisposedError(); + if (start < 0) return null; + if (this.fileSizeNonStrict !== null && start + length > this.fileSizeNonStrict) return null; + if (length === 0) { + const buffer = new Uint8Array(0); + return new FileSlice(buffer, toDataView(buffer), 0, start, start); + } + const end = start + length; + const result = this.source._read(start, end, 0, DEFAULT_MAX_READ_POSITION); + if (isThenable(result)) return result.then((x) => { + if (!x) return null; + return new FileSlice(x.bytes, x.view, x.offset, start, end); + }); + else { + if (!result) return null; + return new FileSlice(result.bytes, result.view, result.offset, start, end); + } + } + requestSliceRange(start, minLength, maxLength) { + if (this.source._disposed) throw new InputDisposedError(); + if (start < 0) return null; + if (this.fileSizeNonStrict !== null) return this.requestSlice(start, clamp(this.fileSizeNonStrict - start, minLength, maxLength)); + else { + const promisedAttempt = this.requestSlice(start, maxLength); + const handleAttempt = (attempt) => { + if (attempt) return attempt; + assert(this.fileSizeNonStrict !== null); + return this.requestSlice(start, clamp(this.fileSizeNonStrict - start, minLength, maxLength)); + }; + if (isThenable(promisedAttempt)) return promisedAttempt.then(handleAttempt); + else return handleAttempt(promisedAttempt); + } + } + requestEntireFile() { + if (this.fileSizeNonStrict !== null) return this.requestSlice(0, this.fileSizeNonStrict); + const CHUNK_SIZE = 1024; + return (async () => { + const chunks = []; + let currentSize = 0; + while (true) { + if (chunks.length === 1 && this.fileSizeNonStrict !== null) return this.requestSlice(0, this.fileSizeNonStrict); + let slice = this.requestSliceRange(currentSize, 0, CHUNK_SIZE); + if (isThenable(slice)) slice = await slice; + if (!slice || slice.length === 0) break; + const chunk = readBytes(slice, slice.length); + chunks.push(chunk); + currentSize += slice.length; + } + const joined = new Uint8Array(currentSize); + let offset = 0; + for (const chunk of chunks) { + joined.set(chunk, offset); + offset += chunk.length; + } + return new FileSlice(joined, toDataView(joined), 0, 0, currentSize); + })(); + } +}; +var FileSlice = class FileSlice { + constructor(bytes, view, offset, start, end) { + this.bytes = bytes; + this.view = view; + this.offset = offset; + this.start = start; + this.end = end; + this.bufferPos = start - offset; + } + static tempFromBytes(bytes) { + return new FileSlice(bytes, toDataView(bytes), 0, 0, bytes.length); + } + get length() { + return this.end - this.start; + } + get filePos() { + return this.offset + this.bufferPos; + } + set filePos(value) { + this.bufferPos = value - this.offset; + } + /** The number of bytes left from the current pos to the end of the slice. */ + get remainingLength() { + return Math.max(this.end - this.filePos, 0); + } + skip(byteCount) { + this.bufferPos += byteCount; + } + /** Creates a new subslice of this slice whose byte range must be contained within this slice. */ + slice(filePos, length = this.end - filePos) { + if (filePos < this.start || filePos + length > this.end) throw new RangeError("Slicing outside of original slice."); + return new FileSlice(this.bytes, this.view, this.offset, filePos, filePos + length); + } +}; +var checkIsInRange = (slice, bytesToRead) => { + if (slice.filePos < slice.start || slice.filePos + bytesToRead > slice.end) throw new RangeError(`Tried reading [${slice.filePos}, ${slice.filePos + bytesToRead}), but slice is [${slice.start}, ${slice.end}). This is likely an internal error, please report it alongside the file that caused it.`); +}; +var readBytes = (slice, length) => { + checkIsInRange(slice, length); + const bytes = slice.bytes.subarray(slice.bufferPos, slice.bufferPos + length); + slice.bufferPos += length; + return bytes; +}; +var readU8 = (slice) => { + checkIsInRange(slice, 1); + return slice.view.getUint8(slice.bufferPos++); +}; +var readU16 = (slice, littleEndian) => { + checkIsInRange(slice, 2); + const value = slice.view.getUint16(slice.bufferPos, littleEndian); + slice.bufferPos += 2; + return value; +}; +var readU16Be = (slice) => { + checkIsInRange(slice, 2); + const value = slice.view.getUint16(slice.bufferPos, false); + slice.bufferPos += 2; + return value; +}; +var readU24Be = (slice) => { + checkIsInRange(slice, 3); + const value = getUint24(slice.view, slice.bufferPos, false); + slice.bufferPos += 3; + return value; +}; +var readI16Be = (slice) => { + checkIsInRange(slice, 2); + const value = slice.view.getInt16(slice.bufferPos, false); + slice.bufferPos += 2; + return value; +}; +var readU32 = (slice, littleEndian) => { + checkIsInRange(slice, 4); + const value = slice.view.getUint32(slice.bufferPos, littleEndian); + slice.bufferPos += 4; + return value; +}; +var readU32Be = (slice) => { + checkIsInRange(slice, 4); + const value = slice.view.getUint32(slice.bufferPos, false); + slice.bufferPos += 4; + return value; +}; +var readU32Le = (slice) => { + checkIsInRange(slice, 4); + const value = slice.view.getUint32(slice.bufferPos, true); + slice.bufferPos += 4; + return value; +}; +var readI32Be = (slice) => { + checkIsInRange(slice, 4); + const value = slice.view.getInt32(slice.bufferPos, false); + slice.bufferPos += 4; + return value; +}; +var readI32Le = (slice) => { + checkIsInRange(slice, 4); + const value = slice.view.getInt32(slice.bufferPos, true); + slice.bufferPos += 4; + return value; +}; +var readU64 = (slice, littleEndian) => { + let low; + let high; + if (littleEndian) { + low = readU32(slice, true); + high = readU32(slice, true); + } else { + high = readU32(slice, false); + low = readU32(slice, false); + } + return high * 4294967296 + low; +}; +var readU64Be = (slice) => { + const high = readU32Be(slice); + const low = readU32Be(slice); + return high * 4294967296 + low; +}; +var readI64Be = (slice) => { + const high = readI32Be(slice); + const low = readU32Be(slice); + return high * 4294967296 + low; +}; +var readI64Le = (slice) => { + const low = readU32Le(slice); + return readI32Le(slice) * 4294967296 + low; +}; +var readF32Be = (slice) => { + checkIsInRange(slice, 4); + const value = slice.view.getFloat32(slice.bufferPos, false); + slice.bufferPos += 4; + return value; +}; +var readF64Be = (slice) => { + checkIsInRange(slice, 8); + const value = slice.view.getFloat64(slice.bufferPos, false); + slice.bufferPos += 8; + return value; +}; +var readAscii = (slice, length) => { + checkIsInRange(slice, length); + let str = ""; + for (let i = 0; i < length; i++) str += String.fromCharCode(slice.bytes[slice.bufferPos++]); + return str; +}; +var readAllLines = (slice, length, options) => { + return textDecoder.decode(readBytes(slice, length)).split("\n").map((x) => x.trim()).filter((x) => x.length > 0 && !options?.ignore?.(x)); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/id3.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Id3V2HeaderFlags; +(function(Id3V2HeaderFlags) { + Id3V2HeaderFlags[Id3V2HeaderFlags["Unsynchronisation"] = 128] = "Unsynchronisation"; + Id3V2HeaderFlags[Id3V2HeaderFlags["ExtendedHeader"] = 64] = "ExtendedHeader"; + Id3V2HeaderFlags[Id3V2HeaderFlags["ExperimentalIndicator"] = 32] = "ExperimentalIndicator"; + Id3V2HeaderFlags[Id3V2HeaderFlags["Footer"] = 16] = "Footer"; +})(Id3V2HeaderFlags || (Id3V2HeaderFlags = {})); +var Id3V2TextEncoding; +(function(Id3V2TextEncoding) { + Id3V2TextEncoding[Id3V2TextEncoding["ISO_8859_1"] = 0] = "ISO_8859_1"; + Id3V2TextEncoding[Id3V2TextEncoding["UTF_16_WITH_BOM"] = 1] = "UTF_16_WITH_BOM"; + Id3V2TextEncoding[Id3V2TextEncoding["UTF_16_BE_NO_BOM"] = 2] = "UTF_16_BE_NO_BOM"; + Id3V2TextEncoding[Id3V2TextEncoding["UTF_8"] = 3] = "UTF_8"; +})(Id3V2TextEncoding || (Id3V2TextEncoding = {})); +var ID3_V1_GENRES = [ + "Blues", + "Classic rock", + "Country", + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-hop", + "Jazz", + "Metal", + "New age", + "Oldies", + "Other", + "Pop", + "Rhythm and blues", + "Rap", + "Reggae", + "Rock", + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death metal", + "Pranks", + "Soundtrack", + "Euro-techno", + "Ambient", + "Trip-hop", + "Vocal", + "Jazz & funk", + "Fusion", + "Trance", + "Classical", + "Instrumental", + "Acid", + "House", + "Game", + "Sound clip", + "Gospel", + "Noise", + "Alternative rock", + "Bass", + "Soul", + "Punk", + "Space", + "Meditative", + "Instrumental pop", + "Instrumental rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-industrial", + "Electronic", + "Pop-folk", + "Eurodance", + "Dream", + "Southern rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian rap", + "Pop/funk", + "Jungle music", + "Native US", + "Cabaret", + "New wave", + "Psychedelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-fi", + "Tribal", + "Acid punk", + "Acid jazz", + "Polka", + "Retro", + "Musical", + "Rock 'n' roll", + "Hard rock", + "Folk", + "Folk rock", + "National folk", + "Swing", + "Fast fusion", + "Bebop", + "Latin", + "Revival", + "Celtic", + "Bluegrass", + "Avantgarde", + "Gothic rock", + "Progressive rock", + "Psychedelic rock", + "Symphonic rock", + "Slow rock", + "Big band", + "Chorus", + "Easy listening", + "Acoustic", + "Humour", + "Speech", + "Chanson", + "Opera", + "Chamber music", + "Sonata", + "Symphony", + "Booty bass", + "Primus", + "Porn groove", + "Satire", + "Slow jam", + "Club", + "Tango", + "Samba", + "Folklore", + "Ballad", + "Power ballad", + "Rhythmic Soul", + "Freestyle", + "Duet", + "Punk rock", + "Drum solo", + "A cappella", + "Euro-house", + "Dance hall", + "Goa music", + "Drum & bass", + "Club-house", + "Hardcore techno", + "Terror", + "Indie", + "Britpop", + "Negerpunk", + "Polsk punk", + "Beat", + "Christian gangsta rap", + "Heavy metal", + "Black metal", + "Crossover", + "Contemporary Christian", + "Christian rock", + "Merengue", + "Salsa", + "Thrash metal", + "Anime", + "Jpop", + "Synthpop", + "Christmas", + "Art rock", + "Baroque", + "Bhangra", + "Big beat", + "Breakbeat", + "Chillout", + "Downtempo", + "Dub", + "EBM", + "Eclectic", + "Electro", + "Electroclash", + "Emo", + "Experimental", + "Garage", + "Global", + "IDM", + "Illbient", + "Industro-Goth", + "Jam Band", + "Krautrock", + "Leftfield", + "Lounge", + "Math rock", + "New romantic", + "Nu-breakz", + "Post-punk", + "Post-rock", + "Psytrance", + "Shoegaze", + "Space rock", + "Trop rock", + "World music", + "Neoclassical", + "Audiobook", + "Audio theatre", + "Neue Deutsche Welle", + "Podcast", + "Indie rock", + "G-Funk", + "Dubstep", + "Garage rock", + "Psybient" +]; +var parseId3V1Tag = (slice, tags) => { + const startPos = slice.filePos; + tags.raw ??= {}; + tags.raw["TAG"] ??= readBytes(slice, 125); + slice.filePos = startPos; + const title = readId3V1String(slice, 30); + if (title) tags.title ??= title; + const artist = readId3V1String(slice, 30); + if (artist) tags.artist ??= artist; + const album = readId3V1String(slice, 30); + if (album) tags.album ??= album; + const yearText = readId3V1String(slice, 4); + const year = Number.parseInt(yearText, 10); + if (Number.isInteger(year) && year > 0) tags.date ??= new Date(String(year)); + const commentBytes = readBytes(slice, 30); + let comment; + if (commentBytes[28] === 0 && commentBytes[29] !== 0) { + const trackNum = commentBytes[29]; + if (trackNum > 0) tags.trackNumber ??= trackNum; + slice.skip(-30); + comment = readId3V1String(slice, 28); + slice.skip(2); + } else { + slice.skip(-30); + comment = readId3V1String(slice, 30); + } + if (comment) tags.comment ??= comment; + const genreIndex = readU8(slice); + if (genreIndex < ID3_V1_GENRES.length) tags.genre ??= ID3_V1_GENRES[genreIndex]; +}; +var readId3V1String = (slice, length) => { + const bytes = readBytes(slice, length); + const endIndex = coalesceIndex(bytes.indexOf(0), bytes.length); + const relevantBytes = bytes.subarray(0, endIndex); + let str = ""; + for (let i = 0; i < relevantBytes.length; i++) str += String.fromCharCode(relevantBytes[i]); + return str.trimEnd(); +}; +var readId3V2Header = (slice) => { + const startPos = slice.filePos; + const tag = readAscii(slice, 3); + const majorVersion = readU8(slice); + const revision = readU8(slice); + const flags = readU8(slice); + const sizeRaw = readU32Be(slice); + if (tag !== "ID3" || majorVersion === 255 || revision === 255 || (sizeRaw & 2155905152) !== 0) { + slice.filePos = startPos; + return null; + } + let size = decodeSynchsafe(sizeRaw); + if (flags & Id3V2HeaderFlags.Footer) size += 10; + return { + majorVersion, + revision, + flags, + size + }; +}; +var parseId3V2Tag = (slice, header, tags) => { + if (![ + 2, + 3, + 4 + ].includes(header.majorVersion)) { + Logging._warn(`Unsupported ID3v2 major version: ${header.majorVersion}`); + return; + } + const reader = new Id3V2Reader(header, readBytes(slice, header.flags & Id3V2HeaderFlags.Footer ? header.size - 10 : header.size)); + if (header.flags & Id3V2HeaderFlags.Unsynchronisation && header.majorVersion === 3) reader.ununsynchronizeAll(); + if (header.flags & Id3V2HeaderFlags.ExtendedHeader) { + const extendedHeaderSize = reader.readU32(); + if (header.majorVersion === 3) reader.pos += extendedHeaderSize; + else reader.pos += extendedHeaderSize - 4; + } + while (reader.pos <= reader.bytes.length - reader.frameHeaderSize()) { + const frame = reader.readId3V2Frame(); + if (!frame) break; + const frameStartPos = reader.pos; + const frameEndPos = reader.pos + frame.size; + let frameEncrypted = false; + let frameCompressed = false; + let frameUnsynchronized = false; + if (header.majorVersion === 3) { + frameEncrypted = !!(frame.flags & 64); + frameCompressed = !!(frame.flags & 128); + } else if (header.majorVersion === 4) { + frameEncrypted = !!(frame.flags & 4); + frameCompressed = !!(frame.flags & 8); + frameUnsynchronized = !!(frame.flags & 2) || !!(header.flags & Id3V2HeaderFlags.Unsynchronisation); + } + if (frameEncrypted) { + Logging._warn(`Skipping encrypted ID3v2 frame ${frame.id}`); + reader.pos = frameEndPos; + continue; + } + if (frameCompressed) { + Logging._warn(`Skipping compressed ID3v2 frame ${frame.id}`); + reader.pos = frameEndPos; + continue; + } + if (frameUnsynchronized) reader.ununsynchronizeRegion(reader.pos, frameEndPos); + tags.raw ??= {}; + if (frame.id === "TXXX") { + const txxx = tags.raw["TXXX"] ??= {}; + const encoding = reader.readId3V2TextEncoding(); + const description = reader.readId3V2Text(encoding, frameEndPos); + const value = reader.readId3V2Text(encoding, frameEndPos); + txxx[description] ??= value; + } else if (frame.id[0] === "T") tags.raw[frame.id] ??= reader.readId3V2EncodingAndText(frameEndPos); + else tags.raw[frame.id] ??= reader.readBytes(frame.size); + reader.pos = frameStartPos; + switch (frame.id) { + case "TIT2": + case "TT2": + tags.title ??= reader.readId3V2EncodingAndText(frameEndPos); + break; + case "TIT3": + case "TT3": + tags.description ??= reader.readId3V2EncodingAndText(frameEndPos); + break; + case "TPE1": + case "TP1": + tags.artist ??= reader.readId3V2EncodingAndText(frameEndPos); + break; + case "TALB": + case "TAL": + tags.album ??= reader.readId3V2EncodingAndText(frameEndPos); + break; + case "TPE2": + case "TP2": + tags.albumArtist ??= reader.readId3V2EncodingAndText(frameEndPos); + break; + case "TRCK": + case "TRK": + { + const parts = reader.readId3V2EncodingAndText(frameEndPos).split("/"); + const trackNum = Number.parseInt(parts[0], 10); + const tracksTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(trackNum) && trackNum > 0) tags.trackNumber ??= trackNum; + if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) tags.tracksTotal ??= tracksTotal; + } + break; + case "TPOS": + case "TPA": + { + const parts = reader.readId3V2EncodingAndText(frameEndPos).split("/"); + const discNum = Number.parseInt(parts[0], 10); + const discsTotal = parts[1] && Number.parseInt(parts[1], 10); + if (Number.isInteger(discNum) && discNum > 0) tags.discNumber ??= discNum; + if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) tags.discsTotal ??= discsTotal; + } + break; + case "TCON": + case "TCO": + { + const genreText = reader.readId3V2EncodingAndText(frameEndPos); + let match = /^\((\d+)\)/.exec(genreText); + if (match) { + const genreNumber = Number.parseInt(match[1]); + if (ID3_V1_GENRES[genreNumber] !== void 0) { + tags.genre ??= ID3_V1_GENRES[genreNumber]; + break; + } + } + match = /^\d+$/.exec(genreText); + if (match) { + const genreNumber = Number.parseInt(match[0]); + if (ID3_V1_GENRES[genreNumber] !== void 0) { + tags.genre ??= ID3_V1_GENRES[genreNumber]; + break; + } + } + tags.genre ??= genreText; + } + break; + case "TDRC": + case "TDAT": + { + const dateText = reader.readId3V2EncodingAndText(frameEndPos); + const date = new Date(dateText); + if (!Number.isNaN(date.getTime())) tags.date ??= date; + } + break; + case "TYER": + case "TYE": + { + const yearText = reader.readId3V2EncodingAndText(frameEndPos); + const year = Number.parseInt(yearText, 10); + if (Number.isInteger(year)) tags.date ??= new Date(String(year)); + } + break; + case "TBPM": + case "TBP": + { + const bpmText = reader.readId3V2EncodingAndText(frameEndPos); + const bpm = Number.parseInt(bpmText, 10); + if (Number.isInteger(bpm)) tags.beatsPerMinute ??= bpm; + } + break; + case "USLT": + case "ULT": + { + const encoding = reader.readU8(); + reader.pos += 3; + reader.readId3V2Text(encoding, frameEndPos); + tags.lyrics ??= reader.readId3V2Text(encoding, frameEndPos); + } + break; + case "COMM": + case "COM": + { + const encoding = reader.readU8(); + reader.pos += 3; + reader.readId3V2Text(encoding, frameEndPos); + tags.comment ??= reader.readId3V2Text(encoding, frameEndPos); + } + break; + case "APIC": + case "PIC": + { + const encoding = reader.readId3V2TextEncoding(); + let mimeType; + if (header.majorVersion === 2) { + const imageFormat = reader.readAscii(3); + mimeType = imageFormat === "PNG" ? "image/png" : imageFormat === "JPG" ? "image/jpeg" : "image/*"; + } else mimeType = reader.readId3V2Text(encoding, frameEndPos); + const pictureType = reader.readU8(); + const description = reader.readId3V2Text(encoding, frameEndPos).trimEnd(); + const imageDataSize = frameEndPos - reader.pos; + if (imageDataSize >= 0) { + const imageData = reader.readBytes(imageDataSize); + if (!tags.images) tags.images = []; + tags.images.push({ + data: imageData, + mimeType, + kind: pictureType === 3 ? "coverFront" : pictureType === 4 ? "coverBack" : "unknown", + description + }); + } + } + break; + default: + reader.pos += frame.size; + break; + } + reader.pos = frameEndPos; + } +}; +var Id3V2Reader = class { + constructor(header, bytes) { + this.header = header; + this.bytes = bytes; + this.pos = 0; + this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + } + frameHeaderSize() { + return this.header.majorVersion === 2 ? 6 : 10; + } + ununsynchronizeAll() { + const newBytes = []; + for (let i = 0; i < this.bytes.length; i++) { + const value1 = this.bytes[i]; + newBytes.push(value1); + if (value1 === 255 && i !== this.bytes.length - 1) { + if (this.bytes[i] === 0) i++; + } + } + this.bytes = new Uint8Array(newBytes); + this.view = new DataView(this.bytes.buffer); + } + ununsynchronizeRegion(start, end) { + const newBytes = []; + for (let i = start; i < end; i++) { + const value1 = this.bytes[i]; + newBytes.push(value1); + if (value1 === 255 && i !== end - 1) { + if (this.bytes[i + 1] === 0) i++; + } + } + const before = this.bytes.subarray(0, start); + const after = this.bytes.subarray(end); + this.bytes = new Uint8Array(before.length + newBytes.length + after.length); + this.bytes.set(before, 0); + this.bytes.set(newBytes, before.length); + this.bytes.set(after, before.length + newBytes.length); + this.view = new DataView(this.bytes.buffer); + } + readBytes(length) { + const slice = this.bytes.subarray(this.pos, this.pos + length); + this.pos += length; + return slice; + } + readU8() { + const value = this.view.getUint8(this.pos); + this.pos += 1; + return value; + } + readU16() { + const value = this.view.getUint16(this.pos, false); + this.pos += 2; + return value; + } + readU24() { + const high = this.view.getUint16(this.pos, false); + const low = this.view.getUint8(this.pos + 2); + this.pos += 3; + return high * 256 + low; + } + readU32() { + const value = this.view.getUint32(this.pos, false); + this.pos += 4; + return value; + } + readAscii(length) { + let str = ""; + for (let i = 0; i < length; i++) str += String.fromCharCode(this.view.getUint8(this.pos + i)); + this.pos += length; + return str; + } + readId3V2Frame() { + if (this.header.majorVersion === 2) { + const id = this.readAscii(3); + if (id === "\0\0\0") return null; + return { + id, + size: this.readU24(), + flags: 0 + }; + } else { + const id = this.readAscii(4); + if (id === "\0\0\0\0") return null; + const sizeRaw = this.readU32(); + let size = this.header.majorVersion === 4 ? decodeSynchsafe(sizeRaw) : sizeRaw; + const flags = this.readU16(); + const headerEndPos = this.pos; + const isSizeValid = (size) => { + const nextPos = this.pos + size; + if (nextPos > this.bytes.length) return false; + if (nextPos <= this.bytes.length - this.frameHeaderSize()) { + this.pos += size; + const nextId = this.readAscii(4); + if (nextId !== "\0\0\0\0" && !/[0-9A-Z]{4}/.test(nextId)) return false; + } + return true; + }; + if (!isSizeValid(size)) { + const otherSize = this.header.majorVersion === 4 ? sizeRaw : decodeSynchsafe(sizeRaw); + if (isSizeValid(otherSize)) size = otherSize; + } + this.pos = headerEndPos; + return { + id, + size, + flags + }; + } + } + readId3V2TextEncoding() { + const number = this.readU8(); + if (number > 3) throw new Error(`Unsupported text encoding: ${number}`); + return number; + } + readId3V2Text(encoding, until) { + const startPos = this.pos; + const data = this.readBytes(until - this.pos); + switch (encoding) { + case Id3V2TextEncoding.ISO_8859_1: { + let str = ""; + for (let i = 0; i < data.length; i++) { + const value = data[i]; + if (value === 0) { + this.pos = startPos + i + 1; + break; + } + str += String.fromCharCode(value); + } + return str; + } + case Id3V2TextEncoding.UTF_16_WITH_BOM: if (data[0] === 255 && data[1] === 254) { + const decoder = new TextDecoder("utf-16le"); + const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length); + this.pos = startPos + Math.min(endIndex + 2, data.length); + return decoder.decode(data.subarray(2, endIndex)); + } else if (data[0] === 254 && data[1] === 255) { + const decoder = new TextDecoder("utf-16be"); + const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length); + this.pos = startPos + Math.min(endIndex + 2, data.length); + return decoder.decode(data.subarray(2, endIndex)); + } else { + const endIndex = coalesceIndex(data.findIndex((x) => x === 0), data.length); + this.pos = startPos + Math.min(endIndex + 1, data.length); + return textDecoder.decode(data.subarray(0, endIndex)); + } + case Id3V2TextEncoding.UTF_16_BE_NO_BOM: { + const decoder = new TextDecoder("utf-16be"); + const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length); + this.pos = startPos + Math.min(endIndex + 2, data.length); + return decoder.decode(data.subarray(0, endIndex)); + } + case Id3V2TextEncoding.UTF_8: { + const endIndex = coalesceIndex(data.findIndex((x) => x === 0), data.length); + this.pos = startPos + Math.min(endIndex + 1, data.length); + return textDecoder.decode(data.subarray(0, endIndex)); + } + } + } + readId3V2EncodingAndText(until) { + if (this.pos >= until) return ""; + const encoding = this.readId3V2TextEncoding(); + return this.readId3V2Text(encoding, until); + } +}; +var Id3V2Writer = class { + constructor(writer) { + this.helper = new Uint8Array(8); + this.helperView = toDataView(this.helper); + this.writer = writer; + } + writeId3V2Tag(metadata) { + const tagStartPos = this.writer.getPos(); + this.writeAscii("ID3"); + this.writeU8(4); + this.writeU8(0); + this.writeU8(0); + this.writeSynchsafeU32(0); + const framesStartPos = this.writer.getPos(); + const writtenTags = /* @__PURE__ */ new Set(); + for (const { key, value } of keyValueIterator(metadata)) switch (key) { + case "title": + this.writeId3V2TextFrame("TIT2", value); + writtenTags.add("TIT2"); + break; + case "description": + this.writeId3V2TextFrame("TIT3", value); + writtenTags.add("TIT3"); + break; + case "artist": + this.writeId3V2TextFrame("TPE1", value); + writtenTags.add("TPE1"); + break; + case "album": + this.writeId3V2TextFrame("TALB", value); + writtenTags.add("TALB"); + break; + case "albumArtist": + this.writeId3V2TextFrame("TPE2", value); + writtenTags.add("TPE2"); + break; + case "trackNumber": + { + const string = metadata.tracksTotal !== void 0 ? `${value}/${metadata.tracksTotal}` : value.toString(); + this.writeId3V2TextFrame("TRCK", string); + writtenTags.add("TRCK"); + } + break; + case "discNumber": + { + const string = metadata.discsTotal !== void 0 ? `${value}/${metadata.discsTotal}` : value.toString(); + this.writeId3V2TextFrame("TPOS", string); + writtenTags.add("TPOS"); + } + break; + case "genre": + this.writeId3V2TextFrame("TCON", value); + writtenTags.add("TCON"); + break; + case "date": + this.writeId3V2TextFrame("TDRC", value.toISOString().slice(0, 10)); + writtenTags.add("TDRC"); + break; + case "beatsPerMinute": + this.writeId3V2TextFrame("TBPM", `${value}`); + writtenTags.add("TBPM"); + break; + case "lyrics": + this.writeId3V2LyricsFrame(value); + writtenTags.add("USLT"); + break; + case "comment": + this.writeId3V2CommentFrame(value); + writtenTags.add("COMM"); + break; + case "images": + { + const pictureTypeMap = { + coverFront: 3, + coverBack: 4, + unknown: 0 + }; + for (const image of value) { + const pictureType = pictureTypeMap[image.kind] ?? 0; + const description = image.description ?? ""; + this.writeId3V2ApicFrame(image.mimeType, pictureType, description, image.data); + } + } + break; + case "tracksTotal": + case "discsTotal": break; + case "raw": break; + default: assertNever(key); + } + if (metadata.raw) for (const key in metadata.raw) { + const value = metadata.raw[key]; + if (value == null || key.length !== 4 || writtenTags.has(key)) continue; + let bytes; + if (typeof value === "string") if (isIso88591Compatible(value)) { + bytes = new Uint8Array(value.length + 2); + bytes[0] = Id3V2TextEncoding.ISO_8859_1; + for (let i = 0; i < value.length; i++) bytes[i + 1] = value.charCodeAt(i); + } else { + const encoded = textEncoder.encode(value); + bytes = new Uint8Array(encoded.byteLength + 2); + bytes[0] = Id3V2TextEncoding.UTF_8; + bytes.set(encoded, 1); + } + else if (value instanceof Uint8Array) bytes = value; + else if (key === "TXXX" && isRecordStringString(value)) { + for (const description in value) { + const frameValue = value[description]; + const useIso88591 = isIso88591Compatible(description) && isIso88591Compatible(frameValue); + const encodedDescription = useIso88591 ? null : textEncoder.encode(description); + const encodedValue = useIso88591 ? null : textEncoder.encode(frameValue); + const descriptionDataLength = useIso88591 ? description.length : encodedDescription.byteLength; + const valueDataLength = useIso88591 ? frameValue.length : encodedValue.byteLength; + const frameSize = 1 + descriptionDataLength + 1 + valueDataLength + 1; + this.writeAscii("TXXX"); + this.writeSynchsafeU32(frameSize); + this.writeU16(0); + this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8); + if (useIso88591) { + this.writeIsoString(description); + this.writeIsoString(frameValue); + } else { + this.writer.write(encodedDescription); + this.writeU8(0); + this.writer.write(encodedValue); + this.writeU8(0); + } + } + continue; + } else continue; + this.writeAscii(key); + this.writeSynchsafeU32(bytes.byteLength); + this.writeU16(0); + this.writer.write(bytes); + } + const framesEndPos = this.writer.getPos(); + const framesSize = framesEndPos - framesStartPos; + this.writer.seek(tagStartPos + 6); + this.writeSynchsafeU32(framesSize); + this.writer.seek(framesEndPos); + return framesSize + 10; + } + writeU8(value) { + this.helper[0] = value; + this.writer.write(this.helper.subarray(0, 1)); + } + writeU16(value) { + this.helperView.setUint16(0, value, false); + this.writer.write(this.helper.subarray(0, 2)); + } + writeU32(value) { + this.helperView.setUint32(0, value, false); + this.writer.write(this.helper.subarray(0, 4)); + } + writeAscii(text) { + for (let i = 0; i < text.length; i++) this.helper[i] = text.charCodeAt(i); + this.writer.write(this.helper.subarray(0, text.length)); + } + writeSynchsafeU32(value) { + this.writeU32(encodeSynchsafe(value)); + } + writeIsoString(text) { + const bytes = new Uint8Array(text.length + 1); + for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i); + this.writer.write(bytes); + } + writeUtf8String(text) { + const utf8Data = textEncoder.encode(text); + this.writer.write(utf8Data); + this.writeU8(0); + } + writeId3V2TextFrame(frameId, text) { + const useIso88591 = isIso88591Compatible(text); + const frameSize = 1 + (useIso88591 ? text.length : textEncoder.encode(text).byteLength) + 1; + this.writeAscii(frameId); + this.writeSynchsafeU32(frameSize); + this.writeU16(0); + this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8); + if (useIso88591) this.writeIsoString(text); + else this.writeUtf8String(text); + } + writeId3V2LyricsFrame(lyrics) { + const useIso88591 = isIso88591Compatible(lyrics); + const shortDescription = ""; + const frameSize = 5 + lyrics.length + 1; + this.writeAscii("USLT"); + this.writeSynchsafeU32(frameSize); + this.writeU16(0); + this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8); + this.writeAscii("und"); + if (useIso88591) { + this.writeIsoString(shortDescription); + this.writeIsoString(lyrics); + } else { + this.writeUtf8String(shortDescription); + this.writeUtf8String(lyrics); + } + } + writeId3V2CommentFrame(comment) { + const useIso88591 = isIso88591Compatible(comment); + const textDataLength = useIso88591 ? comment.length : textEncoder.encode(comment).byteLength; + const shortDescription = ""; + const frameSize = 5 + textDataLength + 1; + this.writeAscii("COMM"); + this.writeSynchsafeU32(frameSize); + this.writeU16(0); + this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8); + this.writeU8(117); + this.writeU8(110); + this.writeU8(100); + if (useIso88591) { + this.writeIsoString(shortDescription); + this.writeIsoString(comment); + } else { + this.writeUtf8String(shortDescription); + this.writeUtf8String(comment); + } + } + writeId3V2ApicFrame(mimeType, pictureType, description, imageData) { + const useIso88591 = isIso88591Compatible(mimeType) && isIso88591Compatible(description); + const descriptionDataLength = useIso88591 ? description.length : textEncoder.encode(description).byteLength; + const frameSize = 1 + mimeType.length + 1 + 1 + descriptionDataLength + 1 + imageData.byteLength; + this.writeAscii("APIC"); + this.writeSynchsafeU32(frameSize); + this.writeU16(0); + this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8); + if (useIso88591) this.writeIsoString(mimeType); + else this.writeUtf8String(mimeType); + this.writeU8(pictureType); + if (useIso88591) this.writeIsoString(description); + else this.writeUtf8String(description); + this.writer.write(imageData); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Muxer = class { + constructor(output) { + this.mutex = new AsyncMutex(); + this.trackTimestampInfo = /* @__PURE__ */ new WeakMap(); + this.output = output; + } + onTrackClose(track) {} + validateTimestamp(track, timestampInSeconds, isKeyPacket) { + let timestampInfo = this.trackTimestampInfo.get(track); + if (!timestampInfo) { + if (!isKeyPacket) throw new Error("First packet must be a key packet."); + timestampInfo = { + maxTimestamp: timestampInSeconds, + maxTimestampBeforeLastKeyPacket: null + }; + this.trackTimestampInfo.set(track, timestampInfo); + } else { + if (isKeyPacket) timestampInfo.maxTimestampBeforeLastKeyPacket = timestampInfo.maxTimestamp; + if (timestampInfo.maxTimestampBeforeLastKeyPacket !== null && timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyPacket) throw new Error(`Timestamps cannot be smaller than the largest timestamp of the previous GOP (a GOP begins with a key packet and ends right before the next key packet). Got ${timestampInSeconds}s, but largest timestamp is ${timestampInfo.maxTimestampBeforeLastKeyPacket}s.`); + timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); + } + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/adts/adts-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var AdtsMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.header = null; + this.headerBitstream = null; + this.inputIsAdts = null; + this.format = format; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(true); + if (!metadataTagsAreEmpty(this.output._metadataTags)) new Id3V2Writer(this.writer).writeId3V2Tag(this.output._metadataTags); + release(); + } + async getMimeType() { + return "audio/aac"; + } + async addEncodedVideoPacket() { + throw new Error("ADTS does not support video."); + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + if (this.inputIsAdts === null) { + validateAudioChunkMetadata(meta, track.source._codec); + const description = meta?.decoderConfig?.description; + this.inputIsAdts = !description; + if (!this.inputIsAdts) { + const template = buildAdtsHeaderTemplate(parseAacAudioSpecificConfig(toUint8Array(description))); + this.header = template.header; + this.headerBitstream = template.bitstream; + } + } + if (this.inputIsAdts) { + const startPos = this.writer.getPos(); + this.writer.write(packet.data); + if (this.format._options.onFrame) this.format._options.onFrame(packet.data, startPos); + } else { + assert(this.header); + const frameLength = packet.data.byteLength + this.header.byteLength; + writeAdtsFrameLength(this.headerBitstream, frameLength); + const startPos = this.writer.getPos(); + this.writer.write(this.header); + this.writer.write(packet.data); + if (this.format._options.onFrame) { + const frameBytes = new Uint8Array(frameLength); + frameBytes.set(this.header, 0); + frameBytes.set(packet.data, this.header.byteLength); + this.format._options.onFrame(frameBytes, startPos); + } + } + await this.writer.flush(); + } finally { + release(); + } + } + async addSubtitleCue() { + throw new Error("ADTS does not support subtitles."); + } + async finalize() { + const release = await this.mutex.acquire(); + if (this.inputIsAdts === null) throw new Error("Cannot finalize an empty ADTS file: not a single packet was added."); + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/flac/flac-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var FLAC_HEADER = /* #__PURE__ */ new Uint8Array([ + 102, + 76, + 97, + 67 +]); +var STREAMINFO_SIZE = 38; +var STREAMINFO_BLOCK_SIZE = 34; +var FlacMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.metadataWritten = false; + this.blockSizes = []; + this.frameSizes = []; + this.sampleRate = null; + this.channels = null; + this.bitsPerSample = null; + this.format = format; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(!!this.format._options.appendOnly); + this.writer.write(FLAC_HEADER); + const track = this.output.tracks[0]; + assert(track?.isAudioTrack()); + if (track.metadata.decoderConfig) { + validateAudioChunkMetadata({ decoderConfig: track.metadata.decoderConfig }, track.source._codec); + this.applyDecoderConfig(track.metadata.decoderConfig); + } + release(); + } + applyDecoderConfig(decoderConfig) { + assert(decoderConfig.description); + this.sampleRate = decoderConfig.sampleRate; + this.channels = decoderConfig.numberOfChannels; + const descriptionBitstream = new Bitstream(toUint8Array(decoderConfig.description)); + descriptionBitstream.skipBits(167); + this.bitsPerSample = descriptionBitstream.readBits(5) + 1; + if (this.format._options.appendOnly) this.writeHeader({ + minimumBlockSize: 16, + maximumBlockSize: 65535, + minimumFrameSize: 0, + maximumFrameSize: 0, + sampleRate: this.sampleRate, + channels: this.channels, + bitsPerSample: this.bitsPerSample, + totalSamples: 0 + }); + } + writeHeader({ bitsPerSample, minimumBlockSize, maximumBlockSize, minimumFrameSize, maximumFrameSize, sampleRate, channels, totalSamples }) { + assert(this.writer.getPos() === 4); + const hasMetadata = !metadataTagsAreEmpty(this.output._metadataTags); + const headerBitstream = new Bitstream(new Uint8Array(4)); + headerBitstream.writeBits(1, Number(!hasMetadata)); + headerBitstream.writeBits(7, FlacBlockType.STREAMINFO); + headerBitstream.writeBits(24, STREAMINFO_BLOCK_SIZE); + this.writer.write(headerBitstream.bytes); + const contentBitstream = new Bitstream(new Uint8Array(18)); + contentBitstream.writeBits(16, minimumBlockSize); + contentBitstream.writeBits(16, maximumBlockSize); + contentBitstream.writeBits(24, minimumFrameSize); + contentBitstream.writeBits(24, maximumFrameSize); + contentBitstream.writeBits(20, sampleRate); + contentBitstream.writeBits(3, channels - 1); + contentBitstream.writeBits(5, bitsPerSample - 1); + if (totalSamples >= 2 ** 32) throw new Error("This muxer only supports writing up to 2 ** 32 samples"); + contentBitstream.writeBits(4, 0); + contentBitstream.writeBits(32, totalSamples); + this.writer.write(contentBitstream.bytes); + this.writer.write(new Uint8Array(16)); + } + writePictureBlock(picture) { + const headerSize = 32 + picture.mimeType.length + (picture.description?.length ?? 0) + picture.data.length; + const header = new Uint8Array(headerSize); + let offset = 0; + const dataView = toDataView(header); + dataView.setUint32(offset, picture.kind === "coverFront" ? 3 : picture.kind === "coverBack" ? 4 : 0); + offset += 4; + dataView.setUint32(offset, picture.mimeType.length); + offset += 4; + header.set(textEncoder.encode(picture.mimeType), 8); + offset += picture.mimeType.length; + dataView.setUint32(offset, picture.description?.length ?? 0); + offset += 4; + header.set(textEncoder.encode(picture.description ?? ""), offset); + offset += picture.description?.length ?? 0; + offset += 16; + dataView.setUint32(offset, picture.data.length); + offset += 4; + header.set(picture.data, offset); + offset += picture.data.length; + assert(offset === headerSize); + const headerBitstream = new Bitstream(new Uint8Array(4)); + headerBitstream.writeBits(1, 0); + headerBitstream.writeBits(7, FlacBlockType.PICTURE); + headerBitstream.writeBits(24, headerSize); + this.writer.write(headerBitstream.bytes); + this.writer.write(header); + } + writeVorbisCommentAndPictureBlock() { + if (!this.format._options.appendOnly) this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength); + if (metadataTagsAreEmpty(this.output._metadataTags)) { + this.metadataWritten = true; + return; + } + const pictures = this.output._metadataTags.images ?? []; + for (const picture of pictures) this.writePictureBlock(picture); + const vorbisComment = createVorbisComments(new Uint8Array(0), this.output._metadataTags, false); + const headerBitstream = new Bitstream(new Uint8Array(4)); + headerBitstream.writeBits(1, 1); + headerBitstream.writeBits(7, FlacBlockType.VORBIS_COMMENT); + headerBitstream.writeBits(24, vorbisComment.length); + this.writer.write(headerBitstream.bytes); + this.writer.write(vorbisComment); + this.metadataWritten = true; + } + async getMimeType() { + return "audio/flac"; + } + async addEncodedVideoPacket() { + throw new Error("FLAC does not support video."); + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + if (this.sampleRate === null) { + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + this.applyDecoderConfig(meta.decoderConfig); + } + if (!this.metadataWritten) this.writeVorbisCommentAndPictureBlock(); + const slice = FileSlice.tempFromBytes(packet.data); + slice.skip(2); + const blockSizeOrUncommon = getBlockSizeOrUncommon(new Bitstream(readBytes(slice, 2)).readBits(4)); + if (blockSizeOrUncommon === null) throw new Error("Invalid FLAC frame: Invalid block size."); + readCodedNumber(slice); + const blockSize = readBlockSize(slice, blockSizeOrUncommon); + if (!this.format._options.appendOnly) { + this.blockSizes.push(blockSize); + this.frameSizes.push(packet.data.length); + } + const startPos = this.writer.getPos(); + this.writer.write(packet.data); + if (this.format._options.onFrame) this.format._options.onFrame(packet.data, startPos); + await this.writer.flush(); + } finally { + release(); + } + } + addSubtitleCue() { + throw new Error("FLAC does not support subtitles."); + } + async finalize() { + const release = await this.mutex.acquire(); + if (this.sampleRate === null) throw new Error("Cannot finalize an empty FLAC file: no packets were added and the track specified no decoderConfig in its metadata, so there's no telling what the file should look like."); + if (!this.metadataWritten) this.writeVorbisCommentAndPictureBlock(); + if (!this.format._options.appendOnly) { + let minimumBlockSize = Infinity; + let maximumBlockSize = 0; + let minimumFrameSize = Infinity; + let maximumFrameSize = 0; + let totalSamples = 0; + for (let i = 0; i < this.blockSizes.length; i++) { + minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]); + maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]); + maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]); + totalSamples += this.blockSizes[i]; + if (i === this.blockSizes.length - 1) continue; + minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]); + } + if (this.blockSizes.length === 0) { + minimumBlockSize = 16; + maximumBlockSize = 65535; + minimumFrameSize = 0; + maximumFrameSize = 0; + } + assert(this.channels !== null); + assert(this.bitsPerSample !== null); + this.writer.seek(4); + this.writeHeader({ + minimumBlockSize, + maximumBlockSize, + minimumFrameSize, + maximumFrameSize, + sampleRate: this.sampleRate, + channels: this.channels, + bitsPerSample: this.bitsPerSample, + totalSamples + }); + } + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/subtitles.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; +var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/; +var inlineTimestampRegex = /<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g; +var SubtitleParser = class { + constructor(options) { + this.preambleText = null; + this.preambleEmitted = false; + this.options = options; + } + parse(text) { + text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); + cueBlockHeaderRegex.lastIndex = 0; + let match; + if (!this.preambleText) { + if (!preambleStartRegex.test(text)) throw new Error("WebVTT preamble incorrect."); + match = cueBlockHeaderRegex.exec(text); + const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); + if (!preamble) throw new Error("No WebVTT preamble provided."); + this.preambleText = preamble; + if (match) { + text = text.slice(match.index); + cueBlockHeaderRegex.lastIndex = 0; + } + } + while (match = cueBlockHeaderRegex.exec(text)) { + const notes = text.slice(0, match.index); + const cueIdentifier = match[1]; + const matchEnd = match.index + match[0].length; + const bodyStart = text.indexOf("\n", matchEnd) + 1; + const cueSettings = text.slice(matchEnd, bodyStart).trim(); + let bodyEnd = text.indexOf("\n\n", matchEnd); + if (bodyEnd === -1) bodyEnd = text.length; + const startTime = parseSubtitleTimestamp(match[2]); + const duration = parseSubtitleTimestamp(match[3]) - startTime; + const body = text.slice(bodyStart, bodyEnd).trim(); + text = text.slice(bodyEnd).trimStart(); + cueBlockHeaderRegex.lastIndex = 0; + const cue = { + timestamp: startTime / 1e3, + duration: duration / 1e3, + text: body, + identifier: cueIdentifier, + settings: cueSettings, + notes + }; + const meta = {}; + if (!this.preambleEmitted) { + meta.config = { description: this.preambleText }; + this.preambleEmitted = true; + } + this.options.output(cue, meta); + } + } +}; +var timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; +var parseSubtitleTimestamp = (string) => { + const match = timestampRegex.exec(string); + if (!match) throw new Error("Expected match."); + return 3600 * 1e3 * Number(match[1] || "0") + 60 * 1e3 * Number(match[2]) + 1e3 * Number(match[3]) + Number(match[4]); +}; +var formatSubtitleTimestamp = (timestamp) => { + const hours = Math.floor(timestamp / (3600 * 1e3)); + const minutes = Math.floor(timestamp % (3600 * 1e3) / (60 * 1e3)); + const seconds = Math.floor(timestamp % (60 * 1e3) / 1e3); + const milliseconds = timestamp % 1e3; + return hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + seconds.toString().padStart(2, "0") + "." + milliseconds.toString().padStart(3, "0"); +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/isobmff/isobmff-boxes.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var IsobmffBoxWriter = class { + constructor(writer) { + this.writer = writer; + this.helper = new Uint8Array(8); + this.helperView = new DataView(this.helper.buffer); + /** + * Stores the position from the start of the file to where boxes elements have been written. This is used to + * rewrite/edit elements that were already added before, and to measure sizes of things. + */ + this.offsets = /* @__PURE__ */ new WeakMap(); + } + writeU32(value) { + this.helperView.setUint32(0, value, false); + this.writer.write(this.helper.subarray(0, 4)); + } + writeU64(value) { + this.helperView.setUint32(0, Math.floor(value / 2 ** 32), false); + this.helperView.setUint32(4, value, false); + this.writer.write(this.helper.subarray(0, 8)); + } + writeAscii(text) { + for (let i = 0; i < text.length; i++) { + this.helperView.setUint8(i % 8, text.charCodeAt(i)); + if (i % 8 === 7) this.writer.write(this.helper); + } + if (text.length % 8 !== 0) this.writer.write(this.helper.subarray(0, text.length % 8)); + } + writeBox(box) { + this.offsets.set(box, this.writer.getPos()); + if (box.contents && !box.children) { + this.writeBoxHeader(box, box.size ?? box.contents.byteLength + 8); + this.writer.write(box.contents); + } else { + const startPos = this.writer.getPos(); + this.writeBoxHeader(box, 0); + if (box.contents) this.writer.write(box.contents); + if (box.children) { + for (const child of box.children) if (child) this.writeBox(child); + } + const endPos = this.writer.getPos(); + const size = box.size ?? endPos - startPos; + this.writer.seek(startPos); + this.writeBoxHeader(box, size); + this.writer.seek(endPos); + } + } + writeBoxHeader(box, size) { + this.writeU32(box.largeSize ? 1 : size); + this.writeAscii(box.type); + if (box.largeSize) this.writeU64(size); + } + measureBoxHeader(box) { + return 8 + (box.largeSize ? 8 : 0); + } + patchBox(box) { + const boxOffset = this.offsets.get(box); + assert(boxOffset !== void 0); + const endPos = this.writer.getPos(); + this.writer.seek(boxOffset); + this.writeBox(box); + this.writer.seek(endPos); + } + measureBox(box) { + if (box.contents && !box.children) return this.measureBoxHeader(box) + box.contents.byteLength; + else { + let result = this.measureBoxHeader(box); + if (box.contents) result += box.contents.byteLength; + if (box.children) { + for (const child of box.children) if (child) result += this.measureBox(child); + } + return result; + } + } +}; +var bytes = /* #__PURE__ */ new Uint8Array(8); +var view = /* #__PURE__ */ new DataView(bytes.buffer); +var u8 = (value) => { + return [(value % 256 + 256) % 256]; +}; +var u16 = (value) => { + view.setUint16(0, value, false); + return [bytes[0], bytes[1]]; +}; +var i16 = (value) => { + view.setInt16(0, value, false); + return [bytes[0], bytes[1]]; +}; +var u24 = (value) => { + view.setUint32(0, value, false); + return [ + bytes[1], + bytes[2], + bytes[3] + ]; +}; +var u32 = (value) => { + view.setUint32(0, value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ]; +}; +var i32 = (value) => { + view.setInt32(0, value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ]; +}; +var u64 = (value) => { + view.setUint32(0, Math.floor(value / 2 ** 32), false); + view.setUint32(4, value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3], + bytes[4], + bytes[5], + bytes[6], + bytes[7] + ]; +}; +var i64 = (value) => { + view.setInt32(0, Math.floor(value / 2 ** 32), false); + view.setUint32(4, value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3], + bytes[4], + bytes[5], + bytes[6], + bytes[7] + ]; +}; +var fixed_8_8 = (value) => { + view.setInt16(0, 2 ** 8 * value, false); + return [bytes[0], bytes[1]]; +}; +var fixed_16_16 = (value) => { + view.setInt32(0, 2 ** 16 * value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ]; +}; +var fixed_2_30 = (value) => { + view.setInt32(0, 2 ** 30 * value, false); + return [ + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ]; +}; +var variableUnsignedInt = (value, byteLength) => { + const bytes = []; + let remaining = value; + do { + let byte = remaining & 127; + remaining >>= 7; + if (bytes.length > 0) byte |= 128; + bytes.push(byte); + if (byteLength !== void 0) byteLength--; + } while (remaining > 0 || byteLength); + return bytes.reverse(); +}; +var ascii = (text, nullTerminated = false) => { + const bytes = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); + if (nullTerminated) bytes.push(0); + return bytes; +}; +var matrixToBytes = (matrix) => { + return [ + fixed_16_16(matrix[0]), + fixed_16_16(matrix[1]), + fixed_2_30(matrix[2]), + fixed_16_16(matrix[3]), + fixed_16_16(matrix[4]), + fixed_2_30(matrix[5]), + fixed_16_16(matrix[6]), + fixed_16_16(matrix[7]), + fixed_2_30(matrix[8]) + ]; +}; +var box = (type, contents, children) => ({ + type, + contents: contents && new Uint8Array(contents.flat(10)), + children +}); +/** A FullBox always starts with a version byte, followed by three flag bytes. */ +var fullBox = (type, version, flags, contents, children) => box(type, [ + u8(version), + u24(flags), + contents ?? [] +], children); +/** +* File Type Compatibility Box: Allows the reader to determine whether this is a type of file that the +* reader understands. +*/ +var ftyp = (details) => { + const minorVersion = 512; + if (details.isQuickTime) return box("ftyp", [ + ascii("qt "), + u32(minorVersion), + ascii("qt ") + ]); + if (details.fragmented) if (details.cmaf) return box("ftyp", [ + ascii("iso5"), + u32(minorVersion), + ascii("iso5"), + ascii("iso6"), + ascii("mp41"), + ascii("cmfc"), + ascii("dash") + ]); + else return box("ftyp", [ + ascii("iso5"), + u32(minorVersion), + ascii("iso5"), + ascii("iso6"), + ascii("mp41") + ]); + return box("ftyp", [ + ascii("isom"), + u32(minorVersion), + ascii("isom"), + details.holdsAvc ? ascii("avc1") : [], + ascii("mp41") + ]); +}; +/** Segment Type Box */ +var styp = () => box("styp", [ + ascii("iso5"), + u32(0), + ascii("iso5"), + ascii("iso6"), + ascii("mp41"), + ascii("cmfc"), + ascii("dash") +]); +/** Segment Index Box */ +var sidx = (muxer, referencedSize) => { + const earliestPresentationTime = Math.max(0, muxer.minWrittenTimestamp); + let duration = Math.max(0, muxer.maxWrittenEndTimestamp - earliestPresentationTime); + if (!Number.isFinite(duration)) duration = 0; + return fullBox("sidx", 1, 0, [ + u32(1), + u32(GLOBAL_TIMESCALE), + u64(intoTimescale(earliestPresentationTime, GLOBAL_TIMESCALE)), + u64(0), + u16(0), + u16(1), + u32(referencedSize & 2147483647), + u32(intoTimescale(duration, GLOBAL_TIMESCALE)), + u32(0) + ]); +}; +/** Movie Sample Data Box. Contains the actual frames/samples of the media. */ +var mdat = (reserveLargeSize) => ({ + type: "mdat", + largeSize: reserveLargeSize +}); +/** Free Space Box: A box that designates unused space in the movie data file. */ +var free = (size) => ({ + type: "free", + size +}); +/** +* Movie Box: Used to specify the information that defines a movie - that is, the information that allows +* an application to interpret the sample data that is stored elsewhere. +*/ +var moov = (muxer) => { + return box("moov", void 0, [ + mvhd(muxer.creationTime, muxer.trackDatas), + ...muxer.trackDatas.map((x) => trak(x, muxer.creationTime)), + muxer.isFragmented ? mvex(muxer.trackDatas) : null, + udta(muxer) + ]); +}; +/** Movie Header Box: Used to specify the characteristics of the entire movie, such as timescale and duration. */ +var mvhd = (creationTime, trackDatas) => { + const duration = Math.max(0, ...trackDatas.map((trackData) => Math.max(0, intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)))); + const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1; + const needsU64 = !isU32(creationTime) || !isU32(duration); + const u32OrU64 = needsU64 ? u64 : u32; + return fullBox("mvhd", +needsU64, 0, [ + u32OrU64(creationTime), + u32OrU64(creationTime), + u32(GLOBAL_TIMESCALE), + u32OrU64(duration), + fixed_16_16(1), + fixed_8_8(1), + Array(10).fill(0), + matrixToBytes(IDENTITY_MATRIX), + Array(24).fill(0), + u32(nextTrackId) + ]); +}; +/** +* Track Box: Defines a single track of a movie. A movie may consist of one or more tracks. Each track is +* independent of the other tracks in the movie and carries its own temporal and spatial information. Each Track Box +* contains its associated Media Box. +*/ +var trak = (trackData, creationTime) => { + const trackMetadata = getTrackMetadata(trackData); + const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset !== 0; + return box("trak", void 0, [ + tkhd(trackData, creationTime), + needsEditList ? edts(trackData) : null, + mdia(trackData, creationTime), + trackMetadata.name !== void 0 ? box("udta", void 0, [box("name", [...textEncoder.encode(trackMetadata.name)])]) : null + ]); +}; +/** Track Header Box: Specifies the characteristics of a single track within a movie. */ +var tkhd = (trackData, creationTime) => { + const durationInGlobalTimescale = Math.max(0, intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)); + const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); + const u32OrU64 = needsU64 ? u64 : u32; + let matrix; + if (trackData.type === "video" && trackData.track.metadata.transformationMatrix) matrix = trackData.track.metadata.transformationMatrix; + else if (trackData.type === "video") { + const { rotation, flip } = trackData.track.metadata; + matrix = centeredTransformationMatrix(multiplyMatrices(rotationMatrix(rotation ?? 0), scaleMatrix(flip ? -1 : 1, 1)), trackData.info.width, trackData.info.height); + } else matrix = IDENTITY_MATRIX; + let flags = 2; + if (trackData.track.metadata.disposition?.default !== false) flags |= 1; + const alternateGroup = trackData.type === "video" ? 0 : trackData.type === "audio" ? 1 : trackData.type === "subtitle" ? 2 : assertNever(trackData); + return fullBox("tkhd", +needsU64, flags, [ + u32OrU64(creationTime), + u32OrU64(creationTime), + u32(trackData.track.id), + u32(0), + u32OrU64(durationInGlobalTimescale), + Array(8).fill(0), + u16(0), + u16(alternateGroup), + fixed_8_8(trackData.type === "audio" ? 1 : 0), + u16(0), + matrixToBytes(matrix), + fixed_16_16(trackData.type === "video" ? trackData.info.width : 0), + fixed_16_16(trackData.type === "video" ? trackData.info.height : 0) + ]); +}; +/** Edit Box: Specifies edits to the track's media. */ +var edts = (trackData) => { + const offset = trackData.startTimestampOffset; + assert(offset !== null); + if (offset > 0) { + const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE); + const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE); + const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration); + const u32OrU64 = needs64Bits ? u64 : u32; + const i32OrI64 = needs64Bits ? i64 : i32; + return box("edts", void 0, [fullBox("elst", needs64Bits ? 1 : 0, 0, [ + u32(2), + u32OrU64(startOffset), + i32OrI64(-1), + fixed_16_16(1), + u32OrU64(mediaDuration), + i32OrI64(0), + fixed_16_16(1) + ])]); + } else { + const mediaTime = intoTimescale(-offset, trackData.timescale); + const mediaDuration = Math.max(0, intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(offset, GLOBAL_TIMESCALE)); + const needs64Bits = !isI32(mediaTime) || !isU32(mediaDuration); + const u32OrU64 = needs64Bits ? u64 : u32; + const i32OrI64 = needs64Bits ? i64 : i32; + return box("edts", void 0, [fullBox("elst", needs64Bits ? 1 : 0, 0, [ + u32(1), + u32OrU64(mediaDuration), + i32OrI64(mediaTime), + fixed_16_16(1) + ])]); + } +}; +/** Media Box: Describes and define a track's media type and sample data. */ +var mdia = (trackData, creationTime) => box("mdia", void 0, [ + mdhd(trackData, creationTime), + hdlr(true, TRACK_TYPE_TO_COMPONENT_SUBTYPE[trackData.type], TRACK_TYPE_TO_HANDLER_NAME[trackData.type]), + minf(trackData) +]); +/** Media Header Box: Specifies the characteristics of a media, including timescale and duration. */ +var mdhd = (trackData, creationTime) => { + const localDuration = intoTimescale(presentationSpan(trackData), trackData.timescale); + const needsU64 = !isU32(creationTime) || !isU32(localDuration); + const u32OrU64 = needsU64 ? u64 : u32; + return fullBox("mdhd", +needsU64, 0, [ + u32OrU64(creationTime), + u32OrU64(creationTime), + u32(trackData.timescale), + u32OrU64(localDuration), + u16(getLanguageCodeInt(trackData.track.metadata.languageCode ?? "und")), + u16(0) + ]); +}; +var TRACK_TYPE_TO_COMPONENT_SUBTYPE = { + video: "vide", + audio: "soun", + subtitle: "text" +}; +var TRACK_TYPE_TO_HANDLER_NAME = { + video: "MediabunnyVideoHandler", + audio: "MediabunnySoundHandler", + subtitle: "MediabunnyTextHandler" +}; +/** Handler Reference Box. */ +var hdlr = (hasComponentType, handlerType, name, manufacturer = "\0\0\0\0") => fullBox("hdlr", 0, 0, [ + hasComponentType ? ascii("mhlr") : u32(0), + ascii(handlerType), + ascii(manufacturer), + u32(0), + u32(0), + ascii(name, true) +]); +/** +* Media Information Box: Stores handler-specific information for a track's media data. The media handler uses this +* information to map from media time to media data and to process the media data. +*/ +var minf = (trackData) => box("minf", void 0, [ + TRACK_TYPE_TO_HEADER_BOX[trackData.type](), + dinf(), + stbl(trackData) +]); +/** Video Media Information Header Box: Defines specific color and graphics mode information. */ +var vmhd = () => fullBox("vmhd", 0, 1, [ + u16(0), + u16(0), + u16(0), + u16(0) +]); +/** Sound Media Information Header Box: Stores the sound media's control information, such as balance. */ +var smhd = () => fullBox("smhd", 0, 0, [u16(0), u16(0)]); +/** Null Media Header Box. */ +var nmhd = () => fullBox("nmhd", 0, 0); +var TRACK_TYPE_TO_HEADER_BOX = { + video: vmhd, + audio: smhd, + subtitle: nmhd +}; +/** +* Data Information Box: Contains information specifying the data handler component that provides access to the +* media data. The data handler component uses the Data Information Box to interpret the media's data. +*/ +var dinf = () => box("dinf", void 0, [dref()]); +/** +* Data Reference Box: Contains tabular data that instructs the data handler component how to access the media's data. +*/ +var dref = () => fullBox("dref", 0, 0, [u32(1)], [url()]); +var url = () => fullBox("url ", 0, 1); +/** +* Sample Table Box: Contains information for converting from media time to sample number to sample location. This box +* also indicates how to interpret the sample (for example, whether to decompress the video data and, if so, how). +*/ +var stbl = (trackData) => { + const needsCtts = trackData.compositionTimeOffsetTable.length > 1 || trackData.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0); + return box("stbl", void 0, [ + stsd(trackData), + stts(trackData), + needsCtts ? ctts(trackData) : null, + needsCtts ? cslg(trackData) : null, + stsc(trackData), + stsz(trackData), + stco(trackData), + stss(trackData) + ]); +}; +/** +* Sample Description Box: Stores information that allows you to decode samples in the media. The data stored in the +* sample description varies, depending on the media type. +*/ +var stsd = (trackData) => { + let sampleDescription; + if (trackData.type === "video") sampleDescription = videoSampleDescription(videoCodecToBoxName(trackData.track.source._codec, trackData.info.decoderConfig.codec), trackData); + else if (trackData.type === "audio") { + const boxName = audioCodecToBoxName(trackData.track.source._codec, trackData.info.decoderConfig.codec, trackData.muxer.isQuickTime); + assert(boxName); + sampleDescription = soundSampleDescription(boxName, trackData); + } else if (trackData.type === "subtitle") sampleDescription = subtitleSampleDescription(SUBTITLE_CODEC_TO_BOX_NAME[trackData.track.source._codec], trackData); + assert(sampleDescription); + return fullBox("stsd", 0, 0, [u32(1)], [sampleDescription]); +}; +/** Video Sample Description Box: Contains information that defines how to interpret video media data. */ +var videoSampleDescription = (compressionType, trackData) => box(compressionType, [ + Array(6).fill(0), + u16(1), + u16(0), + u16(0), + Array(12).fill(0), + u16(trackData.info.width), + u16(trackData.info.height), + u32(4718592), + u32(4718592), + u32(0), + u16(1), + u8(VERSIONED_NAME.length), + ascii(VERSIONED_NAME), + Array(31 - VERSIONED_NAME.length).fill(0), + u16(trackData.info.hasAlphaChannel ? 32 : 24), + i16(65535) +], [ + VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec]?.(trackData) ?? null, + pasp(trackData), + colorSpaceIsEmpty(trackData.info.decoderConfig.colorSpace) ? null : colr(trackData), + btrt(trackData) +]); +/** Bit Rate Box: Signals the average and peak bitrate of the track. */ +var btrt = (trackData) => { + if (trackData.avgBitrate === 0 && trackData.maxBitrate === 0) return null; + return box("btrt", [ + u32(0), + u32(trackData.maxBitrate), + u32(trackData.avgBitrate) + ]); +}; +/** Pixel Aspect Ratio Box: Specifies pixel width:height spacing for non-square pixels. */ +var pasp = (trackData) => { + if (trackData.info.pixelAspectRatio.num === trackData.info.pixelAspectRatio.den) return null; + return box("pasp", [u32(trackData.info.pixelAspectRatio.num), u32(trackData.info.pixelAspectRatio.den)]); +}; +/** Colour Information Box: Specifies the color space of the video. */ +var colr = (trackData) => { + const colorSpace = trackData.info.decoderConfig.colorSpace; + return box("colr", [ + ascii(trackData.muxer.isQuickTime ? "nclc" : "nclx"), + u16(colorSpace?.primaries != null ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 2), + u16(colorSpace?.transfer != null ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 2), + u16(colorSpace?.matrix != null ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 2), + trackData.muxer.isQuickTime ? [] : u8((colorSpace?.fullRange ? 1 : 0) << 7) + ]); +}; +/** AVC Configuration Box: Provides additional information to the decoder. */ +var avcC = (trackData) => trackData.info.decoderConfig && box("avcC", [...toUint8Array(trackData.info.decoderConfig.description)]); +/** HEVC Configuration Box: Provides additional information to the decoder. */ +var hvcC = (trackData) => trackData.info.decoderConfig && box("hvcC", [...toUint8Array(trackData.info.decoderConfig.description)]); +/** VP Configuration Box: Provides additional information to the decoder. */ +var vpcC = (trackData) => { + if (!trackData.info.decoderConfig) return null; + const decoderConfig = trackData.info.decoderConfig; + const parts = decoderConfig.codec.split("."); + const profile = Number(parts[1]); + const level = Number(parts[2]); + const bitDepth = Number(parts[3]); + const chromaSubsampling = parts[4] ? Number(parts[4]) : 1; + const videoFullRangeFlag = parts[8] ? Number(parts[8]) : Number(decoderConfig.colorSpace?.fullRange ?? 0); + const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + videoFullRangeFlag; + const colourPrimaries = parts[5] ? Number(parts[5]) : decoderConfig.colorSpace?.primaries ? COLOR_PRIMARIES_MAP[decoderConfig.colorSpace.primaries] : 1; + const transferCharacteristics = parts[6] ? Number(parts[6]) : decoderConfig.colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[decoderConfig.colorSpace.transfer] : 1; + const matrixCoefficients = parts[7] ? Number(parts[7]) : decoderConfig.colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[decoderConfig.colorSpace.matrix] : 1; + return fullBox("vpcC", 1, 0, [ + u8(profile), + u8(level), + u8(thirdByte), + u8(colourPrimaries), + u8(transferCharacteristics), + u8(matrixCoefficients), + u16(0) + ]); +}; +/** AV1 Configuration Box: Provides additional information to the decoder. */ +var av1C = (trackData) => { + return box("av1C", generateAv1CodecConfigurationFromCodecString(trackData.info.decoderConfig.codec)); +}; +/** Sound Sample Description Box: Contains information that defines how to interpret sound media data. */ +var soundSampleDescription = (compressionType, trackData) => { + let version = 0; + let contents; + let sampleSizeInBits = 16; + const isPcmCodec = PCM_AUDIO_CODECS.includes(trackData.track.source._codec); + if (isPcmCodec) { + const codec = trackData.track.source._codec; + const { sampleSize } = parsePcmCodec(codec); + sampleSizeInBits = 8 * sampleSize; + if (sampleSizeInBits > 16) version = 1; + } + if (trackData.muxer.isQuickTime) version = 1; + if (version === 0) contents = [ + Array(6).fill(0), + u16(1), + u16(version), + u16(0), + u32(0), + u16(trackData.info.numberOfChannels), + u16(sampleSizeInBits), + u16(0), + u16(0), + u16(trackData.info.sampleRate < 2 ** 16 ? trackData.info.sampleRate : 0), + u16(0) + ]; + else { + const compressionId = isPcmCodec ? 0 : -2; + contents = [ + Array(6).fill(0), + u16(1), + u16(version), + u16(0), + u32(0), + u16(trackData.info.numberOfChannels), + u16(Math.min(sampleSizeInBits, 16)), + i16(compressionId), + u16(0), + u16(trackData.info.sampleRate < 2 ** 16 ? trackData.info.sampleRate : 0), + u16(0), + isPcmCodec ? [ + u32(1), + u32(sampleSizeInBits / 8), + u32(trackData.info.numberOfChannels * sampleSizeInBits / 8) + ] : [ + u32(0), + u32(0), + u32(0) + ], + u32(2) + ]; + } + return box(compressionType, contents, [audioCodecToConfigurationBox(trackData.track.source._codec, trackData.muxer.isQuickTime)?.(trackData) ?? null, btrt(trackData)]); +}; +/** MPEG-4 Elementary Stream Descriptor Box. */ +var esds = (trackData) => { + let objectTypeIndication; + switch (trackData.track.source._codec) { + case "aac": + objectTypeIndication = 64; + break; + case "mp3": + objectTypeIndication = 107; + break; + case "vorbis": + objectTypeIndication = 221; + break; + default: throw new Error(`Unhandled audio codec: ${trackData.track.source._codec}`); + } + let bytes = [ + ...u8(objectTypeIndication), + ...u8(21), + ...u24(0), + ...u32(trackData.maxBitrate), + ...u32(trackData.avgBitrate) + ]; + if (trackData.info.decoderConfig.description) { + const description = toUint8Array(trackData.info.decoderConfig.description); + bytes = [ + ...bytes, + ...u8(5), + ...variableUnsignedInt(description.byteLength), + ...description + ]; + } + bytes = [ + ...u16(1), + ...u8(0), + ...u8(4), + ...variableUnsignedInt(bytes.length), + ...bytes, + ...u8(6), + ...u8(1), + ...u8(2) + ]; + bytes = [ + ...u8(3), + ...variableUnsignedInt(bytes.length), + ...bytes + ]; + return fullBox("esds", 0, 0, bytes); +}; +var wave = (trackData) => { + return box("wave", void 0, [ + frma(trackData), + enda(trackData), + box("\0\0\0\0") + ]); +}; +var frma = (trackData) => { + return box("frma", [ascii(audioCodecToBoxName(trackData.track.source._codec, trackData.info.decoderConfig.codec, trackData.muxer.isQuickTime))]); +}; +var enda = (trackData) => { + const { littleEndian } = parsePcmCodec(trackData.track.source._codec); + return box("enda", [u16(+littleEndian)]); +}; +/** Opus Specific Box. */ +var dOps = (trackData) => { + let outputChannelCount = trackData.info.numberOfChannels; + let preSkip = 3840; + let inputSampleRate = trackData.info.sampleRate; + let outputGain = 0; + let channelMappingFamily = 0; + let channelMappingTable = new Uint8Array(0); + const description = trackData.info.decoderConfig?.description; + if (description) { + assert(description.byteLength >= 18); + const header = parseOpusIdentificationHeader(toUint8Array(description)); + outputChannelCount = header.outputChannelCount; + preSkip = header.preSkip; + inputSampleRate = header.inputSampleRate; + outputGain = header.outputGain; + channelMappingFamily = header.channelMappingFamily; + if (header.channelMappingTable) channelMappingTable = header.channelMappingTable; + } + return box("dOps", [ + u8(0), + u8(outputChannelCount), + u16(preSkip), + u32(inputSampleRate), + i16(outputGain), + u8(channelMappingFamily), + ...channelMappingTable + ]); +}; +/** FLAC specific box. */ +var dfLa = (trackData) => { + const description = trackData.info.decoderConfig?.description; + assert(description); + return fullBox("dfLa", 0, 0, [...toUint8Array(description).subarray(4)]); +}; +/** PCM Configuration Box, ISO/IEC 23003-5. */ +var pcmC = (trackData) => { + const { littleEndian, sampleSize } = parsePcmCodec(trackData.track.source._codec); + return fullBox("pcmC", 0, 0, [u8(+littleEndian), u8(8 * sampleSize)]); +}; +/** AC3SpecificBox */ +var dac3 = (trackData) => { + assert(trackData.info.primingPacket); + const frameInfo = parseAc3SyncFrame(trackData.info.primingPacket.data); + if (!frameInfo) throw new Error("Couldn't extract AC-3 frame info from the audio packet. Ensure the packets contain valid AC-3 sync frames (as specified in ETSI TS 102 366)."); + const bytes = new Uint8Array(3); + const bitstream = new Bitstream(bytes); + bitstream.writeBits(2, frameInfo.fscod); + bitstream.writeBits(5, frameInfo.bsid); + bitstream.writeBits(3, frameInfo.bsmod); + bitstream.writeBits(3, frameInfo.acmod); + bitstream.writeBits(1, frameInfo.lfeon); + bitstream.writeBits(5, frameInfo.bitRateCode); + bitstream.writeBits(5, 0); + return box("dac3", [...bytes]); +}; +/** EC3SpecificBox */ +var dec3 = (trackData) => { + assert(trackData.info.primingPacket); + const frameInfo = parseEac3SyncFrame(trackData.info.primingPacket.data); + if (!frameInfo) throw new Error("Couldn't extract E-AC-3 frame info from the audio packet. Ensure the packets contain valid E-AC-3 sync frames (as specified in ETSI TS 102 366)."); + let totalBits = 16; + for (const sub of frameInfo.substreams) { + totalBits += 23; + if (sub.numDepSub > 0) totalBits += 9; + else totalBits += 1; + } + const size = Math.ceil(totalBits / 8); + const bytes = new Uint8Array(size); + const bitstream = new Bitstream(bytes); + bitstream.writeBits(13, frameInfo.dataRate); + bitstream.writeBits(3, frameInfo.substreams.length - 1); + for (const sub of frameInfo.substreams) { + bitstream.writeBits(2, sub.fscod); + bitstream.writeBits(5, sub.bsid); + bitstream.writeBits(1, 0); + bitstream.writeBits(1, 0); + bitstream.writeBits(3, sub.bsmod); + bitstream.writeBits(3, sub.acmod); + bitstream.writeBits(1, sub.lfeon); + bitstream.writeBits(3, 0); + bitstream.writeBits(4, sub.numDepSub); + if (sub.numDepSub > 0) bitstream.writeBits(9, sub.chanLoc); + else bitstream.writeBits(1, 0); + } + return box("dec3", [...bytes]); +}; +/** DTSSpecificBox */ +var ddts = (trackData) => { + assert(trackData.info.primingPacket); + const frameInfo = parseDtsFrame(trackData.info.primingPacket.data); + if (!frameInfo) throw new Error("Couldn't extract DTS frame info from the audio packet. Ensure the packets contain valid DTS frames as specified in ETSI TS 102 114."); + return box("ddts", [...buildDtsSpecificBox(frameInfo)]); +}; +var subtitleSampleDescription = (compressionType, trackData) => box(compressionType, [Array(6).fill(0), u16(1)], [SUBTITLE_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), btrt(trackData)]); +var vttC = (trackData) => box("vttC", [...textEncoder.encode(trackData.info.config.description)]); +/** +* Time-To-Sample Box: Stores duration information for a media's samples, providing a mapping from a time in a media +* to the corresponding data sample. The table is compact, meaning that consecutive samples with the same time delta +* will be grouped. +*/ +var stts = (trackData) => { + return fullBox("stts", 0, 0, [u32(trackData.timeToSampleTable.length), trackData.timeToSampleTable.map((x) => [u32(x.sampleCount), u32(x.sampleDelta)])]); +}; +/** Sync Sample Box: Identifies the key frames in the media, marking the random access points within a stream. */ +var stss = (trackData) => { + if (trackData.samples.every((x) => x.type === "key")) return null; + const keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === "key"); + return fullBox("stss", 0, 0, [u32(keySamples.length), keySamples.map(([index]) => u32(index + 1))]); +}; +/** +* Sample-To-Chunk Box: As samples are added to a media, they are collected into chunks that allow optimized data +* access. A chunk contains one or more samples. Chunks in a media may have different sizes, and the samples within a +* chunk may have different sizes. The Sample-To-Chunk Box stores chunk information for the samples in a media, stored +* in a compactly-coded fashion. +*/ +var stsc = (trackData) => { + return fullBox("stsc", 0, 0, [u32(trackData.compactlyCodedChunkTable.length), trackData.compactlyCodedChunkTable.map((x) => [ + u32(x.firstChunk), + u32(x.samplesPerChunk), + u32(1) + ])]); +}; +/** Sample Size Box: Specifies the byte size of each sample in the media. */ +var stsz = (trackData) => { + if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) { + const { sampleSize } = parsePcmCodec(trackData.track.source._codec); + return fullBox("stsz", 0, 0, [u32(sampleSize * trackData.info.numberOfChannels), u32(trackData.samples.reduce((acc, x) => acc + intoTimescale(x.duration, trackData.timescale), 0))]); + } + return fullBox("stsz", 0, 0, [ + u32(0), + u32(trackData.samples.length), + trackData.samples.map((x) => u32(x.size)) + ]); +}; +/** Chunk Offset Box: Identifies the location of each chunk of data in the media's data stream, relative to the file. */ +var stco = (trackData) => { + if (trackData.finalizedChunks.length > 0 && last(trackData.finalizedChunks).offset >= 2 ** 32) return fullBox("co64", 0, 0, [u32(trackData.finalizedChunks.length), trackData.finalizedChunks.map((x) => u64(x.offset))]); + return fullBox("stco", 0, 0, [u32(trackData.finalizedChunks.length), trackData.finalizedChunks.map((x) => u32(x.offset))]); +}; +/** +* Composition Time to Sample Box: Stores composition time offset information (PTS-DTS) for a +* media's samples. The table is compact, meaning that consecutive samples with the same time +* composition time offset will be grouped. +*/ +var ctts = (trackData) => { + return fullBox("ctts", 1, 0, [u32(trackData.compositionTimeOffsetTable.length), trackData.compositionTimeOffsetTable.map((x) => [u32(x.sampleCount), i32(x.sampleCompositionTimeOffset)])]); +}; +/** +* Composition to Decode Box: Stores information about the composition and display times of the media samples. +*/ +var cslg = (trackData) => { + let leastDecodeToDisplayDelta = Infinity; + let greatestDecodeToDisplayDelta = -Infinity; + let compositionStartTime = Infinity; + let compositionEndTime = -Infinity; + assert(trackData.compositionTimeOffsetTable.length > 0); + assert(trackData.samples.length > 0); + for (let i = 0; i < trackData.compositionTimeOffsetTable.length; i++) { + const entry = trackData.compositionTimeOffsetTable[i]; + leastDecodeToDisplayDelta = Math.min(leastDecodeToDisplayDelta, entry.sampleCompositionTimeOffset); + greatestDecodeToDisplayDelta = Math.max(greatestDecodeToDisplayDelta, entry.sampleCompositionTimeOffset); + } + for (let i = 0; i < trackData.samples.length; i++) { + const sample = trackData.samples[i]; + compositionStartTime = Math.min(compositionStartTime, intoTimescale(sample.timestamp, trackData.timescale)); + compositionEndTime = Math.max(compositionEndTime, intoTimescale(sample.timestamp + sample.duration, trackData.timescale)); + } + const compositionToDtsShift = Math.max(-leastDecodeToDisplayDelta, 0); + if (compositionEndTime >= 2 ** 31) return null; + return fullBox("cslg", 0, 0, [ + i32(compositionToDtsShift), + i32(leastDecodeToDisplayDelta), + i32(greatestDecodeToDisplayDelta), + i32(compositionStartTime), + i32(compositionEndTime) + ]); +}; +/** +* Movie Extends Box: This box signals to readers that the file is fragmented. Contains a single Track Extends Box +* for each track in the movie. +*/ +var mvex = (trackDatas) => { + return box("mvex", void 0, trackDatas.map(trex)); +}; +/** Track Extends Box: Contains the default values used by the movie fragments. */ +var trex = (trackData) => { + return fullBox("trex", 0, 0, [ + u32(trackData.track.id), + u32(1), + u32(0), + u32(0), + u32(0) + ]); +}; +/** +* Movie Fragment Box: The movie fragments extend the presentation in time. They provide the information that would +* previously have been in the Movie Box. +*/ +var moof = (sequenceNumber, trackDatas) => { + return box("moof", void 0, [mfhd(sequenceNumber), ...trackDatas.map(traf)]); +}; +/** Movie Fragment Header Box: Contains a sequence number as a safety check. */ +var mfhd = (sequenceNumber) => { + return fullBox("mfhd", 0, 0, [u32(sequenceNumber)]); +}; +var fragmentSampleFlags = (sample) => { + let byte1 = 0; + let byte2 = 0; + const sampleIsDifferenceSample = sample.type === "delta"; + byte2 |= +sampleIsDifferenceSample; + if (sampleIsDifferenceSample) byte1 |= 1; + else byte1 |= 2; + return byte1 << 24 | byte2 << 16 | 0; +}; +/** Track Fragment Box */ +var traf = (trackData) => { + return box("traf", void 0, [ + tfhd(trackData), + tfdt(trackData), + trun(trackData) + ]); +}; +/** Track Fragment Header Box: Provides a reference to the extended track, and flags. */ +var tfhd = (trackData) => { + assert(trackData.currentChunk); + let tfFlags = 0; + tfFlags |= 8; + tfFlags |= 16; + tfFlags |= 32; + tfFlags |= 131072; + const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; + const referenceSampleInfo = { + duration: referenceSample.timescaleUnitsToNextSample, + size: referenceSample.size, + flags: fragmentSampleFlags(referenceSample) + }; + return fullBox("tfhd", 0, tfFlags, [ + u32(trackData.track.id), + u32(referenceSampleInfo.duration), + u32(referenceSampleInfo.size), + u32(referenceSampleInfo.flags) + ]); +}; +/** +* Track Fragment Decode Time Box: Provides the absolute decode time of the first sample of the fragment. This is +* useful for performing random access on the media file. +*/ +var tfdt = (trackData) => { + assert(trackData.currentChunk); + return fullBox("tfdt", 1, 0, [u64(intoTimescale(trackData.currentChunk.startTimestamp, trackData.timescale))]); +}; +/** Track Run Box: Specifies a run of contiguous samples for a given track. */ +var trun = (trackData) => { + assert(trackData.currentChunk); + const allSampleDurations = trackData.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample); + const allSampleSizes = trackData.currentChunk.samples.map((x) => x.size); + const allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); + const allSampleCompositionTimeOffsets = trackData.currentChunk.samples.map((x) => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); + const uniqueSampleDurations = new Set(allSampleDurations); + const uniqueSampleSizes = new Set(allSampleSizes); + const uniqueSampleFlags = new Set(allSampleFlags); + const uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); + const firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; + const sampleDurationPresent = uniqueSampleDurations.size > 1; + const sampleSizePresent = uniqueSampleSizes.size > 1; + const sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; + const sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0); + let flags = 0; + flags |= 1; + flags |= 4 * +firstSampleFlagsPresent; + flags |= 256 * +sampleDurationPresent; + flags |= 512 * +sampleSizePresent; + flags |= 1024 * +sampleFlagsPresent; + flags |= 2048 * +sampleCompositionTimeOffsetsPresent; + return fullBox("trun", 1, flags, [ + u32(trackData.currentChunk.samples.length), + u32(trackData.currentChunk.offset - trackData.currentChunk.moofOffset || 0), + firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [], + trackData.currentChunk.samples.map((_, i) => [ + sampleDurationPresent ? u32(allSampleDurations[i]) : [], + sampleSizePresent ? u32(allSampleSizes[i]) : [], + sampleFlagsPresent ? u32(allSampleFlags[i]) : [], + sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]) : [] + ]) + ]); +}; +/** +* Movie Fragment Random Access Box: For each track, provides pointers to sync samples within the file +* for random access. +*/ +var mfra = (trackDatas) => { + return box("mfra", void 0, [...trackDatas.map(tfra), mfro()]); +}; +/** Track Fragment Random Access Box: Provides pointers to sync samples within the file for random access. */ +var tfra = (trackData) => { + return fullBox("tfra", 1, 0, [ + u32(trackData.track.id), + u32(63), + u32(trackData.finalizedChunks.length), + trackData.finalizedChunks.map((chunk) => [ + u64(intoTimescale(chunk.samples[0].timestamp, trackData.timescale)), + u64(chunk.moofOffset), + u32(chunk.trafIndex + 1), + u32(1), + u32(1) + ]) + ]); +}; +/** +* Movie Fragment Random Access Offset Box: Provides the size of the enclosing mfra box. This box can be used by readers +* to quickly locate the mfra box by searching from the end of the file. +*/ +var mfro = () => { + return fullBox("mfro", 0, 0, [u32(0)]); +}; +/** VTT Empty Cue Box */ +var vtte = () => box("vtte"); +/** VTT Cue Box */ +var vttc = (payload, timestamp, identifier, settings, sourceId) => box("vttc", void 0, [ + sourceId !== null ? box("vsid", [i32(sourceId)]) : null, + identifier !== null ? box("iden", [...textEncoder.encode(identifier)]) : null, + timestamp !== null ? box("ctim", [...textEncoder.encode(formatSubtitleTimestamp(timestamp))]) : null, + settings !== null ? box("sttg", [...textEncoder.encode(settings)]) : null, + box("payl", [...textEncoder.encode(payload)]) +]); +/** VTT Additional Text Box */ +var vtta = (notes) => box("vtta", [...textEncoder.encode(notes)]); +/** User Data Box */ +var udta = (muxer) => { + const boxes = []; + const metadataFormat = muxer.format._options.metadataFormat ?? "auto"; + const metadataTags = muxer.output._metadataTags; + if (metadataFormat === "mdir" || metadataFormat === "auto" && !muxer.isQuickTime) { + const metaBox = metaMdir(metadataTags); + if (metaBox) boxes.push(metaBox); + } else if (metadataFormat === "mdta") { + const metaBox = metaMdta(metadataTags); + if (metaBox) boxes.push(metaBox); + } else if (metadataFormat === "udta" || metadataFormat === "auto" && muxer.isQuickTime) addQuickTimeMetadataTagBoxes(boxes, muxer.output._metadataTags); + if (boxes.length === 0) return null; + return box("udta", void 0, boxes); +}; +var addQuickTimeMetadataTagBoxes = (boxes, tags) => { + for (const { key, value } of keyValueIterator(tags)) switch (key) { + case "title": + boxes.push(metadataTagStringBoxShort("©nam", value)); + break; + case "description": + boxes.push(metadataTagStringBoxShort("©des", value)); + break; + case "artist": + boxes.push(metadataTagStringBoxShort("©ART", value)); + break; + case "album": + boxes.push(metadataTagStringBoxShort("©alb", value)); + break; + case "albumArtist": + boxes.push(metadataTagStringBoxShort("albr", value)); + break; + case "genre": + boxes.push(metadataTagStringBoxShort("©gen", value)); + break; + case "date": + boxes.push(metadataTagStringBoxShort("©day", value.toISOString().slice(0, 10))); + break; + case "comment": + boxes.push(metadataTagStringBoxShort("©cmt", value)); + break; + case "lyrics": + boxes.push(metadataTagStringBoxShort("©lyr", value)); + break; + case "raw": break; + case "discNumber": + case "discsTotal": + case "trackNumber": + case "tracksTotal": + case "beatsPerMinute": + case "images": break; + default: assertNever(key); + } + if (tags.raw) for (const key in tags.raw) { + const value = tags.raw[key]; + if (value == null || key.length !== 4 || boxes.some((x) => x.type === key)) continue; + if (typeof value === "string") boxes.push(metadataTagStringBoxShort(key, value)); + else if (value instanceof Uint8Array) boxes.push(box(key, Array.from(value))); + } +}; +var metadataTagStringBoxShort = (name, value) => { + const encoded = textEncoder.encode(value); + return box(name, [ + u16(encoded.length), + u16(getLanguageCodeInt("und")), + Array.from(encoded) + ]); +}; +var DATA_BOX_MIME_TYPE_MAP = { + "image/jpeg": 13, + "image/png": 14, + "image/bmp": 27 +}; +/** +* Generates key-value metadata for inclusion in the "meta" box. +*/ +var generateMetadataPairs = (tags, isMdta) => { + const pairs = []; + for (const { key, value } of keyValueIterator(tags)) switch (key) { + case "title": + pairs.push({ + key: isMdta ? "title" : "©nam", + value: dataStringBoxLong(value) + }); + break; + case "description": + pairs.push({ + key: isMdta ? "description" : "©des", + value: dataStringBoxLong(value) + }); + break; + case "artist": + pairs.push({ + key: isMdta ? "artist" : "©ART", + value: dataStringBoxLong(value) + }); + break; + case "album": + pairs.push({ + key: isMdta ? "album" : "©alb", + value: dataStringBoxLong(value) + }); + break; + case "albumArtist": + pairs.push({ + key: isMdta ? "album_artist" : "aART", + value: dataStringBoxLong(value) + }); + break; + case "comment": + pairs.push({ + key: isMdta ? "comment" : "©cmt", + value: dataStringBoxLong(value) + }); + break; + case "genre": + pairs.push({ + key: isMdta ? "genre" : "©gen", + value: dataStringBoxLong(value) + }); + break; + case "beatsPerMinute": + if (!isMdta) pairs.push({ + key: "tmpo", + value: box("data", [ + u32(21), + u32(0), + u16(value) + ]) + }); + break; + case "lyrics": + pairs.push({ + key: isMdta ? "lyrics" : "©lyr", + value: dataStringBoxLong(value) + }); + break; + case "date": + pairs.push({ + key: isMdta ? "date" : "©day", + value: dataStringBoxLong(value.toISOString().slice(0, 10)) + }); + break; + case "images": + for (const image of value) { + if (image.kind !== "coverFront") continue; + pairs.push({ + key: "covr", + value: box("data", [ + u32(DATA_BOX_MIME_TYPE_MAP[image.mimeType] ?? 0), + u32(0), + Array.from(image.data) + ]) + }); + } + break; + case "trackNumber": + if (isMdta) { + const string = tags.tracksTotal !== void 0 ? `${value}/${tags.tracksTotal}` : value.toString(); + pairs.push({ + key: "track", + value: dataStringBoxLong(string) + }); + } else pairs.push({ + key: "trkn", + value: box("data", [ + u32(0), + u32(0), + u16(0), + u16(value), + u16(tags.tracksTotal ?? 0), + u16(0) + ]) + }); + break; + case "discNumber": + if (!isMdta) pairs.push({ + key: "disc", + value: box("data", [ + u32(0), + u32(0), + u16(0), + u16(value), + u16(tags.discsTotal ?? 0), + u16(0) + ]) + }); + break; + case "tracksTotal": + case "discsTotal": break; + case "raw": break; + default: assertNever(key); + } + if (tags.raw) for (const key in tags.raw) { + const value = tags.raw[key]; + if (value == null || !isMdta && key.length !== 4 || pairs.some((x) => x.key === key)) continue; + if (typeof value === "string") pairs.push({ + key, + value: dataStringBoxLong(value) + }); + else if (value instanceof Uint8Array) pairs.push({ + key, + value: box("data", [ + u32(0), + u32(0), + Array.from(value) + ]) + }); + else if (value instanceof RichImageData) pairs.push({ + key, + value: box("data", [ + u32(DATA_BOX_MIME_TYPE_MAP[value.mimeType] ?? 0), + u32(0), + Array.from(value.data) + ]) + }); + } + return pairs; +}; +/** Metadata Box (mdir format) */ +var metaMdir = (tags) => { + const pairs = generateMetadataPairs(tags, false); + if (pairs.length === 0) return null; + return fullBox("meta", 0, 0, void 0, [hdlr(false, "mdir", "", "appl"), box("ilst", void 0, pairs.map((pair) => box(pair.key, void 0, [pair.value])))]); +}; +/** Metadata Box (mdta format with keys box) */ +var metaMdta = (tags) => { + const pairs = generateMetadataPairs(tags, true); + if (pairs.length === 0) return null; + return box("meta", void 0, [ + hdlr(false, "mdta", ""), + fullBox("keys", 0, 0, [u32(pairs.length)], pairs.map((pair) => box("mdta", [...textEncoder.encode(pair.key)]))), + box("ilst", void 0, pairs.map((pair, i) => { + return box(String.fromCharCode(...u32(i + 1)), void 0, [pair.value]); + })) + ]); +}; +var dataStringBoxLong = (value) => { + return box("data", [ + u32(1), + u32(0), + ...textEncoder.encode(value) + ]); +}; +var videoCodecToBoxName = (codec, fullCodecString) => { + switch (codec) { + case "avc": return fullCodecString.startsWith("avc3") ? "avc3" : "avc1"; + case "hevc": return "hvc1"; + case "vp8": return "vp08"; + case "vp9": return "vp09"; + case "av1": return "av01"; + case "prores": return fullCodecString; + } +}; +var VIDEO_CODEC_TO_CONFIGURATION_BOX = { + avc: avcC, + hevc: hvcC, + vp8: vpcC, + vp9: vpcC, + av1: av1C, + prores: null +}; +var audioCodecToBoxName = (codec, fullCodecString, isQuickTime) => { + switch (codec) { + case "aac": return "mp4a"; + case "mp3": return "mp4a"; + case "opus": return "Opus"; + case "vorbis": return "mp4a"; + case "flac": return "fLaC"; + case "ulaw": return "ulaw"; + case "alaw": return "alaw"; + case "pcm-u8": return "raw "; + case "pcm-s8": return "sowt"; + case "ac3": return "ac-3"; + case "eac3": return "ec-3"; + case "dts": return fullCodecString; + } + if (isQuickTime) switch (codec) { + case "pcm-s16": return "sowt"; + case "pcm-s16be": return "twos"; + case "pcm-s24": return "in24"; + case "pcm-s24be": return "in24"; + case "pcm-s32": return "in32"; + case "pcm-s32be": return "in32"; + case "pcm-f32": return "fl32"; + case "pcm-f32be": return "fl32"; + case "pcm-f64": return "fl64"; + case "pcm-f64be": return "fl64"; + } + else switch (codec) { + case "pcm-s16": return "ipcm"; + case "pcm-s16be": return "ipcm"; + case "pcm-s24": return "ipcm"; + case "pcm-s24be": return "ipcm"; + case "pcm-s32": return "ipcm"; + case "pcm-s32be": return "ipcm"; + case "pcm-f32": return "fpcm"; + case "pcm-f32be": return "fpcm"; + case "pcm-f64": return "fpcm"; + case "pcm-f64be": return "fpcm"; + } +}; +var audioCodecToConfigurationBox = (codec, isQuickTime) => { + switch (codec) { + case "aac": return esds; + case "mp3": return esds; + case "opus": return dOps; + case "vorbis": return esds; + case "flac": return dfLa; + case "ac3": return dac3; + case "eac3": return dec3; + case "dts": return ddts; + } + if (isQuickTime) switch (codec) { + case "pcm-s24": return wave; + case "pcm-s24be": return wave; + case "pcm-s32": return wave; + case "pcm-s32be": return wave; + case "pcm-f32": return wave; + case "pcm-f32be": return wave; + case "pcm-f64": return wave; + case "pcm-f64be": return wave; + } + else switch (codec) { + case "pcm-s16": return pcmC; + case "pcm-s16be": return pcmC; + case "pcm-s24": return pcmC; + case "pcm-s24be": return pcmC; + case "pcm-s32": return pcmC; + case "pcm-s32be": return pcmC; + case "pcm-f32": return pcmC; + case "pcm-f32be": return pcmC; + case "pcm-f64": return pcmC; + case "pcm-f64be": return pcmC; + } + return null; +}; +var SUBTITLE_CODEC_TO_BOX_NAME = { webvtt: "wvtt" }; +var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { webvtt: vttC }; +var getLanguageCodeInt = (code) => { + assert(code.length === 3); + let language = 0; + for (let i = 0; i < 3; i++) { + language <<= 5; + language += code.charCodeAt(i) - 96; + } + return language; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/writer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Writer = class { + constructor(target, isMonotonic) { + this.finalized = false; + this.started = false; + this.pos = 0; + this.trackedWrites = null; + this.trackedStart = -1; + this.trackedEnd = -1; + if (target._writerAcquired) throw new Error("Can't have multiple Writers for the same Target."); + this.target = target; + target._setMonotonicity(isMonotonic); + target._writerAcquired = true; + } + start() { + assert(!this.started); + this.target._start(); + this.started = true; + } + /** Writes the given data to the target, at the current position. */ + write(data) { + assert(this.started && !this.finalized); + this.maybeTrackWrites(data); + this.target._write(data, this.pos); + this.pos += data.byteLength; + } + /** Sets the current position for future writes to a new one. */ + seek(newPos) { + this.pos = newPos; + } + /** Returns the current position. */ + getPos() { + return this.pos; + } + /** Signals to the writer that it may be time to flush. */ + async flush() { + assert(this.started && !this.finalized); + return this.target._flush(); + } + /** Called after muxing has finished. */ + async finalize() { + assert(this.started && !this.finalized); + await this.target._finalize(); + this.finalized = true; + } + maybeTrackWrites(data) { + if (!this.trackedWrites) return; + let pos = this.getPos(); + if (pos < this.trackedStart) { + if (pos + data.byteLength <= this.trackedStart) return; + data = data.subarray(this.trackedStart - pos); + pos = 0; + } + const neededSize = pos + data.byteLength - this.trackedStart; + let newLength = this.trackedWrites.byteLength; + while (newLength < neededSize) newLength *= 2; + if (newLength !== this.trackedWrites.byteLength) { + const copy = new Uint8Array(newLength); + copy.set(this.trackedWrites, 0); + this.trackedWrites = copy; + } + this.trackedWrites.set(data, pos - this.trackedStart); + this.trackedEnd = Math.max(this.trackedEnd, pos + data.byteLength); + } + startTrackingWrites() { + this.trackedWrites = new Uint8Array(2 ** 10); + this.trackedStart = this.getPos(); + this.trackedEnd = this.trackedStart; + } + stopTrackingWrites() { + if (!this.trackedWrites) throw new Error("Internal error: Can't get tracked writes since nothing was tracked."); + const result = { + data: this.trackedWrites.subarray(0, this.trackedEnd - this.trackedStart), + start: this.trackedStart, + end: this.trackedEnd + }; + this.trackedWrites = null; + return result; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/target.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var node = typeof import___vite_browser_external !== "undefined" ? import___vite_browser_external : void 0; +/** +* Base class for targets, specifying where output files are written. +* @group Output targets +* @public +*/ +var Target = class extends EventEmitter { + constructor() { + super(...arguments); + /** @internal */ + this._writerAcquired = false; + /** @internal */ + this._monotonicity = null; + /** + * Called each time data is written to the target. Will be called with the byte range into which data was written. + * + * Use this callback to track the size of the output file as it grows. But be warned, this function is chatty and + * gets called *extremely* often. + * + * @deprecated Use `target.on('write', ({ start, end }) => ...)` instead. + */ + this.onwrite = null; + } + /** @internal */ + _setMonotonicity(monotonicity) { + if (this._monotonicity !== false) this._monotonicity = monotonicity; + } + /** @internal */ + _dispatchWrite(start, end) { + this.onwrite?.(start, end); + this._emit("write", { + start, + end + }); + } + /** + * Returns a new {@link RangedTarget} that writes data to this target using the given offset. + * + * Useful for writing a file into a section of a larger file. + */ + slice(offset) { + if (!Number.isInteger(offset) || offset < 0) throw new TypeError("offset must be a non-negative integer."); + return new RangedTarget(this, offset); + } +}; +var ARRAY_BUFFER_INITIAL_SIZE = 2 ** 16; +var ARRAY_BUFFER_MAX_SIZE = 2 ** 32; +/** +* A target that writes data directly into an ArrayBuffer in memory. Great for performance, but not suitable for very +* large files. The buffer will be available once the output has been finalized. +* @group Output targets +* @public +*/ +var BufferTarget = class extends Target { + /** Creates a new {@link BufferTarget}. The buffer holding the data will be created and managed internally. */ + constructor(options = {}) { + super(); + /** Stores the final output buffer. Until the output is finalized, this will be `null`. */ + this.buffer = null; + /** @internal */ + this._maxPos = 0; + if (!options || typeof options !== "object") throw new TypeError("BufferTarget options, when provided, must be an object."); + if (options.onFinalize !== void 0 && typeof options.onFinalize !== "function") throw new TypeError("options.onFinalize, when provided, must be a function."); + this._options = options; + this._supportsResize = "resize" in /* @__PURE__ */ new ArrayBuffer(0); + if (this._supportsResize) try { + this._buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE, { maxByteLength: ARRAY_BUFFER_MAX_SIZE }); + } catch { + this._buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE); + this._supportsResize = false; + } + else this._buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE); + this._bytes = new Uint8Array(this._buffer); + } + /** @internal */ + _ensureSize(size) { + let newLength = this._buffer.byteLength; + while (newLength < size) newLength *= 2; + if (newLength === this._buffer.byteLength) return; + if (newLength > ARRAY_BUFFER_MAX_SIZE) throw new Error(`ArrayBuffer exceeded maximum size of ${ARRAY_BUFFER_MAX_SIZE} bytes. Please consider using another target.`); + if (this._supportsResize) this._buffer.resize(newLength); + else { + const newBuffer = new ArrayBuffer(newLength); + const newBytes = new Uint8Array(newBuffer); + newBytes.set(this._bytes, 0); + this._buffer = newBuffer; + this._bytes = newBytes; + } + } + /** @internal */ + _start() {} + /** @internal */ + _write(data, pos) { + this._ensureSize(pos + data.byteLength); + this._bytes.set(data, pos); + this._maxPos = Math.max(this._maxPos, pos + data.byteLength); + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + async _flush() {} + /** @internal */ + async _finalize() { + this.buffer = this._buffer.slice(0, this._maxPos); + if (this._options.onFinalize) await this._options.onFinalize(this.buffer); + this._emit("finalized"); + } + /** @internal */ + async _close() {} + /** @internal */ + _getSlice(start, end) { + return this._bytes.slice(start, end); + } +}; +var DEFAULT_CHUNK_SIZE = 2 ** 24; +var MAX_CHUNKS_AT_ONCE = 2; +/** +* This target writes data to a [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), +* making it a general-purpose target for writing data anywhere. It is also compatible with +* [`FileSystemWritableFileStream`](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream) for +* use with the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). The +* `WritableStream` can also apply backpressure, which will propagate to the output and throttle the encoders. +* @group Output targets +* @public +*/ +var StreamTarget = class extends Target { + /** Creates a new {@link StreamTarget} which writes to the specified `writable`. */ + constructor(writable, options = {}) { + super(); + /** @internal */ + this._sections = []; + /** @internal */ + this._lastWriteEnd = 0; + /** @internal */ + this._lastFlushEnd = 0; + /** @internal */ + this._streamWriter = null; + /** @internal */ + this._writeError = null; + /** + * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out. + * A chunk is flushed if all of its contents have been written. + */ + /** @internal */ + this._chunks = []; + if (!(writable instanceof WritableStream)) throw new TypeError("StreamTarget requires a WritableStream instance."); + if (options != null && typeof options !== "object") throw new TypeError("StreamTarget options, when provided, must be an object."); + if (options.chunked !== void 0 && typeof options.chunked !== "boolean") throw new TypeError("options.chunked, when provided, must be a boolean."); + if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize < 1024)) throw new TypeError("options.chunkSize, when provided, must be an integer and not smaller than 1024."); + this._writable = writable; + this._options = options; + this._chunked = options.chunked ?? false; + this._chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE; + } + /** @internal */ + _start() { + this._streamWriter = this._writable.getWriter(); + } + /** @internal */ + _write(data, pos) { + if (pos > this._lastWriteEnd) { + const paddingBytesNeeded = pos - this._lastWriteEnd; + this._write(new Uint8Array(paddingBytesNeeded), this._lastWriteEnd); + } + this._sections.push({ + data: data.slice(), + start: pos + }); + this._lastWriteEnd = Math.max(this._lastWriteEnd, pos + data.byteLength); + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + async _flush() { + if (this._writeError !== null) throw this._writeError; + assert(this._streamWriter); + if (this._sections.length === 0) return; + const chunks = []; + const sorted = [...this._sections].sort((a, b) => a.start - b.start); + chunks.push({ + start: sorted[0].start, + size: sorted[0].data.byteLength + }); + for (let i = 1; i < sorted.length; i++) { + const lastChunk = chunks[chunks.length - 1]; + const section = sorted[i]; + if (section.start <= lastChunk.start + lastChunk.size) lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start); + else chunks.push({ + start: section.start, + size: section.data.byteLength + }); + } + for (const chunk of chunks) { + chunk.data = new Uint8Array(chunk.size); + for (const section of this._sections) if (chunk.start <= section.start && section.start < chunk.start + chunk.size) chunk.data.set(section.data, section.start - chunk.start); + if (this._streamWriter.desiredSize !== null && this._streamWriter.desiredSize <= 0) await this._streamWriter.ready; + if (this._chunked) { + this._writeDataIntoChunks(chunk.data, chunk.start); + this._tryToFlushChunks(); + } else { + if (this._monotonicity === true && chunk.start !== this._lastFlushEnd) throw new Error("Internal error: Monotonicity violation."); + this._streamWriter.write({ + type: "write", + data: chunk.data, + position: chunk.start + }).catch((error) => { + this._writeError ??= error; + }); + this._lastFlushEnd = chunk.start + chunk.data.byteLength; + } + } + this._sections.length = 0; + } + /** @internal */ + _writeDataIntoChunks(data, position) { + let chunkIndex = this._chunks.findIndex((x) => x.start <= position && position < x.start + this._chunkSize); + if (chunkIndex === -1) chunkIndex = this._createChunk(position); + const chunk = this._chunks[chunkIndex]; + const relativePosition = position - chunk.start; + const toWrite = data.subarray(0, Math.min(this._chunkSize - relativePosition, data.byteLength)); + chunk.data.set(toWrite, relativePosition); + const section = { + start: relativePosition, + end: relativePosition + toWrite.byteLength + }; + this._insertSectionIntoChunk(chunk, section); + if (chunk.written[0].start === 0 && chunk.written[0].end === this._chunkSize) chunk.shouldFlush = true; + if (this._chunks.length > MAX_CHUNKS_AT_ONCE) { + for (let i = 0; i < this._chunks.length - 1; i++) this._chunks[i].shouldFlush = true; + this._tryToFlushChunks(); + } + if (toWrite.byteLength < data.byteLength) this._writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); + } + /** @internal */ + _insertSectionIntoChunk(chunk, section) { + let low = 0; + let high = chunk.written.length - 1; + let index = -1; + while (low <= high) { + const mid = Math.floor(low + (high - low + 1) / 2); + if (chunk.written[mid].start <= section.start) { + low = mid + 1; + index = mid; + } else high = mid - 1; + } + chunk.written.splice(index + 1, 0, section); + if (index === -1 || chunk.written[index].end < section.start) index++; + while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) { + chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end); + chunk.written.splice(index + 1, 1); + } + } + /** @internal */ + _createChunk(includesPosition) { + const chunk = { + start: Math.floor(includesPosition / this._chunkSize) * this._chunkSize, + data: new Uint8Array(this._chunkSize), + written: [], + shouldFlush: false + }; + this._chunks.push(chunk); + this._chunks.sort((a, b) => a.start - b.start); + return this._chunks.indexOf(chunk); + } + /** @internal */ + _tryToFlushChunks(force = false) { + assert(this._streamWriter); + for (let i = 0; i < this._chunks.length; i++) { + const chunk = this._chunks[i]; + if (!chunk.shouldFlush && !force) continue; + for (const section of chunk.written) { + const position = chunk.start + section.start; + if (this._monotonicity === true && position !== this._lastFlushEnd) throw new Error("Internal error: Monotonicity violation."); + const isPartialView = section.start !== 0 || section.end !== chunk.data.byteLength; + let data; + if (isPartialView && isWebKit()) data = chunk.data.slice(section.start, section.end); + else data = chunk.data.subarray(section.start, section.end); + this._streamWriter.write({ + type: "write", + data, + position + }).catch((error) => { + this._writeError ??= error; + }); + this._lastFlushEnd = chunk.start + section.end; + } + this._chunks.splice(i--, 1); + } + } + /** @internal */ + async _finalize() { + if (this._chunked) this._tryToFlushChunks(true); + if (this._writeError !== null) throw this._writeError; + assert(this._streamWriter); + await this._streamWriter.ready; + await this._streamWriter.close(); + this._emit("finalized"); + } + /** @internal */ + async _close() { + return this._streamWriter?.close(); + } +}; +/** +* This target writes to a `WritableStream `, meaning all writes are necessarily append-only and involve no +* seeking. Great for streaming data to a source that can only accept sequential data, like an HTTP server processing +* an incoming upload. +* +* Note that using this target *requires* that the underlying format write data sequentially. Not all formats do this, +* and this target will throw for the formats that don't. Check the guide for more. +* +* @group Output targets +* @public +*/ +var AppendOnlyStreamTarget = class extends Target { + constructor(writable) { + super(); + /** @internal */ + this._writer = null; + /** @internal */ + this._nextWritePos = 0; + this._writable = writable; + this._streamTarget = new StreamTarget(new WritableStream({ + start: () => { + this._writer = this._writable.getWriter(); + }, + write: (chunk) => { + if (this._monotonicity !== true) throw new Error("AppendOnlyStreamTarget requires that data be written monotonically (always appended to the end). You must use a format that guarantees this behavior."); + assert(chunk.position === this._nextWritePos); + this._nextWritePos += chunk.data.byteLength; + assert(this._writer); + return this._writer.write(chunk.data); + }, + close: () => { + return this._writer?.close(); + } + })); + } + /** @internal */ + _start() { + this._streamTarget._start(); + } + /** @internal */ + _write(data, pos) { + this._streamTarget._write(data, pos); + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + _flush() { + return this._streamTarget._flush(); + } + /** @internal */ + _finalize() { + return this._streamTarget._finalize(); + } + /** @internal */ + _close() { + return this._streamTarget._close(); + } + /** @internal */ + _setMonotonicity(monotonicity) { + super._setMonotonicity(monotonicity); + this._streamTarget._setMonotonicity(monotonicity); + } +}; +/** +* A target that writes to a file at the specified path. Intended for server-side usage in Node, Bun, or Deno. +* +* Writing is chunked by default. The internally held file handle will be closed when `.finalize()` or `.cancel()` are +* called on the corresponding {@link Output}. +* @group Output targets +* @public +*/ +var FilePathTarget = class extends Target { + /** Creates a new {@link FilePathTarget} that writes to the file at the specified file path. */ + constructor(filePath, options = {}) { + if (typeof filePath !== "string") throw new TypeError("filePath must be a string."); + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!node.fs) throw new Error("FilePathTarget is only available in server-side environments (Node.js, Bun, Deno)."); + super(); + /** @internal */ + this._fileHandle = null; + const writable = new WritableStream({ + start: async () => { + this._fileHandle = await node.fs.open(filePath, "w"); + }, + write: async (chunk) => { + assert(this._fileHandle); + await this._fileHandle.write(chunk.data, 0, chunk.data.byteLength, chunk.position); + }, + close: async () => { + if (this._fileHandle) { + await this._fileHandle.close(); + this._fileHandle = null; + } + } + }); + this._streamTarget = new StreamTarget(writable, { + chunked: true, + ...options + }); + } + /** @internal */ + _start() { + this._streamTarget._start(); + } + /** @internal */ + _write(data, pos) { + this._streamTarget._write(data, pos); + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + async _flush() { + return this._streamTarget._flush(); + } + /** @internal */ + async _finalize() { + await this._streamTarget._finalize(); + this._emit("finalized"); + } + /** @internal */ + async _close() { + return this._streamTarget._close(); + } + /** @internal */ + _setMonotonicity(monotonicity) { + super._setMonotonicity(monotonicity); + this._streamTarget._setMonotonicity(monotonicity); + } +}; +/** +* This target just discards all incoming data. It is useful for when you need an {@link Output} but extract data from +* it differently, for example through format-specific callbacks (`onMoof`, `onMdat`, ...) or encoder events. +* @group Output targets +* @public +*/ +var NullTarget = class extends Target { + /** @internal */ + _start() {} + /** @internal */ + _write(data, pos) { + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + async _flush() {} + /** @internal */ + async _finalize() { + this._emit("finalized"); + } + /** @internal */ + async _close() {} +}; +/** +* A target that writes to a subrange (defined by an offset) of another, underlying target. Useful for writing a file +* into a section of a larger file. +* @group Output targets +* @public +*/ +var RangedTarget = class extends Target { + /** @internal */ + constructor(baseTarget, offset) { + super(); + this._baseTarget = baseTarget; + this._offset = offset; + } + /** @internal */ + _start() {} + /** @internal */ + _write(data, pos) { + this._baseTarget._write(data, this._offset + pos); + this._dispatchWrite(pos, pos + data.byteLength); + } + /** @internal */ + _flush() { + return this._baseTarget._flush(); + } + /** @internal */ + async _finalize() { + this._emit("finalized"); + } + /** @internal */ + async _close() {} + /** @internal */ + _setMonotonicity(monotonicity) { + super._setMonotonicity(monotonicity); + this._baseTarget._setMonotonicity(monotonicity); + } +}; +/** +* A special target for writing multi-file media where each file is uniquely identified by a path. +* @group Output targets +* @public +*/ +var PathedTarget = class { + /** Creates a new {@link PathedTarget} from a root path and a callback. */ + constructor(rootPath, getTarget) { + this.rootPath = rootPath; + this.getTarget = getTarget; + if (typeof rootPath !== "string") throw new TypeError("rootPath must be a string."); + if (typeof getTarget !== "function") throw new TypeError("getTarget must be a function."); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/isobmff/isobmff-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var GLOBAL_TIMESCALE = 57600; +var TIMESTAMP_OFFSET = 2082844800; +var getTrackMetadata = (trackData) => { + const metadata = {}; + const track = trackData.track; + if (track.metadata.name !== void 0) metadata.name = track.metadata.name; + return metadata; +}; +var intoTimescale = (timeInSeconds, timescale, round = true) => { + const value = timeInSeconds * timescale; + return round ? Math.round(value) : value; +}; +var presentationSpan = (trackData) => { + if (trackData.samples.length === 0) return 0; + let minTimestamp = Infinity; + let maxEndTimestamp = -Infinity; + for (let i = 0; i < trackData.samples.length; i++) { + const sample = trackData.samples[i]; + if (sample.timestamp < minTimestamp) minTimestamp = sample.timestamp; + if (sample.timestamp + sample.duration > maxEndTimestamp) maxEndTimestamp = sample.timestamp + sample.duration; + } + if (minTimestamp === Infinity) return 0; + return maxEndTimestamp - minTimestamp; +}; +var IsobmffMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.writer = null; + this.boxWriter = null; + this.initWriter = null; + this.initBoxWriter = null; + this.auxTarget = new BufferTarget(); + this.auxWriter = new Writer(this.auxTarget, false); + this.auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); + this.mdat = null; + this.ftypSize = null; + this.trackDatas = []; + this.allTracksKnown = promiseWithResolvers(); + this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET; + this.finalizedChunks = []; + this.wroteFragmentedHeader = false; + this.nextFragmentNumber = 1; + this.maxWrittenTimestamp = -Infinity; + this.minWrittenTimestamp = Infinity; + this.maxWrittenEndTimestamp = -Infinity; + this.segmentHeaderSize = null; + this.format = format; + this.formatOptions = { ...format._options }; + this.isQuickTime = format instanceof MovOutputFormat; + this.isCmaf = format instanceof CmafOutputFormat; + this.minimumFragmentDuration = this.formatOptions.minimumFragmentDuration ?? (format instanceof CmafOutputFormat ? Infinity : 1); + this.auxWriter.start(); + } + async start() { + const release = await this.mutex.acquire(); + if (!this.isCmaf) { + this.writer = await this.output._getRootWriter((target) => this.formatOptions.fastStart !== void 0 ? this.formatOptions.fastStart === "fragmented" : target instanceof BufferTarget); + this.boxWriter = new IsobmffBoxWriter(this.writer); + this.fastStart = this.formatOptions.fastStart ?? (this.writer.target instanceof BufferTarget ? "in-memory" : false); + this.isFragmented = this.fastStart === "fragmented"; + } else { + this.fastStart = "fragmented"; + this.isFragmented = true; + } + if (this.isCmaf) { + if (!this.output._hasInitTarget()) throw new Error("CMAF outputs require the initTarget field in OutputOptions to be set; the init segment will be written to it."); + const initWriter = new Writer(await this.output._getInitTarget(), true); + initWriter.start(); + this.initWriter = initWriter; + this.initBoxWriter = new IsobmffBoxWriter(initWriter); + } + const holdsAvc = this.output.tracks.some((x) => x.isVideoTrack() && x.source._codec === "avc"); + { + const boxWriter = this.initBoxWriter ?? this.boxWriter; + assert(boxWriter); + if (this.formatOptions.onFtyp) boxWriter.writer.startTrackingWrites(); + boxWriter.writeBox(ftyp({ + isQuickTime: this.isQuickTime, + holdsAvc, + fragmented: this.isFragmented, + cmaf: this.isCmaf + })); + if (this.formatOptions.onFtyp) { + const { data, start } = boxWriter.writer.stopTrackingWrites(); + this.formatOptions.onFtyp(data, start); + } + this.ftypSize = boxWriter.writer.getPos(); + if (this.isCmaf) await this.initWriter.flush(); + } + if (this.fastStart === "in-memory") {} else if (this.fastStart === "reserve") { + for (const track of this.output.tracks) if (track.metadata.maximumPacketCount === void 0) throw new Error("All tracks must specify maximumPacketCount in their metadata when using fastStart: 'reserve'."); + } else if (this.isFragmented) {} else { + assert(this.writer); + assert(this.boxWriter); + if (this.formatOptions.onMdat) this.writer.startTrackingWrites(); + this.mdat = mdat(true); + this.boxWriter.writeBox(this.mdat); + } + await this.writer?.flush(); + for (const track of this.output.tracks) if (track.isVideoTrack() && track.metadata.decoderConfig) this.getVideoTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + else if (track.isAudioTrack() && track.metadata.decoderConfig) this.getAudioTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + release(); + } + allTracksAreKnown() { + for (const track of this.output.tracks) if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) return false; + return true; + } + async getMimeType() { + await this.allTracksKnown.promise; + const codecStrings = this.trackDatas.map((trackData) => { + if (trackData.type === "video") return trackData.info.decoderConfig.codec; + else if (trackData.type === "audio") return trackData.info.decoderConfig.codec; + else return { webvtt: "wvtt" }[trackData.track.source._codec]; + }); + return buildIsobmffMimeType({ + isQuickTime: this.isQuickTime, + hasVideo: this.trackDatas.some((x) => x.type === "video"), + hasAudio: this.trackDatas.some((x) => x.type === "audio"), + codecStrings + }); + } + getVideoTrackData(track, packet, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateVideoChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + const decoderConfig = { ...meta.decoderConfig }; + assert(decoderConfig.codedWidth !== void 0); + assert(decoderConfig.codedHeight !== void 0); + let requiresAnnexBTransformation = false; + if (track.source._codec === "avc" && !decoderConfig.description) { + if (!packet) throw new Error("No AVC description provided; you must therefore provide a priming packet."); + const decoderConfigurationRecord = extractAvcDecoderConfigurationRecord(packet.data); + if (!decoderConfigurationRecord) throw new Error("Couldn't extract an AVCDecoderConfigurationRecord from the AVC packet. Make sure the packets are in Annex B format (as specified in ITU-T-REC-H.264) when not providing a description, or provide a description (must be an AVCDecoderConfigurationRecord as specified in ISO 14496-15) and ensure the packets are in AVCC format."); + decoderConfig.description = serializeAvcDecoderConfigurationRecord(decoderConfigurationRecord); + requiresAnnexBTransformation = true; + } else if (track.source._codec === "hevc" && !decoderConfig.description) { + if (!packet) throw new Error("No HEVC description provided; you must therefore provide a priming packet."); + const decoderConfigurationRecord = extractHevcDecoderConfigurationRecord(packet.data); + if (!decoderConfigurationRecord) throw new Error("Couldn't extract an HEVCDecoderConfigurationRecord from the HEVC packet. Make sure the packets are in Annex B format (as specified in ITU-T-REC-H.265) when not providing a description, or provide a description (must be an HEVCDecoderConfigurationRecord as specified in ISO 14496-15) and ensure the packets are in HEVC format."); + decoderConfig.description = serializeHevcDecoderConfigurationRecord(decoderConfigurationRecord); + requiresAnnexBTransformation = true; + } + const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).den; + const displayAspectWidth = decoderConfig.displayAspectWidth; + const displayAspectHeight = decoderConfig.displayAspectHeight; + const pixelAspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? { + num: 1, + den: 1 + } : simplifyRational({ + num: displayAspectWidth * decoderConfig.codedHeight, + den: displayAspectHeight * decoderConfig.codedWidth + }); + const hasAlphaChannel = decoderConfig.codec === "ap4h" || decoderConfig.codec === "ap4x"; + const newTrackData = { + muxer: this, + track, + type: "video", + info: { + width: decoderConfig.codedWidth, + height: decoderConfig.codedHeight, + pixelAspectRatio, + decoderConfig, + requiresAnnexBTransformation, + hasAlphaChannel + }, + timescale, + samples: [], + sampleQueue: [], + timestampProcessingQueue: [], + timeToSampleTable: [], + compositionTimeOffsetTable: [], + lastTimescaleUnits: null, + lastSample: null, + startTimestampOffset: null, + finalizedChunks: [], + currentChunk: null, + compactlyCodedChunkTable: [], + closed: false, + avgBitrate: track.source._nominalBitrate ?? track.metadata.averageBitrate ?? 0, + maxBitrate: track.source._nominalBitrate ?? track.metadata.bitrate ?? 0 + }; + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + getAudioTrackData(track, packet, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + const decoderConfig = { ...meta.decoderConfig }; + let requiresAdtsStripping = false; + if (track.source._codec === "aac" && !decoderConfig.description) { + if (!packet) throw new Error("No AAC description provided; you must therefore provide a priming packet."); + const adtsFrame = readAdtsFrameHeader(FileSlice.tempFromBytes(packet.data)); + if (!adtsFrame) throw new Error("Couldn't parse ADTS header from the AAC packet. Make sure the packets are in ADTS format (as specified in ISO 13818-7) when not providing a description, or provide a description (must be an AudioSpecificConfig as specified in ISO 14496-3) and ensure the packets are raw AAC data."); + const sampleRate = aacFrequencyTable[adtsFrame.samplingFrequencyIndex]; + const numberOfChannels = aacChannelMap[adtsFrame.channelConfiguration]; + if (sampleRate === void 0 || numberOfChannels === void 0) throw new Error("Invalid ADTS frame header."); + decoderConfig.description = buildAacAudioSpecificConfig({ + objectType: adtsFrame.objectType, + outputSampleRate: sampleRate, + outputNumberOfChannels: numberOfChannels + }); + requiresAdtsStripping = true; + } + if (!packet) { + if (track.source._codec === "ac3" || track.source._codec === "eac3") throw new Error("AC-3/E-AC-3 require a priming packet."); + if (track.source._codec === "dts") throw new Error("DTS requires a priming packet."); + } + const newTrackData = { + muxer: this, + track, + type: "audio", + info: { + numberOfChannels: meta.decoderConfig.numberOfChannels, + sampleRate: meta.decoderConfig.sampleRate, + decoderConfig, + requiresPcmTransformation: !this.isFragmented && PCM_AUDIO_CODECS.includes(track.source._codec), + expectedNextPcmPacketTimestamp: null, + requiresAdtsStripping, + primingPacket: packet + }, + timescale: decoderConfig.sampleRate, + samples: [], + sampleQueue: [], + timestampProcessingQueue: [], + timeToSampleTable: [], + compositionTimeOffsetTable: [], + lastTimescaleUnits: null, + lastSample: null, + startTimestampOffset: null, + finalizedChunks: [], + currentChunk: null, + compactlyCodedChunkTable: [], + closed: false, + avgBitrate: track.source._nominalBitrate ?? track.metadata.averageBitrate ?? 0, + maxBitrate: track.source._nominalBitrate ?? track.metadata.bitrate ?? 0 + }; + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + getSubtitleTrackData(track, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateSubtitleMetadata(meta); + assert(meta); + assert(meta.config); + const newTrackData = { + muxer: this, + track, + type: "subtitle", + info: { config: meta.config }, + timescale: 1e3, + samples: [], + sampleQueue: [], + timestampProcessingQueue: [], + timeToSampleTable: [], + compositionTimeOffsetTable: [], + lastTimescaleUnits: null, + lastSample: null, + startTimestampOffset: null, + finalizedChunks: [], + currentChunk: null, + compactlyCodedChunkTable: [], + closed: false, + avgBitrate: track.source._nominalBitrate ?? track.metadata.averageBitrate ?? 0, + maxBitrate: track.source._nominalBitrate ?? track.metadata.bitrate ?? 0, + lastCueEndTimestamp: null, + cueQueue: [], + nextSourceId: 0, + cueToSourceId: /* @__PURE__ */ new WeakMap() + }; + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + async addEncodedVideoPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, packet, meta); + let packetData = packet.data; + if (trackData.info.requiresAnnexBTransformation) { + const nalUnits = [...iterateNalUnitsInAnnexB(packetData)].map((loc) => packetData.subarray(loc.offset, loc.offset + loc.length)); + if (nalUnits.length === 0) throw new Error("Failed to transform packet data. Make sure all packets are provided in Annex B format, as specified in ITU-T-REC-H.264 and ITU-T-REC-H.265."); + packetData = concatNalUnitsInLengthPrefixed(nalUnits, 4); + } + this.validateTimestamp(trackData.track, packet.timestamp, packet.type === "key"); + const internalSample = this.createSampleForTrack(trackData, packetData, packet.timestamp, packet.duration, packet.type); + await this.registerSample(trackData, internalSample); + } finally { + release(); + } + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, packet, meta); + let packetData = packet.data; + if (trackData.info.requiresAdtsStripping) { + const adtsFrame = readAdtsFrameHeader(FileSlice.tempFromBytes(packetData)); + if (!adtsFrame) throw new Error("Expected ADTS frame, didn't get one."); + const headerLength = adtsFrame.crcCheck === null ? 7 : 9; + packetData = packetData.subarray(headerLength); + } + this.validateTimestamp(trackData.track, packet.timestamp, packet.type === "key"); + let timestamp = packet.timestamp; + let duration = packet.duration; + if (trackData.info.requiresPcmTransformation) { + const frameSize = parsePcmCodec(trackData.info.decoderConfig.codec).sampleSize * trackData.info.numberOfChannels; + duration = packetData.byteLength / frameSize / trackData.info.sampleRate; + if (trackData.info.expectedNextPcmPacketTimestamp !== null) { + const diff = timestamp - trackData.info.expectedNextPcmPacketTimestamp; + if (diff < .01) timestamp = trackData.info.expectedNextPcmPacketTimestamp; + else { + const paddedDuration = await this.padWithSilence(trackData, trackData.info.expectedNextPcmPacketTimestamp, diff); + timestamp = trackData.info.expectedNextPcmPacketTimestamp + paddedDuration; + } + } + trackData.info.expectedNextPcmPacketTimestamp = timestamp + duration; + } + const internalSample = this.createSampleForTrack(trackData, packetData, timestamp, duration, packet.type); + await this.registerSample(trackData, internalSample); + } finally { + release(); + } + } + async padWithSilence(trackData, timestamp, duration) { + const deltaInTimescale = intoTimescale(duration, trackData.timescale); + duration = deltaInTimescale / trackData.timescale; + if (deltaInTimescale > 0) { + const { sampleSize, silentValue } = parsePcmCodec(trackData.info.decoderConfig.codec); + const samplesNeeded = deltaInTimescale * trackData.info.numberOfChannels; + const data = new Uint8Array(sampleSize * samplesNeeded).fill(silentValue); + const paddingSample = this.createSampleForTrack(trackData, new Uint8Array(data.buffer), timestamp, duration, "key"); + await this.registerSample(trackData, paddingSample); + } + return duration; + } + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + this.validateTimestamp(trackData.track, cue.timestamp, true); + if (track.source._codec === "webvtt") { + trackData.cueQueue.push(cue); + await this.processWebVTTCues(trackData, cue.timestamp); + } + } finally { + release(); + } + } + async processWebVTTCues(trackData, until) { + while (trackData.cueQueue.length > 0) { + trackData.lastCueEndTimestamp ??= Math.min(0, trackData.cueQueue[0].timestamp); + const timestamps = /* @__PURE__ */ new Set([]); + for (const cue of trackData.cueQueue) { + assert(cue.timestamp <= until); + assert(trackData.lastCueEndTimestamp <= cue.timestamp + cue.duration); + timestamps.add(Math.max(cue.timestamp, trackData.lastCueEndTimestamp)); + timestamps.add(cue.timestamp + cue.duration); + } + const sortedTimestamps = [...timestamps].sort((a, b) => a - b); + const sampleStart = sortedTimestamps[0]; + const sampleEnd = sortedTimestamps[1] ?? sampleStart; + if (until < sampleEnd) break; + if (trackData.lastCueEndTimestamp < sampleStart) { + this.auxWriter.seek(0); + const box = vtte(); + this.auxBoxWriter.writeBox(box); + const body = this.auxTarget._getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack(trackData, body, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, "key"); + await this.registerSample(trackData, sample); + trackData.lastCueEndTimestamp = sampleStart; + } + this.auxWriter.seek(0); + for (let i = 0; i < trackData.cueQueue.length; i++) { + const cue = trackData.cueQueue[i]; + if (cue.timestamp >= sampleEnd) break; + inlineTimestampRegex.lastIndex = 0; + const containsTimestamp = inlineTimestampRegex.test(cue.text); + const endTimestamp = cue.timestamp + cue.duration; + let sourceId = trackData.cueToSourceId.get(cue); + if (sourceId === void 0 && sampleEnd < endTimestamp) { + sourceId = trackData.nextSourceId++; + trackData.cueToSourceId.set(cue, sourceId); + } + if (cue.notes) { + const box = vtta(cue.notes); + this.auxBoxWriter.writeBox(box); + } + const box = vttc(cue.text, containsTimestamp ? sampleStart : null, cue.identifier ?? null, cue.settings ?? null, sourceId ?? null); + this.auxBoxWriter.writeBox(box); + if (endTimestamp === sampleEnd) trackData.cueQueue.splice(i--, 1); + } + const body = this.auxTarget._getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); + await this.registerSample(trackData, sample); + trackData.lastCueEndTimestamp = sampleEnd; + } + } + createSampleForTrack(trackData, data, timestamp, duration, type) { + return { + timestamp, + decodeTimestamp: timestamp, + duration, + data, + size: data.byteLength, + type, + timescaleUnitsToNextSample: intoTimescale(duration, trackData.timescale) + }; + } + processTimestamps(trackData, nextSample) { + if (trackData.timestampProcessingQueue.length === 0) return; + if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) { + assert(!this.isFragmented); + trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp; + let totalDuration = 0; + for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) { + const sample = trackData.timestampProcessingQueue[i]; + const duration = intoTimescale(sample.duration, trackData.timescale); + totalDuration += duration; + } + if (trackData.timeToSampleTable.length === 0) trackData.timeToSampleTable.push({ + sampleCount: totalDuration, + sampleDelta: 1 + }); + else { + const lastEntry = last(trackData.timeToSampleTable); + lastEntry.sampleCount += totalDuration; + } + trackData.timestampProcessingQueue.length = 0; + return; + } + const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b); + if (this.isFragmented) trackData.startTimestampOffset ??= Math.min(sortedTimestamps[0], 0); + else trackData.startTimestampOffset ??= sortedTimestamps[0]; + for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) { + const sample = trackData.timestampProcessingQueue[i]; + sample.decodeTimestamp = sortedTimestamps[i]; + const sampleCompositionTimeOffset = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale); + const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); + if (trackData.lastTimescaleUnits !== null) { + assert(trackData.lastSample); + const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); + const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); + assert(delta >= 0); + trackData.lastTimescaleUnits += delta; + trackData.lastSample.timescaleUnitsToNextSample = delta; + if (!this.isFragmented) { + let lastTableEntry = last(trackData.timeToSampleTable); + assert(lastTableEntry); + if (lastTableEntry.sampleCount === 1) { + lastTableEntry.sampleDelta = delta; + const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; + if (entryBefore && entryBefore.sampleDelta === delta) { + entryBefore.sampleCount++; + trackData.timeToSampleTable.pop(); + lastTableEntry = entryBefore; + } + } else if (lastTableEntry.sampleDelta !== delta) { + lastTableEntry.sampleCount--; + trackData.timeToSampleTable.push(lastTableEntry = { + sampleCount: 1, + sampleDelta: delta + }); + } + if (lastTableEntry.sampleDelta === durationInTimescale) lastTableEntry.sampleCount++; + else trackData.timeToSampleTable.push({ + sampleCount: 1, + sampleDelta: durationInTimescale + }); + const lastCompositionTimeOffsetTableEntry = last(trackData.compositionTimeOffsetTable); + assert(lastCompositionTimeOffsetTableEntry); + if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) lastCompositionTimeOffsetTableEntry.sampleCount++; + else trackData.compositionTimeOffsetTable.push({ + sampleCount: 1, + sampleCompositionTimeOffset + }); + } + } else { + trackData.lastTimescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); + if (!this.isFragmented) { + trackData.timeToSampleTable.push({ + sampleCount: 1, + sampleDelta: durationInTimescale + }); + trackData.compositionTimeOffsetTable.push({ + sampleCount: 1, + sampleCompositionTimeOffset + }); + } + } + trackData.lastSample = sample; + } + trackData.timestampProcessingQueue.length = 0; + assert(trackData.lastSample); + assert(trackData.lastTimescaleUnits !== null); + if (nextSample !== void 0 && trackData.lastSample.timescaleUnitsToNextSample === 0) { + assert(nextSample.type === "key"); + const timescaleUnits = intoTimescale(nextSample.timestamp, trackData.timescale, false); + const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); + trackData.lastSample.timescaleUnitsToNextSample = delta; + } + } + async registerSample(trackData, sample) { + if (sample.type === "key") this.processTimestamps(trackData, sample); + trackData.timestampProcessingQueue.push(sample); + if (this.isFragmented) { + trackData.sampleQueue.push(sample); + await this.interleaveSamples(); + } else if (this.fastStart === "reserve") await this.registerSampleFastStartReserve(trackData, sample); + else await this.addSampleToTrack(trackData, sample); + } + async addSampleToTrack(trackData, sample) { + if (!this.isFragmented) { + trackData.samples.push(sample); + if (this.fastStart === "reserve") { + const maximumPacketCount = trackData.track.metadata.maximumPacketCount; + assert(maximumPacketCount !== void 0); + if (trackData.samples.length > maximumPacketCount) throw new Error(`Track #${trackData.track.id} has already reached the maximum packet count (${maximumPacketCount}). Either add less packets or increase the maximum packet count.`); + } + } + let beginNewChunk = false; + if (!trackData.currentChunk) beginNewChunk = true; + else { + trackData.currentChunk.startTimestamp = Math.min(trackData.currentChunk.startTimestamp, sample.timestamp); + const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; + if (this.isFragmented) { + const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { + if (trackData === otherTrackData) return sample.type === "key"; + const firstQueuedSample = otherTrackData.sampleQueue[0]; + if (firstQueuedSample) return firstQueuedSample.type === "key"; + return otherTrackData.closed; + }); + if (currentChunkDuration >= this.minimumFragmentDuration && keyFrameQueuedEverywhere && sample.timestamp > this.maxWrittenTimestamp) { + beginNewChunk = true; + await this.finalizeFragment(); + } + } else beginNewChunk = currentChunkDuration >= .5; + } + if (beginNewChunk) { + if (trackData.currentChunk) await this.finalizeCurrentChunk(trackData); + trackData.currentChunk = { + startTimestamp: sample.timestamp, + samples: [], + offset: null, + moofOffset: null, + trafIndex: null + }; + } + assert(trackData.currentChunk); + trackData.currentChunk.samples.push(sample); + if (this.isFragmented) { + this.maxWrittenTimestamp = Math.max(this.maxWrittenTimestamp, sample.timestamp); + this.maxWrittenEndTimestamp = Math.max(this.maxWrittenEndTimestamp, sample.timestamp + sample.duration); + this.minWrittenTimestamp = Math.min(this.minWrittenTimestamp, sample.timestamp); + } + } + async finalizeCurrentChunk(trackData) { + assert(!this.isFragmented); + assert(this.writer); + if (!trackData.currentChunk) return; + trackData.finalizedChunks.push(trackData.currentChunk); + this.finalizedChunks.push(trackData.currentChunk); + let sampleCount = trackData.currentChunk.samples.length; + if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) sampleCount = trackData.currentChunk.samples.reduce((acc, sample) => acc + intoTimescale(sample.duration, trackData.timescale), 0); + if (trackData.compactlyCodedChunkTable.length === 0 || last(trackData.compactlyCodedChunkTable).samplesPerChunk !== sampleCount) trackData.compactlyCodedChunkTable.push({ + firstChunk: trackData.finalizedChunks.length, + samplesPerChunk: sampleCount + }); + if (this.fastStart === "in-memory") { + trackData.currentChunk.offset = 0; + return; + } + trackData.currentChunk.offset = this.writer.getPos(); + for (const sample of trackData.currentChunk.samples) { + assert(sample.data); + this.writer.write(sample.data); + sample.data = null; + } + await this.writer.flush(); + } + async interleaveSamples(isFinalCall = false) { + assert(this.isFragmented); + if (!isFinalCall && !this.allTracksAreKnown()) return; + outer: while (true) { + let trackWithMinTimestamp = null; + let minTimestamp = Infinity; + for (const trackData of this.trackDatas) { + if (!isFinalCall && trackData.sampleQueue.length === 0 && !trackData.closed) break outer; + if (trackData.sampleQueue.length > 0 && trackData.sampleQueue[0].timestamp < minTimestamp) { + trackWithMinTimestamp = trackData; + minTimestamp = trackData.sampleQueue[0].timestamp; + } + } + if (!trackWithMinTimestamp) break; + const sample = trackWithMinTimestamp.sampleQueue.shift(); + await this.addSampleToTrack(trackWithMinTimestamp, sample); + } + } + async finalizeFragment(flushWriter = !this.isCmaf) { + assert(this.isFragmented); + if (!this.wroteFragmentedHeader) { + this.wroteFragmentedHeader = true; + const boxWriter = this.initBoxWriter ?? this.boxWriter; + assert(boxWriter); + if (this.formatOptions.onMoov) boxWriter.writer.startTrackingWrites(); + this.ensureOneEnabledTrack(); + const movieBox = moov(this); + boxWriter.writeBox(movieBox); + if (this.formatOptions.onMoov) { + const { data, start } = boxWriter.writer.stopTrackingWrites(); + this.formatOptions.onMoov(data, start); + } + if (this.isCmaf) { + assert(this.initWriter); + await this.initWriter.flush(); + await this.initWriter.finalize(); + this.writer = await this.output._getRootWriter(true); + this.boxWriter = new IsobmffBoxWriter(this.writer); + const stypSize = this.boxWriter.measureBox(styp()); + const sidxSize = this.boxWriter.measureBox(sidx(this, 0)); + this.segmentHeaderSize = stypSize + sidxSize; + this.writer.seek(this.segmentHeaderSize); + } + } + assert(this.writer); + assert(this.boxWriter); + const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk); + if (tracksInFragment.length === 0) { + if (flushWriter) await this.writer.flush(); + return; + } + const fragmentNumber = this.nextFragmentNumber++; + const moofBox = moof(fragmentNumber, tracksInFragment); + const moofOffset = this.writer.getPos(); + const mdatStartPos = moofOffset + this.boxWriter.measureBox(moofBox); + let currentPos = mdatStartPos + 8; + let fragmentStartTimestamp = Infinity; + for (let i = 0; i < tracksInFragment.length; i++) { + const trackData = tracksInFragment[i]; + assert(trackData.currentChunk); + assert(trackData.startTimestampOffset !== null); + trackData.currentChunk.offset = currentPos; + trackData.currentChunk.moofOffset = moofOffset; + trackData.currentChunk.trafIndex = i; + trackData.currentChunk.startTimestamp -= trackData.startTimestampOffset; + for (const sample of trackData.currentChunk.samples) { + currentPos += sample.size; + sample.timestamp -= trackData.startTimestampOffset; + sample.decodeTimestamp -= trackData.startTimestampOffset; + } + fragmentStartTimestamp = Math.min(fragmentStartTimestamp, trackData.currentChunk.startTimestamp); + } + const mdatSize = currentPos - mdatStartPos; + const needsLargeMdatSize = mdatSize >= 2 ** 32; + if (needsLargeMdatSize) for (const trackData of tracksInFragment) trackData.currentChunk.offset += 8; + if (this.formatOptions.onMoof) this.writer.startTrackingWrites(); + const newMoofBox = moof(fragmentNumber, tracksInFragment); + this.boxWriter.writeBox(newMoofBox); + if (this.formatOptions.onMoof) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMoof(data, start, fragmentStartTimestamp); + } + assert(this.writer.getPos() === mdatStartPos); + if (this.formatOptions.onMdat) this.writer.startTrackingWrites(); + const mdatBox = mdat(needsLargeMdatSize); + mdatBox.size = mdatSize; + this.boxWriter.writeBox(mdatBox); + this.writer.seek(mdatStartPos + (needsLargeMdatSize ? 16 : 8)); + for (const trackData of tracksInFragment) for (const sample of trackData.currentChunk.samples) { + this.writer.write(sample.data); + sample.data = null; + } + if (this.formatOptions.onMdat) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMdat(data, start); + } + for (const trackData of tracksInFragment) { + trackData.finalizedChunks.push(trackData.currentChunk); + this.finalizedChunks.push(trackData.currentChunk); + trackData.currentChunk = null; + } + if (flushWriter) await this.writer.flush(); + } + async registerSampleFastStartReserve(trackData, sample) { + if (this.allTracksAreKnown()) { + if (!this.mdat) await this.createFastStartReserveMdat(); + await this.addSampleToTrack(trackData, sample); + } else trackData.sampleQueue.push(sample); + } + async createFastStartReserveMdat() { + assert(this.writer); + assert(this.boxWriter); + this.ensureOneEnabledTrack(); + const moovBox = moov(this); + const reservedSize = this.boxWriter.measureBox(moovBox) + this.computeSampleTableSizeUpperBound() + 4096; + assert(this.ftypSize !== null); + this.writer.seek(this.ftypSize + reservedSize); + if (this.formatOptions.onMdat) this.writer.startTrackingWrites(); + this.mdat = mdat(true); + this.boxWriter.writeBox(this.mdat); + for (const trackData of this.trackDatas) { + for (const sample of trackData.sampleQueue) await this.addSampleToTrack(trackData, sample); + trackData.sampleQueue.length = 0; + } + } + computeSampleTableSizeUpperBound() { + assert(this.fastStart === "reserve"); + let upperBound = 0; + for (const trackData of this.trackDatas) { + const n = trackData.track.metadata.maximumPacketCount; + assert(n !== void 0); + upperBound += 8 * Math.ceil(2 / 3 * n); + upperBound += 4 * n; + upperBound += 8 * Math.ceil(2 / 3 * n); + upperBound += 12 * Math.ceil(2 / 3 * n); + upperBound += 4 * n; + upperBound += 8 * n; + } + return upperBound; + } + async onTrackClose(track) { + const release = await this.mutex.acquire(); + const trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) { + trackData.closed = true; + if (trackData.type === "subtitle" && track.source._codec === "webvtt") await this.processWebVTTCues(trackData, Infinity); + this.processTimestamps(trackData); + } + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + if (this.isFragmented) await this.interleaveSamples(); + release(); + } + ensureOneEnabledTrack() { + for (const type of [ + "video", + "audio", + "subtitle" + ]) { + const tracks = this.trackDatas.filter((t) => t.type === type); + if (tracks.length === 0) continue; + if (!tracks.some((t) => t.track.metadata.disposition?.default !== false)) { + const firstTrack = tracks[0]; + firstTrack.track.metadata.disposition = { + ...firstTrack.track.metadata.disposition, + default: true + }; + } + } + } + /** Internal function for external callers who want to full control fragment boundaries. */ + async forceFragmentFinalization() { + assert(this.isFragmented); + const release = await this.mutex.acquire(); + try { + for (const trackData of this.trackDatas) { + if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") await this.processWebVTTCues(trackData, Infinity); + this.processTimestamps(trackData); + } + await this.interleaveSamples(true); + await this.finalizeFragment(); + } finally { + release(); + } + } + /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ + async finalize() { + const release = await this.mutex.acquire(); + this.allTracksKnown.resolve(); + this.ensureOneEnabledTrack(); + if (!this.mdat && this.fastStart === "reserve") await this.createFastStartReserveMdat(); + for (const trackData of this.trackDatas) { + trackData.closed = true; + if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") await this.processWebVTTCues(trackData, Infinity); + this.processTimestamps(trackData); + } + if (this.isFragmented) { + await this.interleaveSamples(true); + await this.finalizeFragment(false); + } else for (const trackData of this.trackDatas) { + await this.finalizeCurrentChunk(trackData); + const span = presentationSpan(trackData); + if (span > 0) { + let totalBytes = 0; + for (const sample of trackData.samples) totalBytes += sample.size; + trackData.avgBitrate = Math.round(8 * totalBytes / span); + } else trackData.avgBitrate = 0; + let windowStart = 0; + let windowBytes = 0; + let maxWindowBytes = 0; + for (let i = 0; i < trackData.samples.length; i++) { + const sample = trackData.samples[i]; + windowBytes += sample.size; + while (sample.decodeTimestamp - trackData.samples[windowStart].decodeTimestamp >= 1) { + windowBytes -= trackData.samples[windowStart].size; + windowStart++; + } + maxWindowBytes = Math.max(maxWindowBytes, windowBytes); + } + trackData.maxBitrate = 8 * maxWindowBytes; + if (trackData.startTimestampOffset !== null) for (let i = 0; i < trackData.samples.length; i++) { + const sample = trackData.samples[i]; + sample.timestamp -= trackData.startTimestampOffset; + sample.decodeTimestamp -= trackData.startTimestampOffset; + } + } + assert(this.writer); + assert(this.boxWriter); + if (this.fastStart === "in-memory") { + this.mdat = mdat(false); + let mdatSize; + for (let i = 0; i < 2; i++) { + const movieBox = moov(this); + const movieBoxSize = this.boxWriter.measureBox(movieBox); + mdatSize = this.boxWriter.measureBox(this.mdat); + let currentChunkPos = this.writer.getPos() + movieBoxSize + mdatSize; + for (const chunk of this.finalizedChunks) { + chunk.offset = currentChunkPos; + for (const { data } of chunk.samples) { + assert(data); + currentChunkPos += data.byteLength; + mdatSize += data.byteLength; + } + } + if (currentChunkPos < 2 ** 32) break; + if (mdatSize >= 2 ** 32) this.mdat.largeSize = true; + } + if (this.formatOptions.onMoov) this.writer.startTrackingWrites(); + const movieBox = moov(this); + this.boxWriter.writeBox(movieBox); + if (this.formatOptions.onMoov) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMoov(data, start); + } + if (this.formatOptions.onMdat) this.writer.startTrackingWrites(); + this.mdat.size = mdatSize; + this.boxWriter.writeBox(this.mdat); + for (const chunk of this.finalizedChunks) for (const sample of chunk.samples) { + assert(sample.data); + this.writer.write(sample.data); + sample.data = null; + } + if (this.formatOptions.onMdat) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMdat(data, start); + } + } else if (this.isFragmented) if (this.isCmaf) { + const contentSize = this.segmentHeaderSize !== null ? this.writer.getPos() - this.segmentHeaderSize : 0; + this.writer.seek(0); + this.boxWriter.writeBox(styp()); + this.boxWriter.writeBox(sidx(this, contentSize)); + } else { + const startPos = this.writer.getPos(); + const mfraBox = mfra(this.trackDatas); + this.boxWriter.writeBox(mfraBox); + const mfraBoxSize = this.writer.getPos() - startPos; + this.writer.seek(this.writer.getPos() - 4); + this.boxWriter.writeU32(mfraBoxSize); + } + else { + assert(this.mdat); + const mdatPos = this.boxWriter.offsets.get(this.mdat); + assert(mdatPos !== void 0); + const mdatSize = this.writer.getPos() - mdatPos; + this.mdat.size = mdatSize; + this.mdat.largeSize = mdatSize >= 2 ** 32; + this.boxWriter.patchBox(this.mdat); + if (this.formatOptions.onMdat) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMdat(data, start); + } + const movieBox = moov(this); + if (this.fastStart === "reserve") { + assert(this.ftypSize !== null); + this.writer.seek(this.ftypSize); + if (this.formatOptions.onMoov) this.writer.startTrackingWrites(); + this.boxWriter.writeBox(movieBox); + const remainingSpace = this.boxWriter.offsets.get(this.mdat) - this.writer.getPos(); + this.boxWriter.writeBox(free(remainingSpace)); + } else { + if (this.formatOptions.onMoov) this.writer.startTrackingWrites(); + this.boxWriter.writeBox(movieBox); + } + if (this.formatOptions.onMoov) { + const { data, start } = this.writer.stopTrackingWrites(); + this.formatOptions.onMoov(data, start); + } + } + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/matroska/matroska-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var MIN_CLUSTER_TIMESTAMP_MS = -(2 ** 15); +var MAX_CLUSTER_TIMESTAMP_MS = 2 ** 15 - 1; +var SEGMENT_SIZE_BYTES = 6; +var CLUSTER_SIZE_BYTES = 5; +var TRACK_TYPE_MAP = { + video: 1, + audio: 2, + subtitle: 17 +}; +var MatroskaMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.trackDatas = []; + this.allTracksKnown = promiseWithResolvers(); + this.segment = null; + this.segmentInfo = null; + this.seekHead = null; + this.tracksElement = null; + this.tagsElement = null; + this.attachmentsElement = null; + this.segmentDuration = null; + this.cues = null; + this.currentCluster = null; + this.currentClusterStartMsTimestamp = null; + this.currentClusterMaxMsTimestamp = null; + this.trackDatasInCurrentCluster = /* @__PURE__ */ new Map(); + this.startTimestamp = Infinity; + this.endTimestamp = -Infinity; + this.warnedAboutTooNegativeTimestamp = false; + this.format = format; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(!!this.format._options.appendOnly); + this.ebmlWriter = new EBMLWriter(this.writer); + this.writeEBMLHeader(); + this.createSegmentInfo(); + this.createCues(); + await this.writer.flush(); + for (const track of this.output.tracks) if (track.isVideoTrack() && track.metadata.decoderConfig) this.getVideoTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + else if (track.isAudioTrack() && track.metadata.decoderConfig) this.getAudioTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + release(); + } + writeEBMLHeader() { + if (this.format._options.onEbmlHeader) this.writer.startTrackingWrites(); + const ebmlHeader = { + id: EBMLId.EBML, + data: [ + { + id: EBMLId.EBMLVersion, + data: 1 + }, + { + id: EBMLId.EBMLReadVersion, + data: 1 + }, + { + id: EBMLId.EBMLMaxIDLength, + data: 4 + }, + { + id: EBMLId.EBMLMaxSizeLength, + data: 8 + }, + { + id: EBMLId.DocType, + data: this.format instanceof WebMOutputFormat ? "webm" : "matroska" + }, + { + id: EBMLId.DocTypeVersion, + data: 4 + }, + { + id: EBMLId.DocTypeReadVersion, + data: 2 + } + ] + }; + this.ebmlWriter.writeEBML(ebmlHeader); + if (this.format._options.onEbmlHeader) { + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onEbmlHeader(data, start); + } + } + /** + * Creates a SeekHead element which is positioned near the start of the file and allows the media player to seek to + * relevant sections more easily. Since we don't know the positions of those sections yet, we'll set them later. + */ + maybeCreateSeekHead(writeOffsets) { + if (this.format._options.appendOnly) return; + const kaxCues = new Uint8Array([ + 28, + 83, + 187, + 107 + ]); + const kaxInfo = new Uint8Array([ + 21, + 73, + 169, + 102 + ]); + const kaxTracks = new Uint8Array([ + 22, + 84, + 174, + 107 + ]); + const kaxAttachments = new Uint8Array([ + 25, + 65, + 164, + 105 + ]); + const kaxTags = new Uint8Array([ + 18, + 84, + 195, + 103 + ]); + const seekHead = { + id: EBMLId.SeekHead, + data: [ + { + id: EBMLId.Seek, + data: [{ + id: EBMLId.SeekID, + data: kaxInfo + }, { + id: EBMLId.SeekPosition, + size: 5, + data: writeOffsets ? this.ebmlWriter.offsets.get(this.segmentInfo) - this.segmentDataOffset : 0 + }] + }, + { + id: EBMLId.Seek, + data: [{ + id: EBMLId.SeekID, + data: kaxTracks + }, { + id: EBMLId.SeekPosition, + size: 5, + data: writeOffsets ? this.ebmlWriter.offsets.get(this.tracksElement) - this.segmentDataOffset : 0 + }] + }, + this.attachmentsElement ? { + id: EBMLId.Seek, + data: [{ + id: EBMLId.SeekID, + data: kaxAttachments + }, { + id: EBMLId.SeekPosition, + size: 5, + data: writeOffsets ? this.ebmlWriter.offsets.get(this.attachmentsElement) - this.segmentDataOffset : 0 + }] + } : null, + this.tagsElement ? { + id: EBMLId.Seek, + data: [{ + id: EBMLId.SeekID, + data: kaxTags + }, { + id: EBMLId.SeekPosition, + size: 5, + data: writeOffsets ? this.ebmlWriter.offsets.get(this.tagsElement) - this.segmentDataOffset : 0 + }] + } : null, + { + id: EBMLId.Seek, + data: [{ + id: EBMLId.SeekID, + data: kaxCues + }, { + id: EBMLId.SeekPosition, + size: 5, + data: writeOffsets ? this.ebmlWriter.offsets.get(this.cues) - this.segmentDataOffset : 0 + }] + } + ] + }; + this.seekHead = seekHead; + } + createSegmentInfo() { + const segmentDuration = { + id: EBMLId.Duration, + data: new EBMLFloat64(0) + }; + this.segmentDuration = segmentDuration; + const segmentInfo = { + id: EBMLId.Info, + data: [ + { + id: EBMLId.TimestampScale, + data: 1e6 + }, + { + id: EBMLId.MuxingApp, + data: VERSIONED_NAME + }, + { + id: EBMLId.WritingApp, + data: VERSIONED_NAME + }, + !this.format._options.appendOnly ? segmentDuration : null + ] + }; + this.segmentInfo = segmentInfo; + } + createTracks() { + const tracksElement = { + id: EBMLId.Tracks, + data: [] + }; + this.tracksElement = tracksElement; + for (const trackData of this.trackDatas) { + let codecId = CODEC_STRING_MAP[trackData.track.source._codec]; + assert(codecId); + if (trackData.type === "audio" && trackData.track.source._codec === "dts") { + if (trackData.info.decoderConfig.codec === "dtse") codecId = "A_DTS/EXPRESS"; + else if (trackData.info.decoderConfig.codec === "dtsl") codecId = "A_DTS/LOSSLESS"; + } + let seekPreRollNs = 0; + if (trackData.type === "audio" && trackData.track.source._codec === "opus") { + seekPreRollNs = 1e6 * 80; + const description = trackData.info.decoderConfig.description; + if (description) { + const header = parseOpusIdentificationHeader(toUint8Array(description)); + seekPreRollNs = Math.round(1e9 * (header.preSkip / OPUS_SAMPLE_RATE)); + } + } + tracksElement.data.push({ + id: EBMLId.TrackEntry, + data: [ + { + id: EBMLId.TrackNumber, + data: trackData.track.id + }, + { + id: EBMLId.TrackUID, + data: trackData.track.id + }, + { + id: EBMLId.TrackType, + data: TRACK_TYPE_MAP[trackData.type] + }, + trackData.track.metadata.disposition?.default === false ? { + id: EBMLId.FlagDefault, + data: 0 + } : null, + trackData.track.metadata.disposition?.forced ? { + id: EBMLId.FlagForced, + data: 1 + } : null, + trackData.track.metadata.disposition?.hearingImpaired ? { + id: EBMLId.FlagHearingImpaired, + data: 1 + } : null, + trackData.track.metadata.disposition?.visuallyImpaired ? { + id: EBMLId.FlagVisualImpaired, + data: 1 + } : null, + trackData.track.metadata.disposition?.original ? { + id: EBMLId.FlagOriginal, + data: 1 + } : null, + trackData.track.metadata.disposition?.commentary ? { + id: EBMLId.FlagCommentary, + data: 1 + } : null, + { + id: EBMLId.FlagLacing, + data: 0 + }, + { + id: EBMLId.Language, + data: trackData.track.metadata.languageCode ?? "und" + }, + { + id: EBMLId.CodecID, + data: codecId + }, + trackData.codecPrivate ? { + id: EBMLId.CodecPrivate, + data: toUint8Array(trackData.codecPrivate) + } : null, + seekPreRollNs > 0 ? { + id: EBMLId.SeekPreRoll, + data: seekPreRollNs + } : null, + trackData.track.metadata.name !== void 0 ? { + id: EBMLId.Name, + data: new EBMLUnicodeString(trackData.track.metadata.name) + } : null, + trackData.type === "video" ? this.videoSpecificTrackInfo(trackData) : null, + trackData.type === "audio" ? this.audioSpecificTrackInfo(trackData) : null, + trackData.type === "subtitle" ? this.subtitleSpecificTrackInfo(trackData) : null + ] + }); + } + } + videoSpecificTrackInfo(trackData) { + const { frameRate, transformationMatrix } = trackData.track.metadata; + const elements = [frameRate ? { + id: EBMLId.DefaultDuration, + data: 1e9 / frameRate + } : null]; + let rotation; + let horizontalScale; + let verticalScale; + if (transformationMatrix) { + rotation = extractRotationFromMatrix(transformationMatrix); + const unrotated = multiplyMatrices(rotationMatrix(-rotation), transformationMatrix); + horizontalScale = Math.sign(unrotated[0]); + verticalScale = Math.sign(unrotated[4]); + if (verticalScale === -1) { + verticalScale = 1; + horizontalScale = -horizontalScale; + rotation = normalizeRotation(rotation + 180); + } + } else { + rotation = trackData.track.metadata.rotation ?? 0; + horizontalScale = trackData.track.metadata.flip ? -1 : 1; + verticalScale = 1; + } + const roll = rotation ? normalizeRotation(horizontalScale === -1 ? rotation : -rotation) : 0; + const yaw = Math.round(Math.acos(horizontalScale) * RAD_TO_DEG); + const pitch = Math.round(Math.acos(verticalScale) * RAD_TO_DEG); + const hasNonSquarePixelAspectRatio = !!trackData.info.aspectRatio && trackData.info.aspectRatio.num * trackData.info.height !== trackData.info.aspectRatio.den * trackData.info.width; + const colorSpace = trackData.info.decoderConfig.colorSpace; + const videoElement = { + id: EBMLId.Video, + data: [ + { + id: EBMLId.PixelWidth, + data: trackData.info.width + }, + { + id: EBMLId.PixelHeight, + data: trackData.info.height + }, + hasNonSquarePixelAspectRatio ? { + id: EBMLId.DisplayWidth, + data: trackData.info.aspectRatio.num + } : null, + hasNonSquarePixelAspectRatio ? { + id: EBMLId.DisplayHeight, + data: trackData.info.aspectRatio.den + } : null, + hasNonSquarePixelAspectRatio ? { + id: EBMLId.DisplayUnit, + data: 3 + } : null, + trackData.info.alphaMode ? { + id: EBMLId.AlphaMode, + data: 1 + } : null, + colorSpaceIsEmpty(colorSpace) ? null : { + id: EBMLId.Colour, + data: [ + { + id: EBMLId.MatrixCoefficients, + data: colorSpace?.matrix != null ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 2 + }, + { + id: EBMLId.TransferCharacteristics, + data: colorSpace?.transfer != null ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 2 + }, + { + id: EBMLId.Primaries, + data: colorSpace?.primaries != null ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 2 + }, + { + id: EBMLId.Range, + data: colorSpace?.fullRange != null ? colorSpace.fullRange ? 2 : 1 : 0 + } + ] + }, + roll || yaw || pitch ? { + id: EBMLId.Projection, + data: [ + { + id: EBMLId.ProjectionType, + data: 0 + }, + yaw ? { + id: EBMLId.ProjectionPoseYaw, + data: new EBMLFloat32(yaw) + } : null, + pitch ? { + id: EBMLId.ProjectionPosePitch, + data: new EBMLFloat32(pitch) + } : null, + roll ? { + id: EBMLId.ProjectionPoseRoll, + data: new EBMLFloat32((roll + 180) % 360 - 180) + } : null + ] + } : null + ] + }; + elements.push(videoElement); + return elements; + } + audioSpecificTrackInfo(trackData) { + const pcmInfo = PCM_AUDIO_CODECS.includes(trackData.track.source._codec) ? parsePcmCodec(trackData.track.source._codec) : null; + return [{ + id: EBMLId.Audio, + data: [ + { + id: EBMLId.SamplingFrequency, + data: new EBMLFloat32(trackData.info.sampleRate) + }, + { + id: EBMLId.Channels, + data: trackData.info.numberOfChannels + }, + pcmInfo ? { + id: EBMLId.BitDepth, + data: 8 * pcmInfo.sampleSize + } : null + ] + }]; + } + subtitleSpecificTrackInfo(trackData) { + return []; + } + maybeCreateTags() { + const simpleTags = []; + const addSimpleTag = (key, value) => { + simpleTags.push({ + id: EBMLId.SimpleTag, + data: [{ + id: EBMLId.TagName, + data: new EBMLUnicodeString(key) + }, typeof value === "string" ? { + id: EBMLId.TagString, + data: new EBMLUnicodeString(value) + } : { + id: EBMLId.TagBinary, + data: value + }] + }); + }; + const metadataTags = this.output._metadataTags; + const writtenTags = /* @__PURE__ */ new Set(); + for (const { key, value } of keyValueIterator(metadataTags)) switch (key) { + case "title": + addSimpleTag("TITLE", value); + writtenTags.add("TITLE"); + break; + case "description": + addSimpleTag("DESCRIPTION", value); + writtenTags.add("DESCRIPTION"); + break; + case "artist": + addSimpleTag("ARTIST", value); + writtenTags.add("ARTIST"); + break; + case "album": + addSimpleTag("ALBUM", value); + writtenTags.add("ALBUM"); + break; + case "albumArtist": + addSimpleTag("ALBUM_ARTIST", value); + writtenTags.add("ALBUM_ARTIST"); + break; + case "genre": + addSimpleTag("GENRE", value); + writtenTags.add("GENRE"); + break; + case "beatsPerMinute": + addSimpleTag("BPM", value.toString()); + writtenTags.add("BPM"); + break; + case "comment": + addSimpleTag("COMMENT", value); + writtenTags.add("COMMENT"); + break; + case "lyrics": + addSimpleTag("LYRICS", value); + writtenTags.add("LYRICS"); + break; + case "date": + addSimpleTag("DATE", value.toISOString().slice(0, 10)); + writtenTags.add("DATE"); + break; + case "trackNumber": + addSimpleTag("PART_NUMBER", metadataTags.tracksTotal !== void 0 ? `${value}/${metadataTags.tracksTotal}` : value.toString()); + writtenTags.add("PART_NUMBER"); + break; + case "discNumber": + addSimpleTag("DISC", metadataTags.discsTotal !== void 0 ? `${value}/${metadataTags.discsTotal}` : value.toString()); + writtenTags.add("DISC"); + break; + case "tracksTotal": + case "discsTotal": break; + case "images": + case "raw": break; + default: assertNever(key); + } + if (metadataTags.raw) for (const key in metadataTags.raw) { + const value = metadataTags.raw[key]; + if (value == null || writtenTags.has(key)) continue; + if (typeof value === "string" || value instanceof Uint8Array) addSimpleTag(key, value); + } + if (simpleTags.length === 0) return; + this.tagsElement = { + id: EBMLId.Tags, + data: [{ + id: EBMLId.Tag, + data: [{ + id: EBMLId.Targets, + data: [{ + id: EBMLId.TargetTypeValue, + data: 50 + }, { + id: EBMLId.TargetType, + data: "MOVIE" + }] + }, ...simpleTags] + }] + }; + } + maybeCreateAttachments() { + const metadataTags = this.output._metadataTags; + const elements = []; + const existingFileUids = /* @__PURE__ */ new Set(); + const images = metadataTags.images ?? []; + for (const image of images) { + let imageName = image.name; + if (imageName === void 0) imageName = (image.kind === "coverFront" ? "cover" : image.kind === "coverBack" ? "back" : "image") + (imageMimeTypeToExtension(image.mimeType) ?? ""); + let fileUid; + while (true) { + fileUid = 0n; + for (let i = 0; i < 8; i++) { + fileUid <<= 8n; + fileUid |= BigInt(Math.floor(Math.random() * 256)); + } + if (fileUid !== 0n && !existingFileUids.has(fileUid)) break; + } + existingFileUids.add(fileUid); + elements.push({ + id: EBMLId.AttachedFile, + data: [ + image.description !== void 0 ? { + id: EBMLId.FileDescription, + data: new EBMLUnicodeString(image.description) + } : null, + { + id: EBMLId.FileName, + data: new EBMLUnicodeString(imageName) + }, + { + id: EBMLId.FileMediaType, + data: image.mimeType + }, + { + id: EBMLId.FileData, + data: image.data + }, + { + id: EBMLId.FileUID, + data: fileUid + } + ] + }); + } + for (const [key, value] of Object.entries(metadataTags.raw ?? {})) { + if (!(value instanceof AttachedFile)) continue; + if (!/^\d+$/.test(key)) continue; + if (images.find((x) => x.mimeType === value.mimeType && uint8ArraysAreEqual(x.data, value.data))) continue; + elements.push({ + id: EBMLId.AttachedFile, + data: [ + value.description !== void 0 ? { + id: EBMLId.FileDescription, + data: new EBMLUnicodeString(value.description) + } : null, + { + id: EBMLId.FileName, + data: new EBMLUnicodeString(value.name ?? "") + }, + { + id: EBMLId.FileMediaType, + data: value.mimeType ?? "" + }, + { + id: EBMLId.FileData, + data: value.data + }, + { + id: EBMLId.FileUID, + data: BigInt(key) + } + ] + }); + } + if (elements.length === 0) return; + this.attachmentsElement = { + id: EBMLId.Attachments, + data: elements + }; + } + createSegment() { + this.createTracks(); + this.maybeCreateTags(); + this.maybeCreateAttachments(); + this.maybeCreateSeekHead(false); + const segment = { + id: EBMLId.Segment, + size: this.format._options.appendOnly ? -1 : SEGMENT_SIZE_BYTES, + data: [ + this.seekHead, + this.segmentInfo, + this.tracksElement, + this.attachmentsElement, + this.tagsElement + ] + }; + this.segment = segment; + if (this.format._options.onSegmentHeader) this.writer.startTrackingWrites(); + this.ebmlWriter.writeEBML(segment); + if (this.format._options.onSegmentHeader) { + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onSegmentHeader(data, start); + } + } + createCues() { + this.cues = { + id: EBMLId.Cues, + data: [] + }; + } + get segmentDataOffset() { + assert(this.segment); + return this.ebmlWriter.dataOffsets.get(this.segment); + } + allTracksAreKnown() { + for (const track of this.output.tracks) if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) return false; + return true; + } + async getMimeType() { + await this.allTracksKnown.promise; + const codecStrings = this.trackDatas.map((trackData) => { + if (trackData.type === "video") return trackData.info.decoderConfig.codec; + else if (trackData.type === "audio") return trackData.info.decoderConfig.codec; + else return { webvtt: "wvtt" }[trackData.track.source._codec]; + }); + return buildMatroskaMimeType({ + isWebM: this.format instanceof WebMOutputFormat, + hasVideo: this.trackDatas.some((x) => x.type === "video"), + hasAudio: this.trackDatas.some((x) => x.type === "audio"), + codecStrings + }); + } + getVideoTrackData(track, packet, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateVideoChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + assert(meta.decoderConfig.codedWidth !== void 0); + assert(meta.decoderConfig.codedHeight !== void 0); + const displayAspectWidth = meta.decoderConfig.displayAspectWidth; + const displayAspectHeight = meta.decoderConfig.displayAspectHeight; + const aspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? null : simplifyRational({ + num: displayAspectWidth, + den: displayAspectHeight + }); + const newTrackData = { + track, + type: "video", + info: { + width: meta.decoderConfig.codedWidth, + height: meta.decoderConfig.codedHeight, + aspectRatio, + decoderConfig: meta.decoderConfig, + alphaMode: track.metadata.canBeTransparent || (packet ? !!packet.sideData.alpha : null) + }, + chunkQueue: [], + lastWrittenMsTimestamp: null, + codecPrivate: meta.decoderConfig.description ?? null, + closed: false + }; + if (track.source._codec === "vp9") newTrackData.codecPrivate = new Uint8Array(generateVp9CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)); + else if (track.source._codec === "av1") newTrackData.codecPrivate = new Uint8Array(generateAv1CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)); + else if (track.source._codec === "prores") newTrackData.codecPrivate = textEncoder.encode(meta.decoderConfig.codec); + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + getAudioTrackData(track, packet, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + const decoderConfig = { ...meta.decoderConfig }; + let requiresAdtsStripping = false; + if (track.source._codec === "aac" && !decoderConfig.description) { + if (!packet) throw new Error("No AAC description provided; you must therefore provide a priming packet."); + const adtsFrame = readAdtsFrameHeader(FileSlice.tempFromBytes(packet.data)); + if (!adtsFrame) throw new Error("Couldn't parse ADTS header from the AAC packet. Make sure the packets are in ADTS format (as specified in ISO 13818-7) when not providing a description, or provide a description (must be an AudioSpecificConfig as specified in ISO 14496-3) and ensure the packets are raw AAC data."); + const sampleRate = aacFrequencyTable[adtsFrame.samplingFrequencyIndex]; + const numberOfChannels = aacChannelMap[adtsFrame.channelConfiguration]; + if (sampleRate === void 0 || numberOfChannels === void 0) throw new Error("Invalid ADTS frame header."); + decoderConfig.description = buildAacAudioSpecificConfig({ + objectType: adtsFrame.objectType, + outputSampleRate: sampleRate, + outputNumberOfChannels: numberOfChannels + }); + requiresAdtsStripping = true; + } + const newTrackData = { + track, + type: "audio", + info: { + numberOfChannels: meta.decoderConfig.numberOfChannels, + sampleRate: meta.decoderConfig.sampleRate, + decoderConfig, + requiresAdtsStripping + }, + chunkQueue: [], + lastWrittenMsTimestamp: null, + codecPrivate: decoderConfig.description ?? null, + closed: false + }; + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + getSubtitleTrackData(track, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateSubtitleMetadata(meta); + assert(meta); + assert(meta.config); + const newTrackData = { + track, + type: "subtitle", + info: { config: meta.config }, + chunkQueue: [], + lastWrittenMsTimestamp: null, + codecPrivate: textEncoder.encode(meta.config.description), + closed: false + }; + this.trackDatas.push(newTrackData); + this.trackDatas.sort((a, b) => a.track.id - b.track.id); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + async addEncodedVideoPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, packet, meta); + trackData.info.alphaMode ??= !!packet.sideData.alpha; + let packetData = packet.data; + if (track.source._codec === "prores") { + if (packetData.byteLength < 8) throw new Error("ProRes packet too small, expected at least 8 bytes."); + packetData = packetData.subarray(8); + } + const isKeyFrame = packet.type === "key"; + this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame); + let timestamp = packet.timestamp; + let duration = packet.duration; + if (track.metadata.frameRate !== void 0) { + timestamp = roundToDivisor(timestamp, track.metadata.frameRate); + duration = roundToDivisor(duration, track.metadata.frameRate); + } + const additions = trackData.info.alphaMode ? packet.sideData.alpha ?? null : null; + const videoChunk = this.createInternalChunk(packetData, timestamp, duration, packet.type, additions); + if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); + trackData.chunkQueue.push(videoChunk); + await this.interleaveChunks(); + } finally { + release(); + } + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, packet, meta); + let packetData = packet.data; + if (trackData.info.requiresAdtsStripping) { + const adtsFrame = readAdtsFrameHeader(FileSlice.tempFromBytes(packetData)); + if (!adtsFrame) throw new Error("Expected ADTS frame, didn't get one."); + const headerLength = adtsFrame.crcCheck === null ? 7 : 9; + packetData = packetData.subarray(headerLength); + } + const isKeyFrame = packet.type === "key"; + this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame); + const audioChunk = this.createInternalChunk(packetData, packet.timestamp, packet.duration, packet.type); + trackData.chunkQueue.push(audioChunk); + await this.interleaveChunks(); + } finally { + release(); + } + } + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + this.validateTimestamp(trackData.track, cue.timestamp, true); + let bodyText = cue.text; + const timestampMs = Math.round(cue.timestamp * 1e3); + inlineTimestampRegex.lastIndex = 0; + bodyText = bodyText.replace(inlineTimestampRegex, (match) => { + return `<${formatSubtitleTimestamp(parseSubtitleTimestamp(match.slice(1, -1)) - timestampMs)}>`; + }); + const body = textEncoder.encode(bodyText); + const additions = `${cue.settings ?? ""}\n${cue.identifier ?? ""}\n${cue.notes ?? ""}`; + const subtitleChunk = this.createInternalChunk(body, cue.timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); + trackData.chunkQueue.push(subtitleChunk); + await this.interleaveChunks(); + } finally { + release(); + } + } + async interleaveChunks(isFinalCall = false) { + if (!isFinalCall && !this.allTracksAreKnown()) return; + outer: while (true) { + let trackWithMinTimestamp = null; + let minTimestamp = Infinity; + for (const trackData of this.trackDatas) { + if (!isFinalCall && trackData.chunkQueue.length === 0 && !trackData.closed) break outer; + if (trackData.chunkQueue.length > 0 && trackData.chunkQueue[0].timestamp < minTimestamp) { + trackWithMinTimestamp = trackData; + minTimestamp = trackData.chunkQueue[0].timestamp; + } + } + if (!trackWithMinTimestamp) break; + const chunk = trackWithMinTimestamp.chunkQueue.shift(); + this.writeBlock(trackWithMinTimestamp, chunk); + } + if (!isFinalCall) await this.writer.flush(); + } + /** + * Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often + * lack color space information. This method patches in that information. + */ + fixVP9ColorSpace(trackData, chunk) { + if (chunk.type !== "key") return; + if (!trackData.info.decoderConfig.colorSpace || !trackData.info.decoderConfig.colorSpace.matrix) return; + const bitstream = new Bitstream(chunk.data); + bitstream.skipBits(2); + const profileLowBit = bitstream.readBits(1); + const profile = (bitstream.readBits(1) << 1) + profileLowBit; + if (profile === 3) bitstream.skipBits(1); + if (bitstream.readBits(1)) return; + if (bitstream.readBits(1) !== 0) return; + bitstream.skipBits(2); + if (bitstream.readBits(24) !== 4817730) return; + if (profile >= 2) bitstream.skipBits(1); + const colorSpaceID = { + rgb: 7, + bt709: 2, + bt470bg: 1, + smpte170m: 3 + }[trackData.info.decoderConfig.colorSpace.matrix]; + writeBits(chunk.data, bitstream.pos, bitstream.pos + 3, colorSpaceID); + } + /** Converts a read-only external chunk into an internal one for easier use. */ + createInternalChunk(data, timestamp, duration, type, additions = null) { + return { + data, + type, + timestamp, + duration, + additions + }; + } + /** Writes a block containing media data to the file. */ + writeBlock(trackData, chunk) { + if (!this.segment) this.createSegment(); + const msTimestamp = Math.round(1e3 * chunk.timestamp); + const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { + if (trackData === otherTrackData) return chunk.type === "key"; + const firstQueuedSample = otherTrackData.chunkQueue[0]; + if (firstQueuedSample) return firstQueuedSample.type === "key"; + return otherTrackData.closed; + }); + let shouldCreateNewCluster = false; + if (!this.currentCluster) shouldCreateNewCluster = true; + else { + assert(this.currentClusterStartMsTimestamp !== null); + assert(this.currentClusterMaxMsTimestamp !== null); + const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp; + shouldCreateNewCluster = keyFrameQueuedEverywhere && msTimestamp > this.currentClusterMaxMsTimestamp && relativeTimestamp >= 1e3 * (this.format._options.minimumClusterDuration ?? 1) || relativeTimestamp > MAX_CLUSTER_TIMESTAMP_MS; + } + if (shouldCreateNewCluster) this.createNewCluster(msTimestamp); + const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp; + if (relativeTimestamp < MIN_CLUSTER_TIMESTAMP_MS) { + if (!this.warnedAboutTooNegativeTimestamp) { + const formatName = this.format instanceof WebMOutputFormat ? "WebM" : "Matroska"; + Logging._warn(`Packets had to be discarded because their timestamp is too negative to represent in ${formatName}.`); + this.warnedAboutTooNegativeTimestamp = true; + } + return; + } + const prelude = new Uint8Array(4); + const view = new DataView(prelude.buffer); + view.setUint8(0, 128 | trackData.track.id); + view.setInt16(1, relativeTimestamp, false); + const msDuration = Math.round(1e3 * chunk.duration); + if (!(!!chunk.additions || trackData.type === "subtitle")) { + view.setUint8(3, Number(chunk.type === "key") << 7); + const simpleBlock = { + id: EBMLId.SimpleBlock, + data: [prelude, chunk.data] + }; + this.ebmlWriter.writeEBML(simpleBlock); + } else { + const blockGroup = { + id: EBMLId.BlockGroup, + data: [ + { + id: EBMLId.Block, + data: [prelude, chunk.data] + }, + chunk.type === "delta" ? { + id: EBMLId.ReferenceBlock, + data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp - msTimestamp) + } : null, + chunk.additions ? { + id: EBMLId.BlockAdditions, + data: [{ + id: EBMLId.BlockMore, + data: [{ + id: EBMLId.BlockAddID, + data: 1 + }, { + id: EBMLId.BlockAdditional, + data: chunk.additions + }] + }] + } : null, + msDuration > 0 ? { + id: EBMLId.BlockDuration, + data: msDuration + } : null + ] + }; + this.ebmlWriter.writeEBML(blockGroup); + } + this.startTimestamp = Math.min(this.startTimestamp, msTimestamp); + this.endTimestamp = Math.max(this.endTimestamp, msTimestamp + msDuration); + trackData.lastWrittenMsTimestamp = msTimestamp; + if (!this.trackDatasInCurrentCluster.has(trackData)) this.trackDatasInCurrentCluster.set(trackData, { firstMsTimestamp: msTimestamp }); + this.currentClusterMaxMsTimestamp = Math.max(this.currentClusterMaxMsTimestamp, msTimestamp); + } + /** Creates a new Cluster element to contain media chunks. */ + createNewCluster(msTimestamp) { + msTimestamp = Math.max(0, msTimestamp); + if (this.currentCluster) this.finalizeCurrentCluster(); + if (this.format._options.onCluster) this.writer.startTrackingWrites(); + this.currentCluster = { + id: EBMLId.Cluster, + size: this.format._options.appendOnly ? -1 : CLUSTER_SIZE_BYTES, + data: [{ + id: EBMLId.Timestamp, + data: msTimestamp + }] + }; + this.ebmlWriter.writeEBML(this.currentCluster); + this.currentClusterStartMsTimestamp = msTimestamp; + this.currentClusterMaxMsTimestamp = msTimestamp; + this.trackDatasInCurrentCluster.clear(); + } + finalizeCurrentCluster() { + assert(this.currentCluster); + if (!this.format._options.appendOnly) { + const clusterSize = this.writer.getPos() - this.ebmlWriter.dataOffsets.get(this.currentCluster); + const endPos = this.writer.getPos(); + this.writer.seek(this.ebmlWriter.offsets.get(this.currentCluster) + 4); + this.ebmlWriter.writeVarInt(clusterSize, CLUSTER_SIZE_BYTES); + this.writer.seek(endPos); + } + if (this.format._options.onCluster) { + assert(this.currentClusterStartMsTimestamp !== null); + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onCluster(data, start, this.currentClusterStartMsTimestamp / 1e3); + } + const clusterOffsetFromSegment = this.ebmlWriter.offsets.get(this.currentCluster) - this.segmentDataOffset; + const groupedByTimestamp = /* @__PURE__ */ new Map(); + for (const [trackData, { firstMsTimestamp }] of this.trackDatasInCurrentCluster) { + if (!groupedByTimestamp.has(firstMsTimestamp)) groupedByTimestamp.set(firstMsTimestamp, []); + groupedByTimestamp.get(firstMsTimestamp).push(trackData); + } + const groupedAndSortedByTimestamp = [...groupedByTimestamp.entries()].sort((a, b) => a[0] - b[0]); + for (const [msTimestamp, trackDatas] of groupedAndSortedByTimestamp) { + assert(this.cues); + this.cues.data.push({ + id: EBMLId.CuePoint, + data: [{ + id: EBMLId.CueTime, + data: Math.max(0, msTimestamp) + }, ...trackDatas.map((trackData) => { + return { + id: EBMLId.CueTrackPositions, + data: [{ + id: EBMLId.CueTrack, + data: trackData.track.id + }, { + id: EBMLId.CueClusterPosition, + data: clusterOffsetFromSegment + }] + }; + })] + }); + } + } + async onTrackClose(track) { + const release = await this.mutex.acquire(); + const trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) trackData.closed = true; + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + await this.interleaveChunks(); + release(); + } + /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ + async finalize() { + const release = await this.mutex.acquire(); + this.allTracksKnown.resolve(); + for (const trackData of this.trackDatas) trackData.closed = true; + if (!this.segment) this.createSegment(); + await this.interleaveChunks(true); + if (this.currentCluster) this.finalizeCurrentCluster(); + assert(this.cues); + this.ebmlWriter.writeEBML(this.cues); + if (!this.format._options.appendOnly) { + const segmentSize = this.writer.getPos() - this.segmentDataOffset; + this.writer.seek(this.ebmlWriter.offsets.get(this.segment) + 4); + this.ebmlWriter.writeVarInt(segmentSize, SEGMENT_SIZE_BYTES); + const duration = this.startTimestamp === Infinity ? 0 : this.endTimestamp; + this.segmentDuration.data = new EBMLFloat64(duration); + this.writer.seek(this.ebmlWriter.offsets.get(this.segmentDuration)); + this.ebmlWriter.writeEBML(this.segmentDuration); + assert(this.seekHead); + this.writer.seek(this.ebmlWriter.offsets.get(this.seekHead)); + this.maybeCreateSeekHead(true); + this.ebmlWriter.writeEBML(this.seekHead); + } + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mp3/mp3-writer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Mp3Writer = class { + constructor(writer) { + this.writer = writer; + this.helper = new Uint8Array(8); + this.helperView = new DataView(this.helper.buffer); + } + writeU32(value) { + this.helperView.setUint32(0, value, false); + this.writer.write(this.helper.subarray(0, 4)); + } + writeXingFrame(data) { + const startPos = this.writer.getPos(); + const firstByte = 255; + const secondByte = 224 | data.mpegVersionId << 3 | data.layer << 1; + let lowSamplingFrequency; + if (data.mpegVersionId & 2) lowSamplingFrequency = data.mpegVersionId & 1 ? 0 : 1; + else lowSamplingFrequency = 1; + const padding = 0; + const neededBytes = 155; + let bitrateIndex = -1; + const bitrateOffset = lowSamplingFrequency * 16 * 4 + data.layer * 16; + for (let i = 0; i < 16; i++) { + const kbr = KILOBIT_RATES[bitrateOffset + i]; + if (computeMp3FrameSize(lowSamplingFrequency, data.layer, 1e3 * kbr, data.sampleRate, padding) >= neededBytes) { + bitrateIndex = i; + break; + } + } + if (bitrateIndex === -1) throw new Error("No suitable bitrate found."); + const thirdByte = bitrateIndex << 4 | data.frequencyIndex << 2 | padding << 1; + const fourthByte = data.channel << 6 | data.modeExtension << 4 | data.copyright << 3 | data.original << 2 | data.emphasis; + this.helper[0] = firstByte; + this.helper[1] = secondByte; + this.helper[2] = thirdByte; + this.helper[3] = fourthByte; + this.writer.write(this.helper.subarray(0, 4)); + const xingOffset = getXingOffset(data.mpegVersionId, data.channel); + this.writer.seek(startPos + xingOffset); + this.writeU32(XING); + let flags = 0; + if (data.frameCount !== null) flags |= XingFlags.FrameCount; + if (data.fileSize !== null) flags |= XingFlags.FileSize; + if (data.toc !== null) flags |= XingFlags.Toc; + this.writeU32(flags); + this.writeU32(data.frameCount ?? 0); + this.writeU32(data.fileSize ?? 0); + this.writer.write(data.toc ?? new Uint8Array(100)); + const kilobitRate = KILOBIT_RATES[bitrateOffset + bitrateIndex]; + const frameSize = computeMp3FrameSize(lowSamplingFrequency, data.layer, 1e3 * kilobitRate, data.sampleRate, padding); + this.writer.write(new Uint8Array(startPos + frameSize - this.writer.getPos())); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mp3/mp3-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var Mp3Muxer = class extends Muxer { + constructor(output, format) { + super(output); + this.xingFrameData = null; + this.frameCount = 0; + this.framePositions = []; + this.xingFramePos = null; + this.format = format; + this.writeXingHeader = format._options.xingHeader !== false; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(!this.writeXingHeader); + this.mp3Writer = new Mp3Writer(this.writer); + if (!metadataTagsAreEmpty(this.output._metadataTags)) new Id3V2Writer(this.writer).writeId3V2Tag(this.output._metadataTags); + release(); + } + async getMimeType() { + return "audio/mpeg"; + } + async addEncodedVideoPacket() { + throw new Error("MP3 does not support video."); + } + async addEncodedAudioPacket(track, packet) { + const release = await this.mutex.acquire(); + try { + if (!this.xingFrameData && this.writeXingHeader) { + const view = toDataView(packet.data); + if (view.byteLength < 4) throw new Error("Invalid MP3 header in sample."); + const header = readMp3FrameHeader(view.getUint32(0, false), null).header; + if (!header) throw new Error("Invalid MP3 header in sample."); + const xingOffset = getXingOffset(header.mpegVersionId, header.channel); + if (view.byteLength >= xingOffset + 4) { + const word = view.getUint32(xingOffset, false); + if (word === 1483304551 || word === 1231971951) return; + } + this.xingFrameData = { + mpegVersionId: header.mpegVersionId, + layer: header.layer, + frequencyIndex: header.frequencyIndex, + sampleRate: header.sampleRate, + channel: header.channel, + modeExtension: header.modeExtension, + copyright: header.copyright, + original: header.original, + emphasis: header.emphasis, + frameCount: null, + fileSize: null, + toc: null + }; + this.xingFramePos = this.writer.getPos(); + this.mp3Writer.writeXingFrame(this.xingFrameData); + this.frameCount++; + } + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + if (this.writeXingHeader) this.framePositions.push(this.writer.getPos()); + this.writer.write(packet.data); + this.frameCount++; + await this.writer.flush(); + } finally { + release(); + } + } + async addSubtitleCue() { + throw new Error("MP3 does not support subtitles."); + } + async finalize() { + const release = await this.mutex.acquire(); + if (this.frameCount === 0) { + if (!this.writeXingHeader) throw new Error("Cannot finalize an empty MP3 file: not a single packet was added and the Xing header is disabled, so there's no frame we could write."); + const track = this.output.tracks[0]; + assert(track?.isAudioTrack()); + const primingPacket = track.metadata.primingPacket; + if (primingPacket) { + const view = toDataView(primingPacket.data); + if (view.byteLength < 4) throw new Error("Invalid MP3 header in priming packet."); + const header = readMp3FrameHeader(view.getUint32(0, false), null).header; + if (!header) throw new Error("Invalid MP3 header in priming packet."); + this.xingFrameData = { + mpegVersionId: header.mpegVersionId, + layer: header.layer, + frequencyIndex: header.frequencyIndex, + sampleRate: header.sampleRate, + channel: header.channel, + modeExtension: header.modeExtension, + copyright: header.copyright, + original: header.original, + emphasis: header.emphasis, + frameCount: null, + fileSize: null, + toc: null + }; + } else if (track.metadata.decoderConfig) { + const { sampleRate, numberOfChannels } = track.metadata.decoderConfig; + const mpegVersionIds = [ + 3, + 2, + 0 + ]; + let mpegVersionId = null; + let frequencyIndex = -1; + for (let i = 0; i < mpegVersionIds.length; i++) { + frequencyIndex = SAMPLING_RATES.indexOf(sampleRate << i); + if (frequencyIndex !== -1) { + mpegVersionId = mpegVersionIds[i]; + break; + } + } + if (mpegVersionId === null) throw new Error(`${sampleRate} Hz is not a valid MP3 sample rate.`); + this.xingFrameData = { + mpegVersionId, + layer: 1, + frequencyIndex, + sampleRate, + channel: numberOfChannels === 1 ? 3 : 0, + modeExtension: 0, + copyright: 0, + original: 0, + emphasis: 0, + frameCount: null, + fileSize: null, + toc: null + }; + } else throw new Error("Cannot finalize an empty MP3 file: no packets were added and the track specified neither a decoderConfig nor a primingPacket in its metadata, so there's no telling what the file should look like."); + this.xingFramePos = this.writer.getPos(); + this.mp3Writer.writeXingFrame(this.xingFrameData); + this.frameCount++; + } + if (this.writeXingHeader) { + assert(this.xingFrameData); + assert(this.xingFramePos !== null); + const audioDataEndPos = this.writer.getPos() - this.xingFramePos; + this.writer.seek(this.xingFramePos); + if (this.framePositions.length > 0) { + const toc = new Uint8Array(100); + for (let i = 0; i < 100; i++) { + const index = Math.floor(this.framePositions.length * (i / 100)); + toc[i] = 256 * ((this.framePositions[index] - this.xingFramePos) / audioDataEndPos); + } + this.xingFrameData.toc = toc; + } + this.xingFrameData.frameCount = this.frameCount; + this.xingFrameData.fileSize = audioDataEndPos; + if (this.format._options.onXingFrame) this.writer.startTrackingWrites(); + this.mp3Writer.writeXingFrame(this.xingFrameData); + if (this.format._options.onXingFrame) { + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onXingFrame(data, start); + } + } + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/ogg/ogg-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var PAGE_SIZE_TARGET = 8192; +var OggMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.trackDatas = []; + this.bosPagesWritten = false; + this.allTracksKnown = promiseWithResolvers(); + this.pageBytes = new Uint8Array(MAX_PAGE_SIZE); + this.pageView = new DataView(this.pageBytes.buffer); + this.format = format; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(true); + for (const track of this.output.tracks) { + assert(track.isAudioTrack()); + if (track.metadata.decoderConfig) this.getTrackData(track, { decoderConfig: track.metadata.decoderConfig }); + } + release(); + } + async getMimeType() { + await this.allTracksKnown.promise; + return buildOggMimeType({ codecStrings: this.trackDatas.map((x) => x.codecInfo.codec) }); + } + addEncodedVideoPacket() { + throw new Error("Video tracks are not supported."); + } + getTrackData(track, meta) { + const existingTrackData = this.trackDatas.find((td) => td.track === track); + if (existingTrackData) return existingTrackData; + let serialNumber; + do + serialNumber = Math.floor(2 ** 32 * Math.random()); + while (this.trackDatas.some((td) => td.serialNumber === serialNumber)); + assert(track.source._codec === "vorbis" || track.source._codec === "opus"); + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + const newTrackData = { + track, + serialNumber, + internalSampleRate: track.source._codec === "opus" ? OPUS_SAMPLE_RATE : meta.decoderConfig.sampleRate, + codecInfo: { + codec: track.source._codec, + vorbisInfo: null, + opusInfo: null + }, + vorbisLastBlocksize: null, + packetQueue: [], + startTimestampInSamples: null, + currentTimestampInSamples: 0, + audioPacketCount: 0, + pagesWritten: 0, + currentGranulePosition: 0, + currentLacingValues: [], + currentPageData: [], + currentPageSize: 27, + currentPageStartsWithFreshPacket: true, + currentPageStartTimestampInSamples: 0, + closed: false + }; + this.queueHeaderPackets(newTrackData, meta); + this.trackDatas.push(newTrackData); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + queueHeaderPackets(trackData, meta) { + assert(meta.decoderConfig); + if (trackData.track.source._codec === "vorbis") { + assert(meta.decoderConfig.description); + const bytes = toUint8Array(meta.decoderConfig.description); + if (bytes[0] !== 2) throw new TypeError("First byte of Vorbis decoder description must be 2."); + let pos = 1; + const readPacketLength = () => { + let length = 0; + while (true) { + const value = bytes[pos++]; + if (value === void 0) throw new TypeError("Vorbis decoder description is too short."); + length += value; + if (value < 255) return length; + } + }; + const identificationHeaderLength = readPacketLength(); + const commentHeaderLength = readPacketLength(); + if (bytes.length - pos <= 0) throw new TypeError("Vorbis decoder description is too short."); + const identificationHeader = bytes.subarray(pos, pos += identificationHeaderLength); + pos += commentHeaderLength; + const setupHeader = bytes.subarray(pos); + const commentHeaderHeader = new Uint8Array(7); + commentHeaderHeader[0] = 3; + commentHeaderHeader[1] = 118; + commentHeaderHeader[2] = 111; + commentHeaderHeader[3] = 114; + commentHeaderHeader[4] = 98; + commentHeaderHeader[5] = 105; + commentHeaderHeader[6] = 115; + const commentHeader = createVorbisComments(commentHeaderHeader, this.output._metadataTags, true); + trackData.packetQueue.push({ + data: identificationHeader, + timestampInSamples: 0, + durationInSamples: 0, + trimmedDurationInSamples: 0, + forcePageFlush: true + }, { + data: commentHeader, + timestampInSamples: 0, + durationInSamples: 0, + trimmedDurationInSamples: 0, + forcePageFlush: false + }, { + data: setupHeader, + timestampInSamples: 0, + durationInSamples: 0, + trimmedDurationInSamples: 0, + forcePageFlush: true + }); + const blockSizeByte = toDataView(identificationHeader).getUint8(28); + trackData.codecInfo.vorbisInfo = { + blocksizes: [1 << (blockSizeByte & 15), 1 << (blockSizeByte >> 4)], + modeBlockflags: parseModesFromVorbisSetupPacket(setupHeader).modeBlockflags + }; + } else if (trackData.track.source._codec === "opus") { + if (!meta.decoderConfig.description) throw new TypeError("For Ogg, Opus decoder description is required."); + const identificationHeader = toUint8Array(meta.decoderConfig.description); + const commentHeaderHeader = new Uint8Array(8); + const commentHeaderHeaderView = toDataView(commentHeaderHeader); + commentHeaderHeaderView.setUint32(0, 1332770163, false); + commentHeaderHeaderView.setUint32(4, 1415669619, false); + const commentHeader = createVorbisComments(commentHeaderHeader, this.output._metadataTags, true); + trackData.packetQueue.push({ + data: identificationHeader, + timestampInSamples: 0, + durationInSamples: 0, + trimmedDurationInSamples: 0, + forcePageFlush: true + }, { + data: commentHeader, + timestampInSamples: 0, + durationInSamples: 0, + trimmedDurationInSamples: 0, + forcePageFlush: true + }); + trackData.codecInfo.opusInfo = { preSkip: parseOpusIdentificationHeader(identificationHeader).preSkip }; + } + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getTrackData(track, meta); + this.validateTimestamp(trackData.track, packet.timestamp, packet.type === "key"); + if (trackData.startTimestampInSamples === null) { + const timestampInSamples = Math.round(packet.timestamp * trackData.internalSampleRate); + const preSkip = trackData.codecInfo.opusInfo?.preSkip ?? 0; + trackData.startTimestampInSamples = timestampInSamples > 0 ? timestampInSamples + preSkip : 0; + trackData.currentTimestampInSamples = trackData.startTimestampInSamples; + } + const currentTimestampInSamples = trackData.currentTimestampInSamples; + const { durationInSamples, vorbisBlockSize } = extractSampleMetadata(packet.data, trackData.codecInfo, trackData.vorbisLastBlocksize); + trackData.currentTimestampInSamples += durationInSamples; + trackData.vorbisLastBlocksize = vorbisBlockSize; + const trimmedDurationInSamples = packet.duration > 0 ? clamp(Math.round(packet.duration * trackData.internalSampleRate), 0, durationInSamples) : durationInSamples; + const forcePageFlush = trackData.startTimestampInSamples > 0 && trackData.audioPacketCount === 1; + trackData.audioPacketCount++; + trackData.packetQueue.push({ + data: packet.data, + timestampInSamples: currentTimestampInSamples, + durationInSamples, + trimmedDurationInSamples, + forcePageFlush + }); + await this.interleavePages(); + } finally { + release(); + } + } + addSubtitleCue() { + throw new Error("Subtitle tracks are not supported."); + } + allTracksAreKnown() { + for (const track of this.output.tracks) if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) return false; + return true; + } + async interleavePages(isFinalCall = false) { + if (!this.bosPagesWritten) { + if (!this.allTracksAreKnown() && !isFinalCall) return; + for (const trackData of this.trackDatas) while (trackData.packetQueue.length > 0) { + const packet = trackData.packetQueue.shift(); + this.writePacket(trackData, packet, false); + if (packet.forcePageFlush) break; + } + this.bosPagesWritten = true; + } + outer: while (true) { + let trackWithMinTimestamp = null; + let minTimestamp = Infinity; + for (const trackData of this.trackDatas) { + if (!isFinalCall && trackData.packetQueue.length <= 1 && !trackData.closed) break outer; + if (trackData.packetQueue.length > 0 && trackData.packetQueue[0].timestampInSamples < minTimestamp) { + trackWithMinTimestamp = trackData; + minTimestamp = trackData.packetQueue[0].timestampInSamples; + } + } + if (!trackWithMinTimestamp) break; + const packet = trackWithMinTimestamp.packetQueue.shift(); + const isFinalPacket = trackWithMinTimestamp.packetQueue.length === 0; + this.writePacket(trackWithMinTimestamp, packet, isFinalPacket); + } + if (!isFinalCall) await this.writer.flush(); + } + writePacket(trackData, packet, isFinalPacket) { + const packetEndTimestampInSamples = packet.timestampInSamples + (isFinalPacket ? packet.trimmedDurationInSamples : packet.durationInSamples); + if (trackData.currentLacingValues.length === 0) trackData.currentPageStartTimestampInSamples = packet.timestampInSamples; + if (this.format._options.maximumPageDuration !== void 0) { + const maxDurationInSamples = this.format._options.maximumPageDuration * trackData.internalSampleRate; + if (trackData.currentLacingValues.length > 0 && packetEndTimestampInSamples - trackData.currentPageStartTimestampInSamples > maxDurationInSamples) this.writePage(trackData, false); + } + let remainingLength = packet.data.length; + let dataStartOffset = 0; + let dataOffset = 0; + while (true) { + if (trackData.currentLacingValues.length === 0 && dataStartOffset > 0) trackData.currentPageStartsWithFreshPacket = false; + const segmentSize = Math.min(255, remainingLength); + trackData.currentLacingValues.push(segmentSize); + trackData.currentPageSize++; + dataOffset += segmentSize; + const segmentIsLastOfPacket = remainingLength < 255; + if (trackData.currentLacingValues.length === 255) { + const slice = packet.data.subarray(dataStartOffset, dataOffset); + dataStartOffset = dataOffset; + trackData.currentPageData.push(slice); + trackData.currentPageSize += slice.length; + this.writePage(trackData, isFinalPacket && segmentIsLastOfPacket); + if (segmentIsLastOfPacket) return; + } + if (segmentIsLastOfPacket) break; + remainingLength -= 255; + } + const slice = packet.data.subarray(dataStartOffset); + trackData.currentPageData.push(slice); + trackData.currentPageSize += slice.length; + trackData.currentGranulePosition = packetEndTimestampInSamples; + if (trackData.currentPageSize >= PAGE_SIZE_TARGET || packet.forcePageFlush) this.writePage(trackData, isFinalPacket); + } + writePage(trackData, isEos) { + this.pageView.setUint32(0, OGGS, true); + this.pageView.setUint8(4, 0); + let headerType = 0; + if (!trackData.currentPageStartsWithFreshPacket) headerType |= 1; + if (trackData.pagesWritten === 0) headerType |= 2; + if (isEos) headerType |= 4; + this.pageView.setUint8(5, headerType); + const granulePosition = trackData.currentLacingValues.every((x) => x === 255) ? -1 : trackData.currentGranulePosition; + setInt64(this.pageView, 6, granulePosition, true); + this.pageView.setUint32(14, trackData.serialNumber, true); + this.pageView.setUint32(18, trackData.pagesWritten, true); + this.pageView.setUint32(22, 0, true); + this.pageView.setUint8(26, trackData.currentLacingValues.length); + this.pageBytes.set(trackData.currentLacingValues, 27); + let pos = 27 + trackData.currentLacingValues.length; + for (const data of trackData.currentPageData) { + this.pageBytes.set(data, pos); + pos += data.length; + } + const slice = this.pageBytes.subarray(0, pos); + const crc = computeOggPageCrc(slice); + this.pageView.setUint32(22, crc, true); + trackData.pagesWritten++; + trackData.currentLacingValues.length = 0; + trackData.currentPageData.length = 0; + trackData.currentPageSize = 27; + trackData.currentPageStartsWithFreshPacket = true; + trackData.currentPageStartTimestampInSamples = trackData.currentGranulePosition; + if (this.format._options.onPage) this.writer.startTrackingWrites(); + this.writer.write(slice); + if (this.format._options.onPage) { + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onPage(data, start, trackData.track.source); + } + } + async onTrackClose(track) { + const release = await this.mutex.acquire(); + const trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) trackData.closed = true; + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + await this.interleavePages(); + release(); + } + async finalize() { + const release = await this.mutex.acquire(); + this.allTracksKnown.resolve(); + for (const trackData of this.trackDatas) trackData.closed = true; + await this.interleavePages(true); + for (const trackData of this.trackDatas) if (trackData.currentLacingValues.length > 0) this.writePage(trackData, true); + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/mpeg-ts/mpeg-ts-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var PAT_PID = 0; +var PMT_PID = 4096; +var FIRST_TRACK_PID = 256; +var VIDEO_STREAM_ID_BASE = 224; +var AUDIO_STREAM_ID_BASE = 192; +var AVC_AUD_NAL = new Uint8Array([9, 240]); +var HEVC_AUD_NAL = new Uint8Array([70, 1]); +var MpegTsMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.trackDatas = []; + this.tablesWritten = false; + this.continuityCounters = /* @__PURE__ */ new Map(); + this.packetBuffer = new Uint8Array(188); + this.packetView = toDataView(this.packetBuffer); + this.allTracksKnown = promiseWithResolvers(); + this.videoTrackIndex = 0; + this.audioTrackIndex = 0; + this.adaptationFieldBuffer = new Uint8Array(184); + this.payloadBuffer = new Uint8Array(184); + this.format = format; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(true); + release(); + } + async getMimeType() { + await this.allTracksKnown.promise; + return buildMpegTsMimeType(this.trackDatas.map((x) => x.codecString)); + } + getVideoTrackData(track, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateVideoChunkMetadata(meta, track.source._codec); + assert(meta?.decoderConfig); + const codec = track.source._codec; + assert(codec === "avc" || codec === "hevc"); + const streamType = codec === "avc" ? 27 : 36; + const newTrackData = { + track, + pid: FIRST_TRACK_PID + this.trackDatas.length, + streamType, + streamId: VIDEO_STREAM_ID_BASE + this.videoTrackIndex++, + codecString: meta.decoderConfig.codec, + timestampProcessingQueue: [], + packetQueue: [], + inputIsAnnexB: null, + inputIsAdts: null, + avcDecoderConfig: null, + hevcDecoderConfig: null, + adtsHeader: null, + adtsHeaderBitstream: null, + firstPacketWritten: false, + closed: false + }; + this.trackDatas.push(newTrackData); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + getAudioTrackData(track, meta) { + const existingTrackData = this.trackDatas.find((x) => x.track === track); + if (existingTrackData) return existingTrackData; + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta?.decoderConfig); + const codec = track.source._codec; + assert(codec === "aac" || codec === "mp3" || codec === "ac3" || codec === "eac3" || codec === "dts"); + let streamType; + let streamId; + switch (codec) { + case "aac": + streamType = 15; + streamId = AUDIO_STREAM_ID_BASE + this.audioTrackIndex++; + break; + case "mp3": + streamType = 3; + streamId = AUDIO_STREAM_ID_BASE + this.audioTrackIndex++; + break; + case "ac3": + streamType = 129; + streamId = 189; + break; + case "eac3": + streamType = 135; + streamId = 189; + break; + case "dts": + streamType = 130; + streamId = 189; + break; + } + const newTrackData = { + track, + pid: FIRST_TRACK_PID + this.trackDatas.length, + streamType, + streamId, + codecString: meta.decoderConfig.codec, + timestampProcessingQueue: [], + packetQueue: [], + inputIsAnnexB: null, + inputIsAdts: null, + avcDecoderConfig: null, + hevcDecoderConfig: null, + adtsHeader: null, + adtsHeaderBitstream: null, + firstPacketWritten: false, + closed: false + }; + this.trackDatas.push(newTrackData); + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + return newTrackData; + } + async addEncodedVideoPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, meta); + this.validateTimestamp(trackData.track, packet.timestamp, packet.type === "key"); + const preparedData = this.prepareVideoPacket(trackData, packet, meta); + if (packet.type === "key") await this.flushTimestampQueue(trackData); + trackData.timestampProcessingQueue.push({ + data: preparedData, + presentationTimestamp: packet.timestamp, + decodeTimestamp: null, + isKeyframe: packet.type === "key" + }); + } finally { + release(); + } + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, meta); + this.validateTimestamp(trackData.track, packet.timestamp, packet.type === "key"); + const preparedData = this.prepareAudioPacket(trackData, packet, meta); + if (packet.type === "key") await this.flushTimestampQueue(trackData); + trackData.timestampProcessingQueue.push({ + data: preparedData, + presentationTimestamp: packet.timestamp, + decodeTimestamp: null, + isKeyframe: packet.type === "key" + }); + } finally { + release(); + } + } + async addSubtitleCue() { + throw new Error("MPEG-TS does not support subtitles."); + } + prepareVideoPacket(trackData, packet, meta) { + const codec = trackData.track.source._codec; + if (trackData.inputIsAnnexB === null) { + const description = meta?.decoderConfig?.description; + trackData.inputIsAnnexB = !description; + if (!trackData.inputIsAnnexB) { + const bytes = toUint8Array(description); + if (codec === "avc") trackData.avcDecoderConfig = deserializeAvcDecoderConfigurationRecord(bytes); + else trackData.hevcDecoderConfig = deserializeHevcDecoderConfigurationRecord(bytes); + } + } + if (trackData.inputIsAnnexB) return this.prepareAnnexBVideoPacket(packet.data, codec); + else return this.prepareLengthPrefixedVideoPacket(trackData, packet, codec); + } + prepareAnnexBVideoPacket(data, codec) { + const nalUnits = []; + for (const loc of iterateNalUnitsInAnnexB(data)) { + const nalUnit = data.subarray(loc.offset, loc.offset + loc.length); + if (!(codec === "avc" ? extractNalUnitTypeForAvc(nalUnit[0]) === AvcNalUnitType.AUD : extractNalUnitTypeForHevc(nalUnit[0]) === HevcNalUnitType.AUD_NUT)) nalUnits.push(nalUnit); + } + const aud = codec === "avc" ? AVC_AUD_NAL : HEVC_AUD_NAL; + nalUnits.unshift(aud); + return concatNalUnitsInAnnexB(nalUnits); + } + prepareLengthPrefixedVideoPacket(trackData, packet, codec) { + const data = packet.data; + const lengthSize = codec === "avc" ? trackData.avcDecoderConfig.lengthSizeMinusOne + 1 : trackData.hevcDecoderConfig.lengthSizeMinusOne + 1; + const nalUnits = []; + for (const loc of iterateNalUnitsInLengthPrefixed(data, lengthSize)) { + const nalUnit = data.subarray(loc.offset, loc.offset + loc.length); + if (!(codec === "avc" ? extractNalUnitTypeForAvc(nalUnit[0]) === AvcNalUnitType.AUD : extractNalUnitTypeForHevc(nalUnit[0]) === HevcNalUnitType.AUD_NUT)) nalUnits.push(nalUnit); + } + if (packet.type === "key") if (codec === "avc") { + const config = trackData.avcDecoderConfig; + for (const pps of config.pictureParameterSets) nalUnits.unshift(pps); + for (const sps of config.sequenceParameterSets) nalUnits.unshift(sps); + } else { + const config = trackData.hevcDecoderConfig; + for (const arr of config.arrays) if (arr.nalUnitType === HevcNalUnitType.PPS_NUT) for (const nal of arr.nalUnits) nalUnits.unshift(nal); + for (const arr of config.arrays) if (arr.nalUnitType === HevcNalUnitType.SPS_NUT) for (const nal of arr.nalUnits) nalUnits.unshift(nal); + for (const arr of config.arrays) if (arr.nalUnitType === HevcNalUnitType.VPS_NUT) for (const nal of arr.nalUnits) nalUnits.unshift(nal); + } + const aud = codec === "avc" ? AVC_AUD_NAL : HEVC_AUD_NAL; + nalUnits.unshift(aud); + return concatNalUnitsInAnnexB(nalUnits); + } + prepareAudioPacket(trackData, packet, meta) { + const codec = trackData.track.source._codec; + if (codec === "mp3" || codec === "ac3" || codec === "eac3" || codec === "dts") return packet.data; + if (trackData.inputIsAdts === null) { + const description = meta?.decoderConfig?.description; + trackData.inputIsAdts = !description; + if (!trackData.inputIsAdts) { + const template = buildAdtsHeaderTemplate(parseAacAudioSpecificConfig(toUint8Array(description))); + trackData.adtsHeader = template.header; + trackData.adtsHeaderBitstream = template.bitstream; + } + } + if (trackData.inputIsAdts) return packet.data; + assert(trackData.adtsHeader); + assert(trackData.adtsHeaderBitstream); + const header = trackData.adtsHeader; + const frameLength = packet.data.byteLength + header.byteLength; + writeAdtsFrameLength(trackData.adtsHeaderBitstream, frameLength); + const result = new Uint8Array(frameLength); + result.set(header, 0); + result.set(packet.data, header.byteLength); + return result; + } + allTracksAreKnown() { + for (const track of this.output.tracks) if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) return false; + return true; + } + async flushTimestampQueue(trackData, alsoInterleave = true) { + if (trackData.timestampProcessingQueue.length === 0) return; + const sortedTimestamps = trackData.timestampProcessingQueue.map((packet) => packet.presentationTimestamp).sort((a, b) => a - b); + for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) { + const queuedPacket = trackData.timestampProcessingQueue[i]; + queuedPacket.decodeTimestamp = sortedTimestamps[i]; + trackData.packetQueue.push(queuedPacket); + } + trackData.timestampProcessingQueue.length = 0; + if (alsoInterleave) await this.interleavePackets(); + } + async interleavePackets(isFinalCall = false) { + if (!this.tablesWritten) { + if (!this.allTracksAreKnown() && !isFinalCall) return; + this.writeTables(); + } + outer: while (true) { + let trackWithMinTimestamp = null; + let minTimestamp = Infinity; + for (const trackData of this.trackDatas) { + if (!isFinalCall && trackData.packetQueue.length === 0 && !trackData.closed) break outer; + if (trackData.packetQueue.length > 0 && trackData.packetQueue[0].presentationTimestamp < minTimestamp) { + trackWithMinTimestamp = trackData; + minTimestamp = trackData.packetQueue[0].presentationTimestamp; + } + } + if (!trackWithMinTimestamp) break; + const queuedPacket = trackWithMinTimestamp.packetQueue.shift(); + this.writePesPacket(trackWithMinTimestamp, queuedPacket); + } + if (!isFinalCall) await this.writer.flush(); + } + writeTables() { + assert(!this.tablesWritten); + this.writePsiSection(PAT_PID, PAT_SECTION); + this.writePsiSection(PMT_PID, buildPmt(this.trackDatas)); + this.tablesWritten = true; + } + writePsiSection(pid, section) { + let offset = 0; + let isFirst = true; + while (offset < section.length) { + const availablePayload = 184 - (isFirst ? 1 : 0); + const remainingData = section.length - offset; + const chunkSize = Math.min(availablePayload, remainingData); + let payload; + if (isFirst) { + payload = this.payloadBuffer.subarray(0, 1 + chunkSize); + payload[0] = 0; + payload.set(section.subarray(offset, offset + chunkSize), 1); + } else payload = section.subarray(offset, offset + chunkSize); + this.writeTsPacket(pid, isFirst, null, payload); + offset += chunkSize; + isFirst = false; + } + } + writePesPacket(trackData, queuedPacket) { + const includeDts = trackData.track.type === "video"; + const headerDataLength = includeDts ? 10 : 5; + const pesHeaderBuffer = new Uint8Array(9 + headerDataLength); + const pesView = toDataView(pesHeaderBuffer); + const ptsDtsBitstream = new Bitstream(pesHeaderBuffer.subarray(9)); + setUint24(pesView, 0, 1, false); + pesHeaderBuffer[3] = trackData.streamId; + const pesPacketLength = trackData.track.type === "video" ? 0 : Math.min(8 + queuedPacket.data.length, 65535); + pesView.setUint16(4, pesPacketLength, false); + pesView.setUint8(6, 132); + pesView.setUint8(7, includeDts ? 192 : 128); + pesView.setUint8(8, headerDataLength); + const pts = modEuclid(Math.round(queuedPacket.presentationTimestamp * TIMESCALE), TIMESTAMP_MODULUS); + ptsDtsBitstream.pos = 0; + ptsDtsBitstream.writeBits(4, includeDts ? 3 : 2); + ptsDtsBitstream.writeBits(3, Math.floor(pts / 2 ** 30)); + ptsDtsBitstream.writeBits(1, 1); + ptsDtsBitstream.writeBits(15, Math.floor(pts / 2 ** 15) % 2 ** 15); + ptsDtsBitstream.writeBits(1, 1); + ptsDtsBitstream.writeBits(15, pts % 2 ** 15); + ptsDtsBitstream.writeBits(1, 1); + if (includeDts) { + assert(queuedPacket.decodeTimestamp !== null); + const dts = modEuclid(Math.round(queuedPacket.decodeTimestamp * TIMESCALE), TIMESTAMP_MODULUS); + ptsDtsBitstream.writeBits(4, 1); + ptsDtsBitstream.writeBits(3, Math.floor(dts / 2 ** 30)); + ptsDtsBitstream.writeBits(1, 1); + ptsDtsBitstream.writeBits(15, Math.floor(dts / 2 ** 15) % 2 ** 15); + ptsDtsBitstream.writeBits(1, 1); + ptsDtsBitstream.writeBits(15, dts % 2 ** 15); + ptsDtsBitstream.writeBits(1, 1); + } + const totalLength = pesHeaderBuffer.length + queuedPacket.data.length; + let offset = 0; + let isFirstTsPacket = true; + while (offset < totalLength) { + const pusi = isFirstTsPacket; + const remainingData = totalLength - offset; + const randomAccessIndicator = isFirstTsPacket && queuedPacket.isKeyframe; + const discontinuityIndicator = isFirstTsPacket && !trackData.firstPacketWritten; + const basePaddingNeeded = Math.max(0, 184 - remainingData); + let adaptationFieldSize; + if (randomAccessIndicator || discontinuityIndicator) adaptationFieldSize = Math.max(2, basePaddingNeeded); + else adaptationFieldSize = basePaddingNeeded; + let adaptationField = null; + if (adaptationFieldSize > 0) { + const buf = this.adaptationFieldBuffer; + if (adaptationFieldSize === 1) buf[0] = 0; + else { + buf[0] = adaptationFieldSize - 1; + buf[1] = Number(discontinuityIndicator) << 7 | Number(randomAccessIndicator) << 6; + buf.fill(255, 2, adaptationFieldSize); + } + adaptationField = buf.subarray(0, adaptationFieldSize); + } + const payloadSize = Math.min(184 - adaptationFieldSize, remainingData); + const payload = this.payloadBuffer.subarray(0, payloadSize); + let payloadOffset = 0; + if (offset < pesHeaderBuffer.length) { + const headerBytes = Math.min(pesHeaderBuffer.length - offset, payloadSize); + payload.set(pesHeaderBuffer.subarray(offset, offset + headerBytes), 0); + payloadOffset = headerBytes; + } + const dataStart = Math.max(0, offset - pesHeaderBuffer.length); + const dataEnd = dataStart + (payloadSize - payloadOffset); + if (payloadOffset < payloadSize) payload.set(queuedPacket.data.subarray(dataStart, dataEnd), payloadOffset); + this.writeTsPacket(trackData.pid, pusi, adaptationField, payload); + offset += payloadSize; + isFirstTsPacket = false; + } + trackData.firstPacketWritten = true; + } + writeTsPacket(pid, pusi, adaptationField, payload) { + const cc = this.continuityCounters.get(pid) ?? 0; + const hasPayload = payload.length > 0; + const adaptCtrl = adaptationField ? hasPayload ? 3 : 2 : hasPayload ? 1 : 0; + this.packetBuffer[0] = 71; + this.packetView.setUint16(1, (pusi ? 16384 : 0) | pid & 8191, false); + this.packetBuffer[3] = adaptCtrl << 4 | cc & 15; + if (hasPayload) this.continuityCounters.set(pid, cc + 1 & 15); + let offset = 4; + if (adaptationField) { + this.packetBuffer.set(adaptationField, offset); + offset += adaptationField.length; + } + this.packetBuffer.set(payload, offset); + offset += payload.length; + if (offset < 188) this.packetBuffer.fill(255, offset); + const startPos = this.writer.getPos(); + this.writer.write(this.packetBuffer); + if (this.format._options.onPacket) this.format._options.onPacket(this.packetBuffer.slice(), startPos); + } + async onTrackClose(track) { + const release = await this.mutex.acquire(); + const trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) { + trackData.closed = true; + await this.flushTimestampQueue(trackData, false); + } + if (this.allTracksAreKnown()) this.allTracksKnown.resolve(); + await this.interleavePackets(); + release(); + } + async finalize() { + const release = await this.mutex.acquire(); + this.allTracksKnown.resolve(); + for (const trackData of this.trackDatas) { + trackData.closed = true; + await this.flushTimestampQueue(trackData, false); + } + await this.interleavePackets(true); + release(); + } +}; +var MPEG_TS_CRC_POLYNOMIAL = 79764919; +var MPEG_TS_CRC_TABLE = new Uint32Array(256); +for (let n = 0; n < 256; n++) { + let crc = n << 24; + for (let k = 0; k < 8; k++) crc = crc & 2147483648 ? crc << 1 ^ MPEG_TS_CRC_POLYNOMIAL : crc << 1; + MPEG_TS_CRC_TABLE[n] = crc >>> 0 & 4294967295; +} +var computeMpegTsCrc32 = (data) => { + let crc = 4294967295; + for (let i = 0; i < data.length; i++) { + const byte = data[i]; + crc = (crc << 8 ^ MPEG_TS_CRC_TABLE[crc >>> 24 ^ byte]) >>> 0; + } + return crc; +}; +var PAT_SECTION = new Uint8Array(16); +{ + const view = toDataView(PAT_SECTION); + PAT_SECTION[0] = 0; + view.setUint16(1, 45069, false); + view.setUint16(3, 1, false); + PAT_SECTION[5] = 193; + PAT_SECTION[6] = 0; + PAT_SECTION[7] = 0; + view.setUint16(8, 1, false); + view.setUint16(10, 61440, false); + view.setUint32(12, computeMpegTsCrc32(PAT_SECTION.subarray(0, 12)), false); +} +var buildPmt = (trackDatas) => { + let totalEsBytes = 0; + for (const trackData of trackDatas) { + totalEsBytes += 5; + if (trackData.streamType === 129) totalEsBytes += AC3_REGISTRATION_DESCRIPTOR.length; + else if (trackData.streamType === 135) totalEsBytes += EAC3_REGISTRATION_DESCRIPTOR.length; + } + const sectionLength = 9 + totalEsBytes + 4; + const section = new Uint8Array(3 + sectionLength - 4); + const view = toDataView(section); + section[0] = 2; + view.setUint16(1, 45056 | sectionLength & 4095, false); + view.setUint16(3, 1, false); + section[5] = 193; + section[6] = 0; + section[7] = 0; + view.setUint16(8, 65535, false); + view.setUint16(10, 61440, false); + let offset = 12; + for (const trackData of trackDatas) { + section[offset++] = trackData.streamType; + view.setUint16(offset, 57344 | trackData.pid & 8191, false); + offset += 2; + if (trackData.streamType === 129) { + view.setUint16(offset, 61440 | AC3_REGISTRATION_DESCRIPTOR.length, false); + offset += 2; + section.set(AC3_REGISTRATION_DESCRIPTOR, offset); + offset += AC3_REGISTRATION_DESCRIPTOR.length; + } else if (trackData.streamType === 135) { + view.setUint16(offset, 61440 | EAC3_REGISTRATION_DESCRIPTOR.length, false); + offset += 2; + section.set(EAC3_REGISTRATION_DESCRIPTOR, offset); + offset += EAC3_REGISTRATION_DESCRIPTOR.length; + } else { + view.setUint16(offset, 61440, false); + offset += 2; + } + } + const crc = computeMpegTsCrc32(section); + const result = new Uint8Array(section.length + 4); + result.set(section, 0); + toDataView(result).setUint32(section.length, crc, false); + return result; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/wave/riff-writer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var RiffWriter = class { + constructor(writer) { + this.writer = writer; + this.helper = new Uint8Array(8); + this.helperView = new DataView(this.helper.buffer); + } + writeU16(value) { + this.helperView.setUint16(0, value, true); + this.writer.write(this.helper.subarray(0, 2)); + } + writeU32(value) { + this.helperView.setUint32(0, value, true); + this.writer.write(this.helper.subarray(0, 4)); + } + writeU64(value) { + this.helperView.setUint32(0, value, true); + this.helperView.setUint32(4, Math.floor(value / 2 ** 32), true); + this.writer.write(this.helper); + } + writeAscii(text) { + this.writer.write(textEncoder.encode(text)); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/wave/wave-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var WaveMuxer = class extends Muxer { + constructor(output, format) { + super(output); + this.headerWritten = false; + this.dataSize = 0; + this.sampleRate = null; + this.sampleCount = 0; + this.riffSizePos = null; + this.dataSizePos = null; + this.ds64RiffSizePos = null; + this.ds64DataSizePos = null; + this.ds64SampleCountPos = null; + this.format = format; + this.isRf64 = !!format._options.large; + } + async start() { + const release = await this.mutex.acquire(); + this.writer = await this.output._getRootWriter(false); + this.riffWriter = new RiffWriter(this.writer); + const track = this.output.tracks[0]; + assert(track?.isAudioTrack()); + if (track.metadata.decoderConfig) { + validateAudioChunkMetadata({ decoderConfig: track.metadata.decoderConfig }, track.source._codec); + this.writeHeader(track, track.metadata.decoderConfig); + this.sampleRate = track.metadata.decoderConfig.sampleRate; + this.headerWritten = true; + } + release(); + } + async getMimeType() { + return "audio/wav"; + } + async addEncodedVideoPacket() { + throw new Error("WAVE does not support video."); + } + async addEncodedAudioPacket(track, packet, meta) { + const release = await this.mutex.acquire(); + try { + if (!this.headerWritten) { + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta.decoderConfig); + this.writeHeader(track, meta.decoderConfig); + this.sampleRate = meta.decoderConfig.sampleRate; + this.headerWritten = true; + } + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + if (!this.isRf64 && this.writer.getPos() + packet.data.byteLength >= 2 ** 32) throw new Error("Adding more audio data would exceed the maximum RIFF size of 4 GiB. To write larger files, use RF64 by setting `large: true` in the WavOutputFormatOptions."); + this.writer.write(packet.data); + this.dataSize += packet.data.byteLength; + this.sampleCount += Math.round(packet.duration * this.sampleRate); + await this.writer.flush(); + } finally { + release(); + } + } + async addSubtitleCue() { + throw new Error("WAVE does not support subtitles."); + } + writeHeader(track, config) { + if (this.format._options.onHeader) this.writer.startTrackingWrites(); + let format; + const codec = track.source._codec; + const pcmInfo = parsePcmCodec(codec); + if (pcmInfo.dataType === "ulaw") format = WaveFormat.MULAW; + else if (pcmInfo.dataType === "alaw") format = WaveFormat.ALAW; + else if (pcmInfo.dataType === "float") format = WaveFormat.IEEE_FLOAT; + else format = WaveFormat.PCM; + const channels = config.numberOfChannels; + const sampleRate = config.sampleRate; + const blockSize = pcmInfo.sampleSize * channels; + this.riffWriter.writeAscii(this.isRf64 ? "RF64" : "RIFF"); + if (this.isRf64) this.riffWriter.writeU32(4294967295); + else { + this.riffSizePos = this.writer.getPos(); + this.riffWriter.writeU32(0); + } + this.riffWriter.writeAscii("WAVE"); + if (this.isRf64) { + this.riffWriter.writeAscii("ds64"); + this.riffWriter.writeU32(28); + this.ds64RiffSizePos = this.writer.getPos(); + this.riffWriter.writeU64(0); + this.ds64DataSizePos = this.writer.getPos(); + this.riffWriter.writeU64(0); + this.ds64SampleCountPos = this.writer.getPos(); + this.riffWriter.writeU64(0); + this.riffWriter.writeU32(0); + } + this.riffWriter.writeAscii("fmt "); + this.riffWriter.writeU32(16); + this.riffWriter.writeU16(format); + this.riffWriter.writeU16(channels); + this.riffWriter.writeU32(sampleRate); + this.riffWriter.writeU32(sampleRate * blockSize); + this.riffWriter.writeU16(blockSize); + this.riffWriter.writeU16(8 * pcmInfo.sampleSize); + if (!metadataTagsAreEmpty(this.output._metadataTags)) { + const metadataFormat = this.format._options.metadataFormat ?? "info"; + if (metadataFormat === "info") this.writeInfoChunk(this.output._metadataTags); + else if (metadataFormat === "id3") this.writeId3Chunk(this.output._metadataTags); + else assertNever(metadataFormat); + } + this.riffWriter.writeAscii("data"); + if (this.isRf64) this.riffWriter.writeU32(4294967295); + else { + this.dataSizePos = this.writer.getPos(); + this.riffWriter.writeU32(0); + } + if (this.format._options.onHeader) { + const { data, start } = this.writer.stopTrackingWrites(); + this.format._options.onHeader(data, start); + } + } + writeInfoChunk(metadata) { + const startPos = this.writer.getPos(); + this.riffWriter.writeAscii("LIST"); + this.riffWriter.writeU32(0); + this.riffWriter.writeAscii("INFO"); + const writtenTags = /* @__PURE__ */ new Set(); + const writeInfoTag = (tag, value) => { + if (!isIso88591Compatible(value)) { + Logging._warn(`Didn't write tag '${tag}' because '${value}' is not ISO 8859-1-compatible.`); + return; + } + const size = value.length + 1; + const bytes = new Uint8Array(size); + for (let i = 0; i < value.length; i++) bytes[i] = value.charCodeAt(i); + this.riffWriter.writeAscii(tag); + this.riffWriter.writeU32(size); + this.writer.write(bytes); + if (size & 1) this.writer.write(new Uint8Array(1)); + writtenTags.add(tag); + }; + for (const { key, value } of keyValueIterator(metadata)) switch (key) { + case "title": + writeInfoTag("INAM", value); + writtenTags.add("INAM"); + break; + case "artist": + writeInfoTag("IART", value); + writtenTags.add("IART"); + break; + case "album": + writeInfoTag("IPRD", value); + writtenTags.add("IPRD"); + break; + case "trackNumber": + writeInfoTag("ITRK", metadata.tracksTotal !== void 0 ? `${value}/${metadata.tracksTotal}` : value.toString()); + writtenTags.add("ITRK"); + break; + case "genre": + writeInfoTag("IGNR", value); + writtenTags.add("IGNR"); + break; + case "date": + writeInfoTag("ICRD", value.toISOString().slice(0, 10)); + writtenTags.add("ICRD"); + break; + case "comment": + writeInfoTag("ICMT", value); + writtenTags.add("ICMT"); + break; + case "albumArtist": + case "discNumber": + case "tracksTotal": + case "discsTotal": + case "beatsPerMinute": + case "description": + case "lyrics": + case "images": break; + case "raw": break; + default: assertNever(key); + } + if (metadata.raw) for (const key in metadata.raw) { + const value = metadata.raw[key]; + if (value == null || key.length !== 4 || writtenTags.has(key)) continue; + if (typeof value === "string") writeInfoTag(key, value); + } + const endPos = this.writer.getPos(); + const chunkSize = endPos - startPos - 8; + this.writer.seek(startPos + 4); + this.riffWriter.writeU32(chunkSize); + this.writer.seek(endPos); + if (chunkSize & 1) this.writer.write(new Uint8Array(1)); + } + writeId3Chunk(metadata) { + const startPos = this.writer.getPos(); + this.riffWriter.writeAscii("ID3 "); + this.riffWriter.writeU32(0); + const id3TagSize = new Id3V2Writer(this.writer).writeId3V2Tag(metadata); + const endPos = this.writer.getPos(); + this.writer.seek(startPos + 4); + this.riffWriter.writeU32(id3TagSize); + this.writer.seek(endPos); + if (id3TagSize & 1) this.writer.write(new Uint8Array(1)); + } + async finalize() { + const release = await this.mutex.acquire(); + if (!this.headerWritten) throw new Error("Cannot finalize an empty WAVE file: no packets were added and the track specified no decoderConfig in its metadata, so there's no telling what the file should look like."); + const endPos = this.writer.getPos(); + if (this.isRf64) { + assert(this.ds64RiffSizePos !== null); + this.writer.seek(this.ds64RiffSizePos); + this.riffWriter.writeU64(endPos - 8); + assert(this.ds64DataSizePos !== null); + this.writer.seek(this.ds64DataSizePos); + this.riffWriter.writeU64(this.dataSize); + assert(this.ds64SampleCountPos !== null); + this.writer.seek(this.ds64SampleCountPos); + this.riffWriter.writeU64(this.sampleCount); + } else { + assert(this.riffSizePos !== null); + this.writer.seek(this.riffSizePos); + this.riffWriter.writeU32(endPos - 8); + assert(this.dataSizePos !== null); + this.writer.seek(this.dataSizePos); + this.riffWriter.writeU32(this.dataSize); + } + release(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/resample.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Utility class to handle audio resampling, handling both sample rate resampling as well as channel up/downmixing. +* The advantage over doing this manually rather than using OfflineAudioContext to do it for us is the artifact-free +* handling of putting multiple resampled audio samples back to back, which produces flaky results using +* OfflineAudioContext. +*/ +var AudioResampler = class { + constructor(options) { + this.sourceSampleRate = null; + this.sourceNumberOfChannels = null; + this.startTime = null; + /** Start frame of current buffer */ + this.bufferStartFrame = 0; + /** The highest index written to in the current buffer */ + this.maxWrittenFrame = null; + this.targetSampleRate = options.targetSampleRate; + this.targetNumberOfChannels = options.targetNumberOfChannels; + this.onSample = options.onSample; + this.bufferSizeInFrames = Math.floor(this.targetSampleRate * 5); + this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels; + } + /** + * Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match. + */ + doChannelMixerSetup() { + assert(this.sourceNumberOfChannels !== null); + const sourceNum = this.sourceNumberOfChannels; + const targetNum = this.targetNumberOfChannels; + if (sourceNum === 1 && targetNum === 2) this.channelMixer = (sourceData, sourceFrameIndex) => { + return sourceData[sourceFrameIndex * sourceNum]; + }; + else if (sourceNum === 1 && targetNum === 4) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + return sourceData[sourceFrameIndex * sourceNum] * +(targetChannelIndex < 2); + }; + else if (sourceNum === 1 && targetNum === 6) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + return sourceData[sourceFrameIndex * sourceNum] * +(targetChannelIndex === 2); + }; + else if (sourceNum === 2 && targetNum === 1) this.channelMixer = (sourceData, sourceFrameIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + return .5 * (sourceData[baseIdx] + sourceData[baseIdx + 1]); + }; + else if (sourceNum === 2 && targetNum === 4) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + return targetChannelIndex < 2 ? sourceData[sourceFrameIndex * sourceNum + targetChannelIndex] : 0; + }; + else if (sourceNum === 2 && targetNum === 6) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + return targetChannelIndex < 2 ? sourceData[sourceFrameIndex * sourceNum + targetChannelIndex] : 0; + }; + else if (sourceNum === 4 && targetNum === 1) this.channelMixer = (sourceData, sourceFrameIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + return .25 * (sourceData[baseIdx] + sourceData[baseIdx + 1] + sourceData[baseIdx + 2] + sourceData[baseIdx + 3]); + }; + else if (sourceNum === 4 && targetNum === 2) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + return .5 * (sourceData[baseIdx + targetChannelIndex] + sourceData[baseIdx + targetChannelIndex + 2]); + }; + else if (sourceNum === 4 && targetNum === 6) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + if (targetChannelIndex < 2) return sourceData[baseIdx + targetChannelIndex]; + if (targetChannelIndex === 2 || targetChannelIndex === 3) return 0; + return sourceData[baseIdx + targetChannelIndex - 2]; + }; + else if (sourceNum === 6 && targetNum === 1) this.channelMixer = (sourceData, sourceFrameIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + return Math.SQRT1_2 * (sourceData[baseIdx] + sourceData[baseIdx + 1]) + sourceData[baseIdx + 2] + .5 * (sourceData[baseIdx + 4] + sourceData[baseIdx + 5]); + }; + else if (sourceNum === 6 && targetNum === 2) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + return sourceData[baseIdx + targetChannelIndex] + Math.SQRT1_2 * (sourceData[baseIdx + 2] + sourceData[baseIdx + targetChannelIndex + 4]); + }; + else if (sourceNum === 6 && targetNum === 4) this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + const baseIdx = sourceFrameIndex * sourceNum; + if (targetChannelIndex < 2) return sourceData[baseIdx + targetChannelIndex] + Math.SQRT1_2 * sourceData[baseIdx + 2]; + return sourceData[baseIdx + targetChannelIndex + 2]; + }; + else this.channelMixer = (sourceData, sourceFrameIndex, targetChannelIndex) => { + return targetChannelIndex < sourceNum ? sourceData[sourceFrameIndex * sourceNum + targetChannelIndex] : 0; + }; + } + ensureTempBufferSize(requiredSamples) { + let length = this.tempSourceBuffer.length; + while (length < requiredSamples) length *= 2; + if (length !== this.tempSourceBuffer.length) { + const newBuffer = new Float32Array(length); + newBuffer.set(this.tempSourceBuffer); + this.tempSourceBuffer = newBuffer; + } + } + async add(audioSample) { + if (this.sourceSampleRate === null) { + this.sourceSampleRate = audioSample.sampleRate; + this.sourceNumberOfChannels = audioSample.numberOfChannels; + this.startTime = audioSample.timestamp; + const overflowFrames = Math.ceil(this.targetSampleRate / this.sourceSampleRate); + this.bufferCapacityInFrames = this.bufferSizeInFrames + overflowFrames; + this.outputBuffer = new Float32Array(this.bufferCapacityInFrames * this.targetNumberOfChannels); + this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels); + this.doChannelMixerSetup(); + } + assert(this.startTime !== null); + const requiredSamples = audioSample.numberOfFrames * audioSample.numberOfChannels; + this.ensureTempBufferSize(requiredSamples); + const sourceDataSize = audioSample.allocationSize({ + planeIndex: 0, + format: "f32" + }); + const sourceView = new Float32Array(this.tempSourceBuffer.buffer, 0, sourceDataSize / 4); + audioSample.copyTo(sourceView, { + planeIndex: 0, + format: "f32" + }); + const inputStartTime = audioSample.timestamp - this.startTime; + const inputEndTime = inputStartTime + audioSample.duration; + const outputStartFrame = Math.floor((inputStartTime - 1 / this.sourceSampleRate) * this.targetSampleRate) + 1; + const outputEndFrame = Math.ceil(inputEndTime * this.targetSampleRate); + for (let outputFrame = outputStartFrame; outputFrame < outputEndFrame; outputFrame++) { + if (outputFrame < this.bufferStartFrame) continue; + while (outputFrame >= this.bufferStartFrame + this.bufferCapacityInFrames) { + await this.finalizeCurrentBuffer(); + this.bufferStartFrame += this.bufferSizeInFrames; + } + const bufferFrameIndex = outputFrame - this.bufferStartFrame; + assert(bufferFrameIndex < this.bufferCapacityInFrames); + const sourcePosition = (outputFrame / this.targetSampleRate - inputStartTime) * this.sourceSampleRate; + const sourceLowerFrame = Math.floor(sourcePosition); + const sourceUpperFrame = Math.ceil(sourcePosition); + const fraction = sourcePosition - sourceLowerFrame; + for (let targetChannel = 0; targetChannel < this.targetNumberOfChannels; targetChannel++) { + let lowerSample = 0; + let upperSample = 0; + if (sourceLowerFrame >= 0 && sourceLowerFrame < audioSample.numberOfFrames) lowerSample = this.channelMixer(sourceView, sourceLowerFrame, targetChannel); + if (sourceUpperFrame >= 0 && sourceUpperFrame < audioSample.numberOfFrames) upperSample = this.channelMixer(sourceView, sourceUpperFrame, targetChannel); + const outputSample = lowerSample + fraction * (upperSample - lowerSample); + const outputIndex = bufferFrameIndex * this.targetNumberOfChannels + targetChannel; + this.outputBuffer[outputIndex] += outputSample; + } + if (this.maxWrittenFrame === null) this.maxWrittenFrame = bufferFrameIndex; + else this.maxWrittenFrame = Math.max(this.maxWrittenFrame, bufferFrameIndex); + } + } + async finalizeCurrentBuffer() { + if (this.maxWrittenFrame === null) return; + assert(this.startTime !== null); + const samplesWritten = Math.min(this.maxWrittenFrame + 1, this.bufferSizeInFrames) * this.targetNumberOfChannels; + const outputData = new Float32Array(samplesWritten); + outputData.set(this.outputBuffer.subarray(0, samplesWritten)); + const audioSample = new AudioSample({ + format: "f32", + sampleRate: this.targetSampleRate, + numberOfChannels: this.targetNumberOfChannels, + timestamp: this.startTime + this.bufferStartFrame / this.targetSampleRate, + data: outputData + }); + await this.onSample(audioSample); + this.outputBuffer.copyWithin(0, this.bufferSizeInSamples); + this.outputBuffer.fill(0, this.outputBuffer.length - this.bufferSizeInSamples); + this.maxWrittenFrame = this.maxWrittenFrame >= this.bufferSizeInFrames ? this.maxWrittenFrame - this.bufferSizeInFrames : null; + } + async finalize() { + while (this.maxWrittenFrame !== null) { + await this.finalizeCurrentBuffer(); + this.bufferStartFrame += this.bufferSizeInFrames; + } + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/media-source.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var __addDisposableResource$1 = function(env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); + var dispose, inner; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + if (async) inner = dispose; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + if (inner) dispose = function() { + try { + inner.call(this); + } catch (e) { + return Promise.reject(e); + } + }; + env.stack.push({ + value, + dispose, + async + }); + } else if (async) env.stack.push({ async: true }); + return value; +}; +var __disposeResources$1 = (function(SuppressedError) { + return function(env) { + function fail(e) { + env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + var r, s = 0; + function next() { + while (r = env.stack.pop()) try { + if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); + if (r.dispose) { + var result = r.dispose.call(r.value); + if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { + fail(e); + return next(); + }); + } else s |= 1; + } catch (e) { + fail(e); + } + if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); + if (env.hasError) throw env.error; + } + return next(); + }; +})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}); +/** +* Base class for media sources. Media sources are used to add media samples to an output file. +* @group Media sources +* @public +*/ +var MediaSource = class { + constructor() { + /** @internal */ + this._connectedTrack = null; + /** @internal */ + this._closingPromise = null; + /** @internal */ + this._closed = false; + /** + * Set when the encoder is configured with a bitrate. + * @internal + */ + this._nominalBitrate = null; + } + /** @internal */ + _ensureValidAdd() { + if (!this._connectedTrack) throw new Error("Source is not connected to an output track."); + if (this._connectedTrack.output.state === "canceled") throw new Error("Output has been canceled."); + if (this._connectedTrack.output.state === "finalizing" || this._connectedTrack.output.state === "finalized") throw new Error("Output has been finalized."); + if (this._connectedTrack.output.state === "pending") throw new Error("Output has not started."); + if (this._closed) throw new Error("Source is closed."); + } + /** @internal */ + async _start() {} + /** @internal */ + async _flushAndClose(forceClose) {} + /** + * Closes this source. This prevents future samples from being added and signals to the output file that no further + * samples will come in for this track. Calling `.close()` is optional but recommended after adding the + * last sample - for improved performance and reduced memory usage. + */ + close() { + if (this._closingPromise) return; + const connectedTrack = this._connectedTrack; + if (!connectedTrack) throw new Error("Cannot call close without connecting the source to an output track."); + if (connectedTrack.output.state === "pending") throw new Error("Cannot call close before output has been started."); + this._closingPromise = (async () => { + await this._flushAndClose(false); + this._closed = true; + if (connectedTrack.output.state === "finalizing" || connectedTrack.output.state === "finalized") return; + connectedTrack.output._muxer.onTrackClose(connectedTrack); + })(); + } + /** @internal */ + async _flushOrWaitForOngoingClose(forceClose) { + return this._closingPromise ??= (async () => { + await this._flushAndClose(forceClose); + this._closed = true; + })(); + } +}; +/** +* Base class for video sources - sources for video tracks. +* @group Media sources +* @public +*/ +var VideoSource = class extends MediaSource { + /** Internal constructor. */ + constructor(codec) { + super(); + if (!VIDEO_CODECS.includes(codec)) throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); + this._codec = codec; + } +}; +var maybeEnsureIsKeyPacket = (track, packet) => { + if (track.metadata.hasOnlyKeyPackets && packet.type !== "key") throw new Error("Cannot add non-key packets to a hasOnlyKeyPackets video track."); +}; +/** +* The most basic video source; can be used to directly pipe encoded packets into the output file. +* @group Media sources +* @public +*/ +var EncodedVideoPacketSource = class extends VideoSource { + /** Creates a new {@link EncodedVideoPacketSource} whose packets are encoded using `codec`. */ + constructor(codec) { + super(codec); + } + /** + * Adds an encoded packet to the output video track. Packets must be added in *decode order*, while a packet's + * timestamp must be its *presentation timestamp*. B-frames are handled automatically. + * + * @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid + * decoder config. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(packet, meta) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + if (packet.isMetadataOnly) throw new TypeError("Metadata-only packets cannot be added."); + if (meta !== void 0 && (!meta || typeof meta !== "object")) throw new TypeError("meta, when provided, must be an object."); + this._ensureValidAdd(); + maybeEnsureIsKeyPacket(this._connectedTrack, packet); + return this._connectedTrack.output._muxer.addEncodedVideoPacket(this._connectedTrack, packet, meta); + } +}; +var VideoEncoderWrapper = class { + setError(error) { + if (!this.errorSet) { + this.error = error; + this.errorSet = true; + } + } + constructor(source, encodingConfig) { + this.source = source; + this.encodingConfig = encodingConfig; + this.ensureEncoderPromise = null; + this.encoderInitialized = false; + this.encoder = null; + this.encoderConfig = null; + this.encodersReclaimed = false; + this.muxer = null; + this.lastMultipleOfKeyFrameInterval = -1; + this.emittedEncoderPackets = 0; + this.codedWidth = null; + this.codedHeight = null; + this.outputWidth = null; + this.outputHeight = null; + this.frameRateLastSample = null; + this.frameRateLastTimestamp = null; + this.frameRateLastEndTimestamp = null; + this.preciseTimings = []; + this.customEncoder = null; + this.customEncoderCallSerializer = new CallSerializer(); + this.customEncoderQueueSize = 0; + this.defaultEncodeOptions = {}; + this.alphaEncoder = null; + this.splitter = null; + this.splitterCreationFailed = false; + this.alphaFrameQueue = []; + /** + * Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context. + * However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught. + * So, we keep track of the encoder error and throw it as soon as we get the chance. + */ + this.error = null; + this.errorSet = false; + this.lastMuxerPromise = Promise.resolve(); + this.closed = false; + } + async add(videoSample, shouldClose, encodeOptions) { + const originalSample = videoSample; + try { + this.checkForEncoderError(); + this.source._ensureValidAdd(); + const config = this.encodingConfig; + const sizeChangeBehavior = config.sizeChangeBehavior ?? "deny"; + let isSizeChange = false; + if (this.codedWidth !== null && this.codedHeight !== null) { + if (videoSample.codedWidth !== this.codedWidth || videoSample.codedHeight !== this.codedHeight) { + isSizeChange = true; + if (sizeChangeBehavior === "deny") throw new Error(`Video sample size must remain constant. Expected ${this.codedWidth}x${this.codedHeight}, got ${videoSample.codedWidth}x${videoSample.codedHeight}. To allow the sample size to change over time, set \`sizeChangeBehavior\` to a value other than 'deny' in the encoding options.`); + } + } else { + this.codedWidth = videoSample.codedWidth; + this.codedHeight = videoSample.codedHeight; + } + if (config.transform?.width !== void 0 || config.transform?.height !== void 0 || config.transform?.rotate !== void 0 || config.transform?.flip !== void 0 || config.transform?.crop !== void 0 || config.transform?.force === true || isSizeChange && sizeChangeBehavior !== "passThrough") { + let targetWidth = config.transform?.width; + let targetHeight = config.transform?.height; + let appliedFit = config.transform?.fit ?? "fill"; + if (isSizeChange && sizeChangeBehavior !== "passThrough") { + assert(this.outputWidth); + assert(this.outputHeight); + assert(sizeChangeBehavior !== "deny"); + targetWidth = this.outputWidth; + targetHeight = this.outputHeight; + appliedFit = sizeChangeBehavior; + } + const transformed = await videoSample.transform({ + width: targetWidth, + height: targetHeight, + roundDimensionsTo: 2, + crop: config.transform?.crop, + rotate: config.transform?.rotate, + flip: config.transform?.flip, + fit: appliedFit, + alpha: config.alpha + }); + if (this.outputWidth === null || this.outputHeight === null) { + this.outputWidth = transformed.displayWidth; + this.outputHeight = transformed.displayHeight; + } + if (shouldClose) videoSample.close(); + videoSample = transformed; + shouldClose = true; + } else if (this.outputWidth === null || this.outputHeight === null) { + this.outputWidth = videoSample.codedWidth; + this.outputHeight = videoSample.codedHeight; + } + const frameRate = config.transform?.frameRate; + if (frameRate !== void 0) { + const originalEndTimestamp = videoSample.timestamp + videoSample.duration; + const alignedTimestamp = floorToDivisor(videoSample.timestamp, frameRate); + if (this.frameRateLastSample !== null) if (alignedTimestamp <= this.frameRateLastTimestamp) { + this.frameRateLastSample.close(); + this.frameRateLastSample = videoSample.clone(); + this.frameRateLastEndTimestamp = originalEndTimestamp; + return; + } else await this.padFrameRate(alignedTimestamp, encodeOptions); + if (videoSample === originalSample) { + videoSample = videoSample.clone(); + shouldClose = true; + } + videoSample.setTimestamp(alignedTimestamp); + videoSample.setDuration(1 / frameRate); + this.frameRateLastSample?.close(); + this.frameRateLastSample = videoSample.clone(); + this.frameRateLastTimestamp = alignedTimestamp; + this.frameRateLastEndTimestamp = originalEndTimestamp; + } + await this.processAndEncode(videoSample, encodeOptions); + } finally { + if (shouldClose) videoSample.close(); + } + } + /** + * Runs the process function (if any) and encodes the resulting samples. + */ + async processAndEncode(videoSample, encodeOptions) { + const config = this.encodingConfig; + let samplesToEncode; + if (config.transform?.process) { + let processed = config.transform.process(videoSample); + if (isThenable(processed)) processed = await processed; + if (processed === null) return; + if (!Array.isArray(processed)) processed = [processed]; + const mappedSamples = []; + try { + for (const x of processed) if (x instanceof VideoSample) mappedSamples.push(x); + else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) mappedSamples.push(new VideoSample(x)); + else mappedSamples.push(new VideoSample(x, { + timestamp: videoSample.timestamp, + duration: videoSample.duration + })); + } catch (error) { + for (const sample of mappedSamples) if (sample !== videoSample) sample.close(); + for (const x of processed) if (x instanceof VideoSample && x !== videoSample) x.close(); + else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) x.close(); + throw error; + } + samplesToEncode = mappedSamples; + } else samplesToEncode = [videoSample]; + try { + for (const sampleToEncode of samplesToEncode) { + if (!this.encoderInitialized) { + if (!this.ensureEncoderPromise) this.ensureEncoder(sampleToEncode); + if (!this.encoderInitialized) await this.ensureEncoderPromise; + } + assert(this.encoderInitialized); + if (this.closed) break; + if (this.encodersReclaimed) this.recreateWebCodecsEncoders(); + const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2; + const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval); + const mergedEncodeOptions = { + ...this.defaultEncodeOptions, + ...sampleToEncode.encodeOptions, + ...encodeOptions + }; + const finalEncodeOptions = { + ...mergedEncodeOptions, + keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval + }; + this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; + this.encodingConfig.onEncodedSample?.(sampleToEncode); + if (this.customEncoder) { + this.customEncoderQueueSize++; + const clonedSample = sampleToEncode.clone(); + const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).catch((error) => this.setError(error)).finally(() => { + this.customEncoderQueueSize--; + clonedSample.close(); + }); + if (this.customEncoderQueueSize >= 4) await promise; + } else { + assert(this.encoder); + const videoFrame = sampleToEncode.toVideoFrame(); + const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, videoFrame.timestamp, (x) => x.microsecondTimestamp); + const existingEntry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null; + if (existingEntry && existingEntry.microsecondTimestamp === videoFrame.timestamp) { + if (existingEntry.timestamp !== sampleToEncode.timestamp) existingEntry.timestampIsValid = false; + if (existingEntry.duration !== sampleToEncode.duration) existingEntry.durationIsValid = false; + } else { + this.preciseTimings.splice(preciseTimingIndex + 1, 0, { + microsecondTimestamp: videoFrame.timestamp, + timestamp: sampleToEncode.timestamp, + duration: sampleToEncode.duration, + timestampIsValid: true, + durationIsValid: true + }); + if (this.preciseTimings.length > 128) this.preciseTimings.shift(); + } + if (!this.alphaEncoder) try { + this.encoder.encode(videoFrame, finalEncodeOptions); + } finally { + videoFrame.close(); + } + else if (!!videoFrame.format && !videoFrame.format.includes("A") || this.splitterCreationFailed) { + this.alphaFrameQueue.push(null); + try { + this.encoder.encode(videoFrame, finalEncodeOptions); + } finally { + videoFrame.close(); + } + } else { + if (!this.splitter) this.splitter = new ColorAlphaSplitter(); + const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame); + this.alphaFrameQueue.push(alphaFrame); + try { + this.encoder.encode(colorFrame, finalEncodeOptions); + } finally { + colorFrame.close(); + } + } + if (this.encoder.encodeQueueSize >= 4) await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + } + await this.lastMuxerPromise; + } + } finally { + for (const sample of samplesToEncode) if (sample !== videoSample) sample.close(); + } + } + /** Repeats the last frame rate sample to fill the gap up to the given timestamp. */ + async padFrameRate(until, encodeOptions) { + const frameRate = this.encodingConfig.transform.frameRate; + assert(this.frameRateLastSample); + const frameDifference = Math.round((until - this.frameRateLastTimestamp) * frameRate); + for (let i = 1; i < frameDifference; i++) { + const env_1 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const sample = __addDisposableResource$1(env_1, this.frameRateLastSample.clone(), false); + sample.setTimestamp(this.frameRateLastTimestamp + i / frameRate); + sample.setDuration(1 / frameRate); + await this.processAndEncode(sample, encodeOptions); + } catch (e_1) { + env_1.error = e_1; + env_1.hasError = true; + } finally { + __disposeResources$1(env_1); + } + } + } + ensureEncoder(videoSample) { + this.ensureEncoderPromise = (async () => { + const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate); + assert(quality !== void 0); + const candidates = buildVideoEncoderConfigs({ + ...this.encodingConfig, + quality, + width: videoSample.codedWidth, + height: videoSample.codedHeight, + squarePixelWidth: videoSample.squarePixelWidth, + squarePixelHeight: videoSample.squarePixelHeight, + framerate: this.source._connectedTrack?.metadata.frameRate + }); + let selected = null; + let MatchingCustomEncoder; + for (const candidate of candidates) { + const candidateConfig = candidate.config; + this.encodingConfig.onEncoderConfig?.(candidateConfig); + MatchingCustomEncoder = customVideoEncoders.find((x) => x.supports(this.encodingConfig.codec, candidateConfig)); + if (MatchingCustomEncoder) { + selected = candidate; + break; + } + if (typeof VideoEncoder === "undefined") continue; + candidateConfig.alpha = "discard"; + if (this.encodingConfig.alpha === "keep") candidateConfig.latencyMode = "quality"; + if ((candidateConfig.width % 2 === 1 || candidateConfig.height % 2 === 1) && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) throw new Error(`The dimensions ${candidateConfig.width}x${candidateConfig.height} are not supported for codec '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to round your dimensions to the nearest even number.`); + try { + if ((await VideoEncoder.isConfigSupported(candidateConfig)).supported) { + selected = candidate; + break; + } + } catch {} + } + if (!selected) { + if (typeof VideoEncoder === "undefined") throw new Error(missingWebCodecsClassMessage("VideoEncoder")); + const firstConfig = candidates[0].config; + const rateControls = candidates.map(({ config, quantizer }) => quantizer !== null ? `quantizer ${quantizer}` : `${config.bitrate} bps`); + throw new Error(`This specific encoder configuration (${firstConfig.codec}, ${rateControls.join(" / ")}, ${firstConfig.width}x${firstConfig.height}, hardware acceleration: ${firstConfig.hardwareAcceleration ?? "no-preference"}) is not supported in this environment. Consider using another codec or changing your video parameters.`); + } + const encoderConfig = selected.config; + if (selected.quantizer !== null) this.defaultEncodeOptions = buildQuantizerEncodeOptions(this.encodingConfig.codec, selected.quantizer); + else this.source._nominalBitrate = encoderConfig.bitrate ?? null; + if (MatchingCustomEncoder) { + this.customEncoder = new MatchingCustomEncoder(); + this.customEncoder.codec = this.encodingConfig.codec; + this.customEncoder.config = encoderConfig; + this.customEncoder.onPacket = (packet, meta) => { + if (!(packet instanceof EncodedPacket)) throw new TypeError("The first argument passed to onPacket must be an EncodedPacket."); + if (meta !== void 0 && (!meta || typeof meta !== "object")) throw new TypeError("The second argument passed to onPacket must be an object or undefined."); + maybeEnsureIsKeyPacket(this.source._connectedTrack, packet); + this.encodingConfig.onEncodedPacket?.(packet, meta); + this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => { + this.setError(error); + }); + }; + this.customEncoder.onError = (error) => { + this.setError(error); + }; + await this.customEncoder.init(); + } else { + this.encoderConfig = encoderConfig; + this.createWebCodecsEncoders(); + } + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; + this.encoderInitialized = true; + })(); + } + createWebCodecsEncoders() { + const encoderConfig = this.encoderConfig; + assert(encoderConfig); + /** Queue of color chunks waiting for their alpha counterpart. */ + const colorChunkQueue = []; + /** Each value is the number of encoded alpha chunks at which a null alpha chunk should be added. */ + const nullAlphaChunkQueue = []; + let encodedAlphaChunkCount = 0; + let alphaEncoderQueue = 0; + const handleError = (error, stack) => { + if (isCodecReclaimedError(error)) { + this.encodersReclaimed = true; + return; + } + error.stack = stack; + this.setError(error); + }; + const stack = (/* @__PURE__ */ new Error("Encoding error")).stack; + this.encoder = new VideoEncoder({ + output: (chunk, meta) => { + if (!this.alphaEncoder) { + this.addPacket(chunk, null, meta); + return; + } + const alphaFrame = this.alphaFrameQueue.shift(); + assert(alphaFrame !== void 0); + if (alphaFrame) { + this.alphaEncoder.encode(alphaFrame, { + ...this.defaultEncodeOptions, + keyFrame: chunk.type === "key" + }); + alphaEncoderQueue++; + alphaFrame.close(); + colorChunkQueue.push({ + chunk, + meta + }); + } else if (alphaEncoderQueue === 0) this.addPacket(chunk, null, meta); + else { + nullAlphaChunkQueue.push(encodedAlphaChunkCount + alphaEncoderQueue); + colorChunkQueue.push({ + chunk, + meta + }); + } + }, + error: (error) => handleError(error, stack) + }); + this.encoder.configure(encoderConfig); + if (this.encodingConfig.alpha === "keep") { + const stack = (/* @__PURE__ */ new Error("Encoding error")).stack; + this.alphaEncoder = new VideoEncoder({ + output: (chunk, meta) => { + alphaEncoderQueue--; + const colorChunk = colorChunkQueue.shift(); + assert(colorChunk !== void 0); + this.addPacket(colorChunk.chunk, chunk, colorChunk.meta); + encodedAlphaChunkCount++; + while (nullAlphaChunkQueue.length > 0 && nullAlphaChunkQueue[0] === encodedAlphaChunkCount) { + nullAlphaChunkQueue.shift(); + const colorChunk = colorChunkQueue.shift(); + assert(colorChunk !== void 0); + this.addPacket(colorChunk.chunk, null, colorChunk.meta); + } + }, + error: (error) => handleError(error, stack) + }); + this.alphaEncoder.configure(encoderConfig); + } + } + addPacket(colorChunk, alphaChunk, meta) { + const sideData = {}; + if (alphaChunk) { + const alphaData = new Uint8Array(alphaChunk.byteLength); + alphaChunk.copyTo(alphaData); + sideData.alpha = alphaData; + } + let packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData); + const encoderConfig = this.encoderConfig; + assert(encoderConfig); + if (meta?.decoderConfig) meta.decoderConfig.codec = extractVideoCodecString({ + width: encoderConfig.width, + height: encoderConfig.height, + codec: this.encodingConfig.codec, + codecDescription: meta.decoderConfig.description ? toUint8Array(meta.decoderConfig.description) : null, + colorSpace: meta.decoderConfig.colorSpace ?? null, + avcType: encoderConfig.codec.includes("avc1.") ? 1 : encoderConfig.codec.includes("avc3.") ? 3 : null, + avcCodecInfo: this.encodingConfig.codec === "avc" && !meta.decoderConfig.description ? extractAvcDecoderConfigurationRecord(packet.data) : null, + hevcCodecInfo: this.encodingConfig.codec === "hevc" && !meta.decoderConfig.description ? extractHevcDecoderConfigurationRecord(packet.data) : null, + vp9CodecInfo: this.encodingConfig.codec === "vp9" ? extractVp9CodecInfoFromPacket(packet.data) : null, + av1CodecInfo: this.encodingConfig.codec === "av1" ? extractAv1CodecInfoFromPacket(packet.data) : null, + proresFormat: null + }); + const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, colorChunk.timestamp, (x) => x.microsecondTimestamp); + const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null; + let actualType = null; + if (this.emittedEncoderPackets === 0 && packet.type === "delta" && meta?.decoderConfig) actualType = determineVideoPacketType(this.encodingConfig.codec, meta.decoderConfig, packet.data); + if (entry && entry.microsecondTimestamp === colorChunk.timestamp || actualType !== null) packet = packet.clone({ + timestamp: entry?.timestampIsValid ? entry.timestamp : void 0, + duration: entry?.durationIsValid ? entry.duration : void 0, + type: actualType ?? void 0 + }); + maybeEnsureIsKeyPacket(this.source._connectedTrack, packet); + this.encodingConfig.onEncodedPacket?.(packet, meta); + this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => { + this.setError(error); + }); + this.emittedEncoderPackets++; + } + recreateWebCodecsEncoders() { + assert(this.encoder); + if (this.encoder.state !== "closed") this.encoder.close(); + if (this.alphaEncoder && this.alphaEncoder.state !== "closed") this.alphaEncoder.close(); + this.alphaFrameQueue.forEach((x) => x?.close()); + this.alphaFrameQueue.length = 0; + this.encodersReclaimed = false; + this.lastMultipleOfKeyFrameInterval = -1; + this.createWebCodecsEncoders(); + } + async flushAndClose(forceClose) { + try { + if (!forceClose) { + this.checkForEncoderError(); + if (this.frameRateLastSample) { + const frameRate = this.encodingConfig.transform.frameRate; + const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate); + await this.padFrameRate(alignedEnd); + } + } + this.closed = true; + if (!forceClose) { + if (this.customEncoder) this.customEncoderCallSerializer.call(() => this.customEncoder.flush()); + else if (this.encoder && !this.encodersReclaimed) { + await this.encoder.flush(); + await this.alphaEncoder?.flush(); + await wait(25); + } + } + } finally { + this.closed = true; + this.frameRateLastSample?.close(); + this.frameRateLastSample = null; + if (this.customEncoder) await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error)); + else if (this.encoder) { + if (this.encoder.state !== "closed") this.encoder.close(); + if (this.alphaEncoder && this.alphaEncoder.state !== "closed") this.alphaEncoder.close(); + this.alphaFrameQueue.forEach((x) => x?.close()); + this.alphaFrameQueue.length = 0; + this.splitter?.close(); + } + } + if (!forceClose) this.checkForEncoderError(); + } + getQueueSize() { + if (this.customEncoder) return this.customEncoderQueueSize; + else return this.encoder?.encodeQueueSize ?? 0; + } + checkForEncoderError() { + if (this.errorSet) throw this.error; + } +}; +var splitterWorkerUrl = null; +/** Utility class for splitting a composite frame into separate color and alpha parts on the CPU in a worker. */ +var ColorAlphaSplitter = class { + constructor() { + this.worker = null; + this.pendingRequests = /* @__PURE__ */ new Map(); + this.nextRequestId = 0; + } + split(sourceFrame) { + if (!this.worker) { + if (!splitterWorkerUrl) { + const blob = new Blob([`(${colorAlphaSplitterWorkerCode.toString()})()`], { type: "application/javascript" }); + splitterWorkerUrl = URL.createObjectURL(blob); + } + this.worker = new Worker(splitterWorkerUrl); + this.worker.addEventListener("message", (event) => { + const data = event.data; + const pending = this.pendingRequests.get(data.id); + if (!pending) return; + this.pendingRequests.delete(data.id); + if ("error" in data) pending.reject(new Error(data.error)); + else pending.resolve({ + colorFrame: data.colorFrame, + alphaFrame: data.alphaFrame + }); + }); + this.worker.addEventListener("error", (event) => { + const error = new Error(event.message || "Color/alpha splitter worker error."); + for (const pending of this.pendingRequests.values()) pending.reject(error); + this.pendingRequests.clear(); + }); + } + const id = this.nextRequestId++; + const pending = promiseWithResolvers(); + this.pendingRequests.set(id, pending); + this.worker.postMessage({ + id, + sourceFrame + }, { transfer: [sourceFrame] }); + return pending.promise; + } + close() { + this.worker?.terminate(); + this.worker = null; + const error = /* @__PURE__ */ new Error("Color/alpha splitter closed."); + for (const pending of this.pendingRequests.values()) pending.reject(error); + this.pendingRequests.clear(); + } +}; +var colorAlphaSplitterWorkerCode = () => { + let cpuSourceBuffer = null; + let chain = Promise.resolve(); + self.addEventListener("message", (event) => { + const { id, sourceFrame } = event.data; + chain = chain.then(async () => { + try { + const { colorFrame, alphaFrame } = await split(sourceFrame); + self.postMessage({ + id, + colorFrame, + alphaFrame + }, { transfer: [colorFrame, alphaFrame] }); + } catch (error) { + self.postMessage({ + id, + error: error.message + }); + } finally { + sourceFrame.close(); + } + }); + }); + const split = async (sourceFrame) => { + const format = sourceFrame.format; + if (!format) throw new Error("CPU color/alpha splitting requires a known VideoFrame format."); + const sourceSize = sourceFrame.allocationSize(); + if (!cpuSourceBuffer || cpuSourceBuffer.byteLength !== sourceSize) cpuSourceBuffer = new Uint8Array(sourceSize); + await sourceFrame.copyTo(cpuSourceBuffer); + if (format === "RGBA" || format === "BGRA") return splitInterleavedRgba(cpuSourceBuffer, format, sourceFrame); + else if (format === "I420A" || format === "I420AP10" || format === "I420AP12" || format === "I422A" || format === "I422AP10" || format === "I422AP12" || format === "I444A" || format === "I444AP10" || format === "I444AP12") return splitPlanarYuvA(cpuSourceBuffer, format, sourceFrame); + throw new Error(`CPU color/alpha splitting does not support format '${format}'.`); + }; + const splitInterleavedRgba = (source, format, sourceFrame) => { + const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth; + const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight; + const pixelCount = width * height; + const alphaSize = pixelCount + Math.ceil(width / 2) * Math.ceil(height / 2) * 2; + const alphaBuffer = new Uint8Array(alphaSize); + for (let i = 0, j = 3; i < pixelCount; i++, j += 4) alphaBuffer[i] = source[j]; + alphaBuffer.fill(128, pixelCount); + const colorFrame = new VideoFrame(source, { + format: format === "RGBA" ? "RGBX" : "BGRX", + codedWidth: width, + codedHeight: height, + timestamp: sourceFrame.timestamp, + duration: sourceFrame.duration ?? void 0 + }); + const alphaInit = { + format: "I420", + codedWidth: width, + codedHeight: height, + timestamp: sourceFrame.timestamp, + duration: sourceFrame.duration ?? void 0, + colorSpace: { + fullRange: true, + matrix: "bt709", + primaries: "bt709", + transfer: "bt709" + }, + transfer: [alphaBuffer.buffer] + }; + return { + colorFrame, + alphaFrame: new VideoFrame(alphaBuffer, alphaInit) + }; + }; + const splitPlanarYuvA = (source, format, sourceFrame) => { + const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth; + const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight; + const is10 = format.includes("P10"); + const is12 = format.includes("P12"); + const bytesPerSample = is10 || is12 ? 2 : 1; + let chromaW; + let chromaH; + if (format.startsWith("I420")) { + chromaW = Math.ceil(width / 2); + chromaH = Math.ceil(height / 2); + } else if (format.startsWith("I422")) { + chromaW = Math.ceil(width / 2); + chromaH = height; + } else { + chromaW = width; + chromaH = height; + } + const ySamples = width * height; + const uvSamples = chromaW * chromaH; + const yBytes = ySamples * bytesPerSample; + const uvBytes = uvSamples * bytesPerSample; + const aBytes = ySamples * bytesPerSample; + const colorBytes = yBytes + uvBytes * 2; + const colorFormat = format.replace("A", ""); + const alphaUvSamples = Math.ceil(width / 2) * Math.ceil(height / 2); + const alphaSize = aBytes + 2 * (alphaUvSamples * bytesPerSample); + const alphaBuffer = new Uint8Array(alphaSize); + const aPlaneStart = colorBytes; + alphaBuffer.set(source.subarray(aPlaneStart, aPlaneStart + aBytes), 0); + const uvOffset = aBytes; + const neutralChroma = is10 ? 512 : is12 ? 2048 : 128; + if (bytesPerSample === 1) alphaBuffer.fill(neutralChroma, uvOffset); + else new Uint16Array(alphaBuffer.buffer, uvOffset, 2 * alphaUvSamples).fill(neutralChroma); + const alphaFormat = is10 ? "I420P10" : is12 ? "I420P12" : "I420"; + const colorFrame = new VideoFrame(source.subarray(0, colorBytes), { + format: colorFormat, + codedWidth: width, + codedHeight: height, + timestamp: sourceFrame.timestamp, + duration: sourceFrame.duration ?? void 0 + }); + const alphaInit = { + format: alphaFormat, + codedWidth: width, + codedHeight: height, + timestamp: sourceFrame.timestamp, + duration: sourceFrame.duration ?? void 0, + colorSpace: { + fullRange: true, + matrix: "bt709", + primaries: "bt709", + transfer: "bt709" + }, + transfer: [alphaBuffer.buffer] + }; + return { + colorFrame, + alphaFrame: new VideoFrame(alphaBuffer, alphaInit) + }; + }; +}; +/** +* This source can be used to add raw, unencoded video samples (frames) to an output video track. These frames will +* automatically be encoded and then piped into the output. +* @group Media sources +* @public +*/ +var VideoSampleSource = class extends VideoSource { + /** + * Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified + * {@link VideoEncodingConfig}. + */ + constructor(encodingConfig) { + validateVideoEncodingConfig(encodingConfig); + super(encodingConfig.codec); + this._encoder = new VideoEncoderWrapper(this, encodingConfig); + } + /** + * Encodes a video sample (frame) and then adds it to the output. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(videoSample, encodeOptions) { + if (!(videoSample instanceof VideoSample)) throw new TypeError("videoSample must be a VideoSample."); + return this._encoder.add(videoSample, false, encodeOptions); + } + /** @internal */ + _flushAndClose(forceClose) { + return this._encoder.flushAndClose(forceClose); + } +}; +/** +* This source can be used to add video frames to the output track from a fixed canvas element. Since canvases are often +* used for rendering, this source provides a convenient wrapper around {@link VideoSampleSource}. +* @group Media sources +* @public +*/ +var CanvasSource = class extends VideoSource { + /** + * Creates a new {@link CanvasSource} from a canvas element or `OffscreenCanvas` whose samples are encoded + * according to the specified {@link VideoEncodingConfig}. + */ + constructor(canvas, encodingConfig) { + if (!(typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && canvas instanceof OffscreenCanvas)) throw new TypeError("canvas must be an HTMLCanvasElement or OffscreenCanvas."); + validateVideoEncodingConfig(encodingConfig); + super(encodingConfig.codec); + this._encoder = new VideoEncoderWrapper(this, encodingConfig); + this._canvas = canvas; + } + /** + * Captures the current canvas state as a video sample (frame), encodes it and adds it to the output. + * + * @param timestamp - The timestamp of the sample, in seconds. + * @param duration - The duration of the sample, in seconds. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(timestamp, duration = 0, encodeOptions) { + if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be a finite number."); + if (!Number.isFinite(duration) || duration < 0) throw new TypeError("duration must be a non-negative number."); + const sample = new VideoSample(this._canvas, { + timestamp, + duration + }); + return this._encoder.add(sample, true, encodeOptions); + } + /** @internal */ + _flushAndClose(forceClose) { + return this._encoder.flushAndClose(forceClose); + } +}; +/** +* Video source that encodes the frames of a +* [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes them into the +* output. This is useful for capturing live or real-time data such as webcams or screen captures. Frames will +* automatically start being captured once the connected {@link Output} is started, and will keep being captured until +* the {@link Output} is finalized or this source is closed. +* @group Media sources +* @public +*/ +var MediaStreamVideoTrackSource = class extends VideoSource { + /** A promise that rejects upon any error within this source. This promise never resolves. */ + get errorPromise() { + this._errorPromiseAccessed = true; + return this._promiseWithResolvers.promise; + } + /** Whether this source is currently paused as a result of calling `.pause()`. */ + get paused() { + return this._paused; + } + /** + * Creates a new {@link MediaStreamVideoTrackSource} from a + * [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack), which will pull + * video samples from the stream in real time and encode them according to {@link VideoEncodingConfig}. + */ + constructor(track, encodingConfig, options = {}) { + if (!(track instanceof MediaStreamTrack) || track.kind !== "video") throw new TypeError("track must be a video MediaStreamTrack."); + validateVideoEncodingConfig(encodingConfig); + if (typeof options !== "object" || !options) throw new TypeError("options must be an object."); + if (options.frameRate != null && (typeof options.frameRate !== "number" || options.frameRate <= 0)) throw new TypeError("options.frameRate, when provided, must be either a positive number or null."); + if (options.timestampBase !== void 0 && options.timestampBase !== "synced-zero" && options.timestampBase !== "zero" && options.timestampBase !== "unix") throw new TypeError("options.timestampBase, when provided, must be one of 'synced-zero', 'zero', or 'unix'."); + encodingConfig = { + ...encodingConfig, + latencyMode: "realtime" + }; + super(encodingConfig.codec); + /** @internal */ + this._abortController = null; + /** @internal */ + this._workerTrackId = null; + /** @internal */ + this._workerListener = null; + /** @internal */ + this._promiseWithResolvers = promiseWithResolvers(); + /** @internal */ + this._errorPromiseAccessed = false; + /** @internal */ + this._paused = false; + /** @internal */ + this._lastVideoFrame = null; + /** @internal */ + this._timerHandle = null; + /** @internal */ + this._videoElement = null; + this._options = options; + this._encoder = new VideoEncoderWrapper(this, encodingConfig); + this._track = track; + } + /** @internal */ + async _start() { + if (!this._errorPromiseAccessed) Logging._warn("Make sure not to ignore the `errorPromise` field on MediaStreamVideoTrackSource, so that any internal errors get bubbled up properly."); + const frameRate = this._options.frameRate !== void 0 ? this._options.frameRate : this._track.getSettings().frameRate ?? null; + this._abortController = new AbortController(); + let firstVideoFrameTimestamp = null; + let lastFrameTime = null; + let frameCount = 0; + let errored = false; + let lastSampleTimestamp = null; + let timestampOffset = 0; + const tick = () => { + assert(frameRate !== null); + if (!this._lastVideoFrame) return; + assert(lastFrameTime !== null); + assert(firstVideoFrameTimestamp !== null); + const now = performance.now(); + while (now - lastFrameTime > 1e3 / frameRate) { + lastFrameTime += 1e3 / frameRate; + const timestamp = firstVideoFrameTimestamp + frameCount / frameRate; + addVideoFrame(new VideoFrame(this._videoElement ?? this._lastVideoFrame, { + timestamp: 1e6 * timestamp, + duration: 1e6 / frameRate + }), now); + } + }; + if (frameRate !== null) this._timerHandle = setIntervalUnthrottled(tick, 4); + const onVideoFrame = (videoFrame) => { + if (frameRate === null) addVideoFrame(videoFrame); + else { + const now = performance.now(); + if (!this._lastVideoFrame) { + addVideoFrame(videoFrame.clone(), now); + lastFrameTime = now; + this._lastVideoFrame = videoFrame; + } else { + tick(); + this._lastVideoFrame?.close(); + this._lastVideoFrame = videoFrame; + } + } + }; + const addVideoFrame = (videoFrame, now = performance.now()) => { + if (errored) { + videoFrame.close(); + return; + } + frameCount++; + const currentTimestamp = videoFrame.timestamp / 1e6; + if (this._paused) { + if (firstVideoFrameTimestamp !== null) { + if (lastSampleTimestamp !== null && this._options.timestampBase !== "unix") { + const timeDelta = currentTimestamp - lastSampleTimestamp; + timestampOffset -= timeDelta; + } + lastSampleTimestamp = currentTimestamp; + } + videoFrame.close(); + return; + } + if (firstVideoFrameTimestamp === null) { + firstVideoFrameTimestamp = currentTimestamp; + let target; + const timestampBase = this._options.timestampBase ?? "synced-zero"; + if (timestampBase === "unix") target = Date.now() / 1e3; + else if (timestampBase === "zero") target = 0; + else { + const output = this._connectedTrack.output; + if (output._firstMediaStreamTimestamp === null) { + output._firstMediaStreamTimestamp = now / 1e3; + target = 0; + } else target = now / 1e3 - output._firstMediaStreamTimestamp; + } + timestampOffset = target - firstVideoFrameTimestamp; + } + lastSampleTimestamp = currentTimestamp; + if (this._encoder.getQueueSize() >= 8) { + videoFrame.close(); + return; + } + const sample = new VideoSample(videoFrame, { timestamp: currentTimestamp + timestampOffset }); + this._encoder.add(sample, true).catch((error) => { + errored = true; + this._abortController?.abort(); + this._promiseWithResolvers.reject(error); + if (this._workerTrackId !== null) sendMessageToMediaStreamTrackProcessorWorker({ + type: "stopTrack", + trackId: this._workerTrackId + }); + }); + }; + if (typeof MediaStreamTrackProcessor !== "undefined") { + const processor = new MediaStreamTrackProcessor({ track: this._track }); + const consumer = new WritableStream({ write: onVideoFrame }); + processor.readable.pipeTo(consumer, { signal: this._abortController.signal }).catch((error) => { + if (error instanceof DOMException && error.name === "AbortError") return; + this._promiseWithResolvers.reject(error); + }); + } else if (await mediaStreamTrackProcessorIsSupportedInWorker()) { + this._workerTrackId = nextMediaStreamTrackProcessorWorkerId++; + sendMessageToMediaStreamTrackProcessorWorker({ + type: "videoTrack", + trackId: this._workerTrackId, + track: this._track + }); + this._workerListener = (event) => { + const message = event.data; + if (message.type === "videoFrame" && message.trackId === this._workerTrackId) onVideoFrame(message.videoFrame); + else if (message.type === "error" && message.trackId === this._workerTrackId) this._promiseWithResolvers.reject(message.error); + }; + mediaStreamTrackProcessorWorker.addEventListener("message", this._workerListener); + } else if (frameRate !== null) { + const video = document.createElement("video"); + video.style.position = "fixed"; + video.style.left = "-10000px"; + video.style.top = "-10000px"; + video.style.width = "1px"; + video.style.height = "1px"; + video.style.opacity = "0"; + video.style.pointerEvents = "none"; + video.muted = true; + video.srcObject = new MediaStream([this._track]); + document.body.appendChild(video); + this._videoElement = video; + video.addEventListener("loadeddata", () => { + if (errored || !this._videoElement) return; + const frame = new VideoFrame(video, { timestamp: 1e3 * performance.now() }); + onVideoFrame(frame); + frame.close(); + }, { once: true }); + video.play().catch((error) => { + errored = true; + this._promiseWithResolvers.reject(error); + }); + } else throw new Error("When no explicit frame rate is set, MediaStreamTrackProcessor is required; but it's not available in this environment."); + } + /** + * Pauses the capture of video frames - any video frames emitted by the underlying media stream will be ignored + * while paused. This does *not* close the underlying `MediaStreamVideoTrack`, it just ignores its output. + */ + pause() { + this._paused = true; + } + /** Resumes the capture of video frames after being paused. */ + resume() { + this._paused = false; + } + /** @internal */ + async _flushAndClose(forceClose) { + try { + if (this._abortController) { + this._abortController.abort(); + this._abortController = null; + } + if (this._timerHandle) clearIntervalUnthrottled(this._timerHandle); + this._lastVideoFrame?.close(); + if (this._videoElement) { + this._videoElement.srcObject = null; + this._videoElement.remove(); + this._videoElement = null; + } + if (this._workerTrackId !== null) { + assert(this._workerListener); + sendMessageToMediaStreamTrackProcessorWorker({ + type: "stopTrack", + trackId: this._workerTrackId + }); + await new Promise((resolve) => { + const listener = (event) => { + const message = event.data; + if (message.type === "trackStopped" && message.trackId === this._workerTrackId) { + assert(this._workerListener); + mediaStreamTrackProcessorWorker.removeEventListener("message", this._workerListener); + mediaStreamTrackProcessorWorker.removeEventListener("message", listener); + resolve(); + } + }; + mediaStreamTrackProcessorWorker.addEventListener("message", listener); + }); + } + } finally { + await this._encoder.flushAndClose(forceClose); + } + } +}; +/** +* Base class for audio sources - sources for audio tracks. +* @group Media sources +* @public +*/ +var AudioSource = class extends MediaSource { + /** Internal constructor. */ + constructor(codec) { + super(); + if (!AUDIO_CODECS.includes(codec)) throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); + this._codec = codec; + } +}; +/** +* The most basic audio source; can be used to directly pipe encoded packets into the output file. +* @group Media sources +* @public +*/ +var EncodedAudioPacketSource = class extends AudioSource { + /** Creates a new {@link EncodedAudioPacketSource} whose packets are encoded using `codec`. */ + constructor(codec) { + super(codec); + } + /** + * Adds an encoded packet to the output audio track. Packets must be added in *decode order*. + * + * @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid + * decoder config. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(packet, meta) { + if (!(packet instanceof EncodedPacket)) throw new TypeError("packet must be an EncodedPacket."); + if (packet.isMetadataOnly) throw new TypeError("Metadata-only packets cannot be added."); + if (meta !== void 0 && (!meta || typeof meta !== "object")) throw new TypeError("meta, when provided, must be an object."); + this._ensureValidAdd(); + return this._connectedTrack.output._muxer.addEncodedAudioPacket(this._connectedTrack, packet, meta); + } +}; +var AudioEncoderWrapper = class { + setError(error) { + if (!this.errorSet) { + this.error = error; + this.errorSet = true; + } + } + constructor(source, encodingConfig) { + this.source = source; + this.encodingConfig = encodingConfig; + this.ensureEncoderPromise = null; + this.encoderInitialized = false; + this.encoder = null; + this.muxer = null; + this.lastNumberOfChannels = null; + this.lastSampleRate = null; + this.isPcmEncoder = false; + this.outputSampleSize = null; + this.writeOutputValue = null; + this.customEncoder = null; + this.customEncoderCallSerializer = new CallSerializer(); + this.customEncoderQueueSize = 0; + this.lastEndSampleIndex = null; + this.resampler = null; + /** + * Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context. + * However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught. + * So, we keep track of the encoder error and throw it as soon as we get the chance. + */ + this.error = null; + this.errorSet = false; + this.lastMuxerPromise = Promise.resolve(); + this.closed = false; + } + async add(audioSample, shouldClose) { + try { + this.checkForEncoderError(); + this.source._ensureValidAdd(); + if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) { + if (audioSample.numberOfChannels !== this.lastNumberOfChannels || audioSample.sampleRate !== this.lastSampleRate) throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${audioSample.numberOfChannels} channels at ${audioSample.sampleRate} Hz.`); + } else { + this.lastNumberOfChannels = audioSample.numberOfChannels; + this.lastSampleRate = audioSample.sampleRate; + } + const config = this.encodingConfig; + if (config.transform?.numberOfChannels !== void 0 || config.transform?.sampleRate !== void 0) { + if (!this.resampler) this.resampler = new AudioResampler({ + targetNumberOfChannels: config.transform.numberOfChannels ?? audioSample.numberOfChannels, + targetSampleRate: config.transform.sampleRate ?? audioSample.sampleRate, + onSample: async (sample) => { + await this.processAndEncode(sample, true); + } + }); + await this.resampler.add(audioSample); + } else await this.processAndEncode(audioSample, shouldClose); + } finally { + if (shouldClose) audioSample.close(); + } + } + /** + * Runs the process function (if any) and encodes the resulting samples. + */ + async processAndEncode(audioSample, shouldClose) { + const config = this.encodingConfig; + if (config.transform?.sampleFormat !== void 0 && toInterleavedAudioFormat(audioSample.format) !== config.transform.sampleFormat) { + const newSample = audioSampleToInterleavedFormat(audioSample, config.transform.sampleFormat); + if (shouldClose) audioSample.close(); + audioSample = newSample; + shouldClose = true; + } + if (config.transform?.process) try { + let processed = config.transform.process(audioSample); + if (isThenable(processed)) processed = await processed; + if (processed === null) return; + if (!Array.isArray(processed)) processed = [processed]; + try { + for (const sample of processed) if (!(sample instanceof AudioSample)) throw new TypeError("The audio process function must return an AudioSample, null, or an array of AudioSamples."); + for (const sample of processed) await this.encodeSample(sample, true); + } finally { + for (const sample of processed) if (sample instanceof AudioSample) sample.close(); + } + } finally { + if (shouldClose) audioSample.close(); + } + else await this.encodeSample(audioSample, shouldClose); + } + /** + * Encodes a single audio sample, handling encoder init, gap padding, and backpressure. + */ + async encodeSample(audioSample, shouldClose) { + try { + if (!this.encoderInitialized) { + if (!this.ensureEncoderPromise) this.ensureEncoder(audioSample); + if (!this.encoderInitialized) await this.ensureEncoderPromise; + } + assert(this.encoderInitialized); + if (this.closed) return; + { + const startSampleIndex = Math.round(audioSample.timestamp * audioSample.sampleRate); + const endSampleIndex = Math.round((audioSample.timestamp + audioSample.duration) * audioSample.sampleRate); + if (this.lastEndSampleIndex === null) this.lastEndSampleIndex = endSampleIndex; + else { + const sampleDiff = startSampleIndex - this.lastEndSampleIndex; + if (sampleDiff >= 64) { + const fillSample = new AudioSample({ + data: new Float32Array(sampleDiff * audioSample.numberOfChannels), + format: "f32-planar", + sampleRate: audioSample.sampleRate, + numberOfChannels: audioSample.numberOfChannels, + numberOfFrames: sampleDiff, + timestamp: this.lastEndSampleIndex / audioSample.sampleRate + }); + await this.encodeSample(fillSample, true); + } + this.lastEndSampleIndex += audioSample.numberOfFrames; + } + } + this.encodingConfig.onEncodedSample?.(audioSample); + if (this.customEncoder) { + this.customEncoderQueueSize++; + const clonedSample = audioSample.clone(); + const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).catch((error) => this.setError(error)).finally(() => { + this.customEncoderQueueSize--; + clonedSample.close(); + }); + if (this.customEncoderQueueSize >= 4) await promise; + await this.lastMuxerPromise; + } else if (this.isPcmEncoder) await this.doPcmEncoding(audioSample, shouldClose); + else { + assert(this.encoder); + const audioData = audioSample.toAudioData(); + this.encoder.encode(audioData); + audioData.close(); + if (shouldClose) audioSample.close(); + if (this.encoder.encodeQueueSize >= 4) await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + await this.lastMuxerPromise; + } + } finally { + if (shouldClose) audioSample.close(); + } + } + async doPcmEncoding(audioSample, shouldClose) { + assert(this.outputSampleSize); + assert(this.writeOutputValue); + const { numberOfChannels, numberOfFrames, sampleRate, timestamp } = audioSample; + const CHUNK_SIZE = 2048; + const outputs = []; + for (let frame = 0; frame < numberOfFrames; frame += CHUNK_SIZE) { + const frameCount = Math.min(CHUNK_SIZE, audioSample.numberOfFrames - frame); + const outputSize = frameCount * numberOfChannels * this.outputSampleSize; + const outputBuffer = new ArrayBuffer(outputSize); + const outputView = new DataView(outputBuffer); + outputs.push({ + frameCount, + view: outputView + }); + } + const isS32 = audioSample.format === "s32" || audioSample.format === "s32-planar"; + const readFormat = isS32 ? "s32-planar" : "f32-planar"; + const scale = isS32 ? 1 / 2147483648 : 1; + const allocationSize = audioSample.allocationSize({ + planeIndex: 0, + format: readFormat + }); + const values = isS32 ? new Int32Array(allocationSize / 4) : new Float32Array(allocationSize / 4); + for (let i = 0; i < numberOfChannels; i++) { + audioSample.copyTo(values, { + planeIndex: i, + format: readFormat + }); + for (let j = 0; j < outputs.length; j++) { + const { frameCount, view } = outputs[j]; + for (let k = 0; k < frameCount; k++) this.writeOutputValue(view, (k * numberOfChannels + i) * this.outputSampleSize, values[j * CHUNK_SIZE + k] * scale); + } + } + if (shouldClose) audioSample.close(); + const meta = { decoderConfig: { + codec: this.encodingConfig.codec, + numberOfChannels, + sampleRate + } }; + for (let i = 0; i < outputs.length; i++) { + const { frameCount, view } = outputs[i]; + const outputBuffer = view.buffer; + const startFrame = i * CHUNK_SIZE; + const packet = new EncodedPacket(new Uint8Array(outputBuffer), "key", timestamp + startFrame / sampleRate, frameCount / sampleRate); + this.encodingConfig.onEncodedPacket?.(packet, meta); + await this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta); + } + } + ensureEncoder(audioSample) { + this.ensureEncoderPromise = (async () => { + const { numberOfChannels, sampleRate } = audioSample; + const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate); + const encoderConfig = buildAudioEncoderConfig({ + numberOfChannels, + sampleRate, + ...this.encodingConfig, + quality + }); + this.encodingConfig.onEncoderConfig?.(encoderConfig); + this.source._nominalBitrate = encoderConfig.bitrate ?? null; + const MatchingCustomEncoder = customAudioEncoders.find((x) => x.supports(this.encodingConfig.codec, encoderConfig)); + if (MatchingCustomEncoder) { + this.customEncoder = new MatchingCustomEncoder(); + this.customEncoder.codec = this.encodingConfig.codec; + this.customEncoder.config = encoderConfig; + this.customEncoder.onPacket = (packet, meta) => { + if (!(packet instanceof EncodedPacket)) throw new TypeError("The first argument passed to onPacket must be an EncodedPacket."); + if (meta !== void 0 && (!meta || typeof meta !== "object")) throw new TypeError("The second argument passed to onPacket must be an object or undefined."); + this.encodingConfig.onEncodedPacket?.(packet, meta); + this.lastMuxerPromise = this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => { + this.setError(error); + }); + }; + this.customEncoder.onError = (error) => { + this.setError(error); + }; + await this.customEncoder.init(); + } else if (PCM_AUDIO_CODECS.includes(this.encodingConfig.codec)) this.initPcmEncoder(); + else { + if (typeof AudioEncoder === "undefined") throw new Error(missingWebCodecsClassMessage("AudioEncoder")); + let supported; + try { + supported = (await AudioEncoder.isConfigSupported(encoderConfig)).supported ?? false; + } catch { + supported = false; + } + if (!supported) throw new Error(`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps, ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not supported in this environment. Consider using another codec or changing your audio parameters.`); + const stack = (/* @__PURE__ */ new Error("Encoding error")).stack; + this.encoder = new AudioEncoder({ + output: (chunk, meta) => { + if (this.encodingConfig.codec === "aac" && meta?.decoderConfig) { + let needsDescriptionOverwrite = false; + if (!meta.decoderConfig.description || meta.decoderConfig.description.byteLength < 2) needsDescriptionOverwrite = true; + else needsDescriptionOverwrite = parseAacAudioSpecificConfig(toUint8Array(meta.decoderConfig.description)).objectType === 0; + if (needsDescriptionOverwrite) { + const objectType = Number(last(encoderConfig.codec.split("."))); + meta.decoderConfig.description = buildAacAudioSpecificConfig({ + objectType, + outputNumberOfChannels: meta.decoderConfig.numberOfChannels, + outputSampleRate: meta.decoderConfig.sampleRate + }); + } + } + let packet = EncodedPacket.fromEncodedChunk(chunk); + packet = packet.clone({ + timestamp: roundToDivisor(packet.timestamp, encoderConfig.sampleRate), + duration: chunk.duration != null ? roundToDivisor(packet.duration, encoderConfig.sampleRate) : void 0 + }); + this.encodingConfig.onEncodedPacket?.(packet, meta); + this.lastMuxerPromise = this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => { + this.setError(error); + }); + }, + error: (error) => { + error.stack = stack; + this.setError(error); + } + }); + this.encoder.configure(encoderConfig); + } + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; + this.encoderInitialized = true; + })(); + } + initPcmEncoder() { + this.isPcmEncoder = true; + const codec = this.encodingConfig.codec; + const { dataType, sampleSize, littleEndian } = parsePcmCodec(codec); + this.outputSampleSize = sampleSize; + switch (sampleSize) { + case 1: + if (dataType === "unsigned") this.writeOutputValue = (view, byteOffset, value) => view.setUint8(byteOffset, clamp(Math.round(value * 128) + 128, 0, 255)); + else if (dataType === "signed") this.writeOutputValue = (view, byteOffset, value) => { + view.setInt8(byteOffset, clamp(Math.round(value * 128), -128, 127)); + }; + else if (dataType === "ulaw") this.writeOutputValue = (view, byteOffset, value) => { + const int16 = clamp(Math.round(value * 32768), -32768, 32767); + view.setUint8(byteOffset, toUlaw(int16)); + }; + else if (dataType === "alaw") this.writeOutputValue = (view, byteOffset, value) => { + const int16 = clamp(Math.round(value * 32768), -32768, 32767); + view.setUint8(byteOffset, toAlaw(int16)); + }; + else assert(false); + break; + case 2: + if (dataType === "unsigned") this.writeOutputValue = (view, byteOffset, value) => view.setUint16(byteOffset, clamp(Math.round(value * 32768) + 32768, 0, 65535), littleEndian); + else if (dataType === "signed") this.writeOutputValue = (view, byteOffset, value) => view.setInt16(byteOffset, clamp(Math.round(value * 32768), -32768, 32767), littleEndian); + else assert(false); + break; + case 3: + if (dataType === "unsigned") this.writeOutputValue = (view, byteOffset, value) => setUint24(view, byteOffset, clamp(Math.round(value * 8388608) + 8388608, 0, 16777215), littleEndian); + else if (dataType === "signed") this.writeOutputValue = (view, byteOffset, value) => setInt24(view, byteOffset, clamp(Math.round(value * 8388608), -8388608, 8388607), littleEndian); + else assert(false); + break; + case 4: + if (dataType === "unsigned") this.writeOutputValue = (view, byteOffset, value) => view.setUint32(byteOffset, clamp(Math.round(value * 2147483648) + 2147483648, 0, 4294967295), littleEndian); + else if (dataType === "signed") this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, clamp(Math.round(value * 2147483648), -2147483648, 2147483647), littleEndian); + else if (dataType === "float") this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, littleEndian); + else assert(false); + break; + case 8: + if (dataType === "float") this.writeOutputValue = (view, byteOffset, value) => view.setFloat64(byteOffset, value, littleEndian); + else assert(false); + break; + default: + assertNever(sampleSize); + assert(false); + } + } + async flushAndClose(forceClose) { + try { + if (!forceClose) { + this.checkForEncoderError(); + if (this.resampler) await this.resampler.finalize(); + } + this.closed = true; + if (!forceClose) { + if (this.customEncoder) this.customEncoderCallSerializer.call(() => this.customEncoder.flush()); + else if (this.encoder) await this.encoder.flush(); + } + } finally { + this.closed = true; + this.resampler = null; + if (this.customEncoder) await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error)); + else if (this.encoder && this.encoder.state !== "closed") this.encoder.close(); + } + if (!forceClose) this.checkForEncoderError(); + } + getQueueSize() { + if (this.customEncoder) return this.customEncoderQueueSize; + else if (this.isPcmEncoder) return 0; + else return this.encoder?.encodeQueueSize ?? 0; + } + checkForEncoderError() { + if (this.errorSet) throw this.error; + } +}; +/** +* This source can be used to add raw, unencoded audio samples to an output audio track. These samples will +* automatically be encoded and then piped into the output. +* @group Media sources +* @public +*/ +var AudioSampleSource = class extends AudioSource { + /** + * Creates a new {@link AudioSampleSource} whose samples are encoded according to the specified + * {@link AudioEncodingConfig}. + */ + constructor(encodingConfig) { + validateAudioEncodingConfig(encodingConfig); + super(encodingConfig.codec); + this._encoder = new AudioEncoderWrapper(this, encodingConfig); + } + /** + * Encodes an audio sample and then adds it to the output. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(audioSample) { + if (!(audioSample instanceof AudioSample)) throw new TypeError("audioSample must be an AudioSample."); + return this._encoder.add(audioSample, false); + } + /** @internal */ + _flushAndClose(forceClose) { + return this._encoder.flushAndClose(forceClose); + } +}; +/** +* This source can be used to add audio data from an AudioBuffer to the output track. This is useful when working with +* the Web Audio API. +* @group Media sources +* @public +*/ +var AudioBufferSource = class extends AudioSource { + /** + * Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified + * {@link AudioEncodingConfig} and {@link AudioBufferSourceOptions}. + */ + constructor(encodingConfig, options = {}) { + validateAudioEncodingConfig(encodingConfig); + if (typeof options !== "object" || !options) throw new TypeError("options must be an object."); + if (options.startTimestamp !== void 0 && !Number.isFinite(options.startTimestamp)) throw new TypeError("options.startTimestamp, when provided, must be a finite number."); + super(encodingConfig.codec); + this._encoder = new AudioEncoderWrapper(this, encodingConfig); + this._accumulatedTime = options.startTimestamp ?? 0; + } + /** + * Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first `AudioBuffer` will + * be played at the configured start timestamp (the default is 0), and each subsequent `AudioBuffer` will be placed + * directly after the previous one. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + async add(audioBuffer) { + if (!(audioBuffer instanceof AudioBuffer)) throw new TypeError("audioBuffer must be an AudioBuffer."); + const iterator = AudioSample._fromAudioBuffer(audioBuffer, this._accumulatedTime); + this._accumulatedTime += audioBuffer.duration; + for (const audioSample of iterator) await this._encoder.add(audioSample, true); + } + /** @internal */ + _flushAndClose(forceClose) { + return this._encoder.flushAndClose(forceClose); + } +}; +/** +* Audio source that encodes the data of a +* [`MediaStreamAudioTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes it into the +* output. This is useful for capturing live or real-time audio such as microphones or audio from other media elements. +* Audio will automatically start being captured once the connected {@link Output} is started, and will keep being +* captured until the {@link Output} is finalized or this source is closed. +* @group Media sources +* @public +*/ +var MediaStreamAudioTrackSource = class extends AudioSource { + /** A promise that rejects upon any error within this source. This promise never resolves. */ + get errorPromise() { + this._errorPromiseAccessed = true; + return this._promiseWithResolvers.promise; + } + /** Whether this source is currently paused as a result of calling `.pause()`. */ + get paused() { + return this._paused; + } + /** + * Creates a new {@link MediaStreamAudioTrackSource} from a `MediaStreamAudioTrack`, which will pull audio samples + * from the stream in real time and encode them according to {@link AudioEncodingConfig}. + */ + constructor(track, encodingConfig, options = {}) { + if (!(track instanceof MediaStreamTrack) || track.kind !== "audio") throw new TypeError("track must be an audio MediaStreamTrack."); + validateAudioEncodingConfig(encodingConfig); + if (typeof options !== "object" || !options) throw new TypeError("options must be an object."); + if (options.timestampBase !== void 0 && options.timestampBase !== "synced-zero" && options.timestampBase !== "zero" && options.timestampBase !== "unix") throw new TypeError("options.timestampBase, when provided, must be one of 'synced-zero', 'zero', or 'unix'."); + super(encodingConfig.codec); + /** @internal */ + this._abortController = null; + /** @internal */ + this._audioContext = null; + /** @internal */ + this._scriptProcessorNode = null; + /** @internal */ + this._promiseWithResolvers = promiseWithResolvers(); + /** @internal */ + this._errorPromiseAccessed = false; + /** @internal */ + this._paused = false; + this._options = options; + this._encoder = new AudioEncoderWrapper(this, encodingConfig); + this._track = track; + } + /** @internal */ + async _start() { + if (!this._errorPromiseAccessed) Logging._warn("Make sure not to ignore the `errorPromise` field on MediaStreamAudioTrackSource, so that any internal errors get bubbled up properly."); + this._abortController = new AbortController(); + let firstAudioDataTimestamp = null; + let errored = false; + let lastSampleTimestamp = null; + let timestampOffset = 0; + const onAudioSample = (audioSample) => { + if (errored) { + audioSample.close(); + return; + } + const currentTimestamp = audioSample.timestamp; + if (this._paused) { + if (firstAudioDataTimestamp !== null) { + if (lastSampleTimestamp !== null && this._options.timestampBase !== "unix") { + const timeDelta = currentTimestamp - lastSampleTimestamp; + timestampOffset -= timeDelta; + } + lastSampleTimestamp = currentTimestamp; + } + audioSample.close(); + return; + } + if (firstAudioDataTimestamp === null) { + firstAudioDataTimestamp = audioSample.timestamp; + let target; + const timestampBase = this._options.timestampBase ?? "synced-zero"; + if (timestampBase === "unix") target = Date.now() / 1e3; + else if (timestampBase === "zero") target = 0; + else { + const output = this._connectedTrack.output; + if (output._firstMediaStreamTimestamp === null) { + output._firstMediaStreamTimestamp = performance.now() / 1e3; + target = 0; + } else target = performance.now() / 1e3 - output._firstMediaStreamTimestamp; + } + timestampOffset = target - firstAudioDataTimestamp; + } + lastSampleTimestamp = currentTimestamp; + if (this._encoder.getQueueSize() >= 8) { + audioSample.close(); + return; + } + audioSample.setTimestamp(currentTimestamp + timestampOffset); + this._encoder.add(audioSample, true).catch((error) => { + errored = true; + this._abortController?.abort(); + this._promiseWithResolvers.reject(error); + this._audioContext?.suspend(); + }); + }; + if (typeof MediaStreamTrackProcessor !== "undefined") { + const processor = new MediaStreamTrackProcessor({ track: this._track }); + const consumer = new WritableStream({ write: (audioData) => onAudioSample(new AudioSample(audioData)) }); + processor.readable.pipeTo(consumer, { signal: this._abortController.signal }).catch((error) => { + if (error instanceof DOMException && error.name === "AbortError") return; + this._promiseWithResolvers.reject(error); + }); + } else { + const AudioContext = window.AudioContext || window.webkitAudioContext; + this._audioContext = new AudioContext({ sampleRate: this._track.getSettings().sampleRate }); + const sourceNode = this._audioContext.createMediaStreamSource(new MediaStream([this._track])); + this._scriptProcessorNode = this._audioContext.createScriptProcessor(4096); + if (this._audioContext.state === "suspended") await this._audioContext.resume(); + sourceNode.connect(this._scriptProcessorNode); + this._scriptProcessorNode.connect(this._audioContext.destination); + let totalDuration = 0; + this._scriptProcessorNode.onaudioprocess = (event) => { + const iterator = AudioSample._fromAudioBuffer(event.inputBuffer, totalDuration); + totalDuration += event.inputBuffer.duration; + for (const audioSample of iterator) onAudioSample(audioSample); + }; + } + } + /** + * Pauses the capture of audio data - any audio data emitted by the underlying media stream will be ignored + * while paused. This does *not* close the underlying `MediaStreamAudioTrack`, it just ignores its output. + */ + pause() { + this._paused = true; + } + /** Resumes the capture of audio data after being paused. */ + resume() { + this._paused = false; + } + /** @internal */ + async _flushAndClose(forceClose) { + try { + if (this._abortController) { + this._abortController.abort(); + this._abortController = null; + } + if (this._audioContext) { + assert(this._scriptProcessorNode); + this._scriptProcessorNode.disconnect(); + await this._audioContext.suspend(); + } + } finally { + await this._encoder.flushAndClose(forceClose); + } + } +}; +var mediaStreamTrackProcessorWorkerCode = () => { + const sendMessage = (message, transfer) => { + if (transfer) self.postMessage(message, { transfer }); + else self.postMessage(message); + }; + sendMessage({ + type: "support", + supported: typeof MediaStreamTrackProcessor !== "undefined" + }); + const abortControllers = /* @__PURE__ */ new Map(); + const activeTracks = /* @__PURE__ */ new Map(); + self.addEventListener("message", (event) => { + const message = event.data; + switch (message.type) { + case "videoTrack": + { + activeTracks.set(message.trackId, message.track); + const processor = new MediaStreamTrackProcessor({ track: message.track }); + const consumer = new WritableStream({ write: (videoFrame) => { + if (!activeTracks.has(message.trackId)) { + videoFrame.close(); + return; + } + sendMessage({ + type: "videoFrame", + trackId: message.trackId, + videoFrame + }, [videoFrame]); + } }); + const abortController = new AbortController(); + abortControllers.set(message.trackId, abortController); + processor.readable.pipeTo(consumer, { signal: abortController.signal }).catch((error) => { + if (error instanceof DOMException && error.name === "AbortError") return; + sendMessage({ + type: "error", + trackId: message.trackId, + error + }); + }); + } + break; + case "stopTrack": + { + const abortController = abortControllers.get(message.trackId); + if (abortController) { + abortController.abort(); + abortControllers.delete(message.trackId); + } + activeTracks.get(message.trackId)?.stop(); + activeTracks.delete(message.trackId); + sendMessage({ + type: "trackStopped", + trackId: message.trackId + }); + } + break; + default: assertNever(message); + } + }); +}; +var nextMediaStreamTrackProcessorWorkerId = 0; +var mediaStreamTrackProcessorWorker = null; +var initMediaStreamTrackProcessorWorker = () => { + const blob = new Blob([`(${mediaStreamTrackProcessorWorkerCode.toString()})()`], { type: "application/javascript" }); + const url = URL.createObjectURL(blob); + mediaStreamTrackProcessorWorker = new Worker(url); +}; +var mediaStreamTrackProcessorIsSupportedInWorkerCache = null; +var mediaStreamTrackProcessorIsSupportedInWorker = async () => { + if (mediaStreamTrackProcessorIsSupportedInWorkerCache !== null) return mediaStreamTrackProcessorIsSupportedInWorkerCache; + if (!mediaStreamTrackProcessorWorker) initMediaStreamTrackProcessorWorker(); + return new Promise((resolve) => { + assert(mediaStreamTrackProcessorWorker); + const listener = (event) => { + const message = event.data; + if (message.type === "support") { + mediaStreamTrackProcessorIsSupportedInWorkerCache = message.supported; + mediaStreamTrackProcessorWorker.removeEventListener("message", listener); + resolve(message.supported); + } + }; + mediaStreamTrackProcessorWorker.addEventListener("message", listener); + }); +}; +var sendMessageToMediaStreamTrackProcessorWorker = (message, transfer) => { + assert(mediaStreamTrackProcessorWorker); + if (transfer) mediaStreamTrackProcessorWorker.postMessage(message, transfer); + else mediaStreamTrackProcessorWorker.postMessage(message); +}; +/** +* Base class for subtitle sources - sources for subtitle tracks. +* @group Media sources +* @public +*/ +var SubtitleSource = class extends MediaSource { + /** Internal constructor. */ + constructor(codec) { + super(); + if (!SUBTITLE_CODECS.includes(codec)) throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`); + this._codec = codec; + } +}; +/** +* This source can be used to add subtitles from a subtitle text file. +* @group Media sources +* @public +*/ +var TextSubtitleSource = class extends SubtitleSource { + /** Creates a new {@link TextSubtitleSource} where added text chunks are in the specified `codec`. */ + constructor(codec) { + super(codec); + /** @internal */ + this._error = null; + /** @internal */ + this._errorSet = false; + /** @internal */ + this._lastMuxerPromise = Promise.resolve(); + this._parser = new SubtitleParser({ + codec, + output: (cue, metadata) => { + this._lastMuxerPromise = this._connectedTrack.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata).catch((error) => { + this._setError(error); + }); + } + }); + } + /** + * Parses the subtitle text according to the specified codec and adds it to the output track. You don't have to + * add the entire subtitle file at once here; you can provide it in chunks. + * + * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise + * to respect writer and encoder backpressure. + */ + add(text) { + if (typeof text !== "string") throw new TypeError("text must be a string."); + this._checkForError(); + this._ensureValidAdd(); + this._parser.parse(text); + return this._lastMuxerPromise; + } + /** @internal */ + _setError(error) { + if (!this._errorSet) { + this._error = error; + this._errorSet = true; + } + } + /** @internal */ + _checkForError() { + if (this._errorSet) throw this._error; + } + /** @internal */ + async _flushAndClose(forceClose) { + if (!forceClose) this._checkForError(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/hls/hls-muxer.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var toHlsCodecString = (codecString) => { + if (codecString === "opus") return "Opus"; + return codecString; +}; +var HlsMuxer = class extends Muxer { + constructor(output, format) { + if (!(output._target instanceof PathedTarget)) throw new TypeError("HLS outputs require `OutputOptions.target` to be a PathedTarget."); + super(output); + this.trackDatas = []; + this.isRelativeToUnixEpoch = false; + this.numWrittenMasterPlaylists = 0; + this.playlists = []; + this.playlistDeclarations = []; + this.format = format; + this.targetSegmentDuration = format._options.targetDuration ?? 2; + this.singleFilePerPlaylist = format._options.singleFilePerPlaylist ?? false; + this.isLive = format._options.live ?? false; + this.maxLiveSegmentCount = format._options.maxLiveSegmentCount ?? Infinity; + this.globalTargetDuration = this.targetSegmentDuration; + this.getPlaylistPath = format._options.getPlaylistPath ?? (({ n }) => `playlist-${n}.m3u8`); + this.getSegmentPath = format._options.getSegmentPath ?? ((info) => info.isSingleFile ? `segments-${info.playlist.n}${info.format.fileExtension}` : `segment-${info.playlist.n}-${info.n}${info.format.fileExtension}`); + this.getInitPath = format._options.getInitPath ?? ((playlist) => `init-${playlist.n}${playlist.segmentFormat.fileExtension}`); + } + async start() { + const release = await this.mutex.acquire(); + const someRelative = this.output.tracks.some((t) => t.metadata.isRelativeToUnixEpoch); + const someNotRelative = this.output.tracks.some((t) => !t.metadata.isRelativeToUnixEpoch); + if (someRelative && someNotRelative) throw new Error("All tracks must agree on `relativeToUnixEpoch`: some tracks are relative to the Unix epoch and some are not."); + this.isRelativeToUnixEpoch = someRelative; + const groupAssignment = /* @__PURE__ */ new Map(); + const groups = []; + let hasVideo = false; + let illegalPairingDetected = false; + let keyPacketsOnlyPairingWarned = false; + for (const track of this.output.tracks) { + if (track.type === "video") hasVideo = true; + const pairableGroups = /* @__PURE__ */ new Map(); + for (const otherTrack of this.output.tracks) { + if (track === otherTrack) continue; + if (!track.canBePairedWith(otherTrack)) continue; + if (track.type === otherTrack.type) { + if (!illegalPairingDetected) { + Logging._warn(`Illegal pairing of two ${track.type} tracks detected, which is not possible in HLS; treating them as unpaired.`); + illegalPairingDetected = true; + } + continue; + } + if (track.isVideoTrack() && track.metadata.hasOnlyKeyPackets || otherTrack.isVideoTrack() && otherTrack.metadata.hasOnlyKeyPackets) { + if (!keyPacketsOnlyPairingWarned) { + Logging._warn("A key-packets-only video track is pairable with another track, which is not possible in HLS; treating them as unpaired."); + keyPacketsOnlyPairingWarned = true; + } + continue; + } + let groupTracks = pairableGroups.get(otherTrack.source._codec); + if (!groupTracks) pairableGroups.set(otherTrack.source._codec, groupTracks = []); + groupTracks.push(otherTrack); + } + for (const [, pairableTracks] of pairableGroups) { + const key = pairableTracks.map((x) => x.id).join("-"); + if (!groups.find((x) => x.key === key)) groups.push({ + name: pairableTracks[0].type + "-" + (groups.length + 1), + key, + tracks: pairableTracks, + needsEmit: false, + firstNoUri: false + }); + let assignedGroups = groupAssignment.get(track); + if (!assignedGroups) groupAssignment.set(track, assignedGroups = []); + assignedGroups.push(key); + } + } + const mainType = hasVideo ? "video" : "audio"; + const variantStreams = []; + const unpairedVideoTracks = []; + const unpairedAudioTracks = []; + for (const track of this.output.tracks) { + const assignedGroupKeys = groupAssignment.get(track); + if (assignedGroupKeys) { + assert(assignedGroupKeys.length > 0); + if (track.type !== mainType) continue; + for (const key of assignedGroupKeys) { + const group = groups.find((x) => x.key === key); + assert(group); + if (assignedGroupKeys.length === 1 && group.tracks.length === 1) { + const otherGroupKeys = groupAssignment.get(group.tracks[0]); + assert(otherGroupKeys !== void 0); + if (otherGroupKeys.length === 1) { + const otherGroup = groups.find((x) => x.key === otherGroupKeys[0]); + if (otherGroup.tracks.length === 1) { + assert(otherGroup.tracks[0] === track); + variantStreams.push({ + tracks: [track, group.tracks[0]], + linkedGroup: null + }); + continue; + } + } + } + variantStreams.push({ + tracks: [track], + linkedGroup: group + }); + group.needsEmit = true; + } + } else if (track.type === "video") unpairedVideoTracks.push(track); + else if (track.type === "audio") unpairedAudioTracks.push(track); + } + const getMetadataKeyForTrack = ({ metadata }) => { + let key = ""; + key += `${metadata.languageCode ?? "und"}-`; + key += `${metadata.name ?? ""}-`; + key += `${metadata.disposition?.default ?? true}-`; + key += `${metadata.disposition?.primary ?? false}-`; + key += `${metadata.disposition?.forced ?? false}-`; + return key; + }; + if (unpairedVideoTracks.length > 0) if (new Set(unpairedVideoTracks.map(getMetadataKeyForTrack)).size > 1) { + const group = { + key: unpairedVideoTracks.map((x) => x.id).join("-"), + name: "video-" + (groups.length + 1), + tracks: unpairedVideoTracks, + needsEmit: true, + firstNoUri: true + }; + groups.push(group); + variantStreams.push({ + tracks: [unpairedVideoTracks[0]], + linkedGroup: group + }); + } else for (const track of unpairedVideoTracks) variantStreams.push({ + tracks: [track], + linkedGroup: null + }); + if (unpairedAudioTracks.length > 0) if (new Set(unpairedAudioTracks.map(getMetadataKeyForTrack)).size > 1) { + const group = { + key: unpairedAudioTracks.map((x) => x.id).join("-"), + name: "audio-" + (groups.length + 1), + tracks: unpairedAudioTracks, + needsEmit: true, + firstNoUri: true + }; + groups.push(group); + variantStreams.push({ + tracks: [unpairedAudioTracks[0]], + linkedGroup: group + }); + } else for (const track of unpairedAudioTracks) variantStreams.push({ + tracks: [track], + linkedGroup: null + }); + const deduceSegmentFormat = (tracks) => { + const codecs = []; + let videoCount = 0; + let audioCount = 0; + let requiresTransformationMetadata = false; + let candidate = null; + let candidateScore = -Infinity; + for (const track of tracks) { + if (track.isVideoTrack()) { + videoCount++; + requiresTransformationMetadata ||= (track.metadata.rotation ?? 0) !== 0 || !!track.metadata.flip || !!track.metadata.transformationMatrix; + } else if (track.isAudioTrack()) audioCount++; + codecs.push(track.source._codec); + } + for (const format of toArray(this.format._options.segmentFormat)) { + const supportedCodecs = format.getSupportedCodecs(); + const trackCounts = format.getSupportedTrackCounts(); + if (codecs.some((codec) => !supportedCodecs.includes(codec))) continue; + if (videoCount < trackCounts.video.min || videoCount > trackCounts.video.max) continue; + if (audioCount < trackCounts.audio.min || audioCount > trackCounts.audio.max) continue; + let score = 0; + if (requiresTransformationMetadata && format.supportsVideoTransformationMetadata) score++; + if (score > candidateScore) { + candidate = format; + candidateScore = score; + } + } + assert(candidate); + return candidate; + }; + const registerPlaylist = async (tracks) => { + if (tracks.some((track) => this.playlists.some((playlist) => playlist.tracks.includes(track)))) throw new Error("Internal error: track is already registered in a playlist."); + const format = deduceSegmentFormat(tracks); + const id = this.playlists.length + 1; + const path = await this.getPlaylistPath({ + n: id, + tracks, + segmentFormat: format + }); + validatePlaylistPath(path); + const playlist = { + id: this.playlists.length + 1, + path, + tracks, + segmentFormat: format, + currentSegmentStartTimestamp: null, + currentSegmentStartTimestampIsFixed: false, + nextSegmentId: 1, + initSegment: null, + writtenSegments: [], + peakBitrate: null, + averageBitrate: null, + mediaSequence: 0, + done: false, + singleFile: null, + mutex: new AsyncMutex() + }; + this.playlists.push(playlist); + return playlist; + }; + for (const group of groups) { + if (!group.needsEmit) continue; + for (let i = 0; i < group.tracks.length; i++) { + const track = group.tracks[i]; + let playlist = this.playlists.find((x) => x.tracks[0].id === track.id); + playlist ??= await registerPlaylist([track]); + this.playlistDeclarations.push({ + playlist, + groupId: group.name, + noUri: group.firstNoUri && i === 0, + references: [] + }); + } + } + for (const variant of variantStreams) { + let playlist = this.playlists.find((x) => x.tracks[0].id === variant.tracks[0].id); + playlist ??= await registerPlaylist(variant.tracks); + this.playlistDeclarations.push({ + playlist, + groupId: null, + noUri: false, + references: variant.linkedGroup ? this.playlistDeclarations.filter((x) => x.groupId === variant.linkedGroup.name) : [] + }); + } + for (const track of this.output.tracks) if (track.isVideoTrack() && track.metadata.decoderConfig) this.getVideoTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + else if (track.isAudioTrack() && track.metadata.decoderConfig) this.getAudioTrackData(track, track.metadata.primingPacket ?? null, { decoderConfig: track.metadata.decoderConfig }); + release(); + } + async getMimeType() { + return HLS_MIME_TYPE; + } + allTracksAreKnown(playlist) { + for (const track of playlist.tracks) if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) return false; + return true; + } + async onTrackClose(track) { + const trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) trackData.closed = true; + const playlist = this.playlists.find((x) => x.tracks.includes(track)); + assert(playlist); + const release = await playlist.mutex.acquire(); + try { + await this.advancePlaylist(playlist); + } finally { + release(); + } + } + getVideoTrackData(track, packet, meta) { + let trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) return trackData; + validateVideoChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta?.decoderConfig); + const playlists = this.playlists.filter((x) => x.tracks.includes(track)); + assert(playlists.length === 1); + trackData = { + track, + packets: [], + playlist: playlists[0], + closed: false, + info: { + type: "video", + decoderConfig: meta.decoderConfig, + primingPacket: packet + } + }; + this.trackDatas.push(trackData); + return trackData; + } + getAudioTrackData(track, packet, meta) { + let trackData = this.trackDatas.find((x) => x.track === track); + if (trackData) return trackData; + validateAudioChunkMetadata(meta, track.source._codec); + assert(meta); + assert(meta?.decoderConfig); + const playlists = this.playlists.filter((x) => x.tracks.includes(track)); + assert(playlists.length === 1); + trackData = { + track, + packets: [], + playlist: playlists[0], + closed: false, + info: { + type: "audio", + decoderConfig: meta.decoderConfig, + primingPacket: packet + } + }; + this.trackDatas.push(trackData); + return trackData; + } + async addEncodedVideoPacket(track, packet, meta) { + const trackData = this.getVideoTrackData(track, packet, meta); + const playlist = trackData.playlist; + const release = await playlist.mutex.acquire(); + try { + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + trackData.packets.push(packet); + if (playlist.currentSegmentStartTimestamp === null) playlist.currentSegmentStartTimestamp = packet.timestamp; + else if (!playlist.currentSegmentStartTimestampIsFixed) playlist.currentSegmentStartTimestamp = Math.min(playlist.currentSegmentStartTimestamp, packet.timestamp); + await this.advancePlaylist(playlist); + } finally { + release(); + } + } + async addEncodedAudioPacket(track, packet, meta) { + const trackData = this.getAudioTrackData(track, packet, meta); + const playlist = trackData.playlist; + const release = await playlist.mutex.acquire(); + try { + this.validateTimestamp(track, packet.timestamp, packet.type === "key"); + trackData.packets.push(packet); + if (playlist.currentSegmentStartTimestamp === null) playlist.currentSegmentStartTimestamp = packet.timestamp; + else if (!playlist.currentSegmentStartTimestampIsFixed) playlist.currentSegmentStartTimestamp = Math.min(playlist.currentSegmentStartTimestamp, packet.timestamp); + await this.advancePlaylist(playlist); + } finally { + release(); + } + } + async addSubtitleCue(track, cue, meta) { + throw new Error("Unreachable."); + } + async advancePlaylist(playlist) { + assert(!playlist.done); + if (!this.allTracksAreKnown(playlist)) return; + const trackDatas = this.trackDatas.filter((x) => playlist.tracks.includes(x.track)); + if (playlist.currentSegmentStartTimestamp === null) { + if (trackDatas.every((x) => x.closed)) await this.onPlaylistDone(playlist); + return; + } + const videoTrack = trackDatas.find((x) => x.info.type === "video"); + const audioTrack = trackDatas.find((x) => x.info.type === "audio"); + while (true) { + const currentSegmentEndTimestamp = playlist.currentSegmentStartTimestamp + this.targetSegmentDuration; + let videoEndIndex = 0; + let audioEndIndex = 0; + if (videoTrack && (!videoTrack.closed || videoTrack.packets.length > 0)) { + const allBelow = videoTrack.packets.every((x) => x.timestamp < currentSegmentEndTimestamp); + let bestKeyPacket = null; + let bestKeyPacketIndex = null; + if (allBelow) { + if (!videoTrack.closed) return; + } else for (let i = 0; i < videoTrack.packets.length; i++) { + const packet = videoTrack.packets[i]; + if (bestKeyPacket !== null && packet.timestamp > currentSegmentEndTimestamp) break; + if (i > 0 && packet.type === "key") { + bestKeyPacket = packet; + bestKeyPacketIndex = i; + } + } + if (bestKeyPacketIndex !== null) { + videoEndIndex = bestKeyPacketIndex; + if (audioTrack) { + const index = audioTrack.packets.findIndex((x) => x.timestamp >= bestKeyPacket.timestamp); + if (index !== -1) audioEndIndex = index; + else if (audioTrack.closed) audioEndIndex = audioTrack.packets.length; + else return; + } + } else { + if (!videoTrack.closed) return; + videoEndIndex = videoTrack.packets.length; + const maxIndex = arrayArgmax(videoTrack.packets, (x) => x.timestamp); + const maxPacket = videoTrack.packets[maxIndex]; + assert(maxPacket); + if (audioTrack) if (maxPacket.timestamp < currentSegmentEndTimestamp) { + const index = audioTrack.packets.findIndex((x) => x.timestamp >= currentSegmentEndTimestamp); + if (index !== -1) audioEndIndex = index; + else if (audioTrack.closed) audioEndIndex = audioTrack.packets.length; + else return; + } else { + const index = audioTrack.packets.findIndex((x) => x.timestamp > maxPacket.timestamp); + if (index !== -1) audioEndIndex = index; + else if (audioTrack.closed) audioEndIndex = audioTrack.packets.length; + else return; + } + } + } else if (audioTrack && (!audioTrack.closed || audioTrack.packets.length > 0)) if (audioTrack.packets.every((x) => x.timestamp < currentSegmentEndTimestamp)) if (audioTrack.closed) audioEndIndex = audioTrack.packets.length; + else return; + else { + const index = findLastIndex(audioTrack.packets, (x) => x.timestamp <= currentSegmentEndTimestamp); + audioEndIndex = Math.max(index, 1); + } + if (videoEndIndex === 0 && audioEndIndex === 0) { + if (trackDatas.every((x) => x.closed)) await this.onPlaylistDone(playlist); + return; + } + let segmentInfo = null; + let relativeSegmentPath; + let fullSegmentPath; + assert(this.output._target instanceof PathedTarget); + const pathedTarget = this.output._target; + if (this.singleFilePerPlaylist) if (playlist.singleFile === null) { + const segmentInfo = { + n: playlist.nextSegmentId, + format: playlist.segmentFormat, + isSingleFile: true, + playlist: toPlaylistInfo(playlist) + }; + relativeSegmentPath = await this.getSegmentPath(segmentInfo); + validateSegmentPath(relativeSegmentPath); + fullSegmentPath = joinPaths(joinPaths(pathedTarget.rootPath, playlist.path), relativeSegmentPath); + const target = await this.output._getTarget({ + path: fullSegmentPath, + isRoot: false, + mimeType: playlist.segmentFormat.mimeType + }); + let fragmentedIsobmffOutput = null; + if (playlist.segmentFormat._isFragmentedIsobmff()) { + fragmentedIsobmffOutput = { + output: new Output({ + format: playlist.segmentFormat, + target + }), + videoSource: null, + audioSource: null, + firstMoofPosition: null, + currentFileSize: 0 + }; + target.on("write", ({ end }) => { + fragmentedIsobmffOutput.currentFileSize = Math.max(fragmentedIsobmffOutput.currentFileSize, end); + }); + const muxer = fragmentedIsobmffOutput.output._muxer; + muxer.minimumFragmentDuration = Infinity; + const originalOnMoof = muxer.formatOptions.onMoof; + muxer.formatOptions.onMoof = (data, position, timestamp) => { + fragmentedIsobmffOutput.firstMoofPosition = position; + originalOnMoof?.(data, position, timestamp); + muxer.formatOptions.onMoof = originalOnMoof; + }; + if (videoTrack) { + fragmentedIsobmffOutput.videoSource = new EncodedVideoPacketSource(videoTrack.track.source._codec); + fragmentedIsobmffOutput.output.addVideoTrack(fragmentedIsobmffOutput.videoSource, { + ...videoTrack.track.metadata, + decoderConfig: videoTrack.info.decoderConfig, + primingPacket: videoTrack.info.primingPacket ?? void 0 + }); + } + if (audioTrack) { + fragmentedIsobmffOutput.audioSource = new EncodedAudioPacketSource(audioTrack.track.source._codec); + fragmentedIsobmffOutput.output.addAudioTrack(fragmentedIsobmffOutput.audioSource, { + ...audioTrack.track.metadata, + decoderConfig: audioTrack.info.decoderConfig, + primingPacket: audioTrack.info.primingPacket ?? void 0 + }); + } + await fragmentedIsobmffOutput.output.start(); + } else target._start(); + playlist.singleFile = { + target, + path: relativeSegmentPath, + nextOffset: 0, + info: segmentInfo, + fragmentedIsobmffOutput + }; + } else { + relativeSegmentPath = playlist.singleFile.path; + fullSegmentPath = joinPaths(joinPaths(pathedTarget.rootPath, playlist.path), relativeSegmentPath); + } + else { + segmentInfo = { + n: playlist.nextSegmentId, + format: playlist.segmentFormat, + isSingleFile: false, + playlist: toPlaylistInfo(playlist) + }; + relativeSegmentPath = await this.getSegmentPath(segmentInfo); + validateSegmentPath(relativeSegmentPath); + fullSegmentPath = joinPaths(joinPaths(pathedTarget.rootPath, playlist.path), relativeSegmentPath); + playlist.nextSegmentId++; + } + let segmentSize = 0; + let outputTarget = null; + let maxEndTimestamp = -Infinity; + let output = null; + let videoSource = null; + let audioSource = null; + try { + if (playlist.singleFile?.fragmentedIsobmffOutput) { + output = playlist.singleFile.fragmentedIsobmffOutput.output; + videoSource = playlist.singleFile.fragmentedIsobmffOutput.videoSource; + audioSource = playlist.singleFile.fragmentedIsobmffOutput.audioSource; + } else { + output = new Output({ + format: playlist.segmentFormat, + target: new PathedTarget(fullSegmentPath, async (request) => { + const proxiedRequest = { + ...request, + isRoot: false + }; + if (request.isRoot) if (playlist.singleFile) { + const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset); + slice.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end)); + return slice; + } else { + const target = await this.output._getTarget(proxiedRequest); + outputTarget = target; + target.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end)); + return target; + } + return this.output._getTarget(proxiedRequest); + }), + initTarget: async () => { + if (playlist.initSegment) return new NullTarget(); + if (playlist.singleFile) { + playlist.initSegment = { + path: playlist.singleFile.path, + duration: 0, + timestamp: 0, + byteSize: 0, + byteOffset: 0, + info: null + }; + const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset); + slice.on("write", ({ end }) => { + playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end); + }); + slice.on("finalized", () => { + playlist.singleFile.nextOffset = playlist.initSegment.byteSize; + }); + return slice; + } else { + const playlistInfo = toPlaylistInfo(playlist); + const initPath = await this.getInitPath(playlistInfo); + validateInitPath(initPath); + playlist.initSegment = { + path: initPath, + duration: 0, + timestamp: 0, + byteSize: 0, + byteOffset: null, + info: null + }; + const fullInitPath = joinPaths(joinPaths(pathedTarget.rootPath, playlist.path), initPath); + const target = await this.output._getTarget({ + path: fullInitPath, + isRoot: false, + mimeType: playlist.segmentFormat.mimeType + }); + target.on("write", ({ end }) => { + playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end); + }); + target.on("finalized", () => { + this.format._options.onInit?.(target, playlistInfo); + }); + return target; + } + } + }); + if (videoTrack) { + videoSource = new EncodedVideoPacketSource(videoTrack.track.source._codec); + output.addVideoTrack(videoSource, { + ...videoTrack.track.metadata, + decoderConfig: videoTrack.info.decoderConfig, + primingPacket: videoTrack.info.primingPacket ?? void 0 + }); + } + if (audioTrack) { + audioSource = new EncodedAudioPacketSource(audioTrack.track.source._codec); + output.addAudioTrack(audioSource, { + ...audioTrack.track.metadata, + decoderConfig: audioTrack.info.decoderConfig, + primingPacket: audioTrack.info.primingPacket ?? void 0 + }); + } + await output.start(); + } + if (videoTrack) { + assert(videoSource); + const meta = { decoderConfig: videoTrack.info.decoderConfig }; + for (let i = 0; i < videoEndIndex; i++) { + const packet = videoTrack.packets[i]; + await videoSource.add(packet, meta); + maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration); + } + } + if (audioTrack) { + assert(audioSource); + const meta = { decoderConfig: audioTrack.info.decoderConfig }; + for (let i = 0; i < audioEndIndex; i++) { + const packet = audioTrack.packets[i]; + await audioSource.add(packet, meta); + maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration); + } + } + if (playlist.singleFile?.fragmentedIsobmffOutput) { + await playlist.singleFile.fragmentedIsobmffOutput.output._muxer.forceFragmentFinalization(); + if (playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition !== null && !playlist.initSegment) { + playlist.initSegment = { + path: playlist.singleFile.path, + duration: 0, + timestamp: 0, + byteSize: playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition, + byteOffset: 0, + info: null + }; + playlist.singleFile.nextOffset = playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition; + } + segmentSize = playlist.singleFile.fragmentedIsobmffOutput.currentFileSize - playlist.singleFile.nextOffset; + } else await output.finalize(); + } catch (e) { + await output?.cancel(); + throw e; + } + if (segmentInfo) { + assert(outputTarget); + this.format._options.onSegment?.(outputTarget, segmentInfo); + } + if (videoEndIndex > 0) { + assert(videoTrack); + videoTrack.packets.splice(0, videoEndIndex); + } + if (audioEndIndex > 0) { + assert(audioTrack); + audioTrack.packets.splice(0, audioEndIndex); + } + let minNextTimestamp = Infinity; + if (videoTrack && videoTrack.packets.length > 0) minNextTimestamp = videoTrack.packets[0].timestamp; + if (audioTrack && audioTrack.packets.length > 0) minNextTimestamp = Math.min(minNextTimestamp, audioTrack.packets[0].timestamp); + const nextSegmentStartTimestamp = minNextTimestamp < Infinity ? minNextTimestamp : maxEndTimestamp; + assert(Number.isFinite(nextSegmentStartTimestamp)); + const segmentDuration = nextSegmentStartTimestamp - playlist.currentSegmentStartTimestamp; + assert(segmentDuration >= 0); + playlist.writtenSegments.push({ + path: relativeSegmentPath, + duration: segmentDuration, + timestamp: playlist.currentSegmentStartTimestamp, + byteSize: segmentSize, + byteOffset: playlist.singleFile ? playlist.singleFile.nextOffset : null, + info: segmentInfo ?? null + }); + this.globalTargetDuration = Math.max(this.globalTargetDuration, segmentDuration); + playlist.currentSegmentStartTimestamp = nextSegmentStartTimestamp; + playlist.currentSegmentStartTimestampIsFixed = true; + if (playlist.singleFile) playlist.singleFile.nextOffset += segmentSize; + if (this.isLive) { + while (playlist.writtenSegments.length > this.maxLiveSegmentCount) { + const popped = playlist.writtenSegments.shift(); + playlist.mediaSequence++; + if (!this.singleFilePerPlaylist) { + assert(popped.info); + this.format._options.onSegmentPopped?.(popped.path, popped.info); + } + } + await this.writePlaylist(playlist); + await this.tryWriteMasterPlaylist(); + } + } + } + async onPlaylistDone(playlist) { + assert(!playlist.done); + playlist.done = true; + if (playlist.singleFile) { + if (playlist.singleFile.fragmentedIsobmffOutput) await playlist.singleFile.fragmentedIsobmffOutput.output.finalize(); + else { + await playlist.singleFile.target._flush(); + await playlist.singleFile.target._finalize(); + } + this.format._options.onSegment?.(playlist.singleFile.target, playlist.singleFile.info); + } + await this.writePlaylist(playlist); + if (this.isLive && playlist.writtenSegments.length === 0) await this.tryWriteMasterPlaylist(); + } + updatePlaylistBitrates(playlist) { + const segments = playlist.writtenSegments; + let peakBitrate = 0; + let totalBits = 0; + let totalDuration = 0; + for (let i = 0; i < segments.length; i++) { + totalDuration += segments[i].duration; + let windowBytes = 0; + let windowDuration = 0; + for (let j = i; j < segments.length; j++) { + windowBytes += segments[j].byteSize; + windowDuration += segments[j].duration; + if (windowDuration >= .5 * this.globalTargetDuration && windowDuration <= 1.5 * this.globalTargetDuration) peakBitrate = Math.max(peakBitrate, 8 * windowBytes / windowDuration); + if (windowDuration > 1.5 * this.globalTargetDuration) break; + } + } + if (peakBitrate === 0) for (const segment of segments) { + const segmentDuration = segment.duration || 1; + peakBitrate = Math.max(peakBitrate, 8 * segment.byteSize / segmentDuration); + } + for (const segment of segments) totalBits += 8 * segment.byteSize; + let averageBitrate = totalBits / (totalDuration || 1); + if (segments.length === 0) for (const track of playlist.tracks) { + const trackData = this.trackDatas.find((x) => x.track === track); + let trackPeakBitrate = track.source._nominalBitrate ?? track.metadata.bitrate ?? 0; + averageBitrate += track.source._nominalBitrate ?? track.metadata.averageBitrate ?? 0; + if (trackPeakBitrate === 0) { + const mediumQuality = new Quality("medium"); + if (track.isVideoTrack()) { + const decoderConfig = trackData?.info.decoderConfig; + trackPeakBitrate = mediumQuality._toVideoBitrate(track.source._codec, decoderConfig?.codedWidth ?? 1920, decoderConfig?.codedHeight ?? 1080); + } else if (track.isAudioTrack()) trackPeakBitrate = mediumQuality._toAudioBitrate(track.source._codec) ?? 0; + } + peakBitrate += trackPeakBitrate; + } + playlist.peakBitrate = peakBitrate; + playlist.averageBitrate = averageBitrate; + } + async writePlaylist(playlist) { + assert(this.output._target instanceof PathedTarget); + const pathedTarget = this.output._target; + this.updatePlaylistBitrates(playlist); + let hasByteOffsets = false; + for (const segment of playlist.writtenSegments) hasByteOffsets ||= segment.byteOffset !== null; + const isKeyPacketsOnly = playlist.tracks[0].isVideoTrack() && playlist.tracks[0].metadata.hasOnlyKeyPackets; + let version = 3; + if (isKeyPacketsOnly || hasByteOffsets) version = 4; + if (playlist.initSegment) version = 5; + if (playlist.initSegment && !isKeyPacketsOnly) version = 6; + const targetDuration = this.isLive ? this.targetSegmentDuration : this.globalTargetDuration; + const playlistPath = joinPaths(pathedTarget.rootPath, playlist.path); + const playlistText = `#EXTM3U +#EXT-X-VERSION:${version}\n` + (!this.isLive ? "#EXT-X-PLAYLIST-TYPE:VOD\n" : "") + `#EXT-X-TARGETDURATION:${Math.ceil(targetDuration)}\n` + (Number.isFinite(this.maxLiveSegmentCount) ? `#EXT-X-MEDIA-SEQUENCE:${playlist.mediaSequence}\n` : "") + "#EXT-X-INDEPENDENT-SEGMENTS\n" + (isKeyPacketsOnly ? "#EXT-X-I-FRAMES-ONLY\n" : "") + (playlist.initSegment ? `#EXT-X-MAP:URI="${playlist.initSegment.path}"` + (playlist.initSegment.byteOffset !== null ? `,BYTERANGE="${playlist.initSegment.byteSize}@${playlist.initSegment.byteOffset}"` : "") + "\n" : "") + "\n" + playlist.writtenSegments.map((segment) => `#EXTINF:${+segment.duration.toFixed(12)},\n` + (this.isRelativeToUnixEpoch ? `#EXT-X-PROGRAM-DATE-TIME:${(/* @__PURE__ */ new Date(1e3 * segment.timestamp)).toISOString()}\n` : "") + (segment.byteOffset !== null ? `#EXT-X-BYTERANGE:${segment.byteSize}@${segment.byteOffset}\n` : "") + `${segment.path}\n`).join("") + (playlist.done ? (playlist.writtenSegments.length > 0 ? "\n" : "") + "#EXT-X-ENDLIST\n" : ""); + this.format._options.onPlaylist?.(playlistText, toPlaylistInfo(playlist)); + const writer = new Writer(await this.output._getTarget({ + path: playlistPath, + isRoot: false, + mimeType: HLS_MIME_TYPE + }), true); + writer.start(); + writer.write(textEncoder.encode(playlistText)); + await writer.flush(); + await writer.finalize(); + } + async writeMasterPlaylist() { + assert(this.output._target instanceof PathedTarget); + const pathedTarget = this.output._target; + let masterPlaylistText = "#EXTM3U\n"; + let firstVariantWritten = false; + let lastGroupId = null; + let groupIdTrackCount = 0; + let hasHadDefaultTrackInGroup = false; + for (const decl of this.playlistDeclarations) if (decl.groupId === null) { + const isKeyPacketsOnly = decl.playlist.tracks[0].isVideoTrack() && decl.playlist.tracks[0].metadata.hasOnlyKeyPackets; + const codecs = []; + for (const track of decl.playlist.tracks) { + const codecString = this.trackDatas.find((x) => x.track === track)?.info.decoderConfig.codec ?? track.source._codec; + codecs.push(toHlsCodecString(codecString)); + } + let peakDeclBitrate = 0; + let maxRefAverageBitrate = 0; + if (decl.references.length > 0) { + const firstTrack = decl.references[0].playlist.tracks[0]; + const codecString = this.trackDatas.find((x) => x.track === firstTrack)?.info.decoderConfig.codec ?? firstTrack.source._codec; + codecs.push(toHlsCodecString(codecString)); + for (const ref of decl.references) { + assert(ref.playlist.peakBitrate !== null); + peakDeclBitrate = Math.max(peakDeclBitrate, ref.playlist.peakBitrate); + maxRefAverageBitrate = Math.max(maxRefAverageBitrate, ref.playlist.averageBitrate ?? 0); + } + } + assert(decl.playlist.peakBitrate !== null); + const totalPeakBitrate = decl.playlist.peakBitrate + peakDeclBitrate; + const totalAverageBitrate = (decl.playlist.averageBitrate ?? 0) + maxRefAverageBitrate; + if (!firstVariantWritten) { + masterPlaylistText += "\n"; + firstVariantWritten = true; + } + if (isKeyPacketsOnly) masterPlaylistText += `#EXT-X-I-FRAME-STREAM-INF:`; + else masterPlaylistText += `#EXT-X-STREAM-INF:`; + masterPlaylistText += `BANDWIDTH=${Math.ceil(totalPeakBitrate)}`; + if (totalAverageBitrate > 0) masterPlaylistText += `,AVERAGE-BANDWIDTH=${Math.ceil(totalAverageBitrate)}`; + masterPlaylistText += `,CODECS="${codecs.join(",")}"`; + const videoTrack = decl.playlist.tracks.find((x) => x.isVideoTrack()); + if (videoTrack?.isVideoTrack()) { + const decoderConfig = this.trackDatas.find((x) => x.track === videoTrack)?.info.decoderConfig; + if (decoderConfig) { + let width = decoderConfig.displayAspectWidth ?? decoderConfig.codedWidth; + let height = decoderConfig.displayAspectHeight ?? decoderConfig.codedHeight; + if (width !== void 0 && height !== void 0) { + if (videoTrack.metadata.rotation !== void 0 && videoTrack.metadata.rotation % 180 === 90) [width, height] = [height, width]; + masterPlaylistText += `,RESOLUTION=${width}x${height}`; + } + } + if (!isKeyPacketsOnly && videoTrack.metadata.frameRate !== void 0) masterPlaylistText += `,FRAME-RATE=${+videoTrack.metadata.frameRate.toFixed(3)}`; + } + if (!isKeyPacketsOnly) { + const groupIdForType = /* @__PURE__ */ new Map(); + for (const ref of decl.references) { + assert(ref.groupId !== null); + const type = ref.playlist.tracks[0].type; + groupIdForType.set(type, ref.groupId); + } + for (const [type, id] of groupIdForType) masterPlaylistText += `,${type.toUpperCase()}="${id}"`; + } + if (isKeyPacketsOnly) { + masterPlaylistText += `,URI="${decl.playlist.path}"`; + masterPlaylistText += "\n"; + } else { + masterPlaylistText += "\n"; + masterPlaylistText += `${decl.playlist.path}\n`; + } + } else { + assert(decl.playlist.tracks.length === 1); + const track = decl.playlist.tracks[0]; + const type = track.type; + let name = track.metadata.name ?? null; + const languageCode = track.metadata.languageCode; + const disposition = track.metadata.disposition; + if (lastGroupId === null || decl.groupId !== lastGroupId) { + groupIdTrackCount = 0; + masterPlaylistText += "\n"; + hasHadDefaultTrackInGroup = false; + } + lastGroupId = decl.groupId; + groupIdTrackCount++; + masterPlaylistText += `#EXT-X-MEDIA:TYPE=${type.toUpperCase()},GROUP-ID="${decl.groupId}"`; + if (name !== null && /[\n\r"]/.test(name)) { + Logging._warn("Dropping track name since it includes a line feed, carriage return, or double quote character, which are not allowed in HLS playlist attributes."); + name = null; + } + name ??= `${languageCode ?? decl.groupId}-${groupIdTrackCount}`; + masterPlaylistText += `,NAME="${name}"`; + if (languageCode !== void 0) masterPlaylistText += `,LANGUAGE="${languageCode}"`; + const dispositionPrimary = disposition?.primary ?? false; + const dispositionDefault = disposition?.default ?? true; + const dispositionForced = disposition?.forced ?? false; + if (dispositionPrimary && !hasHadDefaultTrackInGroup) { + masterPlaylistText += ",DEFAULT=YES"; + hasHadDefaultTrackInGroup = true; + } + if (dispositionPrimary || dispositionDefault) masterPlaylistText += ",AUTOSELECT=YES"; + if (dispositionForced) masterPlaylistText += ",FORCED=YES"; + if (type === "audio") { + const decoderConfig = this.trackDatas.find((x) => x.track === track)?.info.decoderConfig; + if (decoderConfig) masterPlaylistText += `,CHANNELS="${decoderConfig.numberOfChannels}"`; + } + if (!decl.noUri) masterPlaylistText += `,URI="${decl.playlist.path}"`; + masterPlaylistText += "\n"; + } + this.format._options.onMaster?.(masterPlaylistText); + const release = await this.mutex.acquire(); + try { + let writer; + if (this.numWrittenMasterPlaylists === 0) writer = await this.output._getRootWriter(true); + else { + writer = new Writer(await this.output._getTarget({ + path: pathedTarget.rootPath, + isRoot: true, + mimeType: HLS_MIME_TYPE + }), true); + writer.start(); + } + writer.write(textEncoder.encode(masterPlaylistText)); + await writer.flush(); + await writer.finalize(); + this.numWrittenMasterPlaylists++; + } finally { + release(); + } + } + async tryWriteMasterPlaylist() { + assert(this.isLive); + for (const playlist of this.playlists) if (playlist.writtenSegments.length === 0 && !playlist.done) return; + await this.writeMasterPlaylist(); + } + async finalize() { + (await Promise.all(this.playlists.map((p) => p.mutex.acquire()))).forEach((release) => release()); + for (const trackData of this.trackDatas) trackData.closed = true; + await Promise.all(this.playlists.map((playlist) => playlist.done ? Promise.resolve() : this.advancePlaylist(playlist))); + if (!this.isLive) await this.writeMasterPlaylist(); + } +}; +var validatePlaylistPath = (path) => { + if (typeof path !== "string") throw new TypeError("options.getPlaylistPath must return or resolve to a string"); + if (/[\n\r"]/.test(path)) throw new TypeError("Playlist paths cannot contain line feed, carriage return, or double quote characters."); +}; +var validateSegmentPath = (path) => { + if (typeof path !== "string") throw new TypeError("options.getSegmentPath must return or resolve to a string"); + if (/[\n\r"]/.test(path)) throw new TypeError("Segment paths cannot contain line feed or carriage return characters."); +}; +var validateInitPath = (path) => { + if (typeof path !== "string") throw new TypeError("options.getInitPath must return or resolve to a string"); + if (/[\n\r"]/.test(path)) throw new TypeError("Init paths cannot contain line feed, carriage return, or double quote characters."); +}; +var toPlaylistInfo = (playlist) => { + return { + n: playlist.id, + tracks: playlist.tracks, + segmentFormat: playlist.segmentFormat + }; +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/output-format.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* Base class representing an output media file format. +* @group Output formats +* @public +*/ +var OutputFormat = class { + /** + * Whether this output format supports video rotation metadata. + * @deprecated Use {@link OutputFormat.supportsVideoTransformationMetadata} instead. + */ + get supportsVideoRotationMetadata() { + return this.supportsVideoTransformationMetadata; + } + /** Returns a list of video codecs that this output format can contain. */ + getSupportedVideoCodecs() { + return this.getSupportedCodecs().filter((codec) => VIDEO_CODECS.includes(codec)); + } + /** Returns a list of audio codecs that this output format can contain. */ + getSupportedAudioCodecs() { + return this.getSupportedCodecs().filter((codec) => AUDIO_CODECS.includes(codec)); + } + /** Returns a list of subtitle codecs that this output format can contain. */ + getSupportedSubtitleCodecs() { + return this.getSupportedCodecs().filter((codec) => SUBTITLE_CODECS.includes(codec)); + } + /** @internal */ + _codecUnsupportedHint(codec) { + return ""; + } + /** @internal */ + _isFragmentedIsobmff() { + return false; + } +}; +/** +* Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files. +* @group Output formats +* @public +*/ +var IsobmffOutputFormat = class extends OutputFormat { + /** Internal constructor. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.fastStart !== void 0 && ![ + false, + "in-memory", + "reserve", + "fragmented" + ].includes(options.fastStart)) throw new TypeError("options.fastStart, when provided, must be false, 'in-memory', 'reserve', or 'fragmented'."); + if (options.minimumFragmentDuration !== void 0 && (!isNumber(options.minimumFragmentDuration) || options.minimumFragmentDuration < 0)) throw new TypeError("options.minimumFragmentDuration, when provided, must be a non-negative number."); + if (options.onFtyp !== void 0 && typeof options.onFtyp !== "function") throw new TypeError("options.onFtyp, when provided, must be a function."); + if (options.onMoov !== void 0 && typeof options.onMoov !== "function") throw new TypeError("options.onMoov, when provided, must be a function."); + if (options.onMdat !== void 0 && typeof options.onMdat !== "function") throw new TypeError("options.onMdat, when provided, must be a function."); + if (options.onMoof !== void 0 && typeof options.onMoof !== "function") throw new TypeError("options.onMoof, when provided, must be a function."); + if (options.metadataFormat !== void 0 && ![ + "mdir", + "mdta", + "udta", + "auto" + ].includes(options.metadataFormat)) throw new TypeError("options.metadataFormat, when provided, must be either 'auto', 'mdir', 'mdta', or 'udta'."); + super(); + this._options = options; + } + getSupportedTrackCounts() { + const max = 2 ** 32 - 1; + return { + video: { + min: 0, + max + }, + audio: { + min: 0, + max + }, + subtitle: { + min: 0, + max + }, + total: { + min: 0, + max + } + }; + } + get supportsVideoTransformationMetadata() { + return true; + } + get supportsTimestampedMediaData() { + return true; + } + get negativeTimestampSupport() { + return "full"; + } + /** @internal */ + _createMuxer(output) { + return new IsobmffMuxer(output, this); + } + /** @internal */ + _isFragmentedIsobmff() { + return this._options.fastStart === "fragmented"; + } +}; +/** +* MPEG-4 Part 14 (MP4) file format. Supports most codecs. +* @group Output formats +* @public +*/ +var Mp4OutputFormat = class extends IsobmffOutputFormat { + /** Creates a new {@link Mp4OutputFormat} configured with the specified `options`. */ + constructor(options) { + super(options); + } + /** @internal */ + get _name() { + return "MP4"; + } + get fileExtension() { + return ".mp4"; + } + get mimeType() { + return "video/mp4"; + } + getSupportedCodecs() { + return [ + ...VIDEO_CODECS, + ...NON_PCM_AUDIO_CODECS, + "pcm-s16", + "pcm-s16be", + "pcm-s24", + "pcm-s24be", + "pcm-s32", + "pcm-s32be", + "pcm-f32", + "pcm-f32be", + "pcm-f64", + "pcm-f64be", + ...SUBTITLE_CODECS + ]; + } + /** @internal */ + _codecUnsupportedHint(codec) { + if (new MovOutputFormat().getSupportedCodecs().includes(codec)) return " Switching to MOV will grant support for this codec."; + return ""; + } +}; +/** +* Creates a single Common Media Application Format (CMAF) segment. An init segment will be written to the +* {@link Target} specified in {@link OutputOptions.initTarget}. Supports most codecs. +* @group Output formats +* @public +*/ +var CmafOutputFormat = class extends IsobmffOutputFormat { + /** Creates a new {@link CmafOutputFormat} configured with the specified `options`. */ + constructor(options) { + super(options); + } + /** @internal */ + get _name() { + return "CMAF"; + } + get fileExtension() { + return ".m4s"; + } + get mimeType() { + return "video/mp4"; + } + getSupportedCodecs() { + return [ + ...VIDEO_CODECS, + ...NON_PCM_AUDIO_CODECS, + "pcm-s16", + "pcm-s16be", + "pcm-s24", + "pcm-s24be", + "pcm-s32", + "pcm-s32be", + "pcm-f32", + "pcm-f32be", + "pcm-f64", + "pcm-f64be", + ...SUBTITLE_CODECS + ]; + } +}; +/** +* QuickTime File Format (QTFF), often called MOV. Supports all video and audio codecs, but not subtitle codecs. +* @group Output formats +* @public +*/ +var MovOutputFormat = class extends IsobmffOutputFormat { + /** Creates a new {@link MovOutputFormat} configured with the specified `options`. */ + constructor(options) { + super(options); + } + /** @internal */ + get _name() { + return "MOV"; + } + get fileExtension() { + return ".mov"; + } + get mimeType() { + return "video/quicktime"; + } + getSupportedCodecs() { + return [...VIDEO_CODECS, ...AUDIO_CODECS]; + } + /** @internal */ + _codecUnsupportedHint(codec) { + if (new Mp4OutputFormat().getSupportedCodecs().includes(codec)) return " Switching to MP4 will grant support for this codec."; + return ""; + } +}; +/** +* Matroska file format. +* +* Supports writing transparent video. For a video track to be marked as transparent, the first packet added must +* contain alpha side data. +* +* @group Output formats +* @public +*/ +var MkvOutputFormat = class extends OutputFormat { + /** Creates a new {@link MkvOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.appendOnly !== void 0 && typeof options.appendOnly !== "boolean") throw new TypeError("options.appendOnly, when provided, must be a boolean."); + if (options.minimumClusterDuration !== void 0 && (!isNumber(options.minimumClusterDuration) || options.minimumClusterDuration < 0)) throw new TypeError("options.minimumClusterDuration, when provided, must be a non-negative number."); + if (options.onEbmlHeader !== void 0 && typeof options.onEbmlHeader !== "function") throw new TypeError("options.onEbmlHeader, when provided, must be a function."); + if (options.onSegmentHeader !== void 0 && typeof options.onSegmentHeader !== "function") throw new TypeError("options.onHeader, when provided, must be a function."); + if (options.onCluster !== void 0 && typeof options.onCluster !== "function") throw new TypeError("options.onCluster, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new MatroskaMuxer(output, this); + } + /** @internal */ + get _name() { + return "Matroska"; + } + getSupportedTrackCounts() { + const max = 127; + return { + video: { + min: 0, + max + }, + audio: { + min: 0, + max + }, + subtitle: { + min: 0, + max + }, + total: { + min: 0, + max + } + }; + } + get fileExtension() { + return ".mkv"; + } + get mimeType() { + return "video/x-matroska"; + } + getSupportedCodecs() { + return [ + ...VIDEO_CODECS, + ...NON_PCM_AUDIO_CODECS, + ...PCM_AUDIO_CODECS.filter((codec) => ![ + "pcm-s8", + "pcm-f32be", + "pcm-f64be", + "ulaw", + "alaw" + ].includes(codec)), + ...SUBTITLE_CODECS + ]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return true; + } + get negativeTimestampSupport() { + return "prefer-non-negative"; + } +}; +/** +* WebM file format, based on Matroska. +* +* Supports writing transparent video. For a video track to be marked as transparent, the first packet added must +* contain alpha side data. +* +* @group Output formats +* @public +*/ +var WebMOutputFormat = class extends MkvOutputFormat { + /** Creates a new {@link WebMOutputFormat} configured with the specified `options`. */ + constructor(options) { + super(options); + } + getSupportedCodecs() { + return [ + ...VIDEO_CODECS.filter((codec) => [ + "vp8", + "vp9", + "av1" + ].includes(codec)), + ...AUDIO_CODECS.filter((codec) => ["opus", "vorbis"].includes(codec)), + ...SUBTITLE_CODECS + ]; + } + /** @internal */ + get _name() { + return "WebM"; + } + get fileExtension() { + return ".webm"; + } + get mimeType() { + return "video/webm"; + } + /** @internal */ + _codecUnsupportedHint(codec) { + if (new MkvOutputFormat().getSupportedCodecs().includes(codec)) return " Switching to MKV will grant support for this codec."; + return ""; + } +}; +/** +* MP3 file format. +* @group Output formats +* @public +*/ +var Mp3OutputFormat = class extends OutputFormat { + /** Creates a new {@link Mp3OutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.xingHeader !== void 0 && typeof options.xingHeader !== "boolean") throw new TypeError("options.xingHeader, when provided, must be a boolean."); + if (options.onXingFrame !== void 0 && typeof options.onXingFrame !== "function") throw new TypeError("options.onXingFrame, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new Mp3Muxer(output, this); + } + /** @internal */ + get _name() { + return "MP3"; + } + getSupportedTrackCounts() { + return { + video: { + min: 0, + max: 0 + }, + audio: { + min: 1, + max: 1 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 1, + max: 1 + } + }; + } + get fileExtension() { + return ".mp3"; + } + get mimeType() { + return "audio/mpeg"; + } + getSupportedCodecs() { + return ["mp3"]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return false; + } + get negativeTimestampSupport() { + return null; + } +}; +/** +* WAVE file format, based on RIFF. +* @group Output formats +* @public +*/ +var WavOutputFormat = class extends OutputFormat { + /** Creates a new {@link WavOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.large !== void 0 && typeof options.large !== "boolean") throw new TypeError("options.large, when provided, must be a boolean."); + if (options.metadataFormat !== void 0 && !["info", "id3"].includes(options.metadataFormat)) throw new TypeError("options.metadataFormat, when provided, must be either 'info' or 'id3'."); + if (options.onHeader !== void 0 && typeof options.onHeader !== "function") throw new TypeError("options.onHeader, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new WaveMuxer(output, this); + } + /** @internal */ + get _name() { + return "WAVE"; + } + getSupportedTrackCounts() { + return { + video: { + min: 0, + max: 0 + }, + audio: { + min: 1, + max: 1 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 1, + max: 1 + } + }; + } + get fileExtension() { + return ".wav"; + } + get mimeType() { + return "audio/wav"; + } + getSupportedCodecs() { + return [...PCM_AUDIO_CODECS.filter((codec) => [ + "pcm-s16", + "pcm-s24", + "pcm-s32", + "pcm-f32", + "pcm-f64", + "pcm-u8", + "ulaw", + "alaw" + ].includes(codec))]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return false; + } + get negativeTimestampSupport() { + return null; + } +}; +/** +* Ogg file format. +* @group Output formats +* @public +*/ +var OggOutputFormat = class extends OutputFormat { + /** Creates a new {@link OggOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.maximumPageDuration !== void 0 && (!isNumber(options.maximumPageDuration) || options.maximumPageDuration <= 0)) throw new TypeError("options.maximumPageDuration, when provided, must be a positive number."); + if (options.onPage !== void 0 && typeof options.onPage !== "function") throw new TypeError("options.onPage, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new OggMuxer(output, this); + } + /** @internal */ + get _name() { + return "Ogg"; + } + getSupportedTrackCounts() { + const max = 2 ** 32; + return { + video: { + min: 0, + max: 0 + }, + audio: { + min: 0, + max + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 0, + max + } + }; + } + get fileExtension() { + return ".ogg"; + } + get mimeType() { + return "application/ogg"; + } + getSupportedCodecs() { + return [...AUDIO_CODECS.filter((codec) => ["vorbis", "opus"].includes(codec))]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return true; + } + get negativeTimestampSupport() { + return "none"; + } +}; +/** +* ADTS file format. +* @group Output formats +* @public +*/ +var AdtsOutputFormat = class extends OutputFormat { + /** Creates a new {@link AdtsOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.onFrame !== void 0 && typeof options.onFrame !== "function") throw new TypeError("options.onFrame, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new AdtsMuxer(output, this); + } + /** @internal */ + get _name() { + return "ADTS"; + } + getSupportedTrackCounts() { + return { + video: { + min: 0, + max: 0 + }, + audio: { + min: 1, + max: 1 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 1, + max: 1 + } + }; + } + get fileExtension() { + return ".aac"; + } + get mimeType() { + return "audio/aac"; + } + getSupportedCodecs() { + return ["aac"]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return false; + } + get negativeTimestampSupport() { + return null; + } +}; +/** +* FLAC file format. +* @group Output formats +* @public +*/ +var FlacOutputFormat = class extends OutputFormat { + /** Creates a new {@link FlacOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.appendOnly !== void 0 && typeof options.appendOnly !== "boolean") throw new TypeError("options.appendOnly, when provided, must be a boolean."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new FlacMuxer(output, this); + } + /** @internal */ + get _name() { + return "FLAC"; + } + getSupportedTrackCounts() { + return { + video: { + min: 0, + max: 0 + }, + audio: { + min: 1, + max: 1 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 1, + max: 1 + } + }; + } + get fileExtension() { + return ".flac"; + } + get mimeType() { + return "audio/flac"; + } + getSupportedCodecs() { + return ["flac"]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return false; + } + get negativeTimestampSupport() { + return null; + } +}; +/** +* MPEG Transport Stream file format. +* @group Output formats +* @public +*/ +var MpegTsOutputFormat = class extends OutputFormat { + /** Creates a new {@link MpegTsOutputFormat} configured with the specified `options`. */ + constructor(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.onPacket !== void 0 && typeof options.onPacket !== "function") throw new TypeError("options.onPacket, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new MpegTsMuxer(output, this); + } + /** @internal */ + get _name() { + return "MPEG-TS"; + } + getSupportedTrackCounts() { + return { + video: { + min: 0, + max: 16 + }, + audio: { + min: 0, + max: 32 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 0, + max: 48 + } + }; + } + get fileExtension() { + return ".ts"; + } + get mimeType() { + return "video/MP2T"; + } + getSupportedCodecs() { + return [...VIDEO_CODECS.filter((codec) => ["avc", "hevc"].includes(codec)), ...AUDIO_CODECS.filter((codec) => [ + "aac", + "mp3", + "ac3", + "eac3", + "dts" + ].includes(codec))]; + } + get supportsVideoTransformationMetadata() { + return false; + } + get supportsTimestampedMediaData() { + return true; + } + get negativeTimestampSupport() { + return "prefer-non-negative"; + } +}; +/** +* HTTP Live Streaming (HLS) output format. HLS media is represented by a set of .m3u8 playlist files and media segment +* files, meaning this format writes out multiple files, requiring the use of a _pathed Output_ +* ({@link OutputOptions.target} must be a {@link PathedTarget}). +* +* This output format creates the following files: +* - A master playlist .m3u8 file, containing the list of available playlists. A master playlist is always emitted, +* written to the root path. +* - One .m3u8 file for each playlist, each containing a list of media segments. +* - Many media segments, containing the actual media data. +* +* To emit media playlists that use the `#EXT-X-PROGRAM-DATE-TIME` tag to map segment timestamps to real-world time, +* set {@link BaseTrackMetadata.isRelativeToUnixEpoch} to `true` for all tracks. +* +* @group Output formats +* @public +*/ +var HlsOutputFormat = class extends OutputFormat { + /** Creates a new {@link HlsOutputFormat} configured with the specified `options`. */ + constructor(options) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!(options.segmentFormat instanceof OutputFormat) && (!Array.isArray(options.segmentFormat) || options.segmentFormat.length === 0 || !options.segmentFormat.every((format) => format instanceof OutputFormat))) throw new TypeError("options.segmentFormat must be an OutputFormat or a non-empty array of OutputFormat instances."); + if (options.targetDuration !== void 0 && (typeof options.targetDuration !== "number" || options.targetDuration <= 0)) throw new TypeError("options.targetDuration, when provided, must be a positive number."); + if (options.singleFilePerPlaylist !== void 0 && typeof options.singleFilePerPlaylist !== "boolean") throw new TypeError("options.singleFilePerPlaylist, when provided, must be a boolean."); + if (options.live !== void 0 && typeof options.live !== "boolean") throw new TypeError("options.live, when provided, must be a boolean."); + if (options.maxLiveSegmentCount !== void 0 && (typeof options.maxLiveSegmentCount !== "number" || options.maxLiveSegmentCount < 1 || Number.isFinite(options.maxLiveSegmentCount) && !Number.isInteger(options.maxLiveSegmentCount))) throw new TypeError("options.maxLiveSegmentCount, when provided, must be a positive integer or Infinity."); + if (options.getPlaylistPath !== void 0 && typeof options.getPlaylistPath !== "function") throw new TypeError("options.getPlaylistPath, when provided, must be a function."); + if (options.getSegmentPath !== void 0 && typeof options.getSegmentPath !== "function") throw new TypeError("options.getSegmentPath, when provided, must be a function."); + if (options.getInitPath !== void 0 && typeof options.getInitPath !== "function") throw new TypeError("options.getInitPath, when provided, must be a function."); + if (options.onMaster !== void 0 && typeof options.onMaster !== "function") throw new TypeError("options.onMaster, when provided, must be a function."); + if (options.onPlaylist !== void 0 && typeof options.onPlaylist !== "function") throw new TypeError("options.onPlaylist, when provided, must be a function."); + if (options.onSegment !== void 0 && typeof options.onSegment !== "function") throw new TypeError("options.onSegment, when provided, must be a function."); + if (options.onInit !== void 0 && typeof options.onInit !== "function") throw new TypeError("options.onInit, when provided, must be a function."); + if (options.onSegmentPopped !== void 0 && typeof options.onSegmentPopped !== "function") throw new TypeError("options.onSegmentPopped, when provided, must be a function."); + super(); + this._options = options; + } + /** @internal */ + _createMuxer(output) { + return new HlsMuxer(output, this); + } + /** @internal */ + get _name() { + return "HTTP Live Streaming (HLS)"; + } + get fileExtension() { + return ".m3u8"; + } + get mimeType() { + return HLS_MIME_TYPE; + } + getSupportedCodecs() { + return [...new Set(toArray(this._options.segmentFormat).flatMap((x) => x.getSupportedCodecs()))]; + } + getSupportedTrackCounts() { + let supportsVideo = false; + let supportsAudio = false; + let supportsSubtitle = false; + for (const format of toArray(this._options.segmentFormat)) { + const trackCounts = format.getSupportedTrackCounts(); + supportsVideo ||= trackCounts.video.max > 0; + supportsAudio ||= trackCounts.audio.max > 0; + supportsSubtitle ||= trackCounts.subtitle.max > 0; + } + return { + video: { + min: 0, + max: supportsVideo ? Infinity : 0 + }, + audio: { + min: 0, + max: supportsAudio ? Infinity : 0 + }, + subtitle: { + min: 0, + max: 0 + }, + total: { + min: 0, + max: Infinity + } + }; + } + get supportsVideoTransformationMetadata() { + return toArray(this._options.segmentFormat).some((format) => format.supportsVideoTransformationMetadata); + } + get supportsTimestampedMediaData() { + return true; + } + get negativeTimestampSupport() { + const formats = toArray(this._options.segmentFormat); + if (formats.some((format) => format.negativeTimestampSupport === "none")) return "none"; + if (formats.some((format) => format.negativeTimestampSupport === "prefer-non-negative")) return "prefer-non-negative"; + if (formats.some((format) => format.negativeTimestampSupport === "full")) return "full"; + return null; + } + /** @internal */ + _codecUnsupportedHint(codec) { + return ` Using different segment formats may grant support for this codec.`; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/output.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/** +* List of all track types. +* @group Miscellaneous +* @public +*/ +var ALL_TRACK_TYPES = [ + "video", + "audio", + "subtitle" +]; +/** +* Represents a track added to an {@link Output}. +* @group Output files +* @public +*/ +var OutputTrack = class OutputTrack { + /** @internal */ + constructor(id, output, type, source, metadata) { + this.id = id; + this.output = output; + this.type = type; + this.source = source; + this.metadata = metadata; + } + /** Returns true if and only if this track is a video track. */ + isVideoTrack() { + return this.type === "video"; + } + /** Returns true if and only if this track is an audio track. */ + isAudioTrack() { + return this.type === "audio"; + } + /** Returns true if and only if this track is a subtitle track. */ + isSubtitleTrack() { + return this.type === "subtitle"; + } + /** + * Returns true if and only if this track can be paired with the given other track. Pairability can be set using + * the {@link BaseTrackMetadata.group} option. + */ + canBePairedWith(other) { + if (!(other instanceof OutputTrack)) throw new TypeError("other must be an OutputTrack."); + if (this === other) return false; + const thisGroups = toArray(this.metadata.group); + const otherGroups = toArray(other.metadata.group); + for (const aGroup of thisGroups) { + if (this.type !== other.type && otherGroups.some((bGroup) => aGroup === bGroup)) return true; + if (otherGroups.some((bGroup) => aGroup._pairedGroups.has(bGroup))) return true; + } + return false; + } +}; +/** +* An {@link OutputTrack} providing video data, created using {@link Output.addVideoTrack}. +* @group Output files +* @public +*/ +var OutputVideoTrack = class extends OutputTrack { + /** @internal */ + constructor(id, output, source, metadata) { + super(id, output, "video", source, metadata); + } +}; +/** +* An {@link OutputTrack} providing audio data, created using {@link Output.addAudioTrack}. +* @group Output files +* @public +*/ +var OutputAudioTrack = class extends OutputTrack { + /** @internal */ + constructor(id, output, source, metadata) { + super(id, output, "audio", source, metadata); + } +}; +/** +* An {@link OutputTrack} providing subtitle data, created using {@link Output.addSubtitleTrack}. +* @group Output files +* @public +*/ +var OutputSubtitleTrack = class extends OutputTrack { + /** @internal */ + constructor(id, output, source, metadata) { + super(id, output, "subtitle", source, metadata); + } +}; +/** +* Used to define pairability between {@link OutputTrack} instances. First create the group, then assign tracks to it +* via {@link BaseTrackMetadata.group}. +* +* Two tracks are considered _pairable_ if they are in the same group but have a different {@link TrackType}, or if they +* are in different groups that are paired with each other. Groups can be paired with each other using the +* {@link OutputTrackGroup.pairWith} method. +* +* @group Output files +* @public +*/ +var OutputTrackGroup = class OutputTrackGroup { + /** Creates a new {@link OutputTrackGroup}. */ + constructor() { + /** @internal */ + this._pairedGroups = /* @__PURE__ */ new Set(); + } + /** + * Marks this group as being pairable with another group, symmetrically. Output tracks where each track is assigned + * to one half of a group pairing are then considered pairable. + * + * You cannot pair a group with itself. + */ + pairWith(other) { + if (!(other instanceof OutputTrackGroup)) throw new TypeError("other must be an OutputTrackGroup."); + if (this === other) throw new TypeError("Cannot pair a group with itself."); + this._pairedGroups.add(other); + other._pairedGroups.add(this); + } +}; +var validateBaseTrackMetadata = (metadata) => { + if (!metadata || typeof metadata !== "object") throw new TypeError("metadata must be an object."); + if (metadata.languageCode !== void 0 && !isIso639Dash2LanguageCode(metadata.languageCode)) throw new TypeError("metadata.languageCode, when provided, must be a three-letter, ISO 639-2/T language code."); + if (metadata.name !== void 0 && typeof metadata.name !== "string") throw new TypeError("metadata.name, when provided, must be a string."); + if (metadata.disposition !== void 0) validateTrackDisposition(metadata.disposition); + if (metadata.maximumPacketCount !== void 0 && (!Number.isInteger(metadata.maximumPacketCount) || metadata.maximumPacketCount < 0)) throw new TypeError("metadata.maximumPacketCount, when provided, must be a non-negative integer."); + if (metadata.bitrate !== void 0 && (!Number.isFinite(metadata.bitrate) || metadata.bitrate < 0)) throw new TypeError("metadata.bitrate, when provided, must be a non-negative number."); + if (metadata.averageBitrate !== void 0 && (!Number.isFinite(metadata.averageBitrate) || metadata.averageBitrate < 0)) throw new TypeError("metadata.averageBitrate, when provided, must be a non-negative number."); + if (metadata.group !== void 0 && !(metadata.group instanceof OutputTrackGroup) && (!Array.isArray(metadata.group) || metadata.group.some((group) => !(group instanceof OutputTrackGroup)))) throw new TypeError("metadata.group, when provided, must be an OutputTrackGroup instance or an array of OutputTrackGroup instances."); +}; +/** +* Main class orchestrating the creation of new media files. +* @group Output files +* @public +*/ +var Output = class extends EventEmitter { + /** + * The target to which the root file will be written. Throws when using {@link PathedTarget} with an async callback; + * prefer the `'target'` event for those cases. + */ + get target() { + const errorMessage = "Output.target cannot be used when using PathedTarget with an async callback. Use the 'target' event instead."; + if (this._rootTargetPromise) throw new TypeError(errorMessage); + const rootTargetResult = this._getRootTarget(); + if (isThenable(rootTargetResult)) throw new TypeError(errorMessage); + return rootTargetResult; + } + /** + * Creates a new instance of {@link Output} which can then be used to create a new media file according to the + * specified {@link OutputOptions}. + */ + constructor(options) { + super(); + /** The current state of the output. */ + this.state = "pending"; + /** + * The {@link OutputTrackGroup} that all tracks are assigned to by default unless otherwise specified by + * {@link BaseTrackMetadata.group}. + */ + this.defaultTrackGroup = new OutputTrackGroup(); + /** + * The tracks that have been added to this output. Treat it as a readonly field; to add tracks, use the methods. + */ + this.tracks = []; + /** @internal */ + this._onFinalize = null; + /** @internal */ + this._unfinalizedTargets = /* @__PURE__ */ new Set(); + /** @internal */ + this._rootWriterPromise = null; + /** @internal */ + this._startPromise = null; + /** @internal */ + this._cancelPromise = null; + /** @internal */ + this._finalizePromise = null; + /** @internal */ + this._mutex = new AsyncMutex(); + /** @internal */ + this._metadataTags = {}; + /** @internal */ + this._rootTarget = null; + /** @internal */ + this._rootTargetPromise = null; + /** + * This field is used to synchronize multiple MediaStreamTracks. They use the same time coordinate system across + * tracks, and to ensure correct audio-video sync, we must use the same offset for all of them. The reason an offset + * is needed at all is because the timestamps typically don't start at zero. + * @internal + */ + this._firstMediaStreamTimestamp = null; + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!(options.format instanceof OutputFormat)) throw new TypeError("options.format must be an OutputFormat."); + if (!(options.target instanceof Target || options.target instanceof PathedTarget)) throw new TypeError("options.target must be a Target or a PathedTarget."); + if (options.target instanceof Target) this._rememberTarget(options.target); + if (options.initTarget !== void 0 && !(options.initTarget instanceof Target) && typeof options.initTarget !== "function") throw new Error("options.initTarget, when provided, must be a Target or a function that returns or resolves to a Target."); + if (options.onFinalize !== void 0 && typeof options.onFinalize !== "function") throw new TypeError("options.onFinalize, when provided, must be a function."); + this.format = options.format; + this._target = options.target; + this._onFinalize = options.onFinalize ?? null; + this._initTarget = options.initTarget ?? null; + if (this._initTarget instanceof Target) this._rememberTarget(this._initTarget); + this._muxer = options.format._createMuxer(this); + } + /** @internal */ + _getTargetValidated(request) { + assert(this._target instanceof PathedTarget); + const result = this._target.getTarget(request); + const handleResult = (result) => { + if (!(result instanceof Target)) throw new TypeError("getTarget must return a Target."); + return result; + }; + if (isThenable(result)) return result.then(handleResult); + else return handleResult(result); + } + /** @internal */ + async _getTarget(request) { + assert(this._target instanceof PathedTarget); + const target = await this._getTargetValidated(request); + this._emit("target", { + target, + request, + isRoot: request.isRoot + }); + if (this.state === "canceled") await target._close(); + else this._rememberTarget(target); + return target; + } + /** @internal */ + _rememberTarget(target) { + this._unfinalizedTargets.add(target); + target.on("finalized", () => this._unfinalizedTargets.delete(target), { once: true }); + } + /** @internal */ + async _getInitTarget() { + assert(this._initTarget !== null); + if (this._initTarget instanceof Target) return this._initTarget; + const target = await this._initTarget(); + if (this.state === "canceled") await target._close(); + else this._rememberTarget(target); + return target; + } + /** @internal */ + _hasInitTarget() { + return this._initTarget !== null; + } + /** @internal */ + _getRootTarget() { + if (this._rootTarget) return this._rootTarget; + if (this._rootTargetPromise) return this._rootTargetPromise; + if (this._target instanceof Target) { + this._emit("target", { + target: this._target, + request: null, + isRoot: true + }); + this._rootTarget = this._target; + return this._target; + } + const request = { + path: this._target.rootPath, + isRoot: true, + mimeType: this.format.mimeType + }; + const result = this._getTargetValidated(request); + const handleResult = (target) => { + if (this.state === "canceled") target._close(); + else this._rememberTarget(target); + this._emit("target", { + target, + request, + isRoot: true + }); + this._rootTarget = target; + return target; + }; + if (isThenable(result)) return this._rootTargetPromise = result.then(handleResult); + else return handleResult(result); + } + /** @internal */ + _getRootWriter(isMonotonic) { + return this._rootWriterPromise ??= (async () => { + const target = await this._getRootTarget(); + const writer = new Writer(target, typeof isMonotonic === "boolean" ? isMonotonic : isMonotonic(target)); + writer.start(); + return writer; + })(); + } + /** Adds a video track to the output with the given source. Can only be called before the output is started. */ + addVideoTrack(source, metadata = {}) { + if (!(source instanceof VideoSource)) throw new TypeError("source must be a VideoSource."); + validateBaseTrackMetadata(metadata); + if (metadata.rotation !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(metadata.rotation)) throw new TypeError(`Invalid video rotation: ${metadata.rotation}. Has to be 0, 90, 180 or 270.`); + if (metadata.flip !== void 0 && typeof metadata.flip !== "boolean") throw new TypeError("metadata.flip, when provided, must be a boolean."); + if (metadata.transformationMatrix !== void 0 && (!Array.isArray(metadata.transformationMatrix) || metadata.transformationMatrix.length !== 9 || !metadata.transformationMatrix.every((x) => Number.isFinite(x)))) throw new TypeError("metadata.transformationMatrix, when provided, must be an array of 9 finite numbers."); + if (metadata.frameRate !== void 0 && (!Number.isFinite(metadata.frameRate) || metadata.frameRate <= 0)) throw new TypeError(`Invalid video frame rate: ${metadata.frameRate}. Must be a positive number.`); + if (metadata.hasOnlyKeyPackets !== void 0 && typeof metadata.hasOnlyKeyPackets !== "boolean") throw new TypeError("metadata.hasOnlyKeyPackets, when provided, must be a boolean."); + if (metadata.canBeTransparent !== void 0 && typeof metadata.canBeTransparent !== "boolean") throw new TypeError("metadata.canBeTransparent, when provided, must be a boolean."); + if (metadata.decoderConfig !== void 0) validateVideoChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec); + if (metadata.primingPacket !== void 0) { + if (!(metadata.primingPacket instanceof EncodedPacket)) throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket."); + if (metadata.decoderConfig === void 0) throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig."); + } + const metadataCopy = { ...metadata }; + metadataCopy.group ??= this.defaultTrackGroup; + return this._addTrack(new OutputVideoTrack(this.tracks.length + 1, this, source, metadataCopy)); + } + /** Adds an audio track to the output with the given source. Can only be called before the output is started. */ + addAudioTrack(source, metadata = {}) { + if (!(source instanceof AudioSource)) throw new TypeError("source must be an AudioSource."); + validateBaseTrackMetadata(metadata); + if (metadata.decoderConfig !== void 0) validateAudioChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec); + if (metadata.primingPacket !== void 0) { + if (!(metadata.primingPacket instanceof EncodedPacket)) throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket."); + if (metadata.decoderConfig === void 0) throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig."); + } + const metadataCopy = { ...metadata }; + metadataCopy.group ??= this.defaultTrackGroup; + return this._addTrack(new OutputAudioTrack(this.tracks.length + 1, this, source, metadataCopy)); + } + /** Adds a subtitle track to the output with the given source. Can only be called before the output is started. */ + addSubtitleTrack(source, metadata = {}) { + if (!(source instanceof SubtitleSource)) throw new TypeError("source must be a SubtitleSource."); + validateBaseTrackMetadata(metadata); + const metadataCopy = { ...metadata }; + metadataCopy.group ??= this.defaultTrackGroup; + return this._addTrack(new OutputSubtitleTrack(this.tracks.length + 1, this, source, metadataCopy)); + } + /** + * Sets descriptive metadata tags about the media file, such as title, author, date, or cover art. When called + * multiple times, only the metadata from the last call will be used. + * + * Can only be called before the output is started. + */ + setMetadataTags(tags) { + validateMetadataTags(tags); + if (this.state !== "pending") throw new Error("Cannot set metadata tags after output has been started or canceled."); + this._metadataTags = tags; + } + /** @internal */ + _addTrack(track) { + if (this.state !== "pending") throw new Error("Cannot add track after output has been started or canceled."); + if (track.source._connectedTrack) throw new Error("Source is already used for a track."); + const supportedTrackCounts = this.format.getSupportedTrackCounts(); + const presentTracksOfThisType = this.tracks.reduce((count, t) => count + (t.type === track.type ? 1 : 0), 0); + const maxCount = supportedTrackCounts[track.type].max; + if (presentTracksOfThisType === maxCount) throw new Error(maxCount === 0 ? `${this.format._name} does not support ${track.type} tracks.` : `${this.format._name} does not support more than ${maxCount} ${track.type} track${maxCount === 1 ? "" : "s"}.`); + const maxTotalCount = supportedTrackCounts.total.max; + if (this.tracks.length === maxTotalCount) throw new Error(`${this.format._name} does not support more than ${maxTotalCount} tracks${maxTotalCount === 1 ? "" : "s"} in total.`); + if (track.isVideoTrack()) { + const supportedVideoCodecs = this.format.getSupportedVideoCodecs(); + if (supportedVideoCodecs.length === 0) throw new Error(`${this.format._name} does not support video tracks.` + this.format._codecUnsupportedHint(track.source._codec)); + else if (!supportedVideoCodecs.includes(track.source._codec)) throw new Error(`Codec '${track.source._codec}' cannot be contained within ${this.format._name}. Supported video codecs are: ${supportedVideoCodecs.map((codec) => `'${codec}'`).join(", ")}.` + this.format._codecUnsupportedHint(track.source._codec)); + } else if (track.isAudioTrack()) { + const supportedAudioCodecs = this.format.getSupportedAudioCodecs(); + if (supportedAudioCodecs.length === 0) throw new Error(`${this.format._name} does not support audio tracks.` + this.format._codecUnsupportedHint(track.source._codec)); + else if (!supportedAudioCodecs.includes(track.source._codec)) throw new Error(`Codec '${track.source._codec}' cannot be contained within ${this.format._name}. Supported audio codecs are: ${supportedAudioCodecs.map((codec) => `'${codec}'`).join(", ")}.` + this.format._codecUnsupportedHint(track.source._codec)); + } else if (track.isSubtitleTrack()) { + const supportedSubtitleCodecs = this.format.getSupportedSubtitleCodecs(); + if (supportedSubtitleCodecs.length === 0) throw new Error(`${this.format._name} does not support subtitle tracks.` + this.format._codecUnsupportedHint(track.source._codec)); + else if (!supportedSubtitleCodecs.includes(track.source._codec)) throw new Error(`Codec '${track.source._codec}' cannot be contained within ${this.format._name}. Supported subtitle codecs are: ${supportedSubtitleCodecs.map((codec) => `'${codec}'`).join(", ")}.` + this.format._codecUnsupportedHint(track.source._codec)); + } + this.tracks.push(track); + track.source._connectedTrack = track; + return track; + } + /** + * Whether the output has enough tracks (of the correct type) to be started, based on the requirements of the output + * format. + */ + hasEnoughTracks() { + const supportedTrackCounts = this.format.getSupportedTrackCounts(); + for (const trackType of ALL_TRACK_TYPES) if (this.tracks.reduce((count, track) => count + (track.type === trackType ? 1 : 0), 0) < supportedTrackCounts[trackType].min) return false; + const totalMinCount = supportedTrackCounts.total.min; + if (this.tracks.length < totalMinCount) return false; + return true; + } + /** + * Starts the creation of the output file. This method should be called after all tracks have been added. Only after + * the output has started can media samples be added to the tracks. + * + * @returns A promise that resolves when the output has successfully started and is ready to receive media samples. + */ + async start() { + const supportedTrackCounts = this.format.getSupportedTrackCounts(); + for (const trackType of ALL_TRACK_TYPES) { + const presentTracksOfThisType = this.tracks.reduce((count, track) => count + (track.type === trackType ? 1 : 0), 0); + const minCount = supportedTrackCounts[trackType].min; + if (presentTracksOfThisType < minCount) throw new Error(minCount === supportedTrackCounts[trackType].max ? `${this.format._name} requires exactly ${minCount} ${trackType} track${minCount === 1 ? "" : "s"}.` : `${this.format._name} requires at least ${minCount} ${trackType} track${minCount === 1 ? "" : "s"}.`); + } + const totalMinCount = supportedTrackCounts.total.min; + if (this.tracks.length < totalMinCount) throw new Error(totalMinCount === supportedTrackCounts.total.max ? `${this.format._name} requires exactly ${totalMinCount} track${totalMinCount === 1 ? "" : "s"}.` : `${this.format._name} requires at least ${totalMinCount} track${totalMinCount === 1 ? "" : "s"}.`); + if (this.state === "canceled") throw new Error("Output has been canceled."); + if (this._startPromise) { + Logging._warn("Output has already been started."); + return this._startPromise; + } + return this._startPromise = (async () => { + this.state = "started"; + const releasePromise = this._mutex.acquire(); + try { + await this._muxer.start(); + const promises = this.tracks.map((track) => track.source._start()); + await Promise.all(promises); + } finally { + (await releasePromise)(); + } + })(); + } + /** + * Resolves with the full MIME type of the output file, including track codecs. + * + * The returned promise will resolve only once the precise codec strings of all tracks are known. + */ + getMimeType() { + return this._muxer.getMimeType(); + } + /** + * Cancels the creation of the output file, releasing internal resources like encoders and preventing further + * samples from being added. + * + * @returns A promise that resolves once all internal resources have been released. + */ + async cancel() { + if (this.state === "canceled") return this._cancelPromise ?? void 0; + else if (this.state === "finalizing" || this.state === "finalized") { + if (this.state === "finalized") Logging._warn("Output has already been finalized."); + return; + } + return this._cancelPromise = (async () => { + this.state = "canceled"; + const release = await this._mutex.acquire(); + try { + const promises = this.tracks.map((x) => x.source._flushOrWaitForOngoingClose(true)); + await Promise.all(promises); + await Promise.all([...this._unfinalizedTargets].map((target) => target._close())); + this._unfinalizedTargets.clear(); + } finally { + release(); + } + })(); + } + /** + * Finalizes the output file. This method must be called after all media samples across all tracks have been added. + * Once the Promise returned by this method completes, the output file is ready. + */ + async finalize() { + if (this.state === "pending") throw new Error("Cannot finalize before starting."); + if (this.state === "canceled") throw new Error("Cannot finalize after canceling."); + if (this._finalizePromise) { + Logging._warn("Output has already been finalized."); + return this._finalizePromise; + } + return this._finalizePromise = (async () => { + this.state = "finalizing"; + const release = await this._mutex.acquire(); + try { + const promises = this.tracks.map((x) => x.source._flushOrWaitForOngoingClose(false)); + await Promise.all(promises); + await this._muxer.finalize(); + if (this._rootWriterPromise) { + const rootWriter = await this._rootWriterPromise; + if (!rootWriter.finalized) { + await rootWriter.flush(); + await rootWriter.finalize(); + } + } + if (this._onFinalize) await this._onFinalize(); + this.state = "finalized"; + } catch (error) { + this.state = "canceled"; + throw error; + } finally { + await Promise.all([...this._unfinalizedTargets].map((target) => target._close().catch(() => {}))); + this._unfinalizedTargets.clear(); + release(); + } + })(); + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/conversion.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var __addDisposableResource = function(env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); + var dispose, inner; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + if (async) inner = dispose; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + if (inner) dispose = function() { + try { + inner.call(this); + } catch (e) { + return Promise.reject(e); + } + }; + env.stack.push({ + value, + dispose, + async + }); + } else if (async) env.stack.push({ async: true }); + return value; +}; +var __disposeResources = (function(SuppressedError) { + return function(env) { + function fail(e) { + env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + var r, s = 0; + function next() { + while (r = env.stack.pop()) try { + if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); + if (r.dispose) { + var result = r.dispose.call(r.value); + if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { + fail(e); + return next(); + }); + } else s |= 1; + } catch (e) { + fail(e); + } + if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); + if (env.hasError) throw env.error; + } + return next(); + }; +})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}); +var validateVideoOptions = (videoOptions) => { + if (!videoOptions || typeof videoOptions !== "object") throw new TypeError("options.video, when provided, must be an object."); + if (videoOptions?.discard !== void 0 && typeof videoOptions.discard !== "boolean") throw new TypeError("options.video.discard, when provided, must be a boolean."); + if (videoOptions?.forceTranscode !== void 0 && typeof videoOptions.forceTranscode !== "boolean") throw new TypeError("options.video.forceTranscode, when provided, must be a boolean."); + if (videoOptions?.codec !== void 0 && !VIDEO_CODECS.includes(videoOptions.codec)) throw new TypeError(`options.video.codec, when provided, must be one of: ${VIDEO_CODECS.join(", ")}.`); + const bitrate = videoOptions?.bitrate; + if (videoOptions?.quality !== void 0 && !(videoOptions.quality instanceof Quality)) throw new TypeError("options.video.quality, when provided, must be a Quality."); + if (videoOptions?.quality !== void 0 && bitrate !== void 0) throw new TypeError("options.video.quality and options.video.bitrate cannot both be provided."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("options.video.bitrate, when provided, must be a positive integer or a quality."); + if (videoOptions?.width !== void 0 && (!Number.isInteger(videoOptions.width) || videoOptions.width <= 0)) throw new TypeError("options.video.width, when provided, must be a positive integer."); + if (videoOptions?.height !== void 0 && (!Number.isInteger(videoOptions.height) || videoOptions.height <= 0)) throw new TypeError("options.video.height, when provided, must be a positive integer."); + if (videoOptions?.fit !== void 0 && ![ + "fill", + "contain", + "cover" + ].includes(videoOptions.fit)) throw new TypeError("options.video.fit, when provided, must be one of 'fill', 'contain', or 'cover'."); + if (videoOptions?.width !== void 0 && videoOptions.height !== void 0 && videoOptions.fit === void 0) throw new TypeError("When both options.video.width and options.video.height are provided, options.video.fit must also be provided."); + if (videoOptions?.rotate !== void 0 && ![ + 0, + 90, + 180, + 270 + ].includes(videoOptions.rotate)) throw new TypeError("options.video.rotate, when provided, must be 0, 90, 180 or 270."); + if (videoOptions?.flip !== void 0 && typeof videoOptions.flip !== "boolean") throw new TypeError("options.video.flip, when provided, must be a boolean."); + if (videoOptions?.allowTransformationMetadata !== void 0 && typeof videoOptions.allowTransformationMetadata !== "boolean") throw new TypeError("options.video.allowTransformationMetadata, when provided, must be a boolean."); + if (videoOptions?.allowRotationMetadata !== void 0 && typeof videoOptions.allowRotationMetadata !== "boolean") throw new TypeError("options.video.allowRotationMetadata, when provided, must be a boolean."); + if (videoOptions?.crop !== void 0) validateCropRectangle(videoOptions.crop, "options.video."); + if (videoOptions?.frameRate !== void 0 && (!Number.isFinite(videoOptions.frameRate) || videoOptions.frameRate <= 0)) throw new TypeError("options.video.frameRate, when provided, must be a finite positive number."); + if (videoOptions?.alpha !== void 0 && !["discard", "keep"].includes(videoOptions.alpha)) throw new TypeError("options.video.alpha, when provided, must be either 'discard' or 'keep'."); + if (videoOptions?.keyFrameInterval !== void 0 && (!Number.isFinite(videoOptions.keyFrameInterval) || videoOptions.keyFrameInterval < 0)) throw new TypeError("options.video.keyFrameInterval, when provided, must be a non-negative number."); + if (videoOptions?.process !== void 0 && typeof videoOptions.process !== "function") throw new TypeError("options.video.process, when provided, must be a function."); + if (videoOptions?.processedWidth !== void 0 && (!Number.isInteger(videoOptions.processedWidth) || videoOptions.processedWidth <= 0)) throw new TypeError("options.video.processedWidth, when provided, must be a positive integer."); + if (videoOptions?.processedHeight !== void 0 && (!Number.isInteger(videoOptions.processedHeight) || videoOptions.processedHeight <= 0)) throw new TypeError("options.video.processedHeight, when provided, must be a positive integer."); + if (videoOptions?.hardwareAcceleration !== void 0 && ![ + "no-preference", + "prefer-hardware", + "prefer-software" + ].includes(videoOptions.hardwareAcceleration)) throw new TypeError("options.video.hardwareAcceleration, when provided, must be 'no-preference', 'prefer-hardware' or 'prefer-software'."); + if (videoOptions?.group !== void 0 && !(videoOptions.group instanceof OutputTrackGroup || Array.isArray(videoOptions.group) && videoOptions.group.every((x) => x instanceof OutputTrackGroup))) throw new TypeError("options.video.group, when provided, must be an OutputTrackGroup or an array of OutputTrackGroups."); +}; +var validateAudioOptions = (audioOptions) => { + if (!audioOptions || typeof audioOptions !== "object") throw new TypeError("options.audio, when provided, must be an object."); + if (audioOptions?.discard !== void 0 && typeof audioOptions.discard !== "boolean") throw new TypeError("options.audio.discard, when provided, must be a boolean."); + if (audioOptions?.forceTranscode !== void 0 && typeof audioOptions.forceTranscode !== "boolean") throw new TypeError("options.audio.forceTranscode, when provided, must be a boolean."); + if (audioOptions?.codec !== void 0 && !AUDIO_CODECS.includes(audioOptions.codec)) throw new TypeError(`options.audio.codec, when provided, must be one of: ${AUDIO_CODECS.join(", ")}.`); + const bitrate = audioOptions?.bitrate; + if (audioOptions?.quality !== void 0 && !(audioOptions.quality instanceof Quality)) throw new TypeError("options.audio.quality, when provided, must be a Quality."); + if (audioOptions?.quality !== void 0 && bitrate !== void 0) throw new TypeError("options.audio.quality and options.audio.bitrate cannot both be provided."); + if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) throw new TypeError("options.audio.bitrate, when provided, must be a positive integer or a quality."); + if (audioOptions?.numberOfChannels !== void 0 && (!Number.isInteger(audioOptions.numberOfChannels) || audioOptions.numberOfChannels <= 0)) throw new TypeError("options.audio.numberOfChannels, when provided, must be a positive integer."); + if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer."); + if (audioOptions?.sampleFormat !== void 0 && ![ + "u8", + "s16", + "s32", + "f32" + ].includes(audioOptions.sampleFormat)) throw new TypeError("options.audio.sampleFormat, when provided, must be one of: u8, s16, s32, f32."); + if (audioOptions?.process !== void 0 && typeof audioOptions.process !== "function") throw new TypeError("options.audio.process, when provided, must be a function."); + if (audioOptions?.processedNumberOfChannels !== void 0 && (!Number.isInteger(audioOptions.processedNumberOfChannels) || audioOptions.processedNumberOfChannels <= 0)) throw new TypeError("options.audio.processedNumberOfChannels, when provided, must be a positive integer."); + if (audioOptions?.processedSampleRate !== void 0 && (!Number.isInteger(audioOptions.processedSampleRate) || audioOptions.processedSampleRate <= 0)) throw new TypeError("options.audio.processedSampleRate, when provided, must be a positive integer."); + if (audioOptions?.group !== void 0 && !(audioOptions.group instanceof OutputTrackGroup || Array.isArray(audioOptions.group) && audioOptions.group.every((x) => x instanceof OutputTrackGroup))) throw new TypeError("options.audio.group, when provided, must be an OutputTrackGroup or an array of OutputTrackGroups."); +}; +var FALLBACK_NUMBER_OF_CHANNELS = 2; +var FALLBACK_SAMPLE_RATE = 48e3; +/** +* Represents a media file conversion process, used to convert one media file into another. In addition to conversion, +* this class can be used to resize and rotate video, resample audio, drop tracks, or trim to a specific time range. +* @group Conversion +* @public +*/ +var Conversion = class Conversion { + /** + * The current state of the conversion. + * + * - `'idle'`: The conversion is not currently executing and isn't done; `execute` can be called. + * - `'executing'`: A call to `execute` is currently running. + * - `'canceled'`: The conversion has been canceled and can no longer be executed. + * - `'done'`: The conversion has run to completion. Subsequent calls to `execute` do nothing. + */ + get state() { + return this._state; + } + /** Initializes a new conversion process without starting the conversion. */ + static async init(options) { + const conversion = new Conversion(options); + await conversion._init(); + return conversion; + } + /** Creates a new Conversion instance (duh). */ + constructor(options) { + /** @internal */ + this._state = "idle"; + /** @internal */ + this._timestampOffset = 0; + /** @internal */ + this._timestampOffsetAdjusted = false; + /** @internal */ + this._copyTimestampPossible = /* @__PURE__ */ new Map(); + /** @internal */ + this._copyStartPackets = /* @__PURE__ */ new Map(); + /** @internal */ + this._nextOutputTrackId = 0; + /** @internal */ + this._outputTrackIds = []; + /** @internal */ + this._outputOwnTrackGroups = []; + /** @internal */ + this._trackPumps = []; + /** @internal */ + this._composable = false; + /** @internal */ + this._executed = false; + /** @internal */ + this._executionUntil = Infinity; + /** @internal */ + this._pauseRequested = false; + /** @internal */ + this._synchronizer = new TrackSynchronizer(this); + /** @internal */ + this._totalDuration = null; + /** @internal */ + this._maxTimestamps = /* @__PURE__ */ new Map(); + /** + * A callback that is fired whenever the conversion progresses. Gets passed as first argument a number between + * 0 and 1, indicating the completion of the conversion. Note that a progress of 1 doesn't necessarily mean the + * conversion is complete; the conversion is complete once `execute()` resolves. + * + * As second argument, this callback receives the input time in seconds that has been processed. + * + * In order for progress to be computed, this property must be set before `execute` is called. + */ + this.onProgress = void 0; + /** @internal */ + this._computeProgress = false; + /** @internal */ + this._lastProgress = 0; + /** + * Whether this conversion, as it has been configured, is valid and can be executed. If this field is `false`, check + * the `discardedTracks` field for reasons. Composable conversions are always valid, even if they utilize + * zero tracks. + * + * Note: a conversion having discarded tracks does not automatically mean it is invalid; if the remaining, utilized + * tracks make for a valid output file, the conversion is still allowed. + */ + this.isValid = false; + /** + * The list of tracks that are included in the output file. When fan-out is used, the same track appears in this + * array multiple times. + */ + this.utilizedTracks = []; + /** The list of tracks from the input file that have been discarded, alongside the discard reason. */ + this.discardedTracks = []; + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (!(options.input instanceof Input)) throw new TypeError("options.input must be an Input."); + if (!(options.output instanceof Output)) throw new TypeError("options.output must be an Output."); + if (options.tracks !== void 0 && options.tracks !== "all" && options.tracks !== "primary") throw new TypeError("options.tracks, when provided, must be either 'all' or 'primary'."); + if (options.composable !== void 0 && typeof options.composable !== "boolean") throw new TypeError("options.composable, when provided, must be a boolean."); + if (options.copy !== void 0 && options.copy !== false) { + if (!options.copy || typeof options.copy !== "object") throw new TypeError("options.copy, when provided, must be an object or false."); + if (options.copy.mode !== void 0 && !["forced", "preferred"].includes(options.copy.mode)) throw new TypeError("options.copy.mode, when provided, must be 'forced' or 'preferred'."); + if (options.copy.shiftTolerance !== void 0 && (!isNumber(options.copy.shiftTolerance) || options.copy.shiftTolerance < 0)) throw new TypeError("options.copy.shiftTolerance, when provided, must be a non-negative number."); + if (options.copy.boundaryPolicy !== void 0 && !["expand", "shrink"].includes(options.copy.boundaryPolicy)) throw new TypeError("options.copy.boundaryPolicy, when provided, must be 'expand' or 'shrink'."); + if (options.copy.boundaryTolerance !== void 0 && (!isNumber(options.copy.boundaryTolerance) || options.copy.boundaryTolerance < 0)) throw new TypeError("options.copy.boundaryTolerance, when provided, must be a non-negative number."); + } + const composable = options.composable ?? false; + if (!composable) { + if (options.output.tracks.length > 0 || Object.keys(options.output._metadataTags).length > 0 || options.output.state !== "pending") throw new TypeError("options.output must be fresh: no tracks or metadata tags added and not started."); + } else { + if (options.tags !== void 0) throw new TypeError("options.tags cannot be set by a composable conversion; set metadata directly on the output instead."); + if (options.output.state !== "pending") throw new TypeError("options.output must not have been started yet."); + } + if (options.video !== void 0 && typeof options.video !== "function") if (Array.isArray(options.video)) for (const obj of options.video) validateVideoOptions(obj); + else validateVideoOptions(options.video); + if (options.audio !== void 0 && typeof options.audio !== "function") if (Array.isArray(options.audio)) for (const obj of options.audio) validateAudioOptions(obj); + else validateAudioOptions(options.audio); + if (options.trim !== void 0 && (!options.trim || typeof options.trim !== "object")) throw new TypeError("options.trim, when provided, must be an object."); + if (options.trim?.start !== void 0 && !Number.isFinite(options.trim.start)) throw new TypeError("options.trim.start, when provided, must be a finite number."); + if (options.trim?.end !== void 0 && !isNumber(options.trim.end)) throw new TypeError("options.trim.end, when provided, must be a number."); + if (options.trim?.start !== void 0 && options.trim.end !== void 0 && options.trim.start >= options.trim.end) throw new TypeError("options.trim.start must be less than options.trim.end."); + if (options.tags !== void 0 && (typeof options.tags !== "object" || !options.tags) && typeof options.tags !== "function") throw new TypeError("options.tags, when provided, must be an object or a function."); + if (typeof options.tags === "object") validateMetadataTags(options.tags); + if (options.showWarnings !== void 0 && typeof options.showWarnings !== "boolean") throw new TypeError("options.showWarnings, when provided, must be a boolean."); + this._options = options; + this._copyMode = options.copy === false ? false : options.copy?.mode ?? "preferred"; + this._copyTimestampShiftTolerance = options.copy === false ? 0 : options.copy?.shiftTolerance ?? 0; + this._copyBoundaryPolicy = options.copy === false ? "expand" : options.copy?.boundaryPolicy ?? "expand"; + this._copyBoundaryTolerance = options.copy === false ? Infinity : options.copy?.boundaryTolerance ?? Infinity; + this._composable = composable; + this.input = options.input; + this.output = options.output; + } + /** @internal */ + async _init() { + const inputFormat = await this.input.getFormat(); + let tracks; + let trackMode = this._options.tracks; + if (trackMode === void 0) trackMode = inputFormat.name.includes("(HLS)") ? "primary" : "all"; + if (trackMode === "all") tracks = await this.input.getTracks(); + else if (trackMode === "primary") tracks = [await this.input.getPrimaryVideoTrack(), await this.input.getPrimaryAudioTrack()].filter((x) => x !== null); + else { + assertNever(trackMode); + assert(false); + } + const outputTrackCounts = this.output.format.getSupportedTrackCounts(); + let nVideo = 1; + let nAudio = 1; + const filteredTracks = []; + const filteredTrackOptions = []; + for (const track of tracks) { + let trackOptions; + if (track.isVideoTrack()) if (this._options.video) if (typeof this._options.video === "function") { + const returnedTrackOptions = await this._options.video(track, nVideo) ?? {}; + if (Array.isArray(returnedTrackOptions)) for (const obj of returnedTrackOptions) validateVideoOptions(obj); + else validateVideoOptions(returnedTrackOptions); + trackOptions = Array.isArray(returnedTrackOptions) ? returnedTrackOptions : [returnedTrackOptions]; + nVideo++; + } else trackOptions = Array.isArray(this._options.video) ? this._options.video : [this._options.video]; + else trackOptions = [{}]; + else if (track.isAudioTrack()) if (this._options.audio) if (typeof this._options.audio === "function") { + const returnedTrackOptions = await this._options.audio(track, nAudio) ?? {}; + if (Array.isArray(returnedTrackOptions)) for (const obj of returnedTrackOptions) validateAudioOptions(obj); + else validateAudioOptions(returnedTrackOptions); + trackOptions = Array.isArray(returnedTrackOptions) ? returnedTrackOptions : [returnedTrackOptions]; + nAudio++; + } else trackOptions = Array.isArray(this._options.audio) ? this._options.audio : [this._options.audio]; + else trackOptions = [{}]; + else assert(false); + const discardOptions = trackOptions.filter((x) => x.discard); + for (const discardOption of discardOptions) this.discardedTracks.push({ + track, + reason: "discarded_by_user", + trackOptions: discardOption + }); + if (trackOptions.length === discardOptions.length) { + if (trackOptions.length === 0) this.discardedTracks.push({ + track, + reason: "discarded_by_user", + trackOptions: {} + }); + continue; + } + const nonDiscardOptions = trackOptions.filter((x) => !x.discard); + filteredTracks.push(track); + filteredTrackOptions.push(nonDiscardOptions); + } + if (this._options.trim?.start !== void 0) this._startTimestamp = this._options.trim.start; + else this._startTimestamp = Math.max(await this.input.getFirstTimestamp(filteredTracks), 0); + this._endTimestamp = Math.max(this._options.trim?.end ?? Infinity, this._startTimestamp); + this._timestampOffset = -this._startTimestamp; + for (let i = 0; i < filteredTracks.length; i++) { + const track = filteredTracks[i]; + const options = filteredTrackOptions[i]; + for (const option of options) { + if (this.output.tracks.length === outputTrackCounts.total.max) { + this.discardedTracks.push({ + track, + reason: "max_track_count_reached", + trackOptions: option + }); + continue; + } + if (this.output.tracks.reduce((count, t) => count + (t.type === track.type ? 1 : 0), 0) === outputTrackCounts[track.type].max) { + this.discardedTracks.push({ + track, + reason: "max_track_count_of_type_reached", + trackOptions: option + }); + continue; + } + const outputTrackId = this._nextOutputTrackId++; + if (track.isVideoTrack()) await this._processVideoTrack(track, option, outputTrackId); + else if (track.isAudioTrack()) await this._processAudioTrack(track, option, outputTrackId); + else assert(false); + } + } + for (let i = 0; i < this.utilizedTracks.length - 1; i++) for (let j = i + 1; j < this.utilizedTracks.length; j++) { + const trackA = this.utilizedTracks[i]; + const trackB = this.utilizedTracks[j]; + const ownGroupA = this._outputOwnTrackGroups[i]; + const ownGroupB = this._outputOwnTrackGroups[j]; + assert(ownGroupA !== void 0); + assert(ownGroupB !== void 0); + if (ownGroupA && ownGroupB && trackA.canBePairedWith(trackB)) ownGroupA.pairWith(ownGroupB); + } + if (!this._composable) { + const inputTags = await this.input.getMetadataTags(); + let outputTags; + if (this._options.tags) { + const result = typeof this._options.tags === "function" ? await this._options.tags(inputTags) : this._options.tags; + validateMetadataTags(result); + outputTags = result; + } else outputTags = inputTags; + const inputAndOutputFormatMatch = inputFormat.mimeType === this.output.format.mimeType; + if (inputTags.raw === outputTags.raw && !inputAndOutputFormatMatch) delete outputTags.raw; + this.output.setMetadataTags(outputTags); + } + if (!this._composable) this.isValid = this.output.hasEnoughTracks() && this.output.tracks.length > 0; + else this.isValid = true; + if (this._options.showWarnings ?? true) { + const warnElements = []; + const unintentionallyDiscardedTracks = this.discardedTracks.filter((x) => x.reason !== "discarded_by_user"); + if (unintentionallyDiscardedTracks.length > 0) warnElements.push("Some tracks had to be discarded from the conversion:", unintentionallyDiscardedTracks); + if (!this.isValid) { + if (warnElements.length > 0) warnElements.push("\n\n"); + warnElements.push(this._getInvalidityExplanation().join("")); + } + if (warnElements.length > 0) Logging._warn(...warnElements); + } + } + /** @internal */ + _getInvalidityExplanation() { + const elements = []; + if (this.discardedTracks.length === 0) elements.push("Due to missing tracks, this conversion cannot be executed."); + else { + const encodabilityIsTheProblem = this.discardedTracks.every((x) => x.reason === "discarded_by_user" || x.reason === "no_encodable_target_codec") && this.discardedTracks.some((x) => x.reason === "no_encodable_target_codec"); + elements.push("Due to discarded tracks, this conversion cannot be executed."); + if (encodabilityIsTheProblem) { + const codecs = this.discardedTracks.flatMap((x) => { + if (x.reason === "discarded_by_user") return []; + let supportedCodecs; + if (x.track.type === "video") supportedCodecs = this.output.format.getSupportedVideoCodecs(); + else if (x.track.type === "audio") supportedCodecs = this.output.format.getSupportedAudioCodecs(); + else supportedCodecs = this.output.format.getSupportedSubtitleCodecs(); + return supportedCodecs.filter((codec) => !x.trackOptions.codec || codec === x.trackOptions.codec); + }); + const uniqueCodecs = [...new Set(codecs)]; + if (uniqueCodecs.length === 1) elements.push(`\nTracks were discarded because your environment is not able to encode '${uniqueCodecs[0]}' with the provided parameters.`); + else elements.push(` +Tracks were discarded because your environment is not able to encode any of the codecs ${uniqueCodecs.map((x) => `'${x}'`).join(", ")} with the provided parameters.`); + if (uniqueCodecs.includes("mp3")) elements.push(`\nThe @mediabunny/mp3-encoder extension package provides support for encoding MP3.`); + if (uniqueCodecs.includes("aac")) elements.push("\nThe @mediabunny/aac-encoder extension package provides support for encoding AAC."); + if (uniqueCodecs.includes("ac3") || uniqueCodecs.includes("eac3")) elements.push("\nThe @mediabunny/ac3 extension package provides support for encoding and decoding AC-3/E-AC-3."); + if (uniqueCodecs.includes("flac")) elements.push("\nThe @mediabunny/flac-encoder extension package provides support for encoding FLAC."); + } else elements.push("\nCheck the discardedTracks field for more info."); + } + return elements; + } + /** + * Executes the conversion process and resolves when the conversion is complete. When + * {@link ConversionExecuteOptions.until} is provided, the conversion will be suspended once that output timestamp + * is reached and can be resumed with another call to `execute`. An ongoing execution may also be suspended via + * {@link ConversionExecuteOptions.pauseSignal}. + * + * Execution will throw if `isValid` is `false`. + */ + async execute(options = {}) { + if (!options || typeof options !== "object") throw new TypeError("options must be an object."); + if (options.until !== void 0 && (typeof options.until !== "number" || Number.isNaN(options.until))) throw new TypeError("options.until, when provided, must be a number."); + if (options.pauseSignal !== void 0 && !(options.pauseSignal instanceof AbortSignal)) throw new TypeError("options.pauseSignal, when provided, must be an AbortSignal."); + if (!this.isValid) throw new Error("Cannot execute this conversion because its output configuration is invalid. Make sure to always check the isValid field before executing a conversion.\n" + this._getInvalidityExplanation().join("")); + if (this._state === "executing") throw new Error("Cannot call execute() while a previous call to execute() is still running."); + if (this._state === "canceled") throw new ConversionCanceledError(); + if (this._state === "done") return; + if (this._composable && this.output.state === "pending") throw new Error("A composable conversion requires the output to be started. Call start() on the output before executing the conversion."); + this._state = "executing"; + this._executionUntil = options.until ?? Infinity; + this._pauseRequested = options.pauseSignal?.aborted ?? false; + const onPause = () => { + if (this._state !== "executing") return; + this._pauseRequested = true; + this._synchronizer.resolveAll(); + }; + options.pauseSignal?.addEventListener("abort", onPause); + for (const pump of this._trackPumps) if (!pump.done) pump.resolvers = promiseWithResolvers(); + if (!this._executed) { + this._executed = true; + for (const id of this._outputTrackIds) this._synchronizer.declareTrack(id); + if (this.onProgress) { + const durationPromises = [...new Set(this.utilizedTracks)].map(async (track) => { + if (await track.isLive()) return Infinity; + return await track.getDurationFromMetadata() ?? await track.computeDuration(); + }); + const duration = Math.max(0, ...await Promise.all(durationPromises)); + this._computeProgress = true; + this._totalDuration = Math.min(duration - this._startTimestamp, this._endTimestamp - this._startTimestamp); + for (const id of this._outputTrackIds) this._maxTimestamps.set(id, 0); + this.onProgress?.(0, 0); + } + if (!this._composable) await this.output.start(); + for (const pump of this._trackPumps) pump.start(); + } else for (const pump of this._trackPumps) pump.wake?.(); + try { + await Promise.all(this._trackPumps.map((x) => x.resolvers.promise)); + } catch (error) { + if (this._state !== "canceled") this.cancel(); + throw error; + } finally { + options.pauseSignal?.removeEventListener("abort", onPause); + } + if (this._state === "canceled") throw new ConversionCanceledError(); + const isDone = this._trackPumps.every((x) => x.done); + this._state = isDone ? "done" : "idle"; + if (isDone) { + if (!this._composable) await this.output.finalize(); + if (this._computeProgress) { + const minTimestamp = Math.min(...this._maxTimestamps.values()); + this.onProgress?.(1, minTimestamp); + } + } + } + /** + * Cancels the conversion process, causing any ongoing `execute` call to throw a `ConversionCanceledError`. + * Does nothing if the conversion is already complete. + */ + async cancel() { + if (this._state === "done") return; + if (this._state === "canceled") { + Logging._warn("Conversion already canceled."); + return; + } + this._state = "canceled"; + for (const pump of this._trackPumps) pump.wake?.(); + this._synchronizer.resolveAll(); + if (!this._composable) await this.output.cancel(); + } + /** @internal */ + async _processVideoTrack(track, trackOptions, outputTrackId) { + const sourceCodec = await track.getCodec(); + if (!sourceCodec) { + this.discardedTracks.push({ + track, + reason: "unknown_source_codec", + trackOptions + }); + return; + } + let videoSource; + const { rotation: totalRotation, flip: totalFlip } = composeRotationAndFlip(await track.getRotation(), await track.getFlip(), trackOptions.rotate ?? 0, trackOptions.flip ?? false); + let outputTrackRotation = totalRotation; + let outputTrackFlip = totalFlip; + let outputTrackMatrix = trackOptions.rotate || trackOptions.flip ? null : await track.getTransformationMatrix(); + const canUseRotationMetadata = this.output.format.supportsVideoTransformationMetadata && (trackOptions.allowTransformationMetadata ?? trackOptions.allowRotationMetadata ?? true); + const squarePixelWidth = await track.getSquarePixelWidth(); + const squarePixelHeight = await track.getSquarePixelHeight(); + const [rotatedWidth, rotatedHeight] = totalRotation % 180 === 0 ? [squarePixelWidth, squarePixelHeight] : [squarePixelHeight, squarePixelWidth]; + let crop = trackOptions.crop; + if (crop) crop = clampCropRectangle(crop, rotatedWidth, rotatedHeight); + const [originalWidth, originalHeight] = crop ? [crop.width, crop.height] : [rotatedWidth, rotatedHeight]; + let width = originalWidth; + let height = originalHeight; + const aspectRatio = width / height; + if (trackOptions.width !== void 0 && trackOptions.height === void 0) { + width = ceilToMultipleOfTwo(trackOptions.width); + height = ceilToMultipleOfTwo(Math.round(width / aspectRatio)); + } else if (trackOptions.width === void 0 && trackOptions.height !== void 0) { + height = ceilToMultipleOfTwo(trackOptions.height); + width = ceilToMultipleOfTwo(Math.round(height * aspectRatio)); + } else if (trackOptions.width !== void 0 && trackOptions.height !== void 0) { + width = ceilToMultipleOfTwo(trackOptions.width); + height = ceilToMultipleOfTwo(trackOptions.height); + } + let videoCodecs = this.output.format.getSupportedVideoCodecs(); + const alpha = trackOptions.alpha ?? "discard"; + let needsTranscode = !this._copyMode || !!trackOptions.forceTranscode || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.quality !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || totalFlip && !canUseRotationMetadata || !!crop; + let copyStartPacket = null; + if (!needsTranscode) { + const sink = new EncodedPacketSink(track); + let startPacket = await sink.getKeyPacket(this._startTimestamp, { verifyKeyPackets: true }) ?? await sink.getFirstKeyPacket({ verifyKeyPackets: true }); + if (startPacket && startPacket.timestamp < this._startTimestamp && startPacket.timestamp + startPacket.duration <= this._startTimestamp && this._copyBoundaryPolicy === "shrink") startPacket = await sink.getNextKeyPacket(startPacket, { verifyKeyPackets: true }); + copyStartPacket = startPacket; + if (startPacket) { + const effectiveStartTimestamp = this._copyBoundaryPolicy === "shrink" ? Math.max(startPacket.timestamp, this._startTimestamp) : startPacket.timestamp; + if ((this._copyBoundaryPolicy === "shrink" ? effectiveStartTimestamp - this._startTimestamp : Math.max(this._startTimestamp - effectiveStartTimestamp, 0)) > this._copyBoundaryTolerance) needsTranscode = true; + else if (!this.output.format.supportsTimestampedMediaData) if (this._timestampOffsetAdjusted) { + if (!(effectiveStartTimestamp + this._timestampOffset === 0)) needsTranscode = true; + } else { + const correction = clamp(this._startTimestamp - effectiveStartTimestamp, -this._copyTimestampShiftTolerance, this._copyTimestampShiftTolerance); + if (effectiveStartTimestamp + correction === this._startTimestamp) { + this._timestampOffset = -this._startTimestamp + correction; + this._timestampOffsetAdjusted = true; + } else needsTranscode = true; + } + else if (this.output.format.negativeTimestampSupport !== "full" && effectiveStartTimestamp < this._startTimestamp) { + const correction = Math.min(this._startTimestamp - effectiveStartTimestamp, this._copyTimestampShiftTolerance); + if (effectiveStartTimestamp + correction >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced") this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction); + else needsTranscode = true; + } + } + } + if (needsTranscode && this._copyMode === "forced") { + this.discardedTracks.push({ + track, + reason: "cannot_copy", + trackOptions + }); + return; + } + if (!needsTranscode) { + const source = new EncodedVideoPacketSource(sourceCodec); + videoSource = source; + this._registerTrackPump(async (pump) => { + const sink = new EncodedPacketSink(track); + const meta = { decoderConfig: await track.getDecoderConfig() ?? void 0 }; + let maxTimestamp = null; + if (copyStartPacket) for await (const packet of sink.packets(copyStartPacket, void 0, { verifyKeyPackets: true })) { + if (this._state === "canceled") break; + if (packet.timestamp >= this._endTimestamp) if (this._copyBoundaryPolicy === "shrink") break; + else { + let current = packet; + let found = false; + const lookahead = 6; + for (let i = 0; i < lookahead; i++) { + const next = await sink.getNextPacket(current, { metadataOnly: true }); + if (!next) break; + if (next.timestamp < this._endTimestamp) { + found = true; + break; + } + current = next; + } + if (!found) break; + } + let packetStartTimestamp = packet.timestamp; + let packetEndTimestamp = packet.timestamp + packet.duration; + if (this._copyBoundaryPolicy === "shrink") { + packetStartTimestamp = Math.max(packetStartTimestamp, this._startTimestamp); + packetEndTimestamp = Math.min(packetEndTimestamp, this._endTimestamp); + packetEndTimestamp = Math.max(packetEndTimestamp, packetStartTimestamp); + } + packetStartTimestamp += this._timestampOffset; + packetEndTimestamp += this._timestampOffset; + let packetType = packet.type; + if (packetType === "key" && maxTimestamp !== null && packetStartTimestamp < maxTimestamp) packetType = "delta"; + maxTimestamp = Math.max(maxTimestamp ?? -Infinity, packetStartTimestamp); + const modifiedPacket = packet.clone({ + timestamp: packetStartTimestamp, + duration: packetEndTimestamp - packetStartTimestamp, + sideData: alpha === "discard" ? {} : packet.sideData, + type: packetType + }); + this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); + await source.add(modifiedPacket, meta); + if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) await this._synchronizer.wait(modifiedPacket.timestamp); + await this._checkpoint(pump, modifiedPacket.timestamp); + } + source.close(); + this._synchronizer.closeTrack(outputTrackId); + }); + } else { + if (!await track.canDecode()) { + this.discardedTracks.push({ + track, + reason: "undecodable_source_codec", + trackOptions + }); + return; + } + if (trackOptions.codec) videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec); + const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high"); + const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, { + width: trackOptions.process && trackOptions.processedWidth ? trackOptions.processedWidth : width, + height: trackOptions.process && trackOptions.processedHeight ? trackOptions.processedHeight : height, + quality + }); + if (!encodableCodec) { + this.discardedTracks.push({ + track, + reason: "no_encodable_target_codec", + trackOptions + }); + return; + } + const encodingConfig = { + codec: encodableCodec, + quality, + keyFrameInterval: trackOptions.keyFrameInterval, + sizeChangeBehavior: trackOptions.fit ?? "passThrough", + alpha, + hardwareAcceleration: trackOptions.hardwareAcceleration, + transform: {} + }; + assert(encodingConfig.transform); + let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && (!canUseRotationMetadata || trackOptions.process !== void 0) || totalFlip && (!canUseRotationMetadata || trackOptions.process !== void 0) || !!crop || squarePixelWidth !== await track.getCodedWidth() || squarePixelHeight !== await track.getCodedHeight(); + if (!needsRerender) { + const env_1 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const tempOutput = new Output({ + format: new Mp4OutputFormat(), + target: new NullTarget() + }); + const tempSource = new VideoSampleSource(encodingConfig); + tempOutput.addVideoTrack(tempSource); + await tempOutput.start(); + const firstSample = __addDisposableResource(env_1, await new VideoSampleSink(track).getSample(await track.getFirstTimestamp()), false); + if (firstSample) try { + await tempSource.add(firstSample); + firstSample.close(); + await tempOutput.finalize(); + } catch (error) { + Logging._warn("An error occurred when probing encoder support. Falling back to rerender path.", error); + tempOutput.cancel(); + needsRerender = true; + encodingConfig.transform.force = true; + } + else await tempOutput.cancel(); + } catch (e_1) { + env_1.error = e_1; + env_1.hasError = true; + } finally { + __disposeResources(env_1); + } + } + if (trackOptions.frameRate) encodingConfig.transform.frameRate = trackOptions.frameRate; + if (trackOptions.process) encodingConfig.transform.process = trackOptions.process; + if (needsRerender) { + outputTrackRotation = 0; + outputTrackFlip = false; + outputTrackMatrix = null; + encodingConfig.transform.width = width; + encodingConfig.transform.height = height; + encodingConfig.transform.fit = trackOptions.fit ?? "fill"; + encodingConfig.transform.rotate = trackOptions.rotate; + encodingConfig.transform.flip = trackOptions.flip; + encodingConfig.transform.crop = crop; + encodingConfig.transform.alpha = alpha; + } + let lastSampleTimestamp = null; + encodingConfig.onEncodedSample = (sample) => { + lastSampleTimestamp = sample.timestamp; + }; + const source = new VideoSampleSource(encodingConfig); + videoSource = source; + this._registerTrackPump(async (pump) => { + const sink = new VideoSampleSink(track); + for await (const sample_1 of sink.samples(this._startTimestamp, this._endTimestamp)) { + const env_2 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const sample = __addDisposableResource(env_2, sample_1, false); + if (this._state === "canceled") break; + const clampedStartTimestamp = Math.max(this._startTimestamp, sample.timestamp); + const clampedEndTimestamp = Math.min(this._endTimestamp, sample.timestamp + sample.duration); + if (clampedStartTimestamp >= clampedEndTimestamp) continue; + sample.setTimestamp(clampedStartTimestamp + this._timestampOffset); + sample.setDuration(clampedEndTimestamp - clampedStartTimestamp); + this._reportProgress(outputTrackId, sample.timestamp + sample.duration); + await source.add(sample); + sample.close(); + if (lastSampleTimestamp !== null) { + if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) await this._synchronizer.wait(lastSampleTimestamp); + await this._checkpoint(pump, lastSampleTimestamp); + } + } catch (e_2) { + env_2.error = e_2; + env_2.hasError = true; + } finally { + __disposeResources(env_2); + } + } + source.close(); + this._synchronizer.closeTrack(outputTrackId); + }); + } + let ownGroup = null; + if (!trackOptions.group && !this._composable) ownGroup = new OutputTrackGroup(); + const videoTrackLanguageCode = await track.getLanguageCode(); + const trackName = await track.getName(); + const trackDisposition = await track.getDisposition(); + const bitrate = needsTranscode ? null : await track.getBitrate(); + const averageBitrate = needsTranscode ? null : await track.getAverageBitrate(); + const canBeTransparent = alpha === "keep" && await track.canBeTransparent(); + this.output.addVideoTrack(videoSource, { + frameRate: trackOptions.frameRate, + languageCode: isIso639Dash2LanguageCode(videoTrackLanguageCode) ? videoTrackLanguageCode : void 0, + name: trackName ?? void 0, + disposition: trackDisposition, + rotation: outputTrackRotation, + flip: outputTrackFlip, + transformationMatrix: outputTrackMatrix ?? void 0, + canBeTransparent, + group: ownGroup ?? trackOptions.group, + bitrate: bitrate ?? void 0, + averageBitrate: averageBitrate ?? void 0 + }); + this.utilizedTracks.push(track); + this._outputTrackIds.push(outputTrackId); + this._outputOwnTrackGroups.push(ownGroup); + } + /** @internal */ + async _processAudioTrack(track, trackOptions, outputTrackId) { + const sourceCodec = await track.getCodec(); + if (!sourceCodec) { + this.discardedTracks.push({ + track, + reason: "unknown_source_codec", + trackOptions + }); + return; + } + let audioSource; + const originalNumberOfChannels = await track.getNumberOfChannels(); + const originalSampleRate = await track.getSampleRate(); + let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels; + let sampleRate = trackOptions.sampleRate ?? originalSampleRate; + let audioCodecs = this.output.format.getSupportedAudioCodecs(); + let needsTranscode = !this._copyMode || !!trackOptions.forceTranscode || !!trackOptions.quality || !!trackOptions.bitrate || numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || !audioCodecs.includes(sourceCodec) || !!trackOptions.codec && trackOptions.codec !== sourceCodec || trackOptions.process !== void 0 || trackOptions.sampleFormat !== void 0; + let copyStartPacket = null; + if (!needsTranscode) { + const sink = new EncodedPacketSink(track); + let startPacket = await sink.getKeyPacket(this._startTimestamp) ?? await sink.getFirstKeyPacket(); + if (startPacket && (this._copyBoundaryPolicy === "shrink" && startPacket.timestamp < this._startTimestamp || this._copyBoundaryPolicy === "expand" && startPacket.timestamp + startPacket.duration <= this._startTimestamp)) startPacket = await sink.getNextKeyPacket(startPacket); + const hasDecoderWarmup = NON_PCM_AUDIO_CODECS.includes(sourceCodec) && sourceCodec !== "flac"; + if (startPacket && this._copyBoundaryPolicy === "expand" && hasDecoderWarmup) { + const previousPacket = await sink.getKeyPacket(startPacket.timestamp - 1 / await track.getTimeResolution()); + if (previousPacket) startPacket = previousPacket; + } + copyStartPacket = startPacket; + if (startPacket) { + if ((this._copyBoundaryPolicy === "shrink" ? Math.max(startPacket.timestamp - this._startTimestamp, 0) : Math.max(this._startTimestamp - startPacket.timestamp, 0)) > this._copyBoundaryTolerance) needsTranscode = true; + else if (!this.output.format.supportsTimestampedMediaData) if (this._timestampOffsetAdjusted) { + if (!(startPacket.timestamp + this._timestampOffset === 0)) needsTranscode = true; + } else { + const correction = clamp(this._startTimestamp - startPacket.timestamp, -this._copyTimestampShiftTolerance, this._copyTimestampShiftTolerance); + if (startPacket.timestamp + correction === this._startTimestamp) { + this._timestampOffset = -this._startTimestamp + correction; + this._timestampOffsetAdjusted = true; + } else needsTranscode = true; + } + else if (this.output.format.negativeTimestampSupport !== "full" && startPacket.timestamp < this._startTimestamp) { + const correction = Math.min(this._startTimestamp - startPacket.timestamp, this._copyTimestampShiftTolerance); + if (startPacket.timestamp + correction >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced") this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction); + else needsTranscode = true; + } + } + } + if (needsTranscode && this._copyMode === "forced") { + this.discardedTracks.push({ + track, + reason: "cannot_copy", + trackOptions + }); + return; + } + if (!needsTranscode) { + const source = new EncodedAudioPacketSource(sourceCodec); + audioSource = source; + this._registerTrackPump(async (pump) => { + const sink = new EncodedPacketSink(track); + const meta = { decoderConfig: await track.getDecoderConfig() ?? void 0 }; + let maxTimestamp = null; + if (copyStartPacket) for await (const packet of sink.packets(copyStartPacket)) { + if (this._state === "canceled") break; + if (packet.timestamp >= this._endTimestamp) break; + if (this._copyBoundaryPolicy === "shrink" && packet.timestamp + packet.duration > this._endTimestamp) break; + const packetTimestamp = packet.timestamp + this._timestampOffset; + if (maxTimestamp !== null && packetTimestamp < maxTimestamp) continue; + maxTimestamp = packetTimestamp; + const modifiedPacket = packet.clone({ + timestamp: packetTimestamp, + duration: packet.duration + }); + this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); + await source.add(modifiedPacket, meta); + if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) await this._synchronizer.wait(modifiedPacket.timestamp); + await this._checkpoint(pump, modifiedPacket.timestamp); + } + source.close(); + this._synchronizer.closeTrack(outputTrackId); + }); + } else { + if (!await track.canDecode()) { + this.discardedTracks.push({ + track, + reason: "undecodable_source_codec", + trackOptions + }); + return; + } + let codecOfChoice = null; + if (trackOptions.codec) audioCodecs = audioCodecs.filter((codec) => codec === trackOptions.codec); + const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high"); + const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, { + numberOfChannels: trackOptions.process && trackOptions.processedNumberOfChannels ? trackOptions.processedNumberOfChannels : numberOfChannels, + sampleRate: trackOptions.process && trackOptions.processedSampleRate ? trackOptions.processedSampleRate : sampleRate, + quality + }); + if (!encodableCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && audioCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && (numberOfChannels !== FALLBACK_NUMBER_OF_CHANNELS || sampleRate !== FALLBACK_SAMPLE_RATE)) { + const nonPcmCodec = (await getEncodableAudioCodecs(audioCodecs, { + numberOfChannels: FALLBACK_NUMBER_OF_CHANNELS, + sampleRate: FALLBACK_SAMPLE_RATE, + quality + })).find((codec) => NON_PCM_AUDIO_CODECS.includes(codec)); + if (nonPcmCodec) { + codecOfChoice = nonPcmCodec; + numberOfChannels = FALLBACK_NUMBER_OF_CHANNELS; + sampleRate = FALLBACK_SAMPLE_RATE; + } + } else codecOfChoice = encodableCodecs[0] ?? null; + if (codecOfChoice === null) { + this.discardedTracks.push({ + track, + reason: "no_encodable_target_codec", + trackOptions + }); + return; + } + const encodingConfig = { + codec: codecOfChoice, + quality, + transform: { + sampleFormat: trackOptions.sampleFormat, + process: trackOptions.process + } + }; + assert(encodingConfig.transform); + if (numberOfChannels !== originalNumberOfChannels) encodingConfig.transform.numberOfChannels = numberOfChannels; + if (sampleRate !== originalSampleRate) encodingConfig.transform.sampleRate = sampleRate; + let lastSampleTimestamp = null; + encodingConfig.onEncodedSample = (sample) => { + lastSampleTimestamp = sample.timestamp; + }; + const source = new AudioSampleSource(encodingConfig); + audioSource = source; + this._registerTrackPump(async (pump) => { + let needsPadding = null; + const sink = new AudioSampleSink(track); + for await (const sample_2 of sink.samples(this._startTimestamp, this._endTimestamp)) { + const env_3 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const sample = __addDisposableResource(env_3, sample_2, false); + if (this._state === "canceled") break; + let startFrame = 0; + let endFrame = sample.numberOfFrames; + if (sample.timestamp < this._startTimestamp) startFrame = Math.round((this._startTimestamp - sample.timestamp) * sample.sampleRate); + if (sample.timestamp + sample.duration > this._endTimestamp) endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate); + if (startFrame >= endFrame) { + sample.close(); + continue; + } + let finalSampleLet; + if (startFrame > 0 || endFrame < sample.numberOfFrames) { + const trimmedSample = sample.trim(startFrame, endFrame); + sample.close(); + finalSampleLet = trimmedSample; + if (trimmedSample.numberOfFrames === 0) { + trimmedSample.close(); + continue; + } + } else finalSampleLet = sample; + const finalSample = __addDisposableResource(env_3, finalSampleLet, false); + finalSample.setTimestamp(finalSample.timestamp + this._timestampOffset); + if (needsPadding === null) needsPadding = finalSample.timestamp > 0 && !this.output.format.supportsTimestampedMediaData; + if (needsPadding) { + const env_4 = { + stack: [], + error: void 0, + hasError: false + }; + try { + const paddingLength = finalSample.timestamp; + const paddingLengthSamples = Math.round(paddingLength * originalSampleRate); + const bytesPerSample = getBytesPerSample(sample.format); + const data = new Uint8Array(bytesPerSample * paddingLengthSamples * originalNumberOfChannels); + if (sample.format === "u8" || sample.format === "u8-planar") data.fill(2 ** 7); + const silentSample = __addDisposableResource(env_4, new AudioSample({ + data, + format: sample.format, + numberOfChannels: originalNumberOfChannels, + sampleRate: originalSampleRate, + timestamp: 0 + }), false); + await this._registerAudioSample(pump, silentSample, source, outputTrackId, () => lastSampleTimestamp); + needsPadding = false; + } catch (e_3) { + env_4.error = e_3; + env_4.hasError = true; + } finally { + __disposeResources(env_4); + } + } + await this._registerAudioSample(pump, finalSample, source, outputTrackId, () => lastSampleTimestamp); + } catch (e_4) { + env_3.error = e_4; + env_3.hasError = true; + } finally { + __disposeResources(env_3); + } + } + source.close(); + this._synchronizer.closeTrack(outputTrackId); + }); + } + let ownGroup = null; + if (!trackOptions.group && !this._composable) ownGroup = new OutputTrackGroup(); + const audioTrackLanguageCode = await track.getLanguageCode(); + const trackName = await track.getName(); + const trackDisposition = await track.getDisposition(); + const bitrate = needsTranscode ? null : await track.getBitrate(); + const averageBitrate = needsTranscode ? null : await track.getAverageBitrate(); + this.output.addAudioTrack(audioSource, { + languageCode: isIso639Dash2LanguageCode(audioTrackLanguageCode) ? audioTrackLanguageCode : void 0, + name: trackName ?? void 0, + disposition: trackDisposition, + group: ownGroup ?? trackOptions.group, + bitrate: bitrate ?? void 0, + averageBitrate: averageBitrate ?? void 0 + }); + this.utilizedTracks.push(track); + this._outputTrackIds.push(outputTrackId); + this._outputOwnTrackGroups.push(ownGroup); + } + /** @internal */ + async _registerAudioSample(pump, sample, source, outputTrackId, getLastSampleTimestamp) { + this._reportProgress(outputTrackId, sample.timestamp + sample.duration); + await source.add(sample); + sample.close(); + const lastSampleTimestamp = getLastSampleTimestamp(); + if (lastSampleTimestamp !== null) { + if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) await this._synchronizer.wait(lastSampleTimestamp); + await this._checkpoint(pump, lastSampleTimestamp); + } + } + /** @internal */ + _registerTrackPump(fn) { + const pump = { + done: false, + resolvers: promiseWithResolvers(), + wake: null, + start: () => { + fn(pump).then(() => { + pump.done = true; + pump.resolvers.resolve(); + }, (error) => { + pump.resolvers.reject(error); + }); + } + }; + this._trackPumps.push(pump); + } + /** @internal */ + async _checkpoint(pump, timestamp) { + while (this._state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) { + pump.resolvers.resolve(); + const { promise, resolve } = promiseWithResolvers(); + pump.wake = resolve; + await promise; + } + } + /** @internal */ + _reportProgress(trackId, endTimestamp) { + if (!this._computeProgress) return; + assert(this._totalDuration !== null); + this._maxTimestamps.set(trackId, Math.max(endTimestamp, this._maxTimestamps.get(trackId))); + const minTimestamp = Math.min(...this._maxTimestamps.values()); + const newProgress = clamp(minTimestamp / this._totalDuration, 0, 1); + if (newProgress !== this._lastProgress) { + this._lastProgress = newProgress; + this.onProgress?.(newProgress, minTimestamp); + } + } +}; +/** +* Thrown when a conversion couldn't complete due to being canceled. +* @group Conversion +* @public +*/ +var ConversionCanceledError = class extends Error { + /** Creates a new {@link ConversionCanceledError}. */ + constructor(message = "Conversion has been canceled.") { + super(message); + this.name = "ConversionCanceledError"; + } +}; +var MAX_TIMESTAMP_GAP = 1; +/** +* Utility class for synchronizing multiple track packet consumers with one another. We don't want one consumer to get +* too out-of-sync with the others, as that may lead to a large number of packets that need to be internally buffered +* before they can be written. Therefore, we use this class to slow down a consumer if it is too far ahead of the +* slowest consumer. +*/ +var TrackSynchronizer = class { + constructor(conversion) { + this.maxTimestamps = /* @__PURE__ */ new Map(); + this.resolvers = []; + this.conversion = conversion; + } + declareTrack(trackId) { + this.maxTimestamps.set(trackId, -Infinity); + } + shouldWait(trackId, timestamp) { + const currentValue = this.maxTimestamps.get(trackId); + assert(currentValue !== void 0); + this.maxTimestamps.set(trackId, Math.max(timestamp, currentValue)); + const newMin = this.computeMinAndMaybeResolve(); + if (this.conversion._state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) return false; + return timestamp - newMin > MAX_TIMESTAMP_GAP; + } + wait(timestamp) { + const { promise, resolve } = promiseWithResolvers(); + this.resolvers.push({ + timestamp, + resolve + }); + return promise; + } + closeTrack(trackId) { + this.maxTimestamps.delete(trackId); + this.computeMinAndMaybeResolve(); + } + resolveAll() { + for (const entry of this.resolvers) entry.resolve(); + this.resolvers.length = 0; + } + computeMinAndMaybeResolve() { + let newMin = Infinity; + for (const [, timestamp] of this.maxTimestamps) newMin = Math.min(newMin, timestamp); + for (let i = 0; i < this.resolvers.length; i++) { + const entry = this.resolvers[i]; + if (entry.timestamp - newMin < MAX_TIMESTAMP_GAP) { + entry.resolve(); + this.resolvers.splice(i, 1); + i--; + } + } + return newMin; + } +}; +//#endregion +//#region node_modules/mediabunny/dist/modules/src/index.js +/*! +* Copyright (c) 2026-present, Vanilagy and contributors +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +var MEDIABUNNY_LOADED_SYMBOL = Symbol.for("mediabunny loaded"); +if (globalThis[MEDIABUNNY_LOADED_SYMBOL]) Logging._error("[WARNING]\nMediabunny was loaded twice. This will likely cause Mediabunny not to work correctly. Check if multiple dependencies are importing different versions of Mediabunny, or if something is being bundled incorrectly."); +globalThis[MEDIABUNNY_LOADED_SYMBOL] = true; +//#endregion +export { ADTS, ALL_FORMATS, ALL_TRACK_TYPES, AUDIO_CODECS, AdtsInputFormat, AdtsOutputFormat, AppendOnlyStreamTarget, AttachedFile, AudioBufferSink, AudioBufferSource, AudioSample, AudioSampleResource, AudioSampleSink, AudioSampleSource, AudioSource, BaseMediaSampleSink, BlobSource, BufferSource, BufferTarget, CanvasSink, CanvasSource, CmafOutputFormat, ConcurrentRunner, Conversion, ConversionCanceledError, CustomAudioDecoder, CustomAudioEncoder, CustomPathedSource, CustomSource, CustomVideoDecoder, CustomVideoEncoder, EncodedAudioPacketSource, EncodedPacket, EncodedPacketSink, EncodedVideoPacketSource, EventEmitter, FLAC, FilePathSource, FilePathTarget, FlacInputFormat, FlacOutputFormat, HLS, HLS_FORMATS, HlsInputFormat, HlsOutputFormat, Input, InputAudioTrack, InputDisposedError, InputFormat, InputTrack, InputVideoTrack, IsobmffInputFormat, IsobmffOutputFormat, LogLevel, Logging, MATROSKA, MP3, MP4, MPEG_TS, MatroskaInputFormat, MediaSource, MediaStreamAudioTrackSource, MediaStreamVideoTrackSource, MkvOutputFormat, MovOutputFormat, Mp3InputFormat, Mp3OutputFormat, Mp4InputFormat, Mp4OutputFormat, MpegTsInputFormat, MpegTsOutputFormat, NON_PCM_AUDIO_CODECS, NullTarget, OGG, OggInputFormat, OggOutputFormat, Output, OutputAudioTrack, OutputFormat, OutputSubtitleTrack, OutputTrack, OutputTrackGroup, OutputVideoTrack, PCM_AUDIO_CODECS, PathedSource, PathedTarget, QTFF, QUALITY_HIGH, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_VERY_HIGH, QUALITY_VERY_LOW, Quality, QuickTimeInputFormat, RangedSource, RangedTarget, ReadableStreamSource, RichImageData, SUBTITLE_CODECS, Source, SourceRef, StreamSource, StreamTarget, SubtitleSource, Target, TextSubtitleSource, UnsupportedInputFormatError, UrlSource, VIDEO_CODECS, VIDEO_SAMPLE_PIXEL_FORMATS, VideoSample, VideoSampleColorSpace, VideoSampleResource, VideoSampleSink, VideoSampleSource, VideoSource, WAVE, WEBM, WavOutputFormat, WaveInputFormat, WebMInputFormat, WebMOutputFormat, asc, canDecode, canDecodeAudio, canDecodeVideo, canEncode, canEncodeAudio, canEncodeSubtitles, canEncodeVideo, desc, getDecodableAudioCodecs, getDecodableCodecs, getDecodableVideoCodecs, getEncodableAudioCodecs, getEncodableCodecs, getEncodableSubtitleCodecs, getEncodableVideoCodecs, getFirstEncodableAudioCodec, getFirstEncodableSubtitleCodec, getFirstEncodableVideoCodec, __require as n, prefer, __toESM as r, registerDecoder, registerEncoder, registerVideoSampleTransformer, require___vite_browser_external as t }; diff --git a/dist/index.html b/dist/index.html index 4ba7b90..d945cbe 100644 --- a/dist/index.html +++ b/dist/index.html @@ -13,7 +13,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/dist/sw.js b/dist/sw.js index 1ec3693..120fb68 100644 --- a/dist/sw.js +++ b/dist/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const d=e=>i(e,t),c={module:{uri:t},exports:o,require:d};s[t]=Promise.all(n.map(e=>c[e]||d(e))).then(e=>(r(...e),o))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"c3f11cd6433cb8fbfa5ba522a8abdaca"},{url:"assets/index-DDqI2rlK.css",revision:null},{url:"assets/index-DCxWcq5K.js",revision:null},{url:"appstore_content/✺.png",revision:"06dd8ca13016278fc47e9a226acdb7f9"},{url:"manifest.webmanifest",revision:"70d853d7dcb45ddd8488db46950b4b33"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))}); +if(!self.define){let e,s={};const n=(n,i)=>(n=new URL(n+".js",i).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(i,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>n(e,t),d={module:{uri:t},exports:l,require:o};s[t]=Promise.all(i.map(e=>d[e]||o(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"83d384dc5870ba8fbe678bc7c224f429"},{url:"assets/src-Bv2YeDx1.js",revision:null},{url:"assets/mediabunny-aac-encoder-CtuZhjiD.js",revision:null},{url:"assets/index-DDqI2rlK.css",revision:null},{url:"assets/index-BUD8Xh0Q.js",revision:null},{url:"assets/__vite-browser-external-BU5UGKjj.js",revision:null},{url:"appstore_content/✺.png",revision:"06dd8ca13016278fc47e9a226acdb7f9"},{url:"manifest.webmanifest",revision:"70d853d7dcb45ddd8488db46950b4b33"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});