Skip to content

fix(livetv): run recording post-processors and refresh processed metadata - #842

Open
kmitchel wants to merge 2 commits into
mangoleaf:mainfrom
kmitchel:codex/recording-postprocessor
Open

kmitchel wants to merge 2 commits into
mangoleaf:mainfrom
kmitchel:codex/recording-postprocessor

Conversation

@kmitchel

@kmitchel kmitchel commented Nov 8, 2026

Copy link
Copy Markdown

What

Ferrofin persists RecordingPostProcessor and RecordingPostProcessorArguments
but never invokes the configured executable. Retained DVR recordings now run
that processor after being marked Completed, then queue a metadata refresh.
For example, a processor that replaces MPEG-2 with HEVC now runs automatically,
and subsequent library playback reports the processed file's codec and duration.

Upgrade note: Previously ignored post-processor settings become active.
Check the executable, service-account permissions, and argument template before
upgrading. Templates use POSIX shell-word quoting without shell execution;
"{path}" passes the recording path as one argument. No database migration is
required.

Why

A configured or adopted Jellyfin post-processing pipeline otherwise silently
stops running. Refreshing only before processing also leaves stale media metadata
when the script replaces a recording and its library has no filesystem watcher.

Compared against the pinned Jellyfin v12.4
RecordingsManager.RecordStream and PostProcessRecording.

How

  • Invoke the processor from settle_timer after retaining the capture and
    persisting Completed. This includes retained partial captures when retries are
    exhausted or the retry window has ended. Empty captures and attempts scheduled
    for retry do not invoke it.
  • End the recorder's own-write suppression before the external processor runs.
    Start the executable directly, expand {path} case-insensitively, and await
    its exit. Blank executable settings disable processing.
  • Log launch/argument errors and report process exit status without deleting the
    recording or scheduling another capture. A nonzero exit leaves the recording
    Completed. This preserves server state; it does not undo changes a script made
    to the file.
  • Queue a recording-path refresh after a started processor exits, including a
    nonzero exit because the script may already have changed the file.

Three intentional differences are recorded in the new recording-postprocessing
subsystem parity row: argument templates are parsed with POSIX shell-word rules
before path substitution, argument values are omitted from logs, and a refresh
is queued after processing. Parsing before substitution keeps spaces, quotes,
and metacharacters in recording names literal instead of letting them change
argument boundaries. POSIX quoting is not a claim of exact .NET argument-parser
compatibility.

The private module reuses the workspace's existing shlex dependency. The API
source change contains only the parity record; no request handler or query path
changes. There are no route, schema, migration, adoption, or boot-repair changes.

Verification

  • Twelve focused regression cases cover literal arguments, malformed quoting,
    disabled settings, missing executables, nonzero exits, retained captures,
    retries, empty captures, and refresh after successful and unsuccessful scripts.
  • Full workspace nextest run: 16,747 passed, five intentionally ignored
    (four live external-provider API tests and one query-cost measurement).
    All doctests passed, including three executable cases.
  • Live TV: 802 tests passed under nextest and the release-profile test runner.
    Separate per-crate coverage gates passed: 95.59% Live TV, 91.36% API.
  • Workspace-wide Clippy passed with all targets/features and -D warnings.
    Formatting, workspace dependency declarations, cargo deny --locked check,
    cargo machete apps crates, and patch whitespace checks passed.
  • On Linux with the optimized server, a real bounded DVR recording invoked a
    Comskip/comcut and Intel VA-API HEVC script and observed exit 0. The resulting
    33.33-second file passed full video/audio decoding. Automatic refresh changed
    the scanned library item's playback metadata to HEVC; authenticated static
    delivery through that item's ID returned HTTP 206 for the requested 1 MiB.
  • A deterministic EDL fixture separately exercised commercial cutting. Existing
    recording schedules/settings and database integrity passed post-deployment
    checks. Disposable recordings and fixtures were removed.

Playback verification used the scanned library item's ID. A separate DVR row-ID
resolution gap still returns 404 at PlaybackInfo; this PR does not fix that gap.

Checklist

  • Conventional Commit subjects and DCO sign-offs.
  • cargo fmt --all --check.
  • Workspace Clippy, all targets/features, warnings denied.
  • Workspace nextest and doctests.
  • Separate coverage gates above 80% for both touched crates.
  • Upstream C# comparison and subsystem parity row with intentional differences.
  • Real server and HTTP verification of the stateful change.
  • Route changes: not applicable; no routes added or renamed.
  • Adoption validation: not applicable; no migrations, adoption, or boot repairs changed.
  • Performance scope: API changes only the parity record; core and database code unchanged.

Signed-off-by: Kenneth Mitchell <kmitchel46725@gmail.com>
Signed-off-by: Kenneth Mitchell <kmitchel46725@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant