feat: Phase N power-user surface (terminal, palette, per-item, sort, url-templates)
- Built-in yt-dlp terminal: src/main/terminal.ts streams raw yt-dlp runs (binary fixed, gated on customCommandEnabled), new TerminalView + sidebar tab - Command palette (Ctrl/Cmd+K), portal-free CommandPalette.tsx wired in App - Per-playlist-item editing: per-row video/audio toggle + All video/All audio batch; addPlaylist enqueues via addMany with per-item kind - Weighted format sorting: DownloadOptions.formatSort -> raw -S (overrides codec) - URL-regex template auto-matching: CommandTemplate.urlPattern + selectExtraArgs matchesUrl, threaded through resolveExtraArgs; field in TemplateManager typecheck + test (192) + electron-vite build all clean. Roadmap: Phase N COMPLETE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -461,6 +461,60 @@ describe('sidecar files', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('format sorting (-S)', () => {
|
||||
it('emits a raw -S string when formatSort is set, overriding the codec tiebreaker', () => {
|
||||
const argv = build(opts(), dlo({ formatSort: 'res:1080,vcodec:av01', preferredVideoCodec: 'h264' }))
|
||||
expect(hasSeq(argv, '-S', 'res:1080,vcodec:av01')).toBe(true)
|
||||
expect(hasSeq(argv, '-S', 'res,fps,vcodec:h264')).toBe(false)
|
||||
})
|
||||
|
||||
it('falls back to the codec tiebreaker when formatSort is empty', () => {
|
||||
const argv = build(opts(), dlo({ formatSort: '', preferredVideoCodec: 'vp9' }))
|
||||
expect(hasSeq(argv, '-S', 'res,fps,vcodec:vp9')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectExtraArgs url-pattern matching', () => {
|
||||
const base = {
|
||||
customCommandEnabled: true,
|
||||
perDownloadExtraArgs: undefined,
|
||||
defaultTemplateId: null as string | null
|
||||
}
|
||||
const templates = [
|
||||
{ id: 'a', args: '--audio-quality 0', urlPattern: 'soundcloud\\.com' },
|
||||
{ id: 'b', args: '--write-thumbnail' }
|
||||
]
|
||||
|
||||
it('auto-applies a template whose urlPattern matches the URL', () => {
|
||||
const out = selectExtraArgs({ ...base, templates, url: 'https://soundcloud.com/x/y' })
|
||||
expect(out).toEqual(['--audio-quality', '0'])
|
||||
})
|
||||
|
||||
it('does not apply a urlPattern template to a non-matching URL', () => {
|
||||
const out = selectExtraArgs({ ...base, templates, url: 'https://youtube.com/watch?v=x' })
|
||||
expect(out).toEqual([])
|
||||
})
|
||||
|
||||
it('urlPattern match takes precedence over the global default template', () => {
|
||||
const out = selectExtraArgs({
|
||||
...base,
|
||||
defaultTemplateId: 'b',
|
||||
templates,
|
||||
url: 'https://soundcloud.com/x'
|
||||
})
|
||||
expect(out).toEqual(['--audio-quality', '0'])
|
||||
})
|
||||
|
||||
it('a bad regex never matches (and is skipped safely)', () => {
|
||||
const out = selectExtraArgs({
|
||||
...base,
|
||||
templates: [{ id: 'c', args: '--x', urlPattern: '(' }],
|
||||
url: 'https://soundcloud.com/x'
|
||||
})
|
||||
expect(out).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('chapters', () => {
|
||||
it('emits --split-chapters when splitChapters is on', () => {
|
||||
expect(build(opts(), dlo({ splitChapters: true }))).toContain('--split-chapters')
|
||||
|
||||
Reference in New Issue
Block a user