diff --git a/README.md b/README.md index 2b129f7..1fd0f72 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 358f9c6..2282979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/tooling/cli.py b/src/tooling/cli.py index 832e438..b4d1ab7 100644 --- a/src/tooling/cli.py +++ b/src/tooling/cli.py @@ -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: diff --git a/src/tooling/ocr_screenshot.py b/src/tooling/ocr_screenshot.py index 8cd3de4..50541d0 100644 --- a/src/tooling/ocr_screenshot.py +++ b/src/tooling/ocr_screenshot.py @@ -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. diff --git a/uv.lock b/uv.lock index 6cb74d6..ff3ecac 100644 --- a/uv.lock +++ b/uv.lock @@ -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" }]