# Neocodec — Corresponding Source

Neocodec (internal repo codename `vidconvert`) bundles three independent open-source command-line programs as separate helper
executables (invoked via `Process` + argv, never linked into the app):
**ffmpeg**, **ffprobe**, and **gifsicle**. All three are distributed under the GNU GPL —
ffmpeg and ffprobe under **GPLv3-or-later**, gifsicle under **GPLv2-only**.
This directory is the **complete corresponding source** for those helper binaries, hosted
next to the app download so buyers and redistributors can obtain, study, and rebuild them.

> **Staging notice.** This tree is the STAGING copy of the corresponding-source package,
> kept inside Neocodec's **private** development repo (internal codename `vidconvert`).
> **The public host is LIVE since 2026-07-14:** `https://neocodec.com/source` — every
> artifact in `source-manifest.json` is served from the site Worker's R2 bucket under the
> `source/` key prefix (`/source` = index page, `/source/<file>` = the artifact; upload +
> manifest-redeploy commands in the vidconvert-site README). The in-app Acknowledgments
> screen already links it; the download page and purchase-receipt email must link it too
> when commerce ships. Tarballs are gitignored here (served from R2); after re-fetching or
> adding a source set, regenerate `source-manifest.json`, re-upload, and redeploy the site.

## Licensing basis and duration

| Helper | Upstream license | Obligation satisfied here |
|---|---|---|
| ffmpeg / ffprobe | **GPLv3-or-later** (built `--enable-gpl --enable-version3`; the banner reads "version 3 of the License, or (at your option) any later version") | **GPLv3 §6(d)** — complete corresponding source hosted next to the binary, free of charge |
| gifsicle | **GPLv2-only** (no "or later" clause) | **GPLv2 §3(a)** — complete corresponding source accompanying the binary, free of charge |

Three GPL binaries under two GPL versions, so two separate obligations; this one
self-hosted package satisfies both.

**Duration — keep-while-served.** These sources must stay available **for as long as any
corresponding binary is distributed**, including older app versions still downloadable by
*any* route — the Sparkle appcast **or** a direct `/dl/` URL. Retiring a version from the
feed does not retire its zip, and does not end the obligation while that zip is still
served. Keep **one source set per shipped helper build, forever-while-served.**
Do **not** publish a written offer (GPLv2 §3(b) / GPLv3 §6(b)) — the written-offer route is
what carries the "3 years" clock and the noncommercial-only §3(c) pass-through; the hosted
route used here does not. Pointing customers at martin-riedl.de is **not** sufficient (the
GPLv2 §3(c) pass-through is noncommercial-only; a commercial seller must self-host).

## App version → helper binary (SHA-256) → source set

Helper SHA-256 values are the **vendored** arm64 binaries recorded in `Vendor/MANIFEST.md`
— the copies as they stand BEFORE bundle assembly, **not** the shipped ones in
`Contents/Helpers/`. Assembly re-signs every helper with Neocodec's own code signature,
which changes both its digest and its size, so a shipped helper will not match these values;
identify it with `ffmpeg -version` / `gifsicle --version` instead (same explanation the
bundled `CORRESPONDING-SOURCE.txt` gives recipients). Add a new row here whenever a shipped
app version changes any helper binary, and keep every referenced source set in place while
that app version is served.

