@poe-platform/safe-bash 0.1.844 → 0.1.845
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.
- package/dist/safe-bash/command-chunks/{chunk-3SWIXYXT.js → chunk-4KM2VJRB.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-LORSYIAF.js → chunk-CMGVQM4Y.js} +14 -4
- package/dist/safe-bash-command-ffmpeg/index.js +113 -25
- package/dist/safe-bash-command-ffprobe/index.js +2 -2
- package/dist/safe-bash-command-ffprobe/media.js +2 -2
- package/dist/safe-bash-command-imagemagick/index.d.ts +1 -0
- package/dist/safe-bash-command-imagemagick/index.js +60 -29
- package/package.json +2 -2
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
scanMp4Frames,
|
|
14
14
|
scanMp4Packets,
|
|
15
15
|
wavPacketFrames
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CMGVQM4Y.js";
|
|
17
17
|
|
|
18
18
|
// packages/safe-bash-command-ffprobe/src/read-intervals.ts
|
|
19
19
|
import { PagedStorage } from "@poe-platform/safe-fs/storage";
|
|
@@ -399724,19 +399724,29 @@ function serializeWav(doc, options = {}) {
|
|
|
399724
399724
|
} else {
|
|
399725
399725
|
throw new Error("WAV encoding requires decoded PCM audio");
|
|
399726
399726
|
}
|
|
399727
|
+
const channelMask = audioTrack?.decodedAudio?.channelMask ?? (decodedAudio ? decodedAudio.channelMask : void 0) ?? [11, 263, 55, 63, 1807, 1599][channels - 3];
|
|
399728
|
+
const extensible = channelMask !== void 0 || channels > 2;
|
|
399729
|
+
const fmtSize = extensible ? 40 : 16;
|
|
399727
399730
|
const padding = pcmBytes.byteLength % 2;
|
|
399728
|
-
const writer = new BinaryWriter(
|
|
399731
|
+
const writer = new BinaryWriter(28 + fmtSize + pcmBytes.byteLength + padding);
|
|
399729
399732
|
writer.writeFourCC("RIFF");
|
|
399730
|
-
writer.writeU32LE(
|
|
399733
|
+
writer.writeU32LE(20 + fmtSize + pcmBytes.byteLength + padding);
|
|
399731
399734
|
writer.writeFourCC("WAVE");
|
|
399732
399735
|
writer.writeFourCC("fmt ");
|
|
399733
|
-
writer.writeU32LE(
|
|
399734
|
-
writer.writeU16LE(formatTag);
|
|
399736
|
+
writer.writeU32LE(fmtSize);
|
|
399737
|
+
writer.writeU16LE(extensible ? 65534 : formatTag);
|
|
399735
399738
|
writer.writeU16LE(channels);
|
|
399736
399739
|
writer.writeU32LE(sampleRate);
|
|
399737
399740
|
writer.writeU32LE(sampleRate * channels * (bitsPerSample >>> 3));
|
|
399738
399741
|
writer.writeU16LE(channels * (bitsPerSample >>> 3));
|
|
399739
399742
|
writer.writeU16LE(bitsPerSample);
|
|
399743
|
+
if (extensible) {
|
|
399744
|
+
writer.writeU16LE(22);
|
|
399745
|
+
writer.writeU16LE(bitsPerSample);
|
|
399746
|
+
writer.writeU32LE(channelMask ?? 0);
|
|
399747
|
+
writer.writeU32LE(formatTag);
|
|
399748
|
+
writer.writeBytes(Uint8Array.of(0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113));
|
|
399749
|
+
}
|
|
399740
399750
|
writer.writeFourCC("data");
|
|
399741
399751
|
writer.writeU32LE(pcmBytes.byteLength);
|
|
399742
399752
|
writer.writeBytes(pcmBytes);
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
sliceMp4,
|
|
24
24
|
srtAst,
|
|
25
25
|
webvttAst
|
|
26
|
-
} from "../safe-bash/command-chunks/chunk-
|
|
26
|
+
} from "../safe-bash/command-chunks/chunk-CMGVQM4Y.js";
|
|
27
27
|
import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
|
|
28
28
|
|
|
29
29
|
// packages/safe-bash-command-ffmpeg/src/index.ts
|
|
@@ -78,6 +78,73 @@ async function resampleAudio(channels, outputLength, rateRatio, budget, signal)
|
|
|
78
78
|
|
|
79
79
|
// packages/safe-bash-command-ffmpeg/src/audio-tempo.ts
|
|
80
80
|
import { yieldTurn as yieldTurn2 } from "../safe-bash-contracts/yield.js";
|
|
81
|
+
async function filterUnityTempo(channels, sampleRate, pcm16, budget, signal) {
|
|
82
|
+
const length = channels[0]?.length ?? 0;
|
|
83
|
+
const window = 2 ** Math.ceil(Math.log2(Math.max(2, Math.floor(sampleRate / 24))));
|
|
84
|
+
const hop = window / 2;
|
|
85
|
+
if (length < hop) return channels.map(() => new Float32Array(0));
|
|
86
|
+
const hann = Float32Array.from({ length: window }, (_, i) => (1 - Math.cos(2 * Math.PI * i / (window - 1))) / 2);
|
|
87
|
+
const mono = new Float32Array(length);
|
|
88
|
+
for (let i = 0; i < length; i++) {
|
|
89
|
+
if (i % 16384 === 0) {
|
|
90
|
+
await yieldTurn2(signal);
|
|
91
|
+
signal?.throwIfAborted();
|
|
92
|
+
budget.checkCpu();
|
|
93
|
+
}
|
|
94
|
+
for (const channel of channels) {
|
|
95
|
+
if (Math.abs(channel[i]) > Math.abs(mono[i])) mono[i] = channel[i];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const output = channels.map(() => []);
|
|
99
|
+
let previous = -hop;
|
|
100
|
+
let position = -hop;
|
|
101
|
+
let work = 0;
|
|
102
|
+
while (true) {
|
|
103
|
+
let current = previous + hop;
|
|
104
|
+
position += hop;
|
|
105
|
+
const drift = position - hop - previous;
|
|
106
|
+
const start = Math.min(window, Math.max(0, -drift));
|
|
107
|
+
const end = Math.max(0, Math.min(window - drift, window - window / 16));
|
|
108
|
+
let correction = -drift;
|
|
109
|
+
let best = -Infinity;
|
|
110
|
+
for (let lag = start; lag < end; lag++) {
|
|
111
|
+
let correlation = 0;
|
|
112
|
+
for (let j = Math.max(0, -current); j < Math.min(window - lag, length - current, length - previous - lag); j++) {
|
|
113
|
+
correlation += (mono[previous + j + lag] ?? 0) * mono[current + j];
|
|
114
|
+
if (++work % 16384 === 0) {
|
|
115
|
+
await yieldTurn2(signal);
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
budget.checkCpu();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const metric = correlation * (drift + lag) * (lag - start) * (end - lag);
|
|
121
|
+
if (metric > best) {
|
|
122
|
+
best = metric;
|
|
123
|
+
correction = lag - hop;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
current -= correction;
|
|
127
|
+
const count = Math.max(0, Math.min(window, length - current));
|
|
128
|
+
const overlap = Math.min(hop, count, length - previous - hop);
|
|
129
|
+
for (let c = 0; c < channels.length; c++) {
|
|
130
|
+
const channel = channels[c];
|
|
131
|
+
const dst = output[c];
|
|
132
|
+
for (let i = 0; i < overlap; i++) {
|
|
133
|
+
const a = channel[previous + hop + i] ?? 0;
|
|
134
|
+
const b = channel[current + i] ?? 0;
|
|
135
|
+
const sample = current + i < 0 ? a : Math.fround(a * hann[hop + i] + Math.fround(b * hann[i]));
|
|
136
|
+
dst.push(pcm16 ? Math.trunc(sample * 32768) / 32768 : sample);
|
|
137
|
+
}
|
|
138
|
+
if (current + count >= length) {
|
|
139
|
+
for (let i = overlap; i < count; i++) dst.push(channel[current + i] ?? 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
budget.checkDuration((output[0]?.length ?? 0) / sampleRate);
|
|
143
|
+
if (current + count >= length) break;
|
|
144
|
+
previous = current;
|
|
145
|
+
}
|
|
146
|
+
return output.map((channel) => Float32Array.from(channel));
|
|
147
|
+
}
|
|
81
148
|
async function stretchAudio(channels, sampleRate, tempo, budget, signal) {
|
|
82
149
|
const length = channels[0]?.length ?? 0;
|
|
83
150
|
if (tempo === 1 || length === 0) return channels;
|
|
@@ -1411,8 +1478,10 @@ function evalSyncFfmpeg(inBytesOrArgs, maybeArgs, readFileSync, writeFileSync) {
|
|
|
1411
1478
|
if (!Number.isSafeInteger(val) || val < 0) return void 0;
|
|
1412
1479
|
if (args.slice(i + 1).includes("-i")) pendingStartNumber = val;
|
|
1413
1480
|
else outputStartNumber = val;
|
|
1414
|
-
} else if (arg === "-ar")
|
|
1415
|
-
|
|
1481
|
+
} else if (arg === "-ar") {
|
|
1482
|
+
audioRate = parseInt(args[++i] ?? "0", 10) || void 0;
|
|
1483
|
+
if (audioRate !== void 0 && audioRate < 0) return void 0;
|
|
1484
|
+
} else if (arg === "-ac") audioChannels = parseInt(args[++i] ?? "0", 10) || void 0;
|
|
1416
1485
|
else if (arg === "-hide_banner" || arg === "-nostdin" || arg === "-stats") continue;
|
|
1417
1486
|
else if (arg === "-f") {
|
|
1418
1487
|
const val = args[++i];
|
|
@@ -1483,7 +1552,7 @@ function evalSyncFfmpeg(inBytesOrArgs, maybeArgs, readFileSync, writeFileSync) {
|
|
|
1483
1552
|
} else if (arg === "-vf" || arg === "-filter:v") {
|
|
1484
1553
|
const f = args[++i];
|
|
1485
1554
|
if (f) vfFilters.push(f);
|
|
1486
|
-
} else if (arg === "-af" || arg === "-filter:a") {
|
|
1555
|
+
} else if (arg === "-af" || arg === "-filter:a" || arg.startsWith("-af:") || arg.startsWith("-filter:a:")) {
|
|
1487
1556
|
return void 0;
|
|
1488
1557
|
} else if (arg === "-disposition" || arg.startsWith("-disposition:")) {
|
|
1489
1558
|
streamDispositions.push([arg.slice("-disposition:".length), args[++i] ?? ""]);
|
|
@@ -1778,6 +1847,10 @@ function createFfmpegCommand(options = {}) {
|
|
|
1778
1847
|
else outputStartNumber = val;
|
|
1779
1848
|
} else if (arg === "-ar") {
|
|
1780
1849
|
audioRate = parseInt(args[++i] ?? "0", 10) || void 0;
|
|
1850
|
+
if (audioRate !== void 0 && audioRate < 0) {
|
|
1851
|
+
await writeBytes(context.stderr, encodeUtf8("ffmpeg: sample rate must be nonnegative\n"), context.signal);
|
|
1852
|
+
return { exitCode: 1 };
|
|
1853
|
+
}
|
|
1781
1854
|
} else if (arg === "-ac") {
|
|
1782
1855
|
audioChannels = parseInt(args[++i] ?? "0", 10) || void 0;
|
|
1783
1856
|
} else if (arg === "-hide_banner" || arg === "-nostdin" || arg === "-stats") {
|
|
@@ -1890,9 +1963,14 @@ function createFfmpegCommand(options = {}) {
|
|
|
1890
1963
|
} else if (arg === "-vf" || arg === "-filter:v") {
|
|
1891
1964
|
const f = args[++i];
|
|
1892
1965
|
if (f) vfFilters.push(f);
|
|
1893
|
-
} else if (arg === "-af" || arg === "-filter:a") {
|
|
1966
|
+
} else if (arg === "-af" || arg === "-filter:a" || arg.startsWith("-af:") || arg.startsWith("-filter:a:")) {
|
|
1894
1967
|
const f = args[++i];
|
|
1895
|
-
|
|
1968
|
+
const selector = arg.startsWith("-af:") ? arg.slice(4) : arg.startsWith("-filter:a:") ? arg.slice(10) : void 0;
|
|
1969
|
+
const ordinal = selector === void 0 ? void 0 : Number(selector);
|
|
1970
|
+
if (selector !== void 0 && (!selector || !Array.from(selector).every((char) => char >= "0" && char <= "9") || !Number.isSafeInteger(ordinal))) {
|
|
1971
|
+
throw new Error(`Unsupported audio stream specifier: ${selector}`);
|
|
1972
|
+
}
|
|
1973
|
+
if (f) afFilters.push({ ordinal, expression: f });
|
|
1896
1974
|
} else if (arg === "-filter_complex" || arg === "-lavfi") {
|
|
1897
1975
|
filterComplex = args[++i];
|
|
1898
1976
|
} else if (arg === "-disposition" || arg.startsWith("-disposition:")) {
|
|
@@ -2430,7 +2508,8 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2430
2508
|
}, context.signal);
|
|
2431
2509
|
}
|
|
2432
2510
|
validateAudioEncoder(audioCodec, workingDoc, astPlugins);
|
|
2433
|
-
|
|
2511
|
+
const audioTrackCount = workingDoc.tracks.filter((track) => track.type === "audio").length;
|
|
2512
|
+
if (audioCodec === "copy" && audioTrackCount > 0 && afFilters.some((filter) => filter.ordinal === void 0 || filter.ordinal < audioTrackCount)) {
|
|
2434
2513
|
await writeBytes(context.stderr, encodeUtf8("ffmpeg: Filtering and streamcopy cannot be used together for audio.\n"), context.signal);
|
|
2435
2514
|
return { exitCode: 218 };
|
|
2436
2515
|
}
|
|
@@ -2538,11 +2617,15 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2538
2617
|
workingDoc = limitAudioCopyPackets(workingDoc, maxAudioFrames);
|
|
2539
2618
|
}
|
|
2540
2619
|
if ((audioRate !== void 0 || audioChannels !== void 0 || afFilters.length > 0) && audioCodec !== "copy") {
|
|
2620
|
+
let audioOrdinal = 0;
|
|
2541
2621
|
workingDoc = {
|
|
2542
2622
|
...workingDoc,
|
|
2543
2623
|
tracks: await mapWork(workingDoc.tracks, async (t) => {
|
|
2544
2624
|
let work = 0;
|
|
2545
2625
|
if (t.type !== "audio") return t;
|
|
2626
|
+
const ordinal = audioOrdinal++;
|
|
2627
|
+
const filter = afFilters.filter((filter2) => filter2.ordinal === void 0 || filter2.ordinal === ordinal).at(-1);
|
|
2628
|
+
if (audioRate === void 0 && audioChannels === void 0 && !filter) return t;
|
|
2546
2629
|
const audio = t.decodedAudio ?? (t.codecDescriptions[0]?.codecName === "aac" ? decodeAacTrack(workingDoc, t) : void 0);
|
|
2547
2630
|
if (!audio) throw new Error("Audio filtering requires decoded PCM audio");
|
|
2548
2631
|
const origRate = audio.sampleRate;
|
|
@@ -2552,19 +2635,15 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2552
2635
|
const channelMask = audio.channelMask;
|
|
2553
2636
|
const surroundMono = origCh === 6 && targetCh === 1;
|
|
2554
2637
|
const monoStereo = origCh === 1 && (targetCh === 2 || targetCh === 3);
|
|
2638
|
+
const pcm16MonoStereo = monoStereo && !filter && targetRate === origRate && t.codecDescriptions[0]?.codecName === "pcm_s16le" && (audioCodec === "pcm_s16le" || audioCodec === void 0 && (outputFormat ?? outputTarget2.split(".").pop()?.toLowerCase()) === "wav");
|
|
2639
|
+
const monoSurround = origCh === 1 && targetCh >= 4 && targetCh <= 8;
|
|
2555
2640
|
if (targetCh !== origCh && origCh > 2 && (!surroundMono || channelMask && channelMask !== 63 && channelMask !== 1551)) {
|
|
2556
2641
|
throw new Error(`Unsupported audio channel layout conversion: ${origCh} channels (mask ${channelMask ?? 0}) to ${targetCh}`);
|
|
2557
2642
|
}
|
|
2558
|
-
const
|
|
2559
|
-
let
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
for (const af of afFilters) {
|
|
2563
|
-
if (++work % 1024 === 0) {
|
|
2564
|
-
await yieldTurn5(context.signal);
|
|
2565
|
-
context.signal?.throwIfAborted();
|
|
2566
|
-
}
|
|
2567
|
-
for (const item of af.split(",")) {
|
|
2643
|
+
const volFactor = t.volume ?? 1;
|
|
2644
|
+
let stretched = audio.channelData;
|
|
2645
|
+
if (filter) {
|
|
2646
|
+
for (const item of filter.expression.split(",")) {
|
|
2568
2647
|
if (++work % 1024 === 0) {
|
|
2569
2648
|
await yieldTurn5(context.signal);
|
|
2570
2649
|
context.signal?.throwIfAborted();
|
|
@@ -2576,22 +2655,31 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2576
2655
|
const gain = parseFilterExpression(decibels ? volSpec.slice(0, -2) : volSpec, "volume", {});
|
|
2577
2656
|
const factor = decibels ? Math.pow(10, gain / 20) : gain;
|
|
2578
2657
|
if (!Number.isFinite(factor)) throw new Error("volume: invalid gain");
|
|
2579
|
-
|
|
2658
|
+
stretched = await mapWork(stretched, async (channel) => {
|
|
2659
|
+
const output = new Float32Array(channel.length);
|
|
2660
|
+
for (let i2 = 0; i2 < channel.length; i2++) {
|
|
2661
|
+
if (++work % 1024 === 0) {
|
|
2662
|
+
await yieldTurn5(context.signal);
|
|
2663
|
+
context.signal?.throwIfAborted();
|
|
2664
|
+
}
|
|
2665
|
+
output[i2] = channel[i2] * factor;
|
|
2666
|
+
}
|
|
2667
|
+
return output;
|
|
2668
|
+
}, context.signal);
|
|
2580
2669
|
} else if (trimmed.startsWith("atempo=")) {
|
|
2581
2670
|
const tempo = Number(trimmed.slice(7));
|
|
2582
2671
|
if (!Number.isFinite(tempo) || tempo < 0.5 || tempo > 100) {
|
|
2583
2672
|
throw new Error("atempo must be in the range [0.5, 100]");
|
|
2584
2673
|
}
|
|
2585
|
-
|
|
2674
|
+
stretched = tempo === 1 ? await filterUnityTempo(stretched, origRate, t.codecDescriptions[0]?.codecName === "pcm_s16le", budget, context.signal) : await stretchAudio(stretched, origRate, tempo, budget, context.signal);
|
|
2586
2675
|
} else if (trimmed === "areverse") {
|
|
2587
|
-
|
|
2676
|
+
stretched = await mapWork(stretched, async (channel) => channel.slice().reverse(), context.signal);
|
|
2588
2677
|
} else {
|
|
2589
2678
|
throw new Error(`Unsupported audio filter: ${trimmed}`);
|
|
2590
2679
|
}
|
|
2591
2680
|
}
|
|
2592
2681
|
}
|
|
2593
|
-
const targetSamples = Math.max(0, Math.round(
|
|
2594
|
-
let stretched = await stretchAudio(audio.channelData, origRate, tempoFactor, budget, context.signal);
|
|
2682
|
+
const targetSamples = Math.max(0, Math.round((stretched[0]?.length ?? 0) * targetRate / origRate));
|
|
2595
2683
|
if (targetRate !== origRate) stretched = await resampleAudio(stretched, targetSamples, origRate / targetRate, budget, context.signal);
|
|
2596
2684
|
const newChannelData = [];
|
|
2597
2685
|
for (let c = 0; c < targetCh; c++) {
|
|
@@ -2600,7 +2688,7 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2600
2688
|
context.signal?.throwIfAborted();
|
|
2601
2689
|
}
|
|
2602
2690
|
const dst = new Float32Array(targetSamples);
|
|
2603
|
-
const srcCh = monoStereo && c >= 2 ? void 0 : stretched[origCh === 1 ? 0 : c];
|
|
2691
|
+
const srcCh = monoStereo && c >= 2 || monoSurround && c !== 2 ? void 0 : stretched[origCh === 1 ? 0 : c];
|
|
2604
2692
|
const rightCh = origCh === 2 && targetCh === 1 ? stretched[1] : void 0;
|
|
2605
2693
|
if (srcCh && srcCh.length > 0) {
|
|
2606
2694
|
for (let i2 = 0; i2 < targetSamples; i2++) {
|
|
@@ -2610,10 +2698,10 @@ To ignore this, add a trailing '?' to the map.
|
|
|
2610
2698
|
}
|
|
2611
2699
|
const srcIdx = Math.min(srcCh.length - 1, i2);
|
|
2612
2700
|
const sample = surroundMono ? ((stretched[0][srcIdx] + stretched[1][srcIdx]) * Math.SQRT1_2 + stretched[2][srcIdx] + (stretched[4][srcIdx] + stretched[5][srcIdx]) / 2) / (2 + Math.SQRT2) : rightCh ? (srcCh[srcIdx] + rightCh[srcIdx]) / 2 : srcCh[srcIdx];
|
|
2613
|
-
|
|
2701
|
+
const mixed = pcm16MonoStereo ? Math.floor((sample * 32768 * 23170 + 16384) / 32768) / 32768 : sample * (monoStereo ? Math.SQRT1_2 : 1);
|
|
2702
|
+
dst[i2] = Math.max(-1, Math.min(1, mixed * volFactor));
|
|
2614
2703
|
}
|
|
2615
2704
|
}
|
|
2616
|
-
if (reverseAudio) dst.reverse();
|
|
2617
2705
|
newChannelData.push(dst);
|
|
2618
2706
|
}
|
|
2619
2707
|
const nextDescs = t.codecDescriptions.map((d) => ({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFfprobeCommand
|
|
3
|
-
} from "../safe-bash/command-chunks/chunk-
|
|
3
|
+
} from "../safe-bash/command-chunks/chunk-4KM2VJRB.js";
|
|
4
4
|
import "../safe-bash/command-chunks/chunk-MGBHGZEX.js";
|
|
5
|
-
import "../safe-bash/command-chunks/chunk-
|
|
5
|
+
import "../safe-bash/command-chunks/chunk-CMGVQM4Y.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-
|
|
14
|
+
} from "../safe-bash/command-chunks/chunk-4KM2VJRB.js";
|
|
15
15
|
import "../safe-bash/command-chunks/chunk-MGBHGZEX.js";
|
|
16
|
-
import "../safe-bash/command-chunks/chunk-
|
|
16
|
+
import "../safe-bash/command-chunks/chunk-CMGVQM4Y.js";
|
|
17
17
|
import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
|
|
18
18
|
export {
|
|
19
19
|
createFfprobeCommand,
|
|
@@ -36,6 +36,7 @@ export interface MagickGeometry {
|
|
|
36
36
|
readonly percentY?: number;
|
|
37
37
|
readonly areaLimit?: number;
|
|
38
38
|
readonly isSubdivide?: boolean;
|
|
39
|
+
readonly aspectRatio?: number;
|
|
39
40
|
}
|
|
40
41
|
export declare function parseMagickGeometry(raw: string): MagickGeometry;
|
|
41
42
|
export declare function runIdentifyCli(argv: readonly string[], files: Map<string, Uint8Array> | IdentifyFileInput, stdinBytes?: Uint8Array, signal?: AbortSignal): Promise<ImageMagickCliResult>;
|
|
@@ -3947,6 +3947,13 @@ function parseMagickGeometry(raw) {
|
|
|
3947
3947
|
break;
|
|
3948
3948
|
}
|
|
3949
3949
|
}
|
|
3950
|
+
if (s.includes(":")) {
|
|
3951
|
+
const parts = s.split(":");
|
|
3952
|
+
const ratio = Number(parts[0]) / Number(parts[1]);
|
|
3953
|
+
if (parts.length === 2 && Number(parts[0]) > 0 && Number(parts[1]) > 0 && Number.isFinite(ratio)) {
|
|
3954
|
+
return { aspectRatio: ratio, x, y, hasOffset, forceExact, shrinkOnly, enlargeOnly, fillArea, isPercent: false };
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3950
3957
|
if (isArea) {
|
|
3951
3958
|
if (s.includes("x") || s.includes("X")) {
|
|
3952
3959
|
const [wStr, hStr] = s.split(/[xX]/);
|
|
@@ -4803,14 +4810,16 @@ function* createCheckerboardImageSteps(width, height) {
|
|
|
4803
4810
|
function* applyMagickSpliceSteps(img, geomStr, bg, gravity = "northwest", signal) {
|
|
4804
4811
|
let cooperativeWork = 0;
|
|
4805
4812
|
const g = parseMagickGeometry(geomStr);
|
|
4806
|
-
const sw = Math.max(0, Math.round(g.width ?? 0));
|
|
4807
|
-
const sh = Math.max(0, Math.round(g.height ?? 0));
|
|
4813
|
+
const sw = Math.max(0, Math.round((g.width ?? 0) * (g.isPercent ? img.width / 100 : 1)));
|
|
4814
|
+
const sh = Math.max(0, Math.round((g.height ?? 0) * (g.isPercent ? img.height / 100 : 1)));
|
|
4808
4815
|
const box = gravityAdjustBox(img.width, img.height, sw, sh, g.x, g.y, gravity);
|
|
4809
4816
|
const anchor = resolveGravityOffset(sw, sh, gravity);
|
|
4810
|
-
const sx = Math.max(0, Math.min(img.width, box.x + anchor.left));
|
|
4811
|
-
const sy = Math.max(0, Math.min(img.height, box.y + anchor.top));
|
|
4812
4817
|
const outW = img.width + sw;
|
|
4813
4818
|
const outH = img.height + sh;
|
|
4819
|
+
const xOffset = box.x + anchor.left;
|
|
4820
|
+
const sx = xOffset < 0 || xOffset > img.width ? outW : xOffset;
|
|
4821
|
+
if (box.y + anchor.top > outH) throw new Error("pixels are not authentic");
|
|
4822
|
+
const sy = Math.max(0, Math.min(img.height, box.y + anchor.top));
|
|
4814
4823
|
const out = new Uint8Array(new ArrayBuffer(outW * outH * 4 + outH), 0, outW * outH * 4);
|
|
4815
4824
|
for (let y = 0; y < outH; y++) {
|
|
4816
4825
|
if (++cooperativeWork % 65536 === 0)
|
|
@@ -4825,7 +4834,7 @@ function* applyMagickSpliceSteps(img, geomStr, bg, gravity = "northwest", signal
|
|
|
4825
4834
|
out[dstIdx + 2] = bg.b;
|
|
4826
4835
|
out[dstIdx + 3] = bg.a;
|
|
4827
4836
|
} else {
|
|
4828
|
-
const origX = x < sx ? x : x - sw;
|
|
4837
|
+
const origX = Math.min(img.width - 1, x < sx ? x : x - sw);
|
|
4829
4838
|
const origY = y < sy ? y : y - sh;
|
|
4830
4839
|
const srcIdx = (origY * img.width + origX) * 4;
|
|
4831
4840
|
out[dstIdx] = img.data[srcIdx];
|
|
@@ -6898,13 +6907,12 @@ function magickCropArea(img, geomStr, gravity) {
|
|
|
6898
6907
|
}
|
|
6899
6908
|
function magickExtentLayout(img, geomStr, state) {
|
|
6900
6909
|
const g = parseMagickGeometry(geomStr);
|
|
6901
|
-
const targetW = Math.max(1, Math.round(g.width ?? img.width));
|
|
6902
|
-
const targetH = Math.max(1, Math.round(g.height ?? img.height));
|
|
6903
|
-
|
|
6904
|
-
const
|
|
6905
|
-
const
|
|
6906
|
-
const
|
|
6907
|
-
const top = Math.round(offset.top - (isSouth ? -g.y : g.y));
|
|
6910
|
+
const targetW = Math.max(1, Math.round(g.aspectRatio !== void 0 ? Math.min(img.width, img.height * g.aspectRatio) : g.isPercent ? img.width * (g.percentX ?? 100) / 100 : g.width ?? img.width));
|
|
6911
|
+
const targetH = Math.max(1, Math.round(g.aspectRatio !== void 0 ? Math.min(img.height, img.width / g.aspectRatio) : g.isPercent ? img.height * (g.percentY ?? 100) / 100 : g.height ?? img.height));
|
|
6912
|
+
if (g.enlargeOnly && img.width > targetW && img.height > targetH || g.shrinkOnly && img.width < targetW && img.height < targetH) return void 0;
|
|
6913
|
+
const offset = gravityAdjustBox(img.width, img.height, targetW, targetH, g.x, g.y, state.gravity);
|
|
6914
|
+
const left = -offset.x;
|
|
6915
|
+
const top = -offset.y;
|
|
6908
6916
|
const dstX0 = Math.max(0, left);
|
|
6909
6917
|
const dstY0 = Math.max(0, top);
|
|
6910
6918
|
const dstX1 = Math.min(targetW, left + img.width);
|
|
@@ -6914,6 +6922,7 @@ function magickExtentLayout(img, geomStr, state) {
|
|
|
6914
6922
|
function* applyMagickExtentSteps(img, geomStr, state, signal) {
|
|
6915
6923
|
yield;
|
|
6916
6924
|
const layout = magickExtentLayout(img, geomStr, state);
|
|
6925
|
+
if (!layout) return img;
|
|
6917
6926
|
const virtualCanvas = magickResizeCanvas(img, layout.width, layout.height);
|
|
6918
6927
|
const source = img[magickInput];
|
|
6919
6928
|
const canvas = {
|
|
@@ -8950,18 +8959,22 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack, stdinBy
|
|
|
8950
8959
|
return { ...canvas, virtualCanvas: magickBorderCanvas(im, bw, bh) };
|
|
8951
8960
|
});
|
|
8952
8961
|
} else if (t === "-shave") {
|
|
8953
|
-
const
|
|
8962
|
+
const geometry = tokens[++i] ?? "0x0";
|
|
8963
|
+
const g = parseMagickGeometry(geometry);
|
|
8954
8964
|
const sw = Math.max(0, Math.round(g.width ?? 0));
|
|
8955
8965
|
const sh = Math.max(0, Math.round(g.height ?? sw));
|
|
8956
8966
|
stack = yield* mapSteps(stack, function* (im) {
|
|
8957
|
-
|
|
8958
|
-
|
|
8967
|
+
if (sw * 2 >= im.width || sh * 2 >= im.height) {
|
|
8968
|
+
analysis.warnings += `magick: geometry does not contain image '${geometry}'
|
|
8969
|
+
`;
|
|
8970
|
+
return im;
|
|
8971
|
+
}
|
|
8959
8972
|
return { ...yield* extractImageSteps2(im, {
|
|
8960
|
-
left:
|
|
8961
|
-
top:
|
|
8962
|
-
width:
|
|
8963
|
-
height:
|
|
8964
|
-
}), virtualCanvas: magickBorderCanvas(im, -
|
|
8973
|
+
left: sw,
|
|
8974
|
+
top: sh,
|
|
8975
|
+
width: im.width - sw * 2,
|
|
8976
|
+
height: im.height - sh * 2
|
|
8977
|
+
}), virtualCanvas: magickBorderCanvas(im, -sw, -sh) };
|
|
8965
8978
|
});
|
|
8966
8979
|
} else if (t === "-trim") {
|
|
8967
8980
|
stack = yield* mapSteps(stack, function* (im) {
|
|
@@ -9525,14 +9538,17 @@ async function transformStoredMagickCoordinates(image, backend, token, geometry,
|
|
|
9525
9538
|
xs = [{ source: w - dx, target: 0, length: dx }, { source: 0, target: dx, length: w - dx }];
|
|
9526
9539
|
ys = [{ source: h - dy, target: 0, length: dy }, { source: 0, target: dy, length: h - dy }];
|
|
9527
9540
|
} else if (token === "-splice") {
|
|
9528
|
-
const sw = Math.max(0, Math.round(g.width ?? 0)
|
|
9541
|
+
const sw = Math.max(0, Math.round((g.width ?? 0) * (g.isPercent ? w / 100 : 1)));
|
|
9542
|
+
const sh = Math.max(0, Math.round((g.height ?? 0) * (g.isPercent ? h / 100 : 1)));
|
|
9529
9543
|
const box = gravityAdjustBox(w, h, sw, sh, g.x, g.y, state.gravity);
|
|
9530
9544
|
const anchor = resolveGravityOffset(sw, sh, state.gravity);
|
|
9531
|
-
const x = Math.max(0, Math.min(w, box.x + anchor.left));
|
|
9532
|
-
const y = Math.max(0, Math.min(h, box.y + anchor.top));
|
|
9533
9545
|
width += sw;
|
|
9534
9546
|
height += sh;
|
|
9535
|
-
|
|
9547
|
+
const xOffset = box.x + anchor.left;
|
|
9548
|
+
const x = xOffset < 0 || xOffset > w ? width : xOffset;
|
|
9549
|
+
if (box.y + anchor.top > height) throw new Error("pixels are not authentic");
|
|
9550
|
+
const y = Math.max(0, Math.min(h, box.y + anchor.top));
|
|
9551
|
+
xs = [{ source: 0, target: 0, length: x }, { source: x, target: x + sw, length: Math.max(0, w - x) }];
|
|
9536
9552
|
ys = [{ source: 0, target: 0, length: y }, { source: y, target: y + sh, length: h - y }];
|
|
9537
9553
|
} else {
|
|
9538
9554
|
const cw = Math.max(0, Math.round((g.width ?? 0) * (g.isPercent ? w / 100 : 1)));
|
|
@@ -9553,7 +9569,16 @@ async function transformStoredMagickCoordinates(image, backend, token, geometry,
|
|
|
9553
9569
|
for (let offset = 0; offset < x.length; offset += 4096) {
|
|
9554
9570
|
if (work++ % 64 === 0) await yieldTurn11(signal);
|
|
9555
9571
|
const length = Math.min(4096, x.length - offset) * 4;
|
|
9556
|
-
const
|
|
9572
|
+
const sourceX = Math.min(w - 1, x.source + offset);
|
|
9573
|
+
const sourceLength = Math.min(length, (w - sourceX) * 4);
|
|
9574
|
+
let bytes = await backend.storage.read(image.position + ((y.source + row) * w + sourceX) * 4, sourceLength);
|
|
9575
|
+
if (sourceLength < length) {
|
|
9576
|
+
const extended = new Uint8Array(length);
|
|
9577
|
+
extended.set(bytes);
|
|
9578
|
+
const edge = bytes.subarray(sourceLength - 4);
|
|
9579
|
+
for (let i = sourceLength; i < length; i += 4) extended.set(edge, i);
|
|
9580
|
+
bytes = extended;
|
|
9581
|
+
}
|
|
9557
9582
|
await backend.storage.write(position + ((y.target + row) * width + x.target + offset) * 4, bytes);
|
|
9558
9583
|
}
|
|
9559
9584
|
}
|
|
@@ -10290,13 +10315,18 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
10290
10315
|
return frames;
|
|
10291
10316
|
});
|
|
10292
10317
|
} else if (!operandsOnly && token === "-shave") {
|
|
10293
|
-
const geometry =
|
|
10318
|
+
const geometry = tokens[++i] ?? "0x0", parsed = parseMagickGeometry(geometry);
|
|
10319
|
+
const width = Math.max(0, Math.round(parsed.width ?? 0)), height = Math.max(0, Math.round(parsed.height ?? width));
|
|
10294
10320
|
steps.push(async (image, backend) => {
|
|
10295
10321
|
if (!image) return;
|
|
10296
|
-
|
|
10322
|
+
if (width * 2 >= image.width || height * 2 >= image.height) {
|
|
10323
|
+
analysis.warnings += `magick: geometry does not contain image '${geometry}'
|
|
10324
|
+
`;
|
|
10325
|
+
return image;
|
|
10326
|
+
}
|
|
10297
10327
|
return {
|
|
10298
|
-
...await transformStoredImage2(image, backend.storage, { kind: "extract", left, top, width:
|
|
10299
|
-
virtualCanvas: magickBorderCanvas(image, -
|
|
10328
|
+
...await transformStoredImage2(image, backend.storage, { kind: "extract", left: width, top: height, width: image.width - width * 2, height: image.height - height * 2 }, signal),
|
|
10329
|
+
virtualCanvas: magickBorderCanvas(image, -width, -height)
|
|
10300
10330
|
};
|
|
10301
10331
|
});
|
|
10302
10332
|
} else if (!operandsOnly && token === "-trim") {
|
|
@@ -10310,6 +10340,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
10310
10340
|
steps.push(async (image, backend) => {
|
|
10311
10341
|
if (!image) return;
|
|
10312
10342
|
const layout = magickExtentLayout(image, geometry, captured);
|
|
10343
|
+
if (!layout) return image;
|
|
10313
10344
|
const virtualCanvas = magickResizeCanvas(image, layout.width, layout.height);
|
|
10314
10345
|
const source = image[magickInput];
|
|
10315
10346
|
const canvas = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poe-platform/safe-bash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.845",
|
|
4
4
|
"description": "Extensible virtual shell with pluggable filesystems and streaming commands",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1415,7 +1415,7 @@
|
|
|
1415
1415
|
"access": "public"
|
|
1416
1416
|
},
|
|
1417
1417
|
"dependencies": {
|
|
1418
|
-
"@poe-platform/safe-fs": "0.1.
|
|
1418
|
+
"@poe-platform/safe-fs": "0.1.845",
|
|
1419
1419
|
"pdf-lib": "1.17.1",
|
|
1420
1420
|
"@types/node": "^25.2.2",
|
|
1421
1421
|
"@formatjs/intl-numberformat": "9.4.0",
|