@poe-platform/safe-bash 0.1.839 → 0.1.841

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 (68) hide show
  1. package/dist/audio-ast/binary.d.ts +19 -0
  2. package/dist/audio-ast/index.d.ts +1 -0
  3. package/dist/audio-ast/index.js +92 -91
  4. package/dist/audio-ast/mpeg.d.ts +23 -0
  5. package/dist/mp4-ast/containers/adapters.d.ts +1 -0
  6. package/dist/mp4-ast/types.d.ts +8 -2
  7. package/dist/pdf-ast/cos/security.d.ts +10 -0
  8. package/dist/pdf-ast/edit/retained-attachment-edits.d.ts +4 -0
  9. package/dist/pdf-ast/edit/retained-graph.d.ts +1 -1
  10. package/dist/pdf-ast/image.js +1 -1
  11. package/dist/pdf-ast/index.d.ts +2 -1
  12. package/dist/pdf-ast/index.js +7 -1
  13. package/dist/pdf-ast/vendor/pdfjs-fonts.d.mts +8 -0
  14. package/dist/pdf-ast/vendor/pdfjs-fonts.mjs +28 -0
  15. package/dist/safe-bash/audio-ast.browser.js +2 -1
  16. package/dist/safe-bash/audio-ast.js +1 -1
  17. package/dist/safe-bash/command-chunks/{chunk-SG2KOOD4.js → chunk-3SAYZFMD.js} +2 -2
  18. package/dist/safe-bash/command-chunks/{chunk-T6OXG7QQ.js → chunk-AAOPVEPF.js} +66 -1
  19. package/dist/safe-bash/command-chunks/{chunk-FH5VMAPS.js → chunk-BZW25LNV.js} +420 -131
  20. package/dist/safe-bash/command-chunks/{chunk-DQVOBGI2.js → chunk-CIV3GER2.js} +6770 -6523
  21. package/dist/safe-bash/command-chunks/{chunk-46GA576V.js → chunk-CPKVUB7S.js} +2 -2
  22. package/dist/safe-bash/command-chunks/{chunk-MBXNG6C3.js → chunk-DLLN3C3Q.js} +1 -1
  23. package/dist/safe-bash/command-chunks/{chunk-ZSQCROFE.js → chunk-DM5T5D2K.js} +5 -1
  24. package/dist/safe-bash/command-chunks/{chunk-3EAZRKI4.js → chunk-G7HQYFIU.js} +10 -1
  25. package/dist/safe-bash/command-chunks/{chunk-GYKQ3IOV.js → chunk-LMYQMJYY.js} +94 -18
  26. package/dist/safe-bash/command-chunks/{chunk-DX7KU6YU.js → chunk-N52AHO6S.js} +1 -1
  27. package/dist/safe-bash/command-chunks/{chunk-4BSD2RGK.js → chunk-QPKL5GWH.js} +102 -42
  28. package/dist/safe-bash/command-chunks/{chunk-Z2T7E4ID.js → chunk-R7QLP5RH.js} +1212 -1119
  29. package/dist/safe-bash/command-chunks/{chunk-77ROD2TR.js → chunk-X3WNWRVT.js} +11 -5
  30. package/dist/safe-bash/command-chunks/{chunk-RSFKQE2U.js → chunk-YAXP4EHP.js} +19 -10
  31. package/dist/safe-bash/command-chunks/{chunk-TCXM7BSZ.js → chunk-YFMNXPLP.js} +4 -4
  32. package/dist/safe-bash-command-exiftool/index.js +25 -1
  33. package/dist/safe-bash-command-ffmpeg/index.js +191 -49
  34. package/dist/safe-bash-command-ffprobe/index.js +2 -2
  35. package/dist/safe-bash-command-ffprobe/media.js +2 -2
  36. package/dist/safe-bash-command-imagemagick/index.js +498 -101
  37. package/dist/safe-bash-command-mdq/index.js +2 -0
  38. package/dist/safe-bash-command-pandoc/citeproc-filters.js +7 -7
  39. package/dist/safe-bash-command-pandoc/command.js +8 -8
  40. package/dist/safe-bash-command-pandoc/index.js +52 -52
  41. package/dist/safe-bash-command-qpdf/index.js +1037 -654
  42. package/dist/safe-bash-command-qpdf/page-range.d.ts +2 -0
  43. package/dist/safe-bash-command-soffice/index.js +28 -8
  44. package/dist/safe-bash-command-sqlite3/btree.d.ts +2 -1
  45. package/dist/safe-bash-command-sqlite3/engine.d.ts +15 -1
  46. package/dist/safe-bash-command-sqlite3/index.js +294 -68
  47. package/dist/safe-bash-command-tar/create.js +2 -2
  48. package/dist/safe-bash-command-tar/extract.js +2 -2
  49. package/dist/safe-bash-command-tar/format.js +1 -1
  50. package/dist/safe-bash-command-tar/index.js +4 -4
  51. package/dist/safe-bash-command-tar/modes.js +4 -4
  52. package/dist/safe-bash-command-unzip/index.js +1 -1
  53. package/dist/safe-bash-command-unzip/unzip.js +1 -1
  54. package/dist/safe-bash-markdown-engine/commonmark-blocks.d.ts +17 -1
  55. package/dist/safe-bash-markdown-engine/commonmark-blocks.js +3 -3
  56. package/dist/safe-bash-markdown-engine/commonmark-inlines.d.ts +1 -1
  57. package/dist/safe-bash-markdown-engine/commonmark-inlines.js +3 -3
  58. package/dist/safe-bash-markdown-engine/commonmark-syntax.d.ts +5 -0
  59. package/dist/safe-bash-markdown-engine/commonmark-syntax.js +5 -1
  60. package/dist/safe-bash-markdown-engine/gfm-syntax.js +2 -2
  61. package/dist/safe-bash-markdown-engine/index.js +4 -4
  62. package/dist/safe-bash-zip-engine/index.js +1 -1
  63. package/dist/safe-bash-zip-engine/zip/repair.js +1 -1
  64. package/dist/safe-bash-zip-engine/zip/sfx.js +1 -1
  65. package/dist/safe-bash-zip-engine/zip-format.d.ts +2 -0
  66. package/dist/safe-bash-zip-engine/zip-format.js +1 -1
  67. package/package.json +2 -2
  68. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +2 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bareAutolink,
