# 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 |
| 1.0.5 (build 15) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.6 (build 16) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.7 (build 17) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.8 (build 18) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.9 (build 19) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.10 (build 22) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.11 (build 29) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.0.12 (build 30) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.1.0 (build 34) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.1.1 (build 36) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.1.2 (build 37) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.2.0 (build 38) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.2.1 (build 39) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.2.2 (build 40) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.2.3 (build 41) | ffmpeg / ffprobe / gifsicle | unchanged from 1.0.2 | same three SHA-256s as the 1.0.2 rows above | same source sets |
| 1.2.4 (selected local candidate) | ffmpeg | 9.0.2-neocodec-commercial | `a6e35d85f8566edac5944b45552a4035772b4f0a9ca5b933998e9436182957cf` | `neocodec-ffmpeg-9.0.2-commercial-source-f796441ef0ddcc252b5cfa5e50c7c45d2b7502a0752c5c5ca2a1423bf495eebc.tar.xz` |
| 1.2.4 (selected local candidate) | ffprobe | 9.0.2-neocodec-commercial | `141e155fd6c8cd7f7078512cb604fbc70ef8980c49d7780294d285ba588cd30f` | same FFmpeg 9.0.2 source archive |
| 1.2.4 (selected local candidate) | gifsicle | 1.96 | `9bb32495a20b9abb77e6be5f23db751aec59782b32d77cf13de5054b3caf46b7` | [`gifsicle-1.96/`](gifsicle-1.96/) |
| 1.2.5 (selected local candidate, build 47) | ffmpeg / ffprobe / gifsicle | unchanged from 1.2.4 | same three SHA-256s as the 1.2.4 rows above | same source sets |
| 1.2.5 (selected local candidate, build 48) | ffmpeg / ffprobe / gifsicle | unchanged from 1.2.4 | same three SHA-256s as the 1.2.4 rows above | same source sets |
| 1.2.6 (build 49) | ffmpeg / ffprobe / gifsicle | unchanged from 1.2.4 | same three SHA-256s as the 1.2.4 rows above | same source sets |
| 1.2.7 (consumed local candidate, build 50) | ffmpeg / ffprobe / gifsicle | unchanged from 1.2.4 | same three SHA-256s as the 1.2.4 rows above | same source sets |
| 1.2.7 (selected local candidate, build 51) | ffmpeg / ffprobe / gifsicle | unchanged from 1.2.4 | same three SHA-256s as the 1.2.4 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**, 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` and upload only new
   immutable names under `source/`. Mapping metadata uses `README-<full-sha256>.md`; the
   commercial notice uses `CORRESPONDING-SOURCE-COMMERCIAL-<full-sha256>.txt`. Upload the
   local documents at their exact manifest names. Retain both legacy objects and all prior
   qualified objects. Never overwrite retained names or remove historical source rows.
   Use the site's guarded `bun run deploy` to publish and verify source with the old
   installer/feed before promoting binaries. Keep the signed cut-time source snapshot
   unchanged. The `build-opensource-dir` skill records the complete workflow. Legacy
   metadata URLs retain historical bytes; /source links current qualified documents.

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).

## Qualified helper source candidate, 6 October 2026

The isolated 9.0.2 qualification uses authenticated official source and the same pinned
x264, SVT-AV1, libvpx and Opus sources. The source-only qualification completed on
6 October 2026 produced the following deterministic archive:

- Archive: [`neocodec-ffmpeg-9.0.2-commercial-source-f796441ef0ddcc252b5cfa5e50c7c45d2b7502a0752c5c5ca2a1423bf495eebc.tar.xz`](https://neocodec.com/source/neocodec-ffmpeg-9.0.2-commercial-source-f796441ef0ddcc252b5cfa5e50c7c45d2b7502a0752c5c5ca2a1423bf495eebc.tar.xz)
- SHA-256: `f796441ef0ddcc252b5cfa5e50c7c45d2b7502a0752c5c5ca2a1423bf495eebc`
- Size: 40,095,792 bytes.

This archive contains the current `COMMERCIAL-BUILD.md` disclosure and hardened
`package-corresponding-source.py`. Those are the only members that differ from the
retained `neocodec-ffmpeg-9.0.2-commercial-source.tar.xz`, SHA-256
`a8613b5fab18d662aca82bcd39918fbbd77a21d07db40a6f48fb212779446a72`.
The build recipe, configure flags and compiled source pins are unchanged. The archive
qualification reused the exact previously qualified helper pair; it did not rebuild helpers.
Both immutable archive names and their bytes must be retained.

Published Neocodec 1.2.6/build49 uses these helpers unchanged. They are also assigned unchanged
to the selected local Neocodec 1.2.7/build51 candidate. The retained 1.2.4, build 47, build 48,
build 49 and build 50 mappings remain above. This new assignment does not claim a signed, installed or public
1.2.7 release.
Neocodec 1.2.3/build41 uses FFmpeg 9.0. The unchanged 9.0.2 source archives were already published and verified for the build 46
test installers. The build 47, build 48 and build 49 mappings and notices are published. Build 50 was never distributed. The new build 51 metadata is local only; publish and verify
these new immutable metadata objects before distributing build 51. This assignment does
not claim a public app update.
The signed candidate needs its own final helper, source, SPDX and provenance joins.
