# NextNote Windows FFmpeg Build

Reproducible, auditable Windows x64 FFmpeg 8.1.2 build containing only the
capabilities NextNote uses. Replaces the third-party Gyan `ffmpeg.exe` embedded
in NextNote Display, NextNote PowerPoint Helper, and NextNote Google Slides
Helper. See [PROJECT_PLAN.md](PROJECT_PLAN.md) for the full plan; work
proceeds milestone by milestone with a review stop at each exit criterion.

## Status

| Milestone | State |
|---|---|
| M0 — Baseline inventory | **Complete** — Windows capture passed on OB1-AAAA, 2026-07-22 |
| M1 — Pinned build environment | **Complete** — bootstrap + fetch/verify + tamper tests passed on OB1-AAAA, 2026-07-22 |
| M2 — Compatibility build | **Complete** — full build + test suite passed on OB1-AAAA, 2026-07-22 |
| M3 — Hardware qualification | Harness delivered — awaiting real-hardware testing (needs actual machines/GPUs) |
| M4 — Minimalisation | Audit + link-time reduction delivered — awaiting Windows rebuild + M3 results (see [MINIMISATION.md](MINIMISATION.md)) |
| M5 — Application integration | Implementation complete — awaits a release-ready signed artifact and application rebuild/upgrade tests |
| M6 — Release and source publication | Packaging automation complete — awaits hardware qualification, signing, public upload, and legal review |
| M7 — Windows Release Manager integration | **Complete** — candidate and production profile invokes the established build, package, and asset-update scripts |

## Milestone 0 artefacts

- [baseline/BASELINE.md](baseline/BASELINE.md) — baseline report: current
  archive/executable hashes, Gyan build identity, extractor defect record.
- [baseline/ffmpeg-invocations.json](baseline/ffmpeg-invocations.json) —
  machine-readable inventory of every FFmpeg command the three apps issue.
- [config/required-capabilities.json](config/required-capabilities.json) —
  required/forbidden capability sets consumed by the capability tests.
- [baseline/gyan-configure-line.txt](baseline/gyan-configure-line.txt) —
  configure line embedded in the current Gyan binary (static extraction).
- `baseline/capture-gyan-baseline.ps1` — **run this on Windows** to capture the
  live Gyan capability dumps and baseline CPU/hardware test results:

```bash
powershell -ExecutionPolicy Bypass -File "baseline\capture-gyan-baseline.ps1"
```

## Milestone 1 artefacts

- [config/dependencies.lock.json](config/dependencies.lock.json) — every source
  input pinned: exact version/commit, canonical URL, SHA-256, licence, and
  reason. Pins established 2026-07-22:

| Component | Pin |
|---|---|
| FFmpeg | 8.1.2 release tarball, PGP-verified against the release key |
| AMF headers | v1.5.2 (headers-only release asset, MIT) |
| nv-codec-headers | n13.0.19.1 (SDK 13.0.19; needs NVIDIA driver ≥ 570) |
| libvpl | v2.17.0 (statically linked dispatcher, MIT) |
| MSYS2 base | 2026-06-11 snapshot + explicit package set |
| 7-Zip | 26.02 (packaging tool only) |

- [config/ffmpeg-release-key.asc](config/ffmpeg-release-key.asc) — pinned FFmpeg
  release signing key, fingerprint `FCF986EA15E6E293A5644F10B4322F04D67658D8`
  (hash-locked in the dependency lock).
- [scripts/BuildCommon.psm1](scripts/BuildCommon.psm1) — single implementation
  of the fail-closed pinned downloader (HTTPS-only, redirect-host allowlist,
  SHA-256 before acceptance) shared by all scripts.
- [scripts/bootstrap-msys2.ps1](scripts/bootstrap-msys2.ps1) — extracts the
  pinned MSYS2 snapshot, installs the pinned package set, records the full
  package inventory and tool versions.
- [scripts/fetch-and-verify.ps1](scripts/fetch-and-verify.ps1) — downloads and
  verifies every pinned input; PGP-verifies the FFmpeg tarball; writes
  `verification-manifest.json`; `-TamperTest` proves fail-closed behaviour.
- [scripts/check-ffmpeg-update.ps1](scripts/check-ffmpeg-update.ps1) — checks
  the official FFmpeg download page against the pinned release without changing
  the dependency lock or any application assets.

### Checking for an FFmpeg update

```powershell
powershell -ExecutionPolicy Bypass -File "scripts\check-ffmpeg-update.ps1"
```