3
3
  filterGfmHtml
4
- } from "./chunk-DX7KU6YU.js";
4
+ } from "./chunk-N52AHO6S.js";
5
5
  import {
6
6
  asciiPunctuation,
7
7
  decodeSyntax,
@@ -14,7 +14,7 @@ import {
14
14
  readLabel,
15
15
  readTitle,
16
16
  whitespace
17
- } from "./chunk-T6OXG7QQ.js";
17
+ } from "./chunk-AAOPVEPF.js";
18
18
  import {
19
19
  unicodePunctuation,
20
20
  unicodeWhitespace
@@ -156,7 +156,7 @@ function html(text, start, context) {
156
156
  }
157
157
  }
158
158
  }
159
- async function parseCommonMarkInlines(pending, definitions, context, extensions = {}) {
159
+ async function parseCommonMarkInlines(pending, definitions, context, extensions = {}, footnote) {
160
160
  context.checkpoint(0);
161
161
  const size = pending.lines.reduce((n, line) => n + line.text.length + 1, -1);
162
162
  context.bound("text", Math.max(0, size));
@@ -353,10 +353,16 @@ async function parseCommonMarkInlines(pending, definitions, context, extensions
353
353
  continue;
354
354
  }
355
355
  if (char === "[" || char === "!" && text[i + 1] === "[") {
356
- if (char === "[" && text[i + 1] === "^" && context.footnoteReference) {
356
+ if (char === "[" && text[i + 1] === "^" && (footnote || context.footnoteReference)) {
357
357
  const end = text.indexOf("]", i + 2);
358
358
  const label = end < 0 ? "" : text.slice(i + 2, end);
359
- if (label && context.footnoteReference(label)) {
359
+ const note = label ? await footnote?.(label) : void 0;
360
+ if (note) {
361
+ append(note);
362
+ i = end + 1;
363
+ continue;
364
+ }
365
+ if (label && context.footnoteReference?.(label)) {
360
366
  append({ t: "RawInline", c: ["mdq-footnote", label] });
361
367
  i = end + 1;
362
368
  continue;
@@ -11,8 +11,9 @@ import {
11
11
  probeOggFlacSource,
12
12
  probeOggStreamMetadata,
13
13
  scanMp4Frames,
14
- scanMp4Packets
15
- } from "./chunk-FH5VMAPS.js";
14
+ scanMp4Packets,
15
+ wavPacketFrames
16
+ } from "./chunk-BZW25LNV.js";
16
17
 
17
18
  // packages/safe-bash-command-ffprobe/src/probe-text.ts
18
19
  import { yieldTurn } from "../../safe-bash-contracts/yield.js";
@@ -583,7 +584,7 @@ function audioProbeRows(audio, size, parsed, retainedTags = false) {
583
584
  const firstFrame = audio.nodes.find((node) => node.type === "MPEG");
584
585
  const vbr = firstFrame?.fields?.vbr;
585
586
  const encodedSamples = Number(firstFrame?.fields?.encodedSamples);
586
- const mp3Bitrate = vbr?.bytes && encodedSamples ? vbr.bytes * 8 * audio.streams[0].sampleRate / encodedSamples : audio.streams[0].bitrate;
587
+ const mp3Bitrate = vbr?.type === "Info" ? Number(firstFrame?.fields?.bitrate) : vbr?.bytes && encodedSamples ? vbr.bytes * 8 * audio.streams[0].sampleRate / encodedSamples : audio.streams[0].bitrate;
587
588
  const formatTags = audio.format === "ogg" ? {} : { ...audio.tags };
588
589
  const brands = audio.nodes.find((node) => node.type === "ftyp")?.fields;
589
590
  if (brands)
@@ -602,6 +603,7 @@ function audioProbeRows(audio, size, parsed, retainedTags = false) {
602
603
  ...stream.tags && Object.keys(stream.tags).length ? { tags: stream.tags } : {},
603
604
  codec_name: stream.codec === "pcm" ? stream.bitsPerSample === 8 ? "pcm_u8" : `pcm_s${stream.bitsPerSample}le` : stream.codec === "pcm_float" ? `pcm_f${stream.bitsPerSample}le` : stream.codec,
604
605
  codec_type: "audio",
606
+ ...["pcm", "pcm_float", "mp3", "flac"].includes(stream.codec) ? { sample_fmt: stream.codec === "mp3" ? "fltp" : stream.codec === "pcm_float" ? stream.bitsPerSample === 64 ? "dbl" : "flt" : stream.codec === "pcm" && stream.bitsPerSample === 8 ? "u8" : stream.bitsPerSample <= 16 ? "s16" : "s32" } : {},
605
607
  sample_rate: String(stream.sampleRate),
606
608
  channels: stream.channels,
607
609
  bits_per_sample: stream.codec.startsWith("pcm") ? stream.bitsPerSample ?? 0 : 0,
@@ -1624,10 +1626,15 @@ function* formatProbeSteps(probe2, opts) {
1624
1626
  if (typeOrdinal++ !== (parseInt(spec.slice(2), 10) || 0)) return;
1625
1627
  } else if ([...spec].every((c) => c >= "0" && c <= "9") && stream.index !== parseInt(spec, 10)) return;
1626
1628
  }
1629
+ const recordCount = probe2.format?.format_name === "wav" ? String(Math.ceil(stream.duration_ts / wavPacketFrames(
1630
+ Number(stream.sample_rate),
1631
+ Math.max(1, (stream.channels ?? 1) * (stream.bits_per_sample ?? 16) / 8)
1632
+ ))) : stream.nb_frames ?? "0";
1633
+ const { nb_read_frames, nb_read_packets, ...fields } = stream;
1627
1634
  return filterObject({
1628
- ...stream,
1629
- ...opts.countFrames ? { nb_read_frames: stream.nb_frames ?? "0" } : {},
1630
- ...opts.countPackets ? { nb_read_packets: stream.nb_frames ?? "0" } : {}
1635
+ ...fields,
1636
+ ...opts.countFrames ? { nb_read_frames: nb_read_frames ?? recordCount } : {},
1637
+ ...opts.countPackets ? { nb_read_packets: nb_read_packets ?? recordCount } : {}
1631
1638
  }, "stream", "stream_tags");
1632
1639
  };
1633
1640
  const finalFormat = includeFormat && probe2.format ? filterObject(probe2.format, "format", "format_tags") : void 0;
@@ -1817,6 +1824,9 @@ function parseProbeArguments(args) {
1817
1824
  showFormat = true;
1818
1825
  showStreams = true;
1819
1826
  }
1827
+ const entryFilter = parseEntryFilter(showEntries);
1828
+ showPackets ||= entryFilter.has("packet");
1829
+ showFrames ||= entryFilter.has("frame");
1820
1830
  return {
1821
1831
  printFormat,
1822
1832
  showFormat,
@@ -2038,11 +2048,10 @@ function createFfprobeCommand(options = {}) {
2038
2048
  inputTarget
2039
2049
  } = parseProbeArguments(args);
2040
2050
  const entryFilter = parseEntryFilter(showEntries);
2041
- const packetRecords = showPackets || entryFilter.has("packet"), frameRecords = showFrames || entryFilter.has("frame");
2042
2051
  const mp4Records = !options.asts ? {
2043
- packets: packetRecords,
2044
- frames: frameRecords,
2045
- metadata: !(packetRecords || frameRecords) || showStreams || showFormat || showChapters || countFrames || countPackets || [...entryFilter.keys()].some((section) => section !== "packet" && section !== "frame")
2052
+ packets: showPackets,
2053
+ frames: showFrames,
2054
+ metadata: !(showPackets || showFrames) || showStreams || showFormat || showChapters || countFrames || countPackets || [...entryFilter.keys()].some((section) => section !== "packet" && section !== "frame")
2046
2055
  } : void 0;
2047
2056
  const packetPlugins = mp4Records ? [registry.findByFormatName("mp4"), registry.findByFormatName("mov")] : [];
2048
2057
  let audioInput;
@@ -123,8 +123,8 @@ function decimalTime(value) {
123
123
  return value.toFixed(9).replace(/(\.[0-9]*?)0+$/u, "$1").replace(/\.$/u, "");
124
124
  }
125
125
  var supportedKeywords = /* @__PURE__ */ new Set(["path", "linkpath", "size", "uid", "gid", "mtime", "atime", "ctime", "uname", "gname", "comment", "charset", "hdrcharset"]);
126
- var optionalKeywords = /* @__PURE__ */ new Set(["SCHILY.fflags", "LIBARCHIVE.creationtime"]);
127
- var optionalKeywordPrefixes = ["LIBARCHIVE.xattr.", "SCHILY.xattr."];
126
+ var unsupportedKeywords = /* @__PURE__ */ new Set(["SCHILY.realsize", "SCHILY.filetype", "SCHILY.offset", "SCHILY.dev", "SCHILY.ino", "SCHILY.nlink", "SUN.holesdata", "LIBARCHIVE.symlinktype"]);
127
+ var unsupportedKeywordPrefixes = ["GNU.sparse.", "GNU.volume."];
128
128
  function parsePax(payload) {
129
129
  const result = /* @__PURE__ */ new Map();
130
130
  let offset = 0;
@@ -141,8 +141,8 @@ function parsePax(payload) {
141
141
  const key = text(record.subarray(0, equals));
142
142
  if (/[\0\n]/u.test(key)) fail("invalid PAX key/value");
143
143
  offset += size;
144
- if (optionalKeywords.has(key) || optionalKeywordPrefixes.some((prefix) => key.startsWith(prefix) && key.length > prefix.length)) continue;
145
- if (!supportedKeywords.has(key)) fail(`unsupported PAX keyword: ${key}`);
144
+ if (unsupportedKeywords.has(key) || unsupportedKeywordPrefixes.some((prefix) => key.startsWith(prefix))) fail(`unsupported PAX keyword: ${key}`);
145
+ if (!supportedKeywords.has(key)) continue;
146
146
  const value = text(record.subarray(equals + 1));
147
147
  if (value.includes("\0")) fail("invalid PAX key/value");
148
148
  if (key === "hdrcharset" && value !== "" && value !== "ISO-IR 10646 2000 UTF-8") fail("unsupported PAX header charset");
@@ -2208,6 +2208,29 @@ function selected(tags, names, duplicates, resources) {
2208
2208
  }
2209
2209
  return values;
2210
2210
  }
2211
+ var jpegPrintConversions = {
2212
+ Orientation: {
2213
+ "1": "Horizontal (normal)",
2214
+ "2": "Mirror horizontal",
2215
+ "3": "Rotate 180",
2216
+ "4": "Mirror vertical",
2217
+ "5": "Mirror horizontal and rotate 270 CW",
2218
+ "6": "Rotate 90 CW",
2219
+ "7": "Mirror horizontal and rotate 90 CW",
2220
+ "8": "Rotate 270 CW"
2221
+ },
2222
+ ResolutionUnit: { "1": "None", "2": "inches", "3": "cm" }
2223
+ };
2224
+ function printConverted(tags, invocation, resources) {
2225
+ if (invocation.numeric || invocation.binary) return tags;
2226
+ resources.admit("work", tags.length * (invocation.valueConvTags.length + 1) * 64);
2227
+ resources.admit("retained", tags.length * 512);
2228
+ return tags.map((tag) => {
2229
+ const conversions = tag.chunkType === "JPEG" && tag.group === "IFD0" ? jpegPrintConversions[tag.name] : void 0;
2230
+ if (!conversions || invocation.valueConvTags.includes(tag.name.toLowerCase())) return tag;
2231
+ return { ...tag, value: conversions[tag.value] ?? "Unknown (" + tag.value + ")" };
2232
+ });
2233
+ }
2211
2234
  function createExiftoolCommand(options = {}) {
2212
2235
  const limits = Object.freeze({ ...options.limits });
2213
2236
  return Object.freeze({
@@ -2436,6 +2459,7 @@ Limits:
2436
2459
  continue;
2437
2460
  }
2438
2461
  read++;
2462
+ tags = printConverted(tags, invocation, resources);
2439
2463
  const chosen = selected(tags, invocation.tags, invocation.json ? invocation.groupFamily === 4 : invocation.duplicates && !invocation.csv, resources);
2440
2464
  if (invocation.xml || invocation.tabular || invocation.template !== void 0) {
2441
2465
  const values = invocation.template !== void 0 ? selected(tags, [], false, resources) : chosen;
@@ -2634,7 +2658,7 @@ function evalSyncExiftool(inBytes, opArgs, readFileSync, writeFileSync) {
2634
2658
  continue;
2635
2659
  }
2636
2660
  const metadata = jpeg ? inspectJpeg(bytes, resources).tags : png ? inspectPng(bytes, resources).tags : inspectPdf(bytes, resources).tags;
2637
- const tags = [...systemTags(file, bytes.length, metadata, invocation, resources), ...metadata];
2661
+ const tags = printConverted([...systemTags(file, bytes.length, metadata, invocation, resources), ...metadata], invocation, resources);
2638
2662
  const chosen = selected(tags, invocation.tags, invocation.json ? invocation.groupFamily === 4 : invocation.duplicates && !invocation.csv, resources);
2639
2663
  if (invocation.xml || invocation.tabular || invocation.template !== void 0) {
2640
2664
  const values = invocation.template !== void 0 ? selected(tags, [], false, resources) : chosen;
@@ -20,7 +20,7 @@ import {
20
20
  sliceMp4,
21
21
  srtAst,
22
22
  webvttAst
23
- } from "../safe-bash/command-chunks/chunk-FH5VMAPS.js";
23
+ } from "../safe-bash/command-chunks/chunk-BZW25LNV.js";
24
24
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
25
25
 
26
26
  // packages/safe-bash-command-ffmpeg/src/index.ts
@@ -87,8 +87,21 @@ function parseFfmpegTimestamp(spec) {
87
87
  return parseFloat(trimmed) || 0;
88
88
  }
89
89
  function parseColorRgba(colorStr) {
90
- const s = colorStr.trim().toLowerCase();
90
+ const value = colorStr.trim().toLowerCase();
91
+ const separator = value.indexOf("@");
92
+ const s = separator < 0 ? value : value.slice(0, separator);
93
+ let alpha;
94
+ if (separator >= 0) {
95
+ const suffix = value.slice(separator + 1);
96
+ const opacity = Number(suffix);
97
+ const maximum = suffix.startsWith("0x") ? 255 : 1;
98
+ if (!suffix || !Number.isFinite(opacity) || opacity < 0 || opacity > maximum) {
99
+ throw new Error(`Invalid color alpha: ${colorStr}`);
100
+ }
101
+ alpha = Math.floor(opacity * 255 / maximum);
102
+ }
91
103
  const named = {
104
+ aliceblue: [240, 248, 255, 255],
92
105
  black: [0, 0, 0, 255],
93
106
  white: [255, 255, 255, 255],
94
107
  red: [255, 0, 0, 255],
@@ -103,17 +116,21 @@ function parseColorRgba(colorStr) {
103
116
  orange: [255, 165, 0, 255],
104
117
  purple: [128, 0, 128, 255]
105
118
  };
106
- if (named[s]) return named[s];
107
- const hex = s.startsWith("#") ? s.slice(1) : s.startsWith("0x") ? s.slice(2) : "";
108
- if (hex.length === 6) {
119
+ if (named[s]) {
120
+ const color = named[s];
121
+ color[3] = alpha ?? color[3];
122
+ return color;
123
+ }
124
+ const hex = s.startsWith("#") ? s.slice(1) : s.startsWith("0x") ? s.slice(2) : s;
125
+ if ((hex.length === 6 || hex.length === 8) && [...hex].every((digit) => "0123456789abcdef".includes(digit))) {
109
126
  return [
110
127
  parseInt(hex.slice(0, 2), 16) || 0,
111
128
  parseInt(hex.slice(2, 4), 16) || 0,
112
129
  parseInt(hex.slice(4, 6), 16) || 0,
113
- 255
130
+ alpha ?? (hex.length === 8 ? parseInt(hex.slice(6, 8), 16) : 255)
114
131
  ];
115
132
  }
116
- return [40, 120, 220, 255];
133
+ throw new Error(`Invalid color: ${colorStr}`);
117
134
  }
118
135
  function detachBytesBuffer(bytes) {
119
136
  if (!bytes) return;
@@ -155,11 +172,12 @@ function* lavfiSteps(spec, budget, durationOverride) {
155
172
  }
156
173
  if (filterName === "sine" || filterName === "anullsrc") {
157
174
  const sampleRate = parseInt(kv.sample_rate ?? kv.r ?? "44100", 10) || 44100;
158
- const freq = parseFloat(kv.frequency ?? kv.f ?? "440") || 440;
175
+ const parsedFrequency = parseFloat(kv.frequency ?? kv.f ?? "440");
176
+ const freq = Number.isNaN(parsedFrequency) ? 440 : parsedFrequency;
159
177
  const duration2 = durationOverride ?? (parseFloat(kv.duration ?? kv.d ?? "1") || 1);
160
178
  const layout = kv.channel_layout ?? kv.cl;
161
179
  const channels = layout === "mono" || layout === void 0 && filterName === "sine" ? 1 : 2;
162
- const totalSamples = Math.max(1024, Math.round(duration2 * sampleRate));
180
+ const totalSamples = Math.max(0, Math.round(duration2 * sampleRate));
163
181
  budget.checkDuration(duration2);
164
182
  const channelData = [];
165
183
  for (let channel = 0; channel < channels; channel++) {
@@ -167,7 +185,7 @@ function* lavfiSteps(spec, budget, durationOverride) {
167
185
  if (filterName === "sine") {
168
186
  for (let i = 0; i < totalSamples; i++) {
169
187
  if (i % 4096 === 0) yield;
170
- arr[i] = Math.sin(2 * Math.PI * freq * i / sampleRate) * 0.5;
188
+ arr[i] = Math.sin(2 * Math.PI * freq * i / sampleRate) * 0.125;
171
189
  }
172
190
  }
173
191
  channelData.push(arr);
@@ -302,12 +320,63 @@ function evalScaleDim(expr, iw, ih) {
302
320
  if (clean === "-1" || clean === "-2") return -1;
303
321
  if (clean === "iw" || clean === "in_w") return iw;
304
322
  if (clean === "ih" || clean === "in_h") return ih;
305
- if (clean.startsWith("iw/")) return Math.max(2, Math.round(iw / (parseFloat(clean.slice(3)) || 1)));
306
- if (clean.startsWith("ih/")) return Math.max(2, Math.round(ih / (parseFloat(clean.slice(3)) || 1)));
307
- if (clean.startsWith("iw*")) return Math.max(2, Math.round(iw * (parseFloat(clean.slice(3)) || 1)));
308
- if (clean.startsWith("ih*")) return Math.max(2, Math.round(ih * (parseFloat(clean.slice(3)) || 1)));
323
+ if (clean.startsWith("iw/")) return Math.max(1, Math.round(iw / (parseFloat(clean.slice(3)) || 1)));
324
+ if (clean.startsWith("ih/")) return Math.max(1, Math.round(ih / (parseFloat(clean.slice(3)) || 1)));
325
+ if (clean.startsWith("iw*")) return Math.max(1, Math.round(iw * (parseFloat(clean.slice(3)) || 1)));
326
+ if (clean.startsWith("ih*")) return Math.max(1, Math.round(ih * (parseFloat(clean.slice(3)) || 1)));
309
327
  return parseInt(clean, 10) || iw;
310
328
  }
329
+ function parseFilterExpression(expression, filter, variables) {
330
+ let offset = 0;
331
+ const source = expression.trim();
332
+ function parse(minPrecedence = 0, depth = 0) {
333
+ if (depth > 64) throw new Error(`${filter}: expression is too deeply nested`);
334
+ while (source[offset] === " ") offset++;
335
+ let value;
336
+ const first = source[offset];
337
+ if (first === "+" || first === "-") {
338
+ offset++;
339
+ value = parse(3, depth + 1) * (first === "-" ? -1 : 1);
340
+ } else if (first === "(") {
341
+ offset++;
342
+ value = parse(0, depth + 1);
343
+ if (source[offset++] !== ")") throw new Error(`${filter}: invalid expression`);
344
+ } else if (first && (first >= "a" && first <= "z" || first >= "A" && first <= "Z" || first === "_")) {
345
+ const start = offset++;
346
+ while (offset < source.length) {
347
+ const c = source[offset];
348
+ if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_") offset++;
349
+ else break;
350
+ }
351
+ const name = source.slice(start, offset);
352
+ value = Object.hasOwn(variables, name) ? variables[name] : NaN;
353
+ } else {
354
+ const start = offset;
355
+ while (offset < source.length) {
356
+ const c = source[offset];
357
+ if (c >= "0" && c <= "9" || c === "." || c === "e" || c === "E" || (c === "+" || c === "-") && (source[offset - 1] === "e" || source[offset - 1] === "E")) offset++;
358
+ else break;
359
+ }
360
+ value = start === offset ? NaN : Number(source.slice(start, offset));
361
+ }
362
+ for (; ; ) {
363
+ while (source[offset] === " ") offset++;
364
+ const operator = source[offset];
365
+ const precedence = operator === "+" || operator === "-" ? 1 : operator === "*" || operator === "/" ? 2 : 0;
366
+ if (precedence === 0 || precedence < minPrecedence) break;
367
+ offset++;
368
+ const right = parse(precedence + 1, depth + 1);
369
+ if (operator === "+") value += right;
370
+ else if (operator === "-") value -= right;
371
+ else if (operator === "*") value *= right;
372
+ else value /= right;
373
+ }
374
+ return value;
375
+ }
376
+ const angle = parse();
377
+ if (offset !== source.length || !Number.isFinite(angle)) throw new Error(`${filter}: invalid or unsupported expression`);
378
+ return angle;
379
+ }
311
380
  var GLYPH_5X7 = {
312
381
  " ": [0, 0, 0, 0, 0, 0, 0],
313
382
  "0": [14, 17, 19, 21, 25, 17, 14],
@@ -464,7 +533,23 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
464
533
  let work = 0;
465
534
  let current = frames;
466
535
  if (current.length === 0) return current;
467
- const filters = filterChainStr.split(",").map((f) => f.trim()).filter(Boolean);
536
+ const filters = [];
537
+ let filter = "";
538
+ let quoted = false;
539
+ for (let i = 0; i < filterChainStr.length; i++) {
540
+ const char = filterChainStr[i];
541
+ if (char === "'") {
542
+ quoted = !quoted;
543
+ } else if (!quoted && char === "\\" && i + 1 < filterChainStr.length) {
544
+ filter += filterChainStr[++i];
545
+ } else if (!quoted && char === ",") {
546
+ if (filter.trim()) filters.push(filter.trim());
547
+ filter = "";
548
+ } else {
549
+ filter += char;
550
+ }
551
+ }
552
+ if (filter.trim()) filters.push(filter.trim());
468
553
  for (const filterSpec of filters) {
469
554
  if (++work % 1024 === 0) {
470
555
  await yieldTurn(signal);
@@ -494,14 +579,14 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
494
579
  let targetW = evalScaleDim(wSpec, iw, ih);
495
580
  let targetH = evalScaleDim(hSpec, iw, ih);
496
581
  if (targetW < 0 && targetH > 0) {
497
- targetW = Math.max(2, Math.round(iw * targetH / Math.max(1, ih)));
498
- if (wSpec.trim() === "-2" && targetW % 2 !== 0) targetW++;
582
+ const multiple = wSpec.trim() === "-2" ? 2 : 1;
583
+ targetW = Math.max(1, Math.round(iw * targetH / (Math.max(1, ih) * multiple))) * multiple;
499
584
  } else if (targetH < 0 && targetW > 0) {
500
- targetH = Math.max(2, Math.round(ih * targetW / Math.max(1, iw)));
501
- if (hSpec.trim() === "-2" && targetH % 2 !== 0) targetH++;
585
+ const multiple = hSpec.trim() === "-2" ? 2 : 1;
586
+ targetH = Math.max(1, Math.round(ih * targetW / (Math.max(1, iw) * multiple))) * multiple;
502
587
  }
503
- targetW = Math.max(2, targetW);
504
- targetH = Math.max(2, targetH);
588
+ targetW = Math.max(1, targetW);
589
+ targetH = Math.max(1, targetH);
505
590
  current = await mapWork(current, async (f) => {
506
591
  budget.recordFrame(targetW, targetH);
507
592
  const resized = resizeImage(makeRgbaImg(f.width, f.height, f.data), {
@@ -524,15 +609,19 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
524
609
  } else if (name === "crop") {
525
610
  const iw = current[0].width;
526
611
  const ih = current[0].height;
527
- const cw = Math.min(iw, Math.max(1, evalScaleDim(named.w ?? positional[0] ?? String(iw), iw, ih)));
528
- const ch = Math.min(ih, Math.max(1, evalScaleDim(named.h ?? positional[1] ?? String(ih), iw, ih)));
612
+ const cw = evalScaleDim(named.w ?? positional[0] ?? String(iw), iw, ih);
613
+ const ch = evalScaleDim(named.h ?? positional[1] ?? String(ih), iw, ih);
614
+ if (!Number.isFinite(cw) || !Number.isFinite(ch) || cw <= 0 || ch <= 0 || cw > iw || ch > ih) {
615
+ throw new Error(`Invalid too big or non positive size for width '${cw}' or height '${ch}'`);
616
+ }
617
+ const variables = { iw, ih, ow: cw, oh: ch, in_w: iw, in_h: ih, out_w: cw, out_h: ch };
529
618
  const cx = Math.max(
530
619
  0,
531
- Math.min(iw - cw, parseInt(named.x ?? positional[2] ?? String(Math.floor((iw - cw) / 2)), 10) || 0)
620
+ Math.min(iw - cw, Math.trunc(parseFilterExpression(named.x ?? positional[2] ?? String(Math.floor((iw - cw) / 2)), "crop", variables)))
532
621
  );
533
622
  const cy = Math.max(
534
623
  0,
535
- Math.min(ih - ch, parseInt(named.y ?? positional[3] ?? String(Math.floor((ih - ch) / 2)), 10) || 0)
624
+ Math.min(ih - ch, Math.trunc(parseFilterExpression(named.y ?? positional[3] ?? String(Math.floor((ih - ch) / 2)), "crop", variables)))
536
625
  );
537
626
  current = await mapWork(current, async (f) => {
538
627
  const cropped = extractImage(makeRgbaImg(f.width, f.height, f.data), {
@@ -551,10 +640,16 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
551
640
  } else if (name === "pad") {
552
641
  const iw = current[0].width;
553
642
  const ih = current[0].height;
554
- const pw = Math.max(iw, evalScaleDim(named.w ?? named.width ?? positional[0] ?? String(iw), iw, ih));
555
- const ph = Math.max(ih, evalScaleDim(named.h ?? named.height ?? positional[1] ?? String(ih), iw, ih));
556
- const px = Math.max(0, Math.min(pw - iw, parseInt(named.x ?? positional[2] ?? "0", 10) || 0));
557
- const py = Math.max(0, Math.min(ph - ih, parseInt(named.y ?? positional[3] ?? "0", 10) || 0));
643
+ const width = named.w ?? named.width ?? positional[0] ?? String(iw);
644
+ const height = named.h ?? named.height ?? positional[1] ?? String(ih);
645
+ const pw = Number(width) === 0 ? iw : evalScaleDim(width, iw, ih);
646
+ const ph = Number(height) === 0 ? ih : evalScaleDim(height, iw, ih);
647
+ if (pw < iw || ph < ih) {
648
+ throw new Error("Padded dimensions cannot be smaller than input dimensions");
649
+ }
650
+ const variables = { iw, ih, ow: pw, oh: ph, in_w: iw, in_h: ih, out_w: pw, out_h: ph };
651
+ const px = Math.max(0, Math.min(pw - iw, Math.trunc(parseFilterExpression(named.x ?? positional[2] ?? "0", "pad", variables))));
652
+ const py = Math.max(0, Math.min(ph - ih, Math.trunc(parseFilterExpression(named.y ?? positional[3] ?? "0", "pad", variables))));
558
653
  const col = parseColorRgba(named.color ?? positional[4] ?? "black");
559
654
  current = await mapWork(current, async (f) => {
560
655
  const padded = extendImage(makeRgbaImg(f.width, f.height, f.data), {
@@ -562,7 +657,7 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
562
657
  top: py,
563
658
  right: Math.max(0, pw - iw - px),
564
659
  bottom: Math.max(0, ph - ih - py),
565
- background: { r: col[0], g: col[1], b: col[2], a: 255 },
660
+ background: { r: col[0], g: col[1], b: col[2], a: col[3] },
566
661
  extendWith: "background"
567
662
  });
568
663
  return {
@@ -582,11 +677,12 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
582
677
  const out = flipImage(makeRgbaImg(f.width, f.height, f.data));
583
678
  return { ...f, data: out.data };
584
679
  }, signal);
585
- } else if (name === "transpose" || name === "rotate") {
680
+ } else if (name === "transpose") {
586
681
  const dir = named.dir ?? positional[0] ?? "1";
587
- const angle = name === "transpose" ? dir === "2" ? 270 : 90 : 90;
682
+ const angle = dir === "0" || dir === "2" ? 270 : 90;
588
683
  current = await mapWork(current, async (f) => {
589
- const out = rotateImage(makeRgbaImg(f.width, f.height, f.data), angle);
684
+ const rotated = rotateImage(makeRgbaImg(f.width, f.height, f.data), angle);
685
+ const out = dir === "0" || dir === "3" ? flipImage(rotated) : rotated;
590
686
  return {
591
687
  ...f,
592
688
  width: out.width,
@@ -594,6 +690,41 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
594
690
  data: out.data
595
691
  };
596
692
  }, signal);
693
+ } else if (name === "rotate") {
694
+ const angle = parseFilterExpression(named.angle ?? named.a ?? (positional[0] || "0"), "rotate", { PI: Math.PI });
695
+ const cos = Math.cos(angle);
696
+ const sin = Math.sin(angle);
697
+ current = await mapWork(current, async (f) => {
698
+ budget.recordFrame(f.width, f.height);
699
+ const data = new Uint8Array(f.data.length);
700
+ const cx = (f.width - 1) / 2;
701
+ const cy = (f.height - 1) / 2;
702
+ for (let y = 0; y < f.height; y++) {
703
+ await yieldTurn(signal);
704
+ budget.checkCpu();
705
+ for (let x = 0; x < f.width; x++) {
706
+ const sx = cos * (x - cx) + sin * (y - cy) + cx;
707
+ const sy = -sin * (x - cx) + cos * (y - cy) + cy;
708
+ const dest = (y * f.width + x) * 4;
709
+ data[dest + 3] = 255;
710
+ if (sx < -1 || sx > f.width || sy < -1 || sy > f.height) continue;
711
+ const px = Math.max(0, Math.min(f.width - 1, sx));
712
+ const py = Math.max(0, Math.min(f.height - 1, sy));
713
+ const x0 = Math.floor(px);
714
+ const y0 = Math.floor(py);
715
+ const x1 = Math.min(x0 + 1, f.width - 1);
716
+ const y1 = Math.min(y0 + 1, f.height - 1);
717
+ const dx = px - x0;
718
+ const dy = py - y0;
719
+ for (let c = 0; c < 4; c++) {
720
+ const top = f.data[(y0 * f.width + x0) * 4 + c] * (1 - dx) + f.data[(y0 * f.width + x1) * 4 + c] * dx;
721
+ const bottom = f.data[(y1 * f.width + x0) * 4 + c] * (1 - dx) + f.data[(y1 * f.width + x1) * 4 + c] * dx;
722
+ data[dest + c] = Math.floor(top * (1 - dy) + bottom * dy + 1e-8);
723
+ }
724
+ }
725
+ }
726
+ return { ...f, data };
727
+ }, signal);
597
728
  } else if (name === "negate") {
598
729
  current = await mapWork(current, async (f) => {
599
730
  const out = negateImage(makeRgbaImg(f.width, f.height, f.data));
@@ -609,15 +740,15 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
609
740
  const totalDuration = current.reduce((acc, f) => acc + f.durationSeconds, 0) || 1;
610
741
  const targetCount = Math.max(1, Math.round(totalDuration * targetFps));
611
742
  const resampled = [];
743
+ let srcIdx = 0;
612
744
  for (let i = 0; i < targetCount; i++) {
613
745
  if (++work % 1024 === 0) {
614
746
  await yieldTurn(signal);
615
747
  signal?.throwIfAborted();
616
748
  }
617
- const srcIdx = Math.min(
618
- current.length - 1,
619
- Math.floor(i / targetCount * current.length)
620
- );
749
+ while (srcIdx + 1 < current.length && Math.round(current[srcIdx + 1].ptsSeconds * targetFps) <= i) {
750
+ srcIdx++;
751
+ }
621
752
  const src = current[srcIdx];
622
753
  resampled.push({
623
754
  ...src,
@@ -762,13 +893,17 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
762
893
  return out;
763
894
  }, signal);
764
895
  } else if (name === "drawbox") {
765
- const bx = parseInt(named.x ?? positional[0] ?? "0", 10) || 0;
766
- const by = parseInt(named.y ?? positional[1] ?? "0", 10) || 0;
767
896
  const bw = parseInt(named.w ?? named.width ?? positional[2] ?? "16", 10) || 16;
768
897
  const bh = parseInt(named.h ?? named.height ?? positional[3] ?? "16", 10) || 16;
769
898
  const col = parseColorRgba(named.color ?? named.c ?? positional[4] ?? "red");
899
+ const thicknessOption = named.t ?? named.thickness ?? positional[5] ?? "3";
900
+ const thickness = thicknessOption === "fill" ? Infinity : parseInt(thicknessOption, 10);
901
+ const replace = named.replace === "1";
770
902
  current = await mapWork(current, async (f) => {
771
903
  let work2 = 0;
904
+ const variables = { iw: f.width, ih: f.height, in_w: f.width, in_h: f.height };
905
+ const bx = Math.trunc(parseFilterExpression(named.x ?? positional[0] ?? "0", "drawbox", variables));
906
+ const by = Math.trunc(parseFilterExpression(named.y ?? positional[1] ?? "0", "drawbox", variables));
772
907
  const nextData = new Uint8Array(f.data);
773
908
  for (let y = Math.max(0, by); y < Math.min(f.height, by + bh); y++) {
774
909
  if (++work2 % 1024 === 0) {
@@ -780,11 +915,15 @@ async function applyVideoFilterChain(frames, filterChainStr, budget, subtitleCue
780
915
  await yieldTurn(signal);
781
916
  signal?.throwIfAborted();
782
917
  }
918
+ if (Math.min(x - bx, y - by, bx + bw - 1 - x, by + bh - 1 - y) >= thickness) continue;
783
919
  const idx = (y * f.width + x) * 4;
784
- nextData[idx] = col[0];
785
- nextData[idx + 1] = col[1];
786
- nextData[idx + 2] = col[2];
787
- nextData[idx + 3] = 255;
920
+ if (replace) {
921
+ nextData.set(col, idx);
922
+ continue;
923
+ }
924
+ for (let channel = 0; channel < 3; channel++) {
925
+ nextData[idx + channel] = (nextData[idx + channel] * (255 - col[3]) + col[channel] * col[3]) / 255;
926
+ }
788
927
  }
789
928
  }
790
929
  return { ...f, data: nextData };
@@ -837,7 +976,6 @@ function evalSyncFfmpeg(inBytesOrArgs, maybeArgs, readFileSync, writeFileSync) {
837
976
  let rotation;
838
977
  let noOverwrite = true;
839
978
  const vfFilters = [];
840
- const afFilters = [];
841
979
  const metadata = {};
842
980
  let outputTarget;
843
981
  for (let i = 0; i < args.length; i++) {
@@ -914,8 +1052,7 @@ function evalSyncFfmpeg(inBytesOrArgs, maybeArgs, readFileSync, writeFileSync) {
914
1052
  const f = args[++i];
915
1053
  if (f) vfFilters.push(f);
916
1054
  } else if (arg === "-af" || arg === "-filter:a") {
917
- const f = args[++i];
918
- if (f) afFilters.push(f);
1055
+ return void 0;
919
1056
  } else if (arg === "-metadata" || arg.startsWith("-metadata:")) {
920
1057
  const kv = args[++i] ?? "";
921
1058
  const eq = kv.indexOf("=");
@@ -1799,7 +1936,7 @@ function createFfmpegCommand(options = {}) {
1799
1936
  const origCh = t.decodedAudio?.channels ?? t.codecDescriptions[0]?.channels ?? 2;
1800
1937
  const targetRate = audioRate ?? origRate;
1801
1938
  const targetCh = audioChannels ?? origCh;
1802
- const durSec = Math.max(0.05, (t.duration || origRate) / Math.max(1, t.timescale || origRate));
1939
+ const durSec = (t.duration || origRate) / Math.max(1, t.timescale || origRate);
1803
1940
  let volFactor = t.volume ?? 1;
1804
1941
  let tempoFactor = 1;
1805
1942
  let reverseAudio = false;
@@ -1816,11 +1953,14 @@ function createFfmpegCommand(options = {}) {
1816
1953
  const trimmed = item.trim();
1817
1954
  if (trimmed.startsWith("volume=")) {
1818
1955
  const volSpec = trimmed.slice("volume=".length).trim();
1819
- volFactor *= volSpec.endsWith("dB") ? Math.pow(10, (parseFloat(volSpec.slice(0, -2)) || 0) / 20) : parseFloat(volSpec) || 1;
1956
+ const gain = parseFloat(volSpec);
1957
+ volFactor *= volSpec.endsWith("dB") ? Math.pow(10, (parseFloat(volSpec.slice(0, -2)) || 0) / 20) : Number.isNaN(gain) ? 1 : gain;
1820
1958
  } else if (trimmed.startsWith("atempo=")) {
1821
1959
  tempoFactor *= Math.max(0.25, Math.min(4, parseFloat(trimmed.slice(7)) || 1));
1822
1960
  } else if (trimmed === "areverse") {
1823
1961
  reverseAudio = true;
1962
+ } else {
1963
+ throw new Error(`Unsupported audio filter: ${trimmed}`);
1824
1964
  }
1825
1965
  }
1826
1966
  }
@@ -1833,6 +1973,7 @@ function createFfmpegCommand(options = {}) {
1833
1973
  }
1834
1974
  const dst = new Float32Array(targetSamples);
1835
1975
  const srcCh = t.decodedAudio?.channelData[Math.min(c, (t.decodedAudio.channelData.length || 1) - 1)];
1976
+ const rightCh = origCh === 2 && targetCh === 1 ? t.decodedAudio.channelData[1] : void 0;
1836
1977
  if (srcCh && srcCh.length > 0) {
1837
1978
  for (let i = 0; i < targetSamples; i++) {
1838
1979
  if (++work % 1024 === 0) {
@@ -1840,7 +1981,8 @@ function createFfmpegCommand(options = {}) {
1840
1981
  context.signal?.throwIfAborted();
1841
1982
  }
1842
1983
  const srcIdx = Math.min(srcCh.length - 1, Math.floor(i / targetSamples * srcCh.length));
1843
- dst[i] = Math.max(-1, Math.min(1, srcCh[srcIdx] * volFactor));
1984
+ const sample = rightCh ? (srcCh[srcIdx] + rightCh[srcIdx]) / 2 : srcCh[srcIdx];
1985
+ dst[i] = Math.max(-1, Math.min(1, sample * volFactor));
1844
1986
  }
1845
1987
  }
1846
1988
  if (reverseAudio) dst.reverse();
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createFfprobeCommand
3
- } from "../safe-bash/command-chunks/chunk-RSFKQE2U.js";
3
+ } from "../safe-bash/command-chunks/chunk-YAXP4EHP.js";
4
4
  import "../safe-bash/command-chunks/chunk-MGBHGZEX.js";
5
- import "../safe-bash/command-chunks/chunk-FH5VMAPS.js";
5
+ import "../safe-bash/command-chunks/chunk-BZW25LNV.js";
6
6
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
7
7
 
8
8
  // packages/safe-bash-command-ffprobe/src/index.ts
@@ -11,9 +11,9 @@ import {
11
11
  loadManifestResources,
12
12
  resolvePath,
13
13
  rethrowRuntimeError
14
- } from "../safe-bash/command-chunks/chunk-RSFKQE2U.js";
14
+ } from "../safe-bash/command-chunks/chunk-YAXP4EHP.js";
15
15
  import "../safe-bash/command-chunks/chunk-MGBHGZEX.js";
16
- import "../safe-bash/command-chunks/chunk-FH5VMAPS.js";
16
+ import "../safe-bash/command-chunks/chunk-BZW25LNV.js";
17
17
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
18
18
  export {
19
19
  createFfprobeCommand,