| App version | Helper | Version (build) | Vendored binary SHA-256 | Source set |
|---|---|---|---|---|
| 0.1.0 (build 1)¹ | ffmpeg | 8.1.2 (1783011502) | `15d322a0576f050a22ed70490848f2590f13981a2eb00d5264f03a1a1357a758` | [`ffmpeg-8.1.2-build1783011502/`](ffmpeg-8.1.2-build1783011502/) |
| 0.1.0 (build 1)¹ | ffprobe | 8.1.2 (1783011502) | `206bfcf4ec2f4c94d552bd52bdda377cf3ecd7c1cef63f05db72c75731991889` | [`ffmpeg-8.1.2-build1783011502/`](ffmpeg-8.1.2-build1783011502/) (same build) |
| 0.1.0 (build 1)¹ | gifsicle | 1.96 | `9bb32495a20b9abb77e6be5f23db751aec59782b32d77cf13de5054b3caf46b7` | [`gifsicle-1.96/`](gifsicle-1.96/) |
| 0.2.0 (build 2)¹ | ffmpeg / ffprobe / gifsicle | unchanged from 0.1.0 | same three SHA-256s as above (helpers not rebuilt) | same source sets |
| 1.0.0 (build 3) | ffmpeg / ffprobe / gifsicle | unchanged from 0.1.0 | same three SHA-256s as above (helpers not rebuilt) | same source sets |
| 1.0.1 (build 4) | ffmpeg | 8.1.2-neocodec-commercial | `024a5a102c30d21fa0aaa4a6b560fc4b2816145261c3808e17beecfa98e56c54` | [`ffmpeg-8.1.2-build1783011502/`](ffmpeg-8.1.2-build1783011502/) plus [`COMMERCIAL-BUILD.md`](ffmpeg-8.1.2-build1783011502/COMMERCIAL-BUILD.md) |
| 1.0.1 (build 4) | ffprobe | 8.1.2-neocodec-commercial | `7f61f78ad6712a9498017eee7c9b386667883f01156cc164e27bf79d9f7ea7d6` | same commercial FFmpeg build |
| 1.0.1 (build 4) | gifsicle | 1.96 | `9bb32495a20b9abb77e6be5f23db751aec59782b32d77cf13de5054b3caf46b7` | [`gifsicle-1.96/`](gifsicle-1.96/) |
| 1.0.2 (build 6)² | ffmpeg | 9.0-neocodec-commercial | `8c8e6244084abc1c0eb730b51a766e3b474489689cbb395fe56e1db0fc959154` | `neocodec-ffmpeg-9.0-commercial-source.tar.xz` |
| 1.0.2 (build 6)² | ffprobe | 9.0-neocodec-commercial | `9c8f8db7ec1004a18715efa6216f6902b378c6557c94494b64cd621f529e7366` | same FFmpeg 9.0 source archive |
| 1.0.2 (build 6)² | gifsicle | 1.96 | `9bb32495a20b9abb77e6be5f23db751aec59782b32d77cf13de5054b3caf46b7` | [`gifsicle-1.96/`](gifsicle-1.96/) |
| 1.0.3 (build 12) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.4 (build 13) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |

¹ 0.1.0 and 0.2.0 are pre-1.0 development versions. They are **no longer offered by the
Sparkle appcast** — the live feed at `https://neocodec.com/appcast.xml` carries Neocodec
1.0.2/build 6 and 1.0.1/build 4. The 0.1.0/0.2.0 entries were retired from the feed, but
their zips stay reachable by direct `/dl/` URL, so both keep their rows.
Keep-while-served counts *any* route by which a binary is still obtainable, not just the
feed. Add new rows — never delete old ones — while any listed binary is served.

² 1.0.2/build 6 is published. Build 5 was consumed by an earlier notarized rehearsal that
predates the FFmpeg 9 integration and was not reused. These rows bind the build-6 assembled
helper inputs; the release gate rechecked the exact final bundle before publication.

Neocodec 1.0.0 was cut, notarized, and published, then retired from both the appcast and
direct public delivery because that historical build included HEVC. Its corresponding source
remains preserved. Its helpers were byte-identical to the earlier rows, so the existing
ffmpeg 8.1.2 and gifsicle 1.96 sets are its complete corresponding source.

Neocodec 1.0.1 (build 4) is the first notarized commercial/no-HEVC build. Its bundled
FFmpeg/ffprobe pair uses the reduced configuration documented by `COMMERCIAL-BUILD.md`,
`build-commercial-ffmpeg.sh`, and `commercial-configure-flags.txt`; gifsicle is unchanged.
This source mapping identifies the exact helper bytes; verify current distribution state
separately from the live appcast and `/source` manifest.

The 1.0.2/build-6 release upgraded only the commercial/default helper pair to FFmpeg 9.0.
Its five compiled source archives, official FFmpeg signature material, build recipe, and
recovery/package scripts live under `ffmpeg-9.0-commercial/`. The deterministic public
object `neocodec-ffmpeg-9.0-commercial-source.tar.xz` keeps that complete set in one file,
avoiding collisions with the historical 8.1.2 filenames in the site's flat namespace.
Neocodec 1.0.3/build 12 and 1.0.4/build 13 use those same FFmpeg, ffprobe, and gifsicle
inputs, so each release adds a mapping row above without creating or replacing a source archive.

## Layout

