textsnap : OCR hors ligne à partir d'images, captures d'écran et pages web sur CPU

textsnap est un outil Python en une commande qui extrait du texte brut à partir d'images, captures d'écran et pages web en utilisant un modèle ONNX quantifié — sans GPU ni cloud requis.

Qu'est-ce que textsnap ?

textsnap est un outil OCR léger et hors ligne qui convertit des images, captures d'écran et pages web en texte brut — le tout sur votre CPU, sans dépendance au cloud. Il utilise un modèle de vision-langage PaddleOCR-VL-1.5 quantifié de 0,9B (q4 ONNX) pour analyser des pages entières sur un ordinateur portable standard. Pas de CUDA, pas d'astuces réservées aux séries M — juste de bons vieux cœurs.

Pourquoi textsnap ?

La plupart des outils OCR nécessitent un GPU, envoient vos données dans le cloud ou sont fastidieux à configurer. textsnap résout ces trois problèmes :

  • Fonctionne sur CPU — Le modèle est quantifié en q4 ONNX, ce qui le rend efficace sur n'importe quel ordinateur portable moderne.
  • Entièrement hors ligne — Après la première exécution (téléchargement d'environ 890 Mo), tout reste local. Pas de clés API, pas de quotas, pas de données quittant votre machine.
  • Une seule commandepip install textsnap et vous êtes prêt. L'outil est un module Python unique.
  • Types d'entrée multiples — Presse-papiers, fichiers image locaux, URLs d'images directes ou URLs de pages web complètes.
  • Sortie Markdown ou texte brut — Préserve les tableaux, titres et structure par défaut, ou aplatit en texte brut avec --plaintext.

Démarrage rapide

# Installation
pip install textsnap

# Capturez quelque chose
textsnap screenshot.png
textsnap https://example.com/article --plaintext
textsnap photo.jpg -o ~/notes/receipt.txt

La première exécution télécharge le modèle (~890 Mo). Chaque exécution suivante est hors ligne.

Ce qu'il gère

Source Exemple
Presse-papiers textsnap (sans argument)
Fichier image local textsnap chemin/vers/img.png
URL d'image directe textsnap https://example.com/x.png
URL de page web textsnap https://example.com/article

Les fichiers locaux couvrent tout ce que Pillow peut décoder : .png, .jpg, .jpeg, .webp, .bmp, .gif, .tiff, et plus. Pour les URLs de pages web, textsnap utilise readability pour isoler le contenu principal, puis sélectionne l'image la plus importante sur la page et applique l'OCR dessus.

Installation

pip install textsnap

Ceci installe deux commandes équivalentes dans votre PATH : textsnap (canonique) et ocr (alias).

Pour installer à partir d'une copie locale du code source :

pip install .

Pour une installation reproductible avec des versions exactes des dépendances :

pip install -r requirements-lock.txt
pip install .

Note sur le presse-papiers : La lecture d'images depuis le presse-papiers repose sur ImageGrab de Pillow. Sous Linux, vous pourriez avoir besoin d'installer xclip ou wl-clipboard. macOS et Windows fonctionnent directement.

Utilisation

# Presse-papiers (sans argument)
textsnap

# Fichier image local
textsnap chemin/vers/screenshot.png

# URL d'image directe
textsnap "https://example.com/diagram.png"

# Page web — OCR sur l'image la plus importante de la page
textsnap "https://example.com/article"

# Aplatir le markdown du modèle en texte brut
textsnap input.png --plaintext

# Chemin de sortie personnalisé
textsnap input.png -o ./out/extracted.txt

# Augmenter la limite de tokens pour les pages très denses
textsnap dense-page.png --max-tokens 4096

# Utiliser un répertoire de modèle local au lieu de télécharger
textsnap input.png --model-dir ~/models/paddleocr-vl

Sortie

La sortie est en texte brut, UTF-8. L'emplacement par défaut est ./textsnaps/ (créé s'il manque) dans le répertoire de travail courant ; remplacez-le avec -o. Le nom du fichier est dérivé du nom de base du fichier image (par exemple, reçu_ocr.txt), ou du slug de la page web pour les entrées URL.

textsnap est silencieux par défaut, à la Unix : la seule chose imprimée sur stdout est le chemin du fichier écrit, ce qui permet une composition propre :

OUT=$(textsnap receipt.png)  # capture le chemin
textsnap receipt.png | xargs cat  # affiche le texte reconnu

Passez -v pour envoyer les diagnostics de progression (type d'entrée, taille de l'image, vitesse de décodage, nombre de tokens) sur stderr ; stdout reste uniquement le chemin dans les deux cas.

La sortie par défaut du fichier est le markdown natif du modèle — il préserve les tableaux, titres et structure du document :

# Rapport Trimestriel

| Région | Revenu |
| ------ | ------- |
| EMEA | 1,2 M$ |
| APAC | 0,9 M$ |

Avec --plaintext, le markdown est aplati en texte brut :

Rapport Trimestriel

Région Revenu
EMEA 1,2 M$
APAC 0,9 M$

Options

