// Makes a release of mcap for Windows x64: one folder with everything it needs to run (the program, // onnxruntime, FFmpeg, the OCR, label and segmentation models, and NVIDIA's CUDA 13 runtime and // cuDNN 9, so the GPU works with nothing but the NVIDIA driver installed), zipped: // // jai release.jai release/mcap-VERSION-windows-x64/ and release/mcap-VERSION-windows-x64.zip // jai release.jai - no_cuda without the CUDA runtime (1.3 GB less): the GPU then needs CUDA 13 and cuDNN 9 installed // jai release.jai - no_build package bin/ as it is, without building it again first // // VERSION comes from first.jai. A tree with changes, or a HEAD that is not tagged vVERSION, is warned // about, not refused. The models must be in bin/ already: OCR's and the labels' from their // fetch_models.jai, segmentation's from tools/export_mask2former.py (see each module's README). #run,stallable { set_build_options_dc(.{do_output = false}); args := get_build_options().compile_time_command_line; with_cuda := !array_find(args, "no_cuda"); build := !array_find(args, "no_build"); set_working_directory(#filepath); version := read_version(); if !version fail("Cannot find VERSION in first.jai."); name := tprint("mcap-%-windows-x64", version); log("Releasing % (%).", name, ifx with_cuda then "with the CUDA runtime" else "without the CUDA runtime"); // Where the source is: warned about, not refused. status, status_ok := run("git", "status", "--porcelain"); if status_ok && trim(status) log("Warning: the working tree has changes; the release is built from them."); tag, tag_ok := run("git", "describe", "--exact-match", "--tags", "HEAD"); if !tag_ok || trim(tag) != tprint("v%", version) log("Warning: HEAD is not tagged v%.", version); if build { log("Building the release build..."); _, built := run("jai", "first.jai", "-", "release", print = true); if !built fail("The build failed."); } // The folder, from nothing. out := tprint("release/%", name); if file_exists(out) && (!delete_directory(out) || file_exists(out)) fail(tprint("Cannot delete the last % (is a program or a shell in it?).", out)); make_directory_if_it_does_not_exist(out, recursive = true); // The program and its libraries. copy("bin/mcap.exe", out); copy("bin/mcap.pdb", out); // symbols, for crash reports' stack traces for file_list("bin") { if ends_with_nocase(it, ".dll") copy(it, out); } for string.["data", "fonts", "images"] copy_folder(tprint("bin/%", it), tprint("%/%", out, it)); // The models: each folder as the build copied it, checked for the files its models.config names. for string.["ocr-models", "label-models", "segment-models"] { check_models(tprint("bin/%", it)); copy_folder(tprint("bin/%", it), tprint("%/%", out, it)); } // NVIDIA's runtime, beside the program, where Windows looks for a DLL first. if with_cuda { missing: [..] string; for CUDA_FILES { path := find_cuda_file(it); if !path { array_add(*missing, it); continue; } copy(path, out); } // All of cuDNN, not a list of it: it loads sub-libraries by name as it needs them (9.26 added // cudnn_engines_tensor_ir64_9.dll), and one missing fails every run on the GPU. cudnn := find_cuda_file("cudnn64_9.dll"); if !cudnn array_add(*missing, "cudnn64_9.dll"); else for file_list(path_strip_filename(cudnn)) { name := path_filename(it); if begins_with_nocase(name, "cudnn") && ends_with_nocase(name, ".dll") copy(it, out); } if missing fail(tprint("Cannot find % (on PATH, or under Program Files\\NVIDIA). Install the CUDA 13 toolkit and cuDNN 9 for CUDA 13, or release with `- no_cuda`.", join(..missing, ", "))); } // print has no escape for %, which the README uses: the templates' blanks are filled by name. readme := replace(RELEASE_README, "{VERSION}", version); readme = replace(readme, "{GPU}", ifx with_cuda then CUDA_INCLUDED else CUDA_NOT_INCLUDED); write_entire_file(tprint("%/README.txt", out), readme); write_entire_file(tprint("%/THIRD-PARTY-NOTICES.txt", out), replace(NOTICES, "{CUDA}", ifx with_cuda then CUDA_NOTICE else "")); if !copy_file("modules/ffmpeg/windows/LICENSE-BUILD.txt", tprint("%/FFMPEG-BUILD.txt", out)) fail("Cannot copy FFmpeg's build notes."); // The zip, with Windows' own tar (bsdtar), which writes zips (and Zip64, past 4 GB). zip := tprint("release/%.zip", name); if file_exists(zip) file_delete(zip); log("Zipping % ...", zip); _, zipped := run("C:/Windows/System32/tar.exe", "-a", "-c", "-f", zip, "-C", "release", name); if !zipped fail("Zipping failed."); folder_bytes := folder_size(out); zip_bytes := file_size(zip); hash_text, hashed := run("certutil", "-hashfile", zip, "SHA256"); sha := ""; if hashed { lines := split(hash_text, "\n"); if lines.count >= 2 sha = trim(lines[1]); } log("Done: % (% MB), from % (% MB).", zip, zip_bytes / (1024 * 1024), out, folder_bytes / (1024 * 1024)); if sha log("SHA-256 %", sha); } // The CUDA toolkit's files the CUDA provider loads (modules/onnxruntime/cuda.jai: CUDA_LIBRARIES, but // the driver and cuDNN, which is copied whole), and NVRTC, which cuDNN's runtime-compiled engines load. CUDA_FILES :: string.[ "cudart64_13.dll", "cublas64_13.dll", "cublasLt64_13.dll", "cufft64_12.dll", "nvrtc64_130_0.dll", "nvrtc-builtins64_134.dll", ]; // A file of CUDA's or cuDNN's: on PATH (the toolkit's installer puts it there), else the newest cuDNN // for CUDA 13 under Program Files\NVIDIA\CUDNN (its installer does not). "" when not found. find_cuda_file :: (name: string) -> string { for split(path_variable(), ";") { if !it continue; path := tprint("%/%", trim_right(it, "\\/"), name); if file_exists(path) return path; } // cuDNN: Program Files/NVIDIA/CUDNN/v9.x/bin/13.y/x64/NAME, the newest (it sorts last). best := ""; cudnn_root := "C:/Program Files/NVIDIA/CUDNN"; if file_exists(cudnn_root) { for file_list(cudnn_root, recursive = true) { normal := replace(it, "\\", "/"); if path_filename(normal) != name || !contains(normal, "/bin/13.") continue; if compare(normal, best) > 0 best = normal; } } return best; } // Every file a models folder's models.config names in quotes is there. check_models :: (folder: string) { config, ok := read_entire_file(tprint("%/models.config", folder), log_errors = false); if !ok fail(tprint("% has no models.config: build the app first.", folder)); for raw: split(config, "\n") { line := trim(raw); if !line || line[0] == #char "#" || line[0] == #char "[" || line[0] == #char "@" continue; colon := find_index_from_left(line, #char ":"); if colon < 0 continue; key := trim(slice(line, 0, colon)); value := trim(advance(line, colon + 1)); if key == "title" || value.count < 2 || value[0] != #char "\"" continue; file := slice(value, 1, find_index_from_left(advance(value, 1), #char "\"")); if !file_exists(tprint("%/%", folder, file)) fail(tprint("% names %, which is not there; fetch or make the models (see the module's README).", folder, file)); } } // This process's PATH. path_variable :: () -> string { kernel32 :: #system_library "kernel32"; GetEnvironmentVariableW :: (name: *u16, buffer: *u16, size: u32) -> u32 #foreign kernel32; buffer: [32768] u16; // the longest an environment variable can be count := GetEnvironmentVariableW(utf8_to_wide("PATH",, temp), buffer.data, buffer.count); if !count || count >= buffer.count return ""; text, ok := wide_to_utf8(buffer.data, cast(s32) count); return ifx ok then text else ""; } read_version :: () -> string { text, ok := read_entire_file("first.jai"); if !ok return ""; for raw: split(text, "\n") { line := trim(raw); if !begins_with(line, "VERSION ::") continue; quote := find_index_from_left(line, #char "\""); if quote < 0 return ""; rest := advance(line, quote + 1); return slice(rest, 0, find_index_from_left(rest, #char "\"")); } return ""; } // Runs a command; its output, and whether it ran and succeeded. run :: (args: .. string, print := false) -> output: string, ok: bool { result, output, error := run_command(..args, capture_and_return_output = !print, print_captured_output = print); return output, result.type == .EXITED && result.exit_code == 0; } copy :: (source: string, folder: string) { dest := tprint("%/%", folder, path_filename(source)); if !copy_file(source, dest) fail(tprint("Cannot copy % to %.", source, dest)); } copy_folder :: (source: string, dest: string) { if !file_exists(source) fail(tprint("% is not there: build the app first.", source)); if !copy_directory(source, dest) fail(tprint("Cannot copy % to %.", source, dest)); } file_size :: (path: string) -> s64 { _, size, ok := file_modtime_and_size(path); return ifx ok then size else 0; } folder_size :: (folder: string) -> s64 { total: s64; for file_list(folder, recursive = true) total += file_size(it); return total; } fail :: (message: string) { log_error("Release failed: %", message); exit(1); } RELEASE_README :: #string END mcap {VERSION}, for Windows 10 and 11 (x64) A library of screenshots, photos and videos, each kept as one .mcap file: imported from any folder (and Snagit's .snagx), read with OCR, labelled, segmented, and searched. Running it Unzip this folder anywhere and run mcap.exe. Everything it needs is in this folder. The library: mcap shows the .mcap files in D:\McapIndex\mcap. To use another folder, start it with -library, for example from a shortcut whose target is: "C:\Programs\mcap\mcap.exe" -library "E:\My library" Import (above the grid) brings in a folder of screenshots, photos, videos or Snagit captures; OCR, Labels and Segments read them. See README.md in the source for searching and the command line. The GPU {GPU} Without a usable NVIDIA GPU, OCR, labels and segmentation run on the CPU: slower, but the same. The OCR, Labels and Segments windows say whether the GPU is in use, and if not, why. Where it keeps things The library's .mcap files hold everything read from them. The app's settings, label list and segment colours are in %APPDATA%\mcap. See THIRD-PARTY-NOTICES.txt for the libraries and models included. END CUDA_INCLUDED :: #string END NVIDIA's CUDA 13 runtime and cuDNN 9 are included, so OCR, labels and segmentation run on an NVIDIA GPU (Turing or newer: GeForce RTX 20 series and up) with only the NVIDIA driver installed: a recent Game Ready or Studio driver that supports CUDA 13. END CUDA_NOT_INCLUDED :: #string END This release does not include NVIDIA's CUDA runtime. To use an NVIDIA GPU, install the CUDA 13 toolkit and cuDNN 9 for CUDA 13 from NVIDIA; mcap finds them where their installers put them. END NOTICES :: #string END Third-party software and models in this release onnxruntime 1.30.0 (onnxruntime.dll, onnxruntime_providers_*.dll) Microsoft, MIT License. https://github.com/microsoft/onnxruntime FFmpeg 6.0 (avcodec, avformat, avutil, swscale, swresample DLLs) The FFmpeg developers. This is gyan.dev's "full" shared build, licensed GPL v3 as a whole: see FFMPEG-BUILD.txt. Source: https://github.com/FFmpeg/FFmpeg (commit ea3d24bbe3). PP-OCRv5 text detection and recognition, and PP-LCNet text line orientation (ocr-models) PaddlePaddle (Baidu), Apache License 2.0. https://github.com/PaddlePaddle/PaddleOCR SigLIP 2 base, patch 16, 384 (label-models) Google, Apache License 2.0, as converted to ONNX by onnx-community. https://huggingface.co/google/siglip2-base-patch16-384 Mask2Former, Swin-B, COCO panoptic (segment-models) Meta (Facebook AI Research), from facebook/mask2former-swin-base-coco-panoptic, exported to ONNX by mcap's tools/export_mask2former.py. https://github.com/facebookresearch/Mask2Former Its class names are COCO panoptic's (https://cocodataset.org). {CUDA} Dear ImGui (in mcap.exe), Omar Cornut, MIT License. stb_image, stb_image_write and stb_image_resize (in mcap.exe), Sean Barrett, public domain or MIT. END CUDA_NOTICE :: #string END NVIDIA CUDA 13 runtime (cudart, cublas, cublasLt, cufft, nvrtc DLLs) and cuDNN 9 (cudnn DLLs) NVIDIA Corporation, redistributed under the CUDA Toolkit EULA (Attachment A) and the cuDNN Software License Agreement. https://docs.nvidia.com/cuda/eula/ https://docs.nvidia.com/deeplearning/cudnn/latest/reference/eula.html END #import "Basic"; #import "String"; #import "File"; #import "File_Utilities"; #import "Process"; #import "System"; #import "Compiler"; #import "Windows_Utf8";