The default check never mutates the production pin. In Release Manager, use
**Check upstream and prepare latest candidate** to download and PGP-verify a
new release, then write a separate lock under `config/candidates/`. Select
that lock for an unsigned **candidate** build. The production lock and all
application assets remain unchanged until an explicit production publish.

### Running on Windows

The build lives under a **local, space-free work root** (default `C:\nnffb`) —
network drives (`Z:`) and paths with spaces are rejected because MSYS2 and
FFmpeg's build system fail on both. Run, in order:

```bash
powershell -ExecutionPolicy Bypass -File "scripts\bootstrap-msys2.ps1"
```

```bash
powershell -ExecutionPolicy Bypass -File "scripts\fetch-and-verify.ps1" -TamperTest
```

Outputs land in `C:\nnffb\downloads\` and `C:\nnffb\manifests\`
(`bootstrap-manifest.json`, `msys2-package-inventory.txt`,
`verification-manifest.json`).

## Milestone 2 artefacts

- [config/ffmpeg.configure.args](config/ffmpeg.configure.args) — the exact
  FFmpeg 8.1.2 `configure` argument list, derived directly from the real
  8.1.2 `configure` script and source (`libavcodec`/`libavformat`/
  `libavfilter`/`libavdevice` registration files) rather than guessed.
- [scripts/build-dependencies.sh](scripts/build-dependencies.sh) — installs
  nv-codec-headers and builds libvpl (static) inside MSYS2, producing the
  `ffnvcodec.pc`/`vpl.pc` files FFmpeg's `configure` looks up through
  `pkg-config`. AMF is headers-only and dynamically loaded at runtime, so it
  needs no build step.
- [scripts/build-ffmpeg.sh](scripts/build-ffmpeg.sh) — applies the checked-in
  configure args plus the path-dependent flags that can't live in a checked-in
  file (AMF include path, dependency `pkg-config` path, install prefix),
  builds and installs `ffmpeg.exe`, then checks the built binary's DLL
  imports to rule out a dependency on MSYS2/MinGW runtime DLLs.
- [scripts/build.ps1](scripts/build.ps1) — orchestrates bootstrap → fetch →
  extract → build-dependencies → build-ffmpeg → verify-binary → test, and
  writes a build record to `artifacts/<version>-<build-id>/`.
- [scripts/verify-binary.ps1](scripts/verify-binary.ps1) — confirms every
  checked-in configure flag appears in the built binary's own `-buildconf`
  output (the plan's "must exactly match" requirement).
- [test.ps1](test.ps1) + [tests/](tests/) — licence/capability tests
  (cross-checked against `required-capabilities.json`, including forbidden
  GPL items) and hardware encode/decode smoke tests using NextNote's exact
  command shapes.

The previous GPL candidate is retained only as historical build evidence. A
new Windows build is required after the 2026-07-24 LGPL policy change; only a
candidate whose `ffmpeg -L` output identifies the LGPL, whose configure line
contains no GPL/non-free flags, and whose encoder list contains no `libx264`
may be published.

### What the real build caught that source-reading alone would have missed

`configure`'s option surface looks complete from the top-level lists, but
several required capabilities are internal, transitively-selected
components with no direct `--enable-X` flag, discoverable only by actually
running the compiled binary against NextNote's exact commands. Each is now
protected in `required-capabilities.json` so a future minimisation pass
(Milestone 4) can't silently drop it again:

- **`buffer`/`buffersink` filters are mandatory** — `fftools/ffmpeg_filter.c`
  calls `avfilter_get_by_name` on them unconditionally; the CLI cannot run
  any video path without them. (The M0 assumption that a `null` filter is
  auto-inserted instead was wrong — no such call exists, and NextNote never
  issues `-vf null` — corrected.)
- **`dump_extra`'s `configure` name is `dump_extradata`** — derived from its
  C symbol (`ff_dump_extradata_bsf`), distinct from its runtime `-bsf:v`
  name (`.p.name` = `dump_extra`).
- **`filter_units` needs `cbs_h264` compiled in** to recognise H.264 at all,
  or it fails at runtime with "Codec 'h264' is not supported" despite being
  enabled. `cbs_h264` is FFmpeg's own `CONFIG_EXTRA` ("not available on the
  command line") and can only be pulled in transitively; `h264_metadata`
  (never invoked by NextNote) is the smallest-footprint bsf that selects it.
- **`rawvideo` is two separate components** — the muxer (enabled) and a
  distinct encoder `ff_rawvideo_encoder` (was missing). Display's
  `-f rawvideo -pix_fmt bgra` output still goes through avcodec's encode API
  via this passthrough codec even though no real encoding happens.
- **Fully static linking needs `--extra-ldflags=-static` explicitly** —
  `--enable-static`/`--disable-shared` only control FFmpeg's own library
  build type, not the GCC/C++/pthread *runtime*, which MinGW links as DLLs
  by default. The first complete build crashed instantly outside the MSYS2
  shell that built it (`STATUS_DLL_NOT_FOUND`) until this was added.
- **A third-party static library's own `.pc` file can be wrong** — libvpl's
  CMake-generated `vpl.pc` computes paths relative to `${pcfiledir}` in a way
  this MSYS2 `pkgconf` mis-joins on Windows, and never declared its C++
  runtime dependency (`-lstdc++`). Both patched in `build-dependencies.sh`
  after install.

Several Windows PowerShell 5.1 quirks surfaced along the way and are now
documented in code comments where they were fixed: values `return`ed from
inside a `try`/`finally` in a dot-sourced script were silently lost;
`[HashSet[T]]::new(...)`'s overload resolution is unreliable; a function
parameter named `$Args` collides with the automatic `$args` variable; and
`ffmpeg -buildconf`'s real format (one flag per line under a bare
`configuration:` header) differs from `-version`'s single-line format.

### Running on Windows

```bash
powershell -ExecutionPolicy Bypass -File "scripts\build.ps1"
```

This runs the full pipeline from a clean `C:\nnffb` (bootstrap and fetch are
skipped automatically if already done from Milestone 1 — pass `-SkipBootstrap
-SkipFetch` explicitly to force that; add `-SkipDependencies` once
nv-codec-headers/libvpl are already built, to iterate on FFmpeg's own
configure/build alone). Produces `C:\nnffb\dist\bin\ffmpeg.exe` and
`artifacts\8.1.2-<build-id>\`. The Windows Release Manager passes
`-ArtifactDirectory`, so its tested build record is instead kept inside the
manager's immutable release folder.

## Milestone 3 artefacts

Hardware qualification needs real machines with real GPUs and the actual
apps running — it can't be completed from a CLI alone. See
[hardware/HARDWARE_MATRIX.md](hardware/HARDWARE_MATRIX.md) for the full
procedure. Summary:

- `hardware/test-live-decoder.ps1` isolates the live decoder from Avalonia
  rendering. It connects to the Helper's raw H.264 TCP stream, feeds it to the
  exact FFmpeg Display decode command, drains BGRA output, measures effective
  frame rate, and writes a JSON report under `hardware/results/`.

- [hardware/probe-hardware-encoders.ps1](hardware/probe-hardware-encoders.ps1) —
  runs the exact NVENC/AMF/QSV probe command the helpers use at
  startup against a built `ffmpeg.exe`, classifying each encoder the same
  way `baseline/capture-gyan-baseline.ps1` did for the Gyan binary in
  Milestone 0, so results are directly comparable.
- [hardware/deploy-test-build.ps1](hardware/deploy-test-build.ps1) — points
  the real apps at a custom build for end-to-end testing by overwriting only
  the local extracted `%AppData%` copy — **never** the git-tracked
  `Assets\ffmpeg.7z` files, which stay untouched until Milestone 5. Always
  backs up the original Gyan binary first and is fully reversible
  (`-Action Restore`).
- [hardware/HARDWARE_MATRIX.md](hardware/HARDWARE_MATRIX.md) — per-machine
  checklist covering every item the plan's Hardware matrix section
  requires; results go in `hardware/results/`.

### Running on Windows

```bash
powershell -ExecutionPolicy Bypass -File "hardware\deploy-test-build.ps1" -Action Deploy
```

```bash
powershell -ExecutionPolicy Bypass -File "hardware\probe-hardware-encoders.ps1"
```

Then work through [hardware/HARDWARE_MATRIX.md](hardware/HARDWARE_MATRIX.md)
with the real apps, and when done:

```bash
powershell -ExecutionPolicy Bypass -File "hardware\deploy-test-build.ps1" -Action Restore
```

Exit criteria: AMD selected before QSV on an Intel/AMD hybrid machine; no
regression vs. the Milestone 0 Gyan baseline on previously-supported
NVIDIA or Intel hardware.

## Milestone 4 artefacts

Full record in [MINIMISATION.md](MINIMISATION.md). Summary:

- **Configure-surface audit:** nothing to remove. `config/ffmpeg.configure.args`
  was built from `--disable-everything` in Milestone 2 with every
  `--enable-*` traced to a real requirement, not trimmed down from a bloated
  default — so there was no fat left to cut at that level.
- **Link-time dead-code elimination** instead: `-ffunction-sections
  -fdata-sections` on FFmpeg's and libvpl's compiles, plus
  `-Wl,--gc-sections` on the final link, lets the linker discard unused code
  from the statically-linked libvpl archives (pulled in whole
  otherwise, even though only specific entry points are used). Plus
  `strip --strip-all` on the installed binary.
- **Licence audit:** every remaining enabled external component documented
  with its licence and whether it's statically linked (table in
  MINIMISATION.md) — nothing non-free present.

### Running on Windows

```bash
powershell -ExecutionPolicy Bypass -File "scripts\build.ps1"
```

A **full rebuild** — omit `-SkipDependencies` this time, since the
size-reduction flags touch libvpl's own compile, not just
FFmpeg's. Update the before/after size table in MINIMISATION.md with the
result once it's built and passes.

## Milestone 5 artefacts

- `scripts/update-nextnote-assets.ps1` verifies a release-ready package then
  atomically copies its identical `ffmpeg.7z` and `ffmpeg.manifest.json` to
  the Display, PowerPoint Helper, and Google Slides Helper asset folders.
  It refuses missing targets, hash mismatches, or a package that is not marked
  `release-ready`.
- `NextNote/Shared/FfmpegResourceExtractor.cs` is linked into all three apps.
  It hashes an existing extracted executable, reuses it only when it matches
  the embedded manifest, otherwise verifies the embedded archive and temporary
  executable before atomically replacing the canonical app-data file.
- Each application project embeds `Assets/ffmpeg.manifest.json` when present.
  The asset-update script creates this file alongside the replacement archive;
  it must be included in the next application rebuild.

## Milestone 6 artefacts

- `scripts/package-release.ps1` creates the complete signed release set:
  `ffmpeg.exe`, deterministic `ffmpeg.7z`, manifests, checksums, capability
  dumps, test evidence, notices, LGPL text, and an uploadable
  `nextnote-ffmpeg-8.1.2-sources.7z` containing the exact source inputs and
  build materials.
- `hardware/release-qualification.template.json` is the required approval
  record for the three supported hardware classes. Copy it to a real report and mark it
  passed only after the evidence in `hardware/results/` and
  `hardware/HARDWARE_MATRIX.md` is complete.

### Release handoff on Windows

The preferred path is **NextNote Windows Release Manager**: select
**NextNote FFmpeg (Windows x64)**, ensure the hardware qualification report is
present and passed, then start the production release. It passes one build ID
to `scripts/build.ps1`, signs a separate copy of the verified executable,
packages the release, and only then updates all three embedded application
archives. See `Windows Release Manager/docs/RELEASE_RUNBOOK.md` for the full
workflow.

The standalone commands remain available for diagnosis and recovery:

After a full M4 build, complete M3 on real hardware, sign `ffmpeg.exe` with
the established Visionmill Azure Trusted Signing workflow, and then run:

```powershell
powershell -ExecutionPolicy Bypass -File "scripts\package-release.ps1" `
  -ArtifactDirectory "artifacts\<version>-<build-id>" `
  -SignedFfmpegPath "<signed-ffmpeg.exe>" `
  -HardwareQualificationReport "hardware\release-qualification.json"
```

The command requires a valid Authenticode signature, a passed automated-test
record, and passed NVIDIA, AMD, and Intel qualification results. It
produces `artifacts\<version>-<build-id>-release\`. Review and publish that
release directory's matching source bundle before publishing the applications.

Then update the embedded application assets:

```powershell
powershell -ExecutionPolicy Bypass -File "scripts\update-nextnote-assets.ps1" `
  -ReleaseDirectory "artifacts\<version>-<build-id>-release"
```

Rebuild all three applications and execute the planned upgrade-over-existing
install tests: stale Gyan extraction is replaced, a matching extraction is
reused, and corrupt extraction is recovered without leaving a partial binary.

## Layout

The layout follows the plan's repository structure. Build state (source trees,
MSYS2, downloads, intermediates) lives under the local work root `C:\nnffb`,
outside the repo; a `work/` folder inside the repo is git-ignored as a
fallback. `artifacts/` retains completed build records.
