Store policy 10.2.2 forbids dynamic code inclusion, and this was the only third-party binary the app ever downloaded and ran. It had already failed in production: the pin aged out of BtbN's 14-day retention and the download 404'd on the creator's first real recording attempt (2026-09-01). Bundling kills that failure class permanently and deletes the startup network dependency. - scripts/fetch-ffmpeg.sh populates a gitignored ./ffmpeg from a pinned month-end BtbN LGPL-shared artifact, SHA-256 verified before extraction, idempotent, FFMPEG_ZIP= override. 135 MB of DLLs in git would tax every commit on this 9p tree; the URL + hash are the audit trail LGPL §6 wants. - ytLive.csproj copies ffmpeg\** to output and publish (Always), and a GuardBundledFfmpeg target fails the BUILD if the folder is missing, so a fresh clone finds out before a take rather than during one. - FfmpegLocator loses the downloader, the zip extraction and the %APPDATA%\ytLlive\tools cache. Order is now bundle -> PATH, deliberately inverted: a PATH ffmpeg may be a different major version or a GPL build, and the bundle is what was pinned, license-vetted and tested. A missing bundle throws loudly; the old "found something on disk" path could satisfy a missing bundle with a stale copy, and silent drift is the worst failure mode for a dependency certification inspects. - ffplay.exe (18 MB) is not shipped; nothing calls it. - THIRD-PARTY-NOTICES.txt: "downloads" -> "bundled" throughout, and the stale build tag autobuild-2026-08-09-13-03 — the tag that 404'd — corrected to autobuild-2026-08-31-13-27 with the exact artifact name and archive hash. OpenH264 made precise: compiled INTO avcodec-63.dll, no separate binary. Checked whether other third-party binaries needed bundling: no. e_sqlite3.dll and WebView2Loader.dll already ship via NuGet into runtimes\win-x64\native\, OpenH264 is inside avcodec-63.dll, the rest are managed. Two gaps remain that bundling cannot fix, recorded in the map: the WebView2 Evergreen runtime is a machine prerequisite (TASK 48 item 2), and Velopack's updater downloads and executes, so its teardown is a compliance action. Tests: 16 locator facts, suite 379/379, 0 warnings. The integration test runs the bundled ffmpeg -version, which is the only assertion that proves the sibling libav*.dll resolved — presence checks pass with the DLLs missing and a shared build then fails to start. TheLocatorCarriesNoNetworkCapability keeps 10.2.2 from rotting back. Analysis: TASKS/research-store-certification.md §4.
86 lines
4.2 KiB
C#
86 lines
4.2 KiB
C#
using System.IO;
|
|
using ytLive.Helpers;
|
|
|
|
namespace ytLive.Services.Encoder;
|
|
|
|
/// <summary>
|
|
/// The default <see cref="IFfmpegLocator"/>: resolve the <b>bundled</b> FFmpeg that ships
|
|
/// next to the app, falling back to PATH for anyone who deliberately installed their own.
|
|
///
|
|
/// <para><b>Nothing is downloaded.</b> This type used to pin a BtbN release URL, download
|
|
/// the zip on first run, and execute it — which is Windows Store policy 10.2.2 (dynamic
|
|
/// code inclusion, cited in <c>TASKS/research-store-certification.md</c> §4) and already
|
|
/// cost a real failure: the pin aged out of BtbN's 14-day retention and the download
|
|
/// <b>404'd on the creator's first real recording attempt</b> (2026-09-01). The binaries
|
|
/// are now fetched at BUILD time by <c>scripts/fetch-ffmpeg.sh</c> (pinned URL + SHA-256)
|
|
/// and copied into <c>ffmpeg/</c> beside the executable.</para>
|
|
///
|
|
/// <para><b>Order is bundled-first, deliberately.</b> The old code probed PATH first
|
|
/// ("the user's own install wins"). With a bundle in the package that is the wrong way
|
|
/// round: a random PATH <c>ffmpeg</c> may be a different major version or a GPL build, and
|
|
/// the bundle is the one we pinned, license-vetted, and tested against. PATH stays as the
|
|
/// escape hatch, which is what it is actually good for.</para>
|
|
///
|
|
/// <para>The lgpl-<b>shared</b> variant is a licensing choice, not a packaging one: dynamic
|
|
/// linking makes LGPL compliance "license text + source offer" with no static-relink
|
|
/// (LGPL §6) material required. That means the <c>libav*.dll</c> family must sit in the
|
|
/// same folder as the exe — Windows resolves them from the executable's own directory.</para>
|
|
/// </summary>
|
|
public sealed class FfmpegLocator : IFfmpegLocator
|
|
{
|
|
public const string FileName = "ffmpeg.exe";
|
|
|
|
/// <summary>Sibling probe binary — bundled alongside ffmpeg so the media
|
|
/// frame-rate probe (<see cref="ytLive.Services.FfmpegFrameRateProbe"/>) can
|
|
/// derive its path from the located ffmpeg directory.</summary>
|
|
public const string ProbeFileName = "ffprobe.exe";
|
|
|
|
/// <summary>Folder, relative to the app base directory, that the build copies the
|
|
/// bundled build into. One folder for the exe and every DLL.</summary>
|
|
public const string BundleFolderName = "ffmpeg";
|
|
|
|
/// <summary>Base directory the bundle is resolved against. Injectable so tests can
|
|
/// point at a temp tree; defaults to the running app's base directory.</summary>
|
|
private readonly string _bundleRoot;
|
|
|
|
private readonly string[] _searchDirs;
|
|
|
|
public FfmpegLocator(string[]? searchDirs = null, string? bundleRoot = null)
|
|
{
|
|
_searchDirs = searchDirs ?? ParsePath();
|
|
_bundleRoot = bundleRoot ?? AppContext.BaseDirectory;
|
|
}
|
|
|
|
public Task<string> LocateAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var bundled = Path.Combine(_bundleRoot, BundleFolderName, FileName);
|
|
if (IsUsable(bundled)) return Task.FromResult(bundled);
|
|
|
|
foreach (var dir in _searchDirs)
|
|
{
|
|
if (string.IsNullOrEmpty(dir)) continue;
|
|
var candidate = Path.Combine(dir, FileName);
|
|
if (IsUsable(candidate)) return Task.FromResult(candidate);
|
|
}
|
|
|
|
// Deliberately loud. The previous cache-then-download ladder had a "found
|
|
// something on disk" path that could satisfy a missing bundle with a stale copy,
|
|
// which is exactly the silent-drift a certification-gated dependency must not
|
|
// have. If the package forgot ffmpeg/, the app says so and stops.
|
|
throw new IOException(
|
|
$"FFmpeg was not found. The bundled copy is missing from " +
|
|
$"'{Path.Combine(_bundleRoot, BundleFolderName)}' — this build is incomplete " +
|
|
"(reinstall or re-fetch with scripts/fetch-ffmpeg.sh). Installing an LGPL-shared " +
|
|
"FFmpeg and putting 'ffmpeg' on PATH also works.");
|
|
}
|
|
|
|
private static bool IsUsable(string path) =>
|
|
File.Exists(path) && new FileInfo(path).Length > 0;
|
|
|
|
private static string[] ParsePath()
|
|
{
|
|
var raw = Environment.GetEnvironmentVariable("PATH") ?? "";
|
|
return raw.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
|
}
|
|
}
|