github derekshreds/Snacks v2.6.0
Snacks v2.6.0

3 hours ago

Snacks v2.6.0

Automated Video Library Encoder

A minor release that adds directory dry-run analysis — a per-file Queue/Mux/Skip preview the user can review before committing to a real process-directory run. The same release also closes a long-standing wasted-work hole: HEVC files just above the bitrate ceiling that would have produced a near-identical -c:v copy output (no audio/sub work, no filters) are now skipped at scan time instead of running ffmpeg for nothing.


New Features

Directory Analyze (Dry Run)

  • New "Analyze (Dry Run)" action in the library modal -- alongside the existing "Process Folder" / "Process Folder + Subfolders" actions, the directory drawer now offers a third option that opens a per-file preview before anything is queued or written to the database. Useful on large folders where you want to know whether a different bitrate / codec / mux-mode setting would actually do anything before committing to it.
  • Per-file decisions mirror the real run -- each row shows the predicted action (Encode, Shrink, Copy, Mux, Skip, Excluded, Done, Failed, Cancelled, Error), source codec/bitrate/resolution/size, and a human-readable reason. The decision logic in AnalyzeFileAsync mirrors AddFileAsync's skip ladder rung-for-rung — same exclusion rules, same DB-status short-circuit (split into distinct AlreadyCompleted / AlreadyFailed / AlreadyCancelled decisions for filtering), same 4K-skip and codec/bitrate ceilings, same VAAPI-can't-compress-H.264 gate, same Hybrid-mux bypass, same final CalculateBitrates ladder for Queue/Shrink/Copy.
  • Borderline flag for files near the skip ceiling -- the analyze pass uses the file-level bitrate (filesize × 8 / duration) and skips the 15-second video-only remeasurement that the real run does. Files within +30% of the applicable ceiling are flagged with a warning icon and hover tooltip ("real run remeasures video-only bitrate and may decide differently"), so the user knows the prediction is at the edge of its accuracy.
  • DB-cached probe reuse -- when the file's size and mtime match its MediaFiles row, the analyzer reads codec / bitrate / resolution / duration / IsHevc straight from the DB instead of re-running ffprobe. Same change-detection thresholds as AddFileAsync (mtime tick match + ≤10% size delta). Audio/subtitle stream summaries come from the cached AudioStreams / SubtitleStreams JSON blobs the v2.5.0 migration added, so the no-op gate still has the data it needs to answer "would the audio/sub mapping actually change anything?" without probing.
  • Summary cards + filter tabs + name filter -- six summary cards across the top (Total Files / Will Encode / Mux Pass / Copy / Will Skip / Already Done) with rough byte totals, a tab strip below for filtering the table to one bucket, and a free-text filename filter. Footer "Proceed with Conversion" button submits the directory to the real process-directory endpoint with the same options the analyze used (button label includes the will-process count, disabled when there's nothing to act on).
  • Cancellable -- closing the modal mid-analysis (X button, footer Close, Escape, backdrop click) aborts the in-flight analyze-directory request via AbortController; the server honors it through HttpContext.RequestAborted and returns 499. Reopening before the previous run finishes also aborts it, with an identity check on the AbortController so a late-arriving response from a since-aborted request can't paint over the new one.

Bug Fixes & Reliability

Skip-Ladder: No-Op Encode Detection

  • Don't run ffmpeg to produce a near-identical output -- when an HEVC source sits just above the skip ceiling but under TargetBitrate + 700, CalculateBitrates's final override would flip the encode to videoCopy = true. If the audio/subtitle pipeline also has nothing to change (no codec conversion, no track filtering, no language fixups) and no filter is active (no crop / downscale / HDR tonemap), the resulting ffmpeg run is a near-no-op — same video stream copied, same audio/sub mapping, slightly different container. We were burning a transcode pass on those files. AddFileAsync now runs a WouldEncodeBeNoOp check at the top of the skip ladder and marks the file Skipped in the DB before ever invoking the encoder.
  • Same gate applied to the scan-phase bitrate filter -- MeetsBitrateTarget(MediaFile, options) is the pure-function variant used during library scans (no probe required, reads only DB fields). It now mirrors the same no-op logic: HEVC under TargetBitrate + 700 with no filter / audio / sub work counts as "meets target" and is filtered out of the scan results. Uses the cached AudioStreams / SubtitleStreams JSON; HDR is treated as inactive at scan time (the DB doesn't carry HDR metadata) so the worst case is the file falls through to AddFileAsync's probe-aware path, which catches the HDR case correctly.
  • Helper extraction -- the active-filter and would-downscale checks (HasActiveFilter, WouldDownscale) were factored into shared helpers so the no-op gate, the analyze preview, and the scan filter all use the same logic. This also closes a small bug where the scan filter previously didn't account for downscale/tonemap filters at all.

Files Changed

Directory analyze (dry run)

  • Snacks/Controllers/LibraryController.cs -- new POST /api/library/analyze-directory endpoint, returns per-file FileAnalysisResult[] without writing to the DB or queueing work; honors HttpContext.RequestAborted (returns 499 when the client aborts)
  • Snacks/Models/FileAnalysisResult.cs (new) -- single-file dry-run result: path, source metadata, decision label, reason text, predicted encode-target kbps, borderline flag
  • Snacks/Services/TranscodingService.cs -- new AnalyzeDirectoryAsync / AnalyzeFileAsync mirroring AddFileAsync's skip ladder; DB-cached probe reuse with the same mtime + size-delta thresholds
  • Snacks/Views/Home/Index.cshtml -- new analyzeResultsModal markup (summary cards, filter tabs, name filter, results table, Proceed footer button)
  • Snacks/wwwroot/js/library/analyze-modal.js (new) -- AnalyzeModal class: open/fetch/render/proceed flow, decision-to-badge mapping, AbortController-based cancellation, late-response identity guard
  • Snacks/wwwroot/js/library/library-browser.js -- new "Analyze (Dry Run)" entry in the directory drawer; analyzeCurrentDirectory hook; onAnalyzeRequested callback prop
  • Snacks/wwwroot/js/api.js -- libraryApi.analyzeDirectory(dirPath, recursive, options, signal); postJson gains an optional AbortSignal parameter
  • Snacks/wwwroot/js/main.js -- composition root wires AnalyzeModal to LibraryBrowser via the new onAnalyzeRequested callback

Skip-ladder no-op detection

  • Snacks/Services/TranscodingService.cs -- new WouldEncodeBeNoOp rung in AddFileAsync's skip ladder; MeetsBitrateTarget(MediaFile, options) extended with the same no-op check; HasActiveFilter / WouldDownscale extracted as shared helpers used by the no-op gate, the analyze preview, and the scan filter

Version Bumps

  • Snacks/Controllers/HomeController.cs
  • Snacks/Services/ClusterDiscoveryService.cs -- protocol version bump to 2.6.0
  • Snacks/Views/Shared/_Layout.cshtml
  • README.md
  • build-and-export.bat
  • electron-app/package.json / package-lock.json

Full documentation: README.md

Don't miss a new Snacks release

NewReleases is sending notifications on new releases.