Skip to main content

Extract text from office documents and images (PDF, images, DOCX, PPTX, XLSX, and legacy Office via LibreOffice).

Project description

OCR Extractor

Extract text from office documents and images. PDF and image files are processed via OCR (Tesseract + OpenCV). Modern Office files (.docx/.pptx/.xlsx) are read directly. Legacy Office files (.doc/.xls/.ppt) are converted via LibreOffice headless.

ocr-extractor is distributed as an installable Python package. You can use it as a dependency in other projects (pip install ocr-extractor), run it from the command line (ocr-extractor file.pdf), or import it as a library (from ocr_extractor import read_document).

📋 Table of contents

✨ Features

  • Multi-format support: PDF, images (PNG/JPG/TIFF/BMP/WEBP/HEIC), modern Office (DOCX/PPTX/XLSX), and legacy Office (DOC/XLS/PPT via LibreOffice) — all through a single read_document entry point.
  • PDF → Image conversion: each page of the PDF is rendered to a high-resolution image (300 DPI by default) using pdf2image.
  • Image preprocessing: grayscale conversion and noise removal with OpenCV (fastNlMeansDenoising) to improve OCR accuracy.
  • Multi-page TIFF support: each frame of a multi-page TIFF becomes its own === PAGE N === block, useful for scanner output.
  • Tesseract OCR: text extraction via pytesseract with a configurable language (eng by default).
  • Text cleaning: filtering of short lines, disallowed characters, and symbol-only lines (applied only to OCR-based formats).
  • Page markers: PDF, TIFF, PPTX, and XLSX output is wrapped between === PAGE N === / === END PAGE N === markers so each page/slide/sheet is identifiable.
  • Public API + CLI: installable as a dependency, invokable as the ocr-extractor command, or importable as a Python library.

🛠 Requirements

  • Python 3.8+
  • Tesseract OCR installed on the system (not installed via pip):
    # Debian / Ubuntu
    sudo apt-get install tesseract-ocr
    
    # Fedora
    sudo dnf install tesseract
    
    # macOS (Homebrew)
    brew install tesseract
    
  • Poppler (required by pdf2image):
    # Debian / Ubuntu
    sudo apt-get install poppler-utils
    
    # Fedora
    sudo dnf install poppler-utils
    
    # macOS
    brew install poppler
    
  • LibreOffice — required only for legacy Office files (.doc, .xls, .ppt):
    # Debian / Ubuntu
    sudo apt-get install libreoffice
    
    # Fedora
    sudo dnf install libreoffice
    
    # macOS
    brew install --cask libreoffice
    
  • Tesseract language packs (optional). The default is eng; install others as needed, for example:
    sudo apt-get install tesseract-ocr-spa   # Spanish
    sudo apt-get install tesseract-ocr-deu   # German
    
  • pillow-heif (optional) — required for HEIC/HEIF images from iPhones:
    pip install ocr-extractor[heic]
    

📦 Installation

As a dependency in another project

Once published to PyPI (or your private index):

pip install ocr-extractor

This installs the library and puts the ocr-extractor command on your PATH. Python dependencies are resolved automatically (Tesseract, Poppler, and LibreOffice remain system-level requirements).

To pin or constrain the version in pyproject.toml / requirements.txt:

# requirements.txt
ocr-extractor>=0.2.0

For HEIC/HEIF support (iPhone photos), install the [heic] extra:

pip install ocr-extractor[heic]

Editable install from the repository

Recommended during development. Clone the repo and, inside the virtual environment where you want to use it:

git clone https://github.com/roilanrodriguez55/ocr-extractor.git
cd ocr-extractor
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

pip install -e . registers the package in editable mode: any change in ocr_extractor/ is picked up without reinstalling. The dev extra adds pytest and build for tests and for producing sdist/wheel artifacts.

Dependencies only (without cloning)

If you don't want to install this project and only need the API from your own code, install the Python dependencies directly:

pip install pytesseract pdf2image opencv-python-headless numpy Pillow \
            python-docx python-pptx openpyxl

💡 If your platform supports it, you can also use opencv-python instead of opencv-python-headless (the headless variant avoids GUI dependencies).

