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 commande —
pip install textsnapet 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
maindé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.sha256et 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--hashpar roue avecpip-compile --generate-hashespour 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
- 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).
- 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.
- 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.
- Formatage. Markdown natif par défaut ;
--plaintextle 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 spatialeonnx/decoder_q4.onnx— décodeur autorégressifonnx/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-tokenslimite 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.