import { reclipPlaylist, reclipDownloadOne } from '../../reclip-client'; // The download-job executor — pure scripting, no agent. One phase: expand the playlist, then one download // request per item in the chosen format (audio/video), skipping anything that fails (private / deleted / // download error). No metadata prefetch — ReClip names the file from the video title itself. Emits a // compact `download:progress` snapshot (counters, not per-item events — a playlist can be thousands of // items). Throws on abort or a fatal error (playlist expansion); per-item errors are counted + skipped. export type DownloadProgress = { phase: 'expanding' | 'download' | 'done'; done: number; // downloaded successfully failed: number; // skipped (unavailable / download error) total: number; current?: string; // url of the item currently downloading }; export type DownloadEvent = { type: 'download:progress'; progress: DownloadProgress }; export type ExecuteDownloadParams = { jobId: string; userId: number; email: string; username?: string | null; inputs: Record; // { url, format: 'audio'|'video', absDir } cwd?: string; abortSignal: { aborted: boolean }; emit: (event: DownloadEvent) => void; }; const EMIT_THROTTLE_MS = 750; export async function executeDownload(params: ExecuteDownloadParams): Promise { const { url, format, absDir } = params.inputs; if (!url || !absDir) throw new Error('download job missing url or target directory'); const audioOnly = format !== 'video'; // default to audio const progress: DownloadProgress = { phase: 'expanding', done: 0, failed: 0, total: 0 }; let lastEmit = 0; const emit = (force = false) => { const now = Date.now(); if (!force && now - lastEmit < EMIT_THROTTLE_MS) return; lastEmit = now; params.emit({ type: 'download:progress', progress: { ...progress } }); }; const checkAbort = () => { if (params.abortSignal.aborted) throw new Error('aborted'); }; emit(true); // ── Expand ── let urls = [url]; if (url.includes('list=')) { const pl = await reclipPlaylist(url); if (pl.error) throw new Error(pl.error); if (pl.urls?.length) urls = pl.urls; } // ── Download: one request per item, skip failures ── progress.phase = 'download'; progress.total = urls.length; emit(true); for (const u of urls) { checkAbort(); progress.current = u; emit(true); try { await reclipDownloadOne({ url: u, destDir: absDir, audioOnly, signal: params.abortSignal }); progress.done++; } catch { checkAbort(); // an abort surfaces as a throw here — re-check so it stops instead of counting as a skip progress.failed++; } emit(); } progress.phase = 'done'; progress.current = undefined; emit(true); }