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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfhCF3fAgZyDN9BUsEyNFd
This commit is contained in:
Claude Fable 5 2026-08-31 08:57:56 +02:00
parent a302669384
commit 8a86500e80
2 changed files with 14 additions and 4 deletions

View File

@ -24,7 +24,15 @@
es.onmessage = (event) => { es.onmessage = (event) => {
const data = JSON.parse(event.data); 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); setTimeout(scrollToBottom, 0);
if (data.type === 'zip-ready') { if (data.type === 'zip-ready') {

View File

@ -88,7 +88,9 @@ async function runDownload(videoUrl, audioOnly, send, signal) {
'--no-playlist', '--no-playlist',
videoUrl videoUrl
], line => { ], 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: (.+)/); const sub = line.match(/\[info\] Writing video subtitles to: (.+)/);
if (sub) subtitlePaths.push(sub[1].trim()); if (sub) subtitlePaths.push(sub[1].trim());
const merge = line.match(/\[Merger\] Merging formats into "(.+)"/); 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)}`); send('info', `Extracting audio: ${basename(audioPath)}`);
await runProcess(FFMPEG, [ await runProcess(FFMPEG, [
'-i', videoPath, '-vn', '-acodec', 'libmp3lame', '-q:a', '2', '-y', audioPath '-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)}`); 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 settle = (fn, val) => { if (!settled) { settled = true; fn(val); } };
const handleData = data => 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.stdout.on('data', handleData);
proc.stderr.on('data', handleData); proc.stderr.on('data', handleData);