fix: converge IETF language repair and detect Direct Play failures (#747, #746)#748
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
, #746) #747: In monitor mode an invalid/unmappable IETF language tag caused an infinite remux loop because remuxing preserves the tag. Repair invalid ISO 639 / IETF tags in place via MkvPropEdit (recover from the valid counterpart, else undefined), and add a non-convergence guard that marks a file VerifyFailed and stops re-processing when a repair does not resolve the targeted errors. #746: Some Matroska files pass all tool checks yet fail Direct Play because the player's EBML keyframe parse throws. Add a deterministic structural parse mirroring Jellyfin's MatroskaKeyframeExtractor (NEbml) and remux only files that provably fail, leaving valid files untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses two monitor-mode production problems by (1) making invalid/unmappable language-tag repairs converge, and (2) adding a deterministic Matroska structural parse check (player-parity) to detect Direct Play failures and remux only proven-bad files.
Changes:
- Add in-place repair of invalid ISO/IETF language tags via
mkvpropedit, plus a non-convergence guard that marks filesVerifyFailedto prevent repeated rewrite loops. - Introduce a NEbml-based Matroska EBML/Cues structural parse (“player-like” check) and a targeted remux repair path for failures.
- Update docs/changelog and add the
NEbmldependency.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates release notes + feature bullets to document convergence fix and new Direct Play structural check; adds NEbml to the tools list. |
| HISTORY.md | Adds 3.18 release history entries describing both fixes and the non-convergence guard. |
| PlexCleaner/ProcessFile.cs | Implements language-tag repair + convergence guard; adds Matroska structural verification/remux logic. |
| PlexCleaner/Process.cs | Wires the new Matroska structure repair step into the processing pipeline. |
| PlexCleaner/MkvPropEditTool.cs | Extends SetTrackLanguage to optionally include undefined tags so invalid tags can be overwritten deterministically. |
| PlexCleaner/MatroskaKeyframe.cs | New NEbml-based Matroska structural parser used as a Direct Play pass/fail oracle. |
| PlexCleaner/PlexCleaner.csproj | Adds NEbml package reference. |
| Directory.Packages.props | Pins NEbml version centrally. |
| PlexCleaner.code-workspace | Updates recommended VS Code TODO extension. |
| .gitignore | Ignores .artifacts output directory. |
…it stays in sync Address review feedback: document that the structural parse intentionally mirrors Jellyfin's extractor (ignored mandatory-element returns and the open Segment scope are deliberate for player parity), pin the upstream revision it was ported from, and note the sync strategy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…log message Address review feedback: the HISTORY entry overstated the guard scope, it covers language-tag repair, metadata remux, and the Matroska structure check, not set-flags/clear-tags. Rename the helper to SetVerifyFailed and use a neutral message since it also covers the repair-disabled case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in port) and close SetLanguage convergence gap Licensing: the previous parser was ported from Jellyfin (GPL-2.0) into this MIT project, which is incompatible. Reimplement independently from the Matroska element specification using the MIT NEbml reader. Instead of mirroring the player's parse and relying on it to crash, validate the seek index logically: the SeekHead must reference Info, Tracks and Cues, and the Cues must be positioned after the Tracks/Info (a forward-only reader, like the player, cannot reach Cues placed earlier). Verified against the real regression file: the broken file is detected, good files pass. Convergence: the post-remux guard now also re-checks SetLanguage errors when SetIetfLanguageTags is enabled, mirroring the remux trigger condition, so a remux that fails to resolve them cannot loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review feedback: ClearMetadataErrors now also resets per-track HasErrors (which AnyErrors checks) so cleared files are truly clear, and the seek-index check stops after the first cue point instead of walking the whole index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review feedback: the comment now states that mkvpropedit sets the legacy language property and the --normalize-language-ietf global option derives the consistent IETF tag, rather than implying it sets both directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clarify that ReadAt resolves against the entered Segment container, which matches the Segment-relative SeekHead positions, so no manual base offset is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes two production issues found in
monitormode.#747 — infinite remux loop on invalid IETF language tags
A track with an unmappable IETF/BCP-47 tag (e.g.
language='und' language_ietf='enc') was flagged for remux, but mkvmerge preserves the tag, so every monitor cycle re-detected the same error and rewrote the file forever (reporter saw 5,927 rewrites of one 1.5 GB file).RepairLanguageTagsrepairs invalid ISO 639 / IETF tags in place viamkvpropedit— recovering the tag from the valid counterpart, else setting both tound(kept consistent so the next pass doesn't see a mismatch). Detection lives in the repair step (HasInvalidLanguageTag), inspection (TrackProps.SetLanguage) is unchanged.VerifyFailed, and after a repair the targeted error states are re-checked — if they persist the file is markedVerifyFailedand is no longer re-processed, breaking the loop for any unfixable condition.#746 — Verify measured tool-parseability, not player Direct-Play-ability
A file can be clean to ffprobe/ffmpeg/mkvmerge/mkvinfo yet fail Direct Play in Jellyfin/Emby/Shield because the player's EBML keyframe parse throws.
MatroskaKeyframeruns the same structural EBML parse the player uses (NEbml, mirroring Jellyfin'sMatroskaKeyframeExtractor) as a deterministic pass/fail oracle.RepairMatroskaStructureremuxes only files that provably fail (video files only), leaving valid files untouched. Same non-convergence guard applies.InvalidOperationException: Operation is not valid due to the current state of the objectJellyfin reports, while good files and the monitor mode: infinite remux loop on files with unmappable IETF language tags (non-converging re-processing) #747 file pass.Notes
NEbmldependency (1.1.0.5, same version Jellyfin uses).3.18cycle (NBGV auto-bumps the patch); HISTORY.md and README updated.Closes #747
Closes #746
🤖 Generated with Claude Code