From 8a86500e804fa9dc7630e6cdd7918d158c72ba2d Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Mon, 31 Aug 2026 08:57:56 +0200 Subject: [PATCH] Collapse yt-dlp progress updates into a single updating log line yt-dlp rewrites its progress line in place via carriage return; the SSE stream forwarded every update as a separate log entry, so the UI piled up hundreds of near-identical boxes. Progress lines (yt-dlp download stats, ffmpeg frame/size lines) are now tagged as their own event type and the client replaces the previous progress entry instead of appending. The line splitter also splits on bare \r. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DfhCF3fAgZyDN9BUsEyNFd --- src/routes/+page.svelte | 10 +++++++++- src/routes/api/download/+server.js | 8 +++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 020b4f3..966bc20 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -24,7 +24,15 @@ es.onmessage = (event) => { const data = JSON.parse(event.data); - if (data.type !== 'zip-ready') log = [...log, data]; + if (data.type !== 'zip-ready') { + // Progress updates replace the previous progress line instead of + // piling up as new entries (yt-dlp rewrites that line in place) + if (data.type === 'progress' && log.at(-1)?.type === 'progress') { + log = [...log.slice(0, -1), data]; + } else { + log = [...log, data]; + } + } setTimeout(scrollToBottom, 0); if (data.type === 'zip-ready') { diff --git a/src/routes/api/download/+server.js b/src/routes/api/download/+server.js index 38fdf45..108d553 100644 --- a/src/routes/api/download/+server.js +++ b/src/routes/api/download/+server.js @@ -88,7 +88,9 @@ async function runDownload(videoUrl, audioOnly, send, signal) { '--no-playlist', videoUrl ], line => { - send('log', line); + // yt-dlp rewrites its progress line in place (carriage return); tag those + // updates so the client can replace the previous one instead of appending + send(/^\[download\]\s+[~\d]/.test(line) ? 'progress' : 'log', line); const sub = line.match(/\[info\] Writing video subtitles to: (.+)/); if (sub) subtitlePaths.push(sub[1].trim()); const merge = line.match(/\[Merger\] Merging formats into "(.+)"/); @@ -152,7 +154,7 @@ async function runDownload(videoUrl, audioOnly, send, signal) { send('info', `Extracting audio: ${basename(audioPath)}`); await runProcess(FFMPEG, [ '-i', videoPath, '-vn', '-acodec', 'libmp3lame', '-q:a', '2', '-y', audioPath - ], line => send('log', line), signal); + ], line => send(/^(?:frame|size)=/.test(line) ? 'progress' : 'log', line), signal); send('info', `Audio saved: ${basename(audioPath)}`); } @@ -182,7 +184,7 @@ function runProcess(cmd, args, onLine, signal, spawnOptions = {}) { const settle = (fn, val) => { if (!settled) { settled = true; fn(val); } }; const handleData = data => - data.toString().split('\n').filter(Boolean).forEach(onLine); + data.toString().split(/\r\n|\n|\r/).filter(Boolean).forEach(onLine); proc.stdout.on('data', handleData); proc.stderr.on('data', handleData);