Summary
A file can be 100% valid to ffprobe, ffmpeg, mkvmerge, and mkvinfo (rc=0, no errors, no warnings) yet still fail Direct Play in the players PlexCleaner targets (Jellyfin/Emby, Android TV / NVIDIA Shield / ExoPlayer). Because PlexCleaner's Verify/AutoRepair is built on exactly those tools, it reports the file as healthy and never remuxes it — so the file silently can't be Direct Played, which is the one thing PlexCleaner exists to guarantee.
The ask is not to remux MKVs heuristically or "just in case". Blindly remuxing every file that wasn't muxed by mkvmerge is wasteful and non-deterministic. The ask is for a deterministic check that predicts whether the target player will fail, and to remux only when that check says it will.
The deterministic signal (and why it's cheap for PlexCleaner specifically)
The failure is fully deterministic and reproducible. Jellyfin reads Matroska keyframes via Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor, which is built on the NEbml library. On an affected file it throws every time:
[ERR] Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor:
Extracting keyframes from FILE using matroska metadata failed
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at NEbml.Core.EbmlReader.EnterContainer()
at Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor.GetKeyframeData(String filePath)
The Shield (Jellyfin Android TV / ExoPlayer) independently hangs at 0 ms with no server transcode ever started, confirming the file genuinely isn't Direct-Play-able — not an NEbml quirk.
The key point for PlexCleaner: PlexCleaner is .NET, NEbml is a .NET library, and so is Jellyfin's keyframe extractor. PlexCleaner can run the exact same structural EBML read the player uses and get a deterministic yes/no — "will Jellyfin's keyframe/Matroska parse succeed on this file?" — without re-encoding or guessing. If that read throws, the file is known-bad → remux; otherwise leave it untouched. This is a precise pass/fail oracle, not a heuristic.
(A strict Cues/SeekHead/EBML-element validation would achieve the same thing if you'd rather not take an NEbml dependency.)
Real-world repro
One episode refused to play on a Shield via Jellyfin; three siblings in the same folder played fine.
The file (broken for Direct Play): Matroska, Writing application: Lavf58.42.100 (older ffmpeg), H.264 High / E-AC3 5.1 / ASS subs.
Every tool PlexCleaner currently uses says it's clean:
$ ffmpeg -v error -i FILE -f null - # full decode -> rc=0, no output
$ ffmpeg -v warning -i FILE -map 0:v -c copy -f null - # container read -> rc=0, no output
$ mkvmerge -i FILE # rc=0
$ mkvmerge -J FILE # errors: [] warnings: []
$ mkvinfo FILE # rc=0, parses end-to-end
Yet Jellyfin/Shield can't Direct Play it (NEbml exception + 0 ms hang above). A web client only works because that path transcodes. A plain lossless remux fixes it permanently:
$ mkvmerge -o FILE.fixed.mkv FILE # or: ffmpeg -i FILE -map 0 -c copy FILE.fixed.mkv
Why PlexCleaner currently misses it
The "good" siblings were remuxed by PlexCleaner and show Writing application: mkvmerge. The broken one still shows Lavf58.42.100 and was left untouched because (a) it's already MKV so the container-conversion ReMux path doesn't apply, and (b) tool-based Verify reports no problem so AutoRepair never fires. Sidecar confirms: "Verified": false, original timestamp retained.
So the gap is specifically that Verify measures tool-parseability, not player-Direct-Play-ability. A deterministic player-parity check (run the same EBML/keyframe read Jellyfin runs) would close it while only remuxing files that are provably broken.
Environment
- PlexCleaner
3.17.23+5c823303ee (docker ptr727/plexcleaner:latest)
- Jellyfin 10.11.x + Jellyfin Android TV 0.19.9 (NVIDIA Shield)
- mkvmerge v92.0, jellyfin-ffmpeg n7.x for diagnostics
Happy to provide a sample file, full ffprobe/mkvinfo dumps, or test a build.
Summary
A file can be 100% valid to
ffprobe,ffmpeg,mkvmerge, andmkvinfo(rc=0, no errors, no warnings) yet still fail Direct Play in the players PlexCleaner targets (Jellyfin/Emby, Android TV / NVIDIA Shield / ExoPlayer). Because PlexCleaner'sVerify/AutoRepairis built on exactly those tools, it reports the file as healthy and never remuxes it — so the file silently can't be Direct Played, which is the one thing PlexCleaner exists to guarantee.The ask is not to remux MKVs heuristically or "just in case". Blindly remuxing every file that wasn't muxed by mkvmerge is wasteful and non-deterministic. The ask is for a deterministic check that predicts whether the target player will fail, and to remux only when that check says it will.
The deterministic signal (and why it's cheap for PlexCleaner specifically)
The failure is fully deterministic and reproducible. Jellyfin reads Matroska keyframes via
Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor, which is built on the NEbml library. On an affected file it throws every time:The Shield (Jellyfin Android TV / ExoPlayer) independently hangs at 0 ms with no server transcode ever started, confirming the file genuinely isn't Direct-Play-able — not an NEbml quirk.
The key point for PlexCleaner: PlexCleaner is .NET, NEbml is a .NET library, and so is Jellyfin's keyframe extractor. PlexCleaner can run the exact same structural EBML read the player uses and get a deterministic yes/no — "will Jellyfin's keyframe/Matroska parse succeed on this file?" — without re-encoding or guessing. If that read throws, the file is known-bad → remux; otherwise leave it untouched. This is a precise pass/fail oracle, not a heuristic.
(A strict Cues/SeekHead/EBML-element validation would achieve the same thing if you'd rather not take an NEbml dependency.)
Real-world repro
One episode refused to play on a Shield via Jellyfin; three siblings in the same folder played fine.
The file (broken for Direct Play): Matroska,
Writing application: Lavf58.42.100(older ffmpeg), H.264 High / E-AC3 5.1 / ASS subs.Every tool PlexCleaner currently uses says it's clean:
Yet Jellyfin/Shield can't Direct Play it (NEbml exception + 0 ms hang above). A web client only works because that path transcodes. A plain lossless remux fixes it permanently:
$ mkvmerge -o FILE.fixed.mkv FILE # or: ffmpeg -i FILE -map 0 -c copy FILE.fixed.mkvWhy PlexCleaner currently misses it
The "good" siblings were remuxed by PlexCleaner and show
Writing application: mkvmerge. The broken one still showsLavf58.42.100and was left untouched because (a) it's already MKV so the container-conversionReMuxpath doesn't apply, and (b) tool-basedVerifyreports no problem soAutoRepairnever fires. Sidecar confirms:"Verified": false, original timestamp retained.So the gap is specifically that
Verifymeasures tool-parseability, not player-Direct-Play-ability. A deterministic player-parity check (run the same EBML/keyframe read Jellyfin runs) would close it while only remuxing files that are provably broken.Environment
3.17.23+5c823303ee(dockerptr727/plexcleaner:latest)Happy to provide a sample file, full ffprobe/mkvinfo dumps, or test a build.