Snacks v2.19.1
Automated Media Library Transcoder
A point release that fixes HEVC playback on Apple devices. ffmpeg's MP4 muxer writes HEVC with the hev1 sample entry by default, which AVFoundation / VideoToolbox players (Safari, iOS, tvOS, QuickTime, Infuse, Plex on Apple hardware) refuse to render — the file plays audio over a black screen. Every HEVC stream Snacks writes into an MP4 container is now tagged hvc1, whether it was encoded by libx265 / NVENC / QSV / VAAPI / AMF / VideoToolbox or stream-copied from an HEVC source. Dolby Vision copies are handled by profile so they stay Dolby Vision instead of silently dropping to the base layer, and advanced profiles that take ownership of the tag are left alone (with a validator warning when a Dolby Vision profile forgets to).
It also fixes hardware decode of 10-bit H.264 sources: High 10 / 4:2:2 / 4:4:4 H.264 has no fixed-function decoder on any GPU, so those files now software-decode into the hardware encoder instead of failing every packet under QSV / VAAPI / NVDEC. The TesseractOCR package moves to 5.5.2.
HEVC in MP4 is tagged hvc1
TranscodingService.GetVideoTagArguments (new) returns the literal output tokens that fix the MP4 sample entry, and the ffmpeg command builder places them right after the video codec flags — before the audio, subtitle, and muxer options, and before the output path.
| Case | Emitted | Why |
|---|---|---|
HEVC encode into MP4 (any h265 encoder in VideoEncoderRegistry)
| -tag:v hvc1
| ffmpeg defaults to hev1; Apple wants hvc1
|
| HEVC stream copy into MP4, no Dolby Vision | -tag:v hvc1
| Untagged MKV sources ([0][0][0][0]) and hev1 sources are retagged; hvc1 is reaffirmed
|
| HEVC copy of a Dolby Vision profile 5 source | -strict unofficial -tag:v dvh1
| Profile 5 has no compatible base layer and must be dvh1
|
| HEVC copy of a Dolby Vision profile 8 source | -strict unofficial -tag:v hvc1
| Profile 8 is HDR10 / HLG-compatible; Apple wants hvc1
|
| HEVC copy of another Dolby Vision profile (4, 7, …) | nothing | Dual-layer profile 7's enhancement layer is not carried into MP4; legacy profiles are left to the muxer |
HEVC copy whose only Dolby Vision signal is a dvh1 / dvhe tag
| nothing | The source tag stands |
HEVC copy from a dvh1 / dvhe MP4 into MKV
| -tag:v hvc1
| Avoids an inherited tag Matroska rejects; the output keeps its normal HEVC mapping and Dolby Vision metadata |
| Non-HEVC video, WebM, or other MKV output | nothing | No sample-entry adjustment is needed |
Dolby Vision copies keep their configuration record
ffmpeg's MP4 muxer writes the Dolby Vision dvcC / dvvC box under -strict unofficial or a more permissive level; without it a stream copy of a Dolby Vision source quietly loses the record and becomes plain HDR10 (or, for profile 5, unwatchable). Snacks now reads the DOVI configuration record from ffprobe's per-stream side_data_list — the only place an MKV source carries Dolby Vision, since its codec tag is always [0][0][0][0] — and raises the compliance level together with the profile's correct tag. The job log records "Dolby Vision source: keeping its configuration record in the MP4 copy." when it does. Ordinary HEVC encodes receive hvc1; Dolby Vision re-encodes need an advanced profile configured for Dolby Vision, including the sample entry and muxer compliance level.
Advanced profiles own their tag
An advanced video profile that emits a codec tag option (-tag:v, -tag:v:0, -vtag) or drives a Dolby Vision encode (x265 dolby-vision-* params or ffmpeg's -dolbyvision) is responsible for the sample entry, and Snacks adds nothing (AdvancedProfileOwnsVideoTag, new; logged as "Advanced profile owns the MP4 sample entry; not adding a video tag."). -tag:a and other non-video tags do not count. Disabling Dolby Vision with -dolbyvision 0 / false or dolby-vision-profile=0 keeps automatic hvc1 tagging enabled; unrelated filenames containing "dolby" do not affect it. Repeated options use the last applicable value.
Mp4SampleEntryOptions (new) holds the shared rules for video-tag options, effective Dolby Vision settings, muxer compliance, and the side-data label used by the transcoder and validator.
Validator warning: dolby_vision_sample_entry
AdvancedVideoValidator now warns when a profile's custom options enable Dolby Vision but do not also set both -tag:v and a muxer compliance level that writes the configuration record. -strict normal, an empty value, or a later restrictive override still warns. unofficial / -1 and experimental / -2 are accepted; stream-specific encoder compliance does not set muxer compliance:
Dolby Vision options make this profile responsible for the MP4 sample entry: add
-tag:v(dvh1for profile 5,hvc1for profile 8) and-strict unofficialso the Dolby Vision configuration box is written.
It is a warning, not an error — the profile still runs — because the author may be targeting MKV, where none of this applies.
Probe model
ProbeResult.Stream gains SideDataList, and the new StreamSideData models just enough of an ffprobe side-data entry to recognise a Dolby Vision record: side_data_type, dv_profile, and dv_bl_signal_compatibility_id. Other side-data entries (content light level, mastering display metadata, …) deserialize with only their type and are ignored.
Hardware decode skips H.264 High 10 / 4:2:2 / 4:4:4
The hardware-decode gate (CanVaapiDecode) only looked at the source codec name, so a 10-bit H.264 file (profile High 10, yuv420p10le) was sent through the full -hwaccel qsv / -hwaccel vaapi / -c:v h264_cuvid pipeline. No consumer Intel, AMD, or NVIDIA GPU decodes H.264 outside 8-bit 4:2:0, so every packet failed (QSV: "Error querying IO surface: unsupported" / "Error submitting packet to decoder: Function not implemented") and the job never produced a frame.
TranscodingService.IsHwDecodableVideoProfile (new) rejects H.264 whose ffprobe profile or pix_fmt indicates High 10, High 4:2:2, or High 4:4:4 (either signal is enough; unknown values are treated as decodable). It is applied in three places:
- Both
CanVaapiDecodeoverloads — the vainfo-detected codec set can't override it, since vainfo reports codec-level profiles (VAProfileH264High) and never High 10. This covers Linux Intel QSV, VAAPI, and the VAAPI quality calibration pass. - The NVIDIA path —
-c:v h264_cuvidis not forced on such sources, leaving-hwaccel cuda's auto-attach to fall back to the software h264 decoder. - The job log — "Using software decode + QSV encode (source profile has no hardware decoder)" explains the choice up front instead of only on the retry path.
The result is the same command the software-decode retry already used (-init_hw_device … -filter_hw_device hw with no -hwaccel): ffmpeg's native decoder runs on the CPU, the hardware encoder (hevc_qsv, hevc_vaapi, hevc_nvenc, …) still runs on the GPU, and ffmpeg converts 10-bit frames to p010 on the way in. HEVC Main 10, VP9 Profile 2, and AV1 10-bit are unaffected.
Retry tiers recognise decoder failures
HandleConversionFailure treats "Error querying IO surface", "Error submitting packet to decoder", "Error while decoding", and [dec: as decoder-side failures. Previously the trailing "Function not implemented" matched the encoder-feature tier first, so the conservative-encoder-flags retry replayed the identical decode before the software-decode retry got a turn. Decoder failures now skip straight to the software-decode + hardware-encode retry.
TesseractOCR 5.5.2
The OCR package moves from 5.3.5 to 5.5.2. Its native libraries are now tesseract55 / leptonica-1.85.0, so the Dockerfile and bundle-ocr-mac.sh symlink the system libtesseract / libleptonica under those names for the package's Linux / macOS loader. Windows uses the package's bundled DLLs directly.
Tests
VideoTagArgumentsTests(new) — everyh265encoder is taggedhvc1into MP4 and no other encoder is; Dolby Vision MP4 tags are normalized when copying into MKV, while other MKV / WebM outputs are unchanged; HEVC copies retagnull/[0][0][0][0]/hev1/hvc1sources, keepdvh1/dvhesource tags, and follow the Dolby Vision profile matrix (5 →dvh1, 8 →hvc1, both with-strict unofficial; 4 and 7 untouched); a real ffprobeside_data_listpayload round-trips throughProbeResult; encodes of Dolby Vision sources drop the record; non-HEVC copies ignore the encoder name; advanced-profile ownership handles disabled, repeated, and stream-specific Dolby Vision options and explicit tags; and the tag and compliance tokens land in the right position of the assembled ffmpeg argument vector.AdvancedVideoArgumentsTests— thedolby_vision_sample_entrywarning checks enabled Dolby Vision settings and the effective muxer compliance value, including numeric aliases and repeated options.ProbeBuilder—Video(...)acceptscodecTaganddolbyVisionProfileso fixtures can describe MKV Dolby Vision sources.RateControlAndScaleTests—IsHwDecodableVideoProfilematrix (H.264 High / Main / Constrained Baseline stay decodable; High 10, High 10 Intra, High 4:2:2, High 4:4:4 Predictive are rejected by profile or bypix_fmtalone; HEVC Main 10, VP9 Profile 2, and AV1 10-bit are untouched; null stream is optimistic);CanVaapiDecoderejects H.264 High 10 on the Elkhart Lake baseline and when the vainfo-detected set listsh264, and keeps HEVC Main 10.
Files Changed
HEVC sample entry
Snacks/Services/TranscodingService.cs—GetVideoTagArguments,AdvancedProfileOwnsVideoTag,RaisesMuxerComplianceLevel,GetHevcCopyArguments,GetDolbyVisionProfile(all new); the tag tokens ride in the literal video arguments passed toBuildFfmpegArgumentsSnacks/Services/Mp4SampleEntryOptions.cs(new) — shared token rules for-tag:v/-vtag,-strict, Dolby Vision mentions, and the ffprobe side-data labelSnacks/Services/AdvancedVideoValidator.cs—WarnWhenDolbyVisionLeavesSampleEntryUnset(new) and thedolby_vision_sample_entrydiagnosticSnacks/Models/ProbeResult.cs—Stream.SideDataListandStreamSideData(new)
Hardware decode profile gate
Snacks/Services/TranscodingService.cs—IsHwDecodableVideoProfile(new);CanVaapiDecodeapplies it before the codec check; the NVIDIA cuvid selection and the software-decode log line inConvertVideoAsyncuse it;HandleConversionFailureclassifies decoder-side errors ahead of encoder-feature errors and routes them to the software-decode retry
TesseractOCR 5.5.2
Snacks/Snacks.csproj—TesseractOCR5.3.5 → 5.5.2Snacks/Dockerfile—/app/x64/libtesseract55.dll.soand/app/x64/libleptonica-1.85.0.dll.sosymlinkselectron-app/scripts/bundle-ocr-mac.sh— matching.dylibsymlink names
Tests
Snacks.Tests/Video/VideoTagArgumentsTests.cs— sample-entry, Dolby Vision, ownership, and argument-position suite (new)Snacks.Tests/Video/AdvancedVideoArgumentsTests.cs— validator warning casesSnacks.Tests/Fixtures/ProbeBuilder.cs—codecTaganddolbyVisionProfileparametersSnacks.Tests/Video/RateControlAndScaleTests.cs— hardware-decode profile gate cases
Version bumps
Snacks/Snacks.csproj—<Version>2.19.1</Version>(single source of truth)electron-app/package.json/package-lock.json,build-and-export.bat,Snacks.Tests/Settings/AppVersionTests.cs,Snacks/wwwroot/docs/index.html— synchronized viasync-version.mjs(the README carries a live latest release badge and no version string)
Full documentation: README.md · /docs/index.html on a running instance