27cae28e12
describeDownloadError condenses a postprocessing failure to one line for the queue/notification, discarding the rest of yt-dlp's stderr. The persisted error log now also keeps the fuller non-noise transcript (stderrDetail) as an optional `detail` field, and the Diagnostics card gets a collapsible "Show details" expander per row so a failure like "Postprocessing: Conversion failed!" can be inspected -- and copied into a bug report -- without re-running the download from a terminal.
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
/**
|
|
* Shared yt-dlp stderr parsing. Used by both download.ts and probe.ts so the
|
|
* error-extraction logic lives in one place (audit M1).
|
|
*/
|
|
|
|
/** Trim yt-dlp's noisy stderr down to the most useful trailing error line. */
|
|
export function cleanError(stderr: string): string {
|
|
const lines = stderr
|
|
.split('\n')
|
|
.map((l) => l.trim())
|
|
.filter(Boolean)
|
|
const errLine = [...lines].reverse().find((l) => /^error/i.test(l))
|
|
return (errLine ?? lines[lines.length - 1] ?? '').replace(/^error:\s*/i, '').trim()
|
|
}
|
|
|
|
// Lines that carry no diagnostic value as post-processing context: per-tick
|
|
// download progress and the "Deleting original file" cleanup bookkeeping yt-dlp
|
|
// prints between merge steps.
|
|
const CONTEXT_NOISE = /^\[download\]|^Deleting original file/i
|
|
// A single context line, capped so a long output path can't bloat the message.
|
|
const CONTEXT_LINE_MAX = 200
|
|
|
|
/**
|
|
* Richer error for the DOWNLOAD failure path (probe/indexer/updater keep the
|
|
* concise cleanError line).
|
|
*
|
|
* yt-dlp collapses an ffmpeg post-processing failure to a bare
|
|
* "ERROR: Postprocessing: Conversion failed!" that names neither the step that
|
|
* died nor the reason — and without `--verbose` the underlying ffmpeg detail is
|
|
* never emitted at all. What yt-dlp DOES print is a tagged progress line per
|
|
* post-processor ([Merger], [ModifyChapters], [ExtractAudio], …; its phase tags
|
|
* like [download]/[info] are lowercase). So for that generic wrapper we append
|
|
* the last couple of non-noise trailing lines, which name the failing step (e.g.
|
|
* the SponsorBlock re-encode) and surface any ffmpeg diagnostic when one is
|
|
* present. Self-explanatory errors (a private video, a 403) are returned
|
|
* unchanged.
|
|
*/
|
|
export function describeDownloadError(stderr: string): string {
|
|
const headline = cleanError(stderr)
|
|
if (!headline || !/conversion failed|postprocessing/i.test(headline)) return headline
|
|
const lines = stderr
|
|
.split('\n')
|
|
.map((l) => l.trim())
|
|
.filter(Boolean)
|
|
const context: string[] = []
|
|
for (const line of [...lines].reverse()) {
|
|
if (context.length >= 2) break
|
|
if (CONTEXT_NOISE.test(line) || /^error/i.test(line)) continue
|
|
// Skip anything already conveyed by the headline (avoids echoing it back).
|
|
if (headline.includes(line) || line.includes(headline)) continue
|
|
const capped = line.length > CONTEXT_LINE_MAX ? `${line.slice(0, CONTEXT_LINE_MAX - 1)}…` : line
|
|
if (!context.includes(capped)) context.unshift(capped)
|
|
}
|
|
return context.length > 0 ? `${headline} — ${context.join(' · ')}` : headline
|
|
}
|
|
|
|
/**
|
|
* Full non-noise stderr, for the persisted error log's "Show details" expander
|
|
* (Diagnostics card) — everything describeDownloadError condenses to one line,
|
|
* kept in full (progress ticks and delete-bookkeeping still stripped) so a bug
|
|
* report carries the real yt-dlp/ffmpeg transcript instead of a paraphrase.
|
|
* Returns undefined when there's nothing beyond a single line — the headline
|
|
* the caller already shows — so a plain, self-explanatory error doesn't grow a
|
|
* details toggle with nothing new in it.
|
|
*/
|
|
export function stderrDetail(stderr: string): string | undefined {
|
|
const lines = stderr
|
|
.split('\n')
|
|
.map((l) => l.trim())
|
|
.filter((l) => l && !CONTEXT_NOISE.test(l))
|
|
return lines.length > 1 ? lines.join('\n') : undefined
|
|
}
|