Snacks v2.16.0
Automated Media Library Transcoder
A feature release centered on AV1 and automation. AV1 hardware encoding is now calibrated on its own quantizer scale instead of the H.264 one it was inheriting — the old scale pinned av1_vaapi at near-lossless and made automatic calibration unable to ever hit the bitrate target — and per-folder AV1 overrides now actually encode as AV1 instead of silently falling back to the global codec. VAAPI encoder detection was probing only one power mode, which had already shipped a regression in each direction (dropping AMD or LP-only Intel encodes to software); it now probes both. And Snacks is now driveable headlessly: an API key for /api/*, plus environment-variable overrides that let you pin any encoder, auto-scan, or integration setting straight from your compose file.
AV1 automatic calibration
AV1 gets its own VAAPI quality scale
-global_quality is codec-scale dependent: h264_vaapi and hevc_vaapi take a 0–51 QP, but av1_vaapi maps the same flag onto the AV1 quantizer index (1–255, roughly 5× coarser). Snacks was feeding AV1 the H.264 scale, which pinned it at near-lossless quality — automatic calibration would ratchet toward the bitrate target and never reach it.
VaapiQualityScale(new) — a per-codec record of the CQP search bounds (Start,Min,Max,MaxStep,FixedCqp). AV1 encoders resolve to(Start: 120, Min: 90, Max: 255, MaxStep: 20, FixedCqp: 125); H.264/HEVC keep(Start: 24, Min: 18, Max: 51, MaxStep: 4, FixedCqp: 25). Calibration, the fixed-CQP fallback, and the encoder probe all read their numbers from here, so the two scales can never drift apart again.- AV1 VAAPI profile detection —
VAProfileAV1→av1is now recognized when probing the driver's capabilities, so an AV1-capable GPU is used instead of falling through to the static Elkhart Lake baseline (which under-reported AV1 support). - AV1 skip logic — the "VAAPI can't compress below target" skip and the below-target copy shortcut now understand an AV1 target the same way they already understood HEVC, so an already-small source isn't needlessly re-encoded.
Per-folder AV1 overrides now encode as AV1
The transcoding pipeline branches on Encoder (which GetEncoder, the skip logic, and cluster routing all read), not on Codec. The settings form keeps the two in sync, but the per-folder override dialog only wrote Codec — so a folder set to "AV1" still encoded with the global libx265.
EncoderOptionsOverride— when an override setsCodecwithout an explicitEncoder, the encoder is now re-derived (av1→libsvtav1,h264→libx264, elselibx265) so the override actually takes effect.
Hardware-acceleration aliases normalized on every load path
Legacy hardware aliases were only mapped during a settings restore through the browser form. Headless and cluster nodes deserialize settings.json directly and never run that form, so an alias like vaapi or nvenc fell straight through the encoder switches to silent software encoding.
EncoderOptions.HardwareAcceleration— normalization moved into the property setter, so every deserialization path is covered:nvenc/cuda→nvidia,qsv→intel,amf→amd,null/empty →auto.vaapiis vendor-ambiguous (both Intel and AMD use VAAPI on Linux) and maps toautoso hardware detection resolves the actual vendor.
ffprobe stdout preferred over "longest stream"
Some ffprobe builds write their JSON to stderr, so Snacks used whichever stream captured more content. A valid probe accompanied by many warning lines made stderr longer, and Snacks returned an empty ProbeResult for a perfectly probeable file.
FfprobeService— now prefers stdout whenever it actually contains JSON ({), only falling back to stderr when stdout has none.
VAAPI low-power probe fix
VAAPI encoder detection ran a single probe, but no single mode covers the hardware matrix:
- AMD (Mesa
radeonsi) exposes only the normal entrypoint and rejects-low_powerwithEINVAL. - LP-only Intel parts — Jasper Lake, Elkhart Lake, and the ADL-N / Twin Lake N-series (N95 / N100 / N305 / N355) — expose only the low-power entrypoint and reject plain CQP.
Probing one mode has shipped a regression in each direction already: low-power-only dropped every AMD encode to software; plain-only dropped LP-only Intel to software.
BuildEncoderProbeAttempts(new) — builds the ordered ffmpeg probe argument sets. Non-VAAPI encoders get a single bare attempt; VAAPI encoders are probed with the same CQP flags real encodes use, twice — plain first, then with-low_power 1. A pass on either mode means the encoder is usable (the real encode negotiates its own mode during calibration). Plain goes first so healthy AMD and full-featured Intel never pay for a second probe.TestEncoderAsync/RunEncoderProbeAsync— split so detection iterates the probe attempts and logs which variant (encodervsencoder (low_power)) passed, timed out, or failed.
API access & automation
Everything the pause button and scan trigger do is now available over HTTP, so orchestrators (Komodo, cron, CI, Home Assistant, …) can drive Snacks — e.g. pause encoding while backups run.
API key for /api/*
AuthConfig.ApiKey+SNACKS_API_KEY— with sign-in enabled,/api/*accepts an API key via theX-Api-Keyheader or anAuthorization: Bearertoken, for callers that can't do cookie login. A key can be generated in the UI (stored inauth.json, plaintext like the cluster shared secret — the config directory is the trust boundary) or supplied via theSNACKS_API_KEYenv var. Both work at once; the env key is honored independently and is never exposed in the UI. With sign-in disabled, no key is needed.AuthApiController(new) —GET /api/auth/apikey(reveal, never returns an env-supplied key),POST /api/auth/apikey/generate,DELETE /api/auth/apikey(leaves anySNACKS_API_KEYvalid).SecretCompare(new) — constant-time comparison for the presented key, and the auth middlewares now accept a valid API key as an alternative to a session cookie.- UI — an API Access section in Settings → Security to generate, reveal, and clear the stored key.
Configuration via environment variables
Any encoder, auto-scan, or integration setting can be pinned from your compose file instead of the GUI — handy for version-controlling config and recreating nodes. Env-pinned settings show a lock icon in the GUI and always win: they're applied on every load, never written into the config files, and removing the variable reverts to whatever's in the file.
EnvConfigOverrides(new) — resolves three prefixes over the config files:SNACKS_SET_<Property>→ encoder settings (settings.json), nested fields via__(SNACKS_SET_Codec=av1,SNACKS_SET_Music__BitrateKbps=256)SNACKS_SCAN_<Property>→ auto-scan (autoscan.json)SNACKS_INTEG_<Section>__<Property>→ integrations (integrations.json)
- Type-aware parsing — booleans (
true/false,1/0,yes/no,on/off), numbers/enums, comma-separated or JSON-array string lists, and full JSON for complex values. Invalid names or values are logged as warnings and skipped — a typo never blocks startup. - Runtime fields excluded —
QueuePausedandLastScanTimecan't be pinned (use the API to pause). On cluster nodes, per-job settings shipped from the master stay authoritative; node-localSNACKS_SET_*only affects encodes the node queues itself. - UI — locked settings render read-only with a lock indicator in the settings panels (encoder, auto-scan, integrations) so it's obvious what the environment is controlling.
Files Changed
AV1 calibration
Snacks/Services/TranscodingService.cs—VaapiQualityScaleper-codec CQP scale; AV1-aware calibration, skip logic, and VAAPI profile detectionSnacks/Models/EncoderOptions.cs—HardwareAccelerationalias normalization in the setterSnacks/Models/EncoderOptionsOverride.cs— re-deriveEncoderfrom aCodec-only overrideSnacks/Services/FfprobeService.cs— prefer stdout when it contains JSONSnacks/wwwroot/js/settings/encoder-form.js— codec/encoder form wiring
VAAPI low-power probe
Snacks/Services/TranscodingService.cs—BuildEncoderProbeAttempts,RunEncoderProbeAsync(new);TestEncoderAsynciterates both power modes
API access & environment overrides
Snacks/Controllers/AuthApiController.cs— API-key generate / reveal / delete (new)Snacks/Models/AuthConfig.cs—ApiKeyfieldSnacks/Services/AuthService.cs,AuthMiddleware.cs,ClusterAuthMiddleware.cs,SecretCompare.cs— API-key auth path and constant-time compare (SecretCompare new)Snacks/Services/EnvConfigOverrides.cs—SNACKS_SET_/SNACKS_SCAN_/SNACKS_INTEG_resolution (new)Snacks/Controllers/SettingsController.cs,AutoScanController.cs,IntegrationsController.cs— surface locked-by-env state; apply overrides on loadSnacks/Services/AutoScanService.cs,IntegrationService.cs,ClusterService.cs,RollingVerificationService.cs— honor env overridesSnacks/Views/Shared/_SecuritySettings.cshtml— API Access panelSnacks/wwwroot/js/api.js,js/settings/env-locks.js(new),js/settings/panels/{auth,auto-scan,integrations}-panel.js,css/site.css— key management UI and env-lock indicatorsREADME.md— API Access & Automation and Configuration via Environment Variables sectionsdeploy-compose.yml,docker-compose.yml,unraid/snacks.xml— example env vars
Tests
Snacks.Tests/Video/VaapiAv1CalibrationTests.cs,EncoderProbeTests.cs,HardwareAccelerationNormalizationTests.cs,RateControlAndScaleTests.cs,CalculateBitratesTests.cs— AV1 calibration, dual-mode probe, alias normalizationSnacks.Tests/Overrides/EncoderOptionOverrideTests.cs— codec-only override derives the encoderSnacks.Tests/Auth/ApiKeyAuthTests.cs— API-key acceptance and env-key precedenceSnacks.Tests/Settings/EnvConfigOverridesTests.cs,EnvLockedSaveTests.cs— env override parsing and locked-save behavior
Version bumps
Snacks/Controllers/HomeController.cs— health endpoint versionSnacks/Services/ClusterDiscoveryService.cs—ClusterVersionprotocol bump to 2.16.0Snacks/Views/Shared/_Layout.cshtml— footer versionREADME.md— version badge and footerbuild-and-export.bat— Docker tag versionelectron-app/package.json/package-lock.json— version
Full documentation: README.md