@twick/media-utils 0.14.11 → 0.14.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ import { getObjectFitSize } from './dimension-handler';
10
10
  import { getScaledDimensions } from './dimension-handler';
11
11
  import { getThumbnail } from './get-thumbnail';
12
12
  import { getVideoMeta } from './get-video-metadata';
13
+ import { hasAudio } from './audio-utils';
13
14
  import { limit } from './limit';
14
15
  import { loadFile } from './file-helper';
15
16
  import { Position } from './types';
@@ -41,6 +42,8 @@ export { getThumbnail }
41
42
 
42
43
  export { getVideoMeta }
43
44
 
45
+ export { hasAudio }
46
+
44
47
  export { limit }
45
48
 
46
49
  export { loadFile }
package/dist/index.js CHANGED
@@ -203,6 +203,12 @@ const extractAudio = async ({
203
203
  const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
204
204
  if (!isSafeUrl) throw new Error("Unsafe media source URL");
205
205
  const audioBuffer = await fetchAndDecodeAudio(src);
206
+ if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
207
+ throw new Error("No audio track found in the media source");
208
+ }
209
+ if (isAudioSilent(audioBuffer)) {
210
+ throw new Error("Audio track is silent (no audio content detected)");
211
+ }
206
212
  const clampedStart = Math.max(0, start || 0);
207
213
  const fullDuration = audioBuffer.duration;
208
214
  const clampedEnd = Math.min(
@@ -220,6 +226,23 @@ const extractAudio = async ({
220
226
  const mp3Blob = await audioBufferToMp3(renderedBuffer);
221
227
  return URL.createObjectURL(mp3Blob);
222
228
  };
229
+ const hasAudio = async (src) => {
230
+ if (!src) return false;
231
+ const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
232
+ if (!isSafeUrl) return false;
233
+ try {
234
+ const audioBuffer = await fetchAndDecodeAudio(src);
235
+ if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
236
+ return false;
237
+ }
238
+ if (isAudioSilent(audioBuffer)) {
239
+ return false;
240
+ }
241
+ return true;
242
+ } catch (error) {
243
+ return false;
244
+ }
245
+ };
223
246
  const stitchAudio = async (segments, totalDuration) => {
224
247
  if (!segments || segments.length === 0) {
225
248
  throw new Error("At least one audio segment is required");
@@ -244,13 +267,24 @@ const decodeAudioData = async (arrayBuffer) => {
244
267
  audioContext.decodeAudioData(
245
268
  arrayBuffer.slice(0),
246
269
  (buf) => resolve(buf),
247
- (err) => reject(err || new Error("Failed to decode audio"))
270
+ (err) => reject(err || new Error("Failed to decode audio: no audio track found or unsupported format"))
248
271
  );
249
272
  });
250
273
  } finally {
251
274
  audioContext.close();
252
275
  }
253
276
  };
277
+ const isAudioSilent = (buffer, threshold = 1e-3) => {
278
+ for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
279
+ const channelData = buffer.getChannelData(channel);
280
+ for (let i = 0; i < channelData.length; i += 100) {
281
+ if (Math.abs(channelData[i]) > threshold) {
282
+ return false;
283
+ }
284
+ }
285
+ }
286
+ return true;
287
+ };
254
288
  const renderAudioSegment = async (audioBuffer, start, end, playbackRate) => {
255
289
  const OfflineAudioContextCtor = window.OfflineAudioContext || window.webkitOfflineAudioContext;
256
290
  if (!OfflineAudioContextCtor) throw new Error("OfflineAudioContext not supported");
@@ -585,6 +619,7 @@ exports.getObjectFitSize = getObjectFitSize;
585
619
  exports.getScaledDimensions = getScaledDimensions;
586
620
  exports.getThumbnail = getThumbnail;
587
621
  exports.getVideoMeta = getVideoMeta;
622
+ exports.hasAudio = hasAudio;
588
623
  exports.limit = limit;
589
624
  exports.loadFile = loadFile;
590
625
  exports.saveAsFile = saveAsFile;
package/dist/index.js.map CHANGED
@@ -1 +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/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 for better performance.\n * The function creates a temporary audio element, loads only metadata, and extracts\n * the duration without downloading the entire audio file.\n *\n * @param audioSrc - The source URL of the audio file\n * @returns Promise resolving to the duration of the audio in seconds\n * \n * @example\n * ```js\n * // Get duration of an MP3 file\n * const duration = await getAudioDuration(\"https://example.com/audio.mp3\");\n * // duration = 180.5 (3 minutes and 0.5 seconds)\n * \n * // Get duration of a local blob URL\n * const duration = await getAudioDuration(\"blob:http://localhost:3000/abc123\");\n * // duration = 45.2\n * ```\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 * Internal helper function that manages the execution of queued tasks.\n * Dequeues and executes the next task when a concurrency slot becomes available.\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 the concurrency limit is reached, the function is queued and executed later\n * when a slot becomes available. This prevents overwhelming the system with too\n * many concurrent operations, which is useful for resource-intensive tasks like\n * media processing or API calls.\n * \n * @param fn - Async function returning a Promise that should be executed with concurrency control\n * @returns Promise resolving with the result of the wrapped function\n * \n * @example\n * ```js\n * // Limit concurrent image processing operations\n * const processImage = async (imageUrl) => {\n * // Expensive image processing operation\n * return await someImageProcessing(imageUrl);\n * };\n * \n * // Process multiple images with concurrency limit\n * const results = await Promise.all([\n * limit(() => processImage(\"image1.jpg\")),\n * limit(() => processImage(\"image2.jpg\")),\n * limit(() => processImage(\"image3.jpg\")),\n * limit(() => processImage(\"image4.jpg\")),\n * limit(() => processImage(\"image5.jpg\")),\n * limit(() => processImage(\"image6.jpg\")), // This will be queued until a slot opens\n * ]);\n * ```\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 * Internal helper function that creates a temporary Image element to extract\n * the natural width and height of an image without displaying it.\n *\n * @param url - The image URL to load\n * @returns Promise resolving with the image's natural 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, and employs\n * a concurrency limiter to control resource usage and prevent overwhelming\n * the browser with too many simultaneous image loads.\n *\n * @param url - The URL of the image to analyze\n * @returns Promise resolving to an object containing width and height\n * \n * @example\n * ```js\n * // Get dimensions of a remote image\n * const dimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // dimensions = { width: 1920, height: 1080 }\n * \n * // Get dimensions of a local blob URL\n * const dimensions = await getImageDimensions(\"blob:http://localhost:3000/abc123\");\n * // dimensions = { width: 800, height: 600 }\n * \n * // Subsequent calls for the same URL will use cache\n * const cachedDimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // Returns immediately from cache without reloading\n * ```\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 * Uses a cache to avoid reloading the same video multiple times for better performance.\n * The function creates a temporary video element, loads only metadata, and extracts\n * the video properties without downloading the entire file.\n *\n * @param videoSrc - The URL or path to the video file\n * @returns Promise resolving to an object containing video metadata\n * \n * @example\n * ```js\n * // Get metadata for a video\n * const metadata = await getVideoMeta(\"https://example.com/video.mp4\");\n * // metadata = { width: 1920, height: 1080, duration: 120.5 }\n * \n * // Get metadata for a local blob URL\n * const metadata = await getVideoMeta(\"blob:http://localhost:3000/abc123\");\n * // metadata = { width: 1280, height: 720, duration: 30.0 }\n * ```\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 * Creates a hidden video element in the browser, seeks to the specified time,\n * and captures the frame into a canvas, which is then exported as a JPEG data URL or Blob URL.\n * The function handles video loading, seeking, frame capture, and cleanup automatically.\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\n * @param playbackRate - Playback speed for the video\n * @returns Promise resolving to a thumbnail image URL\n * \n * @example\n * ```js\n * // Extract thumbnail at 5 seconds\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 5);\n * // thumbnail is a data URL like \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...\"\n * \n * // Extract thumbnail with custom playback rate\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 2.5, 1.5);\n * ```\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 }, 15000);\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 between start and end times,\n * rendered at the specified playback rate, and returns a Blob URL to an MP3 file.\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 * @param src - The source URL of the media file\n * @param playbackRate - The playback rate for the extracted segment\n * @param start - The start time in seconds\n * @param end - The end time in seconds\n * @returns Promise resolving to a Blob URL to the extracted MP3 file\n * \n * @example\n * ```js\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 * ```\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\n * @returns Promise resolving to a Blob URL to the stitched MP3 file\n * \n * @example\n * ```js\n * const segments = [\n * { src: \"audio1.mp3\", s: 0, e: 5, volume: 1.0 },\n * { src: \"audio2.mp3\", s: 10, e: 15, volume: 0.8 }\n * ];\n * const url = await stitchAudio(segments, 15);\n * // Creates a 15-second audio file with segments at specified times\n * ```\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 * \n * @param src - The URL of the audio file to fetch and decode\n * @returns Promise<AudioBuffer> - The decoded audio buffer\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 while maintaining aspect ratio.\n * Ensures the resulting dimensions are even numbers and fit within the specified bounds.\n * If the original dimensions are already smaller than the max values, returns the original dimensions.\n *\n * @param width - The original width of the element in pixels\n * @param height - The original height of the element in pixels\n * @param maxWidth - The maximum allowed width of the container in pixels\n * @param maxHeight - The maximum allowed height of the container in pixels\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Scale down a large image to fit in a smaller container\n * const scaled = getScaledDimensions(1920, 1080, 800, 600);\n * // scaled = { width: 800, height: 450 }\n * \n * // Small image that doesn't need scaling\n * const scaled = getScaledDimensions(400, 300, 800, 600);\n * // scaled = { width: 400, height: 300 }\n * \n * // Ensure even dimensions for video encoding\n * const scaled = getScaledDimensions(1001, 1001, 1000, 1000);\n * // scaled = { width: 1000, height: 1000 }\n * ```\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 * Implements CSS object-fit behavior for programmatic dimension calculations.\n * Useful for responsive design and media scaling applications.\n *\n * @param objectFit - The object-fit behavior\n * @param elementSize - The original size of the element\n * @param containerSize - The size of the container\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Contain: fit entire element inside container\n * const contained = getObjectFitSize(\"contain\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // contained = { width: 400, height: 200 }\n * \n * // Cover: fill container while maintaining aspect ratio\n * const covered = getObjectFitSize(\"cover\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // covered = { width: 600, height: 300 }\n * \n * // Fill: stretch to completely fill container\n * const filled = getObjectFitSize(\"fill\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // filled = { width: 400, height: 300 }\n * ```\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 * Fetches the blob data from the URL and creates a new File object with the specified name.\n * Useful for converting blob URLs back to File objects for upload or processing.\n *\n * @param blobUrl - The Blob URL to convert\n * @param fileName - The name to assign to the resulting File object\n * @returns Promise resolving to a File object with the blob data\n * \n * @example\n * ```js\n * const file = await blobUrlToFile(\"blob:http://localhost:3000/abc123\", \"image.jpg\");\n * // file is now a File object that can be uploaded or processed\n * ```\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 * Opens a native file picker and resolves with the selected File.\n * The accepted file types can be specified using the same format as the\n * input accept attribute (e.g. \"application/json\", \".png,.jpg\", \"image/*\").\n *\n * @param accept - The accept filter string for the file input\n * @returns Promise resolving to the chosen File\n * \n * @example\n * ```ts\n * const file = await loadFile(\"application/json\");\n * const text = await file.text();\n * const data = JSON.parse(text);\n * ```\n */\n export const loadFile = (accept: string): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n try {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = accept;\n input.style.display = \"none\";\n document.body.appendChild(input);\n\n const cleanup = () => {\n // Clear the value so the same file can be picked again next time\n input.value = \"\";\n document.body.removeChild(input);\n };\n\n input.onchange = () => {\n const file = input.files && input.files[0];\n cleanup();\n if (!file) {\n reject(new Error(\"No file selected\"));\n return;\n }\n resolve(file);\n };\n\n // Some browsers need a small timeout to ensure the element is attached\n // before programmatic click, but generally this works without it.\n input.click();\n } catch (error) {\n reject(error as Error);\n }\n });\n };\n \n /**\n * Triggers a download of a file from a string or Blob.\n * Creates a temporary download link and automatically clicks it to initiate the download.\n * The function handles both string content and Blob objects, and automatically cleans up\n * the created object URL after the download is initiated.\n *\n * @param content - The content to save, either a string or a Blob object\n * @param type - The MIME type of the content\n * @param name - The name of the file to be saved\n * \n * @example\n * ```js\n * // Download text content\n * saveAsFile(\"Hello World\", \"text/plain\", \"hello.txt\");\n * \n * // Download JSON data\n * saveAsFile(JSON.stringify({data: \"value\"}), \"application/json\", \"data.json\");\n * \n * // Download blob content\n * saveAsFile(imageBlob, \"image/png\", \"screenshot.png\");\n * ```\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 * Fetches the file content from the provided URL and creates a download link\n * to save it locally. The function handles the entire download process including\n * fetching, blob creation, and cleanup of temporary resources.\n *\n * @param url - The URL of the file to download\n * @param filename - The name of the file to be saved locally\n * @returns Promise resolving when the download is initiated\n * \n * @example\n * ```js\n * await downloadFile(\"https://example.com/image.jpg\", \"downloaded-image.jpg\");\n * // Browser will automatically download the file with the specified name\n * ```\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 ","/**\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\n * Uses a lightweight HEAD request to fetch only the headers, avoiding download of the full file.\n * The function analyzes the Content-Type header to determine the media type category.\n *\n * @param url - The URL of the media file to analyze\n * @returns Promise resolving to the detected media type or null\n * \n * @example\n * ```js\n * // Detect image type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/image.jpg\");\n * // type = \"image\"\n * \n * // Detect video type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/video.mp4\");\n * // type = \"video\"\n * \n * // Detect audio type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/audio.mp3\");\n * // type = \"audio\"\n * \n * // Invalid or inaccessible URL\n * const type = await detectMediaTypeFromUrl(\"https://example.com/invalid\");\n * // type = null\n * ```\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":";;;;AAEO,MAAM,uBAAmD,EAAC;AAC1D,MAAM,iBAA4C,EAAC;AACnD,MAAM,qBAA6C,EAAC;;ACkBpD,MAAM,gBAAA,GAAmB,CAAC,QAAA,KAAsC;AAErE,EAAA,IAAI,kBAAA,CAAmB,QAAQ,CAAA,EAAG;AAChC,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAAA,EACrD;AAEA,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,OAAA,GAAU,UAAA;AAEhB,IAAA,MAAM,SAAA,GAAY,gCAAA,CAAiC,IAAA,CAAK,QAAQ,CAAA;AAChE,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAAA,IAC3C;AACA,IAAA,KAAA,CAAM,GAAA,GAAM,QAAA;AAGZ,IAAA,KAAA,CAAM,mBAAmB,MAAM;AAC7B,MAAA,MAAM,WAAW,KAAA,CAAM,QAAA;AACvB,MAAA,kBAAA,CAAmB,QAAQ,CAAA,GAAI,QAAA;AAC/B,MAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAClB,CAAA;AAGA,IAAA,KAAA,CAAM,UAAU,MAAM;AACpB,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,+BAA+B,CAAC,CAAA;AAAA,IACnD,CAAA;AAAA,EACF,CAAC,CAAA;AACH;;ACjDA,MAAM,gBAAA,GAAmB,CAAA;AAGzB,IAAI,WAAA,GAAc,CAAA;AAGlB,MAAM,QAA2B,EAAC;AAOlC,SAAS,OAAA,GAAU;AAEjB,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,WAAA,IAAe,gBAAA,EAAkB;AAG3D,EAAA,MAAM,IAAA,GAAO,MAAM,KAAA,EAAM;AAEzB,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,WAAA,EAAA;AACA,IAAA,IAAA,EAAK;AAAA,EACP;AACF;AA+BO,SAAS,MAAS,EAAA,EAAkC;AACzD,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AAEtC,IAAA,MAAM,OAAO,MAAM;AACjB,MAAA,EAAA,EAAG,CACA,KAAK,OAAO,CAAA,CACZ,MAAM,MAAM,CAAA,CACZ,QAAQ,MAAM;AACb,QAAA,WAAA,EAAA;AACA,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AAAA,IACL,CAAA;AAEA,IAAA,IAAI,cAAc,gBAAA,EAAkB;AAClC,MAAA,WAAA,EAAA;AACA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA,MAAO;AAEL,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,IACjB;AAAA,EACF,CAAC,CAAA;AACH;;ACjEA,MAAM,mBAAA,GAAsB,CAAC,GAAA,KAAqC;AAChE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACnC,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,wDAAwD,CAAC,CAAA;AAC1E,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,IAAI,KAAA,EAAM;AACtB,IAAA,GAAA,CAAI,SAAS,MAAM;AACjB,MAAA,OAAA,CAAQ,EAAE,KAAA,EAAO,GAAA,CAAI,cAAc,MAAA,EAAQ,GAAA,CAAI,eAAe,CAAA;AAAA,IAChE,CAAA;AACA,IAAA,GAAA,CAAI,OAAA,GAAU,MAAA;AACd,IAAA,GAAA,CAAI,GAAA,GAAM,GAAA;AAAA,EACZ,CAAC,CAAA;AACH,CAAA;AA0BO,MAAM,kBAAA,GAAqB,CAAC,GAAA,KAAqC;AAEtE,EAAA,IAAI,oBAAA,CAAqB,GAAG,CAAA,EAAG;AAC7B,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,oBAAA,CAAqB,GAAG,CAAC,CAAA;AAAA,EAClD;AAGA,EAAA,OAAO,KAAA,CAAM,MAAM,mBAAA,CAAoB,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,UAAA,KAAe;AAChE,IAAA,oBAAA,CAAqB,GAAG,CAAA,GAAI,UAAA;AAC5B,IAAA,OAAO,UAAA;AAAA,EACT,CAAC,CAAA;AACH;;ACxCO,MAAM,YAAA,GAAe,CAAC,QAAA,KAAyC;AAEpE,EAAA,IAAI,cAAA,CAAe,QAAQ,CAAA,EAAG;AAC5B,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,cAAA,CAAe,QAAQ,CAAC,CAAA;AAAA,EACjD;AAEA,EAAA,OAAO,IAAI,OAAA,CAAmB,CAAC,OAAA,EAAS,MAAA,KAAW;AACjD,IAAA,MAAM,KAAA,GAA0B,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC9D,IAAA,KAAA,CAAM,OAAA,GAAU,UAAA;AAEhB,IAAA,MAAM,SAAA,GAAY,gCAAA,CAAiC,IAAA,CAAK,QAAQ,CAAA;AAChE,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,yBAAyB,CAAC,CAAA;AAC3C,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,GAAA,GAAM,QAAA;AAGZ,IAAA,KAAA,CAAM,mBAAmB,MAAM;AAC7B,MAAA,MAAM,IAAA,GAAkB;AAAA,QACtB,OAAO,KAAA,CAAM,UAAA;AAAA,QACb,QAAQ,KAAA,CAAM,WAAA;AAAA,QACd,UAAU,KAAA,CAAM;AAAA,OAClB;AACA,MAAA,cAAA,CAAe,QAAQ,CAAA,GAAI,IAAA;AAC3B,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,IACd,CAAA;AAGA,IAAA,KAAA,CAAM,UAAU,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,+BAA+B,CAAC,CAAA;AAAA,EACzE,CAAC,CAAA;AACH;;ACjCO,MAAM,eAAe,OACxB,QAAA,EACA,QAAA,GAAW,GAAA,EACX,eAAe,CAAA,KACK;AACpB,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,WAAA,GAAc,WAAA;AACpB,IAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,IAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AACpB,IAAA,KAAA,CAAM,QAAA,GAAW,KAAA;AACjB,IAAA,KAAA,CAAM,OAAA,GAAU,MAAA;AAChB,IAAA,KAAA,CAAM,YAAA,GAAe,YAAA;AAGrB,IAAA,KAAA,CAAM,MAAM,QAAA,GAAW,UAAA;AACvB,IAAA,KAAA,CAAM,MAAM,IAAA,GAAO,SAAA;AACnB,IAAA,KAAA,CAAM,MAAM,GAAA,GAAM,SAAA;AAClB,IAAA,KAAA,CAAM,MAAM,KAAA,GAAQ,KAAA;AACpB,IAAA,KAAA,CAAM,MAAM,MAAA,GAAS,KAAA;AACrB,IAAA,KAAA,CAAM,MAAM,OAAA,GAAU,GAAA;AACtB,IAAA,KAAA,CAAM,MAAM,aAAA,GAAgB,MAAA;AAC5B,IAAA,KAAA,CAAM,MAAM,MAAA,GAAS,IAAA;AAErB,IAAA,IAAI,SAAA;AAGJ,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,IAAI,KAAA,CAAM,UAAA,EAAY,KAAA,CAAM,MAAA,EAAO;AACnC,MAAA,IAAI,SAAA,eAAwB,SAAS,CAAA;AAAA,IACvC,CAAA;AAGA,IAAA,MAAM,cAAc,MAAM;AACxB,MAAA,OAAA,EAAQ;AACR,MAAA,MAAA,CAAO,IAAI,MAAM,CAAA,sBAAA,EAAyB,KAAA,CAAM,OAAO,OAAA,IAAW,eAAe,EAAE,CAAC,CAAA;AAAA,IACtF,CAAA;AAGA,IAAA,MAAM,eAAe,MAAM;AACzB,MAAA,IAAI;AACF,QAAA,KAAA,CAAM,KAAA,EAAM;AAEZ,QAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,QAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,IAAc,GAAA;AAClC,QAAA,MAAM,MAAA,GAAS,MAAM,WAAA,IAAe,GAAA;AACpC,QAAA,MAAA,CAAO,KAAA,GAAQ,KAAA;AACf,QAAA,MAAA,CAAO,MAAA,GAAS,MAAA;AAEhB,QAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,GAAA,EAAK;AACR,UAAA,OAAA,EAAQ;AACR,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,8BAA8B,CAAC,CAAA;AAChD,UAAA;AAAA,QACF;AAGA,QAAA,GAAA,CAAI,SAAA,CAAU,KAAA,EAAO,CAAA,EAAG,CAAA,EAAG,OAAO,MAAM,CAAA;AAGxC,QAAA,IAAI;AACF,UAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,YAAA,EAAc,GAAG,CAAA;AAClD,UAAA,OAAA,EAAQ;AACR,UAAA,OAAA,CAAQ,OAAO,CAAA;AAAA,QACjB,CAAA,CAAA,MAAQ;AAEN,UAAA,MAAA,CAAO,MAAA,CAAO,CAAC,IAAA,KAAS;AACtB,YAAA,IAAI,CAAC,IAAA,EAAM;AACT,cAAA,OAAA,EAAQ;AACR,cAAA,MAAA,CAAO,IAAI,KAAA,CAAM,uBAAuB,CAAC,CAAA;AACzC,cAAA;AAAA,YACF;AACA,YAAA,MAAM,OAAA,GAAU,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACxC,YAAA,OAAA,EAAQ;AACR,YAAA,OAAA,CAAQ,OAAO,CAAA;AAAA,UACjB,CAAA,EAAG,cAAc,GAAG,CAAA;AAAA,QACtB;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,OAAA,EAAQ;AACR,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,GAAG,EAAE,CAAC,CAAA;AAAA,MACtD;AAAA,IACF,CAAA;AAEA,IAAA,KAAA,CAAM,iBAAiB,OAAA,EAAS,WAAA,EAAa,EAAE,IAAA,EAAM,MAAM,CAAA;AAC3D,IAAA,KAAA,CAAM,iBAAiB,QAAA,EAAU,YAAA,EAAc,EAAE,IAAA,EAAM,MAAM,CAAA;AAG7D,IAAA,KAAA,CAAM,gBAAA,CAAiB,kBAAkB,MAAM;AAC7C,MAAA,MAAM,WAAA,GAAc,MAAM,IAAA,EAAK;AAC/B,MAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,QAAA,WAAA,CACG,KAAK,MAAM;AACV,UAAA,KAAA,CAAM,WAAA,GAAc,QAAA;AAAA,QACtB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,UAAA,KAAA,CAAM,WAAA,GAAc,QAAA;AAAA,QACtB,CAAC,CAAA;AAAA,MACL,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,WAAA,GAAc,QAAA;AAAA,MACtB;AAAA,IACF,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AAGjB,IAAA,SAAA,GAAY,MAAA,CAAO,WAAW,MAAM;AAClC,MAAA,OAAA,EAAQ;AACR,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,yBAAyB,CAAC,CAAA;AAAA,IAC7C,GAAG,IAAK,CAAA;AAGR,IAAA,KAAA,CAAM,GAAA,GAAM,QAAA;AACZ,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAAA,EACjC,CAAC,CAAA;AACH;;ACtGK,MAAM,eAAe,OAAO;AAAA,EACjC,GAAA;AAAA,EACA,YAAA,GAAe,CAAA;AAAA,EACf,KAAA,GAAQ,CAAA;AAAA,EACR;AACF,CAAA,KAKuB;AACrB,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAC3C,EAAA,IAAI,YAAA,IAAgB,CAAA,EAAG,MAAM,IAAI,MAAM,0BAA0B,CAAA;AAGjE,EAAA,MAAM,SAAA,GAAY,yBAAA,CAA0B,IAAA,CAAK,GAAG,CAAA;AACpD,EAAA,IAAI,CAAC,SAAA,EAAW,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAGzD,EAAA,MAAM,WAAA,GAAc,MAAM,mBAAA,CAAoB,GAAG,CAAA;AAGjD,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAS,CAAC,CAAA;AAC3C,EAAA,MAAM,eAAe,WAAA,CAAY,QAAA;AACjC,EAAA,MAAM,aAAa,IAAA,CAAK,GAAA;AAAA,IACtB,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM,YAAA;AAAA,IAChC;AAAA,GACF;AACA,EAAA,IAAI,UAAA,IAAc,YAAA;AAChB,IAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAGjE,EAAA,MAAM,iBAAiB,MAAM,kBAAA;AAAA,IAC3B,WAAA;AAAA,IACA,YAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AAGA,EAAA,MAAM,OAAA,GAAU,MAAM,gBAAA,CAAiB,cAAc,CAAA;AACrD,EAAA,OAAO,GAAA,CAAI,gBAAgB,OAAO,CAAA;AACpC;AAqBO,MAAM,WAAA,GAAc,OACzB,QAAA,EACA,aAAA,KACoB;AACpB,EAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AAGA,EAAA,MAAM,QAAA,GAAW,aAAA,IAAiB,IAAA,CAAK,GAAA,CAAI,GAAG,SAAS,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,CAAC,CAAC,CAAA;AAGpE,EAAA,MAAM,cAAA,GAAiB,MAAM,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAGnE,EAAA,MAAM,OAAA,GAAU,MAAM,gBAAA,CAAiB,cAAc,CAAA;AACrD,EAAA,OAAO,GAAA,CAAI,gBAAgB,OAAO,CAAA;AACpC;AAUA,MAAM,mBAAA,GAAsB,OAAO,GAAA,KAAsC;AACvE,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAE9E,EAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,gBAAgB,WAAW,CAAA;AACpC,CAAA;AAKA,MAAM,eAAA,GAAkB,OAAO,WAAA,KAAmD;AAChF,EAAA,MAAM,gBAAA,GACH,MAAA,CAAe,YAAA,IAAiB,MAAA,CAAe,kBAAA;AAClD,EAAA,IAAI,CAAC,gBAAA,EAAkB,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAEpE,EAAA,MAAM,YAAA,GAAe,IAAI,gBAAA,EAAiB;AAC1C,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,IAAI,OAAA,CAAqB,CAAC,SAAS,MAAA,KAAW;AACzD,MAAA,YAAA,CAAa,eAAA;AAAA,QACX,WAAA,CAAY,MAAM,CAAC,CAAA;AAAA,QACnB,CAAC,GAAA,KAAQ,OAAA,CAAQ,GAAG,CAAA;AAAA,QACpB,CAAC,GAAA,KAAQ,MAAA,CAAO,OAAO,IAAI,KAAA,CAAM,wBAAwB,CAAC;AAAA,OAC5D;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,KAAA,EAAM;AAAA,EACrB;AACF,CAAA;AAKA,MAAM,kBAAA,GAAqB,OACzB,WAAA,EACA,KAAA,EACA,KACA,YAAA,KACyB;AACzB,EAAA,MAAM,uBAAA,GACH,MAAA,CAAe,mBAAA,IAAwB,MAAA,CAAe,yBAAA;AACzD,EAAA,IAAI,CAAC,uBAAA,EAAyB,MAAM,IAAI,MAAM,mCAAmC,CAAA;AAEjF,EAAA,MAAM,aAAa,WAAA,CAAY,UAAA;AAC/B,EAAA,MAAM,cAAc,WAAA,CAAY,gBAAA;AAChC,EAAA,MAAM,iBAAiB,GAAA,GAAM,KAAA;AAC7B,EAAA,MAAM,iBAAiB,IAAA,CAAK,GAAA;AAAA,IAC1B,CAAA;AAAA,IACA,IAAA,CAAK,IAAA,CAAM,cAAA,GAAiB,YAAA,GAAgB,UAAU;AAAA,GACxD;AAEA,EAAA,MAAM,OAAA,GAAU,IAAI,uBAAA,CAAwB,WAAA,EAAa,gBAAgB,UAAU,CAAA;AACnF,EAAA,MAAM,UAAA,GAAa,QAAQ,kBAAA,EAAmB;AAC9C,EAAA,UAAA,CAAW,MAAA,GAAS,WAAA;AACpB,EAAA,UAAA,CAAW,aAAa,KAAA,GAAQ,YAAA;AAChC,EAAA,UAAA,CAAW,OAAA,CAAQ,QAAQ,WAAW,CAAA;AACtC,EAAA,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,KAAA,EAAO,cAAc,CAAA;AAEzC,EAAA,OAAO,MAAM,QAAQ,cAAA,EAAe;AACtC,CAAA;AAKA,MAAM,mBAAA,GAAsB,OAC1B,QAAA,EACA,QAAA,KACyB;AACzB,EAAA,MAAM,uBAAA,GACH,MAAA,CAAe,mBAAA,IAAwB,MAAA,CAAe,yBAAA;AACzD,EAAA,IAAI,CAAC,uBAAA,EAAyB,MAAM,IAAI,MAAM,mCAAmC,CAAA;AAEjF,EAAA,MAAM,UAAA,GAAa,KAAA;AACnB,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,IAAA,CAAK,QAAA,GAAW,UAAU,CAAA;AACnD,EAAA,MAAM,OAAA,GAAU,IAAI,uBAAA,CAAwB,CAAA,EAAG,aAAa,UAAU,CAAA;AAGtE,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA,EAAG;AAC1B,MAAA,OAAA,CAAQ,KAAK,CAAA,wBAAA,EAA2B,OAAA,CAAQ,CAAC,CAAA,UAAA,EAAa,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAC1E,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,CAAA;AACjC,IAAA,IAAI,UAAU,CAAA,EAAG;AACf,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,wBAAA,EAA2B,OAAA,CAAQ,GAAG,CAAA,CAAE,CAAA;AACrD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,GAAc,MAAM,mBAAA,CAAoB,OAAA,CAAQ,GAAG,CAAA;AACzD,MAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,CAAA,GAAI,OAAA,CAAQ,CAAA;AAC5C,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,GAAA,CAAI,eAAA,EAAiB,YAAY,QAAQ,CAAA;AAErE,MAAA,MAAM,MAAA,GAAS,QAAQ,kBAAA,EAAmB;AAC1C,MAAA,MAAA,CAAO,MAAA,GAAS,WAAA;AAGhB,MAAA,IAAI,WAAW,CAAA,EAAG;AAChB,QAAA,MAAM,QAAA,GAAW,QAAQ,UAAA,EAAW;AACpC,QAAA,QAAA,CAAS,KAAK,KAAA,GAAQ,MAAA;AACtB,QAAA,MAAA,CAAO,QAAQ,QAAQ,CAAA;AACvB,QAAA,QAAA,CAAS,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,MACtC,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,MACpC;AAEA,MAAA,MAAA,CAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,EAAG,CAAA,EAAG,cAAc,CAAA;AAAA,IAC3C,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,2BAAA,EAA8B,OAAA,CAAQ,GAAG,IAAI,KAAK,CAAA;AAAA,IACjE;AAAA,EACF;AAEA,EAAA,OAAO,MAAM,QAAQ,cAAA,EAAe;AACtC,CAAA;AAKA,MAAM,gBAAA,GAAmB,OAAO,MAAA,KAAuC;AACrE,EAAA,IAAI;AAEF,IAAA,MAAM,cAAA,GAAiB,4BAA4B,MAAM,CAAA;AAGzD,IAAA,MAAM,SAAA,GAAY,MAAM,eAAA,CAAgB,cAAc,CAAA;AAGtD,IAAA,OAAO,MAAM,eAAe,SAAS,CAAA;AAAA,EACvC,SAAS,KAAA,EAAO;AAEd,IAAA,OAAO,qBAAqB,MAAM,CAAA;AAAA,EACpC;AACF,CAAA;AAKA,MAAM,2BAAA,GAA8B,CAAC,MAAA,KAAqC;AACxE,EAAA,MAAM,cAAc,MAAA,CAAO,gBAAA;AAC3B,EAAA,MAAM,aAAa,MAAA,CAAO,UAAA;AAC1B,EAAA,MAAM,YAAY,MAAA,CAAO,MAAA;AAGzB,EAAA,MAAM,WAAA,GAAc,UAAA,CAAW,MAAA,EAAQ,WAAA,EAAa,SAAS,CAAA;AAG7D,EAAA,MAAM,cAAA,GAAiB,CAAA;AACvB,EAAA,MAAM,aAAa,WAAA,GAAc,cAAA;AACjC,EAAA,MAAM,WAAW,UAAA,GAAa,UAAA;AAC9B,EAAA,MAAM,QAAA,GAAW,YAAY,MAAA,GAAS,cAAA;AACtC,EAAA,MAAM,aAAa,EAAA,GAAK,QAAA;AACxB,EAAA,MAAM,WAAA,GAAc,IAAI,WAAA,CAAY,UAAU,CAAA;AAC9C,EAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,WAAW,CAAA;AAGrC,EAAA,WAAA,CAAY,IAAA,EAAM,GAAG,MAAM,CAAA;AAC3B,EAAA,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,EAAA,GAAK,QAAA,EAAU,IAAI,CAAA;AACrC,EAAA,WAAA,CAAY,IAAA,EAAM,GAAG,MAAM,CAAA;AAG3B,EAAA,WAAA,CAAY,IAAA,EAAM,IAAI,MAAM,CAAA;AAC5B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,EAAA,EAAI,IAAI,CAAA;AAC3B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,CAAA,EAAG,IAAI,CAAA;AAC1B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,WAAA,EAAa,IAAI,CAAA;AACpC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,UAAA,EAAY,IAAI,CAAA;AACnC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,QAAA,EAAU,IAAI,CAAA;AACjC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,UAAA,EAAY,IAAI,CAAA;AACnC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,EAAA,EAAI,IAAI,CAAA;AAG3B,EAAA,WAAA,CAAY,IAAA,EAAM,IAAI,MAAM,CAAA;AAC5B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,QAAA,EAAU,IAAI,CAAA;AAGjC,EAAA,eAAA,CAAgB,IAAA,EAAM,IAAI,WAAW,CAAA;AAErC,EAAA,OAAO,WAAA;AACT,CAAA;AAKA,MAAM,cAAA,GAAiB,OAAO,MAAA,KAAuC;AACnE,EAAA,MAAM,MAAA,GAAS,MAAM,qCAAO,sBAAQ,qBAAA;AAEpC,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,gBAAA,IAAoB,CAAA,GAAI,CAAA,GAAI,CAAA;AAEpD,EAAA,MAAM,gBAAA,GAAmB,KAAA;AACzB,EAAA,MAAM,iBAAA,GAAoB,qBAAA,CAAsB,MAAA,EAAQ,gBAAgB,CAAA;AACxE,EAAA,MAAM,IAAA,GAAO,EAAA;AAEb,EAAA,MAAM,aAAa,IAAI,MAAA,CAAO,QAAQ,UAAA,CAAW,QAAA,EAAU,kBAAkB,IAAI,CAAA;AACjF,EAAA,MAAM,eAAA,GAAkB,IAAA;AAGxB,EAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,cAAA,CAAe,CAAC,CAAA;AACpD,EAAA,MAAM,IAAA,GAAO,UAAU,SAAS,CAAA;AAChC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,aAAa,CAAA,EAAG;AAClB,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,cAAA,CAAe,CAAC,CAAA;AACrD,IAAA,KAAA,GAAQ,UAAU,UAAU,CAAA;AAAA,EAC9B;AAEA,EAAA,MAAM,YAA0B,EAAC;AACjC,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,eAAA,EAAiB;AACrD,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,GAAI,eAAA,EAAiB,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7E,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,QAAA,KAAa,KAAK,KAAA,EAAO;AAC3B,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,QAAA,CAAS,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,GAAI,eAAA,EAAiB,KAAA,CAAM,MAAM,CAAC,CAAA;AAChF,MAAA,MAAA,GAAS,UAAA,CAAW,YAAA,CAAa,SAAA,EAAW,UAAU,CAAA;AAAA,IACxD,CAAA,MAAO;AACL,MAAA,MAAA,GAAS,UAAA,CAAW,aAAa,SAAS,CAAA;AAAA,IAC5C;AACA,IAAA,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,SAAA,CAAU,KAAK,MAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,MAAM,GAAA,GAAM,WAAW,KAAA,EAAM;AAC7B,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG,SAAA,CAAU,KAAK,GAAG,CAAA;AAEtC,EAAA,OAAO,IAAI,IAAA,CAAK,SAAA,EAAW,EAAE,IAAA,EAAM,cAAc,CAAA;AACnD,CAAA;AAKA,MAAM,oBAAA,GAAuB,CAAC,MAAA,KAA8B;AAC1D,EAAA,MAAM,WAAA,GAAc,4BAA4B,MAAM,CAAA;AACtD,EAAA,OAAO,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,IAAA,EAAM,aAAa,CAAA;AACtD,CAAA;AAKA,MAAM,qBAAA,GAAwB,CAAC,MAAA,EAAqB,gBAAA,KAA0C;AAC5F,EAAA,IAAI,MAAA,CAAO,eAAe,gBAAA,EAAkB;AAC1C,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAO,UAAA,GAAa,gBAAA;AAClC,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,SAAS,KAAK,CAAA;AAClD,EAAA,MAAM,SAAA,GAAY,IAAI,YAAA,EAAa,CAAE,YAAA;AAAA,IACnC,MAAA,CAAO,gBAAA;AAAA,IACP,SAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,MAAA,CAAO,kBAAkB,OAAA,EAAA,EAAW;AAClE,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,cAAA,CAAe,OAAO,CAAA;AAC7C,IAAA,MAAM,OAAA,GAAU,SAAA,CAAU,cAAA,CAAe,OAAO,CAAA;AAEhD,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,SAAA,EAAW,CAAA,EAAA,EAAK;AAClC,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,CAAA,GAAI,KAAK,CAAA;AACrC,MAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B;AAAA,EACF;AAEA,EAAA,OAAO,SAAA;AACT,CAAA;AAKA,MAAM,UAAA,GAAa,CAAC,MAAA,EAAqB,WAAA,EAAqB,SAAA,KAAoC;AAChG,EAAA,IAAI,gBAAgB,CAAA,EAAG;AACrB,IAAA,OAAO,OAAO,cAAA,CAAe,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,SAAS,CAAA;AAAA,EACpD;AACA,EAAA,MAAM,MAAA,GAAS,IAAI,YAAA,CAAa,SAAA,GAAY,WAAW,CAAA;AACvD,EAAA,MAAM,cAA8B,EAAC;AACrC,EAAA,KAAA,IAAS,EAAA,GAAK,CAAA,EAAG,EAAA,GAAK,WAAA,EAAa,EAAA,EAAA,EAAM;AACvC,IAAA,WAAA,CAAY,EAAE,CAAA,GAAI,MAAA,CAAO,cAAA,CAAe,EAAE,CAAA;AAAA,EAC5C;AACA,EAAA,IAAI,UAAA,GAAa,CAAA;AACjB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,SAAA,EAAW,CAAA,EAAA,EAAK;AAClC,IAAA,KAAA,IAAS,EAAA,GAAK,CAAA,EAAG,EAAA,GAAK,WAAA,EAAa,EAAA,EAAA,EAAM;AACvC,MAAA,MAAA,CAAO,UAAA,EAAY,CAAA,GAAI,WAAA,CAAY,EAAE,EAAE,CAAC,CAAA;AAAA,IAC1C;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT,CAAA;AAKA,MAAM,eAAA,GAAkB,CAAC,IAAA,EAAgB,MAAA,EAAgB,KAAA,KAA8B;AACrF,EAAA,IAAI,GAAA,GAAM,MAAA;AACV,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,MAAM,MAAA,EAAQ,CAAA,EAAA,EAAK,OAAO,CAAA,EAAG;AAC/C,IAAA,IAAI,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,IAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AAC1C,IAAA,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,GAAI,CAAA,GAAI,IAAI,KAAA,GAAS,CAAA,GAAI,OAAQ,IAAI,CAAA;AAAA,EAC1D;AACF,CAAA;AAKA,MAAM,SAAA,GAAY,CAAC,KAAA,KAAoC;AACrD,EAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,KAAA,CAAM,MAAM,CAAA;AAC1C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,IAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AAC5C,IAAA,MAAA,CAAO,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,QAAS,CAAA,GAAI,KAAA;AAAA,EACvC;AACA,EAAA,OAAO,MAAA;AACT,CAAA;AAKA,MAAM,WAAA,GAAc,CAAC,IAAA,EAAgB,MAAA,EAAgB,GAAA,KAAsB;AACzE,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,IAAA,CAAK,SAAS,MAAA,GAAS,CAAA,EAAG,GAAA,CAAI,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,EAC7C;AACF,CAAA;;ACpZO,MAAM,mBAAA,GAAsB,CAC/B,KAAA,EACA,MAAA,EACA,UACA,SAAA,KACe;AAEf,EAAA,IAAI,KAAA,IAAS,QAAA,IAAY,MAAA,IAAU,SAAA,EAAW;AAE5C,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,KAAA,GAAQ,CAAA,KAAM,CAAA,GAAI,QAAQ,KAAA,GAAQ,CAAA;AAAA,MACzC,MAAA,EAAQ,MAAA,GAAS,CAAA,KAAM,CAAA,GAAI,SAAS,MAAA,GAAS;AAAA,KAC/C;AAAA,EACF;AAGA,EAAA,MAAM,aAAa,QAAA,GAAW,KAAA;AAC9B,EAAA,MAAM,cAAc,SAAA,GAAY,MAAA;AAGhC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,WAAW,CAAA;AAG9C,EAAA,IAAI,WAAA,GAAc,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,KAAK,CAAA;AAC1C,EAAA,IAAI,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,KAAK,CAAA;AAG5C,EAAA,IAAI,WAAA,GAAc,MAAM,CAAA,EAAG;AACzB,IAAA,WAAA,IAAe,CAAA;AAAA,EACjB;AACA,EAAA,IAAI,YAAA,GAAe,MAAM,CAAA,EAAG;AAC1B,IAAA,YAAA,IAAgB,CAAA;AAAA,EAClB;AAGA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,QAAQ,CAAA;AAAA,IACrC,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,YAAA,EAAc,SAAS;AAAA,GAC1C;AACF;AA4BK,MAAM,gBAAA,GAAmB,CAC9B,SAAA,EACA,WAAA,EACA,aAAA,KACe;AACf,EAAA,MAAM,kBAAA,GAAqB,WAAA,CAAY,KAAA,GAAQ,WAAA,CAAY,MAAA;AAC3D,EAAA,MAAM,oBAAA,GAAuB,aAAA,CAAc,KAAA,GAAQ,aAAA,CAAc,MAAA;AAEjE,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,SAAA;AAEH,MAAA,IAAI,qBAAqB,oBAAA,EAAsB;AAC7C,QAAA,OAAO;AAAA,UACL,OAAO,aAAA,CAAc,KAAA;AAAA,UACrB,MAAA,EAAQ,cAAc,KAAA,GAAQ;AAAA,SAChC;AAAA,MACF,CAAA,MAAO;AACL,QAAA,OAAO;AAAA,UACL,KAAA,EAAO,cAAc,MAAA,GAAS,kBAAA;AAAA,UAC9B,QAAQ,aAAA,CAAc;AAAA,SACxB;AAAA,MACF;AAAA,IAEF,KAAK,OAAA;AAEH,MAAA,IAAI,qBAAqB,oBAAA,EAAsB;AAC7C,QAAA,OAAO;AAAA,UACL,KAAA,EAAO,cAAc,MAAA,GAAS,kBAAA;AAAA,UAC9B,QAAQ,aAAA,CAAc;AAAA,SACxB;AAAA,MACF,CAAA,MAAO;AACL,QAAA,OAAO;AAAA,UACL,OAAO,aAAA,CAAc,KAAA;AAAA,UACrB,MAAA,EAAQ,cAAc,KAAA,GAAQ;AAAA,SAChC;AAAA,MACF;AAAA,IAEF,KAAK,MAAA;AAEH,MAAA,OAAO;AAAA,QACL,OAAO,aAAA,CAAc,KAAA;AAAA,QACrB,QAAQ,aAAA,CAAc;AAAA,OACxB;AAAA,IAEF;AAEE,MAAA,OAAO;AAAA,QACL,OAAO,WAAA,CAAY,KAAA;AAAA,QACnB,QAAQ,WAAA,CAAY;AAAA,OACtB;AAAA;AAEN;;ACpIO,MAAM,aAAA,GAAgB,OAAO,OAAA,EAAiB,QAAA,KAAoC;AACrF,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAO,CAAA;AACpC,EAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,EAAA,OAAO,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,UAAU,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,CAAA;AACvD;AAiBO,MAAM,QAAA,GAAW,CAAC,MAAA,KAAkC;AACzD,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC5C,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,MAAA,KAAA,CAAM,IAAA,GAAO,MAAA;AACb,MAAA,KAAA,CAAM,MAAA,GAAS,MAAA;AACf,MAAA,KAAA,CAAM,MAAM,OAAA,GAAU,MAAA;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAE/B,MAAA,MAAM,UAAU,MAAM;AAEpB,QAAA,KAAA,CAAM,KAAA,GAAQ,EAAA;AACd,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAAA,MACjC,CAAA;AAEA,MAAA,KAAA,CAAM,WAAW,MAAM;AACrB,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,IAAS,KAAA,CAAM,MAAM,CAAC,CAAA;AACzC,QAAA,OAAA,EAAQ;AACR,QAAA,IAAI,CAAC,IAAA,EAAM;AACT,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,kBAAkB,CAAC,CAAA;AACpC,UAAA;AAAA,QACF;AACA,QAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,MACd,CAAA;AAIA,MAAA,KAAA,CAAM,KAAA,EAAM;AAAA,IACd,SAAS,KAAA,EAAO;AACd,MAAA,MAAA,CAAO,KAAc,CAAA;AAAA,IACvB;AAAA,EACF,CAAC,CAAA;AACH;AAwBO,MAAM,UAAA,GAAa,CAAC,OAAA,EAAwB,IAAA,EAAc,IAAA,KAAuB;AACtF,EAAA,MAAM,IAAA,GAAO,OAAO,OAAA,KAAY,QAAA,GAAW,IAAI,IAAA,CAAK,CAAC,OAAO,CAAA,EAAG,EAAE,IAAA,EAAM,CAAA,GAAI,OAAA;AAC3E,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AAEpC,EAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACpC,EAAA,CAAA,CAAE,IAAA,GAAO,GAAA;AACT,EAAA,CAAA,CAAE,QAAA,GAAW,IAAA;AACb,EAAA,CAAA,CAAE,KAAA,EAAM;AAGR,EAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AACzB;AAkBO,MAAM,YAAA,GAAe,OAAO,GAAA,EAAa,QAAA,KAAoC;AAClF,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AAEnD,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,IAAA,IAAA,CAAK,KAAA,EAAM;AAGX,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,IAAA,MAAA,CAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AAAA,EACxC,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAC9C,IAAA,MAAM,KAAA;AAAA,EACR;AACF;;ACjHK,MAAM,sBAAA,GAAyB,OAAO,GAAA,KAA6D;AACtG,EAAA,IAAI;AAEF,IAAA,MAAM,WAAW,MAAM,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAGpD,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA;AAEvD,IAAA,IAAI,CAAC,aAAa,OAAO,IAAA;AAGzB,IAAA,IAAI,WAAA,CAAY,UAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,OAAA;AAC7C,IAAA,IAAI,WAAA,CAAY,UAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,OAAA;AAC7C,IAAA,IAAI,WAAA,CAAY,UAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,OAAA;AAG7C,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,iBAAiB,KAAK,CAAA;AACpC,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;;;;;;;;;;;;;;;"}
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/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 for better performance.\n * The function creates a temporary audio element, loads only metadata, and extracts\n * the duration without downloading the entire audio file.\n *\n * @param audioSrc - The source URL of the audio file\n * @returns Promise resolving to the duration of the audio in seconds\n * \n * @example\n * ```js\n * // Get duration of an MP3 file\n * const duration = await getAudioDuration(\"https://example.com/audio.mp3\");\n * // duration = 180.5 (3 minutes and 0.5 seconds)\n * \n * // Get duration of a local blob URL\n * const duration = await getAudioDuration(\"blob:http://localhost:3000/abc123\");\n * // duration = 45.2\n * ```\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 * Internal helper function that manages the execution of queued tasks.\n * Dequeues and executes the next task when a concurrency slot becomes available.\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 the concurrency limit is reached, the function is queued and executed later\n * when a slot becomes available. This prevents overwhelming the system with too\n * many concurrent operations, which is useful for resource-intensive tasks like\n * media processing or API calls.\n * \n * @param fn - Async function returning a Promise that should be executed with concurrency control\n * @returns Promise resolving with the result of the wrapped function\n * \n * @example\n * ```js\n * // Limit concurrent image processing operations\n * const processImage = async (imageUrl) => {\n * // Expensive image processing operation\n * return await someImageProcessing(imageUrl);\n * };\n * \n * // Process multiple images with concurrency limit\n * const results = await Promise.all([\n * limit(() => processImage(\"image1.jpg\")),\n * limit(() => processImage(\"image2.jpg\")),\n * limit(() => processImage(\"image3.jpg\")),\n * limit(() => processImage(\"image4.jpg\")),\n * limit(() => processImage(\"image5.jpg\")),\n * limit(() => processImage(\"image6.jpg\")), // This will be queued until a slot opens\n * ]);\n * ```\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 * Internal helper function that creates a temporary Image element to extract\n * the natural width and height of an image without displaying it.\n *\n * @param url - The image URL to load\n * @returns Promise resolving with the image's natural 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, and employs\n * a concurrency limiter to control resource usage and prevent overwhelming\n * the browser with too many simultaneous image loads.\n *\n * @param url - The URL of the image to analyze\n * @returns Promise resolving to an object containing width and height\n * \n * @example\n * ```js\n * // Get dimensions of a remote image\n * const dimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // dimensions = { width: 1920, height: 1080 }\n * \n * // Get dimensions of a local blob URL\n * const dimensions = await getImageDimensions(\"blob:http://localhost:3000/abc123\");\n * // dimensions = { width: 800, height: 600 }\n * \n * // Subsequent calls for the same URL will use cache\n * const cachedDimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // Returns immediately from cache without reloading\n * ```\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 * Uses a cache to avoid reloading the same video multiple times for better performance.\n * The function creates a temporary video element, loads only metadata, and extracts\n * the video properties without downloading the entire file.\n *\n * @param videoSrc - The URL or path to the video file\n * @returns Promise resolving to an object containing video metadata\n * \n * @example\n * ```js\n * // Get metadata for a video\n * const metadata = await getVideoMeta(\"https://example.com/video.mp4\");\n * // metadata = { width: 1920, height: 1080, duration: 120.5 }\n * \n * // Get metadata for a local blob URL\n * const metadata = await getVideoMeta(\"blob:http://localhost:3000/abc123\");\n * // metadata = { width: 1280, height: 720, duration: 30.0 }\n * ```\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 * Creates a hidden video element in the browser, seeks to the specified time,\n * and captures the frame into a canvas, which is then exported as a JPEG data URL or Blob URL.\n * The function handles video loading, seeking, frame capture, and cleanup automatically.\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\n * @param playbackRate - Playback speed for the video\n * @returns Promise resolving to a thumbnail image URL\n * \n * @example\n * ```js\n * // Extract thumbnail at 5 seconds\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 5);\n * // thumbnail is a data URL like \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...\"\n * \n * // Extract thumbnail with custom playback rate\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 2.5, 1.5);\n * ```\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 }, 15000);\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 between start and end times,\n * rendered at the specified playback rate, and returns a Blob URL to an MP3 file.\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 * @param src - The source URL of the media file\n * @param playbackRate - The playback rate for the extracted segment\n * @param start - The start time in seconds\n * @param end - The end time in seconds\n * @returns Promise resolving to a Blob URL to the extracted MP3 file\n * \n * @example\n * ```js\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 * ```\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 // Check if audio buffer has no audio content\n if (audioBuffer.duration === 0 || audioBuffer.length === 0) {\n throw new Error(\"No audio track found in the media source\");\n }\n\n // Check if audio is completely silent\n if (isAudioSilent(audioBuffer)) {\n throw new Error(\"Audio track is silent (no audio content detected)\");\n }\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 * Checks if a video or audio file has an audio track with actual sound content.\n * This function attempts to decode the audio and verifies that it's not empty or silent.\n * \n * @param src - The source URL of the media file to check\n * @returns Promise resolving to true if the media has audio, false otherwise\n * \n * @example\n * ```js\n * // Check if a video has audio\n * const hasSound = await hasAudio(\"https://example.com/video.mp4\");\n * if (hasSound) {\n * // Extract audio or show audio controls\n * } else {\n * // Handle video without audio\n * }\n * ```\n */\nexport const hasAudio = async (src: string): Promise<boolean> => {\n if (!src) return false;\n\n // Basic URL safety check\n const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);\n if (!isSafeUrl) return false;\n\n try {\n // Fetch and decode audio\n const audioBuffer = await fetchAndDecodeAudio(src);\n\n // Check if audio buffer has no audio content\n if (audioBuffer.duration === 0 || audioBuffer.length === 0) {\n return false;\n }\n\n // Check if audio is completely silent\n if (isAudioSilent(audioBuffer)) {\n return false;\n }\n\n return true;\n } catch (error) {\n // If decoding fails, assume no audio\n return false;\n }\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\n * @returns Promise resolving to a Blob URL to the stitched MP3 file\n * \n * @example\n * ```js\n * const segments = [\n * { src: \"audio1.mp3\", s: 0, e: 5, volume: 1.0 },\n * { src: \"audio2.mp3\", s: 10, e: 15, volume: 0.8 }\n * ];\n * const url = await stitchAudio(segments, 15);\n * // Creates a 15-second audio file with segments at specified times\n * ```\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 * \n * @param src - The URL of the audio file to fetch and decode\n * @returns Promise<AudioBuffer> - The decoded audio buffer\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: no audio track found or unsupported format\"))\n );\n });\n } finally {\n audioContext.close();\n }\n};\n\n/**\n * Checks if an AudioBuffer contains only silence\n * Samples a portion of the audio to detect if it's completely silent\n */\nconst isAudioSilent = (buffer: AudioBuffer, threshold: number = 0.001): boolean => {\n // Check all channels\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel);\n // Sample every 100th frame for performance\n for (let i = 0; i < channelData.length; i += 100) {\n if (Math.abs(channelData[i]) > threshold) {\n return false; // Found non-silent audio\n }\n }\n }\n return true; // All sampled frames are silent\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 while maintaining aspect ratio.\n * Ensures the resulting dimensions are even numbers and fit within the specified bounds.\n * If the original dimensions are already smaller than the max values, returns the original dimensions.\n *\n * @param width - The original width of the element in pixels\n * @param height - The original height of the element in pixels\n * @param maxWidth - The maximum allowed width of the container in pixels\n * @param maxHeight - The maximum allowed height of the container in pixels\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Scale down a large image to fit in a smaller container\n * const scaled = getScaledDimensions(1920, 1080, 800, 600);\n * // scaled = { width: 800, height: 450 }\n * \n * // Small image that doesn't need scaling\n * const scaled = getScaledDimensions(400, 300, 800, 600);\n * // scaled = { width: 400, height: 300 }\n * \n * // Ensure even dimensions for video encoding\n * const scaled = getScaledDimensions(1001, 1001, 1000, 1000);\n * // scaled = { width: 1000, height: 1000 }\n * ```\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 * Implements CSS object-fit behavior for programmatic dimension calculations.\n * Useful for responsive design and media scaling applications.\n *\n * @param objectFit - The object-fit behavior\n * @param elementSize - The original size of the element\n * @param containerSize - The size of the container\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Contain: fit entire element inside container\n * const contained = getObjectFitSize(\"contain\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // contained = { width: 400, height: 200 }\n * \n * // Cover: fill container while maintaining aspect ratio\n * const covered = getObjectFitSize(\"cover\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // covered = { width: 600, height: 300 }\n * \n * // Fill: stretch to completely fill container\n * const filled = getObjectFitSize(\"fill\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // filled = { width: 400, height: 300 }\n * ```\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 * Fetches the blob data from the URL and creates a new File object with the specified name.\n * Useful for converting blob URLs back to File objects for upload or processing.\n *\n * @param blobUrl - The Blob URL to convert\n * @param fileName - The name to assign to the resulting File object\n * @returns Promise resolving to a File object with the blob data\n * \n * @example\n * ```js\n * const file = await blobUrlToFile(\"blob:http://localhost:3000/abc123\", \"image.jpg\");\n * // file is now a File object that can be uploaded or processed\n * ```\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 * Opens a native file picker and resolves with the selected File.\n * The accepted file types can be specified using the same format as the\n * input accept attribute (e.g. \"application/json\", \".png,.jpg\", \"image/*\").\n *\n * @param accept - The accept filter string for the file input\n * @returns Promise resolving to the chosen File\n * \n * @example\n * ```ts\n * const file = await loadFile(\"application/json\");\n * const text = await file.text();\n * const data = JSON.parse(text);\n * ```\n */\n export const loadFile = (accept: string): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n try {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = accept;\n input.style.display = \"none\";\n document.body.appendChild(input);\n\n const cleanup = () => {\n // Clear the value so the same file can be picked again next time\n input.value = \"\";\n document.body.removeChild(input);\n };\n\n input.onchange = () => {\n const file = input.files && input.files[0];\n cleanup();\n if (!file) {\n reject(new Error(\"No file selected\"));\n return;\n }\n resolve(file);\n };\n\n // Some browsers need a small timeout to ensure the element is attached\n // before programmatic click, but generally this works without it.\n input.click();\n } catch (error) {\n reject(error as Error);\n }\n });\n };\n \n /**\n * Triggers a download of a file from a string or Blob.\n * Creates a temporary download link and automatically clicks it to initiate the download.\n * The function handles both string content and Blob objects, and automatically cleans up\n * the created object URL after the download is initiated.\n *\n * @param content - The content to save, either a string or a Blob object\n * @param type - The MIME type of the content\n * @param name - The name of the file to be saved\n * \n * @example\n * ```js\n * // Download text content\n * saveAsFile(\"Hello World\", \"text/plain\", \"hello.txt\");\n * \n * // Download JSON data\n * saveAsFile(JSON.stringify({data: \"value\"}), \"application/json\", \"data.json\");\n * \n * // Download blob content\n * saveAsFile(imageBlob, \"image/png\", \"screenshot.png\");\n * ```\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 * Fetches the file content from the provided URL and creates a download link\n * to save it locally. The function handles the entire download process including\n * fetching, blob creation, and cleanup of temporary resources.\n *\n * @param url - The URL of the file to download\n * @param filename - The name of the file to be saved locally\n * @returns Promise resolving when the download is initiated\n * \n * @example\n * ```js\n * await downloadFile(\"https://example.com/image.jpg\", \"downloaded-image.jpg\");\n * // Browser will automatically download the file with the specified name\n * ```\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 ","/**\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\n * Uses a lightweight HEAD request to fetch only the headers, avoiding download of the full file.\n * The function analyzes the Content-Type header to determine the media type category.\n *\n * @param url - The URL of the media file to analyze\n * @returns Promise resolving to the detected media type or null\n * \n * @example\n * ```js\n * // Detect image type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/image.jpg\");\n * // type = \"image\"\n * \n * // Detect video type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/video.mp4\");\n * // type = \"video\"\n * \n * // Detect audio type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/audio.mp3\");\n * // type = \"audio\"\n * \n * // Invalid or inaccessible URL\n * const type = await detectMediaTypeFromUrl(\"https://example.com/invalid\");\n * // type = null\n * ```\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/dist/index.mjs CHANGED
@@ -199,6 +199,12 @@ const extractAudio = async ({
199
199
  const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
200
200
  if (!isSafeUrl) throw new Error("Unsafe media source URL");
201
201
  const audioBuffer = await fetchAndDecodeAudio(src);
202
+ if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
203
+ throw new Error("No audio track found in the media source");
204
+ }
205
+ if (isAudioSilent(audioBuffer)) {
206
+ throw new Error("Audio track is silent (no audio content detected)");
207
+ }
202
208
  const clampedStart = Math.max(0, start || 0);
203
209
  const fullDuration = audioBuffer.duration;
204
210
  const clampedEnd = Math.min(
@@ -216,6 +222,23 @@ const extractAudio = async ({
216
222
  const mp3Blob = await audioBufferToMp3(renderedBuffer);
217
223
  return URL.createObjectURL(mp3Blob);
218
224
  };
225
+ const hasAudio = async (src) => {
226
+ if (!src) return false;
227
+ const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
228
+ if (!isSafeUrl) return false;
229
+ try {
230
+ const audioBuffer = await fetchAndDecodeAudio(src);
231
+ if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
232
+ return false;
233
+ }
234
+ if (isAudioSilent(audioBuffer)) {
235
+ return false;
236
+ }
237
+ return true;
238
+ } catch (error) {
239
+ return false;
240
+ }
241
+ };
219
242
  const stitchAudio = async (segments, totalDuration) => {
220
243
  if (!segments || segments.length === 0) {
221
244
  throw new Error("At least one audio segment is required");
@@ -240,13 +263,24 @@ const decodeAudioData = async (arrayBuffer) => {
240
263
  audioContext.decodeAudioData(
241
264
  arrayBuffer.slice(0),
242
265
  (buf) => resolve(buf),
243
- (err) => reject(err || new Error("Failed to decode audio"))
266
+ (err) => reject(err || new Error("Failed to decode audio: no audio track found or unsupported format"))
244
267
  );
245
268
  });
246
269
  } finally {
247
270
  audioContext.close();
248
271
  }
249
272
  };
273
+ const isAudioSilent = (buffer, threshold = 1e-3) => {
274
+ for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
275
+ const channelData = buffer.getChannelData(channel);
276
+ for (let i = 0; i < channelData.length; i += 100) {
277
+ if (Math.abs(channelData[i]) > threshold) {
278
+ return false;
279
+ }
280
+ }
281
+ }
282
+ return true;
283
+ };
250
284
  const renderAudioSegment = async (audioBuffer, start, end, playbackRate) => {
251
285
  const OfflineAudioContextCtor = window.OfflineAudioContext || window.webkitOfflineAudioContext;
252
286
  if (!OfflineAudioContextCtor) throw new Error("OfflineAudioContext not supported");
@@ -571,5 +605,5 @@ const detectMediaTypeFromUrl = async (url) => {
571
605
  }
572
606
  };
573
607
 
574
- export { blobUrlToFile, detectMediaTypeFromUrl, downloadFile, extractAudio, getAudioDuration, getImageDimensions, getObjectFitSize, getScaledDimensions, getThumbnail, getVideoMeta, limit, loadFile, saveAsFile, stitchAudio };
608
+ export { blobUrlToFile, detectMediaTypeFromUrl, downloadFile, extractAudio, getAudioDuration, getImageDimensions, getObjectFitSize, getScaledDimensions, getThumbnail, getVideoMeta, hasAudio, limit, loadFile, saveAsFile, stitchAudio };
575
609
  //# sourceMappingURL=index.mjs.map
@@ -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/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 for better performance.\n * The function creates a temporary audio element, loads only metadata, and extracts\n * the duration without downloading the entire audio file.\n *\n * @param audioSrc - The source URL of the audio file\n * @returns Promise resolving to the duration of the audio in seconds\n * \n * @example\n * ```js\n * // Get duration of an MP3 file\n * const duration = await getAudioDuration(\"https://example.com/audio.mp3\");\n * // duration = 180.5 (3 minutes and 0.5 seconds)\n * \n * // Get duration of a local blob URL\n * const duration = await getAudioDuration(\"blob:http://localhost:3000/abc123\");\n * // duration = 45.2\n * ```\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 * Internal helper function that manages the execution of queued tasks.\n * Dequeues and executes the next task when a concurrency slot becomes available.\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 the concurrency limit is reached, the function is queued and executed later\n * when a slot becomes available. This prevents overwhelming the system with too\n * many concurrent operations, which is useful for resource-intensive tasks like\n * media processing or API calls.\n * \n * @param fn - Async function returning a Promise that should be executed with concurrency control\n * @returns Promise resolving with the result of the wrapped function\n * \n * @example\n * ```js\n * // Limit concurrent image processing operations\n * const processImage = async (imageUrl) => {\n * // Expensive image processing operation\n * return await someImageProcessing(imageUrl);\n * };\n * \n * // Process multiple images with concurrency limit\n * const results = await Promise.all([\n * limit(() => processImage(\"image1.jpg\")),\n * limit(() => processImage(\"image2.jpg\")),\n * limit(() => processImage(\"image3.jpg\")),\n * limit(() => processImage(\"image4.jpg\")),\n * limit(() => processImage(\"image5.jpg\")),\n * limit(() => processImage(\"image6.jpg\")), // This will be queued until a slot opens\n * ]);\n * ```\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 * Internal helper function that creates a temporary Image element to extract\n * the natural width and height of an image without displaying it.\n *\n * @param url - The image URL to load\n * @returns Promise resolving with the image's natural 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, and employs\n * a concurrency limiter to control resource usage and prevent overwhelming\n * the browser with too many simultaneous image loads.\n *\n * @param url - The URL of the image to analyze\n * @returns Promise resolving to an object containing width and height\n * \n * @example\n * ```js\n * // Get dimensions of a remote image\n * const dimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // dimensions = { width: 1920, height: 1080 }\n * \n * // Get dimensions of a local blob URL\n * const dimensions = await getImageDimensions(\"blob:http://localhost:3000/abc123\");\n * // dimensions = { width: 800, height: 600 }\n * \n * // Subsequent calls for the same URL will use cache\n * const cachedDimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // Returns immediately from cache without reloading\n * ```\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 * Uses a cache to avoid reloading the same video multiple times for better performance.\n * The function creates a temporary video element, loads only metadata, and extracts\n * the video properties without downloading the entire file.\n *\n * @param videoSrc - The URL or path to the video file\n * @returns Promise resolving to an object containing video metadata\n * \n * @example\n * ```js\n * // Get metadata for a video\n * const metadata = await getVideoMeta(\"https://example.com/video.mp4\");\n * // metadata = { width: 1920, height: 1080, duration: 120.5 }\n * \n * // Get metadata for a local blob URL\n * const metadata = await getVideoMeta(\"blob:http://localhost:3000/abc123\");\n * // metadata = { width: 1280, height: 720, duration: 30.0 }\n * ```\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 * Creates a hidden video element in the browser, seeks to the specified time,\n * and captures the frame into a canvas, which is then exported as a JPEG data URL or Blob URL.\n * The function handles video loading, seeking, frame capture, and cleanup automatically.\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\n * @param playbackRate - Playback speed for the video\n * @returns Promise resolving to a thumbnail image URL\n * \n * @example\n * ```js\n * // Extract thumbnail at 5 seconds\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 5);\n * // thumbnail is a data URL like \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...\"\n * \n * // Extract thumbnail with custom playback rate\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 2.5, 1.5);\n * ```\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 }, 15000);\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 between start and end times,\n * rendered at the specified playback rate, and returns a Blob URL to an MP3 file.\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 * @param src - The source URL of the media file\n * @param playbackRate - The playback rate for the extracted segment\n * @param start - The start time in seconds\n * @param end - The end time in seconds\n * @returns Promise resolving to a Blob URL to the extracted MP3 file\n * \n * @example\n * ```js\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 * ```\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\n * @returns Promise resolving to a Blob URL to the stitched MP3 file\n * \n * @example\n * ```js\n * const segments = [\n * { src: \"audio1.mp3\", s: 0, e: 5, volume: 1.0 },\n * { src: \"audio2.mp3\", s: 10, e: 15, volume: 0.8 }\n * ];\n * const url = await stitchAudio(segments, 15);\n * // Creates a 15-second audio file with segments at specified times\n * ```\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 * \n * @param src - The URL of the audio file to fetch and decode\n * @returns Promise<AudioBuffer> - The decoded audio buffer\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 while maintaining aspect ratio.\n * Ensures the resulting dimensions are even numbers and fit within the specified bounds.\n * If the original dimensions are already smaller than the max values, returns the original dimensions.\n *\n * @param width - The original width of the element in pixels\n * @param height - The original height of the element in pixels\n * @param maxWidth - The maximum allowed width of the container in pixels\n * @param maxHeight - The maximum allowed height of the container in pixels\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Scale down a large image to fit in a smaller container\n * const scaled = getScaledDimensions(1920, 1080, 800, 600);\n * // scaled = { width: 800, height: 450 }\n * \n * // Small image that doesn't need scaling\n * const scaled = getScaledDimensions(400, 300, 800, 600);\n * // scaled = { width: 400, height: 300 }\n * \n * // Ensure even dimensions for video encoding\n * const scaled = getScaledDimensions(1001, 1001, 1000, 1000);\n * // scaled = { width: 1000, height: 1000 }\n * ```\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 * Implements CSS object-fit behavior for programmatic dimension calculations.\n * Useful for responsive design and media scaling applications.\n *\n * @param objectFit - The object-fit behavior\n * @param elementSize - The original size of the element\n * @param containerSize - The size of the container\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Contain: fit entire element inside container\n * const contained = getObjectFitSize(\"contain\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // contained = { width: 400, height: 200 }\n * \n * // Cover: fill container while maintaining aspect ratio\n * const covered = getObjectFitSize(\"cover\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // covered = { width: 600, height: 300 }\n * \n * // Fill: stretch to completely fill container\n * const filled = getObjectFitSize(\"fill\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // filled = { width: 400, height: 300 }\n * ```\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 * Fetches the blob data from the URL and creates a new File object with the specified name.\n * Useful for converting blob URLs back to File objects for upload or processing.\n *\n * @param blobUrl - The Blob URL to convert\n * @param fileName - The name to assign to the resulting File object\n * @returns Promise resolving to a File object with the blob data\n * \n * @example\n * ```js\n * const file = await blobUrlToFile(\"blob:http://localhost:3000/abc123\", \"image.jpg\");\n * // file is now a File object that can be uploaded or processed\n * ```\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 * Opens a native file picker and resolves with the selected File.\n * The accepted file types can be specified using the same format as the\n * input accept attribute (e.g. \"application/json\", \".png,.jpg\", \"image/*\").\n *\n * @param accept - The accept filter string for the file input\n * @returns Promise resolving to the chosen File\n * \n * @example\n * ```ts\n * const file = await loadFile(\"application/json\");\n * const text = await file.text();\n * const data = JSON.parse(text);\n * ```\n */\n export const loadFile = (accept: string): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n try {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = accept;\n input.style.display = \"none\";\n document.body.appendChild(input);\n\n const cleanup = () => {\n // Clear the value so the same file can be picked again next time\n input.value = \"\";\n document.body.removeChild(input);\n };\n\n input.onchange = () => {\n const file = input.files && input.files[0];\n cleanup();\n if (!file) {\n reject(new Error(\"No file selected\"));\n return;\n }\n resolve(file);\n };\n\n // Some browsers need a small timeout to ensure the element is attached\n // before programmatic click, but generally this works without it.\n input.click();\n } catch (error) {\n reject(error as Error);\n }\n });\n };\n \n /**\n * Triggers a download of a file from a string or Blob.\n * Creates a temporary download link and automatically clicks it to initiate the download.\n * The function handles both string content and Blob objects, and automatically cleans up\n * the created object URL after the download is initiated.\n *\n * @param content - The content to save, either a string or a Blob object\n * @param type - The MIME type of the content\n * @param name - The name of the file to be saved\n * \n * @example\n * ```js\n * // Download text content\n * saveAsFile(\"Hello World\", \"text/plain\", \"hello.txt\");\n * \n * // Download JSON data\n * saveAsFile(JSON.stringify({data: \"value\"}), \"application/json\", \"data.json\");\n * \n * // Download blob content\n * saveAsFile(imageBlob, \"image/png\", \"screenshot.png\");\n * ```\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 * Fetches the file content from the provided URL and creates a download link\n * to save it locally. The function handles the entire download process including\n * fetching, blob creation, and cleanup of temporary resources.\n *\n * @param url - The URL of the file to download\n * @param filename - The name of the file to be saved locally\n * @returns Promise resolving when the download is initiated\n * \n * @example\n * ```js\n * await downloadFile(\"https://example.com/image.jpg\", \"downloaded-image.jpg\");\n * // Browser will automatically download the file with the specified name\n * ```\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 ","/**\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\n * Uses a lightweight HEAD request to fetch only the headers, avoiding download of the full file.\n * The function analyzes the Content-Type header to determine the media type category.\n *\n * @param url - The URL of the media file to analyze\n * @returns Promise resolving to the detected media type or null\n * \n * @example\n * ```js\n * // Detect image type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/image.jpg\");\n * // type = \"image\"\n * \n * // Detect video type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/video.mp4\");\n * // type = \"video\"\n * \n * // Detect audio type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/audio.mp3\");\n * // type = \"audio\"\n * \n * // Invalid or inaccessible URL\n * const type = await detectMediaTypeFromUrl(\"https://example.com/invalid\");\n * // type = null\n * ```\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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"}
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 for better performance.\n * The function creates a temporary audio element, loads only metadata, and extracts\n * the duration without downloading the entire audio file.\n *\n * @param audioSrc - The source URL of the audio file\n * @returns Promise resolving to the duration of the audio in seconds\n * \n * @example\n * ```js\n * // Get duration of an MP3 file\n * const duration = await getAudioDuration(\"https://example.com/audio.mp3\");\n * // duration = 180.5 (3 minutes and 0.5 seconds)\n * \n * // Get duration of a local blob URL\n * const duration = await getAudioDuration(\"blob:http://localhost:3000/abc123\");\n * // duration = 45.2\n * ```\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 * Internal helper function that manages the execution of queued tasks.\n * Dequeues and executes the next task when a concurrency slot becomes available.\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 the concurrency limit is reached, the function is queued and executed later\n * when a slot becomes available. This prevents overwhelming the system with too\n * many concurrent operations, which is useful for resource-intensive tasks like\n * media processing or API calls.\n * \n * @param fn - Async function returning a Promise that should be executed with concurrency control\n * @returns Promise resolving with the result of the wrapped function\n * \n * @example\n * ```js\n * // Limit concurrent image processing operations\n * const processImage = async (imageUrl) => {\n * // Expensive image processing operation\n * return await someImageProcessing(imageUrl);\n * };\n * \n * // Process multiple images with concurrency limit\n * const results = await Promise.all([\n * limit(() => processImage(\"image1.jpg\")),\n * limit(() => processImage(\"image2.jpg\")),\n * limit(() => processImage(\"image3.jpg\")),\n * limit(() => processImage(\"image4.jpg\")),\n * limit(() => processImage(\"image5.jpg\")),\n * limit(() => processImage(\"image6.jpg\")), // This will be queued until a slot opens\n * ]);\n * ```\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 * Internal helper function that creates a temporary Image element to extract\n * the natural width and height of an image without displaying it.\n *\n * @param url - The image URL to load\n * @returns Promise resolving with the image's natural 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, and employs\n * a concurrency limiter to control resource usage and prevent overwhelming\n * the browser with too many simultaneous image loads.\n *\n * @param url - The URL of the image to analyze\n * @returns Promise resolving to an object containing width and height\n * \n * @example\n * ```js\n * // Get dimensions of a remote image\n * const dimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // dimensions = { width: 1920, height: 1080 }\n * \n * // Get dimensions of a local blob URL\n * const dimensions = await getImageDimensions(\"blob:http://localhost:3000/abc123\");\n * // dimensions = { width: 800, height: 600 }\n * \n * // Subsequent calls for the same URL will use cache\n * const cachedDimensions = await getImageDimensions(\"https://example.com/image.jpg\");\n * // Returns immediately from cache without reloading\n * ```\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 * Uses a cache to avoid reloading the same video multiple times for better performance.\n * The function creates a temporary video element, loads only metadata, and extracts\n * the video properties without downloading the entire file.\n *\n * @param videoSrc - The URL or path to the video file\n * @returns Promise resolving to an object containing video metadata\n * \n * @example\n * ```js\n * // Get metadata for a video\n * const metadata = await getVideoMeta(\"https://example.com/video.mp4\");\n * // metadata = { width: 1920, height: 1080, duration: 120.5 }\n * \n * // Get metadata for a local blob URL\n * const metadata = await getVideoMeta(\"blob:http://localhost:3000/abc123\");\n * // metadata = { width: 1280, height: 720, duration: 30.0 }\n * ```\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 * Creates a hidden video element in the browser, seeks to the specified time,\n * and captures the frame into a canvas, which is then exported as a JPEG data URL or Blob URL.\n * The function handles video loading, seeking, frame capture, and cleanup automatically.\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\n * @param playbackRate - Playback speed for the video\n * @returns Promise resolving to a thumbnail image URL\n * \n * @example\n * ```js\n * // Extract thumbnail at 5 seconds\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 5);\n * // thumbnail is a data URL like \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...\"\n * \n * // Extract thumbnail with custom playback rate\n * const thumbnail = await getThumbnail(\"https://example.com/video.mp4\", 2.5, 1.5);\n * ```\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 }, 15000);\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 between start and end times,\n * rendered at the specified playback rate, and returns a Blob URL to an MP3 file.\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 * @param src - The source URL of the media file\n * @param playbackRate - The playback rate for the extracted segment\n * @param start - The start time in seconds\n * @param end - The end time in seconds\n * @returns Promise resolving to a Blob URL to the extracted MP3 file\n * \n * @example\n * ```js\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 * ```\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 // Check if audio buffer has no audio content\n if (audioBuffer.duration === 0 || audioBuffer.length === 0) {\n throw new Error(\"No audio track found in the media source\");\n }\n\n // Check if audio is completely silent\n if (isAudioSilent(audioBuffer)) {\n throw new Error(\"Audio track is silent (no audio content detected)\");\n }\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 * Checks if a video or audio file has an audio track with actual sound content.\n * This function attempts to decode the audio and verifies that it's not empty or silent.\n * \n * @param src - The source URL of the media file to check\n * @returns Promise resolving to true if the media has audio, false otherwise\n * \n * @example\n * ```js\n * // Check if a video has audio\n * const hasSound = await hasAudio(\"https://example.com/video.mp4\");\n * if (hasSound) {\n * // Extract audio or show audio controls\n * } else {\n * // Handle video without audio\n * }\n * ```\n */\nexport const hasAudio = async (src: string): Promise<boolean> => {\n if (!src) return false;\n\n // Basic URL safety check\n const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);\n if (!isSafeUrl) return false;\n\n try {\n // Fetch and decode audio\n const audioBuffer = await fetchAndDecodeAudio(src);\n\n // Check if audio buffer has no audio content\n if (audioBuffer.duration === 0 || audioBuffer.length === 0) {\n return false;\n }\n\n // Check if audio is completely silent\n if (isAudioSilent(audioBuffer)) {\n return false;\n }\n\n return true;\n } catch (error) {\n // If decoding fails, assume no audio\n return false;\n }\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\n * @returns Promise resolving to a Blob URL to the stitched MP3 file\n * \n * @example\n * ```js\n * const segments = [\n * { src: \"audio1.mp3\", s: 0, e: 5, volume: 1.0 },\n * { src: \"audio2.mp3\", s: 10, e: 15, volume: 0.8 }\n * ];\n * const url = await stitchAudio(segments, 15);\n * // Creates a 15-second audio file with segments at specified times\n * ```\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 * \n * @param src - The URL of the audio file to fetch and decode\n * @returns Promise<AudioBuffer> - The decoded audio buffer\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: no audio track found or unsupported format\"))\n );\n });\n } finally {\n audioContext.close();\n }\n};\n\n/**\n * Checks if an AudioBuffer contains only silence\n * Samples a portion of the audio to detect if it's completely silent\n */\nconst isAudioSilent = (buffer: AudioBuffer, threshold: number = 0.001): boolean => {\n // Check all channels\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel);\n // Sample every 100th frame for performance\n for (let i = 0; i < channelData.length; i += 100) {\n if (Math.abs(channelData[i]) > threshold) {\n return false; // Found non-silent audio\n }\n }\n }\n return true; // All sampled frames are silent\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 while maintaining aspect ratio.\n * Ensures the resulting dimensions are even numbers and fit within the specified bounds.\n * If the original dimensions are already smaller than the max values, returns the original dimensions.\n *\n * @param width - The original width of the element in pixels\n * @param height - The original height of the element in pixels\n * @param maxWidth - The maximum allowed width of the container in pixels\n * @param maxHeight - The maximum allowed height of the container in pixels\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Scale down a large image to fit in a smaller container\n * const scaled = getScaledDimensions(1920, 1080, 800, 600);\n * // scaled = { width: 800, height: 450 }\n * \n * // Small image that doesn't need scaling\n * const scaled = getScaledDimensions(400, 300, 800, 600);\n * // scaled = { width: 400, height: 300 }\n * \n * // Ensure even dimensions for video encoding\n * const scaled = getScaledDimensions(1001, 1001, 1000, 1000);\n * // scaled = { width: 1000, height: 1000 }\n * ```\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 * Implements CSS object-fit behavior for programmatic dimension calculations.\n * Useful for responsive design and media scaling applications.\n *\n * @param objectFit - The object-fit behavior\n * @param elementSize - The original size of the element\n * @param containerSize - The size of the container\n * @returns Object containing the calculated width and height\n * \n * @example\n * ```js\n * // Contain: fit entire element inside container\n * const contained = getObjectFitSize(\"contain\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // contained = { width: 400, height: 200 }\n * \n * // Cover: fill container while maintaining aspect ratio\n * const covered = getObjectFitSize(\"cover\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // covered = { width: 600, height: 300 }\n * \n * // Fill: stretch to completely fill container\n * const filled = getObjectFitSize(\"fill\", {width: 1000, height: 500}, {width: 400, height: 300});\n * // filled = { width: 400, height: 300 }\n * ```\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 * Fetches the blob data from the URL and creates a new File object with the specified name.\n * Useful for converting blob URLs back to File objects for upload or processing.\n *\n * @param blobUrl - The Blob URL to convert\n * @param fileName - The name to assign to the resulting File object\n * @returns Promise resolving to a File object with the blob data\n * \n * @example\n * ```js\n * const file = await blobUrlToFile(\"blob:http://localhost:3000/abc123\", \"image.jpg\");\n * // file is now a File object that can be uploaded or processed\n * ```\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 * Opens a native file picker and resolves with the selected File.\n * The accepted file types can be specified using the same format as the\n * input accept attribute (e.g. \"application/json\", \".png,.jpg\", \"image/*\").\n *\n * @param accept - The accept filter string for the file input\n * @returns Promise resolving to the chosen File\n * \n * @example\n * ```ts\n * const file = await loadFile(\"application/json\");\n * const text = await file.text();\n * const data = JSON.parse(text);\n * ```\n */\n export const loadFile = (accept: string): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n try {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = accept;\n input.style.display = \"none\";\n document.body.appendChild(input);\n\n const cleanup = () => {\n // Clear the value so the same file can be picked again next time\n input.value = \"\";\n document.body.removeChild(input);\n };\n\n input.onchange = () => {\n const file = input.files && input.files[0];\n cleanup();\n if (!file) {\n reject(new Error(\"No file selected\"));\n return;\n }\n resolve(file);\n };\n\n // Some browsers need a small timeout to ensure the element is attached\n // before programmatic click, but generally this works without it.\n input.click();\n } catch (error) {\n reject(error as Error);\n }\n });\n };\n \n /**\n * Triggers a download of a file from a string or Blob.\n * Creates a temporary download link and automatically clicks it to initiate the download.\n * The function handles both string content and Blob objects, and automatically cleans up\n * the created object URL after the download is initiated.\n *\n * @param content - The content to save, either a string or a Blob object\n * @param type - The MIME type of the content\n * @param name - The name of the file to be saved\n * \n * @example\n * ```js\n * // Download text content\n * saveAsFile(\"Hello World\", \"text/plain\", \"hello.txt\");\n * \n * // Download JSON data\n * saveAsFile(JSON.stringify({data: \"value\"}), \"application/json\", \"data.json\");\n * \n * // Download blob content\n * saveAsFile(imageBlob, \"image/png\", \"screenshot.png\");\n * ```\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 * Fetches the file content from the provided URL and creates a download link\n * to save it locally. The function handles the entire download process including\n * fetching, blob creation, and cleanup of temporary resources.\n *\n * @param url - The URL of the file to download\n * @param filename - The name of the file to be saved locally\n * @returns Promise resolving when the download is initiated\n * \n * @example\n * ```js\n * await downloadFile(\"https://example.com/image.jpg\", \"downloaded-image.jpg\");\n * // Browser will automatically download the file with the specified name\n * ```\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 ","/**\n * Detects the media type (image, video, or audio) of a given URL by sending a HEAD request.\n * Uses a lightweight HEAD request to fetch only the headers, avoiding download of the full file.\n * The function analyzes the Content-Type header to determine the media type category.\n *\n * @param url - The URL of the media file to analyze\n * @returns Promise resolving to the detected media type or null\n * \n * @example\n * ```js\n * // Detect image type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/image.jpg\");\n * // type = \"image\"\n * \n * // Detect video type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/video.mp4\");\n * // type = \"video\"\n * \n * // Detect audio type\n * const type = await detectMediaTypeFromUrl(\"https://example.com/audio.mp3\");\n * // type = \"audio\"\n * \n * // Invalid or inaccessible URL\n * const type = await detectMediaTypeFromUrl(\"https://example.com/invalid\");\n * // type = null\n * ```\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@twick/media-utils",
3
- "version": "0.14.11",
3
+ "version": "0.14.12",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",