mcap/modules/Capture_File
2026-10-08 11:37:21 +01:00
..
label_chunks.jai [0.0.5] Labels: SigLIP 2 labels and image vectors in .mcap version 3 chunks; label: and like: search 2026-10-08 11:37:21 +01:00
module.jai [0.0.5] Labels: SigLIP 2 labels and image vectors in .mcap version 3 chunks; label: and like: search 2026-10-08 11:37:21 +01:00
ocr_text.jai [0.0.5] Labels: SigLIP 2 labels and image vectors in .mcap version 3 chunks; label: and like: search 2026-10-08 11:37:21 +01:00
README.md [0.0.5] Labels: SigLIP 2 labels and image vectors in .mcap version 3 chunks; label: and like: search 2026-10-08 11:37:21 +01:00

The .mcap file

One capture in one file: a fixed magic, a file version stamp, the capture's data as Capture Text, what OCR read in it, what other models added (chunks), a small preview, then the images, then any large chunks. There is no ZIP and no compression around it; the images keep their own PNG compression.

The parts a library browser needs (header, text, OCR, front chunks and preview) come first, so it reads the start of each file and never touches the images; a search, which needs no preview, reads less still.

Layout, version 3

All integers are little-endian. Sizes count bytes.

Field Type
magic 8 bytes 89 4D 43 41 50 0D 0A 1A, which is \x89MCAP\r\n\x1A. Says this is an .mcap, in every version.
file version u32 3. Versions 1 and 2 are still read; see below.
text size, text u32, bytes Capture Text, UTF-8: every JSON file of the capture, as [[file]] sections.
OCR size, OCR u32, bytes OCR text, UTF-8 (below): what each OCR model read. Size 0 when none has.
front chunks chunk list Small things models added, such as labels and an image vector (below).
preview width, height u16, u16 In pixels.
preview size, preview u32, bytes PNG. Size 0 when there is none.
original name size, name u16, bytes The original image's name in the .snagx, such as {GUID}.png; the page's CaptureBackgroundImage names it too.
original size, original u32, bytes The capture as taken: PNG, exactly as it was in the .snagx.
rendered kind u8 0: none. 1: the same as the original (stored once). 2: its own, below.
rendered size, rendered u32, bytes Only for kind 2. The capture with its annotations drawn in (Snagit's thumbnail.png): PNG, exactly as it was.
back chunks chunk list Large things models added, such as segmentation masks: after the images, so reading the front never reaches them.

The file ends there: anything after the last part is a mistake.

The magic borrows PNG's: the first byte has its high bit set, so a channel that strips it shows; \r\n shows line endings being converted; and \x1A stops type on Windows.

A chunk list is a u32 count, then each chunk: a 4-byte tag of ASCII letters saying what it is (labl, embd), a u32 size, and that many bytes: a u16 key size, the key (whose it is: a model's name), and the chunk's own data. A file holds at most one chunk per tag and key.

Version 2 is version 3 without the two chunk lists; version 1 is version 2 without the OCR size and OCR. A reader takes all three; anything written is version 3, so an older file becomes version 3 the first time something is stored in it.

The parts

  • Text. Everything the capture's JSON held: index.json, metadata.json, the page's {GUID}.json, and {GUID}.backup.json when there is one.
  • OCR. One section per OCR model, so a capture read by several models keeps every reading; reading it again with a model replaces only that model's section. See OCR text below.
  • Chunks. What other models add, each keyed by the model's name, so models do not overwrite each other; storing a chunk again replaces only the one with its tag and key. A chunk a reader does not know is kept as it is when the file is rewritten, so a later build can add kinds without a new file version. See Label chunks below.
  • Preview. The rendered image (or the original, when there is no rendered one) scaled so its longest side is at most 320 pixels, as a PNG. An image already that small is used as it is, never scaled up.
  • Original and rendered. A capture with no annotations has a rendered image identical to the original, byte for byte; it is then stored once, as kind 1.
  • One page. An .mcap holds one page, as every capture so far does; a .snagx with more is not converted.
  • Left out: {GUID}.backup.png, Snagit's image from before an edit. Its {GUID}.backup.json is kept, as plenty of captures have one without the image.

Versions

A reader checks the magic, then the file version. It refuses a version above its own, never writing over a file from a later version, and reads every version up to its own. Version 0 does not exist.

Converting

snagit-converter mcap converts .snagx files (or extracted captures) to .mcap, and snagit-converter snagx converts .mcap files back to a .snagx Snagit can open. See the README for what each checks.

Back in a .snagx, the JSON is laid out as Snagit writes it, the images are the same bytes, and only the backup image is missing. JSON numbers come back as values: 3.0 is written 3.

OCR text

In the style of the Ex4 text formats:

@version 2

[ocr ppocr-v5-mobile]
engine: "onnxruntime 1.30.0, CUDA"
image:  rendered 1920 1080
date:   2026-10-08T21:04:11Z
line: 412 96 640 18 412 96 1052 96 1052 114 412 114 0.993 "Assign Footprints - KiCad"
line: 20 140 220 14 20 140 240 140 240 154 20 154 0.981 "Symbol : Footprint Assignments"
line: 1700 300 20 160 1720 300 1720 460 1700 460 1700 300 0.952 "Read downwards"
Key Value
[ocr NAME] A section: the model's name, as in its models.config.
engine What ran it, quoted.
image Which image was read (rendered or original) and its width and height; the lines' boxes are in its pixels.
date When it was read, UTC.
line A text line: x, y, width and height, then its four corners as x y pairs, its confidence (0 to 1), and its text, quoted with "" for a quote. A control character in the text is written as a space.

The corners go clockwise from the text's top left: top left, top right, bottom right, bottom left, as the text reads. Level text has the box's corners; a turned line's say which way it reads (the third line above reads downwards, so its top left is the box's top right). x, y, width and height bound them.

A line that cannot be read is skipped, as are keys this version does not know. Version 1 OCR text had no corners (line: x y width height confidence "text"); it is still read, with the box's corners. The OCR text carries its own version, so this did not change the .mcap file version. mcap_store_ocr (modules/Capture_File/ocr_text.jai) stores a section in a file.

Label chunks

A label model (an image-text encoder such as SigLIP 2) adds two front chunks, keyed by its name.

labl: the capture's best labels from a label list, as text in the style above:

@version 1
engine: "onnxruntime 1.30.0, CUDA"
date:   2026-10-08T21:04:11Z
list:   9c4f0b2d81e3a675
label: 0.6720 "Settings"
label: 0.0951 "Installer"
Key Value
engine, date As in OCR text.
list Which label list they were scored against: a 64-bit FNV-1a hash of its names and prompts. Labels from another list are out of date.
label A score, 0 to 1 (the model's own sigmoid; small, but their order is what counts), and the label's name, quoted with "" for a quote. Best first.

embd: the image's vector, binary: a u32 length, a u8 format (1: 16-bit floats), then the values, little-endian. It is a unit vector, so any phrase's vector (from the same model's text encoder) scores against it with a dot product: labels from a new list, or a search by meaning, need no new pass over the images. 768 values, 1.5 KB, for SigLIP 2 base.

mcap_store (modules/Capture_File/module.jai) stores an OCR section and chunks in one rewrite; label_chunks.jai reads and writes these two.