# Umi-OCR Administrator & Developer Guide

This guide is the deep reference for **integrators, administrators, and developers** working with Umi-OCR: how the application is built, how its processes talk to each other, the complete HTTP API and command-line surface, configuration files, security model, and the internal architecture that makes it work. End-user task instructions live in the companion **User Guide**.

Umi-OCR is a **portable, fully offline OCR desktop application** built on Python + Qt/QML. It bundles an OCR engine (as a swappable plugin), recognizes images / screenshots / scanned PDFs / QR codes, and exposes its functionality over a local HTTP service that doubles as the cross-process command-line bridge.

---

## 1. Executive Summary

| Attribute | Value |
|---|---|
| Project | Umi-OCR |
| Author | hiroi-sora |
| License | MIT |
| Version (this snapshot) | 2.1.5 (`main` branch) |
| Source commit | `83173ef` (2025-11-20) |
| Primary platform | Windows 7 x64+ (Linux maintained separately) |
| UI stack | PySide2 (Qt for Python) + QML |
| HTTP framework | Bottle (vendored) on a WSGI reference server |
| Default service port | `1224` (auto-increments if occupied) |
| OCR engine | External plugin (PaddleOCR-json / RapidOCR) |
| Network posture | Offline; loopback-only by default |

**What ships in this repository.** This repo contains the **application logic**: the Python backend (`UmiOCR-data/py_src/`), the QML frontend (`UmiOCR-data/qt_res/`), the API documentation (`docs/`), and the i18n tooling (`dev-tools/`). It does **not** contain the Python runtime, Qt libraries, or the OCR engine — those are delivered by the separate [Windows](https://github.com/hiroi-sora/Umi-OCR_runtime_windows) and [Linux](https://github.com/hiroi-sora/Umi-OCR_runtime_linux) runtime repositories, and the engine comes from the [plugins](https://github.com/hiroi-sora/Umi-OCR_plugins) repository.

> [!NOTE]
> Because the runtime and engine live in other repositories, you will not find dependency manifests (`requirements.txt`, `pyproject.toml`) here. Third-party library versions are recorded in `CHANGE_LOG.md` per release (for example, the v2.1.5 Windows build pins `PyMuPDF 1.24.11`, `Pillow 10.4.0`, `pynput 1.8.0`, `psutil`, `fontTools 4.56.0`, `zxing-cpp 2.3.0`).

---

## 2. Architecture

### 2.1. High-Level Diagram

```text
                         ┌──────────────────────────────────────────────┐
   Umi-OCR.exe  ───────► │  main.py  (Windows/PyStand env bootstrap)     │
   (launcher)            │   • os.MessageBox last-resort error dialog    │
                         │   • set cwd = UmiOCR-data, add site-packages  │
                         │   • add PySide2 plugin path                   │
                         └───────────────────────┬──────────────────────┘
                                                 │  from py_src.run import main
                                                 ▼
                         ┌──────────────────────────────────────────────┐
                         │  py_src/run.py : main()                       │
                         │   • umi_about.init()   (version/host info)    │
                         │   • pre_configs.readConfigs()  (.pre_settings)│
                         │   • cmd_client.initCmd()  ── single-instance ─┼──► if already running:
                         │   • runQml()                                  │     POST argv → /argv, exit
                         └───────────────────────┬──────────────────────┘
                                                 ▼
   ┌─────────────────────────────────────────────────────────────────────────────────┐
   │  QGuiApplication + QQmlApplicationEngine  (qt_res/qml/Main.qml)                    │
   │                                                                                   │
   │   QML UI  ◄──── qmlRegisterType ────►  Python "Connector" QObjects                │
   │   (tabs, widgets)                       TagPageConnector, MissionConnector,        │
   │                                         PubSubConnector, KeyMouseConnector,        │
   │                                         UtilsConnector, GlobalConfigsConnector,    │
   │                                         ThemeConnector, ImageConnector,            │
   │                                         PluginsConnector, DocPreviewConnector      │
   │                                                                                   │
   │   ┌──────────────┐   ┌──────────────┐   ┌─────────────────┐   ┌────────────────┐  │
   │   │ Mission       │   │ tbpu          │   │ ocr/output      │   │ plugins_ctrl    │  │
   │   │ (task queues) │   │ (layout parse)│   │ (txt/pdf/csv…)  │   │ (load engines)  │  │
   │   └──────┬───────┘   └──────────────┘   └─────────────────┘   └───────┬────────┘  │
   │          │                                                            │           │
   │          ▼                                                            ▼           │
   │   MissionOCR / MissionDOC / MissionQRCode              OCR engine plugin (subproc)│
   │                                                        e.g. PaddleOCR-json        │
   └───────────────────────────────┬───────────────────────────────────────────────────┘
                                    ▼
              ┌───────────────────────────────────────────────────────┐
              │  Bottle HTTP service  (QThreadPool worker thread)       │
              │  127.0.0.1:1224                                         │
              │   /argv  /api/ocr  /api/qrcode  /api/doc/*              │
              └───────────────────────────────────────────────────────┘
```

### 2.2. Startup Sequence

1. **`Umi-OCR.exe`** (PyStand on Windows) sets the `PYSTAND` environment variable and runs `UmiOCR-data/main.py`.
2. **`main.py`** initializes the runtime environment: installs a `ctypes`-based `os.MessageBox` as the last-resort error reporter, redirects stdout/stderr to the console if present, changes the working directory to `UmiOCR-data`, adds `.` and `site-packages` to the import path, and registers the PySide2 plugin path. It then calls `py_src/run.py:main()`.
3. **`run.py:main()`** adds `py_src/imports` to the path, calls `umi_about.init(app_path)` to populate version/host metadata, strips a problematic `QMLSCENE_DEVICE` env var if present, reads pre-QML configuration, and runs the single-instance check (`initCmd()`).
4. If no other instance owns the lock, **`runQml()`** starts Qt: enables shared OpenGL contexts and high-DPI scaling, initializes the renderer, creates the `QGuiApplication`, registers every Python connector type with `qmlRegisterType`, initializes i18n, and loads `qt_res/qml/Main.qml`.

### 2.3. The QML ↔ Python Bridge

The UI is QML; the logic is Python. They are connected by **connector classes** — `QObject` subclasses registered with `qmlRegisterType()` in `run.py`. QML instantiates them and calls their slots; Python emits signals back. The registered connectors are:

| Connector | Source | Responsibility |
|---|---|---|
| `TagPageConnector` | `tag_pages/tag_pages_connector.py` | Tab/page lifecycle, holds page py/qml objects |
| `MissionConnector` | `mission/mission_connector.py` | Submits and controls OCR task queues |
| `DocPreviewConnector` | `mission/doc_preview_connector.py` | Document preview rendering |
| `PubSubConnector` | `event_bus/pubsub_connector.py` | Publish/subscribe event bus |
| `KeyMouseConnector` | `event_bus/key_mouse/key_mouse_connector.py` | Global keyboard/mouse hooks |
| `PluginsConnector` | `plugins_controller/plugins_connector.py` | OCR engine plugin loading |
| `UtilsConnector` | `utils/utils_connector.py` | Clipboard, file finding, shell open |
| `GlobalConfigsConnector` | `utils/global_configs_connector.py` | Shortcuts, language, OpenGL, log level, web service control |
| `ThemeConnector` | `utils/theme_connector.py` | Theme load/save |
| `ImageConnector` / `PixmapProvider` | `image_controller/` | Image processing, QML image provider |
| `I18n` | `utils/i18n_configs.py` | Runtime translation |

### 2.4. The Mission (Task Queue) Framework

All recognition work flows through a generic task-queue base class, `Mission` (`mission/mission.py`), subclassed by `MissionOCR`, `MissionDOC`, and `MissionQRCode`. Key behaviors:

- **Queues of queues.** Each submission is a *mission list* (a queue of individual tasks) identified by a UUID. Multiple mission lists can be active at once.
- **Scheduling modes.** `_schedulingMode` is `"1111"` (round-robin — take the first task from each queue in turn) or `"1234"` (sequential — finish one queue before starting the next). Round-robin is the default.
- **Async vs. sync.** `addMissionList(msnInfo, msnList)` returns immediately with a mission ID and invokes callbacks (`onStart`, `onReady`, `onGet`, `onEnd`). `addMissionWait(argd, msnList)` blocks on a `threading.Condition` until the queue finishes and returns the result list — this is what the synchronous HTTP `/api/ocr` route uses.
- **Lifecycle control.** `pauseMissionList`, `resumeMissionList`, `stopMissionList`, `stopAllMissions`. State per queue is `waiting → running → stop`.
- **Worker thread.** A single async worker (`_taskRun`) drains all queues under a `QMutex`; subclasses override `msnPreTask` (parameter prep) and `msnTask` (actual recognition).

### 2.5. OCR Engine Plugin System

The OCR engine is **not built in** — it is a plugin loaded at runtime by `plugins_controller/plugins_controller.py`:

- Plugins live under `./UmiOCR-data/plugins/`. The controller adds that directory to the import path and imports every subfolder that contains an `__init__.py`.
- Each plugin must define a `PluginInfo` dict with at least `group` (must be one of `PLUGINS_GROUPS`, currently `["ocr"]`) and `api_class`, plus optional `global_options` / `local_options` that describe its configurable parameters.
- After dynamic plugins load, `ocr/api.initOcrPlugins()` wires them into the API manager.

This is why parameters like `ocr.language`, `ocr.cls`, and `ocr.limit_side_len` are **engine-specific** (they belong to the PaddleOCR plugin). Integrators should never hard-code an engine's parameters — query the live option set via the `get_options` endpoints (see §4) and build requests from that.

### 2.6. Layout Post-Processing (tbpu)

`tbpu` ("text block processing unit", `ocr/tbpu/`) re-orders and merges the engine's raw text blocks into readable paragraphs. The parser registry (`ocr/tbpu/__init__.py`) maps a string key to a parser class:

| Key | Class | Behavior |
|---|---|---|
| `none` | `ParserNone` | No processing |
| `multi_para` | `MultiPara` | Multi-column, natural paragraphs (default) |
| `multi_line` | `MultiLine` | Multi-column, always wrap |
| `multi_none` | `MultiNone` | Multi-column, no wrap |
| `single_para` | `SinglePara` | Single-column, natural paragraphs |
| `single_line` | `SingleLine` | Single-column, always wrap |
| `single_none` | `SingleNone` | Single-column, no wrap |
| `single_code` | `SingleCode` | Single-column, preserve indentation |

`IgnoreArea` (also in `tbpu/`) drops text blocks that fall entirely within user-defined rectangles. Helper algorithms live in `tbpu/parser_tools/` (`gap_tree.py`, `line_preprocessing.py`, `paragraph_parse.py`).

### 2.7. Output Writers

Document/batch results are serialized by writer classes in `ocr/output/`, selected by key via the `Output` registry:

| Key | Writer | Output |
|---|---|---|
| `txt` | `output_txt.py` | Text with page/file info |
| `txtPlain` | `output_txt_plain.py` | Plain recognized text |
| (per-file) | `output_txt_individual.py` | One text file per input |
| `csv` | `output_csv.py` | CSV table |
| `jsonl` | `output_jsonl.py` | One JSON object per line |
| `md` | `output_md.py` | Markdown |
| `pdfLayered` | `output_pdf_layered.py` | Double-layer searchable PDF |
| `pdfOneLayer` | `output_pdf_one_layer.py` | Single-layer plain-text PDF |

---

## 3. Repository Structure

```text
Umi-OCR/
├── README.md / README_en.md / README_ja.md   Project overview (multi-language)
├── CHANGE_LOG.md                              Version history + dependency pins
├── LICENSE                                    MIT
├── docs/
│   ├── README_CLI.md                          Command-line manual (authoritative)
│   └── http/                                  HTTP API manual (authoritative)
│       ├── README.md  api_ocr.md  api_doc.md  api_qrcode.md  argv.md
│       └── api_doc_demo.py / .html            Working API client examples
├── dev-tools/i18n/                            Qt Linguist translation toolchain
└── UmiOCR-data/                               The application (working directory)
    ├── main.py                                Windows/PyStand bootstrap (entry point)
    ├── about.json                             Name, version, URLs, translators
    └── py_src/
        ├── run.py                             App startup, connector registration
        ├── imports/                           umi_about, umi_log, plugin_i18n, call_func
        ├── utils/                             configs, i18n, theme, threads, file finder
        ├── mission/                           task-queue framework (OCR/DOC/QRCode)
        ├── ocr/
        │   ├── api/                           OCR plugin API manager
        │   ├── tbpu/                          layout post-processing parsers
        │   └── output/                        result writers (txt/csv/pdf/…)
        ├── server/                            HTTP service + CLI bridge (bottle)
        ├── tag_pages/                         per-tab Python logic
        ├── event_bus/                         pub/sub + keyboard/mouse hooks
        ├── image_controller/                  screenshot + image handling
        ├── plugins_controller/                dynamic plugin loader
        └── platform/                          win32 / linux platform shims
    └── qt_res/qml/                            QML UI (Main.qml, TabPages, Widgets, …)
```

> [!TIP]
> The two `docs/` manuals are the project's own authoritative API references. They are written in Chinese; this guide translates and consolidates them. When the source code and the manual disagree, trust the source — but in this snapshot they are consistent.

---

## 4. HTTP API Reference

All endpoints are served by the Bottle app in `py_src/server/web_server.py`, listening on `127.0.0.1:1224` by default. Responses are JSON strings (non-ASCII characters are escaped to `\uXXXX` for transport).

> [!IMPORTANT]
> The HTTP service must be enabled (it is on by default). The service-related settings appear only when **Advanced** mode is enabled in the Global Settings tab. To allow LAN clients, switch the host from “local only” to “any available address” — see the [Security](#7-security) section before doing so.

### 4.1. Service Conventions

- **CORS:** the service sets `Access-Control-Allow-Origin: *` and allows `GET, POST, PUT, OPTIONS`. `OPTIONS` preflights are translated to the requested method.
- **Request size limit:** `BaseRequest.MEMFILE_MAX = 104857600` (100 MB per request).
- **Status codes:** `code == 100` means success across all endpoints. `code == 101` means "no text found" (OCR/QR) or "no file uploaded" (doc upload). Other codes indicate errors and carry a human-readable `data` string.
- **Concurrency:** the backend has limited concurrency support — avoid parallel calls. Under sustained heavy load, occasional `Error: connect ECONNREFUSED` responses can occur; simply retry.

### 4.2. Base Routes

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/` , `/umiocr` | Returns the application full-name string (liveness check) |
| `POST` | `/argv` | Cross-process command-line bridge (**localhost only**) |

### 4.3. Image OCR

#### `GET /api/ocr/get_options`

Returns the parameter definitions for the **currently loaded** OCR engine (titles, defaults, types, option lists). Use this to discover valid parameters dynamically rather than hard-coding them.

```bash
curl http://127.0.0.1:1224/api/ocr/get_options
```

Each parameter is described by `title`, `toolTip`, `default`, and `type` (`enum`, `boolean`, `text`, `number`, `var`). For PaddleOCR the set includes `ocr.language`, `ocr.cls`, `ocr.limit_side_len`, `tbpu.parser`, `tbpu.ignoreArea`, and `data.format`.

#### `POST /api/ocr`

Recognizes a single Base64-encoded image.

**Request body (JSON):**

| Field | Required | Description |
|---|---|---|
| `base64` | yes | Image as a Base64 string, **without** any `data:image/png;base64,` prefix |
| `options` | no | Parameter dict (see `get_options`); missing keys take their defaults |

```python
import json, requests

payload = {
    "base64": "iVBORw0KGgoAAAAN……",
    "options": {
        "ocr.language": "models/config_chinese.txt",
        "tbpu.parser": "multi_para",
        "data.format": "text",
    },
}
resp = requests.post(
    "http://127.0.0.1:1224/api/ocr",
    data=json.dumps(payload),
    headers={"Content-Type": "application/json"},
)
print(json.loads(resp.text))
```

**Response (JSON):**

| Field | Type | Description |
|---|---|---|
| `code` | int | `100` success, `101` no text, otherwise failure |
| `data` | list \| string | Result (see below); error string on failure |
| `time` | double | Recognition time in seconds |
| `timestamp` | double | Start timestamp (seconds) |

When `data.format` is `dict` (default), `data` is a list of blocks, each with `text`, `score` (0–1), `box` (four clockwise corner `[x,y]` points starting top-left), and `end` (the line terminator — empty, space, or `\n` — derived from layout parsing, so concatenating `text + end` reconstructs paragraphs). When `data.format` is `text`, `data` is the joined string.

> [!WARNING]
> Some HTTP libraries auto-convert escaped newlines (`\\n`) in the response body into real newlines, which then breaks JSON parsing. If you hit this, fetch the raw response string, replace every real newline with `\\n`, then parse.

**Request-validation error codes (`/api/ocr`):** `800` body not JSON, `801` empty body, `802` missing `base64`, `803` `options` not a dict, `804` `options` parsing failed.

### 4.4. Document OCR (PDF) — Five-Step Workflow

Document recognition (v2.1.4+) is asynchronous: upload → poll → build output → download → clean up. Routes live in `server/doc_server.py`.

```text
1. POST /api/doc/upload      → returns task id
2. POST /api/doc/result      → poll until is_done && state=="success"
3. POST /api/doc/download     → build target file(s), return download URL
4. GET  <download URL>        → fetch the file
5. GET  /api/doc/clear/<id>  → release server resources
```

#### `GET /api/doc/get_options`

Like the OCR options query, plus document-specific keys:

| Key | Default | Type | Meaning |
|---|---|---|---|
| `tbpu.ignoreRangeStart` | `1` | int | First page the ignore-regions apply to |
| `tbpu.ignoreRangeEnd` | `-1` | int | Last page (negatives count from the end) |
| `pageRangeStart` | `1` | int | First page to OCR |
| `pageRangeEnd` | `-1` | int | Last page to OCR (negatives count from the end) |
| `pageList` | `[]` | var | Explicit page list, e.g. `[1,2,5]`; takes priority over the range |
| `password` | `""` | text | Password for encrypted documents |
| `doc.extractionMode` | `"mixed"` | enum | `mixed`, `fullPage`, `imageOnly`, `textOnly` |

#### `POST /api/doc/upload`

Multipart form upload. Fields: `file` (required) and `json` (optional options string). Returns `{"code": 100, "data": "<task id>"}` on success.

> [!NOTE]
> On some Linux systems, a non-ASCII `file_name` causes `code == 101` (file not received). The official client handles this by retrying with an ASCII-only `temp_name` for the upload part. Allowed extensions are restricted to the document suffixes in `DocSuf`.

#### `POST /api/doc/result`

Poll task status. Body: `id` (required), `is_data` (include recognized text, default `false`), `is_unread` (only return new entries, default `true`), `format` (`dict` or `text`). When `code == 100` the response also carries `processed_count`, `pages_count`, `is_done`, `state` (`waiting`/`running`/`success`/`failure`), and `message` (only on failure).

#### `POST /api/doc/download`

Build the output. Body: `id` (required), `file_types` (array — one type returns a single file, several return a zip), `ignore_blank` (skip blank pages, default `true`). Valid `file_types`: `pdfLayered`, `pdfOneLayer`, `txt`, `txtPlain`, `jsonl`, `csv`. Returns a download `data` URL and `name`.

> [!CAUTION]
> The download URL contains an opaque id that is **not** the task id. You cannot construct the download URL from the task id — always use the URL returned by `/api/doc/download`.

#### `GET /api/doc/clear/<id>`

Deletes all of the task's temporary files (including the uploaded original) and aborts it if still running. Tasks auto-clear **24 hours** after completion (or after 24 hours of running); leftovers are also purged on next startup. Always clear tasks promptly to free resources.

**Document error codes:** `201`–`207` cover doc info extraction, encryption/password, mission submission, output-writer creation, write, save, and zip-packaging failures respectively.

### 4.5. QR Code / Barcode

`POST /api/qrcode` serves **both** reading and generating, distinguished by the body.

**Read** — body `base64` (required) + optional `preprocessing.*` options (`median_filter_size`, `sharpness_factor`, `contrast_factor`, `grayscale`, `threshold`). Response mirrors OCR: a `data` list where each item has `text`, `format` (e.g. `QRCode`, `Code128`, …), `box`, `orientation` (0 = upright), and `score` (always `1`, for OCR-format compatibility).

**Generate** — body `text` (required) + optional `options`: `format` (default `QRCode`), `w`, `h` (default `0` = auto-minimum), `quiet_zone` (default `-1` = auto), `ec_level` (error correction: `-1` auto, `1`=7%, `0`=15%, `3`=25%, `2`=30%; effective only for `Aztec`/`PDF417`/`QRCode`). Returns the generated image as a JPEG Base64 string in `data`.

### 4.6. The `/argv` Command Bridge

`POST /argv` accepts a JSON **list** of command-line arguments and executes them against the running instance, returning the result text. This is how the CLI reaches an already-running Umi-OCR.

```javascript
// Equivalent to: Umi-OCR.exe --screenshot
fetch("http://127.0.0.1:1224/argv", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(["--screenshot"]),
}).then(r => r.text()).then(console.log);
```

> [!IMPORTANT]
> `/argv` is **restricted to `127.0.0.1`**. The route checks `REMOTE_ADDR` and returns `401` for any non-loopback caller, because it can perform sensitive actions (read local images, close the app). This restriction holds even when the rest of the service is exposed to the LAN.

---

## 5. Command-Line Interface (Internals)

### 5.1. How the CLI Works

The command line is **not** a separate program — it is `Umi-OCR.exe` invoked with arguments. The flow (`server/cmd_client.py`):

1. `initCmd()` reads `sys.argv[1:]` and checks for `--force`.
2. `_isMultiOpen()` compares the recorded `last_pid` + `last_ptime` (process create time) in `.pre_settings` against live processes (via `psutil`) to detect whether an instance is already running.
3. **If already running:** `_sendCmd(argv)` POSTs the argument list to `http://127.0.0.1:<port>/argv` and exits — the running instance does the work.
4. **If not running, with arguments:** `_newSend()` launches a new process with `--force` (to avoid recursion), waits up to ~30 s for the service to come up, then forwards the arguments.
5. **If not running, no arguments (or `--force`):** the process becomes the primary instance, records its pid/ptime, and starts the GUI.

The parser itself (`server/cmd_server.py`, an `argparse.ArgumentParser(prog="Umi-OCR")`) and the `_Actuator` execute commands inside the GUI process.

### 5.2. Flag Reference

| Flag | Type | Action |
|---|---|---|
| `--show` / `--hide` / `--quit` | store_true | Control the main window |
| `--screenshot` | store_true | Screenshot OCR (accepts `screen=` / `rect=` positional params) |
| `--clipboard` | store_true | OCR the clipboard image |
| `--path` | store_true | OCR image path(s)/folder(s) from positional `paras` |
| `--qrcode_read` | store_true | Read code(s) from positional path(s) |
| `--qrcode_create` | store_true | Generate a code from positional `"text" "out.jpg" [w] [h]` |
| `--reload` | store_true | Publish `<<settingsReload>>` to reload `.settings` (v2.1.5+) |
| `--all_pages` | store_true | List page templates and open pages |
| `--add_page` / `--del_page` | int | Create/close a tab by index |
| `--all_modules` | store_true | List callable py/qml modules |
| `--call_py` / `--call_qml` | str | Target module (prefix match allowed) |
| `--func` | str | Function to call on the target module |
| `--thread` | store_true | Run the call on the worker thread to capture a return value (may be unstable) |
| `--clip` | store_true | Copy result to clipboard |
| `--output` / `-->` | str | Write result to file (overwrite) |
| `--output_append` / `-->>` | str | Write result to file (append) |
| `paras` | nargs=* | Positional parameters for the above |

### 5.3. Advanced Dynamic Invocation

`--all_modules`, `--call_py`, `--call_qml`, `--func`, and `--thread` let you call **arbitrary functions on any tab module**. Module names accept a prefix (e.g. `Scr` matches `ScreenshotOCR_1`). Positional `paras` are auto-coerced to `int` → `float` → JSON (`list`/`dict`) → string. By default calls run on the main thread asynchronously (no return value); `--thread` runs them synchronously on the worker thread to capture a return (with a stability caveat). Example — add documents to the Batch Document tab and start it:

```bash
umi-ocr --call_qml BatchDOC --func addDocs '[ "C:/Users/My/Desktop/111.epub", "C:/Users/My/Desktop/222.pdf"]'
umi-ocr --call_qml BatchDOC --func docStart
```

> [!WARNING]
> Synchronous (`--thread`) calls run outside the QML main thread and can destabilize or crash the UI. Use them only when you must capture a return value, and prefer the dedicated HTTP API for production automation.

---

## 6. Configuration

Umi-OCR keeps configuration in **two files**, both inside the working directory `./UmiOCR-data/`.

### 6.1. `.pre_settings` (pre-QML, JSON)

Loaded by `utils/pre_configs.py` **before** the QML engine starts, so it holds only the few keys needed early:

| Key | Default | Purpose |
|---|---|---|
| `i18n` | `""` | Interface language |
| `opengl` | `""` | Renderer / OpenGL type |
| `server_port` | `1224` | HTTP service port |
| `last_pid` | `-1` | PID of the last run (single-instance detection) |
| `last_ptime` | `-1` | Process create time of the last run |

Written atomically with `json.dump` (UTF-8, indented). Permission/IO errors are captured and surfaced via `getErrorStr()` rather than crashing.

### 6.2. `.settings` (main, INI)

The main configuration, managed by the QML engine and written whenever you change a setting in the UI (language, theme, fonts, OCR engine, service host, log level, etc.). You may edit it by hand and reload it without restarting via `umi-ocr --reload`, which publishes the `<<settingsReload>>` event.

> [!NOTE]
> The HTTP `server_port` lives in `.pre_settings`, not `.settings`, because the service may need to start before the QML config is available. If port `1224` is occupied, the server auto-increments and **persists the new port** back to `.pre_settings`.

### 6.3. Logging

Logging (`imports/umi_log.py`) writes entries at or above a configurable level (default `ERROR`) to `Umi-OCR/UmiOCR-data/logs`. The level is adjustable in the Global Settings tab. Launching Umi-OCR from a terminal shows real-time logs. Qt's own messages are redirected into this logger via `qInstallMessageHandler`.

---

## 7. Security

Umi-OCR is designed to run on a trusted local machine. Understand these properties before exposing it.

### 7.1. Network Exposure

- **Default: loopback only.** The service binds to `127.0.0.1`, reachable only from the local machine.
- **`/argv` is hard-locked to localhost.** It checks `REMOTE_ADDR == "127.0.0.1"` and returns `401` otherwise, regardless of the host setting — because it can read local files and shut down the app.
- **LAN exposure is opt-in.** Switching the host to “any available address” in Global Settings makes the OCR/QR/doc endpoints reachable from the network.

> [!CAUTION]
> When you expose the service to the LAN: there is **no authentication** on the API endpoints, CORS is wide open (`Access-Control-Allow-Origin: *`), and document upload accepts files up to 100 MB. Anyone who can reach the port can submit OCR/document jobs that consume CPU, memory, and disk on your machine. Only enable LAN access on a trusted network, and prefer a firewall rule or reverse proxy with authentication in front of it. The `/argv` control bridge remains localhost-only, so remote callers cannot drive the CLI.

### 7.2. File-Path Safety

The document routes guard against path traversal: both upload and download verify with `os.path.commonpath` that the resolved path stays inside `UPLOAD_DIR` (`./temp_doc`), returning an "Unauthorized path" error otherwise. Uploaded filenames are sanitized (illegal characters replaced, length capped at 255).

### 7.3. Data Handling

- **Fully offline.** No telemetry, no cloud calls. Recognition happens in the local engine subprocess.
- **Temporary files.** Uploaded documents and generated outputs live under `./temp_doc/<uuid>/` and are deleted on task `clear`, after a 24-hour retention window, or at next startup (the upload directory is wiped on init).
- **Encrypted PDFs.** Passwords are passed through to the document engine and are not persisted beyond the task.

### 7.4. Process & Engine Isolation

The OCR engine runs as a **separate subprocess** loaded as a plugin. If it crashes, the API returns `code: 902` ("failed to pass command to recognizer; subprocess likely crashed") rather than taking down the UI. Restarting Umi-OCR re-spawns the engine.

---

## 8. Operations

### 8.1. Starting and Stopping

- **Start:** run `Umi-OCR.exe` (or the Scoop shim, or the Linux launcher). No service manager is involved — it is a desktop process.
- **Stop:** close the window, press `Esc` to hide then quit, or `umi-ocr --quit`.

> [!WARNING]
> If an HTTP client holds an open connection when you close Umi-OCR, the UI thread exits but the network worker thread may linger (the app won't fully close). Ensure clients disconnect, or force-end the process via Task Manager. The custom WSGI server tracks active connections and force-closes them on shutdown, but a client that keeps reconnecting can still hold it open.

### 8.2. Health Check

```bash
curl http://127.0.0.1:1224/
# → the application full-name string if the service is alive
```

### 8.3. Choosing / Swapping the OCR Engine

Install engine plugins into `./UmiOCR-data/plugins/` (from the [plugins repo](https://github.com/hiroi-sora/Umi-OCR_plugins)) and select one under Global Settings. The two common engines:

- **PaddleOCR-json** — slightly faster; rich language model libraries.
- **RapidOCR** — broader hardware/OS compatibility.

### 8.4. Performance Tuning

- Raise/lower the engine's `ocr.limit_side_len` (e.g. `960` → `4320`) to trade speed for large-image accuracy.
- Avoid concurrent API calls; the backend serializes work through the mission queue and has limited concurrency.
- For overnight batch jobs, enable the post-task shutdown/sleep option in Batch OCR.
- On display glitches, switch the **Renderer** (OpenGL → ANGLE/software) or disable hardware acceleration.

### 8.5. Deployment Checklist

- [ ] Extract the release; confirm `Umi-OCR.exe` launches and a tab opens.
- [ ] Verify an OCR engine plugin is present and selected (run a Screenshot OCR).
- [ ] Confirm the HTTP service is enabled and `curl http://127.0.0.1:1224/` responds.
- [ ] If automating, decide loopback-only vs. LAN, and firewall accordingly.
- [ ] Set the log level appropriately and confirm the `logs` directory is writable.
- [ ] For document OCR, verify `./temp_doc` is writable and has enough free space.

---

## 9. Troubleshooting (Operator View)

### Error: `code: 902` — recognizer subprocess crash

**Cause:** The OCR engine subprocess died or stopped responding (issue #987; can surface as `[Errno 22] Invalid argument`).
**Resolution:** Restart Umi-OCR to re-spawn the engine. Switch engines if it recurs. Reduce image size / batch concurrency on low-memory hosts.

### Error: `无法创建输出器` — unable to create output writer (doc code 203)

**Cause:** The selected output writer could not be instantiated (issue #640) — typically a non-writable output path or a file locked by another process.
**Resolution:** Point output to a writable directory; close any program holding the target file.

### Error: `width=0,height=0` on screenshot (Linux)

**Cause:** An empty capture region (issue #1015).
**Resolution:** Drag a non-empty rectangle; pass a valid `rect=x,y,w,h`.

### Document `/result` reports the wrong completion state

**Cause:** Reported as an open issue (#1051) where the doc `/result` endpoint can return a completion state prematurely.
**Resolution:** Rely on `is_done && state == "success"` together before calling `/api/doc/download`, and poll until both hold.

### `Error: connect ECONNREFUSED` under load

**Cause:** Backend concurrency limits during sustained, high-volume calls.
**Resolution:** Retry the request; serialize calls instead of issuing them in parallel.

### App won't fully exit

**Cause:** A lingering HTTP client connection keeps the network thread alive.
**Resolution:** Disconnect clients; force-end via Task Manager if needed.

---

## 10. Internals & Code Quality

### 10.1. Conventions

- **Connectors** bridge QML and Python; each is a `QObject` with QML-invokable slots.
- **Mission subclasses** override `msnPreTask` / `msnTask`; everything funnels through the shared queue runner with `QMutex` locking.
- **Registries** map string keys to classes (`tbpu.Parser`, `ocr.output.Output`, plugin `PluginInfo`), making parsers/writers/engines pluggable.
- **Event bus** (`event_bus/`) uses publish/subscribe for cross-component signals such as `<<ScreenshotOcrEnd>>` and `<<settingsReload>>`.
- **Platform shims** (`platform/win32`, `platform/linux`) isolate OS-specific calls (key translation, new-process launch, native APIs).

### 10.2. Notable Design Choices

- **HTTP-as-IPC.** Using a local HTTP service for both the public API and the cross-process CLI keeps a single code path for "do this OCR action," whether the caller is the command line, a script, or the UI.
- **Vendored Bottle.** `server/bottle.py` is bundled so the app has no external web-framework dependency at runtime.
- **Custom WSGI server.** `_WSGIRefServer` tracks active connections so it can force-close them on shutdown and find a free port if `1224` is taken.

### 10.3. Known Issues & Improvement Opportunities (from the issue tracker)

| Theme | Issues | Note |
|---|---|---|
| PaddleOCR v5 / new engines | #1045, #1016, #989 | Demand for newer PaddleOCR (incl. GPU PP-OCRv5, #1053) — handled via the plugin system |
| Table recognition | #87, #445, #1058 | Recognizing tables/structured data is a recurring request |
| Math formula recognition | #254 | Experimental |
| macOS build | #1057 | Not currently provided (Windows + Linux only) |
| Linux autostart | #1050 | Auto-start on Linux requested |
| Engine subprocess robustness | #987 | `code: 902` crashes — candidate for supervised restart |

> [!TIP]
> Most "support more engines / GPU / new models" requests are addressable **without changing this repository** — they are engine-plugin work in the [plugins repository](https://github.com/hiroi-sora/Umi-OCR_plugins). The plugin contract (`PluginInfo` with `group`/`api_class`/`global_options`/`local_options`) is the extension point.

### 10.4. Contributing

- **Code:** Build instructions live in the [Windows](https://github.com/hiroi-sora/Umi-OCR_runtime_windows) and [Linux](https://github.com/hiroi-sora/Umi-OCR_runtime_linux) runtime repositories — clone a runtime, then drop this application code into it.
- **Translations:** Use [Weblate](https://hosted.weblate.org/engage/umi-ocr/); the `dev-tools/i18n/` toolchain (Qt Linguist, `lupdate`/`lrelease` wrappers) compiles `.ts` → `.qm` files under `UmiOCR-data/i18n/`.
- **Engine plugins:** Implement the `PluginInfo` contract in the plugins repository.

---

## 11. Quick Reference

### 11.1. Endpoints

| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | `/` , `/umiocr` | none | Liveness / app name |
| POST | `/argv` | localhost-only | Run CLI args against the instance |
| GET | `/api/ocr/get_options` | none | OCR parameter schema |
| POST | `/api/ocr` | none | Image OCR (Base64) |
| GET | `/api/doc/get_options` | none | Document OCR parameter schema |
| POST | `/api/doc/upload` | none | Upload document, start task |
| POST | `/api/doc/result` | none | Poll task status / results |
| POST | `/api/doc/download` | none | Build output, get download URL |
| GET | `/api/doc/download/<id>/<name>` | none | Download built file |
| GET | `/api/doc/clear/<id>` | none | Clear task + temp files |
| POST | `/api/qrcode` | none | Read or generate codes |

### 11.2. Status Codes

| Code | Meaning |
|---|---|
| `100` | Success |
| `101` | No text (OCR/QR) / no file uploaded (doc) |
| `201`–`207` | Document task errors (info, password, submit, writer, write, save, zip) |
| `800`–`804` | OCR request validation errors |
| `902` | Recognizer subprocess communication failure |

### 11.3. Key Files & Paths

| Path | Purpose |
|---|---|
| `UmiOCR-data/main.py` | Entry point (env bootstrap) |
| `UmiOCR-data/py_src/run.py` | App startup |
| `UmiOCR-data/.pre_settings` | Pre-QML config (port, language, pid) |
| `UmiOCR-data/.settings` | Main config (INI, GUI-managed) |
| `UmiOCR-data/plugins/` | OCR engine plugins |
| `UmiOCR-data/logs/` | Log files |
| `UmiOCR-data/temp_doc/` | Document-OCR temp files |

### 11.4. Defaults

| Setting | Default |
|---|---|
| Service host | `127.0.0.1` (loopback) |
| Service port | `1224` (auto-increment if busy) |
| Request size limit | 100 MB |
| Layout parser | `multi_para` |
| Document extraction mode | `mixed` |
| Document download type | `pdfLayered` |
| Temp file retention | 24 hours |
| Log level | `ERROR` |

---

_Generated June 2026 from hiroi-sora/Umi-OCR at 83173ef. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
