Files
video-downloader/docs/superpowers/plans/2026-08-11-4k-support.md
T

11 KiB
Raw Blame History

4K Support Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add 2160p (4K) and 1440p (2K) as selectable download quality tiers, and update marketing copy that currently caps the advertised resolution at 1080p.

Architecture: The download pipeline already resolves quality generically — the probe (src/lib/ytdlp-probe.ts) filters a fixed QUALITIES list against the source video's max height, and the yt-dlp arg builder (src/lib/ytdlp.ts) turns any quality string into a height<=?N format selector. No pipeline logic changes; only the QUALITIES list, its test coverage, and static marketing copy change.

Tech Stack: TypeScript, Jest, next-intl (messages/*.json).

Global Constraints

  • Spec: docs/superpowers/specs/2026-08-11-4k-support-design.md
  • Add exactly two new tiers: 2160p and 1440p (no other resolutions).
  • No changes to src/lib/ytdlp.ts, worker/processor.ts, or the probe's filtering logic — both are already generic over the quality string.
  • Marketing copy change is a literal 1080p4K swap; wording around it stays otherwise identical.
  • No changes to src/lib/downloader-platforms.ts — it carries no resolution claim.

Task 1: Add 2160p/1440p to the quality list and probe test coverage

Files:

  • Modify: src/lib/ytdlp-options.ts:2
  • Modify: src/lib/__tests__/ytdlp-probe.test.ts (add a new test in the parseProbeOutput describe block, after the existing "detects a video with multiple qualities" test at line 40)

Interfaces:

  • Consumes: nothing new.

  • Produces: QUALITIES (from src/lib/ytdlp-options.ts) now includes '2160p' and '1440p', ordered highest-to-lowest: ['best', '2160p', '1440p', '1080p', '720p', '480p', '360p']. ytdlp-probe.ts and SubmitForm.tsx both import QUALITIES by name (unchanged) and iterate it in array order — later tasks and existing consumers see the two new entries automatically.

  • Step 1: Write the failing test

Add this test to src/lib/__tests__/ytdlp-probe.test.ts, right after the it('detects a video with multiple qualities and subtitles', ...) block (after line 40):

  it('includes 4K and 2K when the source video offers them', () => {
    const result = parseProbeOutput(json({
      formats: [
        { height: 1080, vcodec: 'avc1' },
        { height: 1440, vcodec: 'avc1' },
        { height: 2160, vcodec: 'avc1' },
      ],
    }))
    expect(result.availableQualities).toEqual(['best', '2160p', '1440p', '1080p'])
  })
  • Step 2: Run test to verify it fails

Run: npm test -- ytdlp-probe.test.ts Expected: FAIL — availableQualities is ['best', '1080p'] because QUALITIES does not yet contain '2160p'/'1440p'.

  • Step 3: Write minimal implementation

In src/lib/ytdlp-options.ts, replace line 2:

export const QUALITIES = ['best', '2160p', '1440p', '1080p', '720p', '480p', '360p'] as const
  • Step 4: Run test to verify it passes

Run: npm test -- ytdlp-probe.test.ts Expected: PASS — all tests in the file pass, including the existing 1080p-source case ('caps available qualities to the max height found' and 'detects a video with multiple qualities and subtitles'), which stay unaffected since 2160p/1440p are correctly filtered out when maxHeight is below them.

  • Step 5: Commit
git add src/lib/ytdlp-options.ts src/lib/__tests__/ytdlp-probe.test.ts
git commit -m "feat: add 4K and 2K quality tiers"

Task 2: Cover the 2160p format selector in the yt-dlp arg builder tests

Files:

  • Modify: src/lib/__tests__/ytdlp.test.ts (add a new test after the existing it('adds a height filter to -f when quality is not "best"', ...) block at line 41)

Interfaces:

  • Consumes: buildYtdlpArgs from src/lib/ytdlp.ts (unchanged signature: (params: YtdlpParams) => string[]), base fixture object defined at the top of the test file.

  • Produces: nothing new — this task only adds test coverage confirming buildYtdlpArgs already handles quality: '2160p' correctly (via its existing generic quality.replace('p', '') logic).

  • Step 1: Write the failing-if-broken test

Add this test to src/lib/__tests__/ytdlp.test.ts, right after the it('adds a height filter to -f when quality is not "best"', ...) block (after line 41):

  it('adds a height filter to -f for 4K quality', () => {
    const args = buildYtdlpArgs({ ...base, quality: '2160p' })
    const idx = args.indexOf('-f')
    expect(idx).toBeGreaterThan(-1)
    expect(args[idx + 1]).toContain('height<=?2160')
  })
  • Step 2: Run test to verify it passes

Run: npm test -- ytdlp.test.ts Expected: PASS immediately — buildYtdlpArgs already builds the filter generically from the quality string, so no source change is needed. This step confirms that generic behavior explicitly with a regression test.

  • Step 3: Commit
git add src/lib/__tests__/ytdlp.test.ts
git commit -m "test: cover 4K format selector in buildYtdlpArgs"

Task 3: Update marketing copy in all 4 locales to advertise 4K

Files:

  • Modify: messages/en.json (lines 52, 60, 79, 117, 166)
  • Modify: messages/fr.json (lines 52, 60, 79, 117, 166)
  • Modify: messages/es.json (lines 52, 60, 79, 117, 166)
  • Modify: messages/it.json (lines 52, 60, 79, 117, 166)

Interfaces:

  • Consumes: nothing — these are static i18n strings rendered by HowItWorksSection.tsx, FaqAccordion.tsx, and the platform FAQ pages (via next-intl), none of which need changes since only string content changes, not keys.
  • Produces: nothing consumed by later tasks.

Each file has exactly 5 occurrences of the literal substring 1080p, all of which should become 4K, at these keys:

  • home.formatsDesc

  • howItWorks.step2Desc

  • faq.a4

  • downloaderPages.youtube.faqA2

  • downloaderPages.vimeo.faqA2

  • Step 1: Update messages/en.json

Current (lines 52, 60, 79, 117, 166):

    "formatsDesc": "mp4, mp3, webm, mkv -- up to 1080p"
    "step2Desc": "Pick MP4, MP3, WebM or MKV and the quality you want, up to 1080p",
    "a4": "Video in MP4, WebM or MKV, or audio-only in MP3, up to 1080p depending on the source video.",
      "faqA2": "Up to 1080p, depending on the resolutions available for that specific video."
      "faqA2": "It depends on what the uploader made available, up to 1080p in most cases."

Replace every 1080p with 4K (5 occurrences in this file):

    "formatsDesc": "mp4, mp3, webm, mkv -- up to 4K"
    "step2Desc": "Pick MP4, MP3, WebM or MKV and the quality you want, up to 4K",
    "a4": "Video in MP4, WebM or MKV, or audio-only in MP3, up to 4K depending on the source video.",
      "faqA2": "Up to 4K, depending on the resolutions available for that specific video."
      "faqA2": "It depends on what the uploader made available, up to 4K in most cases."
  • Step 2: Update messages/fr.json

Current (lines 52, 60, 79, 117, 166):

    "formatsDesc": "mp4, mp3, webm, mkv — jusqu'en 1080p"
    "step2Desc": "Sélectionnez MP4, MP3, WebM ou MKV et la qualité souhaitée, jusqu'en 1080p",
    "a4": "Vidéo en MP4, WebM ou MKV, ou audio seul en MP3, jusqu'en 1080p selon la vidéo source.",
      "faqA2": "Jusqu'à 1080p, selon les résolutions disponibles pour cette vidéo en particulier."
      "faqA2": "Cela dépend de ce que l'auteur a mis à disposition, jusqu'à 1080p dans la plupart des cas."

Replace every 1080p with 4K (5 occurrences in this file):

    "formatsDesc": "mp4, mp3, webm, mkv — jusqu'en 4K"
    "step2Desc": "Sélectionnez MP4, MP3, WebM ou MKV et la qualité souhaitée, jusqu'en 4K",
    "a4": "Vidéo en MP4, WebM ou MKV, ou audio seul en MP3, jusqu'en 4K selon la vidéo source.",
      "faqA2": "Jusqu'à 4K, selon les résolutions disponibles pour cette vidéo en particulier."
      "faqA2": "Cela dépend de ce que l'auteur a mis à disposition, jusqu'à 4K dans la plupart des cas."
  • Step 3: Update messages/es.json

Current (lines 52, 60, 79, 117, 166):

    "formatsDesc": "mp4, mp3, webm, mkv — hasta 1080p"
    "step2Desc": "Selecciona MP4, MP3, WebM o MKV y la calidad deseada, hasta 1080p",
    "a4": "Vídeo en MP4, WebM o MKV, o solo audio en MP3, hasta 1080p según el vídeo de origen.",
      "faqA2": "Hasta 1080p, según las resoluciones disponibles para ese vídeo en concreto."
      "faqA2": "Depende de lo que haya puesto a disposición quien lo subió, hasta 1080p en la mayoría de los casos."

Replace every 1080p with 4K (5 occurrences in this file):

    "formatsDesc": "mp4, mp3, webm, mkv — hasta 4K"
    "step2Desc": "Selecciona MP4, MP3, WebM o MKV y la calidad deseada, hasta 4K",
    "a4": "Vídeo en MP4, WebM o MKV, o solo audio en MP3, hasta 4K según el vídeo de origen.",
      "faqA2": "Hasta 4K, según las resoluciones disponibles para ese vídeo en concreto."
      "faqA2": "Depende de lo que haya puesto a disposición quien lo subió, hasta 4K en la mayoría de los casos."
  • Step 4: Update messages/it.json

Current (lines 52, 60, 79, 117, 166):

    "formatsDesc": "mp4, mp3, webm, mkv — fino a 1080p"
    "step2Desc": "Seleziona MP4, MP3, WebM o MKV e la qualità desiderata, fino a 1080p",
    "a4": "Video in MP4, WebM o MKV, oppure solo audio in MP3, fino a 1080p a seconda del video originale.",
      "faqA2": "Fino a 1080p, in base alle risoluzioni disponibili per quel video specifico."
      "faqA2": "Dipende da cosa ha reso disponibile chi ha caricato il video, fino a 1080p nella maggior parte dei casi."

Replace every 1080p with 4K (5 occurrences in this file):

    "formatsDesc": "mp4, mp3, webm, mkv — fino a 4K"
    "step2Desc": "Seleziona MP4, MP3, WebM o MKV e la qualità desiderata, fino a 4K",
    "a4": "Video in MP4, WebM o MKV, oppure solo audio in MP3, fino a 4K a seconda del video originale.",
      "faqA2": "Fino a 4K, in base alle risoluzioni disponibili per quel video specifico."
      "faqA2": "Dipende da cosa ha reso disponibile chi ha caricato il video, fino a 4K nella maggior parte dei casi."
  • Step 5: Verify no stray 1080p remains in any locale file

Run: grep -rn "1080" messages/ Expected: no output (empty result).

  • Step 6: Run the full test suite

Run: npm test Expected: PASS — locale copy changes don't affect any test, and Tasks 12's new tests still pass.

  • Step 7: Commit
git add messages/en.json messages/fr.json messages/es.json messages/it.json
git commit -m "docs: advertise 4K support in marketing copy across all locales"