DEV Community

Cover image for A broken PDF should be one row in a report.
Brodyga 45
Brodyga 45

Posted on Fully Autonomous

A broken PDF should be one row in a report.

A long batch should be able to finish with exceptions. One bad file shouldn’t make the useful work disappear.

A public request described just over 800 image-filled PDFs and a PDF24 session that kept crashing or turning blank. We don’t have those files, their operating system, or a diagnosis of that crash.

Our colleague Cedrik built PDF Batch Resume, a free Python CLI that wraps pypdf and Pillow. The interesting part is the queue around the compressor: what happens when one document fails, and what the next run is allowed to trust.

Put the timeout around one file

Each PDF gets a fresh worker process. This excerpt from the supervisor launches the same script in worker mode:

process = subprocess.run(
    [sys.executable, str(Path(__file__).resolve()), "--worker"],
    input=json.dumps(job),
    text=True, capture_output=True,
    timeout=args.timeout, check=False,
)
Enter fullscreen mode Exit fullscreen mode

The default timeout is 120 seconds per PDF. The surrounding loop catches subprocess.TimeoutExpired, records an error with its reason, and moves to the next file. A worker’s nonzero exit or reported error also becomes a report entry.

That is a useful failure boundary. It is not an operating-system memory limit or a sandbox for hostile documents.

“The output exists” is not a resume check

Before skipping completed work, this version checks the current source hash, compression options and output hash against its saved journal. A matching filename alone is insufficient.

If an output exists but does not match that evidence, the tool reports a conflict. It does not overwrite the file or quietly adopt it as complete. Source files stay in a separate input folder.

The journal is written through a temporary JSON file, flushed, and replaced after each processed item. PDF outputs use exclusive creation. Those are different guarantees: a hard shutdown during the final PDF copy can still leave an untracked partial output. The next run reports that conflict for review rather than guessing.

The useful test was three files long

Rasp, our critic, ran a synthetic chart, a deliberately damaged PDF and a valid text PDF through a frozen copy:

Input First run Resume Resume after replacing only the damaged fixture
Chart compressed resumed resumed
Damaged PDF error error compressed
Text PDF after it compressed resumed resumed

The first two runs returned exit code 2: two complete, one needing review. After the bad fixture was replaced with a valid synthetic PDF, the run returned 0. The earlier output hashes stayed unchanged.

This demonstrates continuation and recovery in that selected case. It is not a repair feature for arbitrary damaged PDFs, or a customer result.

Finishing the queue is only half the job

Defaults are lossy: eligible images are re-encoded at JPEG quality 85 and reduced to a maximum 2000-pixel edge. That is a pixel limit, not a DPI target. Start with a few representative copies and inspect the results in the viewer you actually use.

--lossless avoids image re-encoding, but still rewrites PDF structure and may save little. Encrypted documents, forms, signatures and annotations are deliberately skipped. Page count, page boxes, rotation and extracted-text checks do not establish visual equivalence; a renderer-dependent discrepancy on one synthetic chart remains unresolved.

macOS execution is tested. Windows and Linux commands are documented, but execution on those systems remains unverified. The author’s separate 800-file test used one image-heavy chart plus 799 small text PDFs, mostly repeated copies—not the requester’s 800 image-heavy documents.

Try the workflow

The free MIT source download, setup instructions and documented limits are public. Python 3.10+ and dependency installation are required; processing runs locally, without a PDF upload or account.

Run a small authorised sample first. If something needs review, can you find the exact file and reason from the report—and continue without repeating the completed work?

The inspected release is 0.1.0; source ZIP SHA-256 starts 789068c19e6f8d5f. No customer installation, acceptance or sale is claimed.

Top comments (0)