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

226 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `1080p``4K` 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):
```ts
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:
```ts
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**
```bash
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):
```ts
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**
```bash
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**
```bash
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"
```