@poe-platform/safe-bash 0.1.809 → 0.1.810

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 (109) hide show
  1. package/dist/audio-ast/index.d.ts +1 -1
  2. package/dist/audio-ast/index.js +6 -0
  3. package/dist/audio-ast/wav-source.d.ts +7 -1
  4. package/dist/image-ast/index.js +1 -1
  5. package/dist/pdf-ast/cos/mutable-object-store.d.ts +3 -0
  6. package/dist/pdf-ast/cos/qpdf-json-document.d.ts +42 -0
  7. package/dist/pdf-ast/cos/qpdf-json-values.d.ts +7 -0
  8. package/dist/pdf-ast/index.d.ts +1 -0
  9. package/dist/pdf-ast/index.js +508 -37
  10. package/dist/pdf-ast/retained-document.d.ts +3 -1
  11. package/dist/safe-bash/chunks/{chunk-RQCK77SC.js → chunk-65TFMAZU.js} +2 -2
  12. package/dist/safe-bash/chunks/{chunk-SOFBGDXG.js → chunk-HHSOUC3Z.js} +32 -0
  13. package/dist/safe-bash/chunks/{chunk-WAZEELYB.js → chunk-ZA5FQ34E.js} +10 -5
  14. package/dist/safe-bash/command-chunks/biff-ORSHM4YX-QYPGRDAZ.js +4937 -0
  15. package/dist/safe-bash/command-chunks/{chunk-62ZAHKFS.js → chunk-2VCKUNDZ.js} +130 -14
  16. package/dist/safe-bash/command-chunks/{chunk-GLVD3JJG.js → chunk-BYZUOF6X.js} +2858 -2816
  17. package/dist/safe-bash/command-chunks/{chunk-6OKH4X67.js → chunk-CAXJ4RLF.js} +264 -1
  18. package/dist/safe-bash/command-chunks/{chunk-YJJBGN7U.js → chunk-CHR2OD2U.js} +9 -9
  19. package/dist/safe-bash/command-chunks/{chunk-D7XBUF76.js → chunk-D2ASPM7R.js} +109 -23
  20. package/dist/safe-bash/command-chunks/chunk-D5AYP2UM.js +816 -0
  21. package/dist/safe-bash/command-chunks/{chunk-WFDG3N5R.js → chunk-ECDEPKKW.js} +1 -1
  22. package/dist/safe-bash/command-chunks/{chunk-OQCJGUI6.js → chunk-HARUYHH7.js} +10 -10
  23. package/dist/safe-bash/command-chunks/{chunk-NVBUC644.js → chunk-HKGNBH7Z.js} +2 -2
  24. package/dist/safe-bash/command-chunks/chunk-MGBHGZEX.js +12 -0
  25. package/dist/safe-bash/command-chunks/{chunk-4557HE3J.js → chunk-PMWW7KSB.js} +161 -42
  26. package/dist/safe-bash/command-chunks/{chunk-JW3KIZU6.js → chunk-RUXY7GN5.js} +1 -1
  27. package/dist/safe-bash/command-chunks/{chunk-KELMIFVV.js → chunk-TI52LKLL.js} +1 -1
  28. package/dist/safe-bash/command-chunks/{chunk-JJQ72P3V.js → chunk-VCML4T5D.js} +1 -1
  29. package/dist/safe-bash/command-chunks/{chunk-5HGEXQVD.js → chunk-WC2OCF4H.js} +1 -1
  30. package/dist/safe-bash/command-chunks/chunk-WH3TIOTS.js +326 -0
  31. package/dist/safe-bash/command-chunks/{chunk-45FI2IPY.js → chunk-WQ6ABTJU.js} +3 -6
  32. package/dist/safe-bash/command-chunks/{chunk-NB7JO7FE.js → chunk-YTFWPJ3F.js} +1 -1
  33. package/dist/safe-bash/command-chunks/{chunk-GKYCV6BU.js → chunk-ZEPDT6I5.js} +482 -168
  34. package/dist/safe-bash/command-chunks/{command-engine-P5DFM4OU.js → command-engine-BEWABZ75.js} +10 -3
  35. package/dist/safe-bash/command-chunks/{engine-RU4OFLPO-CPLFYD67.js → engine-MEPOXIXT-WZMRHACG.js} +1 -1
  36. package/dist/safe-bash/command-chunks/pdf-LOZAXXS6-ERI57IAV.js +557 -0
  37. package/dist/safe-bash/commands/audio/index.browser.js +2 -14
  38. package/dist/safe-bash/commands/diff-patch/index.js +0 -2
  39. package/dist/safe-bash/commands/ffprobe/index.browser.js +2 -5
  40. package/dist/safe-bash/commands/pandoc/index.browser.js +3 -1
  41. package/dist/safe-bash/commands/text.js +10 -1
  42. package/dist/safe-bash/core.d.ts +2 -2
  43. package/dist/safe-bash/core.js +2 -2
  44. package/dist/safe-bash/full-core.browser.js +18 -22
  45. package/dist/safe-bash/lazy-optional.d.ts +9 -0
  46. package/dist/safe-bash/lazy-optional.js +24 -0
  47. package/dist/safe-bash/plugins/index.browser.js +5 -3
  48. package/dist/safe-bash/registry-entry.browser.js +1 -1
  49. package/dist/safe-bash-command-apply-patch/apply.js +2 -2
  50. package/dist/safe-bash-command-apply-patch/index.js +2 -2
  51. package/dist/safe-bash-command-apply-patch/parser.d.ts +20 -1
  52. package/dist/safe-bash-command-apply-patch/parser.js +1 -1
  53. package/dist/safe-bash-command-ffprobe/index.js +2 -1
  54. package/dist/safe-bash-command-ffprobe/media.js +2 -1
  55. package/dist/safe-bash-command-ffprobe/options.d.ts +15 -0
  56. package/dist/safe-bash-command-ffprobe/options.js +7 -0
  57. package/dist/safe-bash-command-media/index.js +2 -2
  58. package/dist/safe-bash-command-media/plugin.js +1 -1
  59. package/dist/safe-bash-command-media/remote.js +2 -2
  60. package/dist/safe-bash-command-pandoc/command.js +6 -6
  61. package/dist/safe-bash-command-pandoc/index.js +31 -31
  62. package/dist/safe-bash-command-patch/index.js +2 -2
  63. package/dist/safe-bash-command-patch/patch-publication.d.ts +2 -2
  64. package/dist/safe-bash-command-patch/patch-publication.js +1 -1
  65. package/dist/safe-bash-command-patch/patch.js +2 -2
  66. package/dist/safe-bash-command-pptx/engine.js +1 -1
  67. package/dist/safe-bash-command-ssconvert/index.js +2 -2
  68. package/dist/safe-bash-command-xmllint/index.js +137 -30
  69. package/dist/safe-bash-compression-engine/bounded-codec.js +2 -2
  70. package/dist/safe-bash-compression-engine/codec-loader.js +1 -1
  71. package/dist/safe-bash-compression-engine/files.js +5 -5
  72. package/dist/safe-bash-compression-engine/index.js +4 -4
  73. package/dist/safe-bash-compression-engine/operand.js +5 -5
  74. package/dist/safe-bash-compression-engine/stream.js +4 -4
  75. package/dist/safe-bash-compression-engine/zstd-decode.js +3 -3
  76. package/dist/safe-bash-media-engine/imagemagick.d.ts +2 -0
  77. package/dist/safe-bash-media-engine/index.js +162 -72
  78. package/dist/safe-bash-network-engine/body.d.ts +1 -0
  79. package/dist/safe-bash-network-engine/body.js +1 -1
  80. package/dist/safe-bash-network-engine/transfer.js +11 -3
  81. package/dist/safe-bash-presentation-engine/command-placeholder.js +4 -3
  82. package/dist/safe-bash-presentation-engine/index.d.ts +1 -1
  83. package/dist/safe-bash-presentation-engine/index.js +9 -4
  84. package/dist/safe-bash-presentation-engine/presentation-model.js +3 -2
  85. package/dist/safe-bash-presentation-engine/retained-text.js +3 -3
  86. package/dist/safe-bash-presentation-engine/retained-xml-parts.d.ts +26 -0
  87. package/dist/safe-bash-presentation-engine/xml-parts.d.ts +1 -0
  88. package/dist/safe-bash-presentation-engine/xml-parts.js +7 -1
  89. package/dist/safe-bash-xml-engine/document.js +1 -1
  90. package/dist/safe-bash-xml-engine/index.js +1 -1
  91. package/dist/spreadsheet-engine/rendering/print/tab-layout.d.ts +11 -0
  92. package/dist/spreadsheet-engine/rendering/print/tab-layout.js +15 -0
  93. package/dist/spreadsheet-engine/rendering/print/text-lines.d.ts +2 -2
  94. package/dist/spreadsheet-engine/rendering/print/text-lines.js +3 -3
  95. package/dist/spreadsheet-format-xls/biff-encrypted-properties.d.ts +4 -0
  96. package/dist/spreadsheet-format-xls/biff-encrypted-properties.js +137 -56
  97. package/dist/spreadsheet-format-xls/biff-encryption.d.ts +1 -1
  98. package/dist/spreadsheet-format-xls/biff-encryption.js +8 -3
  99. package/dist/spreadsheet-format-xls/biff-property-bytes.d.ts +1 -1
  100. package/dist/spreadsheet-format-xls/biff-property-bytes.js +2 -2
  101. package/dist/spreadsheet-format-xls/biff.js +11 -3
  102. package/package.json +3 -2
  103. package/dist/safe-bash/chunks/chunk-I2A5ZCPD.js +0 -17
  104. package/dist/safe-bash/chunks/chunk-PQTT2EZP.js +0 -8
  105. package/dist/safe-bash/command-chunks/biff-DS263V2K-2GP4GZT7.js +0 -4868
  106. package/dist/safe-bash/command-chunks/chunk-6ZFYWT3U.js +0 -581
  107. package/dist/safe-bash/command-chunks/chunk-S244SWDH.js +0 -153
  108. package/dist/safe-bash/command-chunks/pdf-T7AWYZGQ-KNAYJ7YF.js +0 -552
  109. /package/dist/safe-bash/command-chunks/{chunk-EMMSXKEJ.js → chunk-4Q4R3VZU.js} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ resolveFfprobeLimits
