Compare commits
2 Commits
22cad98500
...
2bd77e8121
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bd77e8121 | |||
| 03f23f7122 |
18
README.md
18
README.md
@ -58,6 +58,11 @@ see [Screen capture](#screen-capture).
|
|||||||
move the file to the Recycle Bin or to delete it permanently, overwriting it with random bytes
|
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
|
first; "Don't ask again" keeps the choice until the app restarts. Only the `.mcap` goes: the
|
||||||
`.snagx` it was converted from is untouched.
|
`.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
|
- **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.
|
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.
|
**Refresh** looks at the whole folder again, should the watcher have missed something.
|
||||||
@ -93,7 +98,18 @@ grid (in its order, so through a search's matches).
|
|||||||
### The OCR window
|
### The OCR window
|
||||||
|
|
||||||
- **Model:** PP-OCRv5 mobile (light, about 85 ms a capture on the GPU) or server (more accurate,
|
- **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]`).
|
about 0.3 s when it runs well; see below). The choice is kept in the settings file (`model` under
|
||||||
|
`[ocr]`).
|
||||||
|
- **Known issue, the server model on the GPU:** its speed is bimodal. The same program on the same
|
||||||
|
20 captures (RTX 5080 laptop, 16 GB) runs either at about 0.27 s a capture with a peak of about
|
||||||
|
2.4 GB of process memory, or at about 1.5 s with about 6.6 GB, with no change between runs and
|
||||||
|
15 GB of the GPU's memory free beforehand. Longer runs fare worse: 50 captures peaked at 16 GB, and
|
||||||
|
100 averaged about 4.6 s a capture. The extra memory looks like GPU allocations spilling into shared
|
||||||
|
system memory, which Windows then pages: what the GPU memory defaults (onnxruntime's
|
||||||
|
kNextPowerOfTwo arena, no cuDNN max workspace, 4 workers) were meant to stop, and which they
|
||||||
|
only partly did. It is the same with Jai's allocator and with rpmalloc. Until it is fixed, scan a
|
||||||
|
large library with the mobile model. Measured on 2026-10-13 with
|
||||||
|
`ocr-bench -models ppocr-v5-server -gpu-only -limit 20`.
|
||||||
- **Running on:** the GPU or the CPU, and if the CPU, what to install for the GPU.
|
- **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**,
|
- **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
|
labels every capture not yet labelled with the current list. While a search is active the button
|
||||||
|
|||||||
@ -18,6 +18,10 @@ Ctrl-Equal increase_font_size
|
|||||||
Ctrl-Alt-Space reset_window
|
Ctrl-Alt-Space reset_window
|
||||||
Ctrl-Shift-Alt-R reset_layout
|
Ctrl-Shift-Alt-R reset_layout
|
||||||
|
|
||||||
|
# Library: the selected capture
|
||||||
|
Ctrl-C copy_image
|
||||||
|
Ctrl-Shift-C copy_path
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
Ctrl-Shift-D toggle_debug_panel
|
Ctrl-Shift-D toggle_debug_panel
|
||||||
Ctrl-Shift-L toggle_logger_panel
|
Ctrl-Shift-L toggle_logger_panel
|
||||||
|
|||||||
@ -18,6 +18,10 @@ Cmd-Equal increase_font_size
|
|||||||
Cmd-Opt-Shift-Space reset_window
|
Cmd-Opt-Shift-Space reset_window
|
||||||
Cmd-Shift-Opt-R reset_layout
|
Cmd-Shift-Opt-R reset_layout
|
||||||
|
|
||||||
|
# Library: the selected capture
|
||||||
|
Cmd-C copy_image
|
||||||
|
Cmd-Shift-C copy_path
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
Cmd-Shift-D toggle_debug_panel
|
Cmd-Shift-D toggle_debug_panel
|
||||||
Cmd-Shift-L toggle_logger_panel
|
Cmd-Shift-L toggle_logger_panel
|
||||||
|
|||||||
@ -47,7 +47,7 @@ APP_NAME :: "mcap";
|
|||||||
APP_FOLDER_NAME :: "mcap";
|
APP_FOLDER_NAME :: "mcap";
|
||||||
GIT_BRANCH :: "check";
|
GIT_BRANCH :: "check";
|
||||||
GIT_HASH :: "-";
|
GIT_HASH :: "-";
|
||||||
VERSION :: "0.1.15";
|
VERSION :: "0.1.16";
|
||||||
BUILD_DATE :: "-";
|
BUILD_DATE :: "-";
|
||||||
DEBUG :: true;
|
DEBUG :: true;
|
||||||
COMPILE_TESTS :: true;
|
COMPILE_TESTS :: true;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ APP_NAME :: "mcap";
|
|||||||
APP_FOLDER_NAME :: "mcap";
|
APP_FOLDER_NAME :: "mcap";
|
||||||
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
|
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
|
||||||
|
|
||||||
VERSION :: "0.1.15";
|
VERSION :: "0.1.16";
|
||||||
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
|
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
|
||||||
// builds never write over the ones you run by hand.
|
// builds never write over the ones you run by hand.
|
||||||
build_directory := "bin";
|
build_directory := "bin";
|
||||||
|
|||||||
@ -156,6 +156,14 @@ viewer: struct {
|
|||||||
open_name: string;
|
open_name: string;
|
||||||
open_result: string; // the file written, or ""; heap, the open thread's until it ends
|
open_result: string; // the file written, or ""; heap, the open thread's until it ends
|
||||||
open_error: string;
|
open_error: string;
|
||||||
|
|
||||||
|
// The selected capture's image, decoded off the main thread for the clipboard (viewer_copy_image).
|
||||||
|
copy_thread: Thread;
|
||||||
|
copy_started: bool;
|
||||||
|
copy_busy: bool; // set by the main thread, cleared by the copy thread when it ends
|
||||||
|
copy_source: string; // the .mcap; heap, main thread's until the thread starts
|
||||||
|
copy_name: string;
|
||||||
|
copy_done: Decoded; // the copy thread's until it ends
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- startup and shutdown
|
// ---------------------------------------------------------------- startup and shutdown
|
||||||
@ -229,6 +237,7 @@ viewer_shutdown :: () {
|
|||||||
for 1..viewer.workers_started signal(*viewer.wake);
|
for 1..viewer.workers_started signal(*viewer.wake);
|
||||||
for 0..viewer.workers_started - 1 thread_is_done(*viewer.workers[it], -1);
|
for 0..viewer.workers_started - 1 thread_is_done(*viewer.workers[it], -1);
|
||||||
if viewer.open_started thread_is_done(*viewer.open_thread, -1);
|
if viewer.open_started thread_is_done(*viewer.open_thread, -1);
|
||||||
|
if viewer.copy_started thread_is_done(*viewer.copy_thread, -1);
|
||||||
background_shutdown();
|
background_shutdown();
|
||||||
catalog_cache_save_at_shutdown(); // the workers have stopped: nothing changes the entries now
|
catalog_cache_save_at_shutdown(); // the workers have stopped: nothing changes the entries now
|
||||||
viewer_watch_stop();
|
viewer_watch_stop();
|
||||||
@ -451,6 +460,7 @@ viewer_frame_gpu_work :: () {
|
|||||||
viewer_apply_catalog_updates();
|
viewer_apply_catalog_updates();
|
||||||
catalog_cache_frame(); // saves the catalog cache once it is complete and has changed (app/catalog.jai)
|
catalog_cache_frame(); // saves the catalog cache once it is complete and has changed (app/catalog.jai)
|
||||||
viewer_open_frame();
|
viewer_open_frame();
|
||||||
|
viewer_copy_frame();
|
||||||
segment_overlay_frame(); // the Capture window's segments: what arrived, and its texture (app/segment_ui.jai)
|
segment_overlay_frame(); // the Capture window's segments: what arrived, and its texture (app/segment_ui.jai)
|
||||||
background_frame(); // takes back search results, and starts a search when visible_dirty (app/background.jai)
|
background_frame(); // takes back search results, and starts a search when visible_dirty (app/background.jai)
|
||||||
}
|
}
|
||||||
@ -1215,6 +1225,77 @@ viewer_open_frame :: () {
|
|||||||
else toast_show(.Error, "Could not write % out: %", viewer.open_name, viewer.open_error);
|
else toast_show(.Error, "Could not write % out: %", viewer.open_name, viewer.open_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- copying the selected capture
|
||||||
|
|
||||||
|
// The selected capture: the one outlined in the grid and shown in the Capture window. -1 when none.
|
||||||
|
viewer_selected :: () -> s32 {
|
||||||
|
index := viewer.full_index;
|
||||||
|
if index < 0 || index >= viewer.captures.count || viewer.captures[index].removed return -1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl-Shift-C (copy_path in [hotkeys]): the selected capture's path, as the system writes paths.
|
||||||
|
viewer_copy_path :: () {
|
||||||
|
index := viewer_selected();
|
||||||
|
if index < 0 { toast_show(.Error, "Select a capture to copy its path"); return; }
|
||||||
|
capture := *viewer.captures[index];
|
||||||
|
path := capture.path;
|
||||||
|
#if OS == .WINDOWS path = replace(path, "/", "\\",, temp);
|
||||||
|
clipboard_set_text(path);
|
||||||
|
toast_show(.Success, "Copied the path of %", capture.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl-C (copy_image in [hotkeys]): the selected capture's image, as the Capture window shows it
|
||||||
|
// (annotations and all; a video's frame), decoded on a thread of its own and put on the clipboard
|
||||||
|
// by viewer_copy_frame.
|
||||||
|
viewer_copy_image :: () {
|
||||||
|
index := viewer_selected();
|
||||||
|
if index < 0 { toast_show(.Error, "Select a capture to copy its image"); return; }
|
||||||
|
capture := *viewer.captures[index];
|
||||||
|
#if OS != .WINDOWS {
|
||||||
|
toast_show(.Error, "Copying images is not available on this system yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if viewer.copy_started { toast_show(.Error, "Still copying %", viewer.copy_name); return; }
|
||||||
|
push_allocator(context.default_allocator);
|
||||||
|
free(viewer.copy_source); free(viewer.copy_name);
|
||||||
|
viewer.copy_source = copy_string(capture.path);
|
||||||
|
viewer.copy_name = copy_string(capture.name);
|
||||||
|
viewer.copy_done = .{};
|
||||||
|
viewer.copy_busy = true;
|
||||||
|
// Reading an image takes a few MB of temporary storage, as on the viewer's workers.
|
||||||
|
viewer.copy_started = thread_init(*viewer.copy_thread, viewer_copy_proc, temporary_storage_size = 1024 * 1024, name = "copy image");
|
||||||
|
if !viewer.copy_started { viewer.copy_busy = false; toast_show(.Error, "Could not copy %", capture.name); return; }
|
||||||
|
thread_start(*viewer.copy_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewer_copy_proc :: (thread: *Thread) -> s64 {
|
||||||
|
context.allocator = context.default_allocator;
|
||||||
|
defer atomic_swap(*viewer.copy_busy, false);
|
||||||
|
viewer.copy_done = viewer_decode_full(viewer.copy_source);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each frame: puts what viewer_copy_proc decoded on the clipboard, once it is done.
|
||||||
|
viewer_copy_frame :: () {
|
||||||
|
if !viewer.copy_started || viewer.copy_busy return;
|
||||||
|
thread_is_done(*viewer.copy_thread, -1);
|
||||||
|
thread_deinit(*viewer.copy_thread);
|
||||||
|
viewer.copy_started = false;
|
||||||
|
done := *viewer.copy_done;
|
||||||
|
defer viewer_free_pixels(done);
|
||||||
|
if done.failed { toast_show(.Error, "Could not read the image of %; see the Event Log", viewer.copy_name); return; }
|
||||||
|
pixels := []u8.{cast(s64) done.width * done.height * 4, done.pixels};
|
||||||
|
if !platform_copy_image_to_clipboard(pixels, done.width, done.height) {
|
||||||
|
log_error("[viewer] Could not put the image of % on the clipboard.", viewer.copy_source);
|
||||||
|
toast_show(.Error, "Could not put % on the clipboard", viewer.copy_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scaled := ifx done.width != done.source_width || done.height != done.source_height then tprint(", scaled down from % x %", done.source_width, done.source_height) else "";
|
||||||
|
log("[viewer] Copied the image of % (% x %%).", viewer.copy_source, done.width, done.height, scaled);
|
||||||
|
toast_show(.Success, "Copied % (% x %%)", viewer.copy_name, done.width, done.height, scaled);
|
||||||
|
}
|
||||||
|
|
||||||
viewer_grid :: () { using ImGui;
|
viewer_grid :: () { using ImGui;
|
||||||
style := GetStyle();
|
style := GetStyle();
|
||||||
cell := viewer.thumb_size;
|
cell := viewer.thumb_size;
|
||||||
|
|||||||
@ -148,8 +148,9 @@ to_string_all_bindings :: (hotkey: Hotkey) -> string {
|
|||||||
// The bindings are read from settings.config, or from DEFAULT_HOTKEYS_DATA when the file has
|
// The bindings are read from settings.config, or from DEFAULT_HOTKEYS_DATA when the file has
|
||||||
// no [hotkeys] section; see hotkeys_install. once and ignore_when_text_field_active belong to the
|
// no [hotkeys] section; see hotkeys_install. once and ignore_when_text_field_active belong to the
|
||||||
// action and are set here. To add an action: declare its Hotkey here, add it to
|
// action and are set here. To add an action: declare its Hotkey here, add it to
|
||||||
// hotkey_actions_build and app_check_key_combinations, bind it in both assets/data/default_hotkeys
|
// hotkey_actions_build and app_check_key_combinations, and bind it in both
|
||||||
// files, and raise APP_USER_SETTINGS_VERSION so existing settings files get its default.
|
// assets/data/default_hotkeys files. Settings files at APP_USER_SETTINGS_VERSION get its default
|
||||||
|
// binding at the next start (hotkeys_missing_lines); raising the version would give them "none".
|
||||||
program_exit_hotkey: Hotkey;
|
program_exit_hotkey: Hotkey;
|
||||||
minimize_to_tray_hotkey: Hotkey;
|
minimize_to_tray_hotkey: Hotkey;
|
||||||
toggle_debug_panel_hotkey: Hotkey;
|
toggle_debug_panel_hotkey: Hotkey;
|
||||||
@ -162,6 +163,10 @@ increase_font_size_hotkey: Hotkey;
|
|||||||
reset_window_hotkey: Hotkey;
|
reset_window_hotkey: Hotkey;
|
||||||
reset_layout_hotkey: Hotkey;
|
reset_layout_hotkey: Hotkey;
|
||||||
|
|
||||||
|
// The selected capture (app/viewer.jai). Not while typing, so Ctrl-C in the search box copies its text.
|
||||||
|
copy_image_hotkey: Hotkey = .{ignore_when_text_field_active = true};
|
||||||
|
copy_path_hotkey: Hotkey = .{ignore_when_text_field_active = true};
|
||||||
|
|
||||||
Hotkey_Action :: struct {
|
Hotkey_Action :: struct {
|
||||||
name: string; // as written in [hotkeys]
|
name: string; // as written in [hotkeys]
|
||||||
hotkey: *Hotkey;
|
hotkey: *Hotkey;
|
||||||
@ -182,6 +187,8 @@ hotkey_actions_build :: (out: *[..] Hotkey_Action) {
|
|||||||
add(out, "increase_font_size", *increase_font_size_hotkey);
|
add(out, "increase_font_size", *increase_font_size_hotkey);
|
||||||
add(out, "reset_window", *reset_window_hotkey);
|
add(out, "reset_window", *reset_window_hotkey);
|
||||||
add(out, "reset_layout", *reset_layout_hotkey);
|
add(out, "reset_layout", *reset_layout_hotkey);
|
||||||
|
add(out, "copy_image", *copy_image_hotkey);
|
||||||
|
add(out, "copy_path", *copy_path_hotkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkey_actions: [..] Hotkey_Action;
|
hotkey_actions: [..] Hotkey_Action;
|
||||||
@ -627,6 +634,9 @@ app_check_key_combinations :: () {
|
|||||||
else toast_show(.Error, "Could not reset the window");
|
else toast_show(.Error, "Could not reset the window");
|
||||||
}
|
}
|
||||||
if check_key_combination(*reset_layout_hotkey) imgui_reset_layout_requested = true; // applied between frames
|
if check_key_combination(*reset_layout_hotkey) imgui_reset_layout_requested = true; // applied between frames
|
||||||
|
// The viewer is the shutdown thread's once shutdown has started.
|
||||||
|
if check_key_combination(*copy_image_hotkey) && !app_exit_shutdown_started() viewer_copy_image();
|
||||||
|
if check_key_combination(*copy_path_hotkey) && !app_exit_shutdown_started() viewer_copy_path();
|
||||||
|
|
||||||
if should_exit_this_frame {
|
if should_exit_this_frame {
|
||||||
log("Calling exit hotkey... exiting program!");
|
log("Calling exit hotkey... exiting program!");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user