This commit is contained in:
dingfeng.wong
2025-07-22 01:51:19 +08:00
parent eb47b6a22d
commit e0e5edbfdc
5 changed files with 688 additions and 28 deletions
+145 -15
View File
@@ -5,40 +5,170 @@ A collection of useful command-line tools.
## OCR Screenshot Tool
A CLI tool that takes region screenshots on macOS, performs OCR using Tesseract, and copies the result to clipboard.
A cross-platform CLI tool that takes screenshots, performs OCR using DocTR (state-of-the-art deep learning OCR), and copies the result to clipboard. Features intelligent text formatting preservation and optional image annotation.
### Prerequisites
### Features
- macOS (uses built-in `screencapture` command)
- Tesseract OCR (install with `brew install tesseract`)
- 🌍 **Cross-platform** - Works on Windows, macOS, and Linux
- **Multiple screenshot methods** - Choose the fastest for your system
- 🔍 **Advanced OCR** - Uses DocTR with PARSeq recognition model
- 📝 **Smart formatting** - Preserves text layout and indentation
- 🎨 **Image annotation** - Visualize detected text regions
- 📋 **Clipboard integration** - Automatic text copying
### Installation
#### Basic installation:
```bash
pip install .
```
#### With cross-platform screenshot support:
```bash
# For fastest screenshots (recommended)
pip install ".[screenshot-fast]"
# For full automation features (region selection)
pip install ".[screenshot-full]"
# For maximum compatibility (all backends)
pip install ".[screenshot-all]"
```
#### Install specific screenshot libraries:
```bash
pip install mss # Fastest (~30x faster than others)
pip install pyautogui # Interactive region selection
pip install pyscreenshot # Multiple backends
```
### Usage
Basic usage (takes screenshot, performs OCR, copies to clipboard):
#### Basic Commands
Take a screenshot and perform OCR:
```bash
uv run ocr-screenshot
ocr-screenshot
```
With verbose output:
With verbose output and annotation:
```bash
uv run ocr-screenshot --verbose
ocr-screenshot --verbose --annotate --save-image
```
Save the screenshot image:
#### Screenshot Methods
Choose your preferred screenshot method:
```bash
uv run ocr-screenshot --save-image
# Auto-detect best method (default)
ocr-screenshot --screenshot-method auto
# Use MSS (fastest)
ocr-screenshot --screenshot-method mss
# Use PyAutoGUI (supports region selection)
ocr-screenshot --screenshot-method pyautogui
# Use Pillow ImageGrab (built-in)
ocr-screenshot --screenshot-method pillow
# Interactive region selection
ocr-screenshot --screenshot-method interactive
# macOS native (region selection with drag)
ocr-screenshot --screenshot-method macos
```
Specify OCR language (e.g., for Chinese):
#### Advanced Features
Save screenshot with annotation showing detected text:
```bash
uv run ocr-screenshot --lang chi_sim
ocr-screenshot --save-image --annotate --show-words --show-text
```
Capture specific monitor (MSS method):
```bash
ocr-screenshot --screenshot-method mss --monitor-number 2
```
Full annotation with all detection levels:
```bash
ocr-screenshot --annotate --show-words --show-lines --show-blocks --show-text --save-image
```
### Screenshot Method Comparison
| Method | Speed | Region Selection | Cross-Platform | Notes |
|--------|-------|------------------|----------------|-------|
| **mss** | ⚡⚡⚡ Fastest | ❌ (crop after) | ✅ | ~30x faster, recommended |
| **pyautogui** | ⚡ Slow | ✅ Interactive | ✅ | Best for region selection |
| **pillow** | ⚡ Slow | ✅ Coordinates | ✅ | Built into Pillow |
| **pyscreenshot** | ⚡ Variable | ✅ Coordinates | ✅ | Multiple backends |
| **macos** | ⚡⚡ Fast | ✅ Native UI | 🍎 macOS only | Native drag selection |
### How it works
1. **Screenshot**: Click and drag to select a region, or press Space to capture an entire window
2. **OCR**: The selected region is processed with Tesseract OCR
3. **Clipboard**: The extracted text is automatically copied to your clipboard
1. **Screenshot**: Multiple cross-platform methods available
- **Auto**: Tries best method for your platform
- **MSS**: Fastest full-screen capture
- **Interactive**: Guided region selection
- **macOS**: Native drag-to-select interface
2. **OCR**: Advanced DocTR processing
- Uses state-of-the-art PARSeq recognition model
- Preserves text layout and indentation
- Handles multiple languages
3. **Annotation** (optional): Visual feedback
- Word-level bounding boxes (red)
- Line-level groupings (green)
- Block-level sections (blue)
- Text overlay showing detected content
4. **Output**: Formatted text copied to clipboard
### Command Line Options
```bash
ocr-screenshot [OPTIONS]
Options:
--lang TEXT Language code for OCR (default: eng)
--save-image Save the screenshot image
--output-dir PATH Directory to save images (default: ~/Desktop)
--verbose Show detailed output
--annotate Create annotated image with detection boxes
--show-words Show word-level boxes (default: True)
--show-lines Show line-level boxes
--show-blocks Show block-level boxes
--show-text Overlay detected text on image
--screenshot-method TEXT Method: auto, mss, pyautogui, pillow, pyscreenshot, macos, interactive
--monitor-number INTEGER Monitor to capture (MSS method only, 0=all)
--help Show this message and exit
```
### Examples
**Quick OCR with fastest method:**
```bash
ocr-screenshot --screenshot-method mss
```
**Debug OCR accuracy with annotations:**
```bash
ocr-screenshot --annotate --show-words --show-text --save-image --verbose
```
**Interactive region selection:**
```bash
ocr-screenshot --screenshot-method interactive --save-image
```
**Multi-monitor setup (capture monitor 2):**
```bash
ocr-screenshot --screenshot-method mss --monitor-number 2
```
## Development Guide
+11
View File
@@ -15,6 +15,17 @@ dependencies = [
"rich>=13.0.0",
]
[project.optional-dependencies]
# Cross-platform screenshot libraries
screenshot-fast = ["mss>=7.0.0"] # Fastest screenshot library
screenshot-full = ["pyautogui>=0.9.54"] # Full automation including region selection
screenshot-multi = ["pyscreenshot>=3.1"] # Multiple backend support
screenshot-all = [
"mss>=7.0.0",
"pyautogui>=0.9.54",
"pyscreenshot>=3.1"
] # All screenshot libraries for maximum compatibility
[project.scripts]
ocr-screenshot = "tooling.cli:cli_main"
+43 -13
View File
@@ -18,7 +18,7 @@ from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.syntax import Syntax
from .ocr_screenshot import copy_to_clipboard, perform_ocr, take_region_screenshot, perform_ocr_with_annotation
from .ocr_screenshot import copy_to_clipboard, perform_ocr, take_region_screenshot, perform_ocr_with_annotation, take_region_screenshot_cross_platform
app = typer.Typer(
name="ocr-screenshot",
@@ -66,6 +66,14 @@ def main(
show_text: bool = typer.Option(
default=False,
help="Overlay detected text on the annotated image"
),
screenshot_method: str = typer.Option(
default="auto",
help="Screenshot method to use: auto, mss, pyautogui, pillow, pyscreenshot, macos, interactive"
),
monitor_number: int = typer.Option(
default=0,
help="Monitor number to capture (0=all monitors, 1+=specific monitor, only for MSS method)"
)
):
"""Take a region screenshot, perform OCR, and copy result to clipboard."""
@@ -83,20 +91,42 @@ def main(
try:
# Step 1: Take screenshot
if verbose:
console.print("\n[bold blue]📸 Taking region screenshot...[/bold blue]")
console.print(Panel(
"[bold]Instructions:[/bold]\n"
"• Drag to select a region\n"
"• Press [bold]Space[/bold] to capture entire window\n"
"• Press [bold]Escape[/bold] to cancel",
title="Screenshot Controls",
border_style="blue"
))
console.print(f"\n[bold blue]📸 Taking screenshot using method: {screenshot_method}[/bold blue]")
# Show method-specific instructions
if screenshot_method == "macos":
console.print(Panel(
"[bold]macOS Screenshot Instructions:[/bold]\n"
"• Drag to select a region\n"
"• Press [bold]Space[/bold] to capture entire window\n"
"• Press [bold]Escape[/bold] to cancel",
title="Screenshot Controls",
border_style="blue"
))
elif screenshot_method == "interactive":
console.print(Panel(
"[bold]Interactive Screenshot Instructions:[/bold]\n"
"• Follow the prompts to select region corners\n"
"• Position mouse and press ENTER at each corner",
title="Screenshot Controls",
border_style="green"
))
elif screenshot_method in ["mss", "pillow", "pyautogui", "pyscreenshot"]:
console.print(Panel(
f"[bold]{screenshot_method.upper()} Screenshot:[/bold]\n"
"• Full screen capture (region selection not supported in CLI yet)\n"
"• Use --screenshot-method interactive for region selection",
title="Screenshot Info",
border_style="yellow"
))
else:
console.print("[bold blue]📸 Taking screenshot...[/bold blue]")
console.print(f"[bold blue]📸 Taking screenshot ({screenshot_method})...[/bold blue]")
if not take_region_screenshot(str(screenshot_path)):
console.print("[bold red]❌ Screenshot cancelled or failed.[/bold red]")
if not take_region_screenshot_cross_platform(str(screenshot_path), method=screenshot_method, monitor_number=monitor_number):
console.print(f"[bold red]❌ Screenshot failed with method '{screenshot_method}'.[/bold red]")
if screenshot_method == "auto":
console.print("[yellow]💡 Try installing additional screenshot libraries:[/yellow]")
console.print(" pip install mss pyautogui pyscreenshot")
raise typer.Exit(1)
if verbose:
+288
View File
@@ -7,6 +7,7 @@ Core functionality for taking screenshots, performing OCR using DocTR, and clipb
import os
import subprocess
import sys
from typing import Optional, Tuple
import pyperclip
@@ -15,6 +16,293 @@ from doctr.io import DocumentFile
from doctr.models import ocr_predictor
def take_region_screenshot_mss(output_path: str, monitor_number: int = 0) -> bool:
"""
Take a screenshot using MSS (fastest cross-platform option).
Args:
output_path: Path where the screenshot will be saved
monitor_number: Monitor to capture (0 = all monitors, 1+ = specific monitor)
Returns:
True if screenshot was taken successfully, False otherwise
"""
try:
import mss
with mss.mss() as sct:
if monitor_number == 0:
# Capture all monitors
monitor = sct.monitors[0] # All monitors combined
else:
# Capture specific monitor
if monitor_number > len(sct.monitors) - 1:
monitor_number = 1 # Default to first monitor
monitor = sct.monitors[monitor_number]
# Capture the screen
screenshot = sct.grab(monitor)
# Convert to PIL Image and save
img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
img.save(output_path)
return os.path.exists(output_path) and os.path.getsize(output_path) > 0
except ImportError:
print("MSS library not installed. Install with: pip install mss")
return False
except Exception as e:
print(f"Error taking screenshot with MSS: {e}")
return False
def take_region_screenshot_pyautogui(output_path: str, region: Optional[Tuple[int, int, int, int]] = None) -> bool:
"""
Take a screenshot using PyAutoGUI (supports region selection).
Args:
output_path: Path where the screenshot will be saved
region: Optional tuple of (left, top, width, height) to capture specific region
Returns:
True if screenshot was taken successfully, False otherwise
"""
try:
import pyautogui
# Take screenshot
if region:
screenshot = pyautogui.screenshot(region=region)
else:
screenshot = pyautogui.screenshot()
# Save the screenshot
screenshot.save(output_path)
return os.path.exists(output_path) and os.path.getsize(output_path) > 0
except ImportError:
print("PyAutoGUI library not installed. Install with: pip install pyautogui")
return False
except Exception as e:
print(f"Error taking screenshot with PyAutoGUI: {e}")
return False
def take_region_screenshot_pillow(output_path: str, bbox: Optional[Tuple[int, int, int, int]] = None) -> bool:
"""
Take a screenshot using PIL/Pillow ImageGrab (built into Pillow).
Args:
output_path: Path where the screenshot will be saved
bbox: Optional tuple of (left, top, right, bottom) to capture specific region
Returns:
True if screenshot was taken successfully, False otherwise
"""
try:
from PIL import ImageGrab
# Take screenshot
screenshot = ImageGrab.grab(bbox=bbox)
if screenshot is None:
return False
# Save the screenshot
screenshot.save(output_path)
return os.path.exists(output_path) and os.path.getsize(output_path) > 0
except Exception as e:
print(f"Error taking screenshot with Pillow: {e}")
return False
def take_region_screenshot_pyscreenshot(output_path: str, bbox: Optional[Tuple[int, int, int, int]] = None, backend: str = "auto") -> bool:
"""
Take a screenshot using pyscreenshot (multiple backends).
Args:
output_path: Path where the screenshot will be saved
bbox: Optional tuple of (left, top, right, bottom) to capture specific region
backend: Backend to use ('auto', 'mss', 'pyautogui', 'pillow', etc.)
Returns:
True if screenshot was taken successfully, False otherwise
"""
try:
import pyscreenshot as pys
# Take screenshot with specified backend
if backend != "auto":
screenshot = pys.grab(bbox=bbox, backend=backend)
else:
screenshot = pys.grab(bbox=bbox)
# Save the screenshot
screenshot.save(output_path)
return os.path.exists(output_path) and os.path.getsize(output_path) > 0
except ImportError:
print("pyscreenshot library not installed. Install with: pip install pyscreenshot")
return False
except Exception as e:
print(f"Error taking screenshot with pyscreenshot: {e}")
return False
def take_region_screenshot_interactive_pyautogui(output_path: str) -> bool:
"""
Take an interactive region screenshot using PyAutoGUI with user selection.
Args:
output_path: Path where the screenshot will be saved
Returns:
True if screenshot was taken successfully, False otherwise
"""
try:
import pyautogui
import tkinter as tk
from tkinter import messagebox
print("Click and drag to select a region...")
# Create a simple selection interface
root = tk.Tk()
root.withdraw() # Hide the main window
messagebox.showinfo("Screenshot Selection",
"Instructions:\n"
"1. Close this dialog\n"
"2. Move mouse to top-left corner of desired region\n"
"3. Press and hold left mouse button\n"
"4. Drag to bottom-right corner\n"
"5. Release mouse button")
# Get mouse position for region selection
print("Move mouse to TOP-LEFT corner and press ENTER...")
input()
start_x, start_y = pyautogui.position()
print(f"Top-left: ({start_x}, {start_y})")
print("Move mouse to BOTTOM-RIGHT corner and press ENTER...")
input()
end_x, end_y = pyautogui.position()
print(f"Bottom-right: ({end_x}, {end_y})")
# Calculate region
left = min(start_x, end_x)
top = min(start_y, end_y)
width = abs(end_x - start_x)
height = abs(end_y - start_y)
if width < 10 or height < 10:
print("Region too small!")
return False
# Take screenshot of selected region
screenshot = pyautogui.screenshot(region=(left, top, width, height))
screenshot.save(output_path)
root.destroy()
return os.path.exists(output_path) and os.path.getsize(output_path) > 0
except ImportError:
print("PyAutoGUI library not installed. Install with: pip install pyautogui")
return False
except Exception as e:
print(f"Error taking interactive screenshot: {e}")
return False
def take_region_screenshot_cross_platform(
output_path: str,
method: str = "auto",
region: Optional[Tuple[int, int, int, int]] = None,
**kwargs
) -> bool:
"""
Universal cross-platform screenshot function that tries different methods.
Args:
output_path: Path where the screenshot will be saved
method: Method to use ('auto', 'mss', 'pyautogui', 'pillow', 'pyscreenshot', 'macos', 'interactive')
region: Region to capture (format depends on method)
**kwargs: Additional arguments for specific methods
Returns:
True if screenshot was taken successfully, False otherwise
"""
if method == "auto":
# Try methods in order of preference (speed and reliability)
methods_to_try = ["mss", "pillow", "pyautogui", "pyscreenshot"]
if sys.platform == "darwin": # macOS
methods_to_try.insert(0, "macos") # Prefer native macOS method
for auto_method in methods_to_try:
if take_region_screenshot_cross_platform(output_path, auto_method, region, **kwargs):
return True
print("All screenshot methods failed!")
return False
elif method == "mss":
# MSS doesn't support region selection directly, so we crop afterwards
if region and len(region) == 4:
# Take full screenshot first, then crop
temp_path = output_path + ".temp.png"
if take_region_screenshot_mss(temp_path, kwargs.get('monitor_number', 0)):
try:
img = Image.open(temp_path)
if len(region) == 4:
# Convert PyAutoGUI format (left, top, width, height) to PIL format (left, top, right, bottom)
left, top, width, height = region
right = left + width
bottom = top + height
cropped = img.crop((left, top, right, bottom))
else:
cropped = img.crop(region)
cropped.save(output_path)
os.remove(temp_path)
return True
except Exception as e:
print(f"Error cropping MSS screenshot: {e}")
if os.path.exists(temp_path):
os.remove(temp_path)
return False
else:
return take_region_screenshot_mss(output_path, kwargs.get('monitor_number', 0))
elif method == "pyautogui":
if region and len(region) == 4:
return take_region_screenshot_pyautogui(output_path, region)
else:
return take_region_screenshot_pyautogui(output_path)
elif method == "pillow":
return take_region_screenshot_pillow(output_path, region)
elif method == "pyscreenshot":
return take_region_screenshot_pyscreenshot(output_path, region, kwargs.get('backend', 'auto'))
elif method == "macos":
return take_region_screenshot(output_path) # Original macOS function
elif method == "interactive":
return take_region_screenshot_interactive_pyautogui(output_path)
else:
print(f"Unknown screenshot method: {method}")
return False
def take_region_screenshot(output_path: str) -> bool:
"""
Take a region screenshot on macOS using the built-in screencapture command.
Generated
+201
View File
@@ -1182,6 +1182,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/a7/b276ff776533b423710a285c8168b52551cb2ab0855443131fdc7fd8c16f/easygui-0.98.3-py2.py3-none-any.whl", hash = "sha256:33498710c68b5376b459cd3fc48d1d1f33822139eb3ed01defbc0528326da3ba", size = 92655, upload-time = "2022-04-01T13:15:49.568Z" },
]
[[package]]
name = "easyprocess"
version = "1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/86/e5/1071ad7de469bf3baae5b04d6c8019876c819c2d428cc1b0f15b4b31fc89/EasyProcess-1.1.tar.gz", hash = "sha256:885898302a57aab948973e8b5d32a4229392b9fb2d986ab1d4ffd590e5ba90ec", size = 11829, upload-time = "2022-01-15T10:58:56.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/cf/27d1f4b3bae5e566f94fc716e048120128cf603d5163638d22bcd0fc92d8/EasyProcess-1.1-py3-none-any.whl", hash = "sha256:82eed523a0a5eb12a81fa4eacd9f342caeb3f900eb4b798740e6696ad07e63f9", size = 8660, upload-time = "2022-01-15T10:58:54.473Z" },
]
[[package]]
name = "ebcdic"
version = "1.1.1"
@@ -1275,6 +1284,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617, upload-time = "2025-01-16T06:31:23.526Z" },
]
[[package]]
name = "entrypoint2"
version = "1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/41/2b/513088059a4bfe89acd8ddb74f9350b399507a0fe6676c3346d54449a5b1/entrypoint2-1.1.tar.gz", hash = "sha256:fc0b7fe7b21acdab47a585ab9407ca7e5c4f96cb6888575db6b0ceb91f0e105a", size = 13393, upload-time = "2022-06-11T06:28:21.619Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/ee/84c8990b08efa0265bd10fc8781ef26e3157715bf0dfa47ee3c056b513d4/entrypoint2-1.1-py2.py3-none-any.whl", hash = "sha256:eeb8c327bdb65cdd1668c023a6b110b7e3d1a046fb05e043861ebd9264b3a257", size = 9864, upload-time = "2022-06-11T06:28:19.529Z" },
]
[[package]]
name = "et-xmlfile"
version = "2.0.0"
@@ -2277,6 +2295,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
]
[[package]]
name = "jeepney"
version = "0.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
@@ -2920,6 +2947,17 @@ s3 = [
{ name = "pyyaml" },
]
[[package]]
name = "mouseinfo"
version = "0.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyperclip" },
{ name = "python3-xlib", marker = "sys_platform == 'linux'" },
{ name = "rubicon-objc", marker = "sys_platform == 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/fa/b2ba8229b9381e8f6381c1dcae6f4159a7f72349e414ed19cfbbd1817173/MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7", size = 10850, upload-time = "2020-03-27T21:20:10.136Z" }
[[package]]
name = "mpmath"
version = "1.3.0"
@@ -2969,6 +3007,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/03/54/7f6d3d9acad083dae8c22d9ab483b657359a1bf56fee1d7af88794677707/msoffcrypto_tool-5.4.2-py3-none-any.whl", hash = "sha256:274fe2181702d1e5a107ec1b68a4c9fea997a44972ae1cc9ae0cb4f6a50fef0e", size = 48713, upload-time = "2024-08-08T15:50:27.093Z" },
]
[[package]]
name = "mss"
version = "10.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/2c/6a50c69793918502b4391729bd5741964c9f8ccc98c8482d4a680384923b/mss-10.0.0.tar.gz", hash = "sha256:d903e0d51262bf0f8782841cf16eaa6d7e3e1f12eae35ab41c2e318837c6637f", size = 83127, upload-time = "2024-11-14T09:37:07.848Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/29/aa/b897ae9e1c1616e4df9fb319637ef6a9d07cca6d46de6b59c80209f006a4/mss-10.0.0-py3-none-any.whl", hash = "sha256:82cf6460a53d09e79b7b6d871163c982e6c7e9649c426e7b7591b74956d5cb64", size = 24050, upload-time = "2024-11-14T09:37:06.139Z" },
]
[[package]]
name = "multidict"
version = "6.6.3"
@@ -4492,6 +4539,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/89/bc88a6711935ba795a679ea6ebee07e128050d6382eaa35a0a47c8032bdc/pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", size = 181537, upload-time = "2024-09-11T16:02:10.336Z" },
]
[[package]]
name = "pyautogui"
version = "0.9.54"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mouseinfo" },
{ name = "pygetwindow" },
{ name = "pymsgbox" },
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
{ name = "pyscreeze" },
{ name = "python3-xlib", marker = "sys_platform == 'linux'" },
{ name = "pytweening" },
]
sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de74b6cf4cbcdcaf8fd25857e6c3f205ce4b1794b27814/PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2", size = 61236, upload-time = "2023-05-24T20:11:32.972Z" }
[[package]]
name = "pyclipper"
version = "1.3.0.post6"
@@ -4629,6 +4692,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/0d/95993c08c721ec68892547f2117e8f9dfbcef2ca71e098533541b4a54d5f/pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990", size = 14831, upload-time = "2024-08-30T19:40:42.132Z" },
]
[[package]]
name = "pygetwindow"
version = "0.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyrect" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e1/70/c7a4f46dbf06048c6d57d9489b8e0f9c4c3d36b7479f03c5ca97eaa2541d/PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688", size = 9699, upload-time = "2020-10-04T02:12:50.806Z" }
[[package]]
name = "pygments"
version = "2.19.2"
@@ -4730,6 +4802,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/9c/00301a6df26f0f8d5c5955192892241e803742e7c3da8c2c222efabc0df6/pymongo-4.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c38168263ed94a250fc5cf9c6d33adea8ab11c9178994da1c3481c2a49d235f8", size = 1011057, upload-time = "2025-06-16T18:16:07.917Z" },
]
[[package]]
name = "pymsgbox"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12a663f2aeb6c8b765d3bd592e66eaaac445f547bb875/PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff", size = 18829, upload-time = "2020-10-11T01:51:43.227Z" }
[[package]]
name = "pymysql"
version = "1.1.1"
@@ -4739,6 +4817,55 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" },
]
[[package]]
name = "pyobjc-core"
version = "11.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602, upload-time = "2025-06-14T20:56:34.189Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/a7/55afc166d89e3fcd87966f48f8bca3305a3a2d7c62100715b9ffa7153a90/pyobjc_core-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ec36680b5c14e2f73d432b03ba7c1457dc6ca70fa59fd7daea1073f2b4157d33", size = 671075, upload-time = "2025-06-14T20:44:46.594Z" },
{ url = "https://files.pythonhosted.org/packages/c0/09/e83228e878e73bf756749939f906a872da54488f18d75658afa7f1abbab1/pyobjc_core-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:765b97dea6b87ec4612b3212258024d8496ea23517c95a1c5f0735f96b7fd529", size = 677985, upload-time = "2025-06-14T20:44:48.375Z" },
{ url = "https://files.pythonhosted.org/packages/c5/24/12e4e2dae5f85fd0c0b696404ed3374ea6ca398e7db886d4f1322eb30799/pyobjc_core-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18986f83998fbd5d3f56d8a8428b2f3e0754fd15cef3ef786ca0d29619024f2c", size = 676431, upload-time = "2025-06-14T20:44:49.908Z" },
{ url = "https://files.pythonhosted.org/packages/f7/79/031492497624de4c728f1857181b06ce8c56444db4d49418fa459cba217c/pyobjc_core-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8849e78cfe6595c4911fbba29683decfb0bf57a350aed8a43316976ba6f659d2", size = 719330, upload-time = "2025-06-14T20:44:51.621Z" },
{ url = "https://files.pythonhosted.org/packages/ed/7d/6169f16a0c7ec15b9381f8bf33872baf912de2ef68d96c798ca4c6ee641f/pyobjc_core-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8cb9ed17a8d84a312a6e8b665dd22393d48336ea1d8277e7ad20c19a38edf731", size = 667203, upload-time = "2025-06-14T20:44:53.262Z" },
{ url = "https://files.pythonhosted.org/packages/49/0f/f5ab2b0e57430a3bec9a62b6153c0e79c05a30d77b564efdb9f9446eeac5/pyobjc_core-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:f2455683e807f8541f0d83fbba0f5d9a46128ab0d5cc83ea208f0bec759b7f96", size = 708807, upload-time = "2025-06-14T20:44:54.851Z" },
]
[[package]]
name = "pyobjc-framework-cocoa"
version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/43/6841046aa4e257b6276cd23e53cacedfb842ecaf3386bb360fa9cc319aa1/pyobjc_framework_cocoa-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b9a9b8ba07f5bf84866399e3de2aa311ed1c34d5d2788a995bdbe82cc36cfa0", size = 388177, upload-time = "2025-06-14T20:46:51.454Z" },
{ url = "https://files.pythonhosted.org/packages/68/da/41c0f7edc92ead461cced7e67813e27fa17da3c5da428afdb4086c69d7ba/pyobjc_framework_cocoa-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806de56f06dfba8f301a244cce289d54877c36b4b19818e3b53150eb7c2424d0", size = 388983, upload-time = "2025-06-14T20:46:52.591Z" },
{ url = "https://files.pythonhosted.org/packages/4e/0b/a01477cde2a040f97e226f3e15e5ffd1268fcb6d1d664885a95ba592eca9/pyobjc_framework_cocoa-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54e93e1d9b0fc41c032582a6f0834befe1d418d73893968f3f450281b11603da", size = 389049, upload-time = "2025-06-14T20:46:53.757Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e6/64cf2661f6ab7c124d0486ec6d1d01a9bb2838a0d2a46006457d8c5e6845/pyobjc_framework_cocoa-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fd5245ee1997d93e78b72703be1289d75d88ff6490af94462b564892e9266350", size = 393110, upload-time = "2025-06-14T20:46:54.894Z" },
{ url = "https://files.pythonhosted.org/packages/33/87/01e35c5a3c5bbdc93d5925366421e10835fcd7b23347b6c267df1b16d0b3/pyobjc_framework_cocoa-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:aede53a1afc5433e1e7d66568cc52acceeb171b0a6005407a42e8e82580b4fc0", size = 392644, upload-time = "2025-06-14T20:46:56.503Z" },
{ url = "https://files.pythonhosted.org/packages/c1/7c/54afe9ffee547c41e1161691e72067a37ed27466ac71c089bfdcd07ca70d/pyobjc_framework_cocoa-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:1b5de4e1757bb65689d6dc1f8d8717de9ec8587eb0c4831c134f13aba29f9b71", size = 396742, upload-time = "2025-06-14T20:46:57.64Z" },
]
[[package]]
name = "pyobjc-framework-quartz"
version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/cb/38172fdb350b3f47e18d87c5760e50f4efbb4da6308182b5e1310ff0cde4/pyobjc_framework_quartz-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d501fe95ef15d8acf587cb7dc4ab4be3c5a84e2252017da8dbb7df1bbe7a72a", size = 215565, upload-time = "2025-06-14T20:53:35.262Z" },
{ url = "https://files.pythonhosted.org/packages/9b/37/ee6e0bdd31b3b277fec00e5ee84d30eb1b5b8b0e025095e24ddc561697d0/pyobjc_framework_quartz-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ac806067541917d6119b98d90390a6944e7d9bd737f5c0a79884202327c9204", size = 216410, upload-time = "2025-06-14T20:53:36.346Z" },
{ url = "https://files.pythonhosted.org/packages/bd/27/4f4fc0e6a0652318c2844608dd7c41e49ba6006ee5fb60c7ae417c338357/pyobjc_framework_quartz-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43a1138280571bbf44df27a7eef519184b5c4183a588598ebaaeb887b9e73e76", size = 216816, upload-time = "2025-06-14T20:53:37.358Z" },
{ url = "https://files.pythonhosted.org/packages/b8/8a/1d15e42496bef31246f7401aad1ebf0f9e11566ce0de41c18431715aafbc/pyobjc_framework_quartz-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b23d81c30c564adf6336e00b357f355b35aad10075dd7e837cfd52a9912863e5", size = 221941, upload-time = "2025-06-14T20:53:38.34Z" },
{ url = "https://files.pythonhosted.org/packages/32/a8/a3f84d06e567efc12c104799c7fd015f9bea272a75f799eda8b79e8163c6/pyobjc_framework_quartz-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:07cbda78b4a8fcf3a2d96e047a2ff01f44e3e1820f46f0f4b3b6d77ff6ece07c", size = 221312, upload-time = "2025-06-14T20:53:39.435Z" },
{ url = "https://files.pythonhosted.org/packages/76/ef/8c08d4f255bb3efe8806609d1f0b1ddd29684ab0f9ffb5e26d3ad7957b29/pyobjc_framework_quartz-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:39d02a3df4b5e3eee1e0da0fb150259476910d2a9aa638ab94153c24317a9561", size = 226353, upload-time = "2025-06-14T20:53:40.655Z" },
]
[[package]]
name = "pypandoc"
version = "1.15"
@@ -4816,6 +4943,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
]
[[package]]
name = "pyrect"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f7be0c281cdacdcd939fe13d1deb331fc5ed1a6b3a98/PyRect-0.2.0.tar.gz", hash = "sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78", size = 17219, upload-time = "2022-03-16T04:45:52.36Z" }
[[package]]
name = "pyscreenshot"
version = "3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "easyprocess" },
{ name = "entrypoint2" },
{ name = "jeepney", marker = "sys_platform == 'linux'" },
{ name = "mss" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/bf/a051bea0ad0fb9877684d0f8b87e0e55970b53b117db80c5f729d6dda762/pyscreenshot-3.1.tar.gz", hash = "sha256:8c0e93f0aef66a6bfe55a86abfced6bd396ae4b4f6cc1e36f04a28ad2625594d", size = 29238, upload-time = "2023-03-12T06:34:24.652Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/fb/acc0b5a7251682cd11c67c40a1532ffb1dda71626e9ad7e7ba1e55cc3ae2/pyscreenshot-3.1-py3-none-any.whl", hash = "sha256:73d406d41a0977125bdfd2f6488f0caf1394e84d1d4c1065d5e8b1400b307096", size = 28796, upload-time = "2023-03-12T06:34:22.549Z" },
]
[[package]]
name = "pyscreeze"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826, upload-time = "2024-08-20T23:03:07.291Z" }
[[package]]
name = "pytest"
version = "8.3.5"
@@ -5022,6 +5179,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" },
]
[[package]]
name = "python3-xlib"
version = "0.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828, upload-time = "2014-05-31T12:28:59.603Z" }
[[package]]
name = "pytube"
version = "15.0.0"
@@ -5031,6 +5194,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/64/bcf8632ed2b7a36bbf84a0544885ffa1d0b4bcf25cc0903dba66ec5fdad9/pytube-15.0.0-py3-none-any.whl", hash = "sha256:07b9904749e213485780d7eb606e5e5b8e4341aa4dccf699160876da00e12d78", size = 57594, upload-time = "2023-05-07T19:38:59.191Z" },
]
[[package]]
name = "pytweening"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/79/0c/c16bc93ac2755bac0066a8ecbd2a2931a1735a6fffd99a2b9681c7e83e90/pytweening-1.2.0.tar.gz", hash = "sha256:243318b7736698066c5f362ec5c2b6434ecf4297c3c8e7caa8abfe6af4cac71b", size = 171241, upload-time = "2024-02-20T03:37:56.809Z" }
[[package]]
name = "pytz"
version = "2025.2"
@@ -5448,6 +5617,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b6/dd/641e9cf68d4242aaf7ce9653498009d8925080b6664993988bd50468932a/rtfde-0.1.2.1-py3-none-any.whl", hash = "sha256:c44dfa923a435c54cdbdd0e0f5352a4075542af317af061f82f2d4f032271645", size = 36260, upload-time = "2025-05-04T18:48:01.119Z" },
]
[[package]]
name = "rubicon-objc"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/83/e57741dcf862a2581d53eccf8b11749c97f73d9754bbc538fb6c7b527da3/rubicon_objc-0.5.1.tar.gz", hash = "sha256:90bee9fc1de4515e17615e15648989b88bb8d4d2ffc8c7c52748272cd7f30a66", size = 174639, upload-time = "2025-06-03T06:33:50.822Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/0a/e451c3dbda38dd6abab1fd16c3b35623fc0635dffcbbf97f1acc55a58508/rubicon_objc-0.5.1-py3-none-any.whl", hash = "sha256:17092756241b8370231cfaad45ad6e8ce99534987f2acbc944d65df5bdf8f6cd", size = 63323, upload-time = "2025-06-03T06:33:48.863Z" },
]
[[package]]
name = "s3transfer"
version = "0.10.4"
@@ -5943,6 +6121,22 @@ dependencies = [
{ name = "typer" },
]
[package.optional-dependencies]
screenshot-all = [
{ name = "mss" },
{ name = "pyautogui" },
{ name = "pyscreenshot" },
]
screenshot-fast = [
{ name = "mss" },
]
screenshot-full = [
{ name = "pyautogui" },
]
screenshot-multi = [
{ name = "pyscreenshot" },
]
[package.dev-dependencies]
dev = [
{ name = "open-webui", version = "0.6.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
@@ -5951,12 +6145,19 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "mss", marker = "extra == 'screenshot-all'", specifier = ">=7.0.0" },
{ name = "mss", marker = "extra == 'screenshot-fast'", specifier = ">=7.0.0" },
{ name = "pillow", specifier = ">=11.1.0" },
{ name = "pyautogui", marker = "extra == 'screenshot-all'", specifier = ">=0.9.54" },
{ name = "pyautogui", marker = "extra == 'screenshot-full'", specifier = ">=0.9.54" },
{ name = "pyperclip", specifier = ">=1.9.0" },
{ name = "pyscreenshot", marker = "extra == 'screenshot-all'", specifier = ">=3.1" },
{ name = "pyscreenshot", marker = "extra == 'screenshot-multi'", specifier = ">=3.1" },
{ name = "python-doctr", specifier = ">=0.8.0" },
{ name = "rich", specifier = ">=13.0.0" },
{ name = "typer", specifier = ">=0.12.0" },
]
provides-extras = ["screenshot-fast", "screenshot-full", "screenshot-multi", "screenshot-all"]
[package.metadata.requires-dev]
dev = [{ name = "open-webui", specifier = ">=0.6.5" }]