5de4b40c1d
yt-dlp collapses an ffmpeg postprocessing failure to a bare "Postprocessing: Conversion failed!" with no indication of which step died, and without --verbose the underlying ffmpeg reason is never emitted at all. describeDownloadError() appends the last non-noise trailing lines (e.g. the [ModifyChapters] SponsorBlock re-encode) so the error log and completion notification point at the actual cause instead of a dead end.
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { cleanError, describeDownloadError } from '../src/main/log'
|
|
|
|
describe('cleanError', () => {
|
|
it('returns the last ERROR line, stripped of its prefix', () => {
|
|
const stderr = [
|
|
'[youtube] gHiZA4O5PQM: Downloading webpage',
|
|
'ERROR: [youtube] gHiZA4O5PQM: Video unavailable. This video is private'
|
|
].join('\n')
|
|
expect(cleanError(stderr)).toBe(
|
|
'[youtube] gHiZA4O5PQM: Video unavailable. This video is private'
|
|
)
|
|
})
|
|
|
|
it('falls back to the last line when nothing matches /error/', () => {
|
|
expect(cleanError('one\ntwo\nthree')).toBe('three')
|
|
})
|
|
|
|
it('returns empty string for blank stderr', () => {
|
|
expect(cleanError('')).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('describeDownloadError', () => {
|
|
// A real non-verbose SponsorBlock-remove failure: yt-dlp emits only the
|
|
// [ModifyChapters] step lines and the bare wrapper error.
|
|
const modifyChaptersFailure = [
|
|
'[info] qkUTB65OfAc: Downloading 1 format(s): 137+251',
|
|
'[SponsorBlock] Found 1 segments in the SponsorBlock database',
|
|
'[Merger] Merging formats into "video.mp4"',
|
|
'Deleting original file video.f251.webm (pass -k to keep)',
|
|
'Deleting original file video.f137.mp4 (pass -k to keep)',
|
|
'[ModifyChapters] Re-encoding "video.mp4" with appropriate keyframes',
|
|
'[ModifyChapters] Removing chapters from video.mp4',
|
|
'ERROR: Postprocessing: Conversion failed!'
|
|
].join('\n')
|
|
|
|
it('names the failing post-processing step for the bare "Conversion failed!" wrapper', () => {
|
|
const msg = describeDownloadError(modifyChaptersFailure)
|
|
expect(msg.startsWith('Postprocessing: Conversion failed!')).toBe(true)
|
|
expect(msg).toContain('ModifyChapters')
|
|
})
|
|
|
|
it('skips [download] progress and "Deleting original file" bookkeeping as context', () => {
|
|
const msg = describeDownloadError(modifyChaptersFailure)
|
|
expect(msg).not.toContain('Deleting original file')
|
|
expect(msg).not.toMatch(/\[download\]/)
|
|
})
|
|
|
|
it('leaves a self-explanatory (non-postprocessing) error unchanged', () => {
|
|
const stderr = [
|
|
'[youtube] gHiZA4O5PQM: Downloading webpage',
|
|
'ERROR: [youtube] gHiZA4O5PQM: Video unavailable. This video is private'
|
|
].join('\n')
|
|
expect(describeDownloadError(stderr)).toBe(cleanError(stderr))
|
|
})
|
|
|
|
it('does not echo the headline back as its own context', () => {
|
|
const stderr = 'ERROR: Postprocessing: Conversion failed!'
|
|
expect(describeDownloadError(stderr)).toBe('Postprocessing: Conversion failed!')
|
|
})
|
|
|
|
it('caps an over-long context line', () => {
|
|
const longPath = 'x'.repeat(400)
|
|
const stderr = [
|
|
`[ModifyChapters] Re-encoding "${longPath}" with appropriate keyframes`,
|
|
'ERROR: Postprocessing: Conversion failed!'
|
|
].join('\n')
|
|
const msg = describeDownloadError(stderr)
|
|
expect(msg).toContain('…')
|
|
// headline + separator + a single capped context line, nowhere near 400 chars.
|
|
expect(msg.length).toBeLessThan(300)
|
|
})
|
|
|
|
it('returns empty string for blank stderr (falls through to the caller fallback)', () => {
|
|
expect(describeDownloadError('')).toBe('')
|
|
})
|
|
})
|