Usage

There are three ways to invoke the OCR, in order of recommendation:

From the CLI

After pip install ocr-extractor (or pip install -e .):

# Basic usage: writes texto_limpio.txt next to the input
ocr-extractor file.pdf
ocr-extractor scan.tiff                  # multi-page TIFF
ocr-extractor report.docx                # no OCR — direct text extraction
ocr-extractor presentation.pptx          # one marker block per slide
ocr-extractor spreadsheet.xlsx           # one marker block per sheet

# Customize output, resolution, and language (OCR formats only)
ocr-extractor file.pdf -o output.txt --dpi 300 --lang spa

# Quiet mode (no progress messages)
ocr-extractor file.pdf -q

# Help and version
ocr-extractor --help
ocr-extractor --version

Available arguments:

Argument Description Default
input Path to the input file (positional, required)
-o, --output Path to the output text file texto_limpio.txt
--dpi Rendering resolution in DPI (OCR formats only) 300
--lang Tesseract language code (eng, spa, …) eng
-q, --quiet Suppress progress messages off
--version Print version and exit

As a library (API)

from ocr_extractor import read_document

# Format is detected by extension. Same call works for any supported format.
text = read_document("file.pdf", dpi=300, lang="eng")
text = read_document("scan.tiff")
text = read_document("report.docx")
text = read_document("presentation.pptx")
text = read_document("spreadsheet.xlsx")
text = read_document("legacy.doc")   # requires LibreOffice

# You can also use the intermediate steps directly
from ocr_extractor import preprocess_image, clean_text
from pdf2image import convert_from_path

pages = convert_from_path("file.pdf", dpi=300)
gray = preprocess_image(pages[0])  # numpy.ndarray ready for OCR

Functions exposed from ocr_extractor:

  • read_document(path, *, dpi=300, lang="eng", verbose=True) -> str
  • preprocess_image(image_pil) -> numpy.ndarray
  • clean_line(line) -> str | None
  • clean_text(text) -> str
  • read_pdf(pdf_path, dpi=300, lang="eng", verbose=True) -> str (deprecated, will be removed in the next major release)
  • SUPPORTED_FORMATS — tuple of all supported extensions
  • __version__ — string, e.g. "0.2.0"

Backward-compatible app.py wrapper

app.py is kept for backward compatibility: instead of duplicating logic, it now delegates to the package. It still works as before:

python app.py file.pdf -o output.txt

Internally it calls ocr_extractor.cli.main, so it accepts the same arguments as the CLI (run ocr-extractor --help to see them all).

📁 Project structure

ocr-extractor/
├── pyproject.toml              # Metadata, deps, entry point (built via hatchling)
├── MANIFEST.in                 # Includes README.md in the sdist
├── .gitignore                  # Standard Python ignores
├── README.md                   # This file
├── SUPPORTED_FORMATS.md        # Format inventory with priorities
├── app.py                      # Thin wrapper that delegates to ocr_extractor.cli
└── ocr_extractor/              # Python package (what gets distributed)
    ├── __init__.py             # Public API + __version__ + SUPPORTED_FORMATS
    ├── core.py                 # preprocess_image, clean_line, clean_text, deprecated read_pdf
    ├── dispatcher.py           # read_document (format detection + routing)
    ├── cli.py                  # argparse + main() (CLI entry point)
    └── readers/                # One module per format family
        ├── __init__.py         # EXTENSION_READERS map + get_reader_name
        ├── pdf.py              # OCR via pdf2image + Tesseract
        ├── images.py           # OCR for PNG/JPG/BMP/WEBP/HEIC (single page) and TIFF (multi-page)
        ├── docx.py             # python-docx: paragraphs + tables
        ├── pptx.py             # python-pptx: shapes + tables
        ├── xlsx.py             # openpyxl: worksheets as pages
        └── legacy.py           # .doc/.xls/.ppt → LibreOffice → modern reader

🔍 Code description

ocr_extractor.dispatcher.read_document(path, *, dpi=300, lang="eng", verbose=True)

