Skip to main content

pdfthumb

Generate WebP thumbnails from PDFs. The input can be one file, several files, or directories. You can use it as a command-line tool or as a Python library.

Install

./install.sh          # creates .venv, installs deps, links ~/.local/bin/pdfthumb
./install.sh --dev    # also installs pytest
./uninstall.sh

You need Python 3.9 or later. Dependencies are PyMuPDF and Pillow, so no system packages are required.

Usage

pdfthumb doc.pdf                       # -> thumbnails/doc.webp
pdfthumb a.pdf b.pdf -o out/           # multiple files
pdfthumb ./pdfs -r                     # directory, recursive (structure mirrored in output)
pdfthumb doc.pdf -p all -w 512         # every page -> doc_p1.webp, doc_p2.webp ...
pdfthumb doc.pdf -p 1,3-5 --height 300 # fit within 256x300
pdfthumb doc.pdf --crop-top           # top square only -> 256x256
pdfthumb doc.pdf -c 0.5                # top band, 256x128
pdfthumb ./pdfs --json                 # JSON results on stdout
Option Default Description
-o, --output DIR ./thumbnails Output directory
-w, --width PX 256 Thumbnail width (the aspect ratio is kept)
--height PX – Maximum height; the thumbnail is fitted within width × height
-c, --crop-top [RATIO] off Keep only the top of the page. Crop height = width × RATIO (1.0 = square when the flag is given without a value). Can't be combined with --height
-q, --quality N 80 WebP quality (0–100)
-p, --pages SPEC 1 Pages to render, e.g. 1, 1,3-5, all
-r, --recursive off Recurse into directories
--overwrite off Overwrite existing thumbnails. Without it, existing files are skipped
-j, --jobs N CPU count Number of parallel workers
--json off Print results as JSON

Output naming: <name>.webp with the default -p 1, otherwise <name>_p<N>.webp. The exit code is 1 if any PDF failed, for example a corrupt or password-protected file.

Library usage

Install it into your own environment:

pip install pdfthumb                        # from PyPI
pip install -e /path/to/pdf-thumbnail       # from a local checkout

Batch: files or directories to WebP files

from pdfthumb import generate_thumbnails

results = generate_thumbnails(
    ["a.pdf", "docs/"],      # a path, or a list of files and/or directories
    "thumbnails",            # output directory
    width=256,
    pages="1",               # "1", "1,3-5", "all"
    crop_top=1.0,            # optional: keep only the top square
    recursive=True,
    overwrite=False,
    jobs=4,                  # parallel processes (default 1)
)
for r in results:
    if r.ok:
        print(r.status, r.output, r.width, r.height)   # status: "ok" or "skipped"
    else:
        print("failed:", r.input, r.error)

generate_thumbnails raises ValueError for invalid options. A PDF that fails (for example a corrupt or encrypted file) does not raise. It comes back as a result with status="error". To get JSON-ready output, call r.to_dict().

Single page in memory (e.g. in a web service)

from pdfthumb import render_thumbnail, thumbnail_bytes, PDFThumbError

# A PIL.Image, from a file path or raw PDF bytes
img = render_thumbnail("doc.pdf", page=1, width=300, crop_top=0.75)

# WebP-encoded bytes, ready to upload or return in an HTTP response
with open("doc.pdf", "rb") as f:
    try:
        webp = thumbnail_bytes(f.read(), page=1, width=256, quality=80)
    except PDFThumbError as e:
        print("cannot thumbnail:", e)

API reference

Function Returns Notes
generate_thumbnails(inputs, output_dir="thumbnails", *, width, height, quality, pages, crop_top, recursive, overwrite, jobs) list[ThumbnailResult] Writes the WebP files. Same behaviour as the CLI
render_thumbnail(pdf, page=1, *, width=256, height=None, crop_top=None) PIL.Image.Image pdf is a path or bytes
thumbnail_bytes(pdf, page=1, *, width, height, crop_top, quality=80) bytes (WebP)
collect_pdfs(inputs, recursive=False) list[(Path, Path)] Lists the PDFs found in the inputs, with each one's directory relative to its input folder
parse_pages(spec, page_count) list[int] Page indices, starting at 0
PDFThumbError exception Raised for a corrupt or encrypted PDF, or a page that doesn't exist

You can also run the CLI as a module: python -m pdfthumb ...

Tests

./install.sh --dev && .venv/bin/pytest

CI / Releasing to PyPI

  • .github/workflows/ci.yml runs the tests on every push to main and on every PR. It covers Python 3.9–3.13 on Linux, plus macOS and Windows.
  • .github/workflows/publish.yml runs whenever a GitHub Release is published. It runs the tests, then builds the package using the release tag as the version (0.1.2 or v0.1.2), publishes it to PyPI, and attaches the wheel and sdist to the release. Running the workflow manually ("Run workflow") publishes the current __version__ to TestPyPI instead, as a dry run.

One-time setup, using Trusted Publishing so no API token is stored:

  1. On https://pypi.org/manage/account/publishing/, add a pending publisher: PyPI project pdfthumb, owner stakmi, repository pdfthumb, workflow publish.yml, environment pypi.
  2. Optionally, do the same on https://test.pypi.org with environment testpypi.
  3. Optionally, in GitHub Settings → Environments, add required reviewers to pypi. The environment is created automatically on first use.

Release:

gh release create 0.1.2 --generate-notes    # or use the GitHub UI: Releases → Draft a new release

The tag must be a valid version (e.g. 1.2.3, v1.2.3, 1.3.0rc1). PyPI rejects re-uploading a version that already exists, so each release needs a new tag.

Metadata

Release files for pdfthumb 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pdfthumb 0.1.2
File Size Uploaded
pdfthumb-0.1.2.tar.gz 11.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pdfthumb 0.1.2
File Interpreter ABI Platform
pdfthumb-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 21.9 kB

Release files / pdfthumb-0.1.2.tar.gz

Download URL pdfthumb-0.1.2.tar.gz
Size 11.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b361243e8ff0c16e97c62e07e26e2e3c9fad6f320f4f1268f4a229f2083e092e
BLAKE2b-256 checksum
How to use checksums
1d99a402b49a07bb07660a6048b27e8e977255eb08709a56aab9a2cbd6f337a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / pdfthumb-0.1.2-py3-none-any.whl

Download URL pdfthumb-0.1.2-py3-none-any.whl
Size 10.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
376d5694759f64cdc128e65803072616121d95e718ac5669ce5c7788af5f6410
BLAKE2b-256 checksum
How to use checksums
c8380c6fb6381989f2da7a16277095dec10f1ba6ac452f7fa03bdecd50cdf473
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page