@twick/media-utils 0.0.1

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 ADDED
@@ -0,0 +1,23 @@
1
+ # @twick/media-utils
2
+
3
+ Media utilities for the Twick platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @twick/media-utils
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { /* your exports */ } from '@twick/media-utils';
15
+ ```
16
+
17
+ ## API Documentation
18
+
19
+ For detailed API documentation, see the generated docs in the `docs` directory.
20
+
21
+ ## License
22
+
23
+ Apache-2.0
@@ -0,0 +1,44 @@
1
+ import { blobUrlToFile } from './file-helper';
2
+ import { detectMediaTypeFromUrl } from './url-helper';
3
+ import { Dimensions } from './types';
4
+ import { downloadFile } from './file-helper';
5
+ import { getAudioDuration } from './get-audio-duration';
6
+ import { getImageDimensions } from './get-image-dimensions';
7
+ import { getObjectFitSize } from './dimension-handler';
8
+ import { getScaledDimensions } from './dimension-handler';
9
+ import { getThumbnail } from './get-thumbnail';
10
+ import { getVideoMeta } from './get-video-metadata';
11
+ import { limit } from './limit';
12
+ import { Position } from './types';
13
+ import { saveAsFile } from './file-helper';
14
+ import { VideoMeta } from './types';
15
+
16
+ export { blobUrlToFile }
17
+
18
+ export { detectMediaTypeFromUrl }
19
+
20
+ export { Dimensions }
21
+
22
+ export { downloadFile }
23
+
24
+ export { getAudioDuration }
25
+
26
+ export { getImageDimensions }
27
+
28
+ export { getObjectFitSize }
29
+
30
+ export { getScaledDimensions }
31
+
32
+ export { getThumbnail }
33
+
34
+ export { getVideoMeta }
35
+
36
+ export { limit }
37
+
38
+ export { Position }
39
+
40
+ export { saveAsFile }
41
+
42
+ export { VideoMeta }
43
+
44
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,300 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const imageDimensionsCache = {};
6
+ const videoMetaCache = {};
7
+ const audioDurationCache = {};
8
+
9
+ const getAudioDuration = (audioSrc) => {
10
+ if (audioDurationCache[audioSrc]) {
11
+ return Promise.resolve(audioDurationCache[audioSrc]);
12
+ }
13
+ return new Promise((resolve, reject) => {
14
+ const audio = document.createElement("audio");
15
+ audio.preload = "metadata";
16
+ audio.src = audioSrc;
17
+ audio.onloadedmetadata = () => {
18
+ const duration = audio.duration;
19
+ audioDurationCache[audioSrc] = duration;
20
+ resolve(duration);
21
+ };
22
+ audio.onerror = () => {
23
+ reject(new Error("Failed to load audio metadata"));
24
+ };
25
+ });
26
+ };
27
+
28
+ const concurrencyLimit = 5;
29
+ let activeCount = 0;
30
+ const queue = [];
31
+ function runNext() {
32
+ if (queue.length === 0 || activeCount >= concurrencyLimit) return;
33
+ const next = queue.shift();
34
+ if (next) {
35
+ activeCount++;
36
+ next();
37
+ }
38
+ }
39
+ function limit(fn) {
40
+ return new Promise((resolve, reject) => {
41
+ const task = () => {
42
+ fn().then(resolve).catch(reject).finally(() => {
43
+ activeCount--;
44
+ runNext();
45
+ });
46
+ };
47
+ if (activeCount < concurrencyLimit) {
48
+ activeCount++;
49
+ task();
50
+ } else {
51
+ queue.push(task);
52
+ }
53
+ });
54
+ }
55
+
56
+ function loadImageDimensions(url) {
57
+ return new Promise((resolve, reject) => {
58
+ if (typeof document === "undefined") {
59
+ reject(new Error("getImageDimensions() is only available in the browser."));
60
+ return;
61
+ }
62
+ const img = new Image();
63
+ img.onload = () => {
64
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
65
+ };
66
+ img.onerror = reject;
67
+ img.src = url;
68
+ });
69
+ }
70
+ function getImageDimensions(url) {
71
+ if (imageDimensionsCache[url]) {
72
+ return Promise.resolve(imageDimensionsCache[url]);
73
+ }
74
+ return limit(() => loadImageDimensions(url)).then((dimensions) => {
75
+ imageDimensionsCache[url] = dimensions;
76
+ return dimensions;
77
+ });
78
+ }
79
+
80
+ const getVideoMeta = (videoSrc) => {
81
+ if (videoMetaCache[videoSrc]) {
82
+ return Promise.resolve(videoMetaCache[videoSrc]);
83
+ }
84
+ return new Promise((resolve, reject) => {
85
+ const video = document.createElement("video");
86
+ video.preload = "metadata";
87
+ video.src = videoSrc;
88
+ video.onloadedmetadata = () => {
89
+ const meta = {
90
+ width: video.videoWidth,
91
+ height: video.videoHeight,
92
+ duration: video.duration
93
+ };
94
+ videoMetaCache[videoSrc] = meta;
95
+ resolve(meta);
96
+ };
97
+ video.onerror = () => reject(new Error("Failed to load video metadata"));
98
+ });
99
+ };
100
+
101
+ async function getThumbnail(videoUrl, seekTime = 0.1, playbackRate = 1) {
102
+ return new Promise((resolve, reject) => {
103
+ const video = document.createElement("video");
104
+ video.crossOrigin = "anonymous";
105
+ video.muted = true;
106
+ video.playsInline = true;
107
+ video.autoplay = false;
108
+ video.preload = "auto";
109
+ video.playbackRate = playbackRate;
110
+ let timeoutId;
111
+ const cleanup = () => {
112
+ if (video.parentNode) video.remove();
113
+ if (timeoutId) clearTimeout(timeoutId);
114
+ };
115
+ const handleError = () => {
116
+ cleanup();
117
+ reject(new Error(`Failed to load video: ${video.error?.message || "Unknown error"}`));
118
+ };
119
+ const handleSeeked = () => {
120
+ try {
121
+ video.pause();
122
+ const canvas = document.createElement("canvas");
123
+ const width = video.videoWidth || 640;
124
+ const height = video.videoHeight || 360;
125
+ canvas.width = width;
126
+ canvas.height = height;
127
+ const ctx = canvas.getContext("2d");
128
+ if (!ctx) {
129
+ cleanup();
130
+ reject(new Error("Failed to get canvas context"));
131
+ return;
132
+ }
133
+ ctx.drawImage(video, 0, 0, width, height);
134
+ try {
135
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.8);
136
+ cleanup();
137
+ resolve(dataUrl);
138
+ } catch {
139
+ canvas.toBlob((blob) => {
140
+ if (!blob) {
141
+ cleanup();
142
+ reject(new Error("Failed to create Blob"));
143
+ return;
144
+ }
145
+ const blobUrl = URL.createObjectURL(blob);
146
+ cleanup();
147
+ resolve(blobUrl);
148
+ }, "image/jpeg", 0.8);
149
+ }
150
+ } catch (err) {
151
+ cleanup();
152
+ reject(new Error(`Error creating thumbnail: ${err}`));
153
+ }
154
+ };
155
+ video.addEventListener("error", handleError, { once: true });
156
+ video.addEventListener("seeked", handleSeeked, { once: true });
157
+ video.addEventListener("loadedmetadata", () => {
158
+ const playPromise = video.play();
159
+ if (playPromise !== void 0) {
160
+ playPromise.then(() => {
161
+ video.currentTime = seekTime;
162
+ }).catch(() => {
163
+ video.currentTime = seekTime;
164
+ });
165
+ } else {
166
+ video.currentTime = seekTime;
167
+ }
168
+ }, { once: true });
169
+ timeoutId = window.setTimeout(() => {
170
+ cleanup();
171
+ reject(new Error("Video loading timed out"));
172
+ }, 5e3);
173
+ video.src = videoUrl;
174
+ document.body.appendChild(video);
175
+ });
176
+ }
177
+
178
+ function getScaledDimensions(width, height, maxWidth, maxHeight) {
179
+ if (width <= maxWidth && height <= maxHeight) {
180
+ return {
181
+ width: width % 2 === 0 ? width : width - 1,
182
+ height: height % 2 === 0 ? height : height - 1
183
+ };
184
+ }
185
+ const widthRatio = maxWidth / width;
186
+ const heightRatio = maxHeight / height;
187
+ const scale = Math.min(widthRatio, heightRatio);
188
+ let scaledWidth = Math.round(width * scale);
189
+ let scaledHeight = Math.round(height * scale);
190
+ if (scaledWidth % 2 !== 0) {
191
+ scaledWidth -= 1;
192
+ }
193
+ if (scaledHeight % 2 !== 0) {
194
+ scaledHeight -= 1;
195
+ }
196
+ return {
197
+ width: Math.min(scaledWidth, maxWidth),
198
+ height: Math.min(scaledHeight, maxHeight)
199
+ };
200
+ }
201
+ function getObjectFitSize(objectFit, elementSize, containerSize) {
202
+ const elementAspectRatio = elementSize.width / elementSize.height;
203
+ const containerAspectRatio = containerSize.width / containerSize.height;
204
+ switch (objectFit) {
205
+ case "contain":
206
+ if (elementAspectRatio > containerAspectRatio) {
207
+ return {
208
+ width: containerSize.width,
209
+ height: containerSize.width / elementAspectRatio
210
+ };
211
+ } else {
212
+ return {
213
+ width: containerSize.height * elementAspectRatio,
214
+ height: containerSize.height
215
+ };
216
+ }
217
+ case "cover":
218
+ if (elementAspectRatio > containerAspectRatio) {
219
+ return {
220
+ width: containerSize.height * elementAspectRatio,
221
+ height: containerSize.height
222
+ };
223
+ } else {
224
+ return {
225
+ width: containerSize.width,
226
+ height: containerSize.width / elementAspectRatio
227
+ };
228
+ }
229
+ case "fill":
230
+ return {
231
+ width: containerSize.width,
232
+ height: containerSize.height
233
+ };
234
+ default:
235
+ return {
236
+ width: elementSize.width,
237
+ height: elementSize.height
238
+ };
239
+ }
240
+ }
241
+
242
+ async function blobUrlToFile(blobUrl, fileName) {
243
+ const response = await fetch(blobUrl);
244
+ const blob = await response.blob();
245
+ return new File([blob], fileName, { type: blob.type });
246
+ }
247
+ function saveAsFile(content, type, name) {
248
+ const blob = typeof content === "string" ? new Blob([content], { type }) : content;
249
+ const url = URL.createObjectURL(blob);
250
+ const a = document.createElement("a");
251
+ a.href = url;
252
+ a.download = name;
253
+ a.click();
254
+ URL.revokeObjectURL(url);
255
+ }
256
+ async function downloadFile(url, filename) {
257
+ try {
258
+ const response = await fetch(url);
259
+ const blob = await response.blob();
260
+ const downloadUrl = window.URL.createObjectURL(blob);
261
+ const link = document.createElement("a");
262
+ link.href = downloadUrl;
263
+ link.download = filename;
264
+ document.body.appendChild(link);
265
+ link.click();
266
+ document.body.removeChild(link);
267
+ window.URL.revokeObjectURL(downloadUrl);
268
+ } catch (error) {
269
+ console.error("Error downloading file:", error);
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ async function detectMediaTypeFromUrl(url) {
275
+ try {
276
+ const response = await fetch(url, { method: "HEAD" });
277
+ const contentType = response.headers.get("Content-Type");
278
+ if (!contentType) return null;
279
+ if (contentType.startsWith("image/")) return "image";
280
+ if (contentType.startsWith("video/")) return "video";
281
+ if (contentType.startsWith("audio/")) return "audio";
282
+ return null;
283
+ } catch (error) {
284
+ console.error("Fetch failed:", error);
285
+ return null;
286
+ }
287
+ }
288
+
289
+ exports.blobUrlToFile = blobUrlToFile;
290
+ exports.detectMediaTypeFromUrl = detectMediaTypeFromUrl;
291
+ exports.downloadFile = downloadFile;
292
+ exports.getAudioDuration = getAudioDuration;
293
+ exports.getImageDimensions = getImageDimensions;
294
+ exports.getObjectFitSize = getObjectFitSize;
295
+ exports.getScaledDimensions = getScaledDimensions;
296
+ exports.getThumbnail = getThumbnail;
297
+ exports.getVideoMeta = getVideoMeta;
298
+ exports.limit = limit;
299
+ exports.saveAsFile = saveAsFile;
300
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","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":";;;;AAEO,MAAM,uBAAmD,EAAC;AAC1D,MAAM,iBAA4C,EAAC;AACnD,MAAM,qBAA6C,EAAC;;ACK9C,MAAA,gBAAA,GAAmB,CAAC,QAAsC,KAAA;AAErE,EAAI,IAAA,kBAAA,CAAmB,QAAQ,CAAG,EAAA;AAChC,IAAA,OAAO,OAAQ,CAAA,OAAA,CAAQ,kBAAmB,CAAA,QAAQ,CAAC,CAAA;AAAA;AAGrD,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAW,KAAA;AACtC,IAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,OAAU,GAAA,UAAA;AAChB,IAAA,KAAA,CAAM,GAAM,GAAA,QAAA;AAGZ,IAAA,KAAA,CAAM,mBAAmB,MAAM;AAC7B,MAAA,MAAM,WAAW,KAAM,CAAA,QAAA;AACvB,MAAA,kBAAA,CAAmB,QAAQ,CAAI,GAAA,QAAA;AAC/B,MAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,KAClB;AAGA,IAAA,KAAA,CAAM,UAAU,MAAM;AACpB,MAAO,MAAA,CAAA,IAAI,KAAM,CAAA,+BAA+B,CAAC,CAAA;AAAA,KACnD;AAAA,GACD,CAAA;AACH;;AC/BA,MAAM,gBAAmB,GAAA,CAAA;AAGzB,IAAI,WAAc,GAAA,CAAA;AAGlB,MAAM,QAA2B,EAAC;AAKlC,SAAS,OAAU,GAAA;AAEjB,EAAA,IAAI,KAAM,CAAA,MAAA,KAAW,CAAK,IAAA,WAAA,IAAe,gBAAkB,EAAA;AAG3D,EAAM,MAAA,IAAA,GAAO,MAAM,KAAM,EAAA;AAEzB,EAAA,IAAI,IAAM,EAAA;AACR,IAAA,WAAA,EAAA;AACA,IAAK,IAAA,EAAA;AAAA;AAET;AASO,SAAS,MAAS,EAAkC,EAAA;AACzD,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAW,KAAA;AAEtC,IAAA,MAAM,OAAO,MAAM;AACjB,MAAG,EAAA,EAAA,CACA,KAAK,OAAO,CAAA,CACZ,MAAM,MAAM,CAAA,CACZ,QAAQ,MAAM;AACb,QAAA,WAAA,EAAA;AACA,QAAQ,OAAA,EAAA;AAAA,OACT,CAAA;AAAA,KACL;AAEA,IAAA,IAAI,cAAc,gBAAkB,EAAA;AAClC,MAAA,WAAA,EAAA;AACA,MAAK,IAAA,EAAA;AAAA,KACA,MAAA;AAEL,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA;AACjB,GACD,CAAA;AACH;;AC3CA,SAAS,oBAAoB,GAAkC,EAAA;AAC7D,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAW,KAAA;AACtC,IAAI,IAAA,OAAO,aAAa,WAAa,EAAA;AACnC,MAAO,MAAA,CAAA,IAAI,KAAM,CAAA,wDAAwD,CAAC,CAAA;AAC1E,MAAA;AAAA;AAGF,IAAM,MAAA,GAAA,GAAM,IAAI,KAAM,EAAA;AACtB,IAAA,GAAA,CAAI,SAAS,MAAM;AACjB,MAAA,OAAA,CAAQ,EAAE,KAAO,EAAA,GAAA,CAAI,cAAc,MAAQ,EAAA,GAAA,CAAI,eAAe,CAAA;AAAA,KAChE;AACA,IAAA,GAAA,CAAI,OAAU,GAAA,MAAA;AACd,IAAA,GAAA,CAAI,GAAM,GAAA,GAAA;AAAA,GACX,CAAA;AACH;AAUO,SAAS,mBAAmB,GAAkC,EAAA;AAEnE,EAAI,IAAA,oBAAA,CAAqB,GAAG,CAAG,EAAA;AAC7B,IAAA,OAAO,OAAQ,CAAA,OAAA,CAAQ,oBAAqB,CAAA,GAAG,CAAC,CAAA;AAAA;AAIlD,EAAO,OAAA,KAAA,CAAM,MAAM,mBAAoB,CAAA,GAAG,CAAC,CAAE,CAAA,IAAA,CAAK,CAAC,UAAe,KAAA;AAChE,IAAA,oBAAA,CAAqB,GAAG,CAAI,GAAA,UAAA;AAC5B,IAAO,OAAA,UAAA;AAAA,GACR,CAAA;AACH;;ACnCa,MAAA,YAAA,GAAe,CAAC,QAAyC,KAAA;AAEpE,EAAI,IAAA,cAAA,CAAe,QAAQ,CAAG,EAAA;AAC5B,IAAA,OAAO,OAAQ,CAAA,OAAA,CAAQ,cAAe,CAAA,QAAQ,CAAC,CAAA;AAAA;AAGjD,EAAA,OAAO,IAAI,OAAA,CAAmB,CAAC,OAAA,EAAS,MAAW,KAAA;AACjD,IAAM,MAAA,KAAA,GAA0B,QAAS,CAAA,aAAA,CAAc,OAAO,CAAA;AAC9D,IAAA,KAAA,CAAM,OAAU,GAAA,UAAA;AAChB,IAAA,KAAA,CAAM,GAAM,GAAA,QAAA;AAGZ,IAAA,KAAA,CAAM,mBAAmB,MAAM;AAC7B,MAAA,MAAM,IAAkB,GAAA;AAAA,QACtB,OAAO,KAAM,CAAA,UAAA;AAAA,QACb,QAAQ,KAAM,CAAA,WAAA;AAAA,QACd,UAAU,KAAM,CAAA;AAAA,OAClB;AACA,MAAA,cAAA,CAAe,QAAQ,CAAI,GAAA,IAAA;AAC3B,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,KACd;AAGA,IAAA,KAAA,CAAM,UAAU,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,+BAA+B,CAAC,CAAA;AAAA,GACxE,CAAA;AACH;;ACvBA,eAAsB,YAClB,CAAA,QAAA,EACA,QAAW,GAAA,GAAA,EACX,eAAe,CACE,EAAA;AACjB,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAW,KAAA;AACtC,IAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,WAAc,GAAA,WAAA;AACpB,IAAA,KAAA,CAAM,KAAQ,GAAA,IAAA;AACd,IAAA,KAAA,CAAM,WAAc,GAAA,IAAA;AACpB,IAAA,KAAA,CAAM,QAAW,GAAA,KAAA;AACjB,IAAA,KAAA,CAAM,OAAU,GAAA,MAAA;AAChB,IAAA,KAAA,CAAM,YAAe,GAAA,YAAA;AAErB,IAAI,IAAA,SAAA;AAGJ,IAAA,MAAM,UAAU,MAAM;AACpB,MAAI,IAAA,KAAA,CAAM,UAAY,EAAA,KAAA,CAAM,MAAO,EAAA;AACnC,MAAI,IAAA,SAAA,eAAwB,SAAS,CAAA;AAAA,KACvC;AAGA,IAAA,MAAM,cAAc,MAAM;AACxB,MAAQ,OAAA,EAAA;AACR,MAAO,MAAA,CAAA,IAAI,MAAM,CAAyB,sBAAA,EAAA,KAAA,CAAM,OAAO,OAAW,IAAA,eAAe,EAAE,CAAC,CAAA;AAAA,KACtF;AAGA,IAAA,MAAM,eAAe,MAAM;AACzB,MAAI,IAAA;AACF,QAAA,KAAA,CAAM,KAAM,EAAA;AAEZ,QAAM,MAAA,MAAA,GAAS,QAAS,CAAA,aAAA,CAAc,QAAQ,CAAA;AAC9C,QAAM,MAAA,KAAA,GAAQ,MAAM,UAAc,IAAA,GAAA;AAClC,QAAM,MAAA,MAAA,GAAS,MAAM,WAAe,IAAA,GAAA;AACpC,QAAA,MAAA,CAAO,KAAQ,GAAA,KAAA;AACf,QAAA,MAAA,CAAO,MAAS,GAAA,MAAA;AAEhB,QAAM,MAAA,GAAA,GAAM,MAAO,CAAA,UAAA,CAAW,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,GAAK,EAAA;AACR,UAAQ,OAAA,EAAA;AACR,UAAO,MAAA,CAAA,IAAI,KAAM,CAAA,8BAA8B,CAAC,CAAA;AAChD,UAAA;AAAA;AAIF,QAAA,GAAA,CAAI,SAAU,CAAA,KAAA,EAAO,CAAG,EAAA,CAAA,EAAG,OAAO,MAAM,CAAA;AAGxC,QAAI,IAAA;AACF,UAAA,MAAM,OAAU,GAAA,MAAA,CAAO,SAAU,CAAA,YAAA,EAAc,GAAG,CAAA;AAClD,UAAQ,OAAA,EAAA;AACR,UAAA,OAAA,CAAQ,OAAO,CAAA;AAAA,SACT,CAAA,MAAA;AAEN,UAAO,MAAA,CAAA,MAAA,CAAO,CAAC,IAAS,KAAA;AACtB,YAAA,IAAI,CAAC,IAAM,EAAA;AACT,cAAQ,OAAA,EAAA;AACR,cAAO,MAAA,CAAA,IAAI,KAAM,CAAA,uBAAuB,CAAC,CAAA;AACzC,cAAA;AAAA;AAEF,YAAM,MAAA,OAAA,GAAU,GAAI,CAAA,eAAA,CAAgB,IAAI,CAAA;AACxC,YAAQ,OAAA,EAAA;AACR,YAAA,OAAA,CAAQ,OAAO,CAAA;AAAA,WACjB,EAAG,cAAc,GAAG,CAAA;AAAA;AACtB,eACO,GAAK,EAAA;AACZ,QAAQ,OAAA,EAAA;AACR,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAA6B,0BAAA,EAAA,GAAG,EAAE,CAAC,CAAA;AAAA;AACtD,KACF;AAEA,IAAA,KAAA,CAAM,iBAAiB,OAAS,EAAA,WAAA,EAAa,EAAE,IAAA,EAAM,MAAM,CAAA;AAC3D,IAAA,KAAA,CAAM,iBAAiB,QAAU,EAAA,YAAA,EAAc,EAAE,IAAA,EAAM,MAAM,CAAA;AAG7D,IAAM,KAAA,CAAA,gBAAA,CAAiB,kBAAkB,MAAM;AAC7C,MAAM,MAAA,WAAA,GAAc,MAAM,IAAK,EAAA;AAC/B,MAAA,IAAI,gBAAgB,MAAW,EAAA;AAC7B,QAAA,WAAA,CACG,KAAK,MAAM;AACV,UAAA,KAAA,CAAM,WAAc,GAAA,QAAA;AAAA,SACrB,CACA,CAAA,KAAA,CAAM,MAAM;AACX,UAAA,KAAA,CAAM,WAAc,GAAA,QAAA;AAAA,SACrB,CAAA;AAAA,OACE,MAAA;AACL,QAAA,KAAA,CAAM,WAAc,GAAA,QAAA;AAAA;AACtB,KACC,EAAA,EAAE,IAAM,EAAA,IAAA,EAAM,CAAA;AAGjB,IAAY,SAAA,GAAA,MAAA,CAAO,WAAW,MAAM;AAClC,MAAQ,OAAA,EAAA;AACR,MAAO,MAAA,CAAA,IAAI,KAAM,CAAA,yBAAyB,CAAC,CAAA;AAAA,OAC1C,GAAI,CAAA;AAGP,IAAA,KAAA,CAAM,GAAM,GAAA,QAAA;AACZ,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,KAAK,CAAA;AAAA,GAChC,CAAA;AACH;;ACtGK,SAAS,mBACZ,CAAA,KAAA,EACA,MACA,EAAA,QAAA,EACA,SACY,EAAA;AAEZ,EAAI,IAAA,KAAA,IAAS,QAAY,IAAA,MAAA,IAAU,SAAW,EAAA;AAE5C,IAAO,OAAA;AAAA,MACL,KAAO,EAAA,KAAA,GAAQ,CAAM,KAAA,CAAA,GAAI,QAAQ,KAAQ,GAAA,CAAA;AAAA,MACzC,MAAQ,EAAA,MAAA,GAAS,CAAM,KAAA,CAAA,GAAI,SAAS,MAAS,GAAA;AAAA,KAC/C;AAAA;AAIF,EAAA,MAAM,aAAa,QAAW,GAAA,KAAA;AAC9B,EAAA,MAAM,cAAc,SAAY,GAAA,MAAA;AAGhC,EAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,GAAI,CAAA,UAAA,EAAY,WAAW,CAAA;AAG9C,EAAA,IAAI,WAAc,GAAA,IAAA,CAAK,KAAM,CAAA,KAAA,GAAQ,KAAK,CAAA;AAC1C,EAAA,IAAI,YAAe,GAAA,IAAA,CAAK,KAAM,CAAA,MAAA,GAAS,KAAK,CAAA;AAG5C,EAAI,IAAA,WAAA,GAAc,MAAM,CAAG,EAAA;AACzB,IAAe,WAAA,IAAA,CAAA;AAAA;AAEjB,EAAI,IAAA,YAAA,GAAe,MAAM,CAAG,EAAA;AAC1B,IAAgB,YAAA,IAAA,CAAA;AAAA;AAIlB,EAAO,OAAA;AAAA,IACL,KAAO,EAAA,IAAA,CAAK,GAAI,CAAA,WAAA,EAAa,QAAQ,CAAA;AAAA,IACrC,MAAQ,EAAA,IAAA,CAAK,GAAI,CAAA,YAAA,EAAc,SAAS;AAAA,GAC1C;AACF;AAWc,SAAA,gBAAA,CACd,SACA,EAAA,WAAA,EACA,aACY,EAAA;AACZ,EAAM,MAAA,kBAAA,GAAqB,WAAY,CAAA,KAAA,GAAQ,WAAY,CAAA,MAAA;AAC3D,EAAM,MAAA,oBAAA,GAAuB,aAAc,CAAA,KAAA,GAAQ,aAAc,CAAA,MAAA;AAEjE,EAAA,QAAQ,SAAW;AAAA,IACjB,KAAK,SAAA;AAEH,MAAA,IAAI,qBAAqB,oBAAsB,EAAA;AAC7C,QAAO,OAAA;AAAA,UACL,OAAO,aAAc,CAAA,KAAA;AAAA,UACrB,MAAA,EAAQ,cAAc,KAAQ,GAAA;AAAA,SAChC;AAAA,OACK,MAAA;AACL,QAAO,OAAA;AAAA,UACL,KAAA,EAAO,cAAc,MAAS,GAAA,kBAAA;AAAA,UAC9B,QAAQ,aAAc,CAAA;AAAA,SACxB;AAAA;AACF,IAEF,KAAK,OAAA;AAEH,MAAA,IAAI,qBAAqB,oBAAsB,EAAA;AAC7C,QAAO,OAAA;AAAA,UACL,KAAA,EAAO,cAAc,MAAS,GAAA,kBAAA;AAAA,UAC9B,QAAQ,aAAc,CAAA;AAAA,SACxB;AAAA,OACK,MAAA;AACL,QAAO,OAAA;AAAA,UACL,OAAO,aAAc,CAAA,KAAA;AAAA,UACrB,MAAA,EAAQ,cAAc,KAAQ,GAAA;AAAA,SAChC;AAAA;AACF,IAEF,KAAK,MAAA;AAEH,MAAO,OAAA;AAAA,QACL,OAAO,aAAc,CAAA,KAAA;AAAA,QACrB,QAAQ,aAAc,CAAA;AAAA,OACxB;AAAA,IAEF;AAEE,MAAO,OAAA;AAAA,QACL,OAAO,WAAY,CAAA,KAAA;AAAA,QACnB,QAAQ,WAAY,CAAA;AAAA,OACtB;AAAA;AAEN;;AC1GsB,eAAA,aAAA,CAAc,SAAiB,QAAiC,EAAA;AAClF,EAAM,MAAA,QAAA,GAAW,MAAM,KAAA,CAAM,OAAO,CAAA;AACpC,EAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,EAAO,OAAA,IAAI,IAAK,CAAA,CAAC,IAAI,CAAA,EAAG,UAAU,EAAE,IAAA,EAAM,IAAK,CAAA,IAAA,EAAM,CAAA;AACvD;AASgB,SAAA,UAAA,CAAW,OAAwB,EAAA,IAAA,EAAc,IAAoB,EAAA;AACnF,EAAA,MAAM,IAAO,GAAA,OAAO,OAAY,KAAA,QAAA,GAAW,IAAI,IAAA,CAAK,CAAC,OAAO,CAAG,EAAA,EAAE,IAAK,EAAC,CAAI,GAAA,OAAA;AAC3E,EAAM,MAAA,GAAA,GAAM,GAAI,CAAA,eAAA,CAAgB,IAAI,CAAA;AAEpC,EAAM,MAAA,CAAA,GAAI,QAAS,CAAA,aAAA,CAAc,GAAG,CAAA;AACpC,EAAA,CAAA,CAAE,IAAO,GAAA,GAAA;AACT,EAAA,CAAA,CAAE,QAAW,GAAA,IAAA;AACb,EAAA,CAAA,CAAE,KAAM,EAAA;AAGR,EAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AACzB;AASsB,eAAA,YAAA,CAAa,KAAa,QAAiC,EAAA;AAC/E,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAA,MAAM,WAAc,GAAA,MAAA,CAAO,GAAI,CAAA,eAAA,CAAgB,IAAI,CAAA;AAEnD,IAAM,MAAA,IAAA,GAAO,QAAS,CAAA,aAAA,CAAc,GAAG,CAAA;AACvC,IAAA,IAAA,CAAK,IAAO,GAAA,WAAA;AACZ,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,IAAA,IAAA,CAAK,KAAM,EAAA;AAGX,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,IAAO,MAAA,CAAA,GAAA,CAAI,gBAAgB,WAAW,CAAA;AAAA,WAC/B,KAAO,EAAA;AACd,IAAQ,OAAA,CAAA,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAC9C,IAAM,MAAA,KAAA;AAAA;AAEV;;ACpDF,eAAsB,uBAAuB,GAA0D,EAAA;AACnG,EAAI,IAAA;AAEF,IAAA,MAAM,WAAW,MAAM,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAGpD,IAAA,MAAM,WAAc,GAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,cAAc,CAAA;AAEvD,IAAI,IAAA,CAAC,aAAoB,OAAA,IAAA;AAGzB,IAAA,IAAI,WAAY,CAAA,UAAA,CAAW,QAAQ,CAAA,EAAU,OAAA,OAAA;AAC7C,IAAA,IAAI,WAAY,CAAA,UAAA,CAAW,QAAQ,CAAA,EAAU,OAAA,OAAA;AAC7C,IAAA,IAAI,WAAY,CAAA,UAAA,CAAW,QAAQ,CAAA,EAAU,OAAA,OAAA;AAG7C,IAAO,OAAA,IAAA;AAAA,WACA,KAAO,EAAA;AACd,IAAQ,OAAA,CAAA,KAAA,CAAM,iBAAiB,KAAK,CAAA;AACpC,IAAO,OAAA,IAAA;AAAA;AAEX;;;;;;;;;;;;;;"}
package/dist/index.mjs ADDED
@@ -0,0 +1,286 @@
1
+ const imageDimensionsCache = {};
2
+ const videoMetaCache = {};
3
+ const audioDurationCache = {};
4
+
5
+ const getAudioDuration = (audioSrc) => {
6
+ if (audioDurationCache[audioSrc]) {
7
+ return Promise.resolve(audioDurationCache[audioSrc]);
8
+ }
9
+ return new Promise((resolve, reject) => {
10
+ const audio = document.createElement("audio");
11
+ audio.preload = "metadata";
12
+ audio.src = audioSrc;
13
+ audio.onloadedmetadata = () => {
14
+ const duration = audio.duration;
15
+ audioDurationCache[audioSrc] = duration;
16
+ resolve(duration);
17
+ };
18
+ audio.onerror = () => {
19
+ reject(new Error("Failed to load audio metadata"));
20
+ };
21
+ });
22
+ };
23
+
24
+ const concurrencyLimit = 5;
25
+ let activeCount = 0;
26
+ const queue = [];
27
+ function runNext() {
28
+ if (queue.length === 0 || activeCount >= concurrencyLimit) return;
29
+ const next = queue.shift();
30
+ if (next) {
31
+ activeCount++;
32
+ next();
33
+ }
34
+ }
35
+ function limit(fn) {
36
+ return new Promise((resolve, reject) => {
37
+ const task = () => {
38
+ fn().then(resolve).catch(reject).finally(() => {
39
+ activeCount--;
40
+ runNext();
41
+ });
42
+ };
43
+ if (activeCount < concurrencyLimit) {
44
+ activeCount++;
45
+ task();
46
+ } else {
47
+ queue.push(task);
48
+ }
49
+ });
50
+ }
51
+
52
+ function loadImageDimensions(url) {
53
+ return new Promise((resolve, reject) => {
54
+ if (typeof document === "undefined") {
55
+ reject(new Error("getImageDimensions() is only available in the browser."));
56
+ return;
57
+ }
58
+ const img = new Image();
59
+ img.onload = () => {
60
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
61
+ };
62
+ img.onerror = reject;
63
+ img.src = url;
64
+ });
65
+ }
66
+ function getImageDimensions(url) {
67
+ if (imageDimensionsCache[url]) {
68
+ return Promise.resolve(imageDimensionsCache[url]);
69
+ }
70
+ return limit(() => loadImageDimensions(url)).then((dimensions) => {
71
+ imageDimensionsCache[url] = dimensions;
72
+ return dimensions;
73
+ });
74
+ }
75
+
76
+ const getVideoMeta = (videoSrc) => {
77
+ if (videoMetaCache[videoSrc]) {
78
+ return Promise.resolve(videoMetaCache[videoSrc]);
79
+ }
80
+ return new Promise((resolve, reject) => {
81
+ const video = document.createElement("video");
82
+ video.preload = "metadata";
83
+ video.src = videoSrc;
84
+ video.onloadedmetadata = () => {
85
+ const meta = {
86
+ width: video.videoWidth,
87
+ height: video.videoHeight,
88
+ duration: video.duration
89
+ };
90
+ videoMetaCache[videoSrc] = meta;
91
+ resolve(meta);
92
+ };
93
+ video.onerror = () => reject(new Error("Failed to load video metadata"));
94
+ });
95
+ };
96
+
97
+ async function getThumbnail(videoUrl, seekTime = 0.1, playbackRate = 1) {
98
+ return new Promise((resolve, reject) => {
99
+ const video = document.createElement("video");
100
+ video.crossOrigin = "anonymous";
101
+ video.muted = true;
102
+ video.playsInline = true;
103
+ video.autoplay = false;
104
+ video.preload = "auto";
105
+ video.playbackRate = playbackRate;
106
+ let timeoutId;
107
+ const cleanup = () => {
108
+ if (video.parentNode) video.remove();
109
+ if (timeoutId) clearTimeout(timeoutId);
110
+ };
111
+ const handleError = () => {
112
+ cleanup();
113
+ reject(new Error(`Failed to load video: ${video.error?.message || "Unknown error"}`));
114
+ };
115
+ const handleSeeked = () => {
116
+ try {
117
+ video.pause();
118
+ const canvas = document.createElement("canvas");
119
+ const width = video.videoWidth || 640;
120
+ const height = video.videoHeight || 360;
121
+ canvas.width = width;
122
+ canvas.height = height;
123
+ const ctx = canvas.getContext("2d");
124
+ if (!ctx) {
125
+ cleanup();
126
+ reject(new Error("Failed to get canvas context"));
127
+ return;
128
+ }
129
+ ctx.drawImage(video, 0, 0, width, height);
130
+ try {
131
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.8);
132
+ cleanup();
133
+ resolve(dataUrl);
134
+ } catch {
135
+ canvas.toBlob((blob) => {
136
+ if (!blob) {
137
+ cleanup();
138
+ reject(new Error("Failed to create Blob"));
139
+ return;
140
+ }
141
+ const blobUrl = URL.createObjectURL(blob);
142
+ cleanup();
143
+ resolve(blobUrl);
144
+ }, "image/jpeg", 0.8);
145
+ }
146
+ } catch (err) {
147
+ cleanup();
148
+ reject(new Error(`Error creating thumbnail: ${err}`));
149
+ }
150
+ };
151
+ video.addEventListener("error", handleError, { once: true });
152
+ video.addEventListener("seeked", handleSeeked, { once: true });
153
+ video.addEventListener("loadedmetadata", () => {
154
+ const playPromise = video.play();
155
+ if (playPromise !== void 0) {
156
+ playPromise.then(() => {
157
+ video.currentTime = seekTime;
158
+ }).catch(() => {
159
+ video.currentTime = seekTime;
160
+ });
161
+ } else {
162
+ video.currentTime = seekTime;
163
+ }
164
+ }, { once: true });
165
+ timeoutId = window.setTimeout(() => {
166
+ cleanup();
167
+ reject(new Error("Video loading timed out"));
168
+ }, 5e3);
169
+ video.src = videoUrl;
170
+ document.body.appendChild(video);
171
+ });
172
+ }
173
+
174
+ function getScaledDimensions(width, height, maxWidth, maxHeight) {
175
+ if (width <= maxWidth && height <= maxHeight) {
176
+ return {
177
+ width: width % 2 === 0 ? width : width - 1,
178
+ height: height % 2 === 0 ? height : height - 1
179
+ };
180
+ }
181
+ const widthRatio = maxWidth / width;
182
+ const heightRatio = maxHeight / height;
183
+ const scale = Math.min(widthRatio, heightRatio);
184
+ let scaledWidth = Math.round(width * scale);
185
+ let scaledHeight = Math.round(height * scale);
186
+ if (scaledWidth % 2 !== 0) {
187
+ scaledWidth -= 1;
188
+ }
189
+ if (scaledHeight % 2 !== 0) {
190
+ scaledHeight -= 1;
191
+ }
192
+ return {
193
+ width: Math.min(scaledWidth, maxWidth),
194
+ height: Math.min(scaledHeight, maxHeight)
195
+ };
196
+ }
197
+ function getObjectFitSize(objectFit, elementSize, containerSize) {
198
+ const elementAspectRatio = elementSize.width / elementSize.height;
199
+ const containerAspectRatio = containerSize.width / containerSize.height;
200
+ switch (objectFit) {
201
+ case "contain":
202
+ if (elementAspectRatio > containerAspectRatio) {
203
+ return {
204
+ width: containerSize.width,
205
+ height: containerSize.width / elementAspectRatio
206
+ };
207
+ } else {
208
+ return {
209
+ width: containerSize.height * elementAspectRatio,
210
+ height: containerSize.height
211
+ };
212
+ }
213
+ case "cover":
214
+ if (elementAspectRatio > containerAspectRatio) {
215
+ return {
216
+ width: containerSize.height * elementAspectRatio,
217
+ height: containerSize.height
218
+ };
219
+ } else {
220
+ return {
221
+ width: containerSize.width,
222
+ height: containerSize.width / elementAspectRatio
223
+ };
224
+ }
225
+ case "fill":
226
+ return {
227
+ width: containerSize.width,
228
+ height: containerSize.height
229
+ };
230
+ default:
231
+ return {
232
+ width: elementSize.width,
233
+ height: elementSize.height
234
+ };
235
+ }
236
+ }
237
+
238
+ async function blobUrlToFile(blobUrl, fileName) {
239
+ const response = await fetch(blobUrl);
240
+ const blob = await response.blob();
241
+ return new File([blob], fileName, { type: blob.type });
242
+ }
243
+ function saveAsFile(content, type, name) {
244
+ const blob = typeof content === "string" ? new Blob([content], { type }) : content;
245
+ const url = URL.createObjectURL(blob);
246
+ const a = document.createElement("a");
247
+ a.href = url;
248
+ a.download = name;
249
+ a.click();
250
+ URL.revokeObjectURL(url);
251
+ }
252
+ async function downloadFile(url, filename) {
253
+ try {
254
+ const response = await fetch(url);
255
+ const blob = await response.blob();
256
+ const downloadUrl = window.URL.createObjectURL(blob);
257
+ const link = document.createElement("a");
258
+ link.href = downloadUrl;
259
+ link.download = filename;
260
+ document.body.appendChild(link);
261
+ link.click();
262
+ document.body.removeChild(link);
263
+ window.URL.revokeObjectURL(downloadUrl);
264
+ } catch (error) {
265
+ console.error("Error downloading file:", error);
266
+ throw error;
267
+ }
268
+ }
269
+
270
+ async function detectMediaTypeFromUrl(url) {
271
+ try {
272
+ const response = await fetch(url, { method: "HEAD" });
273
+ const contentType = response.headers.get("Content-Type");
274
+ if (!contentType) return null;
275
+ if (contentType.startsWith("image/")) return "image";
276
+ if (contentType.startsWith("video/")) return "video";
277
+ if (contentType.startsWith("audio/")) return "audio";
278
+ return null;
279
+ } catch (error) {
280
+ console.error("Fetch failed:", error);
281
+ return null;
282
+ }
283
+ }
284
+
285
+ export { blobUrlToFile, detectMediaTypeFromUrl, downloadFile, getAudioDuration, getImageDimensions, getObjectFitSize, getScaledDimensions, getThumbnail, getVideoMeta, limit, saveAsFile };
286
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@twick/media-utils",
3
+ "version": "0.0.1",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "sideEffects": false,
8
+ "license": "Apache-2.0",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "vite build",
14
+ "dev": "vite build --watch",
15
+ "lint": "eslint src/",
16
+ "clean": "rimraf .turbo node_modules dist",
17
+ "docs": "typedoc --out docs src/index.ts"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.11.24",
21
+ "typescript": "5.4.2",
22
+ "vite": "^5.1.4",
23
+ "vite-plugin-dts": "^3.7.3",
24
+ "rimraf": "^5.0.5",
25
+ "typedoc": "^0.25.8",
26
+ "typedoc-plugin-markdown": "^3.17.1"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^18.0.0",
30
+ "react-dom": "^18.0.0"
31
+ }
32
+ }