Metadata-Version: 2.4
Name: textrun
Version: 0.1.0
Summary: Generalized Wayland-compatible text reader with OCR and TTS
Author-email: marvin1099 <marvin1099@noreply.codeberg.org>
License-Expression: AGPL-3.0-only
Project-URL: Homepage, https://codeberg.org/marvin1099/textrun
Project-URL: Repository, https://codeberg.org/marvin1099/textrun
Project-URL: Issues, https://codeberg.org/marvin1099/textrun/issues
Keywords: ocr,tts,screen-reader,wayland,accessibility
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: X11 Applications
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Text Processing
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: tomlkit>=0.12
Requires-Dist: Pillow>=11.0
Requires-Dist: imagehash>=4.3
Requires-Dist: playsound3>=3.0
Requires-Dist: PySide6>=6.5
Provides-Extra: ocr-tesseract
Requires-Dist: pytesseract>=0.3.10; extra == "ocr-tesseract"
Provides-Extra: ocr-rapid
Requires-Dist: rapidocr-onnxruntime; extra == "ocr-rapid"
Provides-Extra: scroll-pyautogui
Requires-Dist: pyautogui>=0.9; extra == "scroll-pyautogui"
Provides-Extra: tts-pyttsx3
Requires-Dist: pyttsx3>=2.90; extra == "tts-pyttsx3"
Provides-Extra: screenshots-pyscreenshot
Requires-Dist: pyscreenshot>=3.0; extra == "screenshots-pyscreenshot"
Provides-Extra: displays
Requires-Dist: wlpdisplays; extra == "displays"
Requires-Dist: screeninfo>=0.8; extra == "displays"
Provides-Extra: shortcuts
Requires-Dist: global-shortcut-portal; extra == "shortcuts"
Requires-Dist: dbus-next; extra == "shortcuts"
Requires-Dist: pynput; extra == "shortcuts"
Provides-Extra: wayland
Requires-Dist: pytesseract>=0.3.10; extra == "wayland"
Requires-Dist: rapidocr-onnxruntime; extra == "wayland"
Requires-Dist: pyscreenshot>=3.0; extra == "wayland"
Requires-Dist: wlpdisplays; extra == "wayland"
Requires-Dist: global-shortcut-portal; extra == "wayland"
Requires-Dist: dbus-next; extra == "wayland"
Provides-Extra: x11
Requires-Dist: pytesseract>=0.3.10; extra == "x11"
Requires-Dist: rapidocr-onnxruntime; extra == "x11"
Requires-Dist: pyautogui>=0.9; extra == "x11"
Requires-Dist: screeninfo>=0.8; extra == "x11"
Requires-Dist: pynput; extra == "x11"
Provides-Extra: mac-win
Requires-Dist: pytesseract>=0.3.10; extra == "mac-win"
Requires-Dist: rapidocr-onnxruntime; extra == "mac-win"
Requires-Dist: pyttsx3>=2.90; extra == "mac-win"
Requires-Dist: screeninfo>=0.8; extra == "mac-win"
Requires-Dist: pynput; extra == "mac-win"
Provides-Extra: all
Requires-Dist: pytesseract>=0.3.10; extra == "all"
Requires-Dist: rapidocr-onnxruntime; extra == "all"
Requires-Dist: pyautogui>=0.9; extra == "all"
Requires-Dist: pyttsx3>=2.90; extra == "all"
Requires-Dist: pyscreenshot>=3.0; extra == "all"
Requires-Dist: wlpdisplays; extra == "all"
Requires-Dist: screeninfo>=0.8; extra == "all"
Requires-Dist: global-shortcut-portal; extra == "all"
Requires-Dist: dbus-next; extra == "all"
Requires-Dist: pynput; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# textrun

Generalized Wayland-compatible text reader with OCR and TTS.
Captures screen content via screenshots, extracts text with OCR, and reads it aloud.