```
corresponding-source/
  README.md                                  ← this file (mapping + license basis + duration)
  ffmpeg-8.1.2-build1783011502/
    configure-flags.txt                      verbatim `ffmpeg -version` (the exact build config)
    COMMERCIAL-BUILD.md                      reduced no-x265 commercial recipe and rationale
    build-commercial-ffmpeg.sh               canonical, publishable commercial build script
    commercial-configure-flags.txt           published 1.0.1 build's exact FFmpeg configuration
    SOURCES.md                               reconciled library → version → source-tarball table
    fetch-sources.sh                         immutable-manifest verifier + staged HTTPS recovery
    test-fetch-sources.sh                    offline destructive/redirect/checksum negative suite
    SOURCE-URLS.tsv                          HTTPS URL → expected relative-path inventory
    libs/                                     (populated by fetch-sources.sh — 30 tarballs)
    ffmpeg-8.1.2.tar.bz2                      (populated by fetch-sources.sh)
    build-script-bb1d6db29c.tar.gz           (populated by fetch-sources.sh; martin-riedl pipeline, Apache-2.0)
    SHA256SUMS                               committed provenance; never rewritten by the fetcher
  ffmpeg-9.0-commercial/                    FFmpeg 9.0 candidate build/source workspace
    COMMERCIAL-BUILD.md                     exact no-HEVC commercial recipe rationale
    SOURCES.md                              five compiled pins + release-signature provenance
    build-commercial-ffmpeg.sh              canonical publishable FFmpeg 9.0 build
    fetch-sources.sh                        immutable-manifest verifier + staged HTTPS recovery
    verify-ffmpeg-signature.sh               isolated official-key fingerprint/signature gate
    package-corresponding-source.py          deterministic single-object source packager
  neocodec-ffmpeg-9.0-commercial-source.tar.xz
                                               complete collision-free FFmpeg 9 source object
  gifsicle-1.96/
    BUILD.md                                 source URL + "./configure && make" recipe
    gifsicle-1.96.tar.gz                     (staged from lcdf.org; present + published)
  licenses/
    GPL-3.0.txt   GPL-2.0.txt   LGPL-2.1.txt   AOM-Patent-License-1.0.txt
```

## Populating / re-populating the package

This tree is **already populated and published** (30 library tarballs + FFmpeg + the
build-script snapshot + `gifsicle-1.96.tar.gz` are all present locally and served from R2).
The steps below are the re-fetch runbook — the tarballs are gitignored, so a fresh clone
starts empty and must re-run them before it can republish.

1. `cd ffmpeg-8.1.2-build1783011502 && ./fetch-sources.sh --fetch`
   — downloads FFmpeg + all 30 library tarballs + the build-script snapshot into a fresh
   temporary tree, requires HTTPS for both initial URLs and redirects, verifies the exact
   `SOURCE-URLS.tsv`/`SHA256SUMS` path set and every committed hash, then promotes the set.
   The committed `SHA256SUMS` is immutable input; the no-argument/`--verify` mode is
   network-free and refuses missing, extra, symlinked, or changed artifacts.
   **Resolve the x264 pinning caveat first** (see `SOURCES.md` → "x264 pinning caveat"):
   the build script pulls x264 from the moving `master` archive, so pin the build-date
   commit for byte-exact source.
2. For FFmpeg 9.0, run `cd ffmpeg-9.0-commercial && ./fetch-sources.sh --fetch`, then
   `./verify-ffmpeg-signature.sh` and `./package-corresponding-source.py`. Re-run the
   packager and compare hashes to prove deterministic source output.
3. Download `gifsicle-1.96.tar.gz` into `gifsicle-1.96/` (see its `BUILD.md`) and record
   its SHA-256.
4. Republish: regenerate `source-manifest.json` with `gen-manifest.sh`, upload the changed
   objects to the site Worker's R2 bucket under the `source/` prefix, and redeploy — the
   `build-opensource-dir` skill drives this end to end. `/source` must stay linked
   everywhere the binaries are offered.

For a genuinely new source set, establish hashes independently from upstream
checksums/signatures and review them before committing its new `SHA256SUMS`. If an already
quarantined set needs a computed comparison, `./fetch-sources.sh --candidate
/tmp/SHA256SUMS.candidate` writes only that separate file and refuses the canonical
`SHA256SUMS` path; candidate output is evidence for review, never provenance by itself. Run
`./test-fetch-sources.sh` after changing the recovery script or URL inventory.

## What is here vs. not

- **ffmpeg/ffprobe** are ONE static build. `SOURCES.md` reconciles the binary's exact
  configure line (25 `--enable-lib*` + fontconfig + openssl) against the pinned build-script
  `version/` directory, adds the transitive static deps (fribidi, libogg, zlib), and flags
  the libraries the script *builds by default but does not link into this release*
  (libbluray, SDL2, system libiconv, and pure build tools) — excluded with justification.
- **License texts** in `licenses/` are the full verbatim GNU texts (from the SPDX
  license-list-data mirror) plus the Alliance for Open Media Patent License 1.0 (from
  aomedia.org), required for binary distribution of libsvtav1.
- **Not included by design:** the Neocodec app source itself (the app is proprietary and
  merely *aggregates* these GPL programs as separate executables — FSF #MereAggregation);
  Apple system libraries (System Library exception).
