• v0.8.0 ac4bbadd96

    v0.8.0
    All checks were successful
    Release / Check if release needed (push) Successful in 8s
    Release / Release (push) Successful in 12m2s
    Stable

    Wuast94 released this 2026-07-13 00:44:03 +02:00 | 0 commits to pgsql since this release

    Bug Fixes

    • Fix recap

    • Correct TimeRange.Intersects containment and identity handling (#816)

    • Fix

    • Fix2

    • Fix3

    • Use dotnet publish to include all runtime dependencies

    • Exclude design-time DLLs and enable dynamic loading

    • Db

    • Db2

    • Db3

    • Db4

    • Db5

    • Use pgsql plugin id in dashboard

    • Migrate pgsql config hash columns

    Features

    • Native PostgreSQL provider for Intro Skipper

    • Initial pgsql plugin release

    • Exclude Jellyfin framework assemblies from plugin package

    • Adopt upstream cache dashboard for pgsql

    • Migrate pgsql season state

    Miscellaneous

    • Bump actions/checkout from 6 to 7 (#800)

    • Bump Microsoft.NET.Test.Sdk from 18.6.0 to 18.7.0 (#813)

    • Bump github/codeql-action/init from 4.36.2 to 4.36.3 (#824)

    • Bump github/codeql-action/autobuild from 4.36.2 to 4.36.3 (#823)

    • Bump github/codeql-action/analyze from 4.36.2 to 4.36.3 (#825)

    • Add plugin fork management to build system

    • Update manifest for v1.10.11.17

    • Update manifest for v1.10.11.17 [skip ci]

    • Trigger release

    • Pull Jellyfin packages from Forgejo NuGet registry to match unsigned server build

    • Bundle meta.json with assemblies whitelist in release zip

    • Avoid node lts alias in release

    Other

    • Ffmpeg extract cache (#784)
    • refactor: extract FFmpegOutputParser and FFmpegProcess from FFmpegWrapper (PR 1)

    • Update FFmpegOutputParser.cs

    • Refactor FFmpeg services

    • Address review feedback: thread-safe chromaprint logs, log version-check failures, dedupe fingerprint range, use NullLogger in tests

    • Replace _chromaprintLogs Dictionary with ConcurrentDictionary: writers are serialized via ScheduledTaskSemaphore, but the support bundle endpoint enumerates the map concurrently
    • Log the exception in CheckFFmpegVersion instead of silently swallowing it
    • Use GetValueOrDefault in GetChromaprintLogs/FormatFFmpegLog so a failed version check can no longer crash the support bundle with KeyNotFoundException
    • Fix comment typo ("supports of the all")
    • Move detection-cache reads before ffmpeg arg construction in DetectSilence/DetectBlackFrames/DetectKeyFrames
    • Consolidate duplicated GetFingerprintRange into QueuedEpisode as the single source of truth for detection cache keys
    • Replace undisposed new LoggerFactory() instances in tests with NullLogger.Instance

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Address CodeQL findings: log kill failures, StringBuilder in GetChromaprintLogs, NullLogger in TestTimeAdjustmentHelper
    • KillProcessTree now logs instead of silently swallowing kill failures (debug for the already-exited race, warning for Win32/NotSupported)
    • GetChromaprintLogs builds the support bundle section with StringBuilder
    • TestTimeAdjustmentHelper uses the NullLogger.Instance singleton instead of an undisposed NullLoggerFactory

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Address CodeQL findings: narrow process-priority catch, use Path.Join for ffprobe path
    • Filter the PriorityClass catch to the documented failure modes (InvalidOperationException, Win32Exception, NotSupportedException)
    • Use Path.Join in GetFFprobePath so no path segment can silently drop earlier arguments

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>


    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Make FFmpeg service process calls async (#785)
    • Make FFmpeg service process calls async

    • Address async cutover review comments

    • Optionally append SponsorBlock chapter types to Intro Skipper chapter regex (#769)
    • Add SponsorBlock chapter pattern defaults

    • Refine SponsorBlock regex literals

    • Remap SponsorBlock intermission chapters

    • Make the predefined chapters a blanket option

    • Add missing documentation annotations

    • Prevent rejecting a match for an invalid fallback

    • Move intro animation to into, but intermission to commercial

    • Potential fix for pull request finding 'Nested 'if' statements can be combined'

    Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

    • Assign explicit labels to types, ambiguous to commercial

    • Address the comments by fixing tests


    Co-authored-by: copilot-swe-agent[bot] 198982749+Copilot@users.noreply.github.com
    Co-authored-by: TwistedUmbrellaX twistedumbrella@gmail.com
    Co-authored-by: TwistedUmbrellaX 1173913+AbandonedCart@users.noreply.github.com
    Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

    • Fix colon-delimited chapter end labels

    • Refactor media segment refresh services (#786)

    • Refactor media segment refresh services

    • Reduce Plugin singleton coupling and dedupe segment provider options

    • Inject ILibraryManager into MediaSegmentRefreshService and MediaSegmentEditorService; resolve items via GetItemById instead of Plugin.Instance.GetItem
    • Centralize the duplicated external-provider LibraryOptions into MediaSegmentProviderDefaults
    • Simplify id filtering in RefreshAsync(IEnumerable) with LINQ
    • Add ID-path refresh test; use collection expression in test fake

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Rethrow fatal exceptions in MediaSegmentRefreshService.RefreshAsync

    Mirror MediaSegmentEditorService: let cancellation and critical/system-level
    exceptions (OOM, StackOverflow, ThreadAbort, etc.) propagate instead of being
    logged-and-swallowed, while keeping per-item log-and-continue resilience for
    ordinary provider failures.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Consolidate ILibraryManager test stub and cover critical-exception rethrow
    • Extract the duplicated DispatchProxy-based ILibraryManager fake from
      TestSkipIntroController and TestMediaSegmentRefreshService into a shared
      EntrypointTestHelpers.CreateLibraryManager helper
    • Add a test asserting RefreshAsync rethrows critical/system exceptions
      instead of swallowing them

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Add direct unit tests for MediaSegmentEditorService

    Cover the previously-untested editor surface: provider-not-found no-op,
    non-commercial replace (delete-existing + create, ItemId/provider id/type
    filter), commercial dedup (skip identical, create distinct), delete-failure
    resilience, per-item lock serialization of concurrent same-item edits, and
    all GetSegmentAsync/DeleteSegmentAsync paths.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>


    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Reanalyze settled seasons to improve segment quality (#787)
    • Reanalyze settled seasons to improve segment quality

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Fix settled-season reanalysis guard and make reset atomic
    • Split the one-time guard into ShouldSettleReanalyze (read-only check) and
      RecordSettleReanalysis (commit), recording completion only after the reset
      succeeds so a cancelled/failed pass is retried instead of permanently skipped.
    • Wrap segment deletes and the EpisodeIds clear in ResetSeasonForReanalysisAsync
      in a single transaction so a cancel can't strand episodes as NoSegments.
    • Update tests for the split guard API.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Add settled-season reanalysis state

    • Remove settled-season rescan interval

    • Drop test-only ShouldSettleReanalyzeAsync; route guard test through production read path

    • Remove Plugin.ShouldSettleReanalyzeAsync (per-mode async wrapper) that was only
      exercised by tests; production eligibility goes through the batch
      GetSettleReanalysisStatesAsync + static ShouldSettleReanalyze. The persistence
      guard test now exercises that same production read path via a local helper.
    • Document why ExpandSettledResetModesForDerivedSegments' derived Preview mode is
      reset but not tracked in completedSettledModes (its completion rides on Credits).

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • cleanup

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Add optional recap detection fallback to early black frame (#771)
    • Initial plan

    • Add optional recap fallback using first black frame

    • Recap segments from the first-black-frame fallback now pass through TimeAdjustmentHelper

    • Use a combination of black frames and audio

    Changed:
    Added Recap fingerprint range support in FFmpegWrapper.cs (line 135).
    Added Recap-mode Chromaprint behavior and blackframe boundary refinement in ChromaprintAnalyzer.cs (line 117).
    Improved blackframe recap fallback to pick the latest valid black frame, not the first, capped before intro when available in ChapterAnalyzer.cs (line 177).
    Ensured Introduction runs before Recap and added Recap Chromaprint to the analyzer chain in BaseItemAnalyzerTask.cs (line 68).
    Added Recap Chromaprint tuning to config hashing in ConfigHasher.cs (line 44).
    Updated UI copy/options in analysis.ts (line 109) and action-bar.ts (line 19).
    Added/updated tests in TestChapterAnalyzer.cs (line 53) and TestRecapDetection.cs (line 17).

    • Provide settings for recap min and max durations

    • Make sure the FullLengthChapters setting is applied

    • Addressed the review comments with a comment

    • Rename the setting to avoid confusion

    • Resolve conflicts with merged branch


    Co-authored-by: copilot-swe-agent[bot] 198982749+Copilot@users.noreply.github.com
    Co-authored-by: TwistedUmbrellaX 1173913+AbandonedCart@users.noreply.github.com
    Co-authored-by: TwistedUmbrellaX twistedumbrella@gmail.com

    • Add structured exclusion lists (#795)
    • Add structured exclusion lists

    • Add library name autocomplete to series/movie exclusion fields

    Feed the SeriesExclusions and MovieExclusions fields native datalist
    suggestions sourced from the user's libraries (getLibraries +
    getShowsInLibrary, filtered by item type), reusing the existing
    exclusion-list-field suggestion hook used by the path field. Gives
    type-to-filter search without a bespoke combobox.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Harden exclusion name evaluation and document cache-delete cancellation
    • Accept nullable series/movie names in ExclusionPolicy and trim safely,
      restoring the defensive null handling the pre-refactor code had (Jellyfin
      metadata is non-null at compile time but not guaranteed at runtime).
    • Document why the excluded-timestamp cache delete uses CancellationToken.None:
      the segment/season-state rows are already deleted, so aborting would orphan
      cache entries — mirroring the existing rationale in EraseSeasonAsync.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Fixea´s

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Make credit black-frame detection adaptive (#799)
    • Make credit black-frame detection adaptive

    • Address credit-detection review findings

    • ConfigHasher: include BlackFrameMinimumPercentage in the BlackInterval
      detection-cache hash. It is passed to blackdetect as pic_th and baked into
      cached intervals, so changing it must invalidate the cache.
    • CreditSceneBuilder.FindSupportingInterval: select the overlapping interval
      that yields the longest supported scene instead of the first match, so an
      early short interval no longer masks a later one that meets the minimum
      duration.
    • CreditSceneBuilder.DetectCreditScenes: gate refinement-only short scenes on
      whether boundary refinement is enabled, so an unrefinable short scene no
      longer suppresses the interval fallback when RefineCreditsBoundary is off.
    • FFmpegService.DetectBlackIntervalsAsync: drop -skip_frame noref so blackdetect
      sees the full decoded stream (avoids merging through non-black B-frames or
      missing short intervals).
    • Tests: assert the probe-window Start/End directly (TimeRange equality is
      duration-based), update the BlackInterval cache-hash test, and add regression
      tests for interval selection and the disabled-refinement fallback.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Make blackdetect frame-skip configurable (ThoroughBlackIntervalScan)

    Restore the -skip_frame noref performance optimization on the credits
    blackdetect scan and make it opt-out via a new ThoroughBlackIntervalScan
    setting. The default keeps reference-frame-only decoding (fast); enabling
    it decodes every frame for higher accuracy on very short or B-frame-only
    black intervals, at the cost of slower scans.

    • FFmpegService.DetectBlackIntervalsAsync: emit -skip_frame noref unless
      ThoroughBlackIntervalScan is enabled.
    • ConfigHasher: include ThoroughBlackIntervalScan in the BlackInterval
      detection-cache hash so toggling it invalidates cached intervals.
    • Add the toggle to the Black Frame settings tab, gated on the alternative
      black frame analyzer like RefineCreditsBoundary.
    • Tests: restore the noref arg-string expectation and add a cache-hash
      invalidation test for the new toggle.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Clean up credit detection: dedupe density check, centralize tuning constants
    • Replace the local MeetsBlackFrameDensity helper with
      CreditSceneMetricsCalculator.Calculate(...).MeetsDensity(...). The helper
      duplicated the metrics calculator, and its ref-cursor "optimization" was inert
      (MergeNearbyScenes re-declared the cursor to 0 on every iteration), so this
      removes the duplication with no behavior or performance change.
    • Move MinimumIntervalOverlapSeconds and MinimumBoundaryProbeWindow into
      CreditDetectionPolicy alongside the other tuning constants.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Simplify adaptive credits detection

    • Tidy blackdetect duration constant and argument construction

    • Reintroduce a single source of truth for the blackdetect minimum duration
      (BlackInterval.MinimumDetectionDuration), referenced by both the blackdetect
      d= filter argument and the BlackInterval detection-cache hash so the two
      cannot silently drift.
    • Collapse the now-unconditional blackdetect argument list back into a single
      collection initializer.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Restore matrix coverage for RankCreditCandidates ranking

    Replace the single two-scene assertion with a focused theory covering:
    latest-scene fallback, interval promoting an earlier scene, sub-threshold
    overlap (below MinimumIntervalOverlapSeconds) not counting as support, an
    interval supporting the later scene, and the latest supported scene winning
    among multiple candidates.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Clarify IsSparse doc and pin sparse-no-interval-support behavior

    Reword IsSparse to describe it as an opportunistic interval-refinement
    trigger rather than a hard trust gate (it is only consulted for a single
    scene that has already cleared the density and duration gates).

    Add a regression test pinning that a temporally sparse single scene whose
    blackdetect probe finds no supporting interval is still returned, as the
    deliberate counterpart to the count==0 candidate path.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>


    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Detect non-black "card" credits via an entropy/saturation fallback (#798)
    • Detect non-black "card" credits via an entropy/saturation fallback

    When the black-frame scan finds no credits, fall back to a single extra
    keyframe decode that emits luma-histogram entropy + mean saturation
    (stock FFmpeg entropy,signalstats,metadata=print) and accepts a
    sustained, near-uniform "card" background as credits. This is
    colour-agnostic, so credits rendered on black, coloured, or bright cards
    are all detected. The frame-accurate black-frame path is byte-for-byte
    unchanged and runs first; the fallback can only add detections when the
    black path returns nothing. Gated by a new DetectNonBlackCredits
    configuration option (default on) and cached like the black-frame scan.

    Also includes three correctness/perf fixes found while auditing the
    analyzer:

    • NormalizeThreshold: clamp the 1st-percentile index into range so a
      sparse keyframe set (<100 frames) cannot index out of bounds.
    • CreditsBoundaryHelper.SelectProbeMinimum: use FirstOrDefault and fall
      back to the scene-change threshold instead of throwing from First()
      when a scene start frame has no matching keyframe (interval-derived
      scenes).
    • CreditSceneBuilder: hoist the merge-search cursor out of the merge
      loop so scene merging no longer rescans from frame 0 every iteration
      (O(n^2) -> O(n)).
    • Invalidate stored credits analysis when DetectNonBlackCredits toggles

    Include the DetectNonBlackCredits flag in the Credits-mode analysis hash
    so enabling/disabling the non-black fallback re-runs credit detection
    instead of hash-matching a stale result with the old segments. Add a
    regression test asserting the hash changes with the flag.

    • Rename SelectLongestQualifyingRun to SelectLatestQualifyingRun

    The helper selects the latest qualifying credit run (credits sit at the
    tail), not the longest. Rename the method and the carried-over best
    parameter, and document that the running best is preserved rather than
    compared by length. No behavior change.

    • Trim over-extended non-black credit tails via density-anchored run end

    FindCreditRange bridged across non-card keyframes purely on a time gap, so
    periodic near-uniform frames in non-credit tail content kept extending a
    credit run past its real end (over-skip). Anchor the run end to the dense
    card cluster: walk the end back while the final card attaches only through
    an above-cadence gap (> maximumInRunGap * TrailingTrimGapFactor), dropping
    isolated tail cards without touching the dense body, so genuinely
    interleaved credits (a brief ident bracketed by cards) are preserved.

    Factor 0.5 validated against a synthetic bracket (over-extension trims;
    mid/near-end interludes, sparse 8s-GOP credits, and a 4s-spaced final card
    all preserved). Adds focused + bracket regression tests.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Collapse double blank line in FakeFFmpegService test mock

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Anchor credit tail-trim to the run's own cadence (fix long-GOP false negatives)

    The trailing-trim threshold derived from maximumInRunGap, which is capped at
    MaximumSceneMergeGapSeconds (20). For keyframe cadences above ~10s the cap
    decoupled the threshold from the real cadence, so every card in a uniformly
    sparse all-card run (e.g. 12s GOP) looked isolated and the run was trimmed to
    nothing — a false negative on long-GOP sources.

    Key the trim off the run's own median card-to-card gap (x TrailingTrimGapMultiplier
    = 2.5) instead: a uniform run has every gap near its median so nothing trims,
    while a sparse tail drifting off a denser body still sits well above that body's
    median and is removed. Adds a 12s-cadence regression case; the ambiguous
    short-dense-prefix case now correctly keeps as a sparse run.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Trim isolated leading cards too (symmetric credit-run anchoring)

    The trailing trim only walked the run end; SelectLatestQualifyingRun always
    took runCards[0] as the start, so an isolated low-entropy pre-credit card
    (e.g. a title card 14-16s before the real credits) that bridged into the dense
    block on a longer-GOP source pulled the start too early.

    Generalize the trim to both ends: TrimIsolatedEnds walks each end inward,
    dropping a leading or trailing card whose gap to its neighbour exceeds the
    run's own median card gap x TrailingTrimGapMultiplier. Each end stops at the
    first in-cadence card, so a dense body - including two real credit sub-blocks
    separated by a gap - is untouched, and uniform sparse long-GOP runs are still
    preserved. Adds a focused leading-trim regression plus a bracket case.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • cleanup

    • Update CreditEntropyFallback.cs

    • Add null guard to FindCreditRange; expose IsCreditCardKeyframe as internal with boundary theory test

    • ArgumentNullException.ThrowIfNull(visuals) in FindCreditRange to match the
      codebase's argument-validation convention (was an unguarded NRE path).
    • IsCreditCardKeyframe: private -> internal (InternalsVisibleTo already set) so
      the entropy/saturation classification boundary is unit-testable directly.
    • Restore focused TestIsCreditCardKeyframe [Theory] pinning both exclusive
      maxima (entropy < 0.35, saturation < 96) at their boundaries.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Bound non-black credit scan to credits window and normalize saturation scale
    • DetectKeyframeVisualsAsync now scans [CreditsFingerprintStart, CreditsFingerprintEnd]
      via GetFingerprintRange + -to range.Duration (was start-to-EOF). FindCreditRange
      picks the latest qualifying run, so an unbounded scan could select a muted tail
      beyond the credits window (e.g. trailing video after the probed audio duration) as
      credits. Cache is now keyed by the real (start, end). Black-frame path unchanged.
    • Prepend format=yuv420p to the entropy/signalstats filter graph. signalstats SATAVG
      is reported in the source's native sample scale (~4x higher on 10-bit), so the
      8-bit-tuned saturation<96 gate missed muted cards on 10-bit/HDR sources; forcing
      8-bit pins both signals to the tuned scale.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Require SATAVG before emitting a KeyframeVisual

    ParseKeyframeVisuals emitted a frame once entropy was parsed, even if the
    signalstats.SATAVG line was missing (e.g. output truncated before it). The
    default saturation 0 then passed the low-saturation credit-card gate
    (0 < SaturationCreditMaximum), so a low-entropy frame with dropped saturation
    could fabricate a false non-black credit. Track hasSaturation alongside
    hasEntropy, reset it per frame, and require both at both emit sites, mirroring
    the existing "drop blocks without entropy" behavior. Adds a regression test for
    a trailing block truncated before SATAVG.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Anchor credit grouping/trim to card cadence (fix sparse-credit miss & tail over-extend)

    Two cadence-estimation bugs in the non-black credit fallback:

    • Grouping (Finding 1): EstimateMaximumInRunGap derived the in-run bridge from the
      median gap of ALL keyframes, so dense pre-credit content drove it below the card
      spacing and split long-GOP static-card credits into discarded sub-minimum runs.
      Now estimated from card keyframes only, so it tracks the credit-card cadence.

    • Trim (Finding 2): TrimIsolatedEnds keyed off the bridged run's overall median,
      which a long sparse tail dominates -> the trailing trim stops and the range
      over-extends to the last stray card. Now anchored to the dense-body cadence
      (lower-quartile gap); when trimming would leave less than the minimum duration
      there is no dominant dense body, so the full (uniformly sparse) run is kept.

    Adds regression tests for both, plus an empty-blackFrames fallback-branch test.
    All 11 trailing-trim bracket cases and existing fallback tests stay green.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Decouple credit run grouping; gate non-black hash; harden metadata parsing

    Addresses four review findings in the non-black credit fallback:

    • Finding 1 (grouping): the in-run bridge was one global cadence estimate, so an
      earlier dense card run pulled it below a later long-GOP credit run's spacing and
      split the real credits into discarded sub-minimum runs (returning the earlier
      run). Replaced with a fixed MaximumSceneMergeGapSeconds bridge so each run is
      independent; removed the now-dead cadence estimate. The dense-anchored trim still
      handles within-run isolated edges.

    • Finding 2 (cache): ConfigHasher appended |nonblack= unconditionally, invalidating
      cached credits on the default BlackFrameAnalyzer path that cannot observe the flag
      (and hides it in the UI). Now included only when UseAlternativeBlackFrameAnalyzer
      is true.

    • Finding 3 (parser): the keyframe pts_time/entropy/saturation regexes were not
      exponent-aware and truncated exponent-form numbers at the mantissa. Made them
      exponent-aware (defensive; stock FFmpeg emits decimal here).

    • Finding 4 (tests): added coverage for the candidate->interval-miss->fallback path.

    Regression tests added for each; all trailing-trim bracket cases stay green.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Reject sparse cards bridged across busy content via card-density floor

    FindCreditRange skipped non-card keyframes entirely, so two isolated
    card-like frames within MaximumSceneMergeGapSeconds bridged across busy
    content (non-card keyframes between them) qualified as a sustained credit
    run. Add a card-density check on the trimmed run span: keyframes inside
    the span must be >=50% cards. A genuine long-GOP sparse run has no
    non-card keyframes between its cards (density ~1.0) and is kept; only
    sparse cards interspersed with busy content are rejected.

    Add regression test plus flip the bracket's predominantly-sparse case
    (same false pattern) to null.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Add disabled-setting regression test for the empty black-scan branch

    TestDetectCreditsAsync_NonBlackCreditsDisabled_SkipsFallback seeded
    non-empty pblack=0 frames, covering only the blackFrames.Count > 0 path.
    Add a companion that seeds an empty scan ([]) with DetectNonBlackCredits
    off and asserts the keyframe-visual fallback is never started
    (VisualScanCalls == 0), pinning that the Count == 0 branch also honors
    the user-disabled option. Test-only; no production change.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Fix ffmpeg test fixture noise

    • Scope DetectNonBlackCredits copy to muted/neutral cards, not vivid colour

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    The UI/config wording advertised 'solid coloured' cards, but the classifier gates on saturation < 96, so a vivid saturated card (e.g. solid blue, SATAVG ~113) is rejected. Excluding vivid uniform frames is deliberate: a solid-colour content frame (fade, stylised transition, saturated sky) is indistinguishable from a saturated colour card by entropy+saturation alone, so admitting them would cost the fallback's zero-false-positive discipline. Correct the UI string and code docs to describe what is actually covered (black/white/grey/muted-colour near-uniform cards) and document the exclusion rationale on the saturation constant. No logic change.

    • Update TestCacheOperations.cs

    • Keep sparse all-card credit runs together across long GOP gaps

    FindCreditRange split a run on any card-to-card gap over the fixed 20s
    bridge, so a real all-card credit sequence on a source whose keyframe
    cadence is just above 20s (e.g. cards every 21s) was split into
    one-frame runs that each failed the minimum duration, returning null.

    Break a run only when real content separates two cards: a gap beyond the
    bridge AND at least one intervening non-card keyframe. A sparse all-card
    source (keyframes farther apart than the bridge but nothing non-card
    between) now stays one run regardless of GOP length; genuine content
    between two card groups still splits them (the latest qualifying run
    wins). Unifies with the density floor: non-card evidence, not raw time,
    separates runs.

    Add a regression test for the 21s-cadence all-card case, and make the
    LaterSparseCredits test realistic (it omitted the content that exists
    between two separated credit groups, which is the evidence the split
    keys on).

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Clip non-black keyframe scan to the credits window

    DetectKeyframeVisualsAsync relied on -to to bound the scan, but FFmpeg
    with -skip_frame nokey still emits keyframes well past the requested
    duration (reproduced on the repo fixture: a 5s window via -ss 1 -to 5
    returned pts_time 9/19/29; -ss 5 -to 30 returned 5/15/25/35/45/55).
    Those out-of-window frames were parsed, cached, and returned, so
    FindCreditRange could select a low-entropy run past CreditsFingerprintEnd
    and DetectNonBlackCreditsAsync would persist credits outside the
    configured scan window.

    Clip parsed visuals to [0, range.Duration] (times are relative to the
    -ss seek) before caching/returning. Add a real-FFmpeg regression test on
    credits.mp4 asserting the window [5,35] returns only the in-window
    keyframes (5/15/25) and never the leaked frames past the duration; the
    prior fixture test only exercised the showinfo DetectKeyFramesAsync path.

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>


    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Inline FFmpeg process runner

    • Simplify detection cache schema check

    • Move shared CI setup

    • Moved shared helper out of TestEntrypointEvents into EntrypointTestHelpers

    • Remove RefCountedSemaphore, _itemLocksLock, RentItemLock, and ReturnItemLock

    • Refine scheduled task semaphore lease

    • FindContiguous droping the final currentRange

    • Keep NoSegments episodes eligible for analyzer fallback chain (#812)

    • Allow NoSegments episodes to be re-analyzed

    • Document and test NoSegments analyzer eligibility

    • Fix NoSegments seasons stranded when Chromaprint becomes available

    Revert the analyzer-gate change back to NotAnalyzed semantics: a season
    settled as NoSegments is a negative-cache hit for the current config and
    must not be re-run every scheduled pass. Instead, fold FFmpeg/Chromaprint
    availability into the analysis config hash for the Chromaprint-using modes
    (Introduction, Credits, Recap).

    Root cause: when the first scan ran without a Chromaprint-capable FFmpeg,
    episodes were recorded as analyzed-with-no-segments and reloaded as
    NoSegments on later scans. Because ffmpegValid was not part of the config
    hash, hashMatches stayed true and the season was skipped forever even after
    Chromaprint became available. Hashing ffmpegValid makes the stored hash stop
    matching on a capability change, so those episodes reset to NotAnalyzed and
    are re-analyzed exactly once instead of every run.

    Tests: gate tests corrected to negative-cache semantics; added hash tests
    asserting capability-awareness for chromaprint modes and invariance for
    chapter-only modes (Preview, Commercial).

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Handle null chapter results in queue verification

    • DispatchProxy cannot use a sealed proxy type


    Co-authored-by: copilot-swe-agent[bot] 198982749+Copilot@users.noreply.github.com
    Co-authored-by: rlauuzo 46294892+rlauuzo@users.noreply.github.com
    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
    Co-authored-by: TwistedUmbrellaX twistedumbrella@gmail.com
    Co-authored-by: TwistedUmbrellaX 1173913+AbandonedCart@users.noreply.github.com

    • Optimization to prevent marking analyzed episodes (#817)
    • Handle null chapter results in queue verification

    • Cache FFmpeg validity in queue manager

    • Optimize queue verification and remove analysis hash overload. (#818)

    Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

    • Release v1.10.11.22

    • Move black frame recap option to black frame page

    Downloads