The selected capture is the one outlined in the grid and shown in the Capture window. Its image is copied as the Capture window shows it, decoded on a thread of its own and put on the clipboard as screen captures are (Windows only so far); its path is copied as the system writes paths. Both are actions in [hotkeys] (copy_image, copy_path), which do not act while typing in the search box. Settings files at the current version get their default bindings at the next start; the comment saying to raise APP_USER_SETTINGS_VERSION for a new action had it backwards.
568 lines
35 KiB
Markdown
568 lines
35 KiB
Markdown
# mcap
|
||
|
||
A browser for a library of screenshots and photos: every `.mcap` file in a folder (and its folders)
|
||
as a grid of thumbnails, searchable by the text in the images (OCR), by what they show (labels), by
|
||
their window title or file name, URL, app or camera and annotations, and by meaning ("a circuit board"). Written in [Jai](https://jai.community)
|
||
with [Dear ImGui](https://github.com/ocornut/imgui), for Windows (macOS and Linux build, but OCR
|
||
and labels are tested on Windows with an NVIDIA GPU).
|
||
|
||
The `.mcap` files come from the **Import** window, which takes any folder of images (PNG, JPEG, HEIC,
|
||
AVIF, WebP, GIF, BMP, TIFF, camera RAW), videos (MP4, MOV, MKV, WebM, AVI, WMV, MPEG) and Snagit
|
||
`.snagx` captures, or from `snagit-converter`
|
||
(`D:\McapIndex\SnagitConverter`); `modules/Capture_File/README.md` describes the format. On
|
||
Windows, **PrintScreen** captures a region of the screen straight into the library, as Snagit does;
|
||
see [Screen capture](#screen-capture).
|
||
|
||
## Getting started
|
||
|
||
1. **Fetch what is not in git** (each download's SHA-256 is checked):
|
||
|
||
```
|
||
cd modules/onnxruntime && jai fetch.jai # onnxruntime 1.30.0, built for CUDA 13
|
||
cd modules/OCR && jai fetch_models.jai # the OCR models (~196 MB; `- mobile` for the light one only)
|
||
cd modules/Labels && jai fetch_models.jai # the label model, SigLIP 2 (~690 MB)
|
||
```
|
||
|
||
2. **For the GPU** (optional; without it everything runs on the CPU, several times slower): an
|
||
NVIDIA driver, the CUDA Toolkit 13.x, and cuDNN 9.x for CUDA 13. cuDNN installed in its usual
|
||
place (`C:\Program Files\NVIDIA\CUDNN`) is found without changing PATH. The OCR window says
|
||
what, if anything, is missing.
|
||
|
||
3. **Build and run:** `jai first.jai - release`, then `bin\mcap.exe`. The build copies onnxruntime
|
||
and the models beside the program.
|
||
|
||
4. **The library** is `D:\McapIndex\mcap`, a folder for each place imported from
|
||
(`SnagitLibraryPersonal`, `SnagitLibraryWork`, ...); `-library FOLDER` shows another folder.
|
||
**Import** (button above the grid) brings in more; see [Importing](#importing).
|
||
|
||
5. **Read the library:** open the **OCR** window (button above the grid), check **Labels too**,
|
||
and press **Scan the library**. Every capture is read and labelled once and the results are
|
||
stored in its file, so this is needed only for new captures afterwards. On an RTX 5080 laptop,
|
||
8,600 captures took about 13 minutes with the mobile OCR model; labels add about 18 ms a capture,
|
||
and the server OCR model is several times slower.
|
||
|
||
## The main window
|
||
|
||
```
|
||
[ search box .......................... ] 8609 captures Size [---o---] OCR Stats Labels Refresh
|
||
[ thin progress bar while a scan runs ]
|
||
+-------+ +-------+ +-------+ +-------+
|
||
| thumb | | thumb | | thumb | | thumb | newest first
|
||
+-------+ +-------+ +-------+ +-------+
|
||
```
|
||
|
||
- **The grid** shows every capture, newest first by capture date (by name while the catalog,
|
||
what search needs from every file, is still loading: a line beside the count says so). **Size**
|
||
sets the thumbnails' size. Click a thumbnail to open it in the Capture window.
|
||
- **Delete:** hover a thumbnail and click the trash button in its corner. You are asked whether to
|
||
move the file to the Recycle Bin or to delete it permanently, overwriting it with random bytes
|
||
first; "Don't ask again" keeps the choice until the app restarts. Only the `.mcap` goes: the
|
||
`.snagx` it was converted from is untouched.
|
||
- **Copy:** click a thumbnail to select it (it is outlined, and shown in the Capture window), then
|
||
**Ctrl-C** copies its image, as the Capture window shows it (annotations and all; a video's
|
||
frame), and **Ctrl-Shift-C** its full path. Neither acts while you type in the search box. They
|
||
are `copy_image` and `copy_path` in `[hotkeys]` in `settings.config`. Copying images is Windows
|
||
only so far.
|
||
- **Files added or deleted outside the app** (in Explorer, by another program) appear in the grid
|
||
or leave it on their own, folders added, renamed or deleted too: the library is watched.
|
||
**Refresh** looks at the whole folder again, should the watcher have missed something.
|
||
- **OCR**, **Stats** and **Labels** open their windows (below).
|
||
- A **thin bar** under the search box shows a running scan; hover it for the details.
|
||
|
||
### The Capture window
|
||
|
||
One capture at full size, with **Left** and **Right** stepping through the captures shown in the
|
||
grid (in its order, so through a search's matches).
|
||
|
||
- **Fit:** Window, Width, Height, or Off with the **Zoom** slider (and **100%**).
|
||
- **OCR** reads this capture now, and labels it when the label model is up (**Read again** when it
|
||
has been read); **Boxes** shows the text
|
||
lines found, outlined over the image (turned with the text for turned lines). Hover a box for its
|
||
text and confidence. A capture read by several OCR models has a menu to choose whose lines show.
|
||
- While searching, the lines that match are highlighted, with **Matches: N** and **Previous** /
|
||
**Next** to scroll to each.
|
||
- **Labels:** the capture's five best labels with their scores (dim when they come from an older
|
||
label list).
|
||
- **Segments**, for a segmented image:
|
||
- **Drawing:** what segmentation found is drawn over the image, each class in its own colour, the
|
||
same in every image. Each instance is a different shade, so two people side by side differ, and
|
||
edges are darker.
|
||
- **Controls:** an opacity slider, and a legend of the classes, largest first, with how many of
|
||
each. Untick a class to hide it; the choice holds as you step through captures. Right-click a
|
||
class to search `has:` for it.
|
||
- **Colours:** a class's own colour is a hue from a hash of its name. Click its square in the legend
|
||
to choose another, in every image and library. Right-click the square to go back to its own. The
|
||
choices are kept in `segment-colours.config` in the app's data folder.
|
||
- **Hover** the image for the segment under the mouse: its class, score and share of the image.
|
||
|
||
### The OCR window
|
||
|
||
- **Model:** PP-OCRv5 mobile (light, about 85 ms a capture on the GPU) or server (more accurate,
|
||
about 0.4 s). The choice is kept in the settings file (`model` under `[ocr]`).
|
||
- **Running on:** the GPU or the CPU, and if the CPU, what to install for the GPU.
|
||
- **Scan the library** reads every capture the model has not read yet and, with **Labels too**,
|
||
labels every capture not yet labelled with the current list. While a search is active the button
|
||
reads **Scan the captures shown** and covers only those. Each capture is stored as soon as it is
|
||
read, so **Pause**, **Cancel** and quitting lose nothing; the next scan picks up where it stopped.
|
||
Progress shows captures done, the rate, time elapsed and time left.
|
||
- **Erase OCR readings from the library** (for testing) takes every OCR reading out of every file,
|
||
after asking. A scan reads them again.
|
||
- Below, the text of the capture open in the Capture window, line by line.
|
||
|
||
### The Labels window
|
||
|
||
- The label model's status, and how many captures are labelled, and with the current list.
|
||
- **The label list:** `labels.config` in the app's data folder (`%APPDATA%\mcap`), with **Open** to
|
||
edit it. Each line is a name and a prompt, the phrase the model compares with the image:
|
||
|
||
```
|
||
label: "Schematic" "an electronic circuit schematic diagram"
|
||
label: "Code editor" "a screenshot of a code editor showing source code"
|
||
```
|
||
|
||
A fuller description scores far better than a single word. Saved changes are picked up while the
|
||
app runs; the next scan relabels every capture from the image vector stored in its file, without
|
||
reading the images again, so changing the list is cheap.
|
||
- **Erase labels and vectors from the library** (for testing), after asking.
|
||
|
||
### The Segmentation window
|
||
|
||
**Segments** (above the grid) finds what is where in images: people, animals and things, each one
|
||
apart, and stuff such as sky, wall and grass (Mask2Former, `modules/Segment`, 133 COCO classes). It
|
||
works only in the library folders you choose, and never on videos.
|
||
|
||
- **Library folders:** a table of every folder in the library:
|
||
- **Segment:** whether scans read that folder's images. It starts off for every folder, so adding a
|
||
large archive never starts a long GPU job by itself.
|
||
- **Segmented:** how many of its images hold segments.
|
||
- **Erase:** takes that folder's segments out of its files, after asking.
|
||
|
||
The choice is kept in the library (`.library-folders`), not in the app's settings, so it goes
|
||
where the library goes.
|
||
- **Segment the folders set to segment** reads every image in them not yet segmented. Each is stored
|
||
as it is done, so a scan can stop and start again. It runs on the OCR thread, with the same
|
||
progress bar, Pause and Cancel. The model loads when the first scan starts and takes about 2.4 GB
|
||
of GPU memory; on an RTX 5080 laptop a scan does about 17 images a second.
|
||
- **Importing** into a folder set to segment segments the new images once they are in. The Import
|
||
window's "Segment the images in this folder" checkbox is the same setting.
|
||
- **Storage:** each image's segments are stored in its file: a short list (class, score, area, box)
|
||
for search, and a mask of about 5 KB for drawing. See `modules/Capture_File/README.md`.
|
||
|
||
### The Stats window
|
||
|
||
What OCR found in the captures shown (the whole library, or a search's matches), by the chosen OCR
|
||
model: captures with text, lines, words, characters, confidence, words per capture with a breakdown,
|
||
a table by year, the captures with the most words (click to open), each capture's best label
|
||
counted (click one to search for it), and the commonest words (click one to search for it). When any
|
||
of them are segmented, it also shows **What is in the images**: how many images hold each class
|
||
(person, sky, dog ...). Click one to search `has:` for it.
|
||
|
||
## Screen capture
|
||
|
||
On Windows, **PrintScreen** captures any part of the screen, from any app, even while mcap is hidden
|
||
in the tray:
|
||
|
||
1. **The screens freeze** the moment it is pressed: everything on every screen is copied and shown
|
||
darkened over them, so menus, tooltips and video hold still while you choose.
|
||
2. **Drag a rectangle** over what you want. It shows at full brightness, outlined. A magnifier beside
|
||
the cursor shows the pixels under it, with the cursor's position, or the selection's size while
|
||
dragging. The arrow keys move the cursor a pixel at a time, ten with Shift.
|
||
3. **Let go** to take it. A **click** without dragging, or **Enter**, takes the whole screen the
|
||
cursor is on. **Escape**, or a right click, cancels; a right click while dragging starts the
|
||
selection again.
|
||
|
||
What is taken is on the clipboard at once, ready to paste. It is also saved into the library, in
|
||
the folder **Screen Captures**, as a PNG in an `.mcap` with the window it was taken from (the window
|
||
under the middle of the selection): its title as the window title and its program as the app, so
|
||
searches find it as they find Snagit's. mcap then comes to the front showing it in the Capture
|
||
window, and OCR and labels read it once they are free (and segmentation, if the folder is set to
|
||
segment).
|
||
|
||
The key is the `capture_region` line of `[global_hotkeys]` in the settings file. Only one program can
|
||
have PrintScreen: if Snagit is running it has it, and so does Windows while Settings >
|
||
Accessibility > Keyboard > "Use the Print screen key to open screen capture" is on. When mcap cannot
|
||
register the key, the Event Log says so; choose another, such as `Ctrl-Shift-S capture_region`.
|
||
|
||
### Recording the screen
|
||
|
||
**Ctrl+PrintScreen** records a region of one screen as a video:
|
||
|
||
1. **The screens freeze**, as for a still, with the outline and magnifier in red.
|
||
2. **Drag a region**, or click (or press Enter) for the whole screen under the cursor:
|
||
- The drag stays on the screen it began on: a recording is of one screen.
|
||
- A region smaller than **240 × 180** is outlined dashed, with the minimum in the label, and letting
|
||
go of it starts nothing; drag again.
|
||
- Escape or a right click cancels.
|
||
3. **Recording starts** at once, at up to 30 frames a second, cursor included. A frame is written
|
||
only when the screen changes, so a still screen costs almost nothing. **Sound:** what the
|
||
computer plays is recorded with it, and the microphone once you turn it on. The toolbar's
|
||
**System sound** and **Mic** buttons turn each on and off, mid-recording too, and their state is
|
||
kept for the next recording.
|
||
- **A red border** is drawn just outside the region.
|
||
- **A toolbar** beside it shows the time, the size and any frames dropped, with **System sound**
|
||
and **Mic** (each on or off), **Pause** (the time paused is left out of the video), **Stop**, and **Discard**, which asks
|
||
first.
|
||
- **Neither is in the recording.** Neither takes the focus from the window being recorded.
|
||
4. **Ctrl+PrintScreen again**, or Stop, stops it. The video goes into **Screen Captures** beside the stills,
|
||
as an H.264 MP4 in an `.mcap` with the window it was recorded from, and opens in the Capture window.
|
||
OCR and labels read a frame from it.
|
||
|
||
- **The screen** is taken with DXGI Desktop Duplication, or with Windows.Graphics.Capture where that
|
||
cannot run (a laptop with two GPUs, a rotated screen).
|
||
- **Encoding** uses NVENC where there is an NVIDIA GPU, else AMD's or Intel's encoder, Media
|
||
Foundation, or libx264.
|
||
- **Losing nothing:**
|
||
- A recording that stops by itself keeps what it has, and so does quitting while recording.
|
||
- When the screen is lost for a moment (a UAC prompt, the lock screen, a resolution change), the
|
||
recording carries on once it is back.
|
||
- A recording a crash cut short is offered at the next start: into the library, or deleted. It
|
||
keeps all but its last two seconds or so.
|
||
- **Settings:** `[video]` in the settings file:
|
||
- `fps` (30) and `cursor` (true).
|
||
- `capture`: `auto` (Desktop Duplication, else Windows.Graphics.Capture), `dxgi` or `wgc`.
|
||
- `system_audio` (true): what the computer plays, from the default output device. The toolbar's
|
||
System sound button sets it.
|
||
- `microphone` (false): the default input device. The toolbar's Mic button sets it.
|
||
- Sound is AAC at 160 kb/s. A device unplugged mid-recording is picked up again from the new default.
|
||
- **PrintScreen while recording** takes the whole screen under the cursor at once, without freezing
|
||
the screens, which would show in the recording.
|
||
- **The key** is `record_region` in `[global_hotkeys]`.
|
||
|
||
## Importing
|
||
|
||
**Import** (above the grid) brings a folder of screenshots, photos and videos into the library:
|
||
|
||
1. **Import from:** the folder, typed or chosen with **Browse...**. Everything under it is looked
|
||
at, its folders too (not hidden ones, such as `.thumbnails`).
|
||
2. **Into library folder:** the folder in the library it goes into, such as `SnagitLibraryWork` or
|
||
`Phone`. It starts as the name of the folder imported from, or the library folder that folder went
|
||
into last time, so importing the same place again adds to the same folder.
|
||
3. **Import.** The progress, the time left, and Pause and Cancel show while it runs; new captures
|
||
appear in the grid as they are written. With **Read text and labels** checked, the OCR scan then
|
||
reads and labels what came in.
|
||
|
||
What becomes of each file:
|
||
|
||
- **Images** (PNG, JPEG, HEIC and AVIF, WebP, GIF, BMP, PSD, TGA, TIFF, and camera RAW such as DNG,
|
||
CR2, CR3, NEF and ARW): an `.mcap` holding the file exactly as it was, a preview turned the right
|
||
way up (by its EXIF orientation), and where it came from: its file name (searched by `title:`), the
|
||
camera or phone (`app:`), and the date it was taken, from its EXIF, else a date in its name
|
||
(`IMG_20240512_101530`, `Screenshot 2024-05-12 at 10.15.30`), else the file's time. It is named
|
||
`2024-05-12_10-15-30_1a2b3c4d.mcap`, the date and the start of its hash. HEIC, AVIF, WebP and RAW
|
||
are read by Windows' own codecs: the HEIF, AV1, WebP and Raw Image Extensions from the Microsoft
|
||
Store.
|
||
- **Animated GIFs** are images; their thumbnail is marked **GIF**, and the Capture window says how
|
||
long they run and opens them in the default viewer.
|
||
- **Videos** (MP4, MOV, M4V, 3GP, MKV, WebM, AVI, WMV, MPEG, MTS and M2TS, FLV), read with FFmpeg
|
||
(`modules/ffmpeg`, Windows only): an `.mcap` holding the file exactly as it was, of any size (it is
|
||
copied a few MB at a time, never read whole), and a frame from it a tenth of the way in (at most 3
|
||
seconds), turned the right way up, which is what the grid, the Capture window, OCR and labels see.
|
||
The date is the phone's (Apple's creation date), else one in its name, else the video's own
|
||
creation time, else the file's time; the phone and its location are kept too. The thumbnail shows a
|
||
play mark and how long it runs; **Play in the default player** in the Capture window writes the
|
||
video out to the temporary folder and opens it.
|
||
- **Snagit `.snagx`:** the same `.mcap` as `snagit-converter mcap` makes, byte for byte, named as
|
||
the `.snagx` is.
|
||
- **Duplicates:** an image already in the library, in any of its folders, is not stored again; the
|
||
Import window lists each with where it is. Importing a folder a second time imports only what is
|
||
new.
|
||
- **Skipped:** sidecar files (`.aae`, `.xmp`, `.json`), a GoPro's thumbnails and low-resolution
|
||
copies (`.thm`, `.lrv`), videos on macOS and Linux, and anything that is not an image or a video,
|
||
counted by kind.
|
||
- **Failed:** files that cannot be read or decoded, listed with why.
|
||
|
||
The files imported from are only read: nothing there is changed, moved or deleted.
|
||
|
||
## Searching
|
||
|
||
Type in the search box above the grid; the grid shows the matches as you type, with "N of M
|
||
captures" beside the box. Clear the box to see everything again.
|
||
|
||
### What a search matches
|
||
|
||
- **Every word must match**, in any order: `kicad footprint` finds captures with both.
|
||
- **Matching ignores case and finds parts of words:** `foot` matches "Footprints".
|
||
- **A plain word is looked for everywhere:** the OCR text (every model's), the window title, the
|
||
URL, the app's name, the annotations' text, and the labels.
|
||
- **"Quoted phrases"** match as one, spaces and all: `"symbol : footprint"`.
|
||
|
||
There is no OR and no NOT: to widen a search, take a word out.
|
||
|
||
### Prefixes
|
||
|
||
A prefix limits a word, or a quoted phrase, to one field: `title:kicad`, `title:"pcb editor"`.
|
||
|
||
| Prefix | Looks in |
|
||
| --- | --- |
|
||
| *(none)* | everything below except `like:`, `folder:` and `has:` |
|
||
| `ocr:` | the text read from the image |
|
||
| `title:` | the window title when it was captured; for an imported image, its file name |
|
||
| `url:` | the web page's address, for browser captures |
|
||
| `app:` | the app's name and version; for a photo, the camera or phone |
|
||
| `note:` | the text of the capture's annotations (Snagit text boxes and callouts) |
|
||
| `label:` | the capture's labels: its best label, and any scoring at least half as much |
|
||
| `like:` | the image's meaning (below) |
|
||
| `folder:` | the library folder it is in: `folder:work` |
|
||
| `has:` | what segmentation found in the image (below): `has:dog` |
|
||
|
||
A word with a colon that is not one of these stays an ordinary word, so `http://` and `C:\` work.
|
||
|
||
### What is in the image: `has:`
|
||
|
||
`has:dog` finds images that segmentation (the Segmentation window) found a dog in. Only images in
|
||
folders set to segment, and already segmented, take part.
|
||
|
||
- **The whole class name:** `has:table` is a table, not a dining table; `has:"dining table"` is that.
|
||
- **The 133 COCO classes:** 80 kinds of thing (person, bicycle, car, dog, cat, chair, laptop, cell
|
||
phone, book ...) and 53 of stuff (sky, wall, grass, road, floor wood, ...). The Stats window lists
|
||
those in the images shown.
|
||
- **Combining:** `has:person has:dog` finds images with both; `has:cat label:"photo"` mixes it with
|
||
labels.
|
||
|
||
### Searching by meaning: `like:`
|
||
|
||
`like:"a circuit board under a microscope"` ranks every labelled capture by how close its image is
|
||
to the phrase, best first: no words need to appear in the image. The label model compares the phrase
|
||
with the image vector each capture's file holds, so only labelled captures take part. The grid is
|
||
then in that order instead of by date.
|
||
|
||
- Describe what the image shows: `like:"a graph with several colored lines"` works better than
|
||
`like:graph`.
|
||
- Combine it with other words to filter the ranking: `like:"a dark themed code editor" title:visual`.
|
||
- The phrase is quoted when it has spaces: `like:"a terminal with an error"`.
|
||
|
||
### Examples
|
||
|
||
| Search | Finds |
|
||
| --- | --- |
|
||
| `digikey` | captures mentioning DigiKey anywhere: text, URL, title |
|
||
| `url:digikey` | DigiKey pages |
|
||
| `app:kicad` | captures of KiCad, by the app's name (more than `title:kicad`, as a window title need not say KiCad) |
|
||
| `ocr:"0402"` | images with "0402" in their text |
|
||
| `title:kicad footprint` | KiCad windows whose text, notes or labels mention a footprint |
|
||
| `label:schematic` | captures labelled Schematic |
|
||
| `label:"code review"` | captures labelled Code review |
|
||
| `note:todo` | captures with an annotation saying TODO |
|
||
| `like:"a photo of a soldering iron"` | captures that look like that, best first |
|
||
| `like:"a spreadsheet" app:excel` | Excel captures, the most spreadsheet-like first |
|
||
| `has:dog` | images with a dog in them (segmented folders) |
|
||
| `has:person has:"cell phone"` | images with a person and a phone |
|
||
|
||
## Command line
|
||
|
||
| Option | What it does |
|
||
| --- | --- |
|
||
| `-library FOLDER` | Shows the `.mcap` files in FOLDER (and its folders) instead of `D:\McapIndex\mcap`. |
|
||
| `-import SOURCE DEST` | Imports the folder SOURCE into the library folder DEST, as the Import window does, then exits: 0, or 1 if any file failed. |
|
||
| `-search QUERY` | Starts with QUERY in the search box. |
|
||
| `-cold` | Loads the library as on a cold start: its files and the catalog cache are dropped from the system's file cache first (Windows and Linux; not macOS, which allows it only with `sudo purge`). The `[startup]` lines in the log time each step. With `-stats`, a startup benchmark: `bin\mcap.exe -cold -stats`. |
|
||
| `-no-catalog-cache` | Reads every capture from its file, as without the catalog cache (for measuring); the cache is still written. |
|
||
| `-catalog-threads N` | Loads the catalog on N threads instead of all the background group's (for measuring). |
|
||
| `-scan` | Scans the library (the chosen OCR model, and labels when the label model is up) once it has loaded, then exits: 0, or 1 if any capture failed. |
|
||
| `-stats` | Logs the Stats window's numbers once the catalog has loaded, then exits; with `-search`, for the matches, and the first ten are logged. |
|
||
| `-record SECONDS` | Records the screen under the cursor that long (no overlay), puts it in the library, then exits: 0, or 1 if it failed. `-record-region X Y W H` records that region instead. `-record-quit` quits while still recording, to check the recording is kept. `-record-ui-visible` leaves the border and toolbar in screenshots, to look at them. |
|
||
| `-segment` | Segments the images not yet segmented in the library folders set to segment (see the Segmentation window), once the library has loaded, then exits: 0, or 1 if any image failed. |
|
||
| `-erase ocr`, `-erase labels`, `-erase segments`, `-erase all` | Erases that from every file in the library, then exits (for testing). |
|
||
| `-allow-multiple-instances` | Runs beside another copy instead of bringing that one forward. |
|
||
| `run_tests` | Runs the test suites (debug builds); with `-headless`, exits with 0 if all passed. |
|
||
| `-headless` | No message boxes: a failure is logged and the app exits with 1. |
|
||
| `-version` | Prints the version. |
|
||
|
||
`-import`, `-scan`, `-stats` and `-erase` still open the window, to show their progress; they are meant for
|
||
scripts and for trying things on a copy of the library: `bin\mcap.exe -library D:\copy -scan`.
|
||
|
||
## Where things are kept
|
||
|
||
| What | Where |
|
||
| --- | --- |
|
||
| OCR readings, labels, image vectors | inside each `.mcap` file (see `modules/Capture_File/README.md`) |
|
||
| The label list | `%APPDATA%\mcap\labels.config` |
|
||
| The segment colours chosen in the Capture window | `%APPDATA%\mcap\segment-colours.config` |
|
||
| Settings (theme, hotkeys, OCR model) | `%APPDATA%\mcap\settings.config` |
|
||
| Logs | `logs\<version>\` beside the program |
|
||
| The hash of every capture's image, for finding duplicates | `.import-index` in the library folder |
|
||
| The catalog cache: every capture's catalog entry, so a start reads one file instead of every capture (an entry is used while its file's size and modified time are unchanged; deleting it is safe) | `.catalog-cache` in the library folder |
|
||
| The folder each library folder was imported from | `.import-sources` in the library folder |
|
||
| Which library folders are segmented | `.library-folders` in the library folder |
|
||
|
||
Nothing is written to the folders imported from (`D:\Snagit`, a phone's backup): the library is a
|
||
converted copy.
|
||
|
||
## How it works
|
||
|
||
- `src/app/viewer.jai`: the grid and the Capture window. Previews and full images are decoded on
|
||
worker threads and uploaded a few per frame; textures far from view are released past 1200.
|
||
Each capture keeps its index for the app's life (Refresh and Delete mark captures removed and
|
||
append new ones), as the workers, the scan and Stats refer to captures by index.
|
||
- `src/app/catalog.jai`: what search needs from every file, read from the front of each `.mcap`
|
||
(its text, OCR, labels and vector, not its images) on the workers at startup; the query parser
|
||
and matching.
|
||
- `src/app/background.jai`: searching and the Stats numbers run on a pool of threads, so typing
|
||
never waits; the grid keeps the last results until the new ones are back, usually within a frame.
|
||
`like:` phrases are turned into vectors there first.
|
||
- `src/app/ocr_ui.jai` and `labels_ui.jai`: the engines, made on one background thread (OCR first,
|
||
then labels); the scan, which decodes each image once and stores its OCR, labels and vector in one
|
||
rewrite of its file; the erase; the windows.
|
||
- `modules/OCR`, `modules/Labels`: the models, run with onnxruntime (`modules/onnxruntime`, which
|
||
also holds the shared session and CUDA code); each has a README.
|
||
- `src/app/import.jai`: the Import window and `-import`. A thread lists the files, brings the
|
||
library index up to date (hashing the image in any `.mcap` it has not seen), then imports on
|
||
several threads; captures written are handed to the grid each frame.
|
||
- `modules/Image_Decode`: any image file's bytes to pixels, the right way up: stb_image for PNG,
|
||
JPEG, GIF, BMP, PSD and TGA, Windows Imaging Component for the rest; EXIF (from JPEG, PNG, WebP,
|
||
HEIF and TIFF); previews; and videos' frames and what their containers say, with FFmpeg
|
||
(`video.jai`), and how many frames a GIF has. `bin\image-probe FILE...` (`jai first.jai -
|
||
image_probe`) shows what it makes of files.
|
||
- `modules/Screen_Record`: a region of one screen recorded to H.264 in an MP4, with DXGI Desktop
|
||
Duplication, or Windows.Graphics.Capture where duplication cannot run (two GPUs, a rotated screen),
|
||
and FFmpeg (NVENC, else AMF, QSV, Media Foundation or libx264), for screen recording
|
||
(`video-capture-plan.html`). `bin\record-probe` (`jai first.jai - record_probe`) records with it
|
||
and says how it went; `-list` shows the screens, `-capture auto|dxgi|wgc` picks the way, `-check
|
||
FILE` reads a recording back.
|
||
- `modules/Snagx`: `.snagx` to `.mcap`, copied from SnagitConverter.
|
||
- `modules/Capture_File`: reading and writing `.mcap` files, with a video's original left on disk.
|
||
- `modules/Segment`: panoptic segmentation with Mask2Former, run with onnxruntime (see its README and
|
||
`segmentation-plan.html`). `src/app/segment_ui.jai` is the scan and the Segmentation window;
|
||
`src/app/library_folders.jai` the per-folder options.
|
||
- `modules/ffmpeg`: FFmpeg 6.0's bindings and DLLs (`modules/ffmpeg/windows`, in git; copied beside
|
||
the executable by the build), from the gyan.dev shared build, for reading videos.
|
||
|
||
# The application template
|
||
|
||
mcap is built on a bare Dear ImGui (docking branch) application template, for Windows, macOS and
|
||
Linux: the window, the frame loop and everything around them, with the app written into
|
||
`src/app/app_ui.jai`.
|
||
|
||
## What you get
|
||
|
||
- **Native rendering per platform.** Win32 + Direct3D 11 on Windows; SDL3 + SDL_GPU (Metal on
|
||
macOS, Vulkan on Linux) elsewhere. On Windows the window comes up on a WARP device in a few
|
||
milliseconds while the hardware device is created on a thread, then moves across; a lost
|
||
device is recovered.
|
||
- **Docking and multi-viewport ImGui**, with a default layout (`assets/data/default_imgui.ini`)
|
||
and a Reset Layout hotkey.
|
||
- **One instance.** Launching the app again brings the running one forward, over a shared
|
||
memory channel (a file lock decides the owner on macOS and Linux).
|
||
- **Tray / menu bar.** Closing or minimizing hides the window to the tray; the tray icon brings
|
||
it back or quits.
|
||
- **Hotkeys** from a `[hotkeys]` section of the settings file, and **global hotkeys**
|
||
(`[global_hotkeys]`: Windows and macOS) that work while another app is in front.
|
||
- **A settings file** (`settings.config`) that is written with every key listed, reloaded as
|
||
soon as it is saved, and merged rather than overwritten when the app saves to it.
|
||
- **Themes**: built-ins, 36 shipped `.theme` files, and a theme editor that saves new ones.
|
||
- **Logging** through a multi-producer, single-consumer queue to a per-version log file and an
|
||
in-app Event Log, plus crash reports.
|
||
- **Debug panels**: frame times, memory, renderer, fonts (Ctrl+Shift+D, Ctrl+Shift+1).
|
||
- **DPI awareness**, live resize that keeps drawing, CJK font fallback from the system fonts.
|
||
- **Background shutdown**: closing hands the remaining work to a thread while the window shows
|
||
progress, so it never looks hung.
|
||
- **Asynchronous tests**, compiled into debug builds and run with `run_tests`.
|
||
|
||
## Building
|
||
|
||
Requires the Jai compiler, beta 0.2.030 or later.
|
||
|
||
```
|
||
jai first.jai # debug build
|
||
jai first.jai - release # release build
|
||
jai first.jai - full_release # debug and release
|
||
jai first.jai - release compile_tests # release build with the test suite
|
||
```
|
||
|
||
Binaries are written to `bin/`, with `assets/` copied beside them.
|
||
|
||
| Platform | Output | Notes |
|
||
| --- | --- | --- |
|
||
| Windows | `bin/mcap-debug.exe`, `bin/mcap.exe` | The release build gets the icon and a manifest, and no console. |
|
||
| macOS | `bin/mcap-macos-debug`, `bin/mcap.app` | Apple silicon (arm64) only, with a native `jai`. The release build is an ad hoc signed `.app`. |
|
||
| Linux | `bin/mcap-linux-debug`, `bin/mcap-linux` | Needs SDL3 installed (`libSDL3.so`). |
|
||
|
||
`jai check_targets.jai - linux` type-checks the Linux build from any OS without linking it.
|
||
The macOS check only works on a Mac, since the Objective-C module runs code at compile time.
|
||
|
||
Run any build with `run_tests` to run the test suites (debug builds include them),
|
||
`-allow-multiple-instances` to skip the single-instance check, or `-version`.
|
||
|
||
For unattended runs (scripts, CI, agents), add `-headless`: the app exits once the tests finish,
|
||
with exit code 0 if every suite passed and 1 otherwise, and a failed assertion or runtime check
|
||
is written to stderr, the log and the crash report and exits with 1 instead of opening a message
|
||
box (`runtime_support_headless` in `modules/Runtime_Support.jai`):
|
||
|
||
```
|
||
bin/mcap-debug.exe run_tests -headless -allow-multiple-instances > test-output.txt
|
||
```
|
||
|
||
`jai first.jai - no_crash_dialog [...]` makes builds for automation: `NO_CRASH_DIALOG` makes them
|
||
headless whatever their arguments, and everything they build goes to `bin-headless/` instead of
|
||
`bin/`, so an automated build never writes over the one you run by hand.
|
||
|
||
## Starting a new app
|
||
|
||
```
|
||
jai fork.jai - <output_directory> <app_name> [-folder <name>] [-bundle-id <id>] [-version <x.y.z>] [-git] [-build]
|
||
jai fork.jai - ../my-cool-app "My Cool App" -git -build
|
||
```
|
||
|
||
This copies the template (without `bin/`, `.build/`, `.git/` or `fork.jai`) and sets the names
|
||
below in the copy. The folder name defaults to the app name in lowercase with spaces as `-`
|
||
(`my-cool-app`), the bundle identifier to `com.example.<folder without dashes>`, and the version
|
||
to `0.0.0`. `-git` initializes a repository and commits the copy; `-build` makes a debug build. It also
|
||
writes `<app_name>.focus-config`, a [focus](https://focus-editor.dev) project with the build and
|
||
run commands, which stays out of git since it holds this machine's paths.
|
||
|
||
## Making it your app
|
||
|
||
1. Name it at the top of `first.jai`, or let `fork.jai` do it: `APP_NAME` (window title, tray,
|
||
macOS bundle), `APP_FOLDER_NAME` (executable and data folder; one short lowercase word),
|
||
`APP_BUNDLE_IDENTIFIER` and `VERSION`.
|
||
2. Replace `assets/images/icon.png` (any square size; it becomes the `.exe`, `.app` and window
|
||
icon) and the tray icons `tray_icon.ico` (Windows) and `tray_icon.png`.
|
||
3. Write the app in `src/app/app_ui.jai`: `app_ui_startup` runs once, `app_ui_frame_start` every
|
||
frame before input, `app_main_window_ui` draws, and `app_ui_shutdown` runs on the shutdown
|
||
thread.
|
||
|
||
To add a hotkey, declare a `Hotkey` in `src/ui/imgui_keyboard.jai`, add it to
|
||
`hotkey_actions_build` and `app_check_key_combinations`, bind it in both
|
||
`assets/data/default_hotkeys*.config` files (the build fails if a default leaves an action
|
||
unbound), and raise `APP_USER_SETTINGS_VERSION` in `src/App.jai` so existing settings files get
|
||
the new default.
|
||
|
||
To add a setting, add it to `App_User_Settings`, `Settings_Seen`, `settings_parse` and
|
||
`app_save_user_settings_file` in `src/ui/imgui_theme_registry.jai`.
|
||
|
||
## Layout
|
||
|
||
```
|
||
first.jai build: workspaces, build constants, Windows icon/manifest, macOS .app
|
||
fork.jai copy the template to a new, named project
|
||
check_targets.jai type-check another OS's build
|
||
src/App.jai loads everything; global state (app, platform, perf)
|
||
src/app/ main loop, your UI, background shutdown
|
||
src/base/ logging, fonts, debug panels, platform layers (base/platform/)
|
||
src/ui/ ImGui setup, hotkeys, themes and settings, toasts
|
||
src/graphics/DX11.jai Direct3D 11 device, WARP-first startup, device-loss recovery
|
||
src/backends/ ImGui's SDL_GPU backend; src/freetype/: ImGui's FreeType loader
|
||
assets/ fonts, icons, default hotkeys, default layout, themes
|
||
modules/ Basic (with arenas), Thread, Shared_Memory_Channel, File_Watcher,
|
||
Secure_Memory, XDG, sdl3, imgui-sdl, imgui-dx11-win32, imgui-source
|
||
```
|
||
|
||
## Files at run time
|
||
|
||
| File | Location |
|
||
| --- | --- |
|
||
| `settings.config`, `imgui.ini`, user themes | `%APPDATA%\<APP_FOLDER_NAME>` on Windows, `~/Library/Application Support/<APP_FOLDER_NAME>` on macOS, `~/.local/share/<APP_FOLDER_NAME>` on Linux |
|
||
| Logs | `logs/<version>/` next to the executable (next to the `.app` on macOS) |
|
||
| Shipped themes | `data/themes/` next to the executable |
|
||
|
||
## Platform notes
|
||
|
||
- Global hotkeys are not implemented on Linux (Wayland has no API for them). A desktop
|
||
shortcut that launches the executable has the same effect, since a second launch brings the
|
||
running instance forward.
|
||
- On XFCE the tray icon stays after the window is restored.
|
||
- `Secure_Memory` (used by `Basic`'s arenas to wipe memory) is built without libsodium; see the
|
||
note at the top of `modules/Secure_Memory/module.jai` if your app keeps secrets in memory.
|
||
|
||
Uses https://github.com/overlord-systems/jai-imgui as a template.
|