diff --git a/src/lib/__tests__/ytdlp.test.ts b/src/lib/__tests__/ytdlp.test.ts new file mode 100644 index 0000000..dfa4530 --- /dev/null +++ b/src/lib/__tests__/ytdlp.test.ts @@ -0,0 +1,63 @@ +import { buildYtdlpArgs } from '../ytdlp' + +const base = { + url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + uuid: 'abc-123', + format: 'mp4', + quality: 'best', + subtitles: false, + extraArgs: null, +} + +describe('buildYtdlpArgs', () => { + it('always includes --no-playlist', () => { + expect(buildYtdlpArgs(base)).toContain('--no-playlist') + }) + + it('sets output path with uuid and %(ext)s template', () => { + const args = buildYtdlpArgs(base) + const idx = args.indexOf('-o') + expect(idx).toBeGreaterThan(-1) + expect(args[idx + 1]).toContain('abc-123') + expect(args[idx + 1]).toContain('%(ext)s') + }) + + it('adds format filter when quality is not "best"', () => { + const args = buildYtdlpArgs({ ...base, quality: '1080p' }) + expect(args).toContain('-f') + }) + + it('does not add -f flag when quality is "best"', () => { + expect(buildYtdlpArgs(base)).not.toContain('-f') + }) + + it('adds subtitle flags when subtitles is true', () => { + const args = buildYtdlpArgs({ ...base, subtitles: true }) + expect(args).toContain('--write-sub') + expect(args).toContain('--sub-lang') + }) + + it('does not add subtitle flags when subtitles is false', () => { + expect(buildYtdlpArgs(base)).not.toContain('--write-sub') + }) + + it('appends extra args from a JSON array string', () => { + const args = buildYtdlpArgs({ + ...base, + extraArgs: '["--sponsorblock-remove","all"]', + }) + expect(args).toContain('--sponsorblock-remove') + expect(args).toContain('all') + }) + + it('ignores malformed extraArgs JSON', () => { + expect(() => + buildYtdlpArgs({ ...base, extraArgs: 'not-json' }) + ).not.toThrow() + }) + + it('url is the last argument', () => { + const args = buildYtdlpArgs(base) + expect(args[args.length - 1]).toBe(base.url) + }) +}) diff --git a/src/lib/ytdlp.ts b/src/lib/ytdlp.ts new file mode 100644 index 0000000..ab56be8 --- /dev/null +++ b/src/lib/ytdlp.ts @@ -0,0 +1,39 @@ +import path from 'path' +import { config } from '../../config/app.config' + +export type YtdlpParams = { + url: string + uuid: string + format: string + quality: string + subtitles: boolean + extraArgs: string | null +} + +export function buildYtdlpArgs(params: YtdlpParams): string[] { + const { url, uuid, format, quality, subtitles, extraArgs } = params + const outputTemplate = path.join(config.STORAGE_PATH, `${uuid}.%(ext)s`) + + const args: string[] = ['--no-playlist', '-o', outputTemplate] + + if (quality !== 'best') { + const height = quality.replace('p', '') + args.push('-f', `${format}[height<=?${height}]+bestaudio/best[height<=?${height}]`) + } + + if (subtitles) { + args.push('--write-sub', '--sub-lang', 'fr,en') + } + + if (extraArgs) { + try { + const extra = JSON.parse(extraArgs) as string[] + args.push(...extra) + } catch { + // malformed extraArgs — skip silently + } + } + + args.push(url) + return args +}