How to Handle Image-Only PDFs with book-to-skill: Detection and OCR Workflows

book-to-skill detects image-only PDFs by running a lightweight pre-flight check using pdftotext on the first five pages; if no text is extracted, it raises an ExtractionError to prevent wasted CPU cycles, allowing you to route the file to an OCR engine like Tesseract instead.

Processing scanned documents and image-based PDFs requires special handling in text extraction pipelines. The virgiliojr94/book-to-skill repository provides a dedicated detection mechanism that identifies PDFs containing only raster images—no extractable text layers—before attempting expensive parsing operations. This guide explains how the detection logic works, where it lives in the source code, and how to implement OCR fallbacks for scanned documents.

How Image-Only PDF Detection Works in book-to-skill

The detection system treats image-only PDFs as a special case to avoid false-negative results from standard text extraction backends. When you attempt to process a PDF, the pipeline first determines whether the file contains selectable text or only scanned images.

The looks_image_only Function Implementation

The core detection logic resides in book_to_skill/parsers/pdf.py within the looks_image_only function. This utility performs a fast pre-flight check on the initial pages of the document using the external pdftotext command-line tool.

The implementation follows three verification steps:

  1. Binary availability check: The function uses shutil.which("pdftotext") to verify the helper utility is installed. If pdftotext is missing, the function returns False and the normal extraction pipeline proceeds without blocking the file.

  2. Sample text extraction: When pdftotext is available, the function executes a subprocess call with page range constraints:

    pdftotext -f 1 -l 5 input.pdf -

    This limits the check to the first five pages (configurable via the pages parameter) for speed.

  3. Emptiness validation: The function returns True only when the command succeeds (returncode == 0) and produces no character output (not result.stdout.strip()). Any exception during execution triggers a fallback to False, ensuring the pipeline attempts normal extraction rather than failing silently.

Integrating Detection into Your Extraction Pipeline

According to the book-to-skill source code in book_to_skill/utils.py, the detection result determines whether the heavy-weight extraction steps execute. When looks_image_only returns True, the pipeline short-circuits and raises an ExtractionError, bypassing downstream parsers like extract_with_pdftotext, extract_with_pypdf, and extract_with_pdfminer.

Here is a practical implementation pattern for your scripts:

from book_to_skill.parsers.pdf import looks_image_only, extract_with_pdftotext

pdf_path = "my_scanned_book.pdf"

# 1️⃣ Quick pre-flight check

if looks_image_only(pdf_path):
    print(f"⚠️  {pdf_path} contains only images – text extraction will fail.")
    # Route to OCR workflow here

else:
    # 2️⃣ Continue with normal extraction

    text = extract_with_pdftotext(pdf_path)
    if text:
        print("✅ Extraction succeeded.")
    else:
        print("❌ Extraction failed – try another backend.")

This pattern prevents CPU-intensive parsing attempts on documents that contain no machine-readable text layers.

Handling Image-Only PDFs with OCR Fallback

When the detection confirms an image-only PDF, you should invoke an optical character recognition (OCR) engine to extract text from the raster images. The following example demonstrates how to convert PDF pages to PNG images and process them with Tesseract:

import subprocess
from pathlib import Path

def ocr_pdf(pdf_path: str, out_txt: str) -> None:
    """Run Tesseract OCR on each page of an image-only PDF."""
    # Convert PDF pages to PNG images

    subprocess.run(
        ["pdf2image", "-pdf", pdf_path, "-o", "page_%03d.png"],
        check=True,
    )
    
    # Combine OCR output from each page

    pages = sorted(Path(".").glob("page_*.png"))
    with open(out_txt, "w", encoding="utf-8") as out_f:
        for img in pages:
            subprocess.run(
                ["tesseract", str(img), "stdout", "-l", "eng"],
                stdout=out_f,
                check=True,
            )

You can integrate this ocr_pdf function into the conditional block from the previous section to automatically handle scanned documents when looks_image_only returns True.

Summary

  • looks_image_only in book_to_skill/parsers/pdf.py provides lightweight detection using the external pdftotext utility on the first five pages.
  • The function returns True only when pdftotext succeeds but produces empty output, indicating pure image content.
  • When detection returns True, book-to-skill raises an ExtractionError in book_to_skill/utils.py to short-circuit expensive extraction backends.
  • The detection defaults to False if pdftotext is unavailable or encounters errors, ensuring robust fallback behavior.
  • For detected image-only PDFs, implement an OCR workflow using Tesseract or similar engines to extract text from raster images.

Frequently Asked Questions

How does book-to-skill detect image-only PDFs?

The library uses the looks_image_only function in book_to_skill/parsers/pdf.py to run pdftotext -f 1 -l 5 against the input file. If the command executes successfully but returns zero characters, the PDF is classified as image-only. This check requires the pdftotext binary to be installed on the system path.

What happens when an image-only PDF is detected?

When looks_image_only returns True, the main extraction flow in book_to_skill/utils.py raises an ExtractionError before invoking heavy-weight parsers like extract_with_pdfminer. This short-circuit prevents wasted computation on OCR-free extraction attempts and signals your application to route the file to an OCR engine or skip it.

Can I customize the number of pages checked?

Yes. The looks_image_only function accepts a pages parameter that defaults to 5. You can increase or decrease this value depending on your accuracy requirements and performance constraints. Checking more pages improves detection reliability for documents with late-starting text content, while fewer pages increase speed.

Which OCR engines work with book-to-skill?

The repository does not bundle a specific OCR engine, but the detection output is designed to trigger any external OCR tool. The examples in the documentation use Tesseract via command-line invocation on PNG conversions of PDF pages. You can substitute alternatives like EasyOCR, PaddleOCR, or cloud-based services by replacing the subprocess calls in your OCR fallback function.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →