pdfxz
A terminal UI and CLI for compressing PDF files with Ghostscript.
┌───────────────────────────────────────────────────────────────┐
│ PDFXZ │
│ PDF Compression Utility │
├───────────────────────────────────────────────────────────────┤
│ INPUT (a PDF file or a directory) │
│ [ ~/Documents/papers ] │
│ │
│ OUTPUT (optional - a sensible default is used if left blank) │
│ [ ~/Documents/compressed ] │
│ │
│ QUALITY │
│ [ Balanced ▼ ] │
│ │
│ │
│ │
│ [ Compress PDFs ] │
├───────────────────────────────────────────────────────────────┤
│ Status: Ready │
└───────────────────────────────────────────────────────────────┘
Features
- Interactive TUI for point-and-shoot use - no CLI syntax to memorize.
- Non-interactive CLI for scripts, cron jobs, and CI.
- Five explicit compression profiles, from archival quality to maximum size reduction.
- Batch directory processing with optional recursion, one bad file never aborts the batch.
- Live, honest progress - per-file counts and results, no fabricated percentages.
- Cancellable mid-batch, with automatic cleanup of partial output.
- Data-safe by default: never overwrites the input, never silently clobbers an existing output.
- Cross-platform: Linux, macOS, and Windows, wherever Python and Ghostscript are available.
Installation
pip install pdfxz
or, from a local checkout:
pip install .
Ghostscript requirement
pdfxz compresses PDFs using Ghostscript, but does not bundle it. Install Ghostscript separately and make sure it's on your PATH:
| Platform | Command |
|---|---|
| Ubuntu / Debian | sudo apt install ghostscript |
| Fedora | sudo dnf install ghostscript |
| Arch Linux | sudo pacman -S ghostscript |
| macOS (Homebrew) | brew install ghostscript |
| Windows | Download the installer from ghostscript.com |
On Windows, pdfxz looks for gswin64c, gswin32c, or gs on PATH (the console builds, not the GUI gswin64.exe). You can also point pdfxz at a specific binary with the PDFXZ_GS_PATH environment variable.
If Ghostscript isn't found, pdfxz tells you clearly instead of failing with a cryptic error.
Usage
TUI
pdfxz
Pick an input PDF or folder and, optionally, a destination folder - either by typing a path or using the Browse… buttons, which open a built-in folder/file browser. Drag the Quality slider (arrow keys or click) to choose a compression profile. Directories are always scanned recursively, missing output folders are created automatically, and an existing output is never overwritten - a uniquely-named file (report_compressed (2).pdf, etc.) is written alongside it instead, so there's nothing to configure and nothing you can accidentally clobber.
Keyboard shortcuts (also shown at the bottom of the screen): Esc cancels a running batch; q quits (when a text field isn't focused); on the report screen, n starts a new batch. Long filenames are truncated in the UI - highlight a row in the report table to see the full path underneath it.
CLI
pdfxz paper.pdf # -> paper_compressed.pdf
pdfxz input.pdf output.pdf # explicit output (positional)
pdfxz input.pdf -o output.pdf # explicit output (flag)
pdfxz ~/papers -o ~/compressed # compress a whole directory
pdfxz ~/papers -o ~/compressed --quality balanced
pdfxz ~/papers -o ~/compressed -q strong -r # recurse into subdirectories
pdfxz input.pdf -o ~/new/path/out.pdf -p # create missing output dirs
usage: pdfxz [-h] [-o PATH] [-p] [-q LEVEL] [-r] [--overwrite] [--no-tui]
[--debug] [--version]
[INPUT] [OUTPUT]
positional arguments:
INPUT A PDF file or a directory containing PDFs.
OUTPUT Output PDF file or directory (positional form).
options:
-h, --help show this help message and exit
-o PATH, --output PATH
Output PDF file or directory.
-p, --parents Create missing output directories (like 'mkdir -p').
-q LEVEL, --quality LEVEL
Compression profile: balanced, high, maximum,
maximum-compression, strong (default: balanced).
-r, --recursive Recurse into subdirectories when INPUT is a directory
(default: off, top level only).
--overwrite Allow overwriting an existing output file.
--no-tui Never launch the interactive TUI; exit with an error
if INPUT is missing instead of prompting.
--debug Enable verbose debug logging.
--version show program's version number and exit
Running pdfxz with no arguments launches the TUI. Passing INPUT runs non-interactively - the TUI never blocks a script. --no-tui is a safety net for automation: if a script accidentally omits INPUT, pdfxz fails fast with a clear error instead of trying to open an interactive terminal.
Exit codes: 0 on full success, 1 if any file failed, was skipped as invalid, or was cancelled, 2 on a usage error, 130 on Ctrl+C.
Compression profiles
Ghostscript's built-in /screen, /ebook, /printer, /prepress presets aren't used directly - their exact behavior has varied across Ghostscript versions. Instead, pdfxz defines five explicit, documented profiles built around image downsampling:
| Level | Color/Gray DPI | Mono DPI | Goal |
|---|---|---|---|
| Maximum Quality | 300 | 1200 | Minimal quality loss; archival copies |
| High Quality | 200 | 600 | Good visual quality, moderate compression |
| Balanced (default) | 150 | 300 | General-purpose default |
| Strong Compression | 120 | 200 | Smaller files, visible trade-off |
| Maximum Compression | 72 | 150 | Prioritizes file size |
No profile guarantees a specific percentage reduction - actual results depend heavily on the PDF's contents (image-heavy scans compress far more than text-only documents).
Batch processing & output naming
- Single file:
paper.pdf->paper_compressed.pdfby default, or your chosen output path. - Directory: each discovered PDF gets a
..._compressed.pdfsibling under the output directory. - Recursive scans: pdfxz preserves the input's relative directory structure under the output directory, so
papers/a/report.pdfandpapers/b/report.pdfnever collide or overwrite each other. - One failed or invalid file never aborts the batch - it's recorded and processing continues.
Output & overwrite behavior
- pdfxz never overwrites the input file, and rejects any operation where input and output resolve to the same file.
- An existing output file is left untouched unless you pass
--overwrite(CLI) or check "Overwrite existing output" (TUI). - Missing output directories are only created if you pass
-p/--parents(CLI) or leave "Create missing directories" checked (TUI, on by default). - If Ghostscript fails or is cancelled partway through, any partial output file is deleted; the input is never touched.
Defaults
| Setting | Default |
|---|---|
| Quality | balanced |
| Recursive | off (top-level only) |
| Create missing directories | on (TUI) / off (CLI, use -p) |
| Overwrite | off |
Troubleshooting
"Ghostscript is not installed" - install it and confirm gs --version (or gswin64c --version on Windows) works in your terminal.
"Output file already exists" - pass --overwrite, or choose a different output path.
"Output path is the same as the input path" - choose a distinct output location; pdfxz refuses to overwrite the source PDF.
"No PDF files found" - check that files actually end in .pdf (case-insensitive) and, for nested folders, that you passed -r/--recursive.
A file is reported invalid - pdfxz checks the %PDF- signature, not just the extension; the file may be corrupt or not actually a PDF.
Development
git clone https://github.com/example/pdfxz
cd pdfxz
python -m venv .venv
source .venv/bin/activate # .venv\Scripts\activate on Windows
pip install -e ".[dev]"
Project layout
src/pdfxz/
├── __init__.py # package version
├── __main__.py # `python -m pdfxz` entry point
├── cli.py # argument parsing + non-interactive execution
├── app.py # Textual TUI (config / processing / report screens)
├── app.tcss # TUI stylesheet
├── compressor.py # Ghostscript subprocess integration
├── workers.py # framework-agnostic batch orchestration (shared by CLI + TUI)
├── scanner.py # PDF discovery + %PDF- signature validation
├── models.py # typed result/state dataclasses
├── profiles.py # the five compression profiles
├── formatting.py # size/percentage/ratio/duration helpers
└── utils.py # Ghostscript discovery, path safety, output planning
The compressor has no knowledge of the TUI or CLI; the scanner has no knowledge of Ghostscript; the TUI and CLI both drive the same BatchRunner. This keeps each layer independently testable.
Testing
pytest
The test suite mocks Ghostscript with a small fake executable (tests/fixtures/fake_gs.py), so it never requires a real Ghostscript installation. It covers the scanner, compressor (including cancellation and cleanup), profiles, formatting helpers, the CLI, and a TUI smoke test.
License
MIT - see LICENSE.
Metadata
Release files for pdfxz 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfxz-0.2.0.tar.gz | 35.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfxz-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.9 kB
Release files / pdfxz-0.2.0.tar.gz
| Download URL | pdfxz-0.2.0.tar.gz |
|---|---|
| Size | 35.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d50eaef4ab06312b5c84b4226f36c54eb8dd71d025d9399032d6da1d6e062de2
|
|
BLAKE2b-256 checksum How to use checksums |
9b746c282fb8fbae86acb9c2cc912b93e5d731b94a6ec8dbc65f33b38322289d
|
| 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 Aug 14, 2026.
Transparency logRelease files / pdfxz-0.2.0-py3-none-any.whl
| Download URL | pdfxz-0.2.0-py3-none-any.whl |
|---|---|
| Size | 31.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
85954fd4c964d1aa55ff63a98506891fa68ab2827317a4cdde97f75ec321c3a0
|
|
BLAKE2b-256 checksum How to use checksums |
cf16757196a00021762cea2574457a68edc187e71727bc92c977c00713c83460
|
| 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 Aug 14, 2026.
Transparency log