@bendyline/squisq-video-react 2.4.20 → 2.4.22
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/README.md +30 -0
- package/dist/chunk-2W6JQSIY.js +51 -0
- package/dist/{chunk-MI4EJXTD.js → chunk-47ODXCBA.js} +33 -44
- package/dist/{chunk-QJWEZRZS.js → chunk-A52VG7RA.js} +1 -1
- package/dist/{chunk-IFQJAW4V.js → chunk-BNNLPKT2.js} +1 -1
- package/dist/{chunk-4GFZ3GUE.js → chunk-C7NEHV5X.js} +20507 -17327
- package/dist/chunk-FMQI6WK2.js +157 -0
- package/dist/{chunk-GBIYYDMY.js → chunk-ISYJJJXN.js} +67 -1
- package/dist/{chunk-KJQVUVOG.js → chunk-JDIMPME5.js} +1 -1
- package/dist/{chunk-JKWCZION.js → chunk-OLFWEKY5.js} +1 -1
- package/dist/{chunk-A6ED7EVY.js → chunk-TUZLVOSP.js} +6 -3
- package/dist/components/index.js +6 -6
- package/dist/cover-image/index.js +3 -3
- package/dist/dashboard-image/index.js +2 -2
- package/dist/encoder/index.d.ts +126 -2
- package/dist/encoder/index.js +12 -1
- package/dist/hooks/index.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -7
- package/dist/lossyEncoder-EIXBAOLG.js +236 -0
- package/dist/media-edit/index.js +1 -1
- package/dist/{mediaEditEngine-ZLG7D3TB.js → mediaEditEngine-JLZ4Z5BB.js} +2 -2
- package/dist/workers/mediaEdit.worker.js +2 -2
- package/package.json +4 -4
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AudioSample,
|
|
3
|
+
AudioSampleSource,
|
|
4
|
+
BufferTarget,
|
|
5
|
+
Mp4OutputFormat,
|
|
6
|
+
Output,
|
|
7
|
+
WebMOutputFormat,
|
|
8
|
+
canEncodeAudio
|
|
9
|
+
} from "./chunk-C7NEHV5X.js";
|
|
10
|
+
import {
|
|
11
|
+
assertOpen,
|
|
12
|
+
blockFrames,
|
|
13
|
+
createSerialQueue
|
|
14
|
+
} from "./chunk-2W6JQSIY.js";
|
|
15
|
+
|
|
16
|
+
// src/audioFile/resampler.ts
|
|
17
|
+
var HALF_TAPS = 16;
|
|
18
|
+
var ROLLOFF = 0.94;
|
|
19
|
+
var MAX_PHASES = 4096;
|
|
20
|
+
function gcd(a, b) {
|
|
21
|
+
let x = a;
|
|
22
|
+
let y = b;
|
|
23
|
+
while (y !== 0) {
|
|
24
|
+
const t = x % y;
|
|
25
|
+
x = y;
|
|
26
|
+
y = t;
|
|
27
|
+
}
|
|
28
|
+
return x;
|
|
29
|
+
}
|
|
30
|
+
function assertRate(name, rate) {
|
|
31
|
+
if (!Number.isInteger(rate) || rate <= 0) {
|
|
32
|
+
throw new RangeError(`${name} must be a positive integer number of hertz (got ${rate}).`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function resampledLength(inputFrames, inputRate, outputRate) {
|
|
36
|
+
return Math.ceil(inputFrames * outputRate / inputRate);
|
|
37
|
+
}
|
|
38
|
+
function createResampler(inputRate, outputRate) {
|
|
39
|
+
assertRate("inputRate", inputRate);
|
|
40
|
+
assertRate("outputRate", outputRate);
|
|
41
|
+
const divisor = gcd(inputRate, outputRate);
|
|
42
|
+
const stepNum = inputRate / divisor;
|
|
43
|
+
const phaseDen = outputRate / divisor;
|
|
44
|
+
const phaseCount = Math.min(phaseDen, MAX_PHASES);
|
|
45
|
+
const ratio = Math.min(1, outputRate / inputRate);
|
|
46
|
+
const cutoff = ratio * ROLLOFF;
|
|
47
|
+
const halfWidth = Math.ceil(HALF_TAPS / ratio);
|
|
48
|
+
const taps = halfWidth * 2;
|
|
49
|
+
const filters = new Array(phaseCount);
|
|
50
|
+
const buildFilter = (frac) => {
|
|
51
|
+
const filter = new Float32Array(taps);
|
|
52
|
+
let sum = 0;
|
|
53
|
+
for (let j = 0; j < taps; j++) {
|
|
54
|
+
const d = frac + (halfWidth - 1 - j);
|
|
55
|
+
const x = d / halfWidth;
|
|
56
|
+
const window = 0.42 + 0.5 * Math.cos(Math.PI * x) + 0.08 * Math.cos(2 * Math.PI * x);
|
|
57
|
+
const arg = Math.PI * cutoff * d;
|
|
58
|
+
const sinc = arg === 0 ? 1 : Math.sin(arg) / arg;
|
|
59
|
+
const weight = cutoff * sinc * window;
|
|
60
|
+
filter[j] = weight;
|
|
61
|
+
sum += weight;
|
|
62
|
+
}
|
|
63
|
+
for (let j = 0; j < taps; j++) filter[j] /= sum;
|
|
64
|
+
return filter;
|
|
65
|
+
};
|
|
66
|
+
let buffer = new Float32Array(Math.max(1024, taps * 4));
|
|
67
|
+
let bufferStart = -(halfWidth - 1);
|
|
68
|
+
let bufferLength = halfWidth - 1;
|
|
69
|
+
let received = 0;
|
|
70
|
+
let nextOutput = 0;
|
|
71
|
+
let flushed = false;
|
|
72
|
+
const append = (input) => {
|
|
73
|
+
const needed = bufferLength + input.length;
|
|
74
|
+
if (needed > buffer.length) {
|
|
75
|
+
let capacity = buffer.length;
|
|
76
|
+
while (capacity < needed) capacity *= 2;
|
|
77
|
+
const grown = new Float32Array(capacity);
|
|
78
|
+
grown.set(buffer.subarray(0, bufferLength));
|
|
79
|
+
buffer = grown;
|
|
80
|
+
}
|
|
81
|
+
buffer.set(input, bufferLength);
|
|
82
|
+
bufferLength += input.length;
|
|
83
|
+
};
|
|
84
|
+
const quantized = phaseCount !== phaseDen;
|
|
85
|
+
const drain = (limit, maxOutputs) => {
|
|
86
|
+
const estimate = Math.max(
|
|
87
|
+
0,
|
|
88
|
+
Math.ceil((limit - halfWidth) * phaseDen / stepNum) - nextOutput + 2
|
|
89
|
+
);
|
|
90
|
+
const out = new Float32Array(Math.min(estimate, Math.max(0, maxOutputs - nextOutput)));
|
|
91
|
+
let count = 0;
|
|
92
|
+
while (nextOutput < maxOutputs && count < out.length) {
|
|
93
|
+
const position = nextOutput * stepNum;
|
|
94
|
+
let base = Math.floor(position / phaseDen);
|
|
95
|
+
const remainder = position - base * phaseDen;
|
|
96
|
+
let key = quantized ? Math.round(remainder * phaseCount / phaseDen) : remainder;
|
|
97
|
+
if (key >= phaseCount) {
|
|
98
|
+
base += 1;
|
|
99
|
+
key = 0;
|
|
100
|
+
}
|
|
101
|
+
if (base + halfWidth >= limit) break;
|
|
102
|
+
const filter = filters[key] ?? (filters[key] = buildFilter(key / phaseCount));
|
|
103
|
+
const offset = base - halfWidth + 1 - bufferStart;
|
|
104
|
+
let acc = 0;
|
|
105
|
+
for (let j = 0; j < taps; j++) acc += buffer[offset + j] * filter[j];
|
|
106
|
+
out[count++] = acc;
|
|
107
|
+
nextOutput += 1;
|
|
108
|
+
}
|
|
109
|
+
const keepFrom = Math.floor(nextOutput * stepNum / phaseDen) - halfWidth + 1;
|
|
110
|
+
const drop = Math.min(bufferLength, Math.max(0, keepFrom - bufferStart));
|
|
111
|
+
if (drop > 0) {
|
|
112
|
+
buffer.copyWithin(0, drop, bufferLength);
|
|
113
|
+
bufferLength -= drop;
|
|
114
|
+
bufferStart += drop;
|
|
115
|
+
}
|
|
116
|
+
return count === out.length ? out : out.slice(0, count);
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
inputRate,
|
|
120
|
+
outputRate,
|
|
121
|
+
process(input) {
|
|
122
|
+
if (flushed) throw new Error("The resampler was already flushed.");
|
|
123
|
+
if (input.length === 0) return new Float32Array(0);
|
|
124
|
+
append(input);
|
|
125
|
+
received += input.length;
|
|
126
|
+
return drain(received, Number.POSITIVE_INFINITY);
|
|
127
|
+
},
|
|
128
|
+
flush() {
|
|
129
|
+
if (flushed) return new Float32Array(0);
|
|
130
|
+
flushed = true;
|
|
131
|
+
append(new Float32Array(halfWidth + 1));
|
|
132
|
+
return drain(received + halfWidth + 1, resampledLength(received, inputRate, outputRate));
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/audioFile/lossyEncoder.ts
|
|
138
|
+
var FALLBACK_ENCODE_RATES = [48e3, 44100];
|
|
139
|
+
var SLICE_SECONDS = 0.5;
|
|
140
|
+
async function pickEncodeRate(codec, channels, inputRate, bitrate) {
|
|
141
|
+
const candidates = [inputRate, ...FALLBACK_ENCODE_RATES.filter((rate) => rate !== inputRate)];
|
|
142
|
+
for (const sampleRate of candidates) {
|
|
143
|
+
if (await canEncodeAudio(codec, { numberOfChannels: channels, sampleRate, bitrate })) {
|
|
144
|
+
return sampleRate;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
async function canEncodeLossyFormat(info) {
|
|
150
|
+
for (const channels of [2, 1]) {
|
|
151
|
+
if (await pickEncodeRate(info.codec, channels, 48e3, info.bitrate[channels]) !== null) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
async function createLossyEncoder(options, info) {
|
|
158
|
+
const channels = options.channels;
|
|
159
|
+
const bitrate = options.bitrate ?? info.bitrate[channels];
|
|
160
|
+
const encodeRate = await pickEncodeRate(info.codec, channels, options.sampleRate, bitrate);
|
|
161
|
+
if (encodeRate === null) {
|
|
162
|
+
throw new Error(`This browser cannot encode ${info.label} audio.`);
|
|
163
|
+
}
|
|
164
|
+
const target = new BufferTarget();
|
|
165
|
+
const output = new Output({
|
|
166
|
+
// Audio-only MP4 (no video track), metadata up front for progressive playback.
|
|
167
|
+
format: info.codec === "aac" ? new Mp4OutputFormat({ fastStart: "in-memory" }) : new WebMOutputFormat(),
|
|
168
|
+
target
|
|
169
|
+
});
|
|
170
|
+
const source = new AudioSampleSource({ codec: info.codec, bitrate });
|
|
171
|
+
output.addAudioTrack(source);
|
|
172
|
+
await output.start();
|
|
173
|
+
const resamplers = encodeRate === options.sampleRate ? null : Array.from({ length: channels }, () => createResampler(options.sampleRate, encodeRate));
|
|
174
|
+
const sliceFrames = Math.max(1024, Math.round(options.sampleRate * SLICE_SECONDS));
|
|
175
|
+
const serial = createSerialQueue();
|
|
176
|
+
let state = "open";
|
|
177
|
+
let written = 0;
|
|
178
|
+
const encodeBlock = async (planar) => {
|
|
179
|
+
const frames = planar[0]?.length ?? 0;
|
|
180
|
+
if (frames === 0) return;
|
|
181
|
+
const data = new Float32Array(frames * channels);
|
|
182
|
+
for (let c = 0; c < channels; c++) data.set(planar[c], c * frames);
|
|
183
|
+
const sample = new AudioSample({
|
|
184
|
+
data,
|
|
185
|
+
format: "f32-planar",
|
|
186
|
+
numberOfChannels: channels,
|
|
187
|
+
sampleRate: encodeRate,
|
|
188
|
+
timestamp: written / encodeRate
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
await source.add(sample);
|
|
192
|
+
} finally {
|
|
193
|
+
sample.close();
|
|
194
|
+
}
|
|
195
|
+
written += frames;
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
format: info.format,
|
|
199
|
+
mimeType: info.mimeType,
|
|
200
|
+
extension: info.extension,
|
|
201
|
+
append: (block) => serial(async () => {
|
|
202
|
+
assertOpen(state);
|
|
203
|
+
const frames = blockFrames(block, channels);
|
|
204
|
+
for (let start = 0; start < frames; start += sliceFrames) {
|
|
205
|
+
assertOpen(state);
|
|
206
|
+
const end = Math.min(frames, start + sliceFrames);
|
|
207
|
+
const slice = block.map((channel) => channel.subarray(start, end));
|
|
208
|
+
await encodeBlock(resamplers ? slice.map((s, c) => resamplers[c].process(s)) : slice);
|
|
209
|
+
}
|
|
210
|
+
}),
|
|
211
|
+
finish: () => serial(async () => {
|
|
212
|
+
assertOpen(state);
|
|
213
|
+
if (resamplers) await encodeBlock(resamplers.map((resampler) => resampler.flush()));
|
|
214
|
+
if (written === 0) {
|
|
215
|
+
state = "cancelled";
|
|
216
|
+
await output.cancel();
|
|
217
|
+
throw new Error("Cannot finish an empty audio file: no samples were appended.");
|
|
218
|
+
}
|
|
219
|
+
state = "finished";
|
|
220
|
+
source.close();
|
|
221
|
+
await output.finalize();
|
|
222
|
+
if (!target.buffer) throw new Error("The audio encoder produced no output.");
|
|
223
|
+
return new Blob([target.buffer], { type: info.mimeType });
|
|
224
|
+
}),
|
|
225
|
+
cancel: async () => {
|
|
226
|
+
if (state !== "open") return;
|
|
227
|
+
state = "cancelled";
|
|
228
|
+
await output.cancel();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
canEncodeLossyFormat,
|
|
234
|
+
createLossyEncoder,
|
|
235
|
+
pickEncodeRate
|
|
236
|
+
};
|
package/dist/media-edit/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
analyzeMediaEditPauses,
|
|
4
4
|
previewMediaEditAudio,
|
|
5
5
|
renderMediaEditAudio
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-BNNLPKT2.js";
|
|
7
|
+
import "./chunk-C7NEHV5X.js";
|
|
8
8
|
export {
|
|
9
9
|
analyzeMediaEditAudio,
|
|
10
10
|
analyzeMediaEditPauses,
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
analyzeMediaEditPauses,
|
|
4
4
|
previewMediaEditAudio,
|
|
5
5
|
renderMediaEditAudio
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-BNNLPKT2.js";
|
|
7
|
+
import "../chunk-C7NEHV5X.js";
|
|
8
8
|
import {
|
|
9
9
|
createRnnoiseDenoiserFactory
|
|
10
10
|
} from "../chunk-TL2IMDIZ.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-video-react",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.22",
|
|
4
4
|
"description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@bendyline/squisq": "2.11.
|
|
81
|
-
"@bendyline/squisq-video": "2.3.
|
|
82
|
-
"@bendyline/squisq-react": "2.11.
|
|
80
|
+
"@bendyline/squisq": "2.11.16",
|
|
81
|
+
"@bendyline/squisq-video": "2.3.20",
|
|
82
|
+
"@bendyline/squisq-react": "2.11.18",
|
|
83
83
|
"@ffmpeg/core": "0.12.9",
|
|
84
84
|
"@ffmpeg/ffmpeg": "0.12.15",
|
|
85
85
|
"@ffmpeg/util": "0.12.2",
|