@twick/media-utils 0.0.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -23
- package/dist/index-BWbdE2uK.cjs +15806 -0
- package/dist/index-BWbdE2uK.cjs.map +1 -0
- package/dist/index-CXhwwSX-.js +15804 -0
- package/dist/index-CXhwwSX-.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +282 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +281 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +39 -32
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,10 @@ const getAudioDuration = (audioSrc) => {
|
|
|
9
9
|
return new Promise((resolve, reject) => {
|
|
10
10
|
const audio = document.createElement("audio");
|
|
11
11
|
audio.preload = "metadata";
|
|
12
|
+
const isSafeUrl = /^(https?:|blob:|data:audio\/)/i.test(audioSrc);
|
|
13
|
+
if (!isSafeUrl) {
|
|
14
|
+
throw new Error("Unsafe audio source URL");
|
|
15
|
+
}
|
|
12
16
|
audio.src = audioSrc;
|
|
13
17
|
audio.onloadedmetadata = () => {
|
|
14
18
|
const duration = audio.duration;
|
|
@@ -49,7 +53,7 @@ function limit(fn) {
|
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
const loadImageDimensions = (url) => {
|
|
53
57
|
return new Promise((resolve, reject) => {
|
|
54
58
|
if (typeof document === "undefined") {
|
|
55
59
|
reject(new Error("getImageDimensions() is only available in the browser."));
|
|
@@ -62,8 +66,8 @@ function loadImageDimensions(url) {
|
|
|
62
66
|
img.onerror = reject;
|
|
63
67
|
img.src = url;
|
|
64
68
|
});
|
|
65
|
-
}
|
|
66
|
-
|
|
69
|
+
};
|
|
70
|
+
const getImageDimensions = (url) => {
|
|
67
71
|
if (imageDimensionsCache[url]) {
|
|
68
72
|
return Promise.resolve(imageDimensionsCache[url]);
|
|
69
73
|
}
|
|
@@ -71,7 +75,7 @@ function getImageDimensions(url) {
|
|
|
71
75
|
imageDimensionsCache[url] = dimensions;
|
|
72
76
|
return dimensions;
|
|
73
77
|
});
|
|
74
|
-
}
|
|
78
|
+
};
|
|
75
79
|
|
|
76
80
|
const getVideoMeta = (videoSrc) => {
|
|
77
81
|
if (videoMetaCache[videoSrc]) {
|
|
@@ -80,6 +84,11 @@ const getVideoMeta = (videoSrc) => {
|
|
|
80
84
|
return new Promise((resolve, reject) => {
|
|
81
85
|
const video = document.createElement("video");
|
|
82
86
|
video.preload = "metadata";
|
|
87
|
+
const isSafeUrl = /^(https?:|blob:|data:video\/)/i.test(videoSrc);
|
|
88
|
+
if (!isSafeUrl) {
|
|
89
|
+
reject(new Error("Unsafe video source URL"));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
83
92
|
video.src = videoSrc;
|
|
84
93
|
video.onloadedmetadata = () => {
|
|
85
94
|
const meta = {
|
|
@@ -94,7 +103,7 @@ const getVideoMeta = (videoSrc) => {
|
|
|
94
103
|
});
|
|
95
104
|
};
|
|
96
105
|
|
|
97
|
-
async
|
|
106
|
+
const getThumbnail = async (videoUrl, seekTime = 0.1, playbackRate = 1) => {
|
|
98
107
|
return new Promise((resolve, reject) => {
|
|
99
108
|
const video = document.createElement("video");
|
|
100
109
|
video.crossOrigin = "anonymous";
|
|
@@ -103,6 +112,14 @@ async function getThumbnail(videoUrl, seekTime = 0.1, playbackRate = 1) {
|
|
|
103
112
|
video.autoplay = false;
|
|
104
113
|
video.preload = "auto";
|
|
105
114
|
video.playbackRate = playbackRate;
|
|
115
|
+
video.style.position = "absolute";
|
|
116
|
+
video.style.left = "-9999px";
|
|
117
|
+
video.style.top = "-9999px";
|
|
118
|
+
video.style.width = "1px";
|
|
119
|
+
video.style.height = "1px";
|
|
120
|
+
video.style.opacity = "0";
|
|
121
|
+
video.style.pointerEvents = "none";
|
|
122
|
+
video.style.zIndex = "-1";
|
|
106
123
|
let timeoutId;
|
|
107
124
|
const cleanup = () => {
|
|
108
125
|
if (video.parentNode) video.remove();
|
|
@@ -169,9 +186,254 @@ async function getThumbnail(videoUrl, seekTime = 0.1, playbackRate = 1) {
|
|
|
169
186
|
video.src = videoUrl;
|
|
170
187
|
document.body.appendChild(video);
|
|
171
188
|
});
|
|
172
|
-
}
|
|
189
|
+
};
|
|
173
190
|
|
|
174
|
-
|
|
191
|
+
const extractAudio = async ({
|
|
192
|
+
src,
|
|
193
|
+
playbackRate = 1,
|
|
194
|
+
start = 0,
|
|
195
|
+
end
|
|
196
|
+
}) => {
|
|
197
|
+
if (!src) throw new Error("src is required");
|
|
198
|
+
if (playbackRate <= 0) throw new Error("playbackRate must be > 0");
|
|
199
|
+
const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
|
|
200
|
+
if (!isSafeUrl) throw new Error("Unsafe media source URL");
|
|
201
|
+
const audioBuffer = await fetchAndDecodeAudio(src);
|
|
202
|
+
const clampedStart = Math.max(0, start || 0);
|
|
203
|
+
const fullDuration = audioBuffer.duration;
|
|
204
|
+
const clampedEnd = Math.min(
|
|
205
|
+
typeof end === "number" ? end : fullDuration,
|
|
206
|
+
fullDuration
|
|
207
|
+
);
|
|
208
|
+
if (clampedEnd <= clampedStart)
|
|
209
|
+
throw new Error("Invalid range: end must be greater than start");
|
|
210
|
+
const renderedBuffer = await renderAudioSegment(
|
|
211
|
+
audioBuffer,
|
|
212
|
+
clampedStart,
|
|
213
|
+
clampedEnd,
|
|
214
|
+
playbackRate
|
|
215
|
+
);
|
|
216
|
+
const mp3Blob = await audioBufferToMp3(renderedBuffer);
|
|
217
|
+
return URL.createObjectURL(mp3Blob);
|
|
218
|
+
};
|
|
219
|
+
const stitchAudio = async (segments, totalDuration) => {
|
|
220
|
+
if (!segments || segments.length === 0) {
|
|
221
|
+
throw new Error("At least one audio segment is required");
|
|
222
|
+
}
|
|
223
|
+
const duration = totalDuration || Math.max(...segments.map((s) => s.e));
|
|
224
|
+
const renderedBuffer = await createAudioTimeline(segments, duration);
|
|
225
|
+
const mp3Blob = await audioBufferToMp3(renderedBuffer);
|
|
226
|
+
return URL.createObjectURL(mp3Blob);
|
|
227
|
+
};
|
|
228
|
+
const fetchAndDecodeAudio = async (src) => {
|
|
229
|
+
const response = await fetch(src);
|
|
230
|
+
if (!response.ok) throw new Error(`Failed to fetch source: ${response.status}`);
|
|
231
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
232
|
+
return decodeAudioData(arrayBuffer);
|
|
233
|
+
};
|
|
234
|
+
const decodeAudioData = async (arrayBuffer) => {
|
|
235
|
+
const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
|
|
236
|
+
if (!AudioContextCtor) throw new Error("Web Audio API not supported");
|
|
237
|
+
const audioContext = new AudioContextCtor();
|
|
238
|
+
try {
|
|
239
|
+
return await new Promise((resolve, reject) => {
|
|
240
|
+
audioContext.decodeAudioData(
|
|
241
|
+
arrayBuffer.slice(0),
|
|
242
|
+
(buf) => resolve(buf),
|
|
243
|
+
(err) => reject(err || new Error("Failed to decode audio"))
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
audioContext.close();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const renderAudioSegment = async (audioBuffer, start, end, playbackRate) => {
|
|
251
|
+
const OfflineAudioContextCtor = window.OfflineAudioContext || window.webkitOfflineAudioContext;
|
|
252
|
+
if (!OfflineAudioContextCtor) throw new Error("OfflineAudioContext not supported");
|
|
253
|
+
const sampleRate = audioBuffer.sampleRate;
|
|
254
|
+
const numChannels = audioBuffer.numberOfChannels;
|
|
255
|
+
const sourceDuration = end - start;
|
|
256
|
+
const renderedFrames = Math.max(
|
|
257
|
+
1,
|
|
258
|
+
Math.ceil(sourceDuration / playbackRate * sampleRate)
|
|
259
|
+
);
|
|
260
|
+
const offline = new OfflineAudioContextCtor(numChannels, renderedFrames, sampleRate);
|
|
261
|
+
const sourceNode = offline.createBufferSource();
|
|
262
|
+
sourceNode.buffer = audioBuffer;
|
|
263
|
+
sourceNode.playbackRate.value = playbackRate;
|
|
264
|
+
sourceNode.connect(offline.destination);
|
|
265
|
+
sourceNode.start(0, start, sourceDuration);
|
|
266
|
+
return await offline.startRendering();
|
|
267
|
+
};
|
|
268
|
+
const createAudioTimeline = async (segments, duration) => {
|
|
269
|
+
const OfflineAudioContextCtor = window.OfflineAudioContext || window.webkitOfflineAudioContext;
|
|
270
|
+
if (!OfflineAudioContextCtor) throw new Error("OfflineAudioContext not supported");
|
|
271
|
+
const sampleRate = 44100;
|
|
272
|
+
const totalFrames = Math.ceil(duration * sampleRate);
|
|
273
|
+
const offline = new OfflineAudioContextCtor(2, totalFrames, sampleRate);
|
|
274
|
+
for (const segment of segments) {
|
|
275
|
+
if (segment.s >= segment.e) {
|
|
276
|
+
console.warn(`Invalid segment: start (${segment.s}) >= end (${segment.e})`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const volume = segment.volume ?? 1;
|
|
280
|
+
if (volume <= 0) {
|
|
281
|
+
console.warn(`Skipping muted segment: ${segment.src}`);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const audioBuffer = await fetchAndDecodeAudio(segment.src);
|
|
286
|
+
const segmentDuration = segment.e - segment.s;
|
|
287
|
+
const sourceDuration = Math.min(segmentDuration, audioBuffer.duration);
|
|
288
|
+
const source = offline.createBufferSource();
|
|
289
|
+
source.buffer = audioBuffer;
|
|
290
|
+
if (volume !== 1) {
|
|
291
|
+
const gainNode = offline.createGain();
|
|
292
|
+
gainNode.gain.value = volume;
|
|
293
|
+
source.connect(gainNode);
|
|
294
|
+
gainNode.connect(offline.destination);
|
|
295
|
+
} else {
|
|
296
|
+
source.connect(offline.destination);
|
|
297
|
+
}
|
|
298
|
+
source.start(segment.s, 0, sourceDuration);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.warn(`Failed to process segment: ${segment.src}`, error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return await offline.startRendering();
|
|
304
|
+
};
|
|
305
|
+
const audioBufferToMp3 = async (buffer) => {
|
|
306
|
+
try {
|
|
307
|
+
const wavArrayBuffer = audioBufferToWavArrayBuffer(buffer);
|
|
308
|
+
const pcmBuffer = await decodeAudioData(wavArrayBuffer);
|
|
309
|
+
return await encodePcmToMp3(pcmBuffer);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return audioBufferToWavBlob(buffer);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const audioBufferToWavArrayBuffer = (buffer) => {
|
|
315
|
+
const numChannels = buffer.numberOfChannels;
|
|
316
|
+
const sampleRate = buffer.sampleRate;
|
|
317
|
+
const numFrames = buffer.length;
|
|
318
|
+
const interleaved = interleave(buffer, numChannels, numFrames);
|
|
319
|
+
const bytesPerSample = 2;
|
|
320
|
+
const blockAlign = numChannels * bytesPerSample;
|
|
321
|
+
const byteRate = sampleRate * blockAlign;
|
|
322
|
+
const dataSize = interleaved.length * bytesPerSample;
|
|
323
|
+
const bufferSize = 44 + dataSize;
|
|
324
|
+
const arrayBuffer = new ArrayBuffer(bufferSize);
|
|
325
|
+
const view = new DataView(arrayBuffer);
|
|
326
|
+
writeString(view, 0, "RIFF");
|
|
327
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
328
|
+
writeString(view, 8, "WAVE");
|
|
329
|
+
writeString(view, 12, "fmt ");
|
|
330
|
+
view.setUint32(16, 16, true);
|
|
331
|
+
view.setUint16(20, 1, true);
|
|
332
|
+
view.setUint16(22, numChannels, true);
|
|
333
|
+
view.setUint32(24, sampleRate, true);
|
|
334
|
+
view.setUint32(28, byteRate, true);
|
|
335
|
+
view.setUint16(32, blockAlign, true);
|
|
336
|
+
view.setUint16(34, 16, true);
|
|
337
|
+
writeString(view, 36, "data");
|
|
338
|
+
view.setUint32(40, dataSize, true);
|
|
339
|
+
floatTo16BitPCM(view, 44, interleaved);
|
|
340
|
+
return arrayBuffer;
|
|
341
|
+
};
|
|
342
|
+
const encodePcmToMp3 = async (buffer) => {
|
|
343
|
+
const lamejs = await import('./index-CXhwwSX-.js').then(n => n.i);
|
|
344
|
+
const channels = buffer.numberOfChannels >= 2 ? 2 : 1;
|
|
345
|
+
const targetSampleRate = 22050;
|
|
346
|
+
const downsampledBuffer = downsampleAudioBuffer(buffer, targetSampleRate);
|
|
347
|
+
const kbps = 48;
|
|
348
|
+
const mp3encoder = new lamejs.default.Mp3Encoder(channels, targetSampleRate, kbps);
|
|
349
|
+
const samplesPerFrame = 1152;
|
|
350
|
+
const leftFloat = downsampledBuffer.getChannelData(0);
|
|
351
|
+
const left = floatTo16(leftFloat);
|
|
352
|
+
let right;
|
|
353
|
+
if (channels === 2) {
|
|
354
|
+
const rightFloat = downsampledBuffer.getChannelData(1);
|
|
355
|
+
right = floatTo16(rightFloat);
|
|
356
|
+
}
|
|
357
|
+
const mp3Chunks = [];
|
|
358
|
+
for (let i = 0; i < left.length; i += samplesPerFrame) {
|
|
359
|
+
const leftChunk = left.subarray(i, Math.min(i + samplesPerFrame, left.length));
|
|
360
|
+
let mp3buf;
|
|
361
|
+
if (channels === 2 && right) {
|
|
362
|
+
const rightChunk = right.subarray(i, Math.min(i + samplesPerFrame, right.length));
|
|
363
|
+
mp3buf = mp3encoder.encodeBuffer(leftChunk, rightChunk);
|
|
364
|
+
} else {
|
|
365
|
+
mp3buf = mp3encoder.encodeBuffer(leftChunk);
|
|
366
|
+
}
|
|
367
|
+
if (mp3buf.length > 0) mp3Chunks.push(mp3buf);
|
|
368
|
+
}
|
|
369
|
+
const end = mp3encoder.flush();
|
|
370
|
+
if (end.length > 0) mp3Chunks.push(end);
|
|
371
|
+
return new Blob(mp3Chunks, { type: "audio/mpeg" });
|
|
372
|
+
};
|
|
373
|
+
const audioBufferToWavBlob = (buffer) => {
|
|
374
|
+
const arrayBuffer = audioBufferToWavArrayBuffer(buffer);
|
|
375
|
+
return new Blob([arrayBuffer], { type: "audio/wav" });
|
|
376
|
+
};
|
|
377
|
+
const downsampleAudioBuffer = (buffer, targetSampleRate) => {
|
|
378
|
+
if (buffer.sampleRate === targetSampleRate) {
|
|
379
|
+
return buffer;
|
|
380
|
+
}
|
|
381
|
+
const ratio = buffer.sampleRate / targetSampleRate;
|
|
382
|
+
const newLength = Math.round(buffer.length / ratio);
|
|
383
|
+
const newBuffer = new AudioContext().createBuffer(
|
|
384
|
+
buffer.numberOfChannels,
|
|
385
|
+
newLength,
|
|
386
|
+
targetSampleRate
|
|
387
|
+
);
|
|
388
|
+
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
|
389
|
+
const oldData = buffer.getChannelData(channel);
|
|
390
|
+
const newData = newBuffer.getChannelData(channel);
|
|
391
|
+
for (let i = 0; i < newLength; i++) {
|
|
392
|
+
const oldIndex = Math.floor(i * ratio);
|
|
393
|
+
newData[i] = oldData[oldIndex];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return newBuffer;
|
|
397
|
+
};
|
|
398
|
+
const interleave = (buffer, numChannels, numFrames) => {
|
|
399
|
+
if (numChannels === 1) {
|
|
400
|
+
return buffer.getChannelData(0).slice(0, numFrames);
|
|
401
|
+
}
|
|
402
|
+
const result = new Float32Array(numFrames * numChannels);
|
|
403
|
+
const channelData = [];
|
|
404
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
405
|
+
channelData[ch] = buffer.getChannelData(ch);
|
|
406
|
+
}
|
|
407
|
+
let writeIndex = 0;
|
|
408
|
+
for (let i = 0; i < numFrames; i++) {
|
|
409
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
410
|
+
result[writeIndex++] = channelData[ch][i];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
};
|
|
415
|
+
const floatTo16BitPCM = (view, offset, input) => {
|
|
416
|
+
let pos = offset;
|
|
417
|
+
for (let i = 0; i < input.length; i++, pos += 2) {
|
|
418
|
+
let s = Math.max(-1, Math.min(1, input[i]));
|
|
419
|
+
view.setInt16(pos, s < 0 ? s * 32768 : s * 32767, true);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const floatTo16 = (input) => {
|
|
423
|
+
const output = new Int16Array(input.length);
|
|
424
|
+
for (let i = 0; i < input.length; i++) {
|
|
425
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
426
|
+
output[i] = s < 0 ? s * 32768 : s * 32767;
|
|
427
|
+
}
|
|
428
|
+
return output;
|
|
429
|
+
};
|
|
430
|
+
const writeString = (view, offset, str) => {
|
|
431
|
+
for (let i = 0; i < str.length; i++) {
|
|
432
|
+
view.setUint8(offset + i, str.charCodeAt(i));
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const getScaledDimensions = (width, height, maxWidth, maxHeight) => {
|
|
175
437
|
if (width <= maxWidth && height <= maxHeight) {
|
|
176
438
|
return {
|
|
177
439
|
width: width % 2 === 0 ? width : width - 1,
|
|
@@ -193,8 +455,8 @@ function getScaledDimensions(width, height, maxWidth, maxHeight) {
|
|
|
193
455
|
width: Math.min(scaledWidth, maxWidth),
|
|
194
456
|
height: Math.min(scaledHeight, maxHeight)
|
|
195
457
|
};
|
|
196
|
-
}
|
|
197
|
-
|
|
458
|
+
};
|
|
459
|
+
const getObjectFitSize = (objectFit, elementSize, containerSize) => {
|
|
198
460
|
const elementAspectRatio = elementSize.width / elementSize.height;
|
|
199
461
|
const containerAspectRatio = containerSize.width / containerSize.height;
|
|
200
462
|
switch (objectFit) {
|
|
@@ -233,14 +495,14 @@ function getObjectFitSize(objectFit, elementSize, containerSize) {
|
|
|
233
495
|
height: elementSize.height
|
|
234
496
|
};
|
|
235
497
|
}
|
|
236
|
-
}
|
|
498
|
+
};
|
|
237
499
|
|
|
238
|
-
async
|
|
500
|
+
const blobUrlToFile = async (blobUrl, fileName) => {
|
|
239
501
|
const response = await fetch(blobUrl);
|
|
240
502
|
const blob = await response.blob();
|
|
241
503
|
return new File([blob], fileName, { type: blob.type });
|
|
242
|
-
}
|
|
243
|
-
|
|
504
|
+
};
|
|
505
|
+
const saveAsFile = (content, type, name) => {
|
|
244
506
|
const blob = typeof content === "string" ? new Blob([content], { type }) : content;
|
|
245
507
|
const url = URL.createObjectURL(blob);
|
|
246
508
|
const a = document.createElement("a");
|
|
@@ -248,8 +510,8 @@ function saveAsFile(content, type, name) {
|
|
|
248
510
|
a.download = name;
|
|
249
511
|
a.click();
|
|
250
512
|
URL.revokeObjectURL(url);
|
|
251
|
-
}
|
|
252
|
-
async
|
|
513
|
+
};
|
|
514
|
+
const downloadFile = async (url, filename) => {
|
|
253
515
|
try {
|
|
254
516
|
const response = await fetch(url);
|
|
255
517
|
const blob = await response.blob();
|
|
@@ -265,9 +527,9 @@ async function downloadFile(url, filename) {
|
|
|
265
527
|
console.error("Error downloading file:", error);
|
|
266
528
|
throw error;
|
|
267
529
|
}
|
|
268
|
-
}
|
|
530
|
+
};
|
|
269
531
|
|
|
270
|
-
async
|
|
532
|
+
const detectMediaTypeFromUrl = async (url) => {
|
|
271
533
|
try {
|
|
272
534
|
const response = await fetch(url, { method: "HEAD" });
|
|
273
535
|
const contentType = response.headers.get("Content-Type");
|
|
@@ -280,7 +542,7 @@ async function detectMediaTypeFromUrl(url) {
|
|
|
280
542
|
console.error("Fetch failed:", error);
|
|
281
543
|
return null;
|
|
282
544
|
}
|
|
283
|
-
}
|
|
545
|
+
};
|
|
284
546
|
|
|
285
|
-
export { blobUrlToFile, detectMediaTypeFromUrl, downloadFile, getAudioDuration, getImageDimensions, getObjectFitSize, getScaledDimensions, getThumbnail, getVideoMeta, limit, saveAsFile };
|
|
547
|
+
export { blobUrlToFile, detectMediaTypeFromUrl, downloadFile, extractAudio, getAudioDuration, getImageDimensions, getObjectFitSize, getScaledDimensions, getThumbnail, getVideoMeta, limit, saveAsFile, stitchAudio };
|
|
286
548
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/cache.ts","../src/get-audio-duration.ts","../src/limit.ts","../src/get-image-dimensions.ts","../src/get-video-metadata.ts","../src/get-thumbnail.ts","../src/dimension-handler.ts","../src/file-helper.ts","../src/url-helper.ts"],"sourcesContent":["import { Dimensions, VideoMeta } from \"./types\";\r\n\r\nexport const imageDimensionsCache: Record<string, Dimensions> = {};\r\nexport const videoMetaCache: Record<string, VideoMeta> = {};\r\nexport const audioDurationCache: Record<string, number> = {};","import { audioDurationCache } from \"./cache\";\r\n\r\n/**\r\n * Retrieves the duration (in seconds) of an audio file from a given source URL.\r\n * Uses a cache to avoid reloading the same audio multiple times.\r\n *\r\n * @param audioSrc - The source URL of the audio file.\r\n * @returns A Promise that resolves to the duration of the audio in seconds.\r\n */\r\nexport const getAudioDuration = (audioSrc: string): Promise<number> => {\r\n // Return cached duration if available\r\n if (audioDurationCache[audioSrc]) {\r\n return Promise.resolve(audioDurationCache[audioSrc]);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const audio = document.createElement(\"audio\");\r\n audio.preload = \"metadata\"; // Only load metadata (e.g., duration)\r\n audio.src = audioSrc;\r\n\r\n // When metadata is loaded, store duration in cache and resolve\r\n audio.onloadedmetadata = () => {\r\n const duration = audio.duration;\r\n audioDurationCache[audioSrc] = duration;\r\n resolve(duration);\r\n };\r\n\r\n // Handle loading errors\r\n audio.onerror = () => {\r\n reject(new Error(\"Failed to load audio metadata\"));\r\n };\r\n });\r\n};\r\n","// Maximum number of concurrent promises allowed to run\r\nconst concurrencyLimit = 5;\r\n\r\n// Number of currently active (running) promises\r\nlet activeCount = 0;\r\n\r\n// Queue to hold pending tasks waiting to be run when concurrency slots free up\r\nconst queue: Array<() => void> = [];\r\n\r\n/**\r\n * Runs the next task from the queue if concurrency limit is not reached.\r\n */\r\nfunction runNext() {\r\n // If no tasks are queued or we're already at the concurrency limit, do nothing\r\n if (queue.length === 0 || activeCount >= concurrencyLimit) return;\r\n\r\n // Dequeue next task\r\n const next = queue.shift();\r\n\r\n if (next) {\r\n activeCount++; // Mark one more active task\r\n next(); // Run it\r\n }\r\n}\r\n\r\n/**\r\n * Wraps an async function to enforce concurrency limits.\r\n * If concurrency limit is reached, the function is queued and executed later.\r\n * \r\n * @param fn - Async function returning a Promise\r\n * @returns Promise that resolves/rejects with fn's result\r\n */\r\nexport function limit<T>(fn: () => Promise<T>): Promise<T> {\r\n return new Promise((resolve, reject) => {\r\n // Task to run the function and handle completion\r\n const task = () => {\r\n fn()\r\n .then(resolve)\r\n .catch(reject)\r\n .finally(() => {\r\n activeCount--; // Mark task as done\r\n runNext(); // Trigger next queued task, if any\r\n });\r\n };\r\n\r\n if (activeCount < concurrencyLimit) {\r\n activeCount++; // Increment active count for immediate run\r\n task();\r\n } else {\r\n // Queue the task if concurrency limit reached\r\n queue.push(task);\r\n }\r\n });\r\n}\r\n","import { limit } from \"./limit\";\r\nimport { Dimensions } from \"./types\";\r\nimport { imageDimensionsCache } from \"./cache\";\r\n\r\n/**\r\n * Loads an image from the given URL and resolves with its natural dimensions.\r\n *\r\n * @param url - The image URL to load.\r\n * @returns A Promise that resolves with the image's width and height.\r\n */\r\nfunction loadImageDimensions(url: string): Promise<Dimensions> {\r\n return new Promise((resolve, reject) => {\r\n if (typeof document === 'undefined') {\r\n reject(new Error('getImageDimensions() is only available in the browser.'));\r\n return;\r\n }\r\n\r\n const img = new Image();\r\n img.onload = () => {\r\n resolve({ width: img.naturalWidth, height: img.naturalHeight });\r\n };\r\n img.onerror = reject;\r\n img.src = url;\r\n });\r\n}\r\n\r\n/**\r\n * Gets the dimensions (width and height) of an image from the given URL.\r\n * Uses a cache to avoid reloading the image if already fetched.\r\n * Also uses a concurrency limiter to control resource usage.\r\n *\r\n * @param url - The URL of the image.\r\n * @returns A Promise that resolves to an object containing `width` and `height`.\r\n */\r\nexport function getImageDimensions(url: string): Promise<Dimensions> {\r\n // Return cached dimensions if available\r\n if (imageDimensionsCache[url]) {\r\n return Promise.resolve(imageDimensionsCache[url]);\r\n }\r\n\r\n // Fetch and cache the dimensions using a concurrency limit\r\n return limit(() => loadImageDimensions(url)).then((dimensions) => {\r\n imageDimensionsCache[url] = dimensions;\r\n return dimensions;\r\n });\r\n}\r\n","import { videoMetaCache } from \"./cache\";\r\nimport { VideoMeta } from \"./types\";\r\n\r\n/**\r\n * Fetches metadata (width, height, duration) for a given video source.\r\n * If metadata has already been fetched and cached, it returns the cached data.\r\n *\r\n * @param videoSrc - The URL or path to the video file.\r\n * @returns A Promise that resolves to an object containing video metadata.\r\n */\r\nexport const getVideoMeta = (videoSrc: string): Promise<VideoMeta> => {\r\n // Return cached metadata if available\r\n if (videoMetaCache[videoSrc]) {\r\n return Promise.resolve(videoMetaCache[videoSrc]);\r\n }\r\n\r\n return new Promise<VideoMeta>((resolve, reject) => {\r\n const video: HTMLVideoElement = document.createElement(\"video\");\r\n video.preload = \"metadata\"; // Only preload metadata to reduce bandwidth\r\n video.src = videoSrc;\r\n\r\n // When metadata is loaded, extract and cache it\r\n video.onloadedmetadata = () => {\r\n const meta: VideoMeta = {\r\n width: video.videoWidth,\r\n height: video.videoHeight,\r\n duration: video.duration,\r\n };\r\n videoMetaCache[videoSrc] = meta;\r\n resolve(meta);\r\n };\r\n\r\n // Handle video loading errors\r\n video.onerror = () => reject(new Error(\"Failed to load video metadata\"));\r\n });\r\n};\r\n","/**\r\n * Extracts a thumbnail from a video at a specific seek time and playback rate.\r\n *\r\n * This function creates a hidden `<video>` element in the browser,\r\n * seeks to the specified time, and captures the frame into a canvas,\r\n * which is then exported as a JPEG data URL or Blob URL.\r\n *\r\n * @param videoUrl - The URL of the video to extract the thumbnail from.\r\n * @param seekTime - The time in seconds at which to capture the frame. Default is 0.1s.\r\n * @param playbackRate - Playback speed for the video. Default is 1.\r\n * @returns A Promise that resolves to a thumbnail image URL (either a base64 data URL or blob URL).\r\n */\r\nexport async function getThumbnail(\r\n videoUrl: string,\r\n seekTime = 0.1,\r\n playbackRate = 1\r\n ): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const video = document.createElement(\"video\");\r\n video.crossOrigin = \"anonymous\";\r\n video.muted = true;\r\n video.playsInline = true;\r\n video.autoplay = false;\r\n video.preload = \"auto\";\r\n video.playbackRate = playbackRate;\r\n \r\n let timeoutId: number | undefined;\r\n \r\n // Cleanup video element and timeout\r\n const cleanup = () => {\r\n if (video.parentNode) video.remove();\r\n if (timeoutId) clearTimeout(timeoutId);\r\n };\r\n \r\n // Handle errors during video loading\r\n const handleError = () => {\r\n cleanup();\r\n reject(new Error(`Failed to load video: ${video.error?.message || \"Unknown error\"}`));\r\n };\r\n \r\n // Once seeked to target frame, capture the image\r\n const handleSeeked = () => {\r\n try {\r\n video.pause();\r\n \r\n const canvas = document.createElement(\"canvas\");\r\n const width = video.videoWidth || 640;\r\n const height = video.videoHeight || 360;\r\n canvas.width = width;\r\n canvas.height = height;\r\n \r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) {\r\n cleanup();\r\n reject(new Error(\"Failed to get canvas context\"));\r\n return;\r\n }\r\n \r\n // Draw current video frame onto canvas\r\n ctx.drawImage(video, 0, 0, width, height);\r\n \r\n // Attempt to export canvas to base64 image URL\r\n try {\r\n const dataUrl = canvas.toDataURL(\"image/jpeg\", 0.8);\r\n cleanup();\r\n resolve(dataUrl);\r\n } catch {\r\n // Fallback: convert canvas to Blob\r\n canvas.toBlob((blob) => {\r\n if (!blob) {\r\n cleanup();\r\n reject(new Error(\"Failed to create Blob\"));\r\n return;\r\n }\r\n const blobUrl = URL.createObjectURL(blob);\r\n cleanup();\r\n resolve(blobUrl);\r\n }, \"image/jpeg\", 0.8);\r\n }\r\n } catch (err) {\r\n cleanup();\r\n reject(new Error(`Error creating thumbnail: ${err}`));\r\n }\r\n };\r\n \r\n video.addEventListener(\"error\", handleError, { once: true });\r\n video.addEventListener(\"seeked\", handleSeeked, { once: true });\r\n \r\n // After metadata is loaded, seek to the desired frame\r\n video.addEventListener(\"loadedmetadata\", () => {\r\n const playPromise = video.play();\r\n if (playPromise !== undefined) {\r\n playPromise\r\n .then(() => {\r\n video.currentTime = seekTime;\r\n })\r\n .catch(() => {\r\n video.currentTime = seekTime;\r\n });\r\n } else {\r\n video.currentTime = seekTime;\r\n }\r\n }, { once: true });\r\n \r\n // Timeout protection in case video loading hangs\r\n timeoutId = window.setTimeout(() => {\r\n cleanup();\r\n reject(new Error(\"Video loading timed out\"));\r\n }, 5000);\r\n \r\n // Assign video source and add it to the DOM (helps Safari/iOS behavior)\r\n video.src = videoUrl;\r\n document.body.appendChild(video);\r\n });\r\n }\r\n ","import { Dimensions } from \"./types\";\r\n\r\n/**\r\n * Calculates the scaled dimensions of an element to fit inside a container\r\n * based on the specified max dimensions.\r\n *\r\n * @param width - The original width of the element.\r\n * @param height - The original height of the element.\r\n * @param maxWidth - The maximum width of the container.\r\n * @param maxHeight - The maximum height of the container.\r\n * @returns An object containing the calculated width and height for the element.\r\n */\r\nexport function getScaledDimensions(\r\n width: number, \r\n height: number,\r\n maxWidth: number,\r\n maxHeight: number\r\n ): Dimensions {\r\n // If the original dimensions are smaller than or equal to the max values, return the original dimensions\r\n if (width <= maxWidth && height <= maxHeight) {\r\n // Ensure the width and height are even numbers\r\n return {\r\n width: width % 2 === 0 ? width : width - 1,\r\n height: height % 2 === 0 ? height : height - 1,\r\n };\r\n }\r\n \r\n // Calculate scaling factor based on the maximum width and height\r\n const widthRatio = maxWidth / width;\r\n const heightRatio = maxHeight / height;\r\n \r\n // Use the smaller of the two ratios to maintain the aspect ratio\r\n const scale = Math.min(widthRatio, heightRatio);\r\n \r\n // Calculate the scaled dimensions\r\n let scaledWidth = Math.round(width * scale);\r\n let scaledHeight = Math.round(height * scale);\r\n \r\n // Ensure the width and height are even numbers\r\n if (scaledWidth % 2 !== 0) {\r\n scaledWidth -= 1; // Make width even if it's odd\r\n }\r\n if (scaledHeight % 2 !== 0) {\r\n scaledHeight -= 1; // Make height even if it's odd\r\n }\r\n \r\n // Ensure the scaled width and height fit within the max dimensions\r\n return {\r\n width: Math.min(scaledWidth, maxWidth),\r\n height: Math.min(scaledHeight, maxHeight),\r\n };\r\n }\r\n\r\n/**\r\n * Calculates the resized dimensions of an element to fit inside a container\r\n * based on the specified object-fit strategy (\"contain\", \"cover\", \"fill\", or default).\r\n *\r\n * @param objectFit - The object-fit behavior ('contain', 'cover', 'fill', or default/fallback).\r\n * @param elementSize - The original size of the element (width and height).\r\n * @param containerSize - The size of the container (width and height).\r\n * @returns An object containing the calculated width and height for the element.\r\n */\r\nexport function getObjectFitSize(\r\n objectFit: string,\r\n elementSize: Dimensions,\r\n containerSize: Dimensions\r\n): Dimensions {\r\n const elementAspectRatio = elementSize.width / elementSize.height;\r\n const containerAspectRatio = containerSize.width / containerSize.height;\r\n\r\n switch (objectFit) {\r\n case \"contain\":\r\n // Fit entire element inside container without cropping, maintaining aspect ratio\r\n if (elementAspectRatio > containerAspectRatio) {\r\n return {\r\n width: containerSize.width,\r\n height: containerSize.width / elementAspectRatio,\r\n };\r\n } else {\r\n return {\r\n width: containerSize.height * elementAspectRatio,\r\n height: containerSize.height,\r\n };\r\n }\r\n\r\n case \"cover\":\r\n // Fill container while maintaining aspect ratio, possibly cropping the element\r\n if (elementAspectRatio > containerAspectRatio) {\r\n return {\r\n width: containerSize.height * elementAspectRatio,\r\n height: containerSize.height,\r\n };\r\n } else {\r\n return {\r\n width: containerSize.width,\r\n height: containerSize.width / elementAspectRatio,\r\n };\r\n }\r\n\r\n case \"fill\":\r\n // Stretch element to completely fill the container, ignoring aspect ratio\r\n return {\r\n width: containerSize.width,\r\n height: containerSize.height,\r\n };\r\n\r\n default:\r\n // Default behavior: return original size of the element\r\n return {\r\n width: elementSize.width,\r\n height: elementSize.height,\r\n };\r\n }\r\n};\r\n\r\n ","/**\r\n * Converts a Blob URL to a File object.\r\n *\r\n * @param blobUrl - The Blob URL to convert.\r\n * @param fileName - The name to assign to the resulting File.\r\n * @returns A Promise that resolves to a File object.\r\n */\r\nexport async function blobUrlToFile(blobUrl: string, fileName: string): Promise<File> {\r\n const response = await fetch(blobUrl);\r\n const blob = await response.blob();\r\n return new File([blob], fileName, { type: blob.type });\r\n }\r\n \r\n /**\r\n * Triggers a download of a file from a string or Blob.\r\n *\r\n * @param content - The content to save, either a string or a Blob.\r\n * @param type - The MIME type of the content.\r\n * @param name - The name of the file to be saved.\r\n */\r\n export function saveAsFile(content: string | Blob, type: string, name: string): void {\r\n const blob = typeof content === \"string\" ? new Blob([content], { type }) : content;\r\n const url = URL.createObjectURL(blob);\r\n \r\n const a = document.createElement(\"a\");\r\n a.href = url;\r\n a.download = name;\r\n a.click();\r\n \r\n // Clean up the URL object after download\r\n URL.revokeObjectURL(url);\r\n }\r\n \r\n /**\r\n * Downloads a file from a given URL and triggers a browser download.\r\n *\r\n * @param url - The URL of the file to download.\r\n * @param filename - The name of the file to be saved.\r\n * @returns A Promise that resolves when the download is initiated or rejects if there is an error.\r\n */\r\n export async function downloadFile(url: string, filename: string): Promise<void> {\r\n try {\r\n const response = await fetch(url);\r\n const blob = await response.blob();\r\n const downloadUrl = window.URL.createObjectURL(blob);\r\n \r\n const link = document.createElement(\"a\");\r\n link.href = downloadUrl;\r\n link.download = filename;\r\n document.body.appendChild(link);\r\n link.click();\r\n \r\n // Clean up\r\n document.body.removeChild(link);\r\n window.URL.revokeObjectURL(downloadUrl);\r\n } catch (error) {\r\n console.error(\"Error downloading file:\", error);\r\n throw error;\r\n }\r\n }\r\n ","/**\r\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\r\n *\r\n * @param url - The URL of the media file.\r\n * @returns A promise that resolves to 'image', 'video', or 'audio' based on the Content-Type header,\r\n * or `null` if the type couldn't be determined or the request fails.\r\n */\r\nexport async function detectMediaTypeFromUrl(url: string): Promise<'image' | 'video' | 'audio' | null> {\r\n try {\r\n // Use a HEAD request to fetch only the headers, avoiding download of the full file\r\n const response = await fetch(url, { method: 'HEAD' });\r\n \r\n // Extract the 'Content-Type' header from the response\r\n const contentType = response.headers.get('Content-Type');\r\n \r\n if (!contentType) return null;\r\n \r\n // Determine the media type from the content type\r\n if (contentType.startsWith('image/')) return 'image';\r\n if (contentType.startsWith('video/')) return 'video';\r\n if (contentType.startsWith('audio/')) return 'audio';\r\n \r\n // Return null if not a recognized media type\r\n return null;\r\n } catch (error) {\r\n console.error('Fetch failed:', error);\r\n return null;\r\n }\r\n }\r\n "],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/cache.ts","../src/get-audio-duration.ts","../src/limit.ts","../src/get-image-dimensions.ts","../src/get-video-metadata.ts","../src/get-thumbnail.ts","../src/audio-utils.ts","../src/dimension-handler.ts","../src/file-helper.ts","../src/url-helper.ts"],"sourcesContent":["import { Dimensions, VideoMeta } from \"./types\";\n\nexport const imageDimensionsCache: Record<string, Dimensions> = {};\nexport const videoMetaCache: Record<string, VideoMeta> = {};\nexport const audioDurationCache: Record<string, number> = {};","import { audioDurationCache } from \"./cache\";\n\n/**\n * Retrieves the duration (in seconds) of an audio file from a given source URL.\n * Uses a cache to avoid reloading the same audio multiple times.\n *\n * @param audioSrc - The source URL of the audio file.\n * @returns A Promise that resolves to the duration of the audio in seconds.\n */\nexport const getAudioDuration = (audioSrc: string): Promise<number> => {\n // Return cached duration if available\n if (audioDurationCache[audioSrc]) {\n return Promise.resolve(audioDurationCache[audioSrc]);\n }\n\n return new Promise((resolve, reject) => {\n const audio = document.createElement(\"audio\");\n audio.preload = \"metadata\"; // Only load metadata (e.g., duration)\n // Sanitize the audioSrc to prevent XSS by only allowing safe URLs (http, https, blob, data)\n const isSafeUrl = /^(https?:|blob:|data:audio\\/)/i.test(audioSrc);\n if (!isSafeUrl) {\n throw new Error(\"Unsafe audio source URL\");\n }\n audio.src = audioSrc;\n\n // When metadata is loaded, store duration in cache and resolve\n audio.onloadedmetadata = () => {\n const duration = audio.duration;\n audioDurationCache[audioSrc] = duration;\n resolve(duration);\n };\n\n // Handle loading errors\n audio.onerror = () => {\n reject(new Error(\"Failed to load audio metadata\"));\n };\n });\n};\n","// Maximum number of concurrent promises allowed to run\nconst concurrencyLimit = 5;\n\n// Number of currently active (running) promises\nlet activeCount = 0;\n\n// Queue to hold pending tasks waiting to be run when concurrency slots free up\nconst queue: Array<() => void> = [];\n\n/**\n * Runs the next task from the queue if concurrency limit is not reached.\n */\nfunction runNext() {\n // If no tasks are queued or we're already at the concurrency limit, do nothing\n if (queue.length === 0 || activeCount >= concurrencyLimit) return;\n\n // Dequeue next task\n const next = queue.shift();\n\n if (next) {\n activeCount++; // Mark one more active task\n next(); // Run it\n }\n}\n\n/**\n * Wraps an async function to enforce concurrency limits.\n * If concurrency limit is reached, the function is queued and executed later.\n * \n * @param fn - Async function returning a Promise\n * @returns Promise that resolves/rejects with fn's result\n */\nexport function limit<T>(fn: () => Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n // Task to run the function and handle completion\n const task = () => {\n fn()\n .then(resolve)\n .catch(reject)\n .finally(() => {\n activeCount--; // Mark task as done\n runNext(); // Trigger next queued task, if any\n });\n };\n\n if (activeCount < concurrencyLimit) {\n activeCount++; // Increment active count for immediate run\n task();\n } else {\n // Queue the task if concurrency limit reached\n queue.push(task);\n }\n });\n}\n","import { limit } from \"./limit\";\nimport { Dimensions } from \"./types\";\nimport { imageDimensionsCache } from \"./cache\";\n\n/**\n * Loads an image from the given URL and resolves with its natural dimensions.\n *\n * @param url - The image URL to load.\n * @returns A Promise that resolves with the image's width and height.\n */\nconst loadImageDimensions = (url: string): Promise<Dimensions> => {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') {\n reject(new Error('getImageDimensions() is only available in the browser.'));\n return;\n }\n\n const img = new Image();\n img.onload = () => {\n resolve({ width: img.naturalWidth, height: img.naturalHeight });\n };\n img.onerror = reject;\n img.src = url;\n });\n};\n\n/**\n * Gets the dimensions (width and height) of an image from the given URL.\n * Uses a cache to avoid reloading the image if already fetched.\n * Also uses a concurrency limiter to control resource usage.\n *\n * @param url - The URL of the image.\n * @returns A Promise that resolves to an object containing `width` and `height`.\n */\nexport const getImageDimensions = (url: string): Promise<Dimensions> => {\n // Return cached dimensions if available\n if (imageDimensionsCache[url]) {\n return Promise.resolve(imageDimensionsCache[url]);\n }\n\n // Fetch and cache the dimensions using a concurrency limit\n return limit(() => loadImageDimensions(url)).then((dimensions) => {\n imageDimensionsCache[url] = dimensions;\n return dimensions;\n });\n};\n","import { videoMetaCache } from \"./cache\";\nimport { VideoMeta } from \"./types\";\n\n/**\n * Fetches metadata (width, height, duration) for a given video source.\n * If metadata has already been fetched and cached, it returns the cached data.\n *\n * @param videoSrc - The URL or path to the video file.\n * @returns A Promise that resolves to an object containing video metadata.\n */\nexport const getVideoMeta = (videoSrc: string): Promise<VideoMeta> => {\n // Return cached metadata if available\n if (videoMetaCache[videoSrc]) {\n return Promise.resolve(videoMetaCache[videoSrc]);\n }\n\n return new Promise<VideoMeta>((resolve, reject) => {\n const video: HTMLVideoElement = document.createElement(\"video\");\n video.preload = \"metadata\"; // Only preload metadata to reduce bandwidth\n // Validate the videoSrc to ensure it's a safe URL before assigning it to video.src\n const isSafeUrl = /^(https?:|blob:|data:video\\/)/i.test(videoSrc);\n if (!isSafeUrl) {\n reject(new Error(\"Unsafe video source URL\"));\n return;\n }\n video.src = videoSrc;\n\n // When metadata is loaded, extract and cache it\n video.onloadedmetadata = () => {\n const meta: VideoMeta = {\n width: video.videoWidth,\n height: video.videoHeight,\n duration: video.duration,\n };\n videoMetaCache[videoSrc] = meta;\n resolve(meta);\n };\n\n // Handle video loading errors\n video.onerror = () => reject(new Error(\"Failed to load video metadata\"));\n });\n};\n","/**\n * Extracts a thumbnail from a video at a specific seek time and playback rate.\n *\n * This function creates a hidden `<video>` element in the browser,\n * seeks to the specified time, and captures the frame into a canvas,\n * which is then exported as a JPEG data URL or Blob URL.\n *\n * @param videoUrl - The URL of the video to extract the thumbnail from.\n * @param seekTime - The time in seconds at which to capture the frame. Default is 0.1s.\n * @param playbackRate - Playback speed for the video. Default is 1.\n * @returns A Promise that resolves to a thumbnail image URL (either a base64 data URL or blob URL).\n */\nexport const getThumbnail = async (\n videoUrl: string,\n seekTime = 0.1,\n playbackRate = 1\n ): Promise<string> => {\n return new Promise((resolve, reject) => {\n const video = document.createElement(\"video\");\n video.crossOrigin = \"anonymous\";\n video.muted = true;\n video.playsInline = true;\n video.autoplay = false;\n video.preload = \"auto\";\n video.playbackRate = playbackRate;\n \n // Make video element hidden\n video.style.position = \"absolute\";\n video.style.left = \"-9999px\";\n video.style.top = \"-9999px\";\n video.style.width = \"1px\";\n video.style.height = \"1px\";\n video.style.opacity = \"0\";\n video.style.pointerEvents = \"none\";\n video.style.zIndex = \"-1\";\n \n let timeoutId: number | undefined;\n \n // Cleanup video element and timeout\n const cleanup = () => {\n if (video.parentNode) video.remove();\n if (timeoutId) clearTimeout(timeoutId);\n };\n \n // Handle errors during video loading\n const handleError = () => {\n cleanup();\n reject(new Error(`Failed to load video: ${video.error?.message || \"Unknown error\"}`));\n };\n \n // Once seeked to target frame, capture the image\n const handleSeeked = () => {\n try {\n video.pause();\n \n const canvas = document.createElement(\"canvas\");\n const width = video.videoWidth || 640;\n const height = video.videoHeight || 360;\n canvas.width = width;\n canvas.height = height;\n \n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n cleanup();\n reject(new Error(\"Failed to get canvas context\"));\n return;\n }\n \n // Draw current video frame onto canvas\n ctx.drawImage(video, 0, 0, width, height);\n \n // Attempt to export canvas to base64 image URL\n try {\n const dataUrl = canvas.toDataURL(\"image/jpeg\", 0.8);\n cleanup();\n resolve(dataUrl);\n } catch {\n // Fallback: convert canvas to Blob\n canvas.toBlob((blob) => {\n if (!blob) {\n cleanup();\n reject(new Error(\"Failed to create Blob\"));\n return;\n }\n const blobUrl = URL.createObjectURL(blob);\n cleanup();\n resolve(blobUrl);\n }, \"image/jpeg\", 0.8);\n }\n } catch (err) {\n cleanup();\n reject(new Error(`Error creating thumbnail: ${err}`));\n }\n };\n \n video.addEventListener(\"error\", handleError, { once: true });\n video.addEventListener(\"seeked\", handleSeeked, { once: true });\n \n // After metadata is loaded, seek to the desired frame\n video.addEventListener(\"loadedmetadata\", () => {\n const playPromise = video.play();\n if (playPromise !== undefined) {\n playPromise\n .then(() => {\n video.currentTime = seekTime;\n })\n .catch(() => {\n video.currentTime = seekTime;\n });\n } else {\n video.currentTime = seekTime;\n }\n }, { once: true });\n \n // Timeout protection in case video loading hangs\n timeoutId = window.setTimeout(() => {\n cleanup();\n reject(new Error(\"Video loading timed out\"));\n }, 5000);\n \n // Assign video source and add it to the DOM (helps Safari/iOS behavior)\n video.src = videoUrl;\n document.body.appendChild(video);\n });\n };","/**\n * Audio segment interface for stitching\n */\nexport interface AudioSegment {\n src: string;\n s: number; // start time in seconds\n e: number; // end time in seconds\n volume?: number; // volume level (0-1), defaults to 1, 0 = muted\n}\n\n/**\n * Extracts an audio segment from a media source (e.g., video) between start and end times,\n * rendered at the specified playback rate, and returns a Blob URL to an MP3 file.\n *\n * The function fetches the source, decodes the audio track using Web Audio API,\n * renders the segment offline for speed and determinism, encodes it as MP3 using lamejs,\n * and returns an object URL. Callers should revoke the URL when done.\n *\n * Example:\n * const url = await extractAudio({ src, start: 3, end: 8, playbackRate: 1.25 });\n * const audio = new Audio(url);\n * audio.play();\n * // later: URL.revokeObjectURL(url);\n */\nexport const extractAudio = async ({\n src,\n playbackRate = 1,\n start = 0,\n end,\n}: {\n src: string;\n playbackRate?: number;\n start?: number;\n end?: number;\n}): Promise<string> => {\n if (!src) throw new Error(\"src is required\");\n if (playbackRate <= 0) throw new Error(\"playbackRate must be > 0\");\n\n // Basic URL safety check\n const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);\n if (!isSafeUrl) throw new Error(\"Unsafe media source URL\");\n\n // Fetch and decode audio\n const audioBuffer = await fetchAndDecodeAudio(src);\n\n // Normalize time range\n const clampedStart = Math.max(0, start || 0);\n const fullDuration = audioBuffer.duration;\n const clampedEnd = Math.min(\n typeof end === \"number\" ? end : fullDuration,\n fullDuration\n );\n if (clampedEnd <= clampedStart)\n throw new Error(\"Invalid range: end must be greater than start\");\n\n // Render segment with playback rate\n const renderedBuffer = await renderAudioSegment(\n audioBuffer,\n clampedStart,\n clampedEnd,\n playbackRate\n );\n\n // Convert to MP3 and return URL\n const mp3Blob = await audioBufferToMp3(renderedBuffer);\n return URL.createObjectURL(mp3Blob);\n};\n\n/**\n * Stitches multiple audio segments into a single MP3 file.\n * Creates a timeline where each segment plays at its specified time,\n * with silence filling gaps between segments.\n * \n * @param segments - Array of audio segments with source, start, and end times\n * @param totalDuration - Total duration of the output audio (optional, auto-calculated if not provided)\n * @returns Promise<string> - Blob URL to the stitched MP3 file\n * \n * Example:\n * const segments = [\n * { src: \"audio1.mp3\", s: 0, e: 2, volume: 1.0 },\n * { src: \"audio2.mp3\", s: 1, e: 4, volume: 0.5 }, // overlaps with audio1\n * { src: \"audio3.mp3\", s: 4, e: 7, volume: 0 } // muted, won't be included\n * ];\n * const url = await stitchAudio(segments, 7); // 7 second output with overlapping audio\n */\nexport const stitchAudio = async (\n segments: AudioSegment[],\n totalDuration?: number\n): Promise<string> => {\n if (!segments || segments.length === 0) {\n throw new Error(\"At least one audio segment is required\");\n }\n\n // Calculate total duration if not provided\n const duration = totalDuration || Math.max(...segments.map(s => s.e));\n\n // Create timeline and render segments\n const renderedBuffer = await createAudioTimeline(segments, duration);\n\n // Convert to MP3 and return URL\n const mp3Blob = await audioBufferToMp3(renderedBuffer);\n return URL.createObjectURL(mp3Blob);\n};\n\n// ===== SHARED UTILITIES =====\n\n/**\n * Fetches and decodes audio from a URL\n */\nconst fetchAndDecodeAudio = async (src: string): Promise<AudioBuffer> => {\n const response = await fetch(src);\n if (!response.ok) throw new Error(`Failed to fetch source: ${response.status}`);\n \n const arrayBuffer = await response.arrayBuffer();\n return decodeAudioData(arrayBuffer);\n};\n\n/**\n * Decodes audio data using Web Audio API\n */\nconst decodeAudioData = async (arrayBuffer: ArrayBuffer): Promise<AudioBuffer> => {\n const AudioContextCtor: typeof AudioContext =\n (window as any).AudioContext || (window as any).webkitAudioContext;\n if (!AudioContextCtor) throw new Error(\"Web Audio API not supported\");\n \n const audioContext = new AudioContextCtor();\n try {\n return await new Promise<AudioBuffer>((resolve, reject) => {\n audioContext.decodeAudioData(\n arrayBuffer.slice(0),\n (buf) => resolve(buf),\n (err) => reject(err || new Error(\"Failed to decode audio\"))\n );\n });\n } finally {\n audioContext.close();\n }\n};\n\n/**\n * Renders an audio segment with playback rate\n */\nconst renderAudioSegment = async (\n audioBuffer: AudioBuffer,\n start: number,\n end: number,\n playbackRate: number\n): Promise<AudioBuffer> => {\n const OfflineAudioContextCtor: typeof OfflineAudioContext =\n (window as any).OfflineAudioContext || (window as any).webkitOfflineAudioContext;\n if (!OfflineAudioContextCtor) throw new Error(\"OfflineAudioContext not supported\");\n\n const sampleRate = audioBuffer.sampleRate;\n const numChannels = audioBuffer.numberOfChannels;\n const sourceDuration = end - start;\n const renderedFrames = Math.max(\n 1,\n Math.ceil((sourceDuration / playbackRate) * sampleRate)\n );\n\n const offline = new OfflineAudioContextCtor(numChannels, renderedFrames, sampleRate);\n const sourceNode = offline.createBufferSource();\n sourceNode.buffer = audioBuffer;\n sourceNode.playbackRate.value = playbackRate;\n sourceNode.connect(offline.destination);\n sourceNode.start(0, start, sourceDuration);\n\n return await offline.startRendering();\n};\n\n/**\n * Creates an audio timeline with multiple segments\n */\nconst createAudioTimeline = async (\n segments: AudioSegment[],\n duration: number\n): Promise<AudioBuffer> => {\n const OfflineAudioContextCtor: typeof OfflineAudioContext =\n (window as any).OfflineAudioContext || (window as any).webkitOfflineAudioContext;\n if (!OfflineAudioContextCtor) throw new Error(\"OfflineAudioContext not supported\");\n\n const sampleRate = 44100; // Standard sample rate\n const totalFrames = Math.ceil(duration * sampleRate);\n const offline = new OfflineAudioContextCtor(2, totalFrames, sampleRate); // Stereo output\n\n // Process each segment\n for (const segment of segments) {\n if (segment.s >= segment.e) {\n console.warn(`Invalid segment: start (${segment.s}) >= end (${segment.e})`);\n continue;\n }\n\n // Skip segments with volume 0 (muted)\n const volume = segment.volume ?? 1;\n if (volume <= 0) {\n console.warn(`Skipping muted segment: ${segment.src}`);\n continue;\n }\n\n try {\n const audioBuffer = await fetchAndDecodeAudio(segment.src);\n const segmentDuration = segment.e - segment.s;\n const sourceDuration = Math.min(segmentDuration, audioBuffer.duration);\n\n const source = offline.createBufferSource();\n source.buffer = audioBuffer;\n \n // Apply volume control if not 1.0\n if (volume !== 1) {\n const gainNode = offline.createGain();\n gainNode.gain.value = volume;\n source.connect(gainNode);\n gainNode.connect(offline.destination);\n } else {\n source.connect(offline.destination);\n }\n \n source.start(segment.s, 0, sourceDuration);\n } catch (error) {\n console.warn(`Failed to process segment: ${segment.src}`, error);\n }\n }\n\n return await offline.startRendering();\n};\n\n/**\n * Converts an AudioBuffer to an MP3 Blob using lamejs\n */\nconst audioBufferToMp3 = async (buffer: AudioBuffer): Promise<Blob> => {\n try {\n // Convert AudioBuffer to WAV ArrayBuffer\n const wavArrayBuffer = audioBufferToWavArrayBuffer(buffer);\n \n // Decode WAV back to PCM using AudioContext\n const pcmBuffer = await decodeAudioData(wavArrayBuffer);\n \n // Encode PCM to MP3 using lamejs\n return await encodePcmToMp3(pcmBuffer);\n } catch (error) {\n // Fallback to WAV if MP3 encoding fails\n return audioBufferToWavBlob(buffer);\n }\n};\n\n/**\n * Converts AudioBuffer to WAV ArrayBuffer\n */\nconst audioBufferToWavArrayBuffer = (buffer: AudioBuffer): ArrayBuffer => {\n const numChannels = buffer.numberOfChannels;\n const sampleRate = buffer.sampleRate;\n const numFrames = buffer.length;\n\n // Interleave channels\n const interleaved = interleave(buffer, numChannels, numFrames);\n\n // Create WAV ArrayBuffer\n const bytesPerSample = 2; // 16-bit\n const blockAlign = numChannels * bytesPerSample;\n const byteRate = sampleRate * blockAlign;\n const dataSize = interleaved.length * bytesPerSample;\n const bufferSize = 44 + dataSize;\n const arrayBuffer = new ArrayBuffer(bufferSize);\n const view = new DataView(arrayBuffer);\n\n // RIFF header\n writeString(view, 0, \"RIFF\");\n view.setUint32(4, 36 + dataSize, true);\n writeString(view, 8, \"WAVE\");\n\n // fmt chunk\n writeString(view, 12, \"fmt \");\n view.setUint32(16, 16, true); // PCM\n view.setUint16(20, 1, true); // audio format = 1 (PCM)\n view.setUint16(22, numChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, 16, true); // bits per sample\n\n // data chunk\n writeString(view, 36, \"data\");\n view.setUint32(40, dataSize, true);\n\n // PCM samples\n floatTo16BitPCM(view, 44, interleaved);\n\n return arrayBuffer;\n};\n\n/**\n * Encodes PCM AudioBuffer to MP3 using lamejs\n */\nconst encodePcmToMp3 = async (buffer: AudioBuffer): Promise<Blob> => {\n const lamejs = await import(\"lamejs\");\n\n const channels = buffer.numberOfChannels >= 2 ? 2 : 1;\n // Downsample to 22050 Hz for smaller file size (good for voice/speech)\n const targetSampleRate = 22050;\n const downsampledBuffer = downsampleAudioBuffer(buffer, targetSampleRate);\n const kbps = 48; // Reduced bitrate for smaller file size\n\n const mp3encoder = new lamejs.default.Mp3Encoder(channels, targetSampleRate, kbps);\n const samplesPerFrame = 1152;\n\n // Prepare PCM Int16 arrays\n const leftFloat = downsampledBuffer.getChannelData(0);\n const left = floatTo16(leftFloat);\n let right: Int16Array | undefined;\n if (channels === 2) {\n const rightFloat = downsampledBuffer.getChannelData(1);\n right = floatTo16(rightFloat);\n }\n\n const mp3Chunks: Uint8Array[] = [];\n for (let i = 0; i < left.length; i += samplesPerFrame) {\n const leftChunk = left.subarray(i, Math.min(i + samplesPerFrame, left.length));\n let mp3buf: Uint8Array;\n if (channels === 2 && right) {\n const rightChunk = right.subarray(i, Math.min(i + samplesPerFrame, right.length));\n mp3buf = mp3encoder.encodeBuffer(leftChunk, rightChunk);\n } else {\n mp3buf = mp3encoder.encodeBuffer(leftChunk);\n }\n if (mp3buf.length > 0) mp3Chunks.push(mp3buf);\n }\n\n const end = mp3encoder.flush();\n if (end.length > 0) mp3Chunks.push(end);\n\n return new Blob(mp3Chunks, { type: \"audio/mpeg\" });\n};\n\n/**\n * Converts an AudioBuffer to a WAV Blob (fallback)\n */\nconst audioBufferToWavBlob = (buffer: AudioBuffer): Blob => {\n const arrayBuffer = audioBufferToWavArrayBuffer(buffer);\n return new Blob([arrayBuffer], { type: \"audio/wav\" });\n};\n\n/**\n * Downsamples an AudioBuffer to a lower sample rate for smaller file size\n */\nconst downsampleAudioBuffer = (buffer: AudioBuffer, targetSampleRate: number): AudioBuffer => {\n if (buffer.sampleRate === targetSampleRate) {\n return buffer;\n }\n\n const ratio = buffer.sampleRate / targetSampleRate;\n const newLength = Math.round(buffer.length / ratio);\n const newBuffer = new AudioContext().createBuffer(\n buffer.numberOfChannels,\n newLength,\n targetSampleRate\n );\n\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const oldData = buffer.getChannelData(channel);\n const newData = newBuffer.getChannelData(channel);\n \n for (let i = 0; i < newLength; i++) {\n const oldIndex = Math.floor(i * ratio);\n newData[i] = oldData[oldIndex];\n }\n }\n\n return newBuffer;\n};\n\n/**\n * Interleaves audio channels\n */\nconst interleave = (buffer: AudioBuffer, numChannels: number, numFrames: number): Float32Array => {\n if (numChannels === 1) {\n return buffer.getChannelData(0).slice(0, numFrames);\n }\n const result = new Float32Array(numFrames * numChannels);\n const channelData: Float32Array[] = [];\n for (let ch = 0; ch < numChannels; ch++) {\n channelData[ch] = buffer.getChannelData(ch);\n }\n let writeIndex = 0;\n for (let i = 0; i < numFrames; i++) {\n for (let ch = 0; ch < numChannels; ch++) {\n result[writeIndex++] = channelData[ch][i];\n }\n }\n return result;\n};\n\n/**\n * Converts float32 audio data to 16-bit PCM\n */\nconst floatTo16BitPCM = (view: DataView, offset: number, input: Float32Array): void => {\n let pos = offset;\n for (let i = 0; i < input.length; i++, pos += 2) {\n let s = Math.max(-1, Math.min(1, input[i]));\n view.setInt16(pos, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n }\n};\n\n/**\n * Converts float32 array to int16 array\n */\nconst floatTo16 = (input: Float32Array): Int16Array => {\n const output = new Int16Array(input.length);\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]));\n output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n }\n return output;\n};\n\n/**\n * Writes string to DataView\n */\nconst writeString = (view: DataView, offset: number, str: string): void => {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n};\n","import { Dimensions } from \"./types\";\n\n/**\n * Calculates the scaled dimensions of an element to fit inside a container\n * based on the specified max dimensions.\n *\n * @param width - The original width of the element.\n * @param height - The original height of the element.\n * @param maxWidth - The maximum width of the container.\n * @param maxHeight - The maximum height of the container.\n * @returns An object containing the calculated width and height for the element.\n */\nexport const getScaledDimensions = (\n width: number, \n height: number,\n maxWidth: number,\n maxHeight: number\n ): Dimensions => {\n // If the original dimensions are smaller than or equal to the max values, return the original dimensions\n if (width <= maxWidth && height <= maxHeight) {\n // Ensure the width and height are even numbers\n return {\n width: width % 2 === 0 ? width : width - 1,\n height: height % 2 === 0 ? height : height - 1,\n };\n }\n \n // Calculate scaling factor based on the maximum width and height\n const widthRatio = maxWidth / width;\n const heightRatio = maxHeight / height;\n \n // Use the smaller of the two ratios to maintain the aspect ratio\n const scale = Math.min(widthRatio, heightRatio);\n \n // Calculate the scaled dimensions\n let scaledWidth = Math.round(width * scale);\n let scaledHeight = Math.round(height * scale);\n \n // Ensure the width and height are even numbers\n if (scaledWidth % 2 !== 0) {\n scaledWidth -= 1; // Make width even if it's odd\n }\n if (scaledHeight % 2 !== 0) {\n scaledHeight -= 1; // Make height even if it's odd\n }\n \n // Ensure the scaled width and height fit within the max dimensions\n return {\n width: Math.min(scaledWidth, maxWidth),\n height: Math.min(scaledHeight, maxHeight),\n };\n };\n\n/**\n * Calculates the resized dimensions of an element to fit inside a container\n * based on the specified object-fit strategy (\"contain\", \"cover\", \"fill\", or default).\n *\n * @param objectFit - The object-fit behavior ('contain', 'cover', 'fill', or default/fallback).\n * @param elementSize - The original size of the element (width and height).\n * @param containerSize - The size of the container (width and height).\n * @returns An object containing the calculated width and height for the element.\n */\nexport const getObjectFitSize = (\n objectFit: string,\n elementSize: Dimensions,\n containerSize: Dimensions\n): Dimensions => {\n const elementAspectRatio = elementSize.width / elementSize.height;\n const containerAspectRatio = containerSize.width / containerSize.height;\n\n switch (objectFit) {\n case \"contain\":\n // Fit entire element inside container without cropping, maintaining aspect ratio\n if (elementAspectRatio > containerAspectRatio) {\n return {\n width: containerSize.width,\n height: containerSize.width / elementAspectRatio,\n };\n } else {\n return {\n width: containerSize.height * elementAspectRatio,\n height: containerSize.height,\n };\n }\n\n case \"cover\":\n // Fill container while maintaining aspect ratio, possibly cropping the element\n if (elementAspectRatio > containerAspectRatio) {\n return {\n width: containerSize.height * elementAspectRatio,\n height: containerSize.height,\n };\n } else {\n return {\n width: containerSize.width,\n height: containerSize.width / elementAspectRatio,\n };\n }\n\n case \"fill\":\n // Stretch element to completely fill the container, ignoring aspect ratio\n return {\n width: containerSize.width,\n height: containerSize.height,\n };\n\n default:\n // Default behavior: return original size of the element\n return {\n width: elementSize.width,\n height: elementSize.height,\n };\n }\n};\n\n ","/**\n * Converts a Blob URL to a File object.\n *\n * @param blobUrl - The Blob URL to convert.\n * @param fileName - The name to assign to the resulting File.\n * @returns A Promise that resolves to a File object.\n */\nexport const blobUrlToFile = async (blobUrl: string, fileName: string): Promise<File> => {\n const response = await fetch(blobUrl);\n const blob = await response.blob();\n return new File([blob], fileName, { type: blob.type });\n };\n \n /**\n * Triggers a download of a file from a string or Blob.\n *\n * @param content - The content to save, either a string or a Blob.\n * @param type - The MIME type of the content.\n * @param name - The name of the file to be saved.\n */\n export const saveAsFile = (content: string | Blob, type: string, name: string): void => {\n const blob = typeof content === \"string\" ? new Blob([content], { type }) : content;\n const url = URL.createObjectURL(blob);\n \n const a = document.createElement(\"a\");\n a.href = url;\n a.download = name;\n a.click();\n \n // Clean up the URL object after download\n URL.revokeObjectURL(url);\n };\n \n /**\n * Downloads a file from a given URL and triggers a browser download.\n *\n * @param url - The URL of the file to download.\n * @param filename - The name of the file to be saved.\n * @returns A Promise that resolves when the download is initiated or rejects if there is an error.\n */\n export const downloadFile = async (url: string, filename: string): Promise<void> => {\n try {\n const response = await fetch(url);\n const blob = await response.blob();\n const downloadUrl = window.URL.createObjectURL(blob);\n \n const link = document.createElement(\"a\");\n link.href = downloadUrl;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n \n // Clean up\n document.body.removeChild(link);\n window.URL.revokeObjectURL(downloadUrl);\n } catch (error) {\n console.error(\"Error downloading file:\", error);\n throw error;\n }\n };\n ","/**\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\n *\n * @param url - The URL of the media file.\n * @returns A promise that resolves to 'image', 'video', or 'audio' based on the Content-Type header,\n * or `null` if the type couldn't be determined or the request fails.\n */\nexport const detectMediaTypeFromUrl = async (url: string): Promise<'image' | 'video' | 'audio' | null> => {\n try {\n // Use a HEAD request to fetch only the headers, avoiding download of the full file\n const response = await fetch(url, { method: 'HEAD' });\n \n // Extract the 'Content-Type' header from the response\n const contentType = response.headers.get('Content-Type');\n \n if (!contentType) return null;\n \n // Determine the media type from the content type\n if (contentType.startsWith('image/')) return 'image';\n if (contentType.startsWith('video/')) return 'video';\n if (contentType.startsWith('audio/')) return 'audio';\n \n // Return null if not a recognized media type\n return null;\n } catch (error) {\n console.error('Fetch failed:', error);\n return null;\n }\n };\n "],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@twick/media-utils",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
}
|
|
32
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@twick/media-utils",
|
|
3
|
+
"version": "0.14.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "vite build",
|
|
18
|
+
"dev": "vite build --watch",
|
|
19
|
+
"lint": "eslint src/",
|
|
20
|
+
"clean": "rimraf .turbo node_modules dist",
|
|
21
|
+
"docs": "typedoc --out docs src/index.ts"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.11.24",
|
|
25
|
+
"typescript": "5.4.2",
|
|
26
|
+
"vite": "^5.1.4",
|
|
27
|
+
"vite-plugin-dts": "^3.7.3",
|
|
28
|
+
"rimraf": "^5.0.5",
|
|
29
|
+
"typedoc": "^0.25.8",
|
|
30
|
+
"typedoc-plugin-markdown": "^3.17.1"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lamejs": "^1.2.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": "^18.0.0",
|
|
37
|
+
"react-dom": "^18.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|