Option Description
-o, --output Chemin du fichier .txt de sortie. Par défaut : ./textsnaps/<nom>_ocr.txt.
-v, --verbose Affiche les diagnostics de progression sur stderr. Désactivé par défaut.
--plaintext Aplatit le markdown natif du modèle en texte brut.
--model-dir Utilise les fichiers ONNX/config de ce répertoire au lieu de télécharger.
--max-tokens Limite les tokens générés. Par défaut 2048. Augmentez pour les pages très denses.
--no-verify Ignore la vérification SHA-256 des fichiers de modèle téléchargés (déconseillé).
--generate-checksums Télécharge les fichiers de modèle épinglés, écrit un nouveau manifeste et quitte.

Sécurité

textsnap télécharge automatiquement environ 890 Mo de poids de modèle depuis le Hub Hugging Face lors de la première exécution, il traite donc ces fichiers comme non fiables jusqu'à preuve du contraire :

  • Révision du modèle épinglée. Les téléchargements sont épinglés à une révision spécifique du dépôt, donc un main déplacé ou re-tagué ne peut pas échanger silencieusement les poids.
  • Vérification SHA-256. Chaque fichier téléchargé est haché et vérifié par rapport à des condensés connus avant d'être chargé. Une discordance interrompt l'exécution avec une erreur claire plutôt que d'exécuter des poids non vérifiés. Les condensés résident dans model_checksums.sha256 et sont également intégrés dans le script comme solution de repli, donc la vérification fonctionne que vous installiez à partir des sources ou d'une roue.
  • Dépendances épinglées. requirements-lock.txt épingle les versions exactes des dépendances pour des installations reproductibles ; le fichier documente comment ajouter des entrées --hash par roue avec pip-compile --generate-hashes pour un verrouillage complet de la chaîne d'approvisionnement.

Régénérez le manifeste des sommes de contrôle après un changement délibéré de révision du modèle :

textsnap --generate-checksums

Pour contourner la vérification (pour une expérimentation locale avec un modèle modifié), passez --no-verify.

Comment ça fonctionne

  1. Chargement. Depuis le presse-papiers, un fichier local, une URL d'image directe ou — pour une URL de page web — l'image la plus importante dans le contenu principal de la page (readability + une heuristique de proéminence).
  2. Prétraitement. L'image est limitée à 640 px sur son côté le plus long, puis traitée par le redimensionnement intelligent et le découpage en patchs de style Qwen2-VL de PaddleOCR-VL, produisant le tenseur de valeurs de pixels et la grille attendus par l'encodeur visuel.
  3. Reconnaissance. Trois composants ONNX s'exécutent sur CPU : un encodeur visuel (q4), un modèle d'incorporation de tokens (fp32) et un décodeur autorégressif (q4) avec un cache KV câblé. Décodage glouton, avec une protection contre les répétitions qui arrête tôt les boucles incontrôlées.
  4. Formatage. Markdown natif par défaut ; --plaintext le réduit en texte brut.

Aucune image n'est envoyée nulle part. Aucun état n'est conservé entre les exécutions, à l'exception du modèle mis en cache.

Modèle et cache

Les composants ONNX de PaddleOCR-VL-1.5 sont téléchargés lors de la première exécution dans ~/.cache/textsnap/ :

  • onnx/vision_encoder_q4.onnx — encodeur visuel + projecteur de fusion spatiale
  • onnx/decoder_q4.onnx — décodeur autorégressif
  • onnx/embedding.onnx — incorporations de tokens (fp32 ; aucune variante q4 n'existe)
  • tokenizer.json, config.json

Ensemble environ 890 Mo. Pour utiliser votre propre copie, pointez --model-dir vers un répertoire contenant les mêmes fichiers onnx/ ainsi que tokenizer.json et config.json.

Notes et limites

  • La première exécution est la plus lente — elle télécharge environ 890 Mo. Ensuite, textsnap est entièrement hors ligne.
  • Le décodage CPU est séquentiel. Les documents denses sur une page entière prennent plus de temps qu'une courte capture d'écran. textsnap limite le nombre de threads à vos cœurs physiques et affiche un débit en tokens/s en direct pour qu'une exécution lente soit visiblement active, pas bloquée.
  • --max-tokens limite la sortie. Les pages très denses peuvent atteindre la limite par défaut de 2048 tokens et être tronquées ; augmentez-la si la fin d'une page manque.
  • Les entrées de page web OCR une seule image — la plus importante dans le contenu principal, pas la page entière rendue.
  • Le décodage glouton peut parfois boucler sur des mises en page répétitives ; un garde-fou intégré détecte et coupe ces boucles.

Licence

MIT pour ce projet. Le modèle est PaddleOCR-VL-1.5, distribué sous Apache-2.0 par PaddlePaddle ; textsnap récupère l'export ONNX depuis onnx-community/PaddleOCR-VL-1.5-ONNX. Propulsé par onnxruntime et huggingface_hub.

Source

kouhxp/textsnap : Capturez n'importe quelle image, capture d'écran ou page web en texte brut. Pas de GPU. Pas de cloud. Une commande.