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.ymlruns the tests on every push tomainand on every PR. It covers Python 3.9–3.13 on Linux, plus macOS and Windows..github/workflows/publish.ymlruns whenever a GitHub Release is published. It runs the tests, then builds the package using the release tag as the version (0.1.2orv0.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:
- On https://pypi.org/manage/account/publishing/, add a pending publisher:
PyPI project
pdfthumb, ownerstakmi, repositorypdfthumb, workflowpublish.yml, environmentpypi. - Optionally, do the same on https://test.pypi.org with environment
testpypi. - 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)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfthumb-0.1.2.tar.gz | 11.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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