docs: add implementation plan for 4K quality support
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
# 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 1–2'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"
|
||||
```
|
||||
Reference in New Issue
Block a user