Files
tts-workshop/packages/extract/src/download.ts
T

81 lines
1.9 KiB
TypeScript

import type { AssetRef } from '@tts/shared';
/** Guess a MIME type from a URL's file extension. */
export function guessMimeType(url: string): string {
const ext = url.split('?')[0]!.split('.').pop()!.toLowerCase();
switch (ext) {
case 'png':
return 'image/png';
case 'jpg':
case 'jpeg':
return 'image/jpeg';
case 'gif':
return 'image/gif';
case 'webp':
return 'image/webp';
case 'pdf':
return 'application/pdf';
default:
return 'application/octet-stream';
}
}
/** Download a single asset as a `Blob`. */
export async function downloadAsset(url: string): Promise<Blob> {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download asset (${res.status}): ${url}`);
}
return res.blob();
}
export interface DownloadProgress {
done: number;
total: number;
url: string;
}
export interface DownloadOptions {
concurrency?: number;
onProgress?: (p: DownloadProgress) => void;
}
export interface DownloadedAsset {
ref: AssetRef;
blob: Blob;
}
/**
* Download multiple assets with limited concurrency and a progress callback.
*/
export async function downloadAll(
refs: AssetRef[],
options: DownloadOptions = {},
): Promise<DownloadedAsset[]> {
const concurrency = options.concurrency ?? 4;
const results: DownloadedAsset[] = [];
let done = 0;
const worker = async (ref: AssetRef): Promise<void> => {
const blob = await downloadAsset(ref.url);
results.push({ ref, blob });
done += 1;
options.onProgress?.({ done, total: refs.length, url: ref.url });
};
const queue = [...refs];
const workers: Promise<void>[] = [];
for (let i = 0; i < Math.min(concurrency, queue.length); i++) {
workers.push(
(async () => {
while (queue.length > 0) {
const ref = queue.shift()!;
await worker(ref);
}
})(),
);
}
await Promise.all(workers);
return results;
}