Format-agnostic entry point. Detects the file extension, picks the right reader from ocr_extractor.readers.EXTENSION_READERS, and returns its output. Raises FileNotFoundError for missing files and ValueError for unsupported extensions.

ocr_extractor.core.preprocess_image(image_pil)

Converts a PIL image to an OpenCV array, turns it grayscale, and applies a denoising filter (cv2.fastNlMeansDenoising) with the parameters h=10, templateWindowSize=7, and searchWindowSize=21. Returns the grayscale image ready for OCR.

ocr_extractor.core.clean_line(line)

Cleans a single text line:

  • Discards lines shorter than 2 characters.
  • Replaces any character not in a-zA-Z0-9 '-.!? with a space (note: the comma is not allowed — see "Customization" below).
  • Collapses multiple spaces into one.
  • Discards lines that contain only symbols (()[]{}_-=+*#@^.,:;<>/\|~).

ocr_extractor.core.clean_text(text)

Applies clean_line to every line of the text and keeps only the lines that pass the filter. Returns the cleaned text joined with newlines.

ocr_extractor.readers.pdf.read_pdf_pages(pdf_path, dpi=300, lang="eng", verbose=True)

Iterates over every page of the PDF:

  1. Renders each page to an image with convert_from_path(pdf_path, dpi=dpi).
  2. Preprocesses the image with preprocess_image.
  3. Extracts the text with pytesseract.image_to_string(..., lang=lang).
  4. Cleans the text with clean_text.
  5. Wraps the result between the markers === PAGE N ====== END PAGE N ===.

ocr_extractor.readers.images.read_image(path, *, dpi, lang, verbose)

Opens a single-page image (PNG, JPG, BMP, WEBP, HEIC) with Pillow, runs it through preprocess_image and pytesseract.image_to_string, and returns the cleaned text without page markers. HEIC requires pillow-heif (install via pip install ocr-extractor[heic]).

ocr_extractor.readers.images.read_tiff(path, *, dpi, lang, verbose)

Iterates over every frame of a multi-page TIFF. Each frame is OCR'd and wrapped in === PAGE N === / === END PAGE N === markers, just like the PDF reader.

ocr_extractor.readers.docx.read_docx(path, *, dpi, lang, verbose)

Walks the document body in order, emitting one line per non-empty paragraph and one line per table row (cells joined with |). DOCX files are not paginated, so no page markers are emitted.

ocr_extractor.readers.pptx.read_pptx(path, *, dpi, lang, verbose)

Iterates over slides, extracting text from every shape that has a text_frame and from tables. Each slide becomes one === PAGE N === marker block.

ocr_extractor.readers.xlsx.read_xlsx(path, *, dpi, lang, verbose)

Opens the workbook with openpyxl (read_only=True, data_only=True), iterates every worksheet, and emits one === PAGE <sheet name> === block per sheet. Rows become | col1 | col2 | col3.

ocr_extractor.readers.legacy.read_legacy_office(path, *, dpi, lang, verbose)

Shells out to soffice --headless --convert-to <modern> to convert .doc/.xls/.ppt to .docx/.xlsx/.pptx, then delegates to the corresponding modern reader. Raises RuntimeError with installation instructions if LibreOffice is not installed on the system.

ocr_extractor.cli.main(argv=None)

Entry point registered in pyproject.toml as ocr-extractor = "ocr_extractor.cli:main". Parses arguments with argparse, validates that the input file exists, calls read_document, and writes the result to the output file. All errors are caught at the top level; the process exits with 0 on success, 1 on error.

app.py (wrapper)

A few lines that import ocr_extractor.cli.main and call it when the file is executed. It exists only to avoid breaking callers that still run python app.py file.pdf.

📤 Output

texto_limpio.txt (or the file passed with -o) contains the extracted text with this structure:

=== PAGE 1 ===

<cleaned text of page 1>

=== END PAGE 1 ===

=== PAGE 2 ===

<cleaned text of page 2>

=== END PAGE 2 ===

ℹ️ OCR quality depends on the PDF's resolution, the typeface used, and the presence of images or watermarks. Low-resolution scanned documents will produce more recognition errors.

⚙️ Customization

  • Change input / output / language / DPI: use the CLI arguments (-o, --lang, --dpi) or the read_document parameters from the API:
    from ocr_extractor import read_document
    text = read_document("file.pdf", dpi=400, lang="spa")
    
  • Adjust the resolution: higher dpi (400–600) gives better results on low-quality documents, at the cost of more memory and time.
  • Loosen the character filter: extend the allowed set inside ocr_extractor.core.clean_line to allow additional characters (for example, ,áéíóúÁÉÍÓÚñÑ¿¡). Since it lives inside ocr_extractor/, rerun pip install -e . after a change so the installed CLI picks it up.

🧪 Troubleshooting

Problem Likely cause Solution
ModuleNotFoundError: No module named 'ocr_extractor' The package isn't installed in the active Python pip install -e . (editable) or pip install ocr-extractor
ocr-extractor: command not found The PATH doesn't include the venv's scripts Activate the virtualenv or use python -m ocr_extractor.cli
TesseractNotFoundError The Tesseract binary is not on the PATH Install Tesseract or set pytesseract.pytesseract.tesseract_cmd to the absolute path of the binary
pdf2image fails with a Poppler error Poppler is not installed Install poppler-utils (Linux) or brew install poppler (macOS)
Extracted text is empty Image-based PDF with very low resolution Raise --dpi or improve the source PDF
Accented characters / ñ are lost clean_line only allows ASCII Extend the allowed set with áéíóúÁÉÍÓÚñÑ¿¡
unsupported file extension '.xyz' The format isn't supported Check ocr_extractor.SUPPORTED_FORMATS for the supported list
LibreOffice error on .doc/.xls/.ppt soffice is not on the PATH Install LibreOffice (see Requirements)
cannot identify image file '.heic' pillow-heif is not installed pip install ocr-extractor[heic]
DeprecationWarning: ocr_extractor.read_pdf is deprecated Code still imports the old read_pdf Switch to from ocr_extractor import read_document
Encoding errors in the .txt File opened without UTF-8 The CLI uses encoding="utf-8"; if you write manually, use the same encoding
Slow on long PDFs 300 DPI rendering + per-page OCR Lower --dpi or process the PDF in batches

🚀 Publishing the package

To build an sdist and a wheel locally (requires the dev extra):

pip install -e ".[dev]"
python -m build
ls dist/   # should show ocr_extractor-0.1.0.tar.gz and *.whl

To publish to PyPI (use the token you have in ~/.pypirc or env vars):

python -m pip install --upgrade twine
python -m twine upload dist/*

⚠️ Before publishing, add a LICENSE file and fill in the license and authors fields in pyproject.toml; PyPI requires them.

📝 License

This project is distributed for educational purposes. No LICENSE file is included at the moment. Feel free to adapt it to your needs; if you plan to redistribute it, add an explicit license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ocr_extractor-0.3.1.tar.gz (30.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ocr_extractor-0.3.1-py3-none-any.whl (22.7 kB view details)

Uploaded Python 3

File details

Details for the file ocr_extractor-0.3.1.tar.gz.

File metadata

  • Download URL: ocr_extractor-0.3.1.tar.gz
  • Upload date:
  • Size: 30.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ocr_extractor-0.3.1.tar.gz
Algorithm Hash digest
SHA256 bf6a6cb2078c95de39babd6c4efd8bca42f127ca3efd70fdf0c096596cb0b4fd
MD5 a1241a927434b8b5941389dc66a0e4a6
BLAKE2b-256 87099082413c9ec6b9934f55376fbb3ab22b6a928ec5ee576394c8205e0b6a77

See more details on using hashes here.

File details

Details for the file ocr_extractor-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: ocr_extractor-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 22.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ocr_extractor-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cf3dd300a9c9708faa365425a2b62a6cb559b2353bcb5a09bc714c083f45cfa4
MD5 42d983f0e4cb2e576583541cea874b4f
BLAKE2b-256 4e7c6d1b17155440b2547c032706893596bac9df145401bf94bc7d90d102e98e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page