**Repository:** [Codeberg](https://codeberg.org/marvin1099/textrun) (main) · [GitHub](https://github.com/marvin1099/textrun) (mirror)

**Created in heavy cooperation with AI**

## Features

- **OCR from screen** capture any region or full screen, extract text
- **Auto-scroll** scrolls and re-captures until no new content appears
- **Text stitching** deduplicates overlapping OCR frames into clean text
- **TTS output** reads extracted text aloud via system TTS or command fallback
- **Live reading** text is spoken while still capturing (`tts.live`), streaming or chunked
- **Configurable backends** every step (screenshot, scroll, OCR, TTS, audio) is swappable
- **Global shortcuts** start and stop captures system-wide while the GUI or `--shortcuts` daemon runs (`capture_shortcut`, `capture_shortcut_stop`; Wayland global-shortcuts portal or pynput on X11/Win/Mac)
- **Mixed-DPI aware** pixel-accurate captures across monitors with different scales
- **Chain fallback** if a backend fails, the next in chain is tried automatically
- **Profiles** per-app settings (Firefox, games, terminals) with inheritance from global
- **GUI** PySide6 config manager with region picker, profile editor, backend reorder
- **Wayland support** dotool/ydotool for scroll, grim for screenshots, speech-dispatcher for TTS

## Installation

### Recommended: uv

[uv](https://docs.astral.sh/uv/) is the fastest way to install Python tools.

```bash
# Install textrun for your platform
uv tool install --with 'textrun[wayland]' .    # Wayland (Sway, Hyprland, GNOME, KDE)
uv tool install --with 'textrun[x11]' .        # X11
uv tool install --with 'textrun[mac-win]' .    # Windows / macOS
```

The GUI (PySide6) is a core dependency and always included.

### Alternative: pipx

[pipx](https://pypa.github.io/pipx/) installs Python CLI tools in isolated environments.

```bash
# Install textrun for your platform
pipx install "textrun[wayland]"
pipx install "textrun[x11]"
pipx install "textrun[mac-win]"
```

### Last resort: pip

```bash
pip install --user "textrun[wayland]"
pip install --user "textrun[x11]"
pip install --user "textrun[mac-win]"
```

Make sure `~/.local/bin` is in your `PATH`.

## Usage

```bash
# Basic usage (global profile)
textrun

# Use a specific profile
textrun -p wayland
textrun -p firefox
textrun -p game-terminal

# Capture a specific region
textrun -p wayland --region 100,200,800,600

# Save text to file
textrun -p firefox -o output.txt

# Disable TTS
textrun --no-tts

# Override settings
textrun --scroll-amount 5 --max-scrolls 100

# List available profiles
textrun --list-profiles

# Launch the GUI
textrun --gui
textrun -p wayland --gui

# Daemon-like: wait for global shortcuts only, no window
textrun --shortcuts

# Verbose logging
textrun -v -p wayland
```

## Configuration

Config file: `~/.config/textrun/config.toml`

### Backend types

Every backend in the config has a `type` field:

| Type | Description | Example |
|------|-------------|---------|
| `command` | Runs an external program via subprocess | grim, dotool, piper |
| `library` | Calls a Python library function directly | pytesseract, rapidocr |
| `adapter` | Calls a textrun adapter for stateful libraries | speechd, pyttsx3 |

### Config example

```toml
# Global defaults (inherited by all profiles)
[global]
scroll_amount = 3
scroll_delay = 1.0
max_scroll_attempts = 50
language = "eng"
tts_enabled = true
tts_volume = 100               # percent; {tts_volume}/{tts_volume_pa} placeholders

# Screenshot backend chain
[global.screenshot]
chain = ["pyscreenshot", "grim", "spectacle"]

[global.screenshot.backends.grim]
type = "command"
program = "grim"
args = ["{output_path}"]

# Scroll backend chain
[global.scroll]
chain = ["pyautogui", "xdotool", "dotool", "ydotool"]

[global.scroll.backends.dotool]
type = "command"
program = "dotoolc"
args = []
stdin = "wheel -{scroll_amount}"

# OCR backend chain
[global.ocr]
chain = ["tesseract", "rapidocr"]

[global.ocr.backends.tesseract]
type = "library"
library = "pytesseract"
function = "image_to_string"
args = { image = "{image_path}", lang = "{language}" }

# TTS backend chain
[global.tts]
chain = ["speechd", "pyttsx3", "piper", "espeak-ng"]

[global.tts.backends.speechd]
type = "adapter"
adapter = "textrun.adapters"
function = "speechd_say"
```

### Placeholders

Placeholders like `{key}` are resolved from the runtime context:

| Placeholder | Source |
|-------------|--------|
| `{scroll_amount}` | config `scroll_amount` |
| `{output_path}` | generated per run |
| `{image_path}` | generated per run |
| `{text}` | OCR output |
| `{audio_path}` | generated per run |
| `{language}` | config `language` |
| `{region_str}` | formatted region |

### Commands

Command backends support these fields:

```toml
[backends.my-command]
type = "command"
program = "dotoolc"           # binary name, resolved via PATH
args = ["arg1", "{var}"]     # list, placeholders resolved
stdin = "text {var}"         # string template: piped to stdin
env = { KEY = "val" }        # extra environment variables
stdout = true                # capture stdout
stderr = true                # capture stderr
```

`stdin` accepts a string template (recommended, e.g. `"{text}"` for TTS) or `true`
(pipes the internal `stdin_text` context key). Stream backends (`type = "stream"`)
are fed incrementally by the live reader and don't use `stdin`.

## Profiles

Profiles inherit from `global` by default. Override only what differs.

### Built-in profiles

| Profile | Description |
|---------|-------------|
| `global` | Base defaults for all settings |
| `wayland` | dotool scroll, grim screenshots |
| `firefox` | Larger scroll amount, slower delay |
| `game-terminal` | Arrow key scroll, no TTS, fast timing |

### Profile example

```toml
[profiles.firefox]
inherits = "global"
scroll_amount = 5
scroll_delay = 1.5

[profiles.firefox.scroll]
chain = ["dotool"]
```

### Region in profiles

Set a capture region per profile:

```toml
[profiles.firefox]
inherits = "global"
scroll_amount = 5

[profiles.firefox.region]
x = 100
y = 200
width = 800
height = 600
```

The GUI also saves two companion keys for pixel-accurate captures:

```toml
[profiles.firefox.screen_size]
width = 2560
height = 1440

[[profiles.firefox.monitors]]
x = 0
y = 0
width = 2560
height = 1440
scale = 1.5
```

`region` uses logical desktop coordinates; `monitors` records each
monitor's layout and scale so crops stay correct on mixed-DPI setups.
Both are written by the GUI region picker, set them by hand only if you
must.

## GUI

Ships with PySide6 as a core dependency, no extra install needed.

```bash
textrun --gui
```

Features:
- **Profile selector** switch between profiles
- **Region picker** click and drag to select capture area
- **Backend reorder** drag and drop to change fallback order
- **Start/Stop buttons + global shortcuts** capture without touching the CLI
- **Copy command** copies `textrun -p <profile>` to clipboard
- **Save config** writes settings to `~/.config/textrun/config.toml`

Headless variant: `textrun --shortcuts` registers the global shortcuts
(`capture_shortcut`, `capture_shortcut_stop`) and waits without showing a
window; captures run with the selected profile's saved config.

Tip: run `textrun --install-desktop` once. The Wayland global-shortcuts
portal attributes shortcuts to the launching app, started from a
terminal they appear under e.g. "Konsole"; with the desktop entry
installed and textrun launched from the app menu, they register under
"textrun" with its own icon.

## Backend defaults

### Screenshot

| Order | Backend | Type | Notes |
|-------|---------|------|-------|
| 1 | pyscreenshot | adapter | Auto-detects Wayland/X11 |
| 2 | grim | command | Wayland native (wlroots) |
| 3 | spectacle | command | KDE native |

### Scroll

| Order | Backend | Type | Notes |
|-------|---------|------|-------|
| 1 | pyautogui | adapter | X11, may prompt on Wayland |
| 2 | xdotool | command | X11 only |
| 3 | dotool | command | Wayland, needs /dev/uinput |
| 4 | ydotool | command | Wayland, needs ydotoold |

**Wayland profile order:** dotool → ydotool → pyautogui → xdotool

### OCR

| Order | Backend | Type | Notes |
|-------|---------|------|-------|
| 1 | tesseract | library | Fast, good for clean text |
| 2 | rapidocr | library | Better accuracy, heavier |

### TTS

| Order | Backend | Type | Notes |
|-------|---------|------|-------|
| 1 | speechd | adapter | Linux system standard |
| 2 | pyttsx3 | adapter | Cross-platform (Win/Mac/Linux) |
| 3 | piper | command | Neural quality, needs model |
| 4 | espeak-ng | command | Robotic, always available |

### Audio playback

| Order | Backend | Type | Notes |
|-------|---------|------|-------|
| 1 | playsound3 | adapter | Cross-platform via GStreamer (no volume support) |
| 2 | mpv | command | Plays anything, honors `tts_volume` |
| 3 | ffplay | command | Fallback, honors `tts_volume` |

### Volume

`tts_volume` (percent, default 100) is set in `[global]` and inherited by
profiles. It reaches backends as template placeholders:

- `{tts_volume}` percent, for ffplay (`-volume`) and mpv (`--volume=`)
- `{tts_volume_pa}` linear 0–65536, for paplay (`--volume=`) and pactl

The piper-stream player and the mpv/ffplay audio backends use these by
default. Native TTS engines (speechd, pyttsx3) and playsound3 ignore it.

### Live reading

With `tts.live = true` in `[global.tts]`, text starts playing while
capture is still running instead of after the last frame:

```toml
[global.tts]
live = true             # read incrementally during capture
live_queue_chunks = true  # see below
```

- **Streaming TTS** (`piper-stream`): synthesis and playback run as a
  continuous pipeline words come out as lines are confirmed stable.
- **Chunked TTS** (all other backends): confirmed text is queued and
  spoken chunk-by-chunk; `live_queue_chunks` controls whether chunks are
  pre-synthesized ahead of playback.

The stitcher holds back the last two lines until later frames confirm
them; when capture ends they are flushed and read too. The stop shortcut
cuts off reading immediately. If live playback never produced audio,
textrun falls back to one batch read at the end.

## Optional dependencies

Platform bundles include everything needed for that platform:

```bash
uv pip install "textrun[wayland]"     # OCR + screenshots for Wayland
uv pip install "textrun[x11]"         # OCR + pyautogui for X11
uv pip install "textrun[mac-win]"     # OCR + pyttsx3 for Windows/macOS
uv pip install "textrun[all]"         # Everything
```

Or install individual extras:

```bash
uv pip install "textrun[ocr-tesseract]"     # Tesseract OCR
uv pip install "textrun[ocr-rapid]"         # RapidOCR
uv pip install "textrun[scroll-pyautogui]"  # pyautogui scroll
uv pip install "textrun[tts-pyttsx3]"       # pyttsx3 TTS
uv pip install "textrun[screenshots-pyscreenshot]"  # pyscreenshot
```

PySide6 (GUI), Pillow, imagehash and playsound3 are core dependencies
and always installed.

## License

[AGPL-3.0](LICENSE)
