Skip to content

media-info-wdx: Matroska/WebM (.mkv/.webm) support via native EBML parser#13

Merged
NikolaiSachok merged 3 commits into
mainfrom
media-info-mkv
Jul 4, 2026
Merged

media-info-wdx: Matroska/WebM (.mkv/.webm) support via native EBML parser#13
NikolaiSachok merged 3 commits into
mainfrom
media-info-mkv

Conversation

@NikolaiSachok

Copy link
Copy Markdown
Owner

What & why

.mkv files showed no metadata in Double Commander because macOS system frameworks can't open Matroska/WebM at allAVURLAsset reports the file unreadable with zero tracks. MKV was never in the plugin's supported set, so DC never called it.

This adds a self-contained EBML parser (ParseMKV) that reads dimensions, duration, and frame rate straight from the file head (Segment > Info and Segment > Tracks > TrackEntry > Video), stopping at the first Cluster. Same philosophy as the existing AVI RIFF reader: no dependency, no network, bounded read, routed to the fast (non-deferred) parse path. .webm is the same container, so it's covered too.

Verification

Tested end-to-end against a real 6h54m 1080p30 .mkv — output matches ffprobe exactly:

Plugin ffprobe
Resolution 1920 × 1080 1920 × 1080
Frame rate 30 fps 30/1
Duration 24854.80 s 24854.809 s
  • Headless real-artifact harness grows a synthetic-Matroska case — 30/30 assertions pass.
  • Universal binary (x86_64 + arm64), all ABI exports present, pre-commit gate + leak-guard clean.
  • .mkv/.webm added to the category table, compiled DetectString, and register_plugin.py.
  • VERSION 0.1.0 → 0.2.0; CHANGELOG + README updated.

A fresh-agent /code-review (high) is running on this diff per the repo charter; findings will be addressed before merge.

NikolaiSachok and others added 3 commits July 4, 2026 16:43
…L parser

macOS system frameworks can't open Matroska/WebM containers — AVURLAsset
reports the file unreadable with zero tracks — so DC showed nothing for
.mkv files. Add a self-contained EBML reader that pulls dimensions,
duration, and frame rate straight from the file head (Segment > Info and
Segment > Tracks > TrackEntry > Video), stopping at the first Cluster.

Mirrors the existing AVI RIFF reader: no dependency, no network, bounded
read, routed to the fast (non-deferred) parse path. .mkv/.webm added to
the category table, the compiled DetectString, and register_plugin.py.

Verified end-to-end against a real 6h54m 1080p30 .mkv — output matches
ffprobe exactly (1920x1080, 30 fps, 24854.809 s). Headless harness grows
a synthetic-Matroska case; 30/30 assertions pass. Bump VERSION 0.1.0 -> 0.2.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes from the fresh-agent /code-review of the EBML parser:

- Bound FormatDuration (>~100y rejected) so a corrupt/hostile Matroska
  Duration float can't reach an out-of-range llround (undefined) and emit
  a garbage time string. Protects every parser path.

- Rewrite ParseMKV as a *seeking* reader: it walks the Segment's children
  by element header, reading only the small Info/Tracks bodies and skipping
  past large SeekHead/Cues/Attachments. Removes the 2MB read cap that both
  (a) permanently cached empty metadata for spec-valid files whose Tracks
  fell past the window and (b) did a synchronous 2MB read on DC's calling
  thread. Now a few KB regardless of file size or layout.

- Prefer DisplayWidth/Height (aspect-correct) over coded PixelWidth/Height,
  matching what a player and the AVFoundation path report for anamorphic
  video; parse the audio track and CodecID too, so mkv/webm now expose
  video/audio codec, sample rate, and channels (audio-only .webm included).

- Extract FillVideoFields, shared by the AVI and MKV readers, removing the
  triplicated dimensions/duration/frame-rate/Summary assembly.

Harness grows anamorphic (display != coded), audio-track/codec, and
hostile-duration cases: 37/37 pass. Re-verified end-to-end against the real
6h54m .mkv — resolution, fps, duration, and now codec/48kHz/stereo all match
ffprobe exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second-pass /code-review of the seeking rewrite:

- Fix a real regression: prefer DisplayWidth/Height only when DisplayUnit
  is pixels (0, the default). DisplayUnit 1/2/3 means cm/inches/aspect-ratio,
  where those fields aren't pixel counts — a 16:9 aspect-ratio track was
  reported as "16 × 9". Now falls back to the coded PixelWidth/Height.

- Robustness: raise the top-level Segment search from 8 to 64 elements (a
  file may carry leading Void/padding before the Segment); handle an
  unknown-size Info/Tracks master by reading a bounded window and walking it
  recursively (stopping at the first Cluster) instead of bailing out.

- Cleanup: remove the dead, write-only MIInfo.category property (never read;
  classification comes from CategoryForPath) and unify the duplicated EBML
  header decode into MKVDecodeHeader, shared by the in-memory walker and the
  file-seeking reader.

Harness grows an aspect-ratio-display (DisplayUnit=3) case asserting the
coded size wins: 40/40 pass. Real 6h54m .mkv output unchanged and still
matches ffprobe (1080p30, 24854.8s, H.264/AAC/48kHz/stereo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NikolaiSachok NikolaiSachok merged commit b4b979b into main Jul 4, 2026
2 checks passed
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