github derekshreds/Snacks v2.17.0
Snacks v2.17.0

4 hours ago

Snacks v2.17.0

Automated Media Library Transcoder

A release centered on documentation, CI, and hardware-detection reliability. Snacks now ships a full in-app user guide, operations handbook, and public API reference at /docs/index.html, backed by a machine-readable OpenAPI contract at /openapi/v1.json — and a new GitHub Actions pipeline builds, tests, audits, smoke-boots, and validates that contract on every push. GPU auto-detection no longer gates a whole vendor on its HEVC probe alone: any passing codec now admits the device, and AMF gets a second explicitly-configured probe attempt for RDNA4 driver quirks. The login redirect is hardened against open-redirect crafting, the SignalR hub now requires authentication, and the version reported by /api/health, the cluster protocol, and the page footer now all derive from the single <Version> in Snacks.csproj instead of hand-maintained strings that each release had to remember to bump.


In-app documentation & public API contract

Documentation site at /docs/index.html

A self-contained user guide, operations handbook, and public API reference, served from wwwroot on every running instance (http://YOUR-SNACKS-HOST:6767/docs/index.html) and linked from the app footer and README. Covers setup, encoding behavior, cluster operation, port binding, API automation, and the full public route list — destructive operations are explicitly marked.

OpenAPI 3 contract at /openapi/v1.json

  • AddOpenApi in Program.cs — generates a machine-readable contract for the supported UI/public JSON API (/api/* and /metrics). Cluster RPC routes (/api/cluster/*) are an internal protocol and are deliberately excluded from the document.
  • scripts/validate-openapi.mjs (new) — asserts the document is OpenAPI 3, has a sane path count, contains /api/health and /api/queue/items, and leaks no internal cluster routes. CI runs it against a live server.

Port binding, documented at startup

The explicit Kestrel config overrides HTTP_PORTS / DOTNET_URLS / appsettings endpoints, which made moving the port a guessing game. Startup now logs the effective bind address plus the one variable that actually works (ASPNETCORE_URLS, e.g. http://0.0.0.0:7070), and the docs cover it.


Hardware detection

Any passing codec admits the device

Windows GPU detection gated each vendor on its HEVC probe alone — if HEVC init failed, the whole device was erased even when other codecs worked. That dropped h264-only NVENC silicon (Kepler / first-gen Maxwell) and RDNA4 cards where HEVC/AV1 AMF init fails while H.264 AMF works. NVENC, QSV, and AMF are now probed per-codec (HEVC, H.264, AV1) and the device is admitted when any probe passes — the same rule the Linux VAAPI path already used — with the codec list built from the actual probe results. macOS VideoToolbox gets the same treatment (HEVC + H.264).

AMF probed twice: bare, then an explicit session

Default-parameter AMF init alone can't be trusted to prove absence: an RX 9070 XT (RDNA4, Adrenalin 26.7.1) reported AMF undetected while an explicitly configured hevc_amf session encoded fine, and per-codec AMF init failures on RDNA4 are a known driver defect class.

  • BuildEncoderProbeAttempts — AMF encoders now get two attempts: bare defaults first (a pass skips the second run), then an explicit -usage transcoding -quality quality session with -qp_i 24 -qp_p 24 for H.264/HEVC. -qp_i/-qp_p are H.264/HEVC-only options and stay off the AV1 attempt.
  • Probe logging — attempts now carry a named variant (low_power, explicit_session) so the detection log shows exactly which probe form passed or failed.

Security

Login redirect hardened against open redirects

AuthController echoed the returnUrl query parameter into the post-login redirect, so a crafted login link could bounce a successful sign-in to an external site.

  • NormalizeReturnUrl (new) — accepts only application-local paths (matching ASP.NET Core's local-URL rules: /path and ~/path are local; scheme-relative //host and slash-backslash /\host variants are not), falling back to /. The redirect itself now goes through LocalRedirect as a second guard.

SignalR hub requires authentication

/transcodingHub was on the auth middleware allowlist, so the real-time progress hub was reachable without a session. It has been removed — hub connections now authenticate like every other page. The allowlist is down to the login form, secret-authenticated cluster RPC, the /api/health liveness probe, static assets, and the exact-match Prometheus /metrics scrape.


One version, everywhere

The version reported by /api/health, the cluster discovery protocol, and the page footer were hand-maintained hardcoded strings — every release had to bump each one individually (the 2.16.0 release notes list all three under "Version bumps").

  • AppVersion (new) — resolves the MSBuild <Version> from assembly metadata at runtime, stripping build metadata. The health endpoint, ClusterDiscoveryService.ClusterVersion, and the page footer all read from it now, so the per-release manual bumps disappear and the values can never drift from the csproj.
  • scripts/sync-version.mjs (new) — propagates the Snacks.csproj version to the electron package.json/package-lock.json, build-and-export.bat, the README badge/footer, the docs site, and the version test. --check mode fails when anything drifts, and CI runs it on every push.
  • AppController (new)api/health and api/restart moved out of HomeController into a dedicated attribute-routed API controller (which also puts them in the OpenAPI document).

CI pipeline

.github/workflows/ci.yml (new) — two jobs on every push and PR:

  • Backend — .NET 10 restore/build/test, a NuGet vulnerability audit (--vulnerable --include-transitive), then a smoke boot of the real server followed by live checks of /api/health, /docs/index.html, and the OpenAPI contract validator.
  • Frontendnpm ci, syntax checks on the electron main/notarize scripts, the version-sync check, and the node --test suites: every public controller route must appear in the HTML API reference, docs fragment links must resolve, and the browser modules (escapeHtml, queue API client, library path encoding) are exercised directly.

Operational logging

Cluster and transcoding diagnostics went to Console.WriteLine, so they reached the console but never the rolling application log.

  • Serilog everywhere — a global using Serilog plus conversion of all cluster, dispatch, transfer, scan, and probe messages to Log.Information/Log.Warning. Dispatch failures, node timeouts, heartbeat anomalies, and probe results are now retained in the file log.
  • Silent catches now log — empty catch { } blocks (cluster config read at startup, heartbeat capability parsing, operation-log persistence/reads, remote log lines) now emit warnings with context instead of swallowing the exception.
  • Log.CloseAndFlush() — the process now flushes the file sink on shutdown, so the last moments before an exit are no longer lost.

Refactoring & tests

  • ClusterCapacityPolicy (new) — the pure cluster scheduling rules (device enablement, effective per-device capacity, free-slot detection, dispatchable node states) extracted from ClusterService and covered by ClusterCapacityPolicyTests.
  • VideoTransformPlanner (new) — the pure video geometry/frame-rate planning (downscale policy and target height, scale expression, fixed-frame letterbox filter, fps cap, frame-rate parsing) extracted from TranscodingService, which now delegates.
  • AuthMiddleware.IsAllowlisted — made internal and locked down by AuthMiddlewareTests (hub requires auth, health/cluster/static stay open) and AuthRedirectTests (open-redirect vectors).
  • AppVersionTests — asserts the runtime version matches the released version and that the cluster protocol version tracks it; sync-version.mjs keeps the assertion current.
  • Electron test harness (new)api-contract.test.cjs, docs.test.cjs, frontend.test.cjs under node --test, wired into npm run check.

Dependency upgrades

  • SkiaSharp 2.88.8 → 4.150.1 — major bump; ImagePreprocessor migrated from the removed SKFilterQuality API to SKSamplingOptions with a cubic resampler (same high-quality scaling for OCR preprocessing).
  • EF Core 10.0.0 → 10.0.10, SQLitePCLRaw.bundle_e_sqlite3 3.0.4 pinned explicitly.
  • Microsoft.AspNetCore.OpenApi 10.0.10 (new), with Microsoft.OpenApi pinned to 2.7.5 to stay above the vulnerable 2.0.0 transitive floor.
  • Serilog.AspNetCore 9 → 10, Serilog.Sinks.File 6 → 7, Newtonsoft.Json 13.0.3 → 13.0.4, TesseractOCR 5.3.5 → 5.5.2.
  • Electron 41 → 43, @electron/notarize 2.5 → 3.1, electron-builder 26.8 → 26.15; package-lock.json regenerated.
  • Test stack — Microsoft.NET.Test.Sdk 17 → 18, xunit 2.9.3, FluentAssertions major bump (API rename BeGreaterOrEqualToBeGreaterThanOrEqualTo), coverlet 10.0.1.

Files Changed

Documentation & OpenAPI

  • Snacks/wwwroot/docs/index.html — user guide, operations handbook, and API reference (new)
  • Snacks/Program.cs — OpenAPI document with public-route filter; startup port-binding log; Serilog flush on exit
  • scripts/validate-openapi.mjs — contract validation (new)
  • Snacks/Views/Shared/_Layout.cshtml — footer Documentation link
  • README.md — documentation pointers

Hardware detection

  • Snacks/Services/TranscodingService.cs — per-codec vendor probes with any-pass admission (Windows NVENC/QSV/AMF, macOS VideoToolbox); AMF explicit-session probe attempt; named probe variants
  • Snacks.Tests/Video/EncoderProbeTests.cs — AMF two-attempt coverage, variant naming

Security

  • Snacks/Controllers/AuthController.csNormalizeReturnUrl + LocalRedirect open-redirect fix
  • Snacks/Services/AuthMiddleware.cs/transcodingHub removed from the allowlist
  • Snacks.Tests/Security/AuthMiddlewareTests.cs, AuthRedirectTests.cs — allowlist and redirect coverage (new)

Versioning

  • Snacks/AppVersion.cs — runtime version from assembly metadata (new)
  • Snacks/Controllers/AppController.csapi/health / api/restart extracted from HomeController (new)
  • Snacks/Services/ClusterDiscoveryService.cs — cluster protocol version derives from AppVersion
  • scripts/sync-version.mjs — version propagation with --check (new)
  • Snacks.Tests/Settings/AppVersionTests.cs — version lock (new)

CI

  • .github/workflows/ci.yml — backend build/test/audit/smoke/contract job + frontend checks job (new)
  • electron-app/package.jsontest, check, check:syntax, check:version, sync:version scripts
  • electron-app/tests/api-contract.test.cjs, docs.test.cjs, frontend.test.cjs — electron/browser test suites (new)

Logging

  • Snacks/GlobalUsings.cs — global Serilog using (new)
  • Snacks/Services/ClusterService.cs, ClusterNodeJobService.cs, ClusterFileTransferService.cs, ClusterDiscoveryService.cs, AutoScanService.cs, IntegrationService.cs, TranscodingService.cs, FileService.cs, RollingVerificationService.cs, NotificationService.cs, and controllers — Console.WriteLine → Serilog; empty catches now log

Refactoring

  • Snacks/Services/ClusterCapacityPolicy.cs — pure cluster scheduling rules (new), Snacks.Tests/Cluster/ClusterCapacityPolicyTests.cs (new)
  • Snacks/Services/VideoTransformPlanner.cs — pure video transform planning (new)
  • Snacks/Services/Ocr/ImagePreprocessor.cs — SkiaSharp 4 sampling API migration

Version bumps

  • Snacks/Snacks.csproj<Version>2.17.0</Version> (single source of truth) and package upgrades
  • Snacks.Tests/Snacks.Tests.csproj — test stack upgrades
  • electron-app/package.json / package-lock.json, build-and-export.bat, README.md, Snacks/wwwroot/docs/index.html — synchronized via sync-version.mjs

Full documentation: README.md · /docs/index.html on a running instance

Don't miss a new Snacks release

NewReleases is sending notifications on new releases.