3
+ } from "./chunk-MGBHGZEX.js";
1
4
  import {
2
5
  MediaBudgetTracker,
3
6
  allMediaAsts,
@@ -6,158 +9,102 @@ import {
6
9
  parseStreamingManifest
7
10
  } from "./chunk-DCD32BEY.js";
8
11
 
9
- // packages/safe-bash-command-ffprobe/src/probe-output.ts
10
- import { PagedStorage } from "@poe-platform/safe-fs/storage";
11
- import { writeBytes } from "../../safe-bash-contracts/io.js";
12
+ // packages/safe-bash-command-ffprobe/src/wav-tags.ts
13
+ import { IntegerTable, PagedStorage } from "@poe-platform/safe-fs/storage";
12
14
  import { yieldTurn } from "../../safe-bash-contracts/yield.js";
13
- async function* encodeParts(parts, signal) {
14
- const encoder = new TextEncoder();
15
- let high = "", steps = 0;
16
- for (const part of parts) {
17
- signal.throwIfAborted();
18
- for (let offset = 0; offset < part.length; offset += 4096) {
19
- signal.throwIfAborted();
20
- let text = high + part.slice(offset, offset + 4096);
21
- high = "";
22
- const last = text.charCodeAt(text.length - 1);
23
- if (last >= 55296 && last <= 56319) {
24
- high = text.at(-1);
25
- text = text.slice(0, -1);
26
- }
27
- if (text) yield encoder.encode(text);
28
- if (++steps % 256 === 0) await yieldTurn(signal);
29
- }
15
+ var WavTags = class {
16
+ constructor(source, context) {
17
+ this.source = source;
18
+ this.context = context;
19
+ this.storage = new PagedStorage(context, 4);
20
+ this.close = this.storage.close.bind(this.storage);
21
+ this.keys = new IntegerTable(this.storage, 128);
22
+ this.numeric = new IntegerTable(this.storage, 128);
30
23
  }
31
- if (high) yield encoder.encode(high);
32
- }
33
- async function writeProbeOutput(context, parts, check) {
34
- const storage = new PagedStorage(context, 4), start = storage.allocate(0);
35
- let size = 0, failed = true;
36
- try {
37
- for await (const bytes of encodeParts(parts, context.signal)) {
38
- size += bytes.length;
39
- check(size);
40
- await storage.append(bytes);
24
+ source;
25
+ context;
26
+ storage;
27
+ keys;
28
+ numeric;
29
+ head = 0;
30
+ tail = 0;
31
+ count = 0;
32
+ close;
33
+ async *decoded(offset, length) {
34
+ const decoder = new TextDecoder();
35
+ let steps = 0;
36
+ for (let at = 0; at < length; at += 16384) {
37
+ this.context.signal.throwIfAborted();
38
+ const bytes = await this.source.read(offset + at, Math.min(16384, length - at));
39
+ this.context.signal.throwIfAborted();
40
+ if (bytes.length !== Math.min(16384, length - at)) throw new Error("Truncated WAV tag");
41
+ yield decoder.decode(bytes, { stream: true });
42
+ if (++steps % 256 === 0) await yieldTurn(this.context.signal);
41
43
  }
42
- context.signal.throwIfAborted();
43
- if (size === 0) await writeBytes(context.stdout, new Uint8Array(0), context.signal);
44
- for (let offset = 0; offset < size; offset += 16384)
45
- await writeBytes(context.stdout, await storage.read(start + offset, Math.min(16384, size - offset)), context.signal);
46
- failed = false;
47
- } finally {
48
- if (failed) {
49
- try {
50
- await storage.close();
51
- } catch {
52
- }
53
- } else await storage.close();
44
+ yield decoder.decode();
54
45
  }
55
- }
56
-
57
- // packages/safe-bash-command-ffprobe/src/stream-input.ts
58
- import { PagedStorage as PagedStorage2 } from "@poe-platform/safe-fs/storage";
59
- import { readBytes } from "../../safe-bash-contracts/io.js";
60
- import { readFileStream } from "../../safe-bash-contracts/filesystem.js";
61
- import { yieldTurn as yieldTurn2 } from "../../safe-bash-contracts/yield.js";
62
- async function openProbeStream(context, path) {
63
- context.signal.throwIfAborted();
64
- if (path === void 0) return readBytes(context.stdin, context.signal);
65
- const capabilities = await context.fs.capabilitiesFor?.(path, { signal: context.signal }) ?? context.fs.capabilities;
66
- context.signal.throwIfAborted();
67
- if (!context.fs.readStream || capabilities.streamingRead === false || context.fs.capabilities.streamingRead === false) return void 0;
68
- return readFileStream(context.fs, path, { signal: context.signal, chunkSize: 16384 });
69
- }
70
- async function sniffWavStream(source, signal, account) {
71
- const iterator = source[Symbol.asyncIterator](), prefix = new Uint8Array(12);
72
- let size = 0, total = 0, ended = false;
73
- let remainder = new Uint8Array(0);
74
- try {
75
- while (size < prefix.length) {
76
- signal.throwIfAborted();
77
- const next = await iterator.next();
78
- signal.throwIfAborted();
79
- if (next.done) {
80
- ended = true;
81
- break;
46
+ async add(span) {
47
+ let units = 0, kept = 0;
48
+ for await (const text of this.decoded(span.offset, span.length)) {
49
+ const zero = text.indexOf("\0"), part = zero < 0 ? text : text.slice(0, zero);
50
+ for (const unit of part) {
51
+ units += unit.length;
52
+ if (unit.trimEnd()) kept = units;
82
53
  }
83
- total += next.value.length;
84
- account(total);
85
- const take = Math.min(prefix.length - size, next.value.length);
86
- prefix.set(next.value.subarray(0, take), size);
87
- size += take;
88
- remainder = next.value.subarray(take);
54
+ if (zero >= 0) break;
89
55
  }
90
- } catch (error) {
91
- try {
92
- await iterator.return?.();
93
- } catch {
56
+ let key = 0n;
57
+ for (let i = 0; i < span.key.length; i++) key = key << (span.key.length > 4 ? 7n : 16n) | BigInt(span.key.charCodeAt(i));
58
+ if (span.key.length > 4) key |= 1n << 63n;
59
+ const existing = await this.keys.get(key);
60
+ const position = existing === void 0 ? this.storage.allocate(56) : Number(existing);
61
+ const bytes = existing === void 0 ? new Uint8Array(56) : await this.storage.read(position, 56);
62
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
63
+ view.setFloat64(8, span.offset, true);
64
+ view.setFloat64(16, span.length, true);
65
+ view.setFloat64(24, kept, true);
66
+ view.setUint16(32, span.key.length, true);
67
+ for (let i = 0; i < span.key.length; i++) view.setUint16(34 + i * 2, span.key.charCodeAt(i), true);
68
+ await this.storage.write(position, bytes);
69
+ if (existing !== void 0) return;
70
+ await this.keys.set(key, BigInt(position));
71
+ this.count++;
72
+ const numeric = Number(span.key);
73
+ if (Number.isInteger(numeric) && numeric >= 0 && numeric < 4294967295 && String(numeric) === span.key) await this.numeric.set(BigInt(numeric), BigInt(position));
74
+ else {
75
+ if (this.tail) {
76
+ const link = new Uint8Array(8);
77
+ new DataView(link.buffer).setFloat64(0, position, true);
78
+ await this.storage.write(this.tail, link);
79
+ } else this.head = position;
80
+ this.tail = position;
94
81
  }
95
- throw error;
96
82
  }
97
- async function* replay() {
98
- let failed = false;
99
- try {
100
- signal.throwIfAborted();
101
- if (size) yield prefix.subarray(0, size);
102
- if (remainder.length) yield remainder;
103
- remainder = new Uint8Array(0);
104
- while (!ended) {
105
- signal.throwIfAborted();
106
- const next = await iterator.next();
107
- signal.throwIfAborted();
108
- if (next.done) {
109
- ended = true;
110
- break;
111
- }
112
- total += next.value.length;
113
- account(total);
114
- yield next.value;
115
- }
116
- } catch (error) {
117
- failed = true;
118
- throw error;
119
- } finally {
120
- if (!ended) {
121
- if (failed) {
122
- try {
123
- await iterator.return?.();
124
- } catch {
125
- }
126
- } else await iterator.return?.();
83
+ async record(position) {
84
+ const bytes = await this.storage.read(position, 56), view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
85
+ let key = "";
86
+ for (let i = 0; i < view.getUint16(32, true); i++) key += String.fromCharCode(view.getUint16(34 + i * 2, true));
87
+ const offset = view.getFloat64(8, true), length = view.getFloat64(16, true), kept = view.getFloat64(24, true), decoded = this.decoded.bind(this);
88
+ return { key, next: view.getFloat64(0, true), async *text() {
89
+ let remaining = kept;
90
+ if (!remaining) return;
91
+ for await (const part of decoded(offset, length)) {
92
+ const take = Math.min(remaining, part.length);
93
+ if (take) yield part.slice(0, take);
94
+ remaining -= take;
95
+ if (!remaining) return;
127
96
  }
128
- }
97
+ } };
129
98
  }
130
- const text = new TextDecoder().decode(prefix);
131
- return { wav: size === 12 && text.slice(0, 4) === "RIFF" && text.slice(8) === "WAVE", stream: replay() };
132
- }
133
- async function withStagedProbeSource(context, input, probe2) {
134
- const storage = new PagedStorage2(context, 4);
135
- let start;
136
- let size = 0, steps = 0, failed = true;
137
- try {
138
- for await (const chunk of input) {
139
- context.signal.throwIfAborted();
140
- size += chunk.length;
141
- start ??= storage.allocate(0);
142
- await storage.append(chunk);
143
- if (++steps % 256 === 0) await yieldTurn2(context.signal);
99
+ async *entries() {
100
+ for await (const [, position] of this.numeric.entries()) yield await this.record(Number(position));
101
+ for (let position = this.head; position; ) {
102
+ const entry = await this.record(position);
103
+ position = entry.next;
104
+ yield entry;
144
105
  }
145
- context.signal.throwIfAborted();
146
- start ??= storage.allocate(0);
147
- const base = start;
148
- const result = await probe2({ size, read: (offset, length) => storage.read(base + offset, length) });
149
- context.signal.throwIfAborted();
150
- failed = false;
151
- return result;
152
- } finally {
153
- if (failed) {
154
- try {
155
- await storage.close();
156
- } catch {
157
- }
158
- } else await storage.close();
159
106
  }
160
- }
107
+ };
161
108
 
162
109
  // packages/safe-bash-command-ffprobe/src/probe.ts
163
110
  import { parseAudio } from "../../audio-ast/index.js";
@@ -221,8 +168,8 @@ function probe(data, args) {
221
168
  const parsed = parseArguments(args);
222
169
  return formatAudioProbe(parseAudio(data, { maxAtomDepth: 64 }), data.length, parsed);
223
170
  }
224
- function formatAudioProbe(audio, size, parsed) {
225
- const { sections, format, settings } = parsed;
171
+ function audioProbeRows(audio, size, parsed, retainedTags = false) {
172
+ const { sections } = parsed;
226
173
  const serials = [
227
174
  ...new Set(
228
175
  audio.nodes.filter((node) => node.type === "OggS").map((node) => node.fields?.serial)
@@ -285,11 +232,16 @@ function formatAudioProbe(audio, size, parsed) {
285
232
  duration: formatDuration.toFixed(6),
286
233
  size: String(size),
287
234
  bit_rate: String(formatDuration ? Math.floor(size * 8 / formatDuration) : 0),
288
- ...Object.keys(formatTags).length ? { tags: formatTags } : {}
235
+ ...retainedTags || Object.keys(formatTags).length ? { tags: formatTags } : {}
289
236
  });
290
237
  if (sections.has("format_tags")) row.tags = tags;
291
238
  rows.push({ section: "format", row });
292
239
  }
240
+ return rows;
241
+ }
242
+ function formatAudioProbe(audio, size, parsed) {
243
+ const { sections, format, settings } = parsed;
244
+ const rows = audioProbeRows(audio, size, parsed);
293
245
  if (format === "json") {
294
246
  const result = {};
295
247
  if (sections.has("stream") || sections.has("stream_tags"))
@@ -334,6 +286,321 @@ function formatAudioProbe(audio, size, parsed) {
334
286
  return lines.length ? lines.join("\n") + "\n" : "";
335
287
  }
336
288
 
289
+ // packages/safe-bash-command-ffprobe/src/tag-format.ts
290
+ async function* parts(value, prefix = "") {
291
+ if (prefix) yield prefix;
292
+ if (typeof value === "function") yield* value();
293
+ else yield String(value);
294
+ }
295
+ async function* jsonString(input) {
296
+ yield '"';
297
+ for await (const part of input) yield JSON.stringify(part).slice(1, -1);
298
+ yield '"';
299
+ }
300
+ async function* replace(input, needle, replacement) {
301
+ let pending = "", first = true;
302
+ if (!needle) {
303
+ for await (const part of input) {
304
+ let text = "";
305
+ for (let i = 0; i < part.length; i++) {
306
+ text += (first ? "" : replacement) + part[i];
307
+ first = false;
308
+ if (text.length >= 4096) {
309
+ yield text;
310
+ text = "";
311
+ }
312
+ }
313
+ if (text) yield text;
314
+ }
315
+ return;
316
+ }
317
+ for await (const part of input) {
318
+ const text = pending + part;
319
+ let at = 0, found, output = "";
320
+ while ((found = text.indexOf(needle, at)) >= 0) {
321
+ output += text.slice(at, found) + replacement;
322
+ at = found + needle.length;
323
+ if (output.length >= 16384) {
324
+ yield output;
325
+ output = "";
326
+ }
327
+ }
328
+ const end = Math.max(at, text.length - needle.length + 1);
329
+ output += text.slice(at, end);
330
+ if (output) yield output;
331
+ pending = text.slice(end);
332
+ }
333
+ if (pending) yield pending;
334
+ }
335
+ async function needsQuotes(input, separator) {
336
+ if (!separator) return true;
337
+ let tail = "";
338
+ for await (const part of input) {
339
+ const text = tail + part;
340
+ if (text.includes(separator) || text.includes('"') || text.includes("\n")) return true;
341
+ tail = text.slice(Math.max(0, text.length - separator.length + 1));
342
+ }
343
+ return false;
344
+ }
345
+ async function* formatTaggedAudio(audio, size, args, tags) {
346
+ const parsed = parseArguments(args), { sections, format, settings } = parsed;
347
+ const rows = audioProbeRows({ ...audio, nodes: [] }, size, parsed, tags.count > 0);
348
+ async function* tagFields(section) {
349
+ const selected = sections.get(section + "_tags");
350
+ for await (const entry of tags.entries()) if (selected === void 0 || selected.includes(entry.key)) yield { key: entry.key, value: entry.text };
351
+ }
352
+ async function* jsonRow(row, section, indent) {
353
+ yield "{";
354
+ let first = true;
355
+ for (const [key, value] of Object.entries(row)) {
356
+ yield (first ? "\n" : ",\n") + " ".repeat(indent + 4) + JSON.stringify(key) + ": ";
357
+ first = false;
358
+ if (typeof value === "object") {
359
+ yield "{";
360
+ let firstTag = true;
361
+ for await (const field of tagFields(section)) {
362
+ yield (firstTag ? "\n" : ",\n") + " ".repeat(indent + 8) + JSON.stringify(field.key) + ": ";
363
+ firstTag = false;
364
+ yield* jsonString(parts(field.value));
365
+ }
366
+ yield (firstTag ? "" : "\n" + " ".repeat(indent + 4)) + "}";
367
+ } else if (typeof value === "number") yield JSON.stringify(value);
368
+ else yield* jsonString(parts(value));
369
+ }
370
+ yield (first ? "" : "\n" + " ".repeat(indent)) + "}";
371
+ }
372
+ if (format === "json") {
373
+ yield "{";
374
+ let first = true;
375
+ if (sections.has("stream") || sections.has("stream_tags")) {
376
+ yield '\n "streams": [';
377
+ first = false;
378
+ let firstStream = true;
379
+ for (const { section, row } of rows) if (section === "stream") {
380
+ yield (firstStream ? "\n" : ",\n") + " ";
381
+ firstStream = false;
382
+ yield* jsonRow(row, section, 8);
383
+ }
384
+ yield (firstStream ? "" : "\n ") + "]";
385
+ }
386
+ for (const { section, row } of rows) if (section === "format") {
387
+ yield (first ? "\n" : ",\n") + ' "format": ';
388
+ first = false;
389
+ yield* jsonRow(row, section, 4);
390
+ }
391
+ yield (first ? "" : "\n") + "}\n";
392
+ return;
393
+ }
394
+ const enabled = (a, b, fallback) => {
395
+ const value = settings.get(a) ?? settings.get(b);
396
+ return value === void 0 ? fallback : value === "1";
397
+ };
398
+ let streamIndex = 0;
399
+ for (const { section, row } of rows) {
400
+ async function* fields() {
401
+ for (const [key, value] of Object.entries(row)) {
402
+ if (typeof value === "object") {
403
+ for await (const tag of tagFields(section)) yield { key: "tag:" + tag.key, value: tag.value };
404
+ } else yield { key, value };
405
+ }
406
+ }
407
+ if (format === "compact" || format === "csv") {
408
+ const separator = settings.get("item_sep") ?? settings.get("s") ?? (format === "csv" ? "," : "|");
409
+ let first = true;
410
+ if (enabled("print_section", "p", true)) {
411
+ yield section;
412
+ first = false;
413
+ }
414
+ for await (const field of fields()) {
415
+ if (!first) yield separator;
416
+ first = false;
417
+ const prefix = enabled("nokey", "nk", format === "csv") ? "" : field.key + "=";
418
+ if (format === "csv") {
419
+ const quote = await needsQuotes(parts(field.value, prefix), separator);
420
+ if (quote) yield '"';
421
+ yield* quote ? replace(parts(field.value, prefix), '"', '""') : parts(field.value, prefix);
422
+ if (quote) yield '"';
423
+ } else yield* replace(replace(replace(replace(parts(field.value, prefix), "\\", "\\\\"), "\n", "\\n"), "\r", "\\r"), separator, "\\" + separator);
424
+ }
425
+ yield "\n";
426
+ } else if (format === "flat") {
427
+ const prefix = section === "stream" ? `streams.stream.${streamIndex++}` : "format";
428
+ for await (const field of fields()) {
429
+ yield prefix + "." + (field.key.startsWith("tag:") ? "tags." + field.key.slice(4) : field.key) + "=";
430
+ if (typeof field.value === "number") yield String(field.value);
431
+ else yield* jsonString(parts(field.value));
432
+ yield "\n";
433
+ }
434
+ } else {
435
+ if (!enabled("noprint_wrappers", "nw", false)) yield "[" + section.toUpperCase() + "]\n";
436
+ for await (const field of fields()) {
437
+ if (!enabled("nokey", "nk", false)) yield (field.key.startsWith("tag:") ? "TAG:" + field.key.slice(4) : field.key) + "=";
438
+ yield* parts(field.value);
439
+ yield "\n";
440
+ }
441
+ if (!enabled("noprint_wrappers", "nw", false)) yield "[/" + section.toUpperCase() + "]\n";
442
+ }
443
+ }
444
+ }
445
+
446
+ // packages/safe-bash-command-ffprobe/src/probe-output.ts
447
+ import { PagedStorage as PagedStorage2 } from "@poe-platform/safe-fs/storage";
448
+ import { writeBytes } from "../../safe-bash-contracts/io.js";
449
+ import { yieldTurn as yieldTurn2 } from "../../safe-bash-contracts/yield.js";
450
+ async function* encodeParts(parts2, signal) {
451
+ const encoder = new TextEncoder();
452
+ let high = "", steps = 0;
453
+ for await (const part of parts2) {
454
+ signal.throwIfAborted();
455
+ for (let offset = 0; offset < part.length; offset += 4096) {
456
+ signal.throwIfAborted();
457
+ let text = high + part.slice(offset, offset + 4096);
458
+ high = "";
459
+ const last = text.charCodeAt(text.length - 1);
460
+ if (last >= 55296 && last <= 56319) {
461
+ high = text.at(-1);
462
+ text = text.slice(0, -1);
463
+ }
464
+ if (text) yield encoder.encode(text);
465
+ if (++steps % 256 === 0) await yieldTurn2(signal);
466
+ }
467
+ }
468
+ if (high) yield encoder.encode(high);
469
+ }
470
+ async function writeProbeOutput(context, parts2, check, beforePublish) {
471
+ const storage = new PagedStorage2(context, 4), start = storage.allocate(0);
472
+ let size = 0, failed = true;
473
+ try {
474
+ for await (const bytes of encodeParts(parts2, context.signal)) {
475
+ size += bytes.length;
476
+ check(size);
477
+ await storage.append(bytes);
478
+ }
479
+ context.signal.throwIfAborted();
480
+ await beforePublish?.();
481
+ context.signal.throwIfAborted();
482
+ if (size === 0) await writeBytes(context.stdout, new Uint8Array(0), context.signal);
483
+ for (let offset = 0; offset < size; offset += 16384)
484
+ await writeBytes(context.stdout, await storage.read(start + offset, Math.min(16384, size - offset)), context.signal);
485
+ failed = false;
486
+ } finally {
487
+ if (failed) {
488
+ try {
489
+ await storage.close();
490
+ } catch {
491
+ }
492
+ } else await storage.close();
493
+ }
494
+ }
495
+
496
+ // packages/safe-bash-command-ffprobe/src/stream-input.ts
497
+ import { PagedStorage as PagedStorage3 } from "@poe-platform/safe-fs/storage";
498
+ import { readBytes } from "../../safe-bash-contracts/io.js";
499
+ import { readFileStream } from "../../safe-bash-contracts/filesystem.js";
500
+ import { yieldTurn as yieldTurn3 } from "../../safe-bash-contracts/yield.js";
501
+ async function openProbeStream(context, path) {
502
+ context.signal.throwIfAborted();
503
+ if (path === void 0) return readBytes(context.stdin, context.signal);
504
+ const capabilities = await context.fs.capabilitiesFor?.(path, { signal: context.signal }) ?? context.fs.capabilities;
505
+ context.signal.throwIfAborted();
506
+ if (!context.fs.readStream || capabilities.streamingRead === false || context.fs.capabilities.streamingRead === false) return void 0;
507
+ return readFileStream(context.fs, path, { signal: context.signal, chunkSize: 16384 });
508
+ }
509
+ async function sniffWavStream(source, signal, account) {
510
+ const iterator = source[Symbol.asyncIterator](), prefix = new Uint8Array(12);
511
+ let size = 0, total = 0, ended = false;
512
+ let remainder = new Uint8Array(0);
513
+ try {
514
+ while (size < prefix.length) {
515
+ signal.throwIfAborted();
516
+ const next = await iterator.next();
517
+ signal.throwIfAborted();
518
+ if (next.done) {
519
+ ended = true;
520
+ break;
521
+ }
522
+ total += next.value.length;
523
+ account(total);
524
+ const take = Math.min(prefix.length - size, next.value.length);
525
+ prefix.set(next.value.subarray(0, take), size);
526
+ size += take;
527
+ remainder = next.value.subarray(take);
528
+ }
529
+ } catch (error) {
530
+ try {
531
+ await iterator.return?.();
532
+ } catch {
533
+ }
534
+ throw error;
535
+ }
536
+ async function* replay() {
537
+ let failed = false;
538
+ try {
539
+ signal.throwIfAborted();
540
+ if (size) yield prefix.subarray(0, size);
541
+ if (remainder.length) yield remainder;
542
+ remainder = new Uint8Array(0);
543
+ while (!ended) {
544
+ signal.throwIfAborted();
545
+ const next = await iterator.next();
546
+ signal.throwIfAborted();
547
+ if (next.done) {
548
+ ended = true;
549
+ break;
550
+ }
551
+ total += next.value.length;
552
+ account(total);
553
+ yield next.value;
554
+ }
555
+ } catch (error) {
556
+ failed = true;
557
+ throw error;
558
+ } finally {
559
+ if (!ended) {
560
+ if (failed) {
561
+ try {
562
+ await iterator.return?.();
563
+ } catch {
564
+ }
565
+ } else await iterator.return?.();
566
+ }
567
+ }
568
+ }
569
+ const text = new TextDecoder().decode(prefix);
570
+ return { wav: size === 12 && text.slice(0, 4) === "RIFF" && text.slice(8) === "WAVE", stream: replay() };
571
+ }
572
+ async function withStagedProbeSource(context, input, probe2, retain) {
573
+ const storage = new PagedStorage3(context, 4);
574
+ retain?.(storage.close.bind(storage));
575
+ let start;
576
+ let size = 0, steps = 0, failed = true;
577
+ try {
578
+ for await (const chunk of input) {
579
+ context.signal.throwIfAborted();
580
+ size += chunk.length;
581
+ start ??= storage.allocate(0);
582
+ await storage.append(chunk);
583
+ if (++steps % 256 === 0) await yieldTurn3(context.signal);
584
+ }
585
+ context.signal.throwIfAborted();
586
+ start ??= storage.allocate(0);
587
+ const base = start;
588
+ const result = await probe2({ size, read: (offset, length) => storage.read(base + offset, length) });
589
+ context.signal.throwIfAborted();
590
+ failed = false;
591
+ return result;
592
+ } finally {
593
+ if (!retain) {
594
+ if (failed) {
595
+ try {
596
+ await storage.close();
597
+ } catch {
598
+ }
599
+ } else await storage.close();
600
+ }
601
+ }
602
+ }
603
+
337
604
  // packages/safe-bash-command-ffprobe/src/media.ts
338
605
  import { probeWavSource } from "../../audio-ast/index.js";
339
606
  import { commandRuntimeIdentity, getCommandArguments } from "../../safe-bash-contracts/command.js";
@@ -411,9 +678,9 @@ function resolvePath(cwd, p) {
411
678
  return normalizePath(`${cwd.endsWith("/") ? cwd : `${cwd}/`}${p}`);
412
679
  }
413
680
  function normalizePath(p) {
414
- const parts = p.split("/");
681
+ const parts2 = p.split("/");
415
682
  const stack = [];
416
- for (const part of parts) {
683
+ for (const part of parts2) {
417
684
  if (!part || part === ".") continue;
418
685
  if (part === "..") {
419
686
  stack.pop();
@@ -840,7 +1107,7 @@ function parseProbeArguments(args) {
840
1107
  inputTarget
841
1108
  };
842
1109
  }
843
- async function probeSourceMetadata(context, plugin, input, filename, budget, records, onAudio) {
1110
+ async function probeSourceMetadata(context, plugin, input, filename, budget, records, onAudio, retain, detect = false) {
844
1111
  let sourceFailed = false;
845
1112
  const source = { size: input.size, async read(offset, length) {
846
1113
  try {
@@ -852,7 +1119,7 @@ async function probeSourceMetadata(context, plugin, input, filename, budget, rec
852
1119
  throw error;
853
1120
  }
854
1121
  } };
855
- if (onAudio) {
1122
+ if (detect || onAudio) {
856
1123
  if (input.size < 12) {
857
1124
  return void 0;
858
1125
  }
@@ -865,8 +1132,17 @@ async function probeSourceMetadata(context, plugin, input, filename, budget, rec
865
1132
  }
866
1133
  const result = await plugin.probeMetadata(source, { ...records, filename, signal: context.signal, budget, limits: budget.limits });
867
1134
  if (onAudio) {
1135
+ const tags = new WavTags(source, context);
1136
+ retain(tags.close);
868
1137
  try {
869
- onAudio(await probeWavSource(source, { signal: context.signal }), input.size);
1138
+ onAudio(await probeWavSource(source, { signal: context.signal, onTag: async (span) => {
1139
+ try {
1140
+ await tags.add(span);
1141
+ } catch (error) {
1142
+ sourceFailed = true;
1143
+ throw error;
1144
+ }
1145
+ } }), input.size, tags);
870
1146
  } catch (error) {
871
1147
  context.signal.throwIfAborted();
872
1148
  if (sourceFailed) throw error;
@@ -874,13 +1150,14 @@ async function probeSourceMetadata(context, plugin, input, filename, budget, rec
874
1150
  }
875
1151
  return result;
876
1152
  }
877
- async function probeRetainedMetadata(context, plugin, path, filename, budget, records, onAudio) {
1153
+ async function probeRetainedMetadata(context, plugin, path, filename, budget, records, onAudio, retain, detect = false) {
878
1154
  if (!plugin.canDemux || !plugin.probeMetadata || !context.fs.openReadFile) return void 0;
879
1155
  context.signal.throwIfAborted();
880
1156
  const capabilities = await context.fs.capabilitiesFor?.(path, { signal: context.signal }) ?? context.fs.capabilities;
881
1157
  context.signal.throwIfAborted();
882
1158
  if (capabilities.retainedRead !== true) return void 0;
883
1159
  const handle = await context.fs.openReadFile(path, { signal: context.signal });
1160
+ if (onAudio) retain(handle.close.bind(handle));
884
1161
  let failed = true;
885
1162
  try {
886
1163
  context.signal.throwIfAborted();
@@ -892,17 +1169,19 @@ async function probeRetainedMetadata(context, plugin, path, filename, budget, re
892
1169
  const result = await probeSourceMetadata(context, plugin, {
893
1170
  size: stat.size,
894
1171
  read: (offset, length) => handle.read(offset, length, { signal: context.signal })
895
- }, filename, budget, records, onAudio);
1172
+ }, filename, budget, records, onAudio, retain, detect);
896
1173
  context.signal.throwIfAborted();
897
1174
  failed = false;
898
1175
  return result;
899
1176
  } finally {
900
- if (failed) {
901
- try {
902
- await handle.close();
903
- } catch {
904
- }
905
- } else await handle.close();
1177
+ if (!onAudio) {
1178
+ if (failed) {
1179
+ try {
1180
+ await handle.close();
1181
+ } catch {
1182
+ }
1183
+ } else await handle.close();
1184
+ }
906
1185
  }
907
1186
  }
908
1187
  async function probeStreamMetadata(context, plugin, filename, budget, records) {
@@ -926,10 +1205,7 @@ async function probeStreamMetadata(context, plugin, filename, budget, records) {
926
1205
  return result;
927
1206
  }
928
1207
  function createFfprobeCommand(options = {}) {
929
- const limits = { maxInputBytes: 32 * 1024 * 1024, maxOutputBytes: 1024 * 1024, ...options.limits };
930
- for (const value of [limits.maxInputBytes, limits.maxOutputBytes]) {
931
- if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) throw new RangeError("Invalid ffprobe limit");
932
- }
1208
+ const limits = resolveFfprobeLimits(options.limits);
933
1209
  const astPlugins = options.asts ?? getDefaultMediaRegistry().astPlugins;
934
1210
  const registry = options.asts ? createMediaAstRegistry(astPlugins) : getDefaultMediaRegistry().registry;
935
1211
  return {
@@ -967,6 +1243,25 @@ function createFfprobeCommand(options = {}) {
967
1243
  return { exitCode: 0 };
968
1244
  }
969
1245
  }
1246
+ const retained = [];
1247
+ const retain = (close) => {
1248
+ retained.push(close);
1249
+ };
1250
+ const closeInputs = async () => {
1251
+ let failure, failed = false;
1252
+ while (retained.length) {
1253
+ try {
1254
+ await retained.pop()();
1255
+ } catch (error) {
1256
+ if (!failed) {
1257
+ failed = true;
1258
+ failure = error;
1259
+ }
1260
+ }
1261
+ }
1262
+ if (failed) throw failure;
1263
+ };
1264
+ let storedTags;
970
1265
  try {
971
1266
  const {
972
1267
  printFormat,
@@ -992,12 +1287,13 @@ function createFfprobeCommand(options = {}) {
992
1287
  automaticAudio = false;
993
1288
  }
994
1289
  }
995
- const automaticPlugin = automaticAudio ? registry.findByFormatName("wav") : void 0;
1290
+ const automaticPlugin = !options.asts && !explicitFormat ? registry.findByFormatName("wav") : void 0;
996
1291
  const explicitPlugin = explicitFormat ? registry.findByFormatName(explicitFormat) : void 0;
997
1292
  const retainedPlugin = explicitPlugin ?? automaticPlugin;
998
- let probeResult = retainedPlugin && !isStdin(inputTarget) ? await probeRetainedMetadata(context, retainedPlugin, resolvePath(context.cwd, inputTarget), inputTarget, budget, { showPackets, showFrames }, automaticAudio ? (audio, size) => {
1293
+ let probeResult = retainedPlugin && !isStdin(inputTarget) ? await probeRetainedMetadata(context, retainedPlugin, resolvePath(context.cwd, inputTarget), inputTarget, budget, { showPackets, showFrames }, automaticAudio ? (audio, size, tags) => {
999
1294
  audioInput = { audio, size, args };
1000
- } : void 0) : void 0;
1295
+ storedTags = tags;
1296
+ } : void 0, retain, automaticPlugin !== void 0) : void 0;
1001
1297
  if (!probeResult && explicitPlugin)
1002
1298
  probeResult = await probeStreamMetadata(context, explicitPlugin, inputTarget, budget, { showPackets, showFrames });
1003
1299
  let replay;
@@ -1009,9 +1305,22 @@ function createFfprobeCommand(options = {}) {
1009
1305
  budget.checkInputBytes(total);
1010
1306
  });
1011
1307
  if (sniffed.wav) {
1012
- probeResult = await withStagedProbeSource(context, sniffed.stream, (source2) => probeSourceMetadata(context, automaticPlugin, source2, inputTarget, budget, { showPackets, showFrames }, (audio, size) => {
1013
- audioInput = { audio, size, args };
1014
- }));
1308
+ if (!automaticAudio && automaticPlugin.probeMetadataStream) {
1309
+ probeResult = await automaticPlugin.probeMetadataStream(sniffed.stream, {
1310
+ showPackets,
1311
+ showFrames,
1312
+ filename: inputTarget,
1313
+ signal: context.signal,
1314
+ budget,
1315
+ limits: budget.limits
1316
+ });
1317
+ context.signal.throwIfAborted();
1318
+ } else {
1319
+ probeResult = await withStagedProbeSource(context, sniffed.stream, (source2) => probeSourceMetadata(context, automaticPlugin, source2, inputTarget, budget, { showPackets, showFrames }, automaticAudio ? (audio, size, tags) => {
1320
+ audioInput = { audio, size, args };
1321
+ storedTags = tags;
1322
+ } : void 0, retain), retain);
1323
+ }
1015
1324
  } else replay = sniffed.stream;
1016
1325
  }
1017
1326
  }
@@ -1022,6 +1331,7 @@ function createFfprobeCommand(options = {}) {
1022
1331
  const msg = explicitFormat ? `ffprobe: Unknown input format: '${explicitFormat}' (AST not registered)
1023
1332
  ` : `ffprobe: ${inputTarget}: Invalid data found when processing input or format AST not registered
1024
1333
  `;
1334
+ await closeInputs();
1025
1335
  if (!quiet) await writeBytes2(context.stderr, encodeUtf8(msg), context.signal);
1026
1336
  return { exitCode: 1 };
1027
1337
  }
@@ -1036,7 +1346,7 @@ function createFfprobeCommand(options = {}) {
1036
1346
  });
1037
1347
  if (!options.asts && !explicitFormat) audioInput = { bytes, args };
1038
1348
  }
1039
- const formatted = formatFfprobeResultChunks(probeResult, {
1349
+ const formatted = storedTags && audioInput && "audio" in audioInput ? formatTaggedAudio(audioInput.audio, audioInput.size, audioInput.args, storedTags) : formatFfprobeResultChunks(probeResult, {
1040
1350
  printFormat,
1041
1351
  showFormat,
1042
1352
  showStreams,
@@ -1049,10 +1359,14 @@ function createFfprobeCommand(options = {}) {
1049
1359
  countFrames,
1050
1360
  countPackets
1051
1361
  }, audioInput);
1052
- await writeProbeOutput(context, formatted, (total) => budget.checkOutputBytes(total));
1362
+ await writeProbeOutput(context, formatted, (total) => budget.checkOutputBytes(total), closeInputs);
1053
1363
  options.onMetrics?.(budget.getStats());
1054
1364
  return { exitCode: 0 };
1055
1365
  } catch (err) {
1366
+ try {
1367
+ await closeInputs();
1368
+ } catch {
1369
+ }
1056
1370
  rethrowRuntimeError(context, err);
1057
1371
  const msg = err instanceof Error ? err.message : String(err);
1058
1372
  if (!quiet)