feat: queue UX (Phase M) + media editing (split-chapters, trim)
Phase L (media editing): - Split by chapters (--split-chapters) as a DownloadOptions toggle - Trim/cut by time range: parseTrimSections -> --download-sections "*A-B" + --force-keyframes-at-cuts (deduped vs SponsorBlock), per-download Trim panel Phase M (queue & daily-use UX), all 7: - Pause/resume: main-side killTree + paused flag (silent close), download:pause IPC, store pause/resume + paused status, QueueItem controls - Reorder via "Download next" (prioritize) - Aggregate progress strip (pure summarizeQueue) + combined speed/ETA - Drag-and-drop links / .url files onto the download card - Retry all failed - Duplicate detection (sameVideo) with "Download anyway" guard - Per-download scheduling (datetime-local) + save-for-later (saved status, 15s promotion ticker); session-only (queue not persisted) New pure modules unit-tested (queueStats); buildArgs trim/split tested. typecheck + test green (178 passed). Roadmap updated: Phase M COMPLETE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
buildArgs,
|
||||
parseExtraArgs,
|
||||
parseTrimSections,
|
||||
selectExtraArgs,
|
||||
formatCommandLine,
|
||||
sanitizeDirSegment,
|
||||
@@ -460,6 +461,71 @@ describe('sidecar files', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chapters', () => {
|
||||
it('emits --split-chapters when splitChapters is on', () => {
|
||||
expect(build(opts(), dlo({ splitChapters: true }))).toContain('--split-chapters')
|
||||
})
|
||||
|
||||
it('does not emit --split-chapters by default', () => {
|
||||
expect(build(opts(), dlo())).not.toContain('--split-chapters')
|
||||
})
|
||||
|
||||
it('emits --split-chapters independently of --embed-chapters', () => {
|
||||
const argv = build(opts(), dlo({ splitChapters: true, embedChapters: false }))
|
||||
expect(argv).toContain('--split-chapters')
|
||||
expect(argv).not.toContain('--embed-chapters')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseTrimSections', () => {
|
||||
it('normalises plain time ranges to *START-END specs', () => {
|
||||
expect(parseTrimSections('1:30-2:00')).toEqual(['*1:30-2:00'])
|
||||
expect(parseTrimSections('90-120')).toEqual(['*90-120'])
|
||||
expect(parseTrimSections('0:00:10.5-0:00:20')).toEqual(['*0:00:10.5-0:00:20'])
|
||||
})
|
||||
|
||||
it('splits on commas and newlines and trims whitespace', () => {
|
||||
expect(parseTrimSections(' 0:30-1:00 , 2:00-2:30 \n 3:00-3:30 ')).toEqual([
|
||||
'*0:30-1:00',
|
||||
'*2:00-2:30',
|
||||
'*3:00-3:30'
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps an existing leading * but does not double it', () => {
|
||||
expect(parseTrimSections('*1:00-2:00')).toEqual(['*1:00-2:00'])
|
||||
})
|
||||
|
||||
it('drops malformed tokens so garbage never reaches the argv', () => {
|
||||
expect(parseTrimSections('not-a-range')).toEqual([])
|
||||
expect(parseTrimSections('1:30')).toEqual([]) // no end
|
||||
expect(parseTrimSections('')).toEqual([])
|
||||
expect(parseTrimSections(undefined)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('trim (--download-sections)', () => {
|
||||
it('emits a --download-sections spec per range plus --force-keyframes-at-cuts', () => {
|
||||
const argv = build(opts({ trim: '0:30-1:00, 2:00-2:30' }), dlo())
|
||||
expect(hasSeq(argv, '--download-sections', '*0:30-1:00')).toBe(true)
|
||||
expect(hasSeq(argv, '--download-sections', '*2:00-2:30')).toBe(true)
|
||||
expect(argv).toContain('--force-keyframes-at-cuts')
|
||||
})
|
||||
|
||||
it('emits nothing when there is no trim', () => {
|
||||
const argv = build(opts(), dlo())
|
||||
expect(argv).not.toContain('--download-sections')
|
||||
})
|
||||
|
||||
it('does not double --force-keyframes-at-cuts when SponsorBlock-remove is also on', () => {
|
||||
const argv = build(
|
||||
opts({ trim: '0:30-1:00' }),
|
||||
dlo({ sponsorBlock: true, sponsorBlockMode: 'remove', sponsorBlockCategories: ['sponsor'] })
|
||||
)
|
||||
expect(argv.filter((a) => a === '--force-keyframes-at-cuts')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
// --- Collection folder paths (Phase G, media-manager) -----------------------
|
||||
|
||||
describe('sanitizeDirSegment', () => {
|
||||
|
||||
Reference in New Issue
Block a user