fix(ffmpeg): re-pin dead BtbN tag (first real recording 404'd), wrap download failures in actionable IOException
The 2026-08-09 pin was a DAILY build; BtbN retention aged it out and the cold-cache download 404'd on the creator's first native recording attempt (2026-09-01, startup.log). Re-pinned to the MONTH-END build autobuild-2026-08-31-13-27 (N-126342, lgpl-shared win64 — 2-year retention; tag+variant recorded in TASKS.md per the licensing rule). Verified alive: HEAD 200 + zip contents (bin/ffmpeg.exe, bin/ffprobe.exe, 7 libav DLLs) match the name-agnostic extractor. Also closes the wrap-gap: HttpRequestException escaped the locator untouched, surfacing a raw 'Response status code... 404' from the frame pump; now wrapped in IOException with the refresh-pin-or-install-ffmpeg message. FfmpegLocatorTests 7/7 (one updated, one added for the 404 case).
This commit is contained in:
@@ -25,9 +25,17 @@ public sealed class FfmpegLocator : IFfmpegLocator
|
|||||||
/// derive its path from the located ffmpeg directory.</summary>
|
/// derive its path from the located ffmpeg directory.</summary>
|
||||||
public const string ProbeFileName = "ffprobe.exe";
|
public const string ProbeFileName = "ffprobe.exe";
|
||||||
|
|
||||||
/// <summary>Pinned BtbN LGPL-shared win64 build (immutable autobuild tag; see TASKS.md).</summary>
|
/// <summary>
|
||||||
|
/// Pinned BtbN LGPL-shared win64 build — <c>autobuild-2026-08-31-13-27</c>, the
|
||||||
|
/// ffmpeg N-126342 build (immutable tag; recorded in TASKS.md per the licensing rule).
|
||||||
|
/// Deliberately a MONTH-END tag: BtbN retains the last ~14 dailies but month-end
|
||||||
|
/// builds for ~2 years — the previous pin (a 2026-08-09 daily) aged out of retention
|
||||||
|
/// and 404'd on a cold cache exactly as ai.md predicted (2026-09-01, the first real
|
||||||
|
/// recording attempt). Dated tags carry versioned asset names (ffmpeg-N-…-win64-lgpl-shared.zip);
|
||||||
|
/// extraction is name-agnostic.
|
||||||
|
/// </summary>
|
||||||
public const string PinnedUrl =
|
public const string PinnedUrl =
|
||||||
"https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-09-13-03/ffmpeg-master-latest-win64-lgpl-shared.zip";
|
"https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-31-13-27/ffmpeg-N-126342-gf88b741dbf-win64-lgpl-shared.zip";
|
||||||
|
|
||||||
private readonly string[] _searchDirs;
|
private readonly string[] _searchDirs;
|
||||||
private readonly string _toolsDir;
|
private readonly string _toolsDir;
|
||||||
@@ -65,6 +73,17 @@ public sealed class FfmpegLocator : IFfmpegLocator
|
|||||||
ExtractBinaries(zip, _toolsDir);
|
ExtractBinaries(zip, _toolsDir);
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
// The old gap: this exception type escaped unwrapped, so a dead pin surfaced
|
||||||
|
// as a raw "Response status code does not indicate success: 404" from the
|
||||||
|
// frame pump. Locator failures are IOException with an actionable message.
|
||||||
|
AppLog.Write(ex, "FFmpeg locator: download failed");
|
||||||
|
throw new IOException(
|
||||||
|
$"FFmpeg download failed ({ex.Message}). The pinned build may have aged out of " +
|
||||||
|
"retention — update FfmpegLocator.PinnedUrl, or install FFmpeg (LGPL-shared build) " +
|
||||||
|
"and make sure 'ffmpeg' is on PATH.", ex);
|
||||||
|
}
|
||||||
catch (Exception ex) when (ex is IOException or InvalidDataException or NotSupportedException)
|
catch (Exception ex) when (ex is IOException or InvalidDataException or NotSupportedException)
|
||||||
{
|
{
|
||||||
AppLog.Write(ex, "FFmpeg locator: download/extract failed");
|
AppLog.Write(ex, "FFmpeg locator: download/extract failed");
|
||||||
|
|||||||
@@ -342,10 +342,15 @@ provides one.
|
|||||||
compliance is "license text + source offer + unmodified binaries" (see `THIRD-PARTY-NOTICES.txt` and
|
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
|
`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.
|
H.264 software fallback) and native AAC — exactly the requirement-1 encoder profile.
|
||||||
2. **Pinned URL** — `https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-09-13-03/ffmpeg-master-latest-win64-lgpl-shared.zip`
|
2. **Pinned URL** — **RE-PINNED 2026-09-01**: `https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-31-13-27/ffmpeg-N-126342-gf88b741dbf-win64-lgpl-shared.zip`
|
||||||
(~75 MB zip — earlier "~30 MB" estimate corrected). A dated autobuild tag is immutable; BtbN retention
|
(BtbN **lgpl-shared** variant, ffmpeg N-126342; ~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
|
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
|
expiry 404s — a logged, recoverable failure (the seam throws; the encoder step surfaces it). The
|
||||||
|
first pin (`autobuild-2026-08-09-13-03`, a daily) aged out on 2026-09-01 — the first real recording
|
||||||
|
attempt — proving the rule; the new pin is deliberately the **month-end** build (2-year retention).
|
||||||
|
Dated tags carry versioned asset names (`ffmpeg-N-…-win64-lgpl-shared.zip`), extraction is
|
||||||
|
name-agnostic; download failures now wrap in `IOException` with an actionable message
|
||||||
|
("refresh `FfmpegLocator.PinnedUrl` or install ffmpeg on PATH") instead of leaking a raw 404. Once
|
||||||
cached, the URL is never touched again. The pin is a single `const`, bumpable in one place — and must
|
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).
|
always stay on the **shared** variant (never `gpl`, `nonfree`, or static; see ai.md Licensing).
|
||||||
3. **Check-then-pull order** — (1) PATH probe (the user's own install wins), (2) cached
|
3. **Check-then-pull order** — (1) PATH probe (the user's own install wins), (2) cached
|
||||||
|
|||||||
@@ -164,6 +164,23 @@ public class FfmpegLocatorTests
|
|||||||
{
|
{
|
||||||
var downloader = new RecordingDownloader { Error = new HttpRequestException("offline") };
|
var downloader = new RecordingDownloader { Error = new HttpRequestException("offline") };
|
||||||
var locator = new FfmpegLocator([], TempDir(), downloader.DownloadAsync);
|
var locator = new FfmpegLocator([], TempDir(), downloader.DownloadAsync);
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(() => locator.LocateAsync());
|
var ex = await Assert.ThrowsAsync<IOException>(() => locator.LocateAsync());
|
||||||
|
Assert.IsType<HttpRequestException>(ex.InnerException);
|
||||||
|
Assert.Contains("pinned build", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Locate_DeadPin404_ThrowsActionableLocatorError()
|
||||||
|
{
|
||||||
|
// 2026-09-01 incident: the pinned autobuild tag aged out of BtbN retention;
|
||||||
|
// the raw HttpRequestException must never surface from the pump again.
|
||||||
|
var downloader = new RecordingDownloader
|
||||||
|
{
|
||||||
|
Error = new HttpRequestException("Response status code does not indicate success: 404 (Not Found)."),
|
||||||
|
};
|
||||||
|
var locator = new FfmpegLocator([], TempDir(), downloader.DownloadAsync);
|
||||||
|
var ex = await Assert.ThrowsAsync<IOException>(() => locator.LocateAsync());
|
||||||
|
Assert.Contains("PATH", ex.Message);
|
||||||
|
Assert.Contains("PinnedUrl", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user