@simon_he/vue-tui 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +68 -28
  2. package/dist/{TMermaidText-CXm_L969.js → TMermaidText-frVr8Ysb.js} +1 -1
  3. package/dist/{TSelect-CiyMvwl-.js → TSelect-tJ87rKPM.js} +6 -168
  4. package/dist/{TTree-DKGiuIcM.js → TTree-CGdE-CYu.js} +12 -13
  5. package/dist/agent.cjs +9 -5
  6. package/dist/agent.js +15 -1197
  7. package/dist/{block-source-5CQp4tF8.js → block-source-CrlmaBM5.js} +7 -601
  8. package/dist/cli/input.d.ts +8 -0
  9. package/dist/cli/video/ffmpeg-frame-source.d.ts +10 -0
  10. package/dist/cli/video/png-frame-parser.d.ts +4 -0
  11. package/dist/cli/video/raw-frame-parser.d.ts +1 -0
  12. package/dist/cli/video/yt-dlp-frame-source.d.ts +11 -0
  13. package/dist/cli.cjs +142 -33
  14. package/dist/cli.d.cts +1 -1
  15. package/dist/cli.d.ts +1 -1
  16. package/dist/cli.js +167 -37
  17. package/dist/core.js +5 -6
  18. package/dist/create-terminal-app.d.ts +24 -1
  19. package/dist/{debug-logger-ChY_PXVk.js → debug-logger-CmsGI4of.js} +1 -1
  20. package/dist/{dom-renderer-DNzmg2As.js → dom-renderer-DXx8fWzD.js} +2 -3
  21. package/dist/experimental/3d/bun/contributor-avatar-atlas.d.ts +12 -0
  22. package/dist/experimental/3d/bun/png.d.ts +1 -0
  23. package/dist/experimental/3d/bun/terminal-badge-scene.d.ts +11 -0
  24. package/dist/experimental/3d/bun/terminal-badge.wgsl.d.ts +1 -0
  25. package/dist/experimental/3d/bun/webgpu-renderer.d.ts +26 -0
  26. package/dist/experimental/3d/bun.cjs +7271 -0
  27. package/dist/experimental/3d/bun.d.cts +4 -0
  28. package/dist/experimental/3d/bun.d.ts +4 -0
  29. package/dist/experimental/3d/bun.js +7390 -0
  30. package/dist/experimental/video/node.cjs +497 -0
  31. package/dist/experimental/video/node.d.cts +4 -0
  32. package/dist/experimental/video/node.d.ts +4 -0
  33. package/dist/experimental/video/node.js +610 -0
  34. package/dist/experimental.cjs +3064 -567
  35. package/dist/experimental.d.cts +5 -0
  36. package/dist/experimental.d.ts +5 -0
  37. package/dist/experimental.js +1524 -9
  38. package/dist/{create-terminal-DXuZ2fci.js → hyperlink-_1FAj7Wq.js} +403 -2
  39. package/dist/index.js +15 -16
  40. package/dist/{linkify-CuuquMQT.js → linkify-D68T7rdW.js} +164 -3
  41. package/dist/markdown.js +8 -8
  42. package/dist/{mermaid-CWto52Wk.js → mermaid-BNhM1jAZ.js} +1 -1
  43. package/dist/mermaid.js +3 -3
  44. package/dist/observability.js +2 -2
  45. package/dist/{path-suggest-fPzRsgPv.js → path-suggest-Dndu4dTi.js} +1 -1
  46. package/dist/renderer/terminal-graphics.d.ts +1 -0
  47. package/dist/renderer-dom.js +3 -4
  48. package/dist/runtime.js +2 -2
  49. package/dist/terminal-graphics-Bbe83wSr.js +597 -0
  50. package/dist/{tlog-theme-CopRt9SC.js → tlog-theme-Bul2Na3u.js} +1206 -12
  51. package/dist/{trace-CYHDKM1m.js → trace-Bh8WnoMo.js} +1 -1
  52. package/dist/vue/components/T3DViewport.d.ts +473 -0
  53. package/dist/vue/components/TAgentTerminalGraphic.d.ts +13 -0
  54. package/dist/vue/components/TVideo.d.ts +291 -0
  55. package/dist/vue/video/types.d.ts +39 -0
  56. package/dist/vue.js +17 -18
  57. package/package.json +38 -1
  58. package/dist/hyperlink-B35-O-cW.js +0 -403
  59. /package/dist/{ansi-palette-CNXqFciC.js → ansi-palette-CH6jc2Z7.js} +0 -0
  60. /package/dist/{env-DNCzvX8b.js → env-Cl8qmNFN.js} +0 -0
  61. /package/dist/{frame-perf-BBhE-iuS.js → frame-perf-CHpWhbC4.js} +0 -0
  62. /package/dist/{path-BI9W15Zt.js → path-mnSgEpCm.js} +0 -0
  63. /package/dist/{rect-Ng-3eKzF.js → rect-DUpeDfBg.js} +0 -0
  64. /package/dist/{runtime-D77C03RM.js → runtime-Byf8XXOT.js} +0 -0
  65. /package/dist/{terminal-selection-CQYr--26.js → terminal-selection-4W1SJj4c.js} +0 -0
  66. /package/dist/{wheel-scroll-CQ22Hcyj.js → wheel-scroll-DASkEZr-.js} +0 -0
@@ -0,0 +1,497 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: !0 });
10
+ }, __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from == "object" || typeof from == "function")
12
+ for (let key of __getOwnPropNames(from))
13
+ !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
22
+ mod
23
+ )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
24
+
25
+ // src/experimental/video/node.ts
26
+ var node_exports = {};
27
+ __export(node_exports, {
28
+ createFfmpegVideoFrameSource: () => createFfmpegVideoFrameSource,
29
+ createYtDlpVideoFrameSource: () => createYtDlpVideoFrameSource
30
+ });
31
+ module.exports = __toCommonJS(node_exports);
32
+
33
+ // src/cli/video/png-frame-parser.ts
34
+ var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
35
+ function readUint32(bytes, offset) {
36
+ return bytes[offset] * 16777216 + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3] >>> 0;
37
+ }
38
+ function hasPngSignature(bytes, offset) {
39
+ for (let index = 0; index < PNG_SIGNATURE.length; index++)
40
+ if (bytes[offset + index] !== PNG_SIGNATURE[index])
41
+ return !1;
42
+ return !0;
43
+ }
44
+ function isChunkType(bytes, offset, type) {
45
+ return bytes[offset] === type.charCodeAt(0) && bytes[offset + 1] === type.charCodeAt(1) && bytes[offset + 2] === type.charCodeAt(2) && bytes[offset + 3] === type.charCodeAt(3);
46
+ }
47
+ function validatePngHeader(bytes, offset, maxDimension) {
48
+ if (readUint32(bytes, offset + 8) !== 13 || !isChunkType(bytes, offset + 12, "IHDR"))
49
+ throw new Error("FFmpeg emitted a PNG without a valid IHDR chunk");
50
+ let width = readUint32(bytes, offset + 16), height = readUint32(bytes, offset + 20);
51
+ if (width <= 0 || height <= 0 || width > maxDimension || height > maxDimension)
52
+ throw new Error(`FFmpeg emitted an unsupported PNG size: ${width}x${height}`);
53
+ }
54
+ async function* parsePngFrames(chunks, options) {
55
+ let pending = new Uint8Array(Math.min(65536, options.maxFrameBytes)), length = 0, frameStart = 0, cursor = 0, headerValidated = !1;
56
+ for await (let chunk of chunks) {
57
+ frameStart > 0 && (pending.copyWithin(0, frameStart, length), length -= frameStart, cursor -= frameStart, frameStart = 0);
58
+ let required = length + chunk.length;
59
+ if (required > pending.length) {
60
+ let capacity = Math.max(1, pending.length);
61
+ for (; capacity < required && capacity < options.maxFrameBytes; )
62
+ capacity = Math.min(options.maxFrameBytes, capacity * 2);
63
+ let grown = new Uint8Array(Math.max(required, capacity));
64
+ grown.set(pending.subarray(0, length)), pending = grown;
65
+ }
66
+ for (pending.set(chunk, length), length = required; length - frameStart >= PNG_SIGNATURE.length; ) {
67
+ if (cursor === frameStart) {
68
+ if (!hasPngSignature(pending, frameStart))
69
+ throw new Error("FFmpeg emitted invalid PNG data");
70
+ cursor = frameStart + PNG_SIGNATURE.length;
71
+ }
72
+ if (!headerValidated) {
73
+ if (length - frameStart < 33)
74
+ break;
75
+ validatePngHeader(pending, frameStart, options.maxDimension), headerValidated = !0;
76
+ }
77
+ if (length - cursor < 12)
78
+ break;
79
+ let chunkBytes = readUint32(pending, cursor), chunkEnd = cursor + 12 + chunkBytes;
80
+ if (chunkEnd - frameStart > options.maxFrameBytes)
81
+ throw new Error(`FFmpeg PNG frame exceeded ${options.maxFrameBytes} bytes`);
82
+ if (chunkEnd > length)
83
+ break;
84
+ if (isChunkType(pending, cursor + 4, "IEND")) {
85
+ if (chunkBytes !== 0)
86
+ throw new Error("FFmpeg emitted an invalid PNG IEND chunk");
87
+ yield new Uint8Array(pending.subarray(frameStart, chunkEnd)), frameStart = chunkEnd, cursor = frameStart, headerValidated = !1, frameStart === length && (length = 0, frameStart = 0, cursor = 0);
88
+ continue;
89
+ }
90
+ cursor = chunkEnd;
91
+ }
92
+ if (length - frameStart > options.maxFrameBytes)
93
+ throw new Error(`FFmpeg PNG frame exceeded ${options.maxFrameBytes} bytes`);
94
+ }
95
+ if (length !== frameStart)
96
+ throw new Error("FFmpeg PNG stream ended with a truncated frame");
97
+ }
98
+
99
+ // src/cli/video/raw-frame-parser.ts
100
+ async function* parseRawFrames(chunks, frameBytes) {
101
+ if (!Number.isSafeInteger(frameBytes) || frameBytes <= 0)
102
+ throw new Error("FFmpeg raw video frame size must be a positive integer");
103
+ let frame = new Uint8Array(frameBytes), frameOffset = 0;
104
+ for await (let chunk of chunks) {
105
+ let chunkOffset = 0;
106
+ for (; chunkOffset < chunk.length; ) {
107
+ let copyBytes = Math.min(frameBytes - frameOffset, chunk.length - chunkOffset);
108
+ frame.set(chunk.subarray(chunkOffset, chunkOffset + copyBytes), frameOffset), frameOffset += copyBytes, chunkOffset += copyBytes, frameOffset === frameBytes && (yield frame, frame = new Uint8Array(frameBytes), frameOffset = 0);
109
+ }
110
+ }
111
+ if (frameOffset !== 0)
112
+ throw new Error("FFmpeg raw video stream ended with a truncated frame");
113
+ }
114
+
115
+ // src/cli/video/ffmpeg-frame-source.ts
116
+ var DEFAULT_MAX_FRAME_BYTES = 8 * 1024 * 1024, MAX_PNG_DIMENSION = 4096, MAX_STDERR_CHARS = 16384, KILL_TIMEOUT_MS = 1e3, MIN_FPS = 1e-3;
117
+ function positiveInt(value, fallback, max) {
118
+ let parsed = Math.floor(Number(value));
119
+ return !Number.isFinite(parsed) || parsed <= 0 ? fallback : Math.min(max, parsed);
120
+ }
121
+ function positiveNumber(value, fallback, max) {
122
+ let parsed = Number(value);
123
+ return !Number.isFinite(parsed) || parsed <= 0 ? fallback : Math.max(MIN_FPS, Math.min(max, parsed));
124
+ }
125
+ function ffmpegNumber(value) {
126
+ return Number(value.toFixed(3)).toString();
127
+ }
128
+ function ffmpegHttpHeaders(headers) {
129
+ if (!headers)
130
+ return "";
131
+ let lines = [];
132
+ for (let [name, value] of Object.entries(headers)) {
133
+ let hasControlCharacter = [...value].some((character) => {
134
+ let code = character.charCodeAt(0);
135
+ return code <= 31 || code === 127;
136
+ });
137
+ if (!/^[!#$%&'*+.^_`|~\w-]+$/u.test(name) || hasControlCharacter)
138
+ throw new Error("Invalid FFmpeg HTTP header");
139
+ lines.push(`${name}: ${value}`);
140
+ }
141
+ return lines.length ? `${lines.join(`\r
142
+ `)}\r
143
+ ` : "";
144
+ }
145
+ function classifyInput(src, live) {
146
+ var _a, _b;
147
+ if (!src.trim())
148
+ throw new Error("TVideo src must not be empty");
149
+ if (/^[a-zA-Z]:[\\/]/u.test(src))
150
+ return { protocolWhitelist: "file", readAtNativeRate: !live };
151
+ let scheme = (_b = (_a = /^([a-zA-Z][a-zA-Z\d+.-]*):/u.exec(src)) == null ? void 0 : _a[1]) == null ? void 0 : _b.toLowerCase();
152
+ if (!scheme || scheme === "file")
153
+ return { protocolWhitelist: "file", readAtNativeRate: !live };
154
+ if (scheme === "http" || scheme === "https")
155
+ return {
156
+ protocolWhitelist: "http,https,httpproxy,tcp,tls",
157
+ readAtNativeRate: !live
158
+ };
159
+ throw new Error(`Unsupported TVideo source protocol: ${scheme}`);
160
+ }
161
+ function pngDimensions(png) {
162
+ return {
163
+ width: png[16] * 16777216 + (png[17] << 16) + (png[18] << 8) + png[19] >>> 0,
164
+ height: png[20] * 16777216 + (png[21] << 16) + (png[22] << 8) + png[23] >>> 0
165
+ };
166
+ }
167
+ function sanitizeProcessError(value) {
168
+ let out = "";
169
+ for (let character of value) {
170
+ let code = character.charCodeAt(0);
171
+ code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code >= 127 && code <= 159 || (out += character);
172
+ }
173
+ return out.trim();
174
+ }
175
+ function abortError() {
176
+ let error = new Error("FFmpeg video decoding aborted");
177
+ return error.name = "AbortError", error;
178
+ }
179
+ function buildFfmpegVideoArgs(context, options = {}) {
180
+ let live = options.live === !0, realtime = options.realtime !== !1, input = classifyInput(context.src, live), fps = positiveNumber(context.maxFps, 12, 60), playbackRate = positiveNumber(context.playbackRate, 1, 3), width = positiveInt(context.pixelWidth, 320, MAX_PNG_DIMENSION), height = positiveInt(context.pixelHeight, 180, MAX_PNG_DIMENSION), preferredFormat = context.preferredFormat ?? "png", startAtMs = Math.max(0, Number(context.startAtMs) || 0), httpHeaders = input.protocolWhitelist === "file" ? "" : ffmpegHttpHeaders(options.httpHeaders), filters = [
181
+ playbackRate === 1 ? "setpts=PTS-STARTPTS" : `setpts=(PTS-STARTPTS)/${ffmpegNumber(playbackRate)}`,
182
+ `fps=${ffmpegNumber(fps)}`,
183
+ ...realtime ? ["realtime"] : [],
184
+ `scale=${width}:${height}:force_original_aspect_ratio=decrease:flags=fast_bilinear`,
185
+ `pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`
186
+ ];
187
+ return [
188
+ "-nostdin",
189
+ "-hide_banner",
190
+ "-loglevel",
191
+ "error",
192
+ ...context.loop || options.loop ? ["-stream_loop", "-1"] : [],
193
+ ...startAtMs > 0 && !live ? ["-ss", ffmpegNumber(startAtMs / 1e3)] : [],
194
+ ...realtime && input.readAtNativeRate ? ["-readrate", ffmpegNumber(playbackRate)] : [],
195
+ "-protocol_whitelist",
196
+ input.protocolWhitelist,
197
+ ...httpHeaders ? ["-headers", httpHeaders] : [],
198
+ "-i",
199
+ context.src,
200
+ "-map",
201
+ "0:V:0",
202
+ "-an",
203
+ "-sn",
204
+ "-dn",
205
+ "-vf",
206
+ filters.join(","),
207
+ "-fps_mode",
208
+ "passthrough",
209
+ "-threads",
210
+ "1",
211
+ ...preferredFormat === "gray8" ? ["-f", "rawvideo", "-c:v", "rawvideo", "-pix_fmt", "gray"] : ["-f", "image2pipe", "-c:v", "png", "-pix_fmt", "rgb24", "-compression_level", "1"],
212
+ "pipe:1"
213
+ ];
214
+ }
215
+ function createFfmpegVideoFrameSource(options = {}) {
216
+ return async function* (context) {
217
+ var _a;
218
+ if (context.signal.aborted)
219
+ throw abortError();
220
+ let fps = positiveNumber(context.maxFps, 12, 60), playbackRate = positiveNumber(context.playbackRate, 1, 3), startAtMs = Math.max(0, Number(context.startAtMs) || 0), width = positiveInt(context.pixelWidth, 320, MAX_PNG_DIMENSION), height = positiveInt(context.pixelHeight, 180, MAX_PNG_DIMENSION), preferredFormat = context.preferredFormat ?? "png", maxFrameBytes = positiveInt(
221
+ options.maxFrameBytes,
222
+ DEFAULT_MAX_FRAME_BYTES,
223
+ DEFAULT_MAX_FRAME_BYTES
224
+ ), rawFrameBytes = width * height;
225
+ if (preferredFormat === "gray8" && rawFrameBytes > maxFrameBytes)
226
+ throw new Error(`FFmpeg raw video frame exceeded ${maxFrameBytes} bytes`);
227
+ let { spawn } = await import("node:child_process");
228
+ if (context.signal.aborted)
229
+ throw abortError();
230
+ let executable = ((_a = options.ffmpegPath) == null ? void 0 : _a.trim()) || "ffmpeg", args = buildFfmpegVideoArgs(context, options), child = spawn(executable, args, {
231
+ shell: !1,
232
+ windowsHide: !0,
233
+ stdio: ["ignore", "pipe", "pipe"]
234
+ }), stdout = child.stdout, stderr = child.stderr, decoder = new TextDecoder(), stderrTail = "", spawnError, closed = !1, aborted = !1, killTimer = null;
235
+ stderr.on("data", (chunk) => {
236
+ stderrTail = `${stderrTail}${decoder.decode(chunk, { stream: !0 })}`.slice(
237
+ -MAX_STDERR_CHARS
238
+ );
239
+ });
240
+ let closePromise = new Promise((resolve) => {
241
+ child.once("error", (error) => {
242
+ spawnError = error;
243
+ }), child.once("close", (code, signal) => {
244
+ closed = !0, killTimer != null && clearTimeout(killTimer), killTimer = null, stderrTail = `${stderrTail}${decoder.decode()}`.slice(-MAX_STDERR_CHARS), resolve({ code, signal });
245
+ });
246
+ }), terminate = () => {
247
+ var _a2;
248
+ closed || (child.kill("SIGTERM"), killTimer == null && (killTimer = setTimeout(() => {
249
+ closed || child.kill("SIGKILL");
250
+ }, KILL_TIMEOUT_MS), (_a2 = killTimer.unref) == null || _a2.call(killTimer)));
251
+ }, onAbort = () => {
252
+ aborted = !0, terminate();
253
+ };
254
+ context.signal.addEventListener("abort", onAbort, { once: !0 }), context.signal.aborted && onAbort();
255
+ let frameIndex = 0;
256
+ try {
257
+ if (preferredFormat === "gray8")
258
+ for await (let pixels of parseRawFrames(stdout, rawFrameBytes)) {
259
+ if (aborted || context.signal.aborted)
260
+ throw abortError();
261
+ let timestampMs = startAtMs + frameIndex * 1e3 * playbackRate / fps;
262
+ frameIndex++, yield {
263
+ format: "gray8",
264
+ pixels,
265
+ timestampMs,
266
+ pixelWidth: width,
267
+ pixelHeight: height
268
+ };
269
+ }
270
+ else
271
+ for await (let png of parsePngFrames(stdout, {
272
+ maxFrameBytes,
273
+ maxDimension: MAX_PNG_DIMENSION
274
+ })) {
275
+ if (aborted || context.signal.aborted)
276
+ throw abortError();
277
+ let timestampMs = startAtMs + frameIndex * 1e3 * playbackRate / fps;
278
+ frameIndex++;
279
+ let dimensions = pngDimensions(png);
280
+ yield {
281
+ png,
282
+ timestampMs,
283
+ pixelWidth: dimensions.width,
284
+ pixelHeight: dimensions.height
285
+ };
286
+ }
287
+ let exit = await closePromise;
288
+ if (aborted || context.signal.aborted)
289
+ throw abortError();
290
+ if (spawnError)
291
+ throw spawnError;
292
+ if (exit.code !== 0) {
293
+ let details = sanitizeProcessError(stderrTail);
294
+ throw new Error(
295
+ details ? `FFmpeg video decoding failed (${exit.code ?? exit.signal}): ${details}` : `FFmpeg video decoding failed (${exit.code ?? exit.signal})`
296
+ );
297
+ }
298
+ } catch (error) {
299
+ throw closed || terminate(), await closePromise, aborted || context.signal.aborted ? abortError() : spawnError || error;
300
+ } finally {
301
+ context.signal.removeEventListener("abort", onAbort), closed || terminate();
302
+ }
303
+ };
304
+ }
305
+
306
+ // src/cli/video/yt-dlp-frame-source.ts
307
+ var DEFAULT_MAX_SOURCE_HEIGHT = 720, MAX_SOURCE_HEIGHT = 4320, MAX_METADATA_BYTES = 128 * 1024, MAX_STDERR_CHARS2 = 16384, KILL_TIMEOUT_MS2 = 1e3, RESOLVED_INPUT_TTL_MS = 5 * 60 * 1e3, METADATA_TEMPLATE = "%(.{url,http_headers,protocol,width,height,fps,vcodec,duration})j", COMMON_SOURCE_HEIGHTS = [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320];
308
+ function positiveInt2(value, fallback, max) {
309
+ let parsed = Math.floor(Number(value));
310
+ return !Number.isFinite(parsed) || parsed <= 0 ? fallback : Math.min(max, parsed);
311
+ }
312
+ function resolveSourceHeight(options, context) {
313
+ let maxSourceHeight = positiveInt2(
314
+ options.maxSourceHeight,
315
+ DEFAULT_MAX_SOURCE_HEIGHT,
316
+ MAX_SOURCE_HEIGHT
317
+ );
318
+ if (!context)
319
+ return maxSourceHeight;
320
+ let minimum = context.preferredFormat === "gray8" ? 144 : 360, requested = Math.max(minimum, positiveInt2(context.pixelHeight, minimum, MAX_SOURCE_HEIGHT)), rounded = COMMON_SOURCE_HEIGHTS.find((height) => height >= requested) ?? MAX_SOURCE_HEIGHT;
321
+ return Math.min(maxSourceHeight, rounded);
322
+ }
323
+ function resolveSourceFps(context) {
324
+ let requested = Number(context == null ? void 0 : context.maxFps);
325
+ return Math.max(30, Number.isFinite(requested) && requested > 0 ? Math.ceil(requested) : 30);
326
+ }
327
+ function abortError2() {
328
+ let error = new Error("yt-dlp video URL resolution aborted");
329
+ return error.name = "AbortError", error;
330
+ }
331
+ function sanitizeProcessError2(value) {
332
+ let out = "";
333
+ for (let character of value) {
334
+ let code = character.charCodeAt(0);
335
+ code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code >= 127 && code <= 159 || (out += character);
336
+ }
337
+ return out.trim();
338
+ }
339
+ function assertHttpUrl(value, label) {
340
+ let url;
341
+ try {
342
+ url = new URL(value);
343
+ } catch {
344
+ throw new Error(`${label} must be an HTTP(S) URL`);
345
+ }
346
+ if (url.protocol !== "http:" && url.protocol !== "https:")
347
+ throw new Error(`${label} must be an HTTP(S) URL`);
348
+ return url.href;
349
+ }
350
+ function buildYtDlpVideoArgs(src, options = {}, context) {
351
+ var _a;
352
+ let source = assertHttpUrl(src, "yt-dlp source"), sourceHeight = resolveSourceHeight(options, context), sourceFps = resolveSourceFps(context), format = ((_a = options.format) == null ? void 0 : _a.trim()) || [
353
+ `bestvideo[height<=${sourceHeight}][fps<=${sourceFps}][protocol^=http][vcodec^=avc1]`,
354
+ `bestvideo[height<=${sourceHeight}][fps<=${sourceFps}][protocol^=http]`,
355
+ `bestvideo[height<=${sourceHeight}][protocol^=http][vcodec^=avc1]`,
356
+ `bestvideo[height<=${sourceHeight}][protocol^=http]`,
357
+ `best[height<=${sourceHeight}][fps<=${sourceFps}][protocol^=http]`,
358
+ `best[height<=${sourceHeight}][protocol^=http]`
359
+ ].join("/");
360
+ return [
361
+ "--no-config",
362
+ "--no-playlist",
363
+ "--no-warnings",
364
+ "--simulate",
365
+ "--print",
366
+ METADATA_TEMPLATE,
367
+ "--format",
368
+ format,
369
+ "--",
370
+ source
371
+ ];
372
+ }
373
+ async function resolveYtDlpVideoInput(src, signal, options, context) {
374
+ var _a;
375
+ if (signal.aborted)
376
+ throw abortError2();
377
+ let { spawn } = await import("node:child_process");
378
+ if (signal.aborted)
379
+ throw abortError2();
380
+ let executable = ((_a = options.ytDlpPath) == null ? void 0 : _a.trim()) || "yt-dlp", child = spawn(executable, buildYtDlpVideoArgs(src, options, context), {
381
+ shell: !1,
382
+ windowsHide: !0,
383
+ stdio: ["ignore", "pipe", "pipe"]
384
+ }), decoder = new TextDecoder(), stderrDecoder = new TextDecoder(), stdout = "", stdoutBytes = 0, stderrTail = "", spawnError, metadataTooLarge = !1, closed = !1, aborted = !1, killTimer = null, terminate = () => {
385
+ var _a2;
386
+ closed || (child.kill("SIGTERM"), killTimer == null && (killTimer = setTimeout(() => {
387
+ closed || child.kill("SIGKILL");
388
+ }, KILL_TIMEOUT_MS2), (_a2 = killTimer.unref) == null || _a2.call(killTimer)));
389
+ };
390
+ child.stdout.on("data", (chunk) => {
391
+ if (stdoutBytes += chunk.byteLength, stdoutBytes > MAX_METADATA_BYTES) {
392
+ metadataTooLarge = !0, terminate();
393
+ return;
394
+ }
395
+ stdout += decoder.decode(chunk, { stream: !0 });
396
+ }), child.stderr.on("data", (chunk) => {
397
+ stderrTail = `${stderrTail}${stderrDecoder.decode(chunk, { stream: !0 })}`.slice(
398
+ -MAX_STDERR_CHARS2
399
+ );
400
+ });
401
+ let closePromise = new Promise(
402
+ (resolve) => {
403
+ child.once("error", (error) => {
404
+ spawnError = error;
405
+ }), child.once("close", (code, closeSignal) => {
406
+ closed = !0, killTimer != null && clearTimeout(killTimer), killTimer = null, stdout += decoder.decode(), stderrTail = `${stderrTail}${stderrDecoder.decode()}`.slice(-MAX_STDERR_CHARS2), resolve({ code, signal: closeSignal });
407
+ });
408
+ }
409
+ ), onAbort = () => {
410
+ aborted = !0, terminate();
411
+ };
412
+ signal.addEventListener("abort", onAbort, { once: !0 }), signal.aborted && onAbort();
413
+ try {
414
+ let exit = await closePromise;
415
+ if (aborted || signal.aborted)
416
+ throw abortError2();
417
+ if (metadataTooLarge)
418
+ throw new Error("yt-dlp video metadata exceeded 131072 bytes");
419
+ if (spawnError) {
420
+ let details = spawnError instanceof Error ? spawnError.message : String(spawnError);
421
+ throw new Error(`yt-dlp video URL resolution failed: ${details}`);
422
+ }
423
+ if (exit.code !== 0) {
424
+ let details = sanitizeProcessError2(stderrTail);
425
+ throw new Error(
426
+ details ? `yt-dlp video URL resolution failed (${exit.code ?? exit.signal}): ${details}` : `yt-dlp video URL resolution failed (${exit.code ?? exit.signal})`
427
+ );
428
+ }
429
+ let lines = stdout.trim().split(/\r?\n/u).filter(Boolean);
430
+ if (lines.length !== 1)
431
+ throw new Error("yt-dlp returned invalid video metadata");
432
+ let metadata;
433
+ try {
434
+ metadata = JSON.parse(lines[0]);
435
+ } catch {
436
+ throw new Error("yt-dlp returned invalid video metadata");
437
+ }
438
+ if (!metadata || typeof metadata != "object")
439
+ throw new Error("yt-dlp returned invalid video metadata");
440
+ let raw = metadata;
441
+ if (typeof raw.url != "string")
442
+ throw new Error("yt-dlp returned invalid video metadata");
443
+ let url = assertHttpUrl(raw.url, "yt-dlp resolved video URL"), httpHeaders = {};
444
+ if (raw.http_headers != null) {
445
+ if (typeof raw.http_headers != "object" || Array.isArray(raw.http_headers))
446
+ throw new Error("yt-dlp returned invalid video metadata");
447
+ for (let [name, value] of Object.entries(raw.http_headers)) {
448
+ if (typeof value != "string")
449
+ throw new Error("yt-dlp returned invalid video metadata");
450
+ httpHeaders[name] = value;
451
+ }
452
+ }
453
+ let durationMs;
454
+ if (raw.duration != null) {
455
+ if (typeof raw.duration != "number" || !Number.isFinite(raw.duration) || raw.duration < 0)
456
+ throw new Error("yt-dlp returned invalid video metadata");
457
+ durationMs = Math.round(raw.duration * 1e3);
458
+ }
459
+ return { url, httpHeaders, durationMs };
460
+ } finally {
461
+ signal.removeEventListener("abort", onAbort), closed || terminate();
462
+ }
463
+ }
464
+ function createYtDlpVideoFrameSource(options = {}) {
465
+ let resolvedInputs = /* @__PURE__ */ new Map();
466
+ return async function* (context) {
467
+ if (context.signal.aborted)
468
+ throw abortError2();
469
+ let cacheKey = JSON.stringify(buildYtDlpVideoArgs(context.src, options, context)), cached = resolvedInputs.get(cacheKey), input;
470
+ cached && cached.expiresAtMs > Date.now() ? input = cached.input : (cached && resolvedInputs.delete(cacheKey), input = await resolveYtDlpVideoInput(context.src, context.signal, options, context), resolvedInputs.set(cacheKey, {
471
+ input,
472
+ expiresAtMs: Date.now() + RESOLVED_INPUT_TTL_MS
473
+ }));
474
+ let frames = await createFfmpegVideoFrameSource({
475
+ ffmpegPath: options.ffmpegPath,
476
+ live: options.live,
477
+ realtime: options.realtime,
478
+ maxFrameBytes: options.maxFrameBytes,
479
+ httpHeaders: input.httpHeaders
480
+ })({ ...context, src: input.url });
481
+ try {
482
+ if (input.durationMs == null) {
483
+ yield* frames;
484
+ return;
485
+ }
486
+ for await (let frame of frames)
487
+ yield { ...frame, durationMs: input.durationMs };
488
+ } catch (error) {
489
+ throw error instanceof Error && error.name === "AbortError" || resolvedInputs.delete(cacheKey), error;
490
+ }
491
+ };
492
+ }
493
+ // Annotate the CommonJS export names for ESM import in node:
494
+ 0 && (module.exports = {
495
+ createFfmpegVideoFrameSource,
496
+ createYtDlpVideoFrameSource
497
+ });
@@ -0,0 +1,4 @@
1
+ export { createFfmpegVideoFrameSource } from "../../cli/video/ffmpeg-frame-source.js";
2
+ export type { FfmpegVideoFrameSourceOptions } from "../../cli/video/ffmpeg-frame-source.js";
3
+ export { createYtDlpVideoFrameSource } from "../../cli/video/yt-dlp-frame-source.js";
4
+ export type { YtDlpVideoFrameSourceOptions } from "../../cli/video/yt-dlp-frame-source.js";
@@ -0,0 +1,4 @@
1
+ export { createFfmpegVideoFrameSource } from "../../cli/video/ffmpeg-frame-source.js";
2
+ export type { FfmpegVideoFrameSourceOptions } from "../../cli/video/ffmpeg-frame-source.js";
3
+ export { createYtDlpVideoFrameSource } from "../../cli/video/yt-dlp-frame-source.js";
4
+ export type { YtDlpVideoFrameSourceOptions } from "../../cli/video/yt-dlp-frame-source.js";