Commit Graph
4 Commits
Author SHA1 Message Date
stumpylog ae3d8680fa Chore: Generate PDF thumbnails with pdftoppm and Pillow instead of ImageMagick
PDF thumbnails were produced by handing the original PDF to ImageMagick's
convert, which delegates PDF handling to Ghostscript. When that failed, a
fallback invoked gs directly and then ran convert a second time just to
encode the WebP, so a single thumbnail could take three subprocess calls
through two general purpose tools for what is only "render page one small".

The first page is now rasterized with Poppler's pdftoppm, which is already
installed for pdftotext, at a DPI computed from the page's own CropBox and
effective rotation as read by pikepdf. The DPI is chosen so the page fits
500x5000 pixels in a single render and is capped at 72 so small pages are
never enlarged, matching the previous shrink-only scale. Pillow then
flattens any alpha onto white, applies a no-enlarge safety clamp and saves
the WebP in process. If the geometry cannot be read, a fixed 150 DPI is
used and the clamp keeps the output in bounds.

The Ghostscript fallback is replaced with a qpdf repair and retry: the PDF
is copied, repaired in place with qpdf (treating its "repaired with
warnings" exit status as success), and rasterized again. If that also
fails, the default thumbnail is used as before.
2026-10-07 12:43:18 -07:00
Trenton HandClaude Sonnet 5 350684cd6b Fix: consolidate born-digital PDF detection between archive decision and OCR (#13409)
* Fix: unify born-digital PDF detection between archive decision and OCR

should_produce_archive() and RasterisedDocumentParser.parse() each
reimplemented the "does this PDF have real text" check independently,
using different normalization of pdftotext output. Raw pdftotext output
can be non-empty (whitespace/form-feed layout padding) even when there
is no real content, so the two checks could disagree: consumer.py
treated a tagged-but-textless PDF as born-digital and skipped the
archive, while the parser's own (stricter, normalized) check found no
text and ran OCR anyway, leaving the document with no archive despite
real OCR text (GH #13387).

Both call sites now share one predicate, pdf_born_digital_text() in
paperless/parsers/utils.py, so they can no longer drift apart.

* Fix: restore extract_text seam for born-digital detection in parse()

parse() had switched to calling pdf_born_digital_text() directly for
its initial text/born-digital check, bypassing the parser's own
extract_text instance method. That broke test mockability (tests patch
tesseract_parser.extract_text to control the born-digital decision)
and caused CI failures with mismatched OCR call counts and text.

Split pdf_born_digital_text() into is_born_digital_text(text, path,
log) - a pure decision function - and a thin pdf_born_digital_text()
wrapper for callers without text in hand (consumer.should_produce_archive).
parse() now extracts via self.extract_text(None, document_path) and
passes the result to is_born_digital_text(), restoring the seam with
no change to production behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Cleanup: simplify born-digital detection, close #13387 test gap

Simplification pass over the born-digital detection consolidation:
- is_born_digital_text(): drop the has_text temp for an early return.
- consumer.py: standardize the archive-decision log lines on plain
  hyphens (was a mix of em-dash and hyphen) and hoist the duplicated
  text_length computation.
- Parametrize TestPdfBornDigitalText instead of four near-identical
  tests.

Code review follow-up: the existing tests only ever exercised
pdf_born_digital_text() through mocks, so the actual #13387 scenario
(a tagged PDF whose only "text" is layout padding) was never checked
against real pdftotext/pikepdf output - a regression in the
normalize-before-decide logic itself would have gone undetected.
Moved tagged_no_text_pdf_file from parsers/conftest.py up to the
shared paperless/tests/conftest.py (it was previously only visible to
tests under parsers/) and added a non-mocked regression test against
the real sample file.

Also fixed two stale comments in test_consumer.py referencing a
_extract_text_for_archive_check helper that no longer exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:41:09 -07:00
Trenton H 92c016ce47 Fix: Handle the UTF 16 and BOM text files better (#12994) 2026-06-13 05:35:38 -07:00
c232d443fa Breaking: Decouple OCR control from archive file control (#12448)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-04-06 15:50:21 -07:00