FFmpeg locator (TASK 4 ship step 2) + licensing: notices, About, LGPL-shared pin
IFfmpegLocator seam that resolves ffmpeg.exe (PATH -> cache -> pinned download), plus the licensing compliance that makes a paid GA product defensible. NOTE FOR USERS: this change shows NO difference in the app's behavior except a new top-bar "About" button (opens THIRD-PARTY-NOTICES.txt). It is scaffolding for the encoder/streaming work. - Services/Encoder/: IFfmpegLocator + FfmpegLocator. Pin is BtbN lgpl-shared autobuild-2026-08-09-13-03 (NOT gyan.dev/GPL or static: LGPLv2.1 §6 static relink material avoided by dynamic linking); extracts ffmpeg.exe + libav*.dll via a staging dir so a crash never leaves a partial cache - THIRD-PARTY-NOTICES.txt: LGPL/BSD/MIT notices + source offer, copied to the build output, surfaced by the About button; v1 gate = bundle full license texts (TASK 4 requirement 9) - ai.md "Licensing - do not violate" guardrails (never GPL/nonfree/static/latest- tag, never link FFmpeg in, never drop notices); TASKS.md + Services/index.md updated - Tests: FfmpegLocatorTests - hermetic decision-ladder integration test + shared- build DLL extraction + edge cases; docs updated (78 tests passing, 0 warnings)
This commit is contained in:
@@ -169,8 +169,8 @@ Preview shows the transition too (WYSIWYG). No wipes/slides/LUTs beyond the four
|
||||
|
||||
### Requirements:
|
||||
|
||||
1. **Encoding** — H.264 (hardware via NVENC/AMD, fallback x264) + AAC audio; **must comply**: keyframes ≤ 4s (gopSizeLong), closed GOP, AAC/MP3 @ 44.1/48kHz, mono/stereo only. **License posture (decided): GPL-free build** — NVENC (NVIDIA) / QSV (Intel) / AMF (AMD) + OpenH264 software fallback + built-in AAC; no libx264 (GPL contaminates a paid product). Output containers are identical either way (H.264+AAC in `.flv` for RTMP, `.mp4`/`.ts` for VOD) — the format is NOT the differentiator, the license and per-GPU quality are.
|
||||
2. **RTMP push** — **FFmpeg subprocess (decided)**: app feeds raw frames via stdin, parses stderr for health; one battle-tested binary does encode + FLV mux + push + reconnect. **Binary distribution (decided): check-then-pull** — probe `where ffmpeg`/PATH at first go-live; if absent, download a **pinned** build (~30 MB, standard gyan.dev/BtB N — no custom minimal build) to `%APPDATA%\ytLlive\tools\ffmpeg.exe` and cache it, offline-friendly. Behind an `IFfmpegLocator` seam so tests fake it. Push goes to the cached reusable stream's ingestion URL
|
||||
1. **Encoding** — H.264 (hardware via NVENC/AMD, fallback x264) + AAC audio; **must comply**: keyframes ≤ 4s (gopSizeLong), closed GOP, AAC/MP3 @ 44.1/48kHz, mono/stereo only. **License posture (decided): GPL-free build** — NVENC (NVIDIA) / QSV (Intel) / AMF (AMD) + OpenH264 software fallback + built-in AAC; no libx264 (GPL contaminates a paid product). Output containers are identical either way (H.264+AAC in `.flv` for RTMP, `.mp4`/`.ts` for VOD) — the format is NOT the differentiator, the license and per-GPU quality are. **License guardrails (never violate — see `ai.md` → "Licensing — do not violate"):** only BtbN `lgpl`/`lgpl-shared` builds; never GPL (gyan.dev) or `nonfree` (fdk-aac); never static for distribution (LGPL §6 relink material); never link FFmpeg into the app; never drop `THIRD-PARTY-NOTICES.txt` from the app/About screen.
|
||||
2. **RTMP push** — **FFmpeg subprocess (decided)**: app feeds raw frames via stdin, parses stderr for health; one battle-tested binary does encode + FLV mux + push + reconnect. **Binary distribution (decided): check-then-pull** — probe `where ffmpeg`/PATH at first go-live; if absent, download a **pinned** build (**BtbN LGPL win64 static** zip, ~75 MB — gyan.dev's builds are GPLv3 and ship libx264, which violates the license posture; BtbN's LGPL variant drops x264/x265 while keeping NVENC/QSV/AMF + libopenh264 + native AAC) to `%APPDATA%\ytLlive\tools\ffmpeg.exe` (extract just `ffmpeg.exe` from the zip) and cache it, offline-friendly. Behind an `IFfmpegLocator` seam so tests fake it (ship step 2, below). Push goes to the cached reusable stream's ingestion URL
|
||||
3. **Quality ladder** — the offered tiers, with **1080p60 @ 8 Mbps as the standard/default**:
|
||||
- 720p30 @ 6 Mbps
|
||||
- 720p60 @ 6 Mbps
|
||||
@@ -192,8 +192,10 @@ Preview shows the transition too (WYSIWYG). No wipes/slides/LUTs beyond the four
|
||||
5. **Health stats** — bitrate, FPS, dropped frames reported live in the bottom bar (encoder-side)
|
||||
6. **One-click go live** — defaults that work out of the box
|
||||
7. **Audio capture (feeds the meter — this task ships the wiring)** — WASAPI loopback (desktop/game at unity, zero UI — "it just is") + the picked mic (`MicSourceName` from the `MicPickerDialog`). The mic capture feeds `AudioLevel` so the realtime meter comes alive (today it reads 0 — the mixer feed is pending, see `ai.md` audio notes). AAC mono/stereo @ 48 kHz per the compliance rules.
|
||||
8. **Private-only go live until v1 (reputation guard, decided 2026-08-10)** — until the v1 release, go-live is **locked to private streams only** so a software error can never publish something public/unlisted that damages the creator's reputation. RTMP push itself has no privacy — privacy lives on the YouTube **live broadcast object**, which this app already controls via its OAuth API calls. So the lock is purely API-side: the Go Live flow always creates/updates the broadcast with `privacyStatus = "private"` and a guard **refuses** to set anything else (same spirit as the Live-only backdrop policy). The UI shows a clear "PRIVATE" badge next to the stream state so the creator always knows who can see them. Enforcement must be verifiable in the auth-service tests (fake the broadcast-insert/update call, assert `privacyStatus` is forced to private).
|
||||
9. **v1 release gate: bundle the full license texts (decided 2026-08-10)** — `THIRD-PARTY-NOTICES.txt` currently links the canonical license texts rather than embedding them. At the **v1 (GA) release**, the full texts of every license it names (LGPL v2.1+, BSD-2-Clause, MIT, Apache-2.0) MUST be bundled alongside it (shipped in the app output, e.g. a `licenses/` folder next to the notices file, still reachable from the About screen). This is a **release blocker for v1, not a task to queue early** — do it in the release pass. The repo should treat this like the private-only go-live gate: a checkbox that cannot silently lapse.
|
||||
|
||||
### Status: 🔶 In progress — **ship step 1 (the output compositor) SHIPPED** (2026-08-10); encoder/RTMP/audio follow it
|
||||
### Status: 🔶 In progress — **ship step 1 (the output compositor) SHIPPED** (2026-08-10); **ship step 2 (the FFmpeg locator) SHIPPED** (2026-08-10); encoder/RTMP/audio follow it
|
||||
|
||||
The pipeline chain the encoder needs doesn't exist yet: **scene compositing** (the master 1920×1080 frame
|
||||
without the preview's editing chrome) → **audio capture** (WASAPI, feeds the meter) → **H.264+AAC encode**
|
||||
@@ -266,6 +268,62 @@ made public (shared hex parse with the compositor — no duplicated color parsin
|
||||
cleaned up — build **0 warnings**. Tests: the `SceneCompositorTests` integration test (full-scene master
|
||||
pixels, vertical tier, flash) + 4 `StretchMath` units — **72 passing**.
|
||||
|
||||
#### Ship step 2 — FFmpeg locator (the encoder's binary)
|
||||
|
||||
**Goal:** resolve a usable `ffmpeg.exe` on demand (the encoder's one external dependency), never shipping
|
||||
a binary in the repo. Returns an absolute path; downloads only when neither PATH nor the local cache
|
||||
provides one.
|
||||
|
||||
**Decisions (locked 2026-08-10):**
|
||||
- **BtbN LGPL-shared win64 build** — not gyan.dev (gyan's "essentials" is GPLv3 and ships libx264, which
|
||||
violates requirement 1's license posture) and **not the static lgpl build**: LGPLv2.1 §6 wants
|
||||
relinkable object files for static linking, but the **shared** (dynamic-DLL) variant sidesteps that —
|
||||
compliance is "license text + source offer + unmodified binaries" (see `THIRD-PARTY-NOTICES.txt` and
|
||||
`ai.md` → Licensing). Drops libx264/libx265 while keeping NVENC/QSV/AMF, libopenh264 (the LGPL-legal
|
||||
H.264 software fallback) and native AAC — exactly the requirement-1 encoder profile.
|
||||
- **Pinned URL** — `https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-09-13-03/ffmpeg-master-latest-win64-lgpl-shared.zip`
|
||||
(~75 MB zip — earlier "~30 MB" estimate corrected). A dated autobuild tag is immutable; BtbN retention
|
||||
keeps the last 14 daily builds + each month-end build for 2 years, so a cold cache after retention
|
||||
expiry 404s — a logged, recoverable failure (the seam throws; the encoder step surfaces it). Once
|
||||
cached, the URL is never touched again. The pin is a single `const`, bumpable in one place — and must
|
||||
always stay on the **shared** variant (never `gpl`, `nonfree`, or static; see ai.md Licensing).
|
||||
- **Check-then-pull order** — (1) PATH probe (the user's own install wins), (2) cached
|
||||
`%APPDATA%\ytLlive\tools\ffmpeg.exe`, (3) download + extract. Extract `ffmpeg.exe` **plus the
|
||||
`libav*.dll` family** (the shared build's bin/ folder; Windows resolves the DLLs from the exe's own
|
||||
directory) into a staging dir then move into place — a crash never leaves a corrupt or partial cache.
|
||||
- **Seam** — `IFfmpegLocator.LocateAsync(CancellationToken)`: search dirs, tools dir, and the downloader
|
||||
(`Func<string, CancellationToken, Task<byte[]>>`) are constructor-injected with production defaults, so
|
||||
tests fake the network (feeding a real in-memory zip) and never touch disk outside a temp dir.
|
||||
|
||||
**New files (all in `Services/Encoder/`):**
|
||||
- `IFfmpegLocator.cs` — the seam.
|
||||
- `FfmpegLocator.cs` — the impl (PATH probe → cache → pull+extract exe + DLLs), failures logged via `AppLog`.
|
||||
- `THIRD-PARTY-NOTICES.txt` (repo root) — the LGPL/BSD/MIT notices + source offer, copied to the build
|
||||
output and surfaced via the top-bar **About** button (`MainWindow` code-behind, opens the file in the
|
||||
OS viewer).
|
||||
|
||||
**Test plan:** the hermetic integration test drives the full decision ladder against a temp tools dir and
|
||||
a fake downloader returning a real in-memory zip (`.../bin/ffmpeg.exe` entry): PATH hit wins without
|
||||
downloading, cache hit skips the network, cold cache downloads → extracts → `ffmpeg.exe` lands in the
|
||||
tools dir, and a second call serves the cache (downloader invoked exactly once). Focused unit tests:
|
||||
**shared-build DLLs extract alongside the exe**, empty zip throws, missing entry throws, empty download
|
||||
throws, downloader failure propagates, zero-byte cache is refreshed.
|
||||
|
||||
**Same-PR housekeeping:** requirement 2's stale binary facts corrected in this plan (~30 MB → ~75 MB zip;
|
||||
"gyan.dev/BtB N" → BtbN LGPL-shared only, with the why); the "never do" licensing guardrails recorded in
|
||||
`ai.md` so the reasoning survives.
|
||||
|
||||
**Out of scope (later ship steps):** the FFmpeg subprocess encoder (frames in via stdin, stderr health
|
||||
parsing), RTMP push, WASAPI audio capture, the frame-pipeline wiring, health stats.
|
||||
|
||||
**Built (2026-08-10):** `IFfmpegLocator` + `FfmpegLocator` shipped in `Services/Encoder/`, pinned to the
|
||||
**lgpl-shared** build `autobuild-2026-08-09-13-03` (extracts `ffmpeg.exe` + the `libav*.dll` family via a
|
||||
staging dir). `THIRD-PARTY-NOTICES.txt` (repo root) ships to the build output and is surfaced by a new
|
||||
top-bar **About** button; the "never do" licensing guardrails are recorded in `ai.md` — build **0 warnings**.
|
||||
Tests: the hermetic `FfmpegLocatorTests` integration test (PATH → cache → download decision ladder with a
|
||||
fake downloader serving a real in-memory zip) + edge/unit cases (shared-build DLL extraction, zero-byte
|
||||
cache refresh, empty payload, missing zip entry, downloader failure) — **78 passing**.
|
||||
|
||||
---
|
||||
|
||||
## TASK 5 — YouTube Live Stream Management
|
||||
|
||||
Reference in New Issue
Block a user