Conversation
* docs(agents): record develop-staleness and issue-closing learnings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Correct develop-staleness check to use main-only range --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface each faulted file's Information-level remediation detail during `--logwarning` runs via an AsyncLocal per-file session and a single Serilog filter; clean files stay silent. Adds unit tests and release notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Force the end-of-run summary at information level so it survives `--logwarning`, and handle SIGINT/SIGTERM/SIGQUIT so a stop (docker stop, Ctrl+C) cancels processing gracefully and logs the summary and exit code before exit instead of being force-killed. Replace the Console.CancelKeyPress and custom Ctrl+Q/Ctrl+Z handlers with one PosixSignalRegistration path.
Replace the warning-only (and mis-counting) VerifyTrackFlags with a cleanup step that clears redundant Default flags: a lone track of a type loses its flag, audio keeps the preferred track as the single default, and subtitles keep none. Add MkvPropEdit.ClearDefaultFlags to write flag-default=0, a dedicated ClearedDefaultFlags sidecar state, a post-encode re-run, and a re-detected-after-clearing warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…791) * Add custom plugin command for bespoke re-processing (IProcessPlugin) Enhanced verification checks never run on files whose sidecar is already Verified, and full re-verification is prohibitively expensive. Add a custom command that loads a user-provided plugin assembly and runs it over the media files, reusing the existing file iteration and processing API for targeted re-checks or repairs without recompiling PlexCleaner. - IProcessPlugin / IPluginHost public interfaces with a deterministic PluginApi.Version the plugin checks in Initialize, plus injected logger, app/OS/runtime info for compatibility decisions - PluginLoader / PluginLoadContext (AssemblyLoadContext) sharing the host's loaded assemblies so type identity and static state are single-instance - custom verb (--pluginassembly) wired through CommandLineOptions and Program.CustomCommand - Plugin loading gated behind the PLUGINS constant, compiled only when not building for NativeAOT; PublishAot default flipped to false to match every shipped build - MatroskaHeaderCleanup example plugin, PluginLoaderTests, and README custom plugins section with CLI and Docker Compose examples Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add Custom Plugins to README table of contents Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix Docker build context and address plugin review feedback - Dockerfile: copy the Plugins directory into the build context so PlexCleanerTests can resolve its project reference (the smoke-build Docker job failed with the plugin project "not found") - PluginLoadContext: override LoadUnmanagedDll so a plugin's bundled native dependencies are probed from the plugin directory - PluginLoader: log the assembly path on load failure since LogAndHandle only reports the caller member - CustomCommand: isolate plugin exceptions to the current file so a faulty plugin fails that file instead of aborting the whole PLINQ run Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add application-version pinning example and second-round review fixes - Example plugin now also pins against the tested PlexCleaner application version, not just PluginApi.Version: PluginApi.Version guards only the plugin contract, but the public API a plugin calls can change in any release, so the example warns when host.ApplicationVersion differs from the tested version (a stricter plugin could hard-pin). Documented in the plugin notes. - PluginLoader: guard null/empty/whitespace assembly path before GetFullPath so an empty --pluginassembly does not resolve to the current directory - README: note the plugin needs a public parameterless constructor and that processing helpers are gated by settings (e.g. ProcessOptions.Verify) - PluginLoaderTests: assign to a non-null local after the NotBeNull assertion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add parameterless to cspell dictionary Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix docs: parallel default wording and AOT-default consistency - README: file processing is serial by default; plugin code must be thread-safe only when --parallel is enabled (it is opt-in, not the default) - ARCHITECTURE.md / AGENTS.md: PublishAot now defaults to false to match the shipped builds and enable the plugin loader; AOT is opt-in via -p:PublishAot=true Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add local markdown/spell lint parity to hook and VS Code tasks CI lint failures (cspell, markdownlint) were only caught after push because the editor extensions are advisory and the pre-commit hook only ran the C# checks. - pre-commit: when Markdown files are staged, run markdownlint and cspell to match CI. These Node tools are unreliable across Windows/WSL/Linux, so run them via Docker. To keep the hook reliable and never a false blocker, only use images already present locally (never pull) and skip when Docker or the image is unavailable; MSYS_NO_PATHCONV keeps the bind mount correct under Git Bash on Windows and is a no-op on Linux/WSL. The commit is gated on lint errors only when the tooling is present; CI enforces regardless. - .vscode/tasks.json: add "Lint: CSharpier", "Lint: Style", "Lint: Markdown", "Lint: Spelling", and a "Lint: All (CI parity)" compound task. The Docker tasks also warm the images the hook reuses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document custom command in README summary and command list The custom plugin command was described in its own section but missing from the 3.20 release-notes summary and the CLI command reference; add both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Resolve plugin path inside try so a malformed path fails cleanly Path.GetFullPath can throw on invalid path characters; move it and the File.Exists check inside the try/catch so a bad --pluginassembly value is logged and returns null instead of crashing the process. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Filter non-Matroska files in the example plugin The custom driver passes every file (mkvFilesOnly=false, matching the flexible TestMediaInfo precedent so plugins can target any container type). The example plugin now skips non-Matroska files and returns true rather than reporting them as errors, and the docs note that a plugin receives all files and should filter by type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document plugin sidecar consistency requirement A plugin that modifies a file must keep the sidecar in sync or the next normal run invalidates the saved state. Document the example flow (confirm Matroska, GetMediaProps to initialize the sidecar, modify via a ProcessFile helper that refreshes it), verified end to end against a real file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Comment sidecar state-flag behavior in the example plugin RepairMatroskaStructure combines state flags (State |= ReMuxed) and only clears Verified via SetVerifyFailed if the remux fails to repair the structure, so a successful repair preserves an existing Verified state and avoids re-verifying. Add a comment explaining this in the sample. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Require --pluginassembly only in non-AOT builds In AOT builds the custom command handler emits a clear "requires a non-AOT build" error, but Required=true made argument parsing fail first. Gate the Required flag behind PLUGINS so custom can reach CustomCommand in AOT builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Match full assembly identity when sharing host assemblies PluginLoadContext deferred to the default context by simple assembly name, which could treat a plugin's private dependency as host-provided when the host loaded a different version or strong name of the same simple name, preventing the plugin from loading its own compatible copy. Compare the full identity (name, version, culture, public key token) so only exact matches are shared; other assemblies resolve through the dependency resolver in isolation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#792) * Validate --pluginassembly at parse time with a native FileInfo option Use Option<FileInfo> with AcceptExistingOnly so a missing plugin path fails at argument parsing with a clear "File does not exist" message before the command runs, and simplify PluginLoader.Load to take a FileInfo and drop the empty-path, GetFullPath, and File.Exists guards (the CLI now guarantees an existing path). Scope is limited to --pluginassembly, the one path option that is a pure input whose validation this removes; --mediafiles/--settingsfile/--schemafile keep their current handling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Resolve plugin FullName inside the try block FileInfo.FullName resolves the path and can throw on a malformed or too-long path; move it inside the try so any error is logged via LogAndHandle and returns null instead of throwing, and log the file name in the catch since it does not resolve the path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Harden plugin null handling and add custom command parse tests - PluginLoader.Load: guard against a null FileInfo with ArgumentNullException so the catch block never dereferences a null argument - CustomCommand: log an explicit error before returning when the plugin path is missing so the failure is visible even though the option is required - CommandLineTests: cover the custom command, an existing plugin path binds and a missing path fails at parse time via AcceptExistingOnly Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Log the resolved plugin path in the load failure message Track the assembly path in a single variable that starts as the file name and upgrades to the resolved full path inside the try, so the catch logs the most specific location available (full path when resolved, file name if FullName threw) instead of only the ambiguous file name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Gate --pluginassembly existence check to non-AOT builds AcceptExistingOnly was applied unconditionally, so under an AOT build a non-existent plugin path would fail at parse instead of reaching CustomCommand and its "requires a non-AOT build" message. Gate both Required and AcceptExistingOnly behind PLUGINS, matching the command handler. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix --logwarning log flood and log detections as warnings The per-file log elevation was triggered by the SidecarFile.State setter calling PerFileLogLevel.Elevate() on every non-None state change. Because nearly every file gets Verified or ClearedDefaultFlags, nearly every file elevated, so --logwarning showed full information output for all files instead of only files with a warning or error. Confirmed by instrumenting Elevate() and observing the state-change trigger fire before the leaked information events. - Revert SidecarFile.State to a plain auto-property; only warning/error events elevate the per-file log level, as intended. - Log a warning at the point each repair method detects a condition needing action (redundant Default flags with per-type counts, invalid language tags, interlaced video, tracks needing re-encode, etc.), keeping the cleanup steps at information level (elevated by the detection warning). This restores the "Multiple Default flagged tracks" style detection warning that regressed to information, and makes --logwarning surface every modified file. - Replace the log-level tests that asserted state-change elevation with a test asserting state changes no longer elevate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Count SetFlags tracks directly instead of via GetTrackList GetTrackList allocates and sorts a combined list; count the SetFlags tracks directly from the per-type collections in the detection warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Evaluate metadata error conditions once and reuse in the warning RepairMetadataRemux computed HasMetadataErrors for Remove/ReMux/SetLanguage in the early-exit and again in the detection warning. Evaluate each once into locals and reuse them, avoiding the duplicate GetTrackList allocations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The redundant Default flags warning reported only the Default-flagged count per track type, so it was not clear why a track was flagged. Report the count over the total track count per type (e.g. Video: 1/1, Audio: 1/1, Subtitle: 0/2) so the reason is visible: 1/1 is a lone track, 2/4 is multiple defaults. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the actions-deps group with 2 updates: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/setup-qemu-action` from 4.1.0 to 4.2.0 - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](docker/setup-qemu-action@0611638...96fe6ef) Updates `docker/build-push-action` from 7.2.0 to 7.3.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@f9f3042...53b7df9) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps - dependency-name: docker/build-push-action dependency-version: 7.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the actions-deps group with 2 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) and [docker/login-action](https://github.com/docker/login-action). Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](docker/setup-buildx-action@d7f5e7f...bb05f3f) Updates `docker/login-action` from 4.2.0 to 4.3.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@650006c...c99871d) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps - dependency-name: docker/login-action dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The merge-bot held semver-major NuGet bumps for human review via a version-update:semver-major guard fed by dependabot/fetch-metadata. Per the every-tier policy already in Utilities and LanguageTags, the required CI checks are the gate, not the version magnitude. - Remove the metadata step and the semver-major guard from the merge step so every in-repo Dependabot PR auto-merges on open once checks pass. - Update the workflow contract to match: WORKFLOW.md self-sufficiency prose, merge-bot diagram, automation prose, D8.2, D8/D9 summary, S12 trace row, and the AGENTS.md Dependabot invariant. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the two AGENTS.md process-learning bullets (develop-staleness check and issue-closing keywords) with the canonical wording from the sibling repos. The old staleness bullet steered readers to git log / three-dot diff and claimed the two-dot `git diff origin/main origin/develop` "will not do" - which is exactly the check the canonical form now mandates (read its `-` lines; a deletion-only hunk is the real staleness signal). Also append the template's "In a derived repo:" upstream-drift reporting paragraph to copilot-instructions.md. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Normalize line endings and final newlines to .editorconfig spec These files drifted from the .editorconfig line-ending rules (LF where CRLF is mandated for .yml/.md/.json) or were missing the mandated final newline. Bring them to spec; no content changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add Codecov coverage reporting to CI The unit-test job collected no coverage. Emit Cobertura from the existing dotnet test run (coverlet.collector was already referenced) and upload it to Codecov for trending only - never gated: the upload sets fail_ci_if_error false and codecov.yml marks status informational, so a coverage change or Codecov outage cannot block a merge. CODECOV_TOKEN is threaded via secrets: inherit and audited in both the Actions and Dependabot secret stores by configure.sh, since a Dependabot-triggered push runs the validate job too. Docs (WORKFLOW.md, repo-config/README.md) list the new secret; AGENTS.md notes CI CLI linters can run via Docker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add the global editorconfig CRLF default Set end_of_line = crlf on the [*] block so every file type has a defined ending, matching the fleet template. Per-type CRLF rules and .gitattributes LF pins unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Pieter Viljoen <ptr727@users.noreply.github.com> * Keep the husky hook LF under the CRLF default The global [*] end_of_line = crlf also matches the extensionless .husky/pre-commit hook, which must stay LF (a CRLF shebang breaks it when run from the working tree). Add a matching editorconfig LF override, aligned with the existing .gitattributes pin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Pieter Viljoen <ptr727@users.noreply.github.com> --------- Signed-off-by: Pieter Viljoen <ptr727@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the actions-deps group with 2 updates: [docker/login-action](https://github.com/docker/login-action) and [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action). Updates `docker/login-action` from 4.3.0 to 4.4.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@c99871d...af1e73f) Updates `DavidAnson/markdownlint-cli2-action` from 23.2.0 to 24.0.0 - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](DavidAnson/markdownlint-cli2-action@ded1f94...8de2aa0) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 24.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Dependabot and Actions rewrite workflow YAML with LF, so pin
`.github/workflows/*.{yml,yaml}` to LF in `.editorconfig` and convert the
existing files, keeping them consistent instead of mixed on every bump. Add
`.editorconfig-checker.json` and a Docker `editorconfig-checker` step after
actionlint in the lint job so CI enforces the endings. The new checker
surfaced a pre-existing LF `.dockerignore`; normalize it to CRLF like the
`[*]` default, since it is a non-executed pattern file (the parser strips a
trailing CR) rather than execution-sensitive like a Dockerfile. Update
AGENTS.md to record that workflow YAML is LF while other YAML stays CRLF.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Docker build dominates Actions minutes and arm64 is emulated on the amd64 runner, so build linux/amd64,linux/arm64 only on a full main publish; smoke and develop build linux/amd64 only. A job-level env.PLATFORMS drives buildx and build-push, and QEMU is set up only when arm64 is in the platform set. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Media logs identified files by base name only, which is ambiguous when
same-named files (e.g. 4K vs 1080p) live in different folders. Log the
full path in ProcessFile, SidecarFile and ProcessDriver media lines,
including the track dumps (via MediaProps construction).
Interlaced detection:
- FindInterlacedTracks is a pure predicate; it outs an FfMpegIdetInfo
that is non-null only when the idet scan detected interlacing (null
for a container metadata flag).
- DeInterlace logs a single warning that reports how it was detected
(Detected by: Idet / Metadata flag) and, for idet, extracts the
MultiFrame evidence (percentage and field counts) from the out object.
Also enrich "Tags detected" (per-tool) and "Cover Art detected" (format),
and normalise the log placeholder to {FileName} everywhere ({File} and a
stray {fileName} renamed).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) * Encapsulate interlaced decision in a self-describing reason string Replace IsInterlaced(out double percentage) with IsInterlaced(out string reason) so the idet types own both the verdict and its human-readable justification. Callers log the reason string directly instead of reaching into MultiFrame and re-deriving the percentage. Drop FfMpegIdetInfo.WriteLine() (and the now-unused Repeated/Frames WriteLine helpers), which logged RepeatedFields and SingleFrame lines that never fed the decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Keep the boolean interlaced check allocation free and assert the idet invariant Split the reason string construction out of the decision so the boolean only IsInterlaced() no longer allocates on the hot idet gating path, and assert the detected-interlaced invariant where DeInterlace logs the reason. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Assert the reason string dominant percentage in the idet tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Match the undetermined-majority reason wording to its >= condition Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps InsaneGenius.Utilities from 3.6.2 to 3.6.18 Bumps ptr727.LanguageTags from 1.5.6 to 1.5.32 --- updated-dependencies: - dependency-name: InsaneGenius.Utilities dependency-version: 3.6.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: ptr727.LanguageTags dependency-version: 1.5.32 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- Error summary prints the failing operation (nameof-tracked) instead of Error: None when a failure left no sidecar state. - Media tools log their own failures with the process exit code and summarized output from the correct per-tool stream (mkvmerge on stdout), collapsed to a single line; the out error parameter was dropped to remove double-logging. - Signal interruption reports the Unix 128 + signal exit code (SIGINT 130, SIGQUIT 131, SIGTERM 143) so callers can detect a graceful stop. - idet parsing uses the last frame-count triple, fixing progressive files with doubled idet output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align the FFmpeg SEI NAL unit lookup with FFprobe's `hevc` codec name so H.265/HEVC closed captions are removed via filter_units, same as H.264 and MPEG-2. HDR10 and HDR10+ HEVC content remains guarded. Adds FfMpegNalUnitTests and updates the closed captions docs.
Reference every PR as a clickable markdown link (never a bare #N), and present input requests as a numbered list so the user can reply per number. Mirrors the canonical ProjectTemplate rule (#268). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt the standard --loglevel / --logfile / --logclear logging pattern with a static LoggerFactory. Keep the per-file auto up-level feature but make it explicit via --logelevate; --logwarning and --logappend are deprecated with one-time warnings. Log events follow a consistent taxonomy (documented in AGENTS.md): Warning = decision to modify the media (elevation trigger), Information = the app doing its job, Debug = tool/read mechanics, Error = failures. Debug/Verbose are now selectable and low-level chatter is re-leveled accordingly. Also: corrected the 3.20 interlace/idet notes to describe the parse-resilience fix; bridged library logging (ptr727.Utilities 3.7.5 and ptr727.LanguageTags) through a shared Microsoft.Extensions.Logging factory; bumped Serilog to 4.4.0.
LogFailedResult folded the ' : ' separator into a quoted string value, so the app's {Message} template rendered 'ExitCode: 0" : ...' with the quote after the exit code. Log the error summary as its own {Error} value with the separator in the template. ExitCode 0 on failure is expected: ffmpeg can exit 0 yet report a fatal error on stderr. Adds tests that exercise LogFailedResult and assert the quote placement; tests run serially since they share global static state.
FfProbe's subcc, bitrate, and temp-file paths logged the tool stderr as a second error line unconditionally. A cancelled tool (SIGTERM) produces no stderr, so this rendered an empty "" line. Route those through a LogErrorOutput helper that skips empty or whitespace-only output. Adds tests.
Replace the assembly-wide xunit.runner.json parallelizeTestCollections:false (added in #821, which slowed the whole suite) with a 'Sequential' collection using DisableParallelization, applied only to the two tests that swap the global Serilog Log.Logger. The rest of the suite parallelizes again.
Bump ptr727.Utilities to 4.0.7 and replace the plain HttpClient with the library's resilient Download API (Polly retry + circuit breaker via Microsoft.Extensions.Http.Resilience) for tool downloads and the app version check. The library factory preserves the 120s timeout and an entry-assembly user-agent (PlexCleaner/<version>); resilience events log through the wired LogOptions factory. GetLatestRelease returns false on download or parse failure rather than throwing.
…r, complete Lint tasks (#825) Adds .editorconfig-checker.json (EOL-only), switches the editorconfig CI step to :latest + renames it, and completes the VS Code Lint group.
Local Lint tasks always pull the current :latest image (ProjectTemplate #284).
There was a problem hiding this comment.
Pull request overview
Promotes develop -> main for the 3.20 release, bringing in a broad set of functional updates (logging overhaul, resilient HTTP downloads/version checks, HEVC closed-caption removal, default-flag normalization, custom plugin command, signal handling), plus CI/lint and documentation updates aligned with the repo’s workflow contract.
Changes:
- Reworked logging and per-file elevation (
--loglevel,--logelevate, append-by-default rolling logs, deprecated flags) and propagated logging intoptr727.*libraries. - Switched downloads/version checks to
ptr727.Utilitiesresilient HTTP, added custom plugin support (non-AOT), and improved media/tool processing behavior (HEVC CC removal, default flag normalization, idet parsing). - Hardened CI/linting (Codecov reporting, EditorConfig EOL enforcement, workflow updates) and updated release/docs/sample config artifacts for 3.20.
Reviewed changes
Copilot reviewed 72 out of 84 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| WORKFLOW.md | Updates workflow contract docs (Dependabot every-tier auto-merge, Codecov token notes). |
| version.json | Bumps version floor to 3.20 for the release. |
| Sandbox/Program.cs | Updates sandbox to new ptr727.Utilities logging factory usage. |
| Samples/PlexCleaner/Sidecar.State.PlexCleaner | Adds/updates persisted sidecar sample for schema/state behaviors. |
| Samples/PlexCleaner/PlexCleaner.v4.json | Normalizes sample JSON formatting/newline. |
| Samples/PlexCleaner/PlexCleaner.v3.json | Normalizes sample JSON formatting/newline. |
| Samples/PlexCleaner/PlexCleaner.v2.json | Normalizes sample JSON formatting/newline. |
| Samples/PlexCleaner/PlexCleaner.v1.json | Normalizes sample JSON formatting/newline. |
| repo-config/README.md | Documents required secret names including Codecov token. |
| repo-config/configure.sh | Audits Codecov token in both Actions/Dependabot secret stores. |
| README.md | Updates 3.20 release notes, logging flags, stop-signal guidance, and adds Custom Plugins docs. |
| Plugins/MatroskaHeaderCleanup/MatroskaHeaderCleanup.csproj | Adds example plugin project wired against PlexCleaner host assembly. |
| Plugins/MatroskaHeaderCleanup/MatroskaCleanupPlugin.cs | Implements example IProcessPlugin for Matroska seek-index repair. |
| PlexCleanerTests/ToolFailureLogFormatTests.cs | Adds regression tests for tool-failure log formatting. |
| PlexCleanerTests/PluginLoaderTests.cs | Adds tests for plugin loader example assembly loading. |
| PlexCleanerTests/PlexCleanerTests.csproj | References example plugin project so its DLL is available to tests. |
| PlexCleanerTests/PlexCleanerFixture.cs | Updates test fixture to ptr727.Utilities logging factory and adds sequential collection definition. |
| PlexCleanerTests/PerFileLogLevelTests.cs | Adds comprehensive tests for per-file log elevation filter behavior. |
| PlexCleanerTests/FfProbeLogTests.cs | Adds tests ensuring cancelled tool runs don’t log empty stderr lines. |
| PlexCleanerTests/FfMpegNalUnitTests.cs | Adds tests for HEVC/H.264/MPEG2 NAL unit mapping. |
| PlexCleanerTests/FfMpegIdetParsingTests.cs | Adds tests ensuring idet parser uses the final stats block. |
| PlexCleanerTests/FfMpegIdetDecisionTests.cs | Updates decision tests for new reason-string interlace verdict. |
| PlexCleanerTests/DefaultTrackFlagsTests.cs | Adds tests for redundant/default flag clearing logic. |
| PlexCleanerTests/CommandLineTests.cs | Updates/extends CLI parsing tests for new logging options and custom plugin option validation. |
| PlexCleaner/VideoProps.cs | Adds level-selectable per-track logging and placeholder normalization. |
| PlexCleaner/TrackProps.cs | Adds level-selectable per-track logging; re-levels some logs to Debug. |
| PlexCleaner/Tools.cs | Switches tool download/info calls to ptr727.Utilities.Download and re-levels noisy logs to Debug. |
| PlexCleaner/SubtitleProps.cs | Re-levels closed-caption flag logging to Debug. |
| PlexCleaner/SidecarFileJsonSchema.cs | Switches utilities dependency to ptr727.Utilities. |
| PlexCleaner/SidecarFile.cs | Adds ClearedDefaultFlags state; switches to full-path logging and re-levels sidecar/tool reads to Debug. |
| PlexCleaner/SevenZipTool.cs | Uses resilient GitHub release lookup and centralized failure logging behavior. |
| PlexCleaner/Properties/launchSettings.json | Normalizes formatting/newline. |
| PlexCleaner/Program.cs | Logging factory integration, library logger propagation, deprecation warnings, POSIX-signal handling, custom command wiring. |
| PlexCleaner/ProcessResultJsonSchema.cs | Adds FailedOperation reporting to results. |
| PlexCleaner/ProcessDriver.cs | Re-levels enumeration/tool probing logs to Debug; forces end-of-task summary logs via override context. |
| PlexCleaner/PluginLoader.cs | Adds plugin load context/host and plugin discovery/initialization logic (non-AOT). |
| PlexCleaner/PlexCleaner.csproj | Makes AOT opt-in; adds PLUGINS constant gating; switches to ptr727.Utilities + Serilog MEL bridge package. |
| PlexCleaner/PerFileLogLevel.cs | Adds per-file log-session filter with opt-in elevation behavior. |
| PlexCleaner/Monitor.cs | Removes legacy “press keys to exit” messaging; keeps monitoring banner. |
| PlexCleaner/MkvPropEditTool.cs | Adds default-flag clearing command and centralizes non-zero exit logging. |
| PlexCleaner/MkvPropEditBuilder.cs | Adds ClearFlags helper for mkvpropedit argument construction. |
| PlexCleaner/MkvMergeTool.cs | Switches network reads to resilient HTTP; centralizes tool-failure logging; treats stdout as error stream. |
| PlexCleaner/MediaTool.cs | Adds structured failed-result logging, output sanitization, and debug-level execution logs. |
| PlexCleaner/MediaProps.cs | Adds log level parameter for per-track dumps. |
| PlexCleaner/MediaInfoTool.cs | Uses resilient GitHub release lookup and centralized failure logging. |
| PlexCleaner/LoggerFactory.cs | New static logger factory (levels, rolling file behavior, per-file elevation filter, MEL bridge). |
| PlexCleaner/IProcessPlugin.cs | Adds plugin API contracts (IProcessPlugin, IPluginHost, PluginApi). |
| PlexCleaner/HandBrakeTool.cs | Uses resilient GitHub release lookup and centralized failure logging. |
| PlexCleaner/GitHubRelease.cs | Implements resilient GitHub latest-release fetch with non-throwing parse behavior. |
| PlexCleaner/FfProbeTool.cs | Centralizes stderr logging to avoid empty-error lines; re-levels execution logs. |
| PlexCleaner/FfMpegTool.cs | Fixes HEVC codec tag mapping for CC removal; centralizes failures; adjusts verify/remux APIs. |
| PlexCleaner/FfMpegIdetInfo.cs | Uses last idet stats block and emits self-describing interlace decision reason string. |
| PlexCleaner/FfMpegBuilder.cs | Minor comment cleanup. |
| PlexCleaner/Convert.cs | Adjusts for updated tool APIs and centralized failure logging. |
| PlexCleaner/CommandLineOptions.cs | Adds --loglevel/--logelevate/--logclear and custom --pluginassembly parsing/binding; deprecates legacy flags. |
| PlexCleaner/Bitrate.cs | Re-levels bitrate diagnostic output to Debug; switches utilities dependency. |
| PlexCleaner.slnx | Adds plugin project to solution. |
| PlexCleaner.schema.json | Normalizes formatting/newline. |
| HISTORY.md | Adds detailed 3.20 release history entry. |
| Docs/LanguageMatching.md | Normalizes formatting/indentation. |
| Docs/CustomOptions.md | Normalizes formatting/indentation. |
| Docker/Dockerfile | Copies Plugins into Docker build context for tests/build. |
| Directory.Packages.props | Replaces InsaneGenius.Utilities with ptr727.Utilities, bumps Serilog/LanguageTags, adds Serilog MEL bridge. |
| cspell.json | Adds new CLI/plugin-related words for spellchecking. |
| codecov.yml | Configures Codecov statuses as informational (non-gating). |
| ARCHITECTURE.md | Documents AOT as opt-in and plugin loader exclusion under AOT. |
| .husky/pre-commit | Adds dockerized Markdown/spell linting on staged Markdown (best-effort local parity). |
| .gitignore | Ignores coverage output directory. |
| .github/workflows/validate-task.yml | Adds coverage collection + Codecov upload; adds EditorConfig EOL enforcement; bumps markdownlint action pin. |
| .github/workflows/test-pull-request.yml | Inherits secrets for validate workflow to enable coverage upload. |
| .github/workflows/publish-release.yml | Formatting-only normalization (no semantic change indicated). |
| .github/workflows/merge-bot-pull-request.yml | Removes semver-major NuGet exception; every-tier Dependabot auto-merge. |
| .github/workflows/build-release-task.yml | Inherits secrets for validate workflow to enable coverage upload. |
| .github/workflows/build-docker-task.yml | Makes platforms conditional (multi-arch only on main publish), updates pinned action versions/SHAs. |
| .github/copilot-instructions.md | Adds “report drift upstream” note for derived repos. |
| .editorconfig-checker.json | Adds EOL-only editorconfig-checker config. |
| .editorconfig | Sets global CRLF default; pins workflow YAML to LF; pins Husky hook to LF. |
| .dockerignore | Normalizes formatting/newline. |
| .config/dotnet-tools.json | Normalizes formatting/newline. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promote develop → main for the 3.20 release (30 commits). Full notes in
HISTORY.md.Highlights
h265never matched FFprobe'shevc, so HEVC files were wrongly reported "Unsupported video format for Closed Captions removal". Now cleaned viafilter_units=remove_types=39(HDR10/HDR10+ still guarded). (Enable closed caption removal for H.265/HEVC video #814)--loglevel(Verbose…Fatal; Debug/Verbose now selectable), opt-in per-file--logelevate, append-by-default log file with 1 GiB rolling +--logclear, deprecated--logwarning/--logappend(still work, warn once), staticLoggerFactory, consistent event taxonomy, logger propagated toptr727.Utilities/ptr727.LanguageTags. (Rework logging with --loglevel and opt-in per-file elevation #818, Fix misplaced quote in the tool-failure log line #821, Do not log an empty error line on tool cancellation #822, Serialize only the log-capture tests, not the whole suite #823, Fix --logwarning log flood and log repair detections as warnings #793, Show default/total track counts in the default-flags detection warning #794)ptr727.Utilities(Polly retry + circuit breaker viaMicrosoft.Extensions.Http.Resilience), replacing the manualHttpClient. (Use the ptr727.Utilities resilient HTTP client for downloads #824)Defaultflags (lone track, preferred-audio, subtitles) instead of only warning; surface default/total counts. (Show default/total track counts in the default-flags detection warning #794,Normalize multiple and redundant Default track flags)customcommand — load a userIProcessPluginassembly and run it over the media files (withMatroskaHeaderCleanupexample);--pluginassemblyvalidated at parse time. (Add custom plugin command for bespoke re-processing (IProcessPlugin) #791, Validate --pluginassembly at parse time with a native FileInfo option #792)SIGINT/SIGTERM/SIGQUITfor gracefuldocker stop, always log the end-of-run summary and exit code. (Always log the process summary and handle stop signals)idetinterlace fix — parser now uses the final cumulative statistics block (ffmpeg can emit stats more than once), fixing occasional interlace-detection failures; plus interlace-detection reporting cleanup. (Log full file paths and surface interlaced detection source #809, Encapsulate interlaced decision in a self-describing reason string #810)maincarries dependabot bumps not ondevelop(forward-only model). Pergit merge-tree, exactly two files conflict, and both resolve indevelop's favor with nothing lost frommain:Directory.Packages.props—mainbumpedInsaneGenius.Utilities3.6.2→3.6.18 andptr727.LanguageTags1.5.6→1.5.32;developremovedInsaneGenius.Utilities(replaced byptr727.Utilities4.0.7), setLanguageTags1.5.33,Serilog4.4.0, and addedSerilog.Extensions.Logging. Takedevelop: 1.5.33 ≥ 1.5.32, and the InsaneGenius bump is moot (package removed)..github/workflows/build-docker-task.yml—mainbumped 4 docker action pins;developbumped the same pins to the identical SHAs and refactored platform selection (multi-arch only on a main publish, via aPLATFORMSenv). Takedevelop: the pins are identical, so nothing frommainis lost..github/workflows/validate-task.ymlauto-merges cleanly;AGENTS.mdandDockerfiledon't conflict (maindidn't touch them).Version:
version.json= 3.20 (NBGV tags the release onmain).