From e7eb8fa8054b4eca3141f07bea294ab12e662478 Mon Sep 17 00:00:00 2001 From: Emiel Molenaar Date: Wed, 13 Mar 2024 22:49:42 +0100 Subject: [PATCH 1/3] Update Dockerfile.alpine (#1268) Use Alpine 3.19 as base image to ensure we get GhostScript 10.2.1 to eliminate serious regressions that corrupt PDFs with existing text. --- .docker/Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/Dockerfile.alpine b/.docker/Dockerfile.alpine index f513831f..e8ea913b 100644 --- a/.docker/Dockerfile.alpine +++ b/.docker/Dockerfile.alpine @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2023 James R. Barlow # SPDX-License-Identifier: MPL-2.0 -FROM alpine:3.18 as base +FROM alpine:3.19 as base ENV LANG=C.UTF-8 ENV TZ=UTC From feeb9f213f1ea4efae3ff22fe4d9c78febab3d46 Mon Sep 17 00:00:00 2001 From: NilsRo <51311453+NilsRo@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:22:24 +0100 Subject: [PATCH 2/3] batch example: added archive, small corrections and optimizations (#1277) * Added archive, small corrections Added a function to archive originals and avoid calling ocrmypdf if they are still is PDF/A. * Added Copyright --- misc/batch.py | 55 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/misc/batch.py b/misc/batch.py index 3b489fc7..87d34419 100644 --- a/misc/batch.py +++ b/misc/batch.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2016 findingorder +# SPDX-FileCopyrightText: 2024 nilsro # SPDX-License-Identifier: MIT """Example of using ocrmypdf as a library in a script. @@ -15,6 +16,10 @@ from __future__ import annotations import logging import sys +import os +import posixpath +import shutil +import filecmp from pathlib import Path import ocrmypdf @@ -22,32 +27,62 @@ import ocrmypdf # pylint: disable=logging-format-interpolation # pylint: disable=logging-not-lazy +def filecompare(a,b): + try: + return filecmp.cmp(a, b, shallow=True) + except FileNotFoundError: + return False + + script_dir = Path(__file__).parent +# set archive_dir to a path for backup original documents. Leave empty if not required. +archive_dir = "/pdfbak" if len(sys.argv) > 1: start_dir = Path(sys.argv[1]) else: - start_dir = Path('.') + start_dir = Path(".") if len(sys.argv) > 2: log_file = Path(sys.argv[2]) else: - log_file = script_dir.with_name('ocr-tree.log') + log_file = script_dir.with_name("ocr-tree.log") logging.basicConfig( level=logging.INFO, - format='%(asctime)s %(message)s', + format="%(asctime)s %(message)s", filename=log_file, - filemode='a', + filemode="a", ) +logging.info(f"Start directory {start_dir}") + ocrmypdf.configure_logging(ocrmypdf.Verbosity.default) -for filename in start_dir.glob("**/*.py"): +for filename in start_dir.glob("**/*.pdf"): logging.info(f"Processing {filename}") - result = ocrmypdf.ocr(filename, filename, deskew=True) - if result == ocrmypdf.ExitCode.already_done_ocr: - logging.error("Skipped document because it already contained text") - elif result == ocrmypdf.ExitCode.ok: + if ocrmypdf.pdfa.file_claims_pdfa(filename)["pass"]: + logging.info("Skipped document because it already contained text") + else: + archive_filename = archive_dir + str(filename) + if len(archive_dir) > 0 and not filecompare(filename, archive_filename): + logging.info(f"Archiving document to {archive_filename}") + try: + shutil.copy2(filename, posixpath.dirname(archive_filename)) + except IOError as io_err: + os.makedirs(posixpath.dirname(archive_filename)) + shutil.copy2(filename, posixpath.dirname(archive_filename)) + try: + result = ocrmypdf.ocr(filename, filename, deskew=True) + logging.info(result) + except ocrmypdf.exceptions.EncryptedPdfError: + logging.info("Skipped document because it is encrypted") + except ocrmypdf.exceptions.PriorOcrFoundError: + logging.info("Skipped document because it already contained text") + except ocrmypdf.exceptions.DigitalSignatureError: + logging.info("Skipped document because it has a digital signature") + except ocrmypdf.exceptions.TaggedPDFError: + logging.info("Skipped document because it does not need ocr as it is tagged") + except: + logging.error("Unhandled error occured") logging.info("OCR complete") - logging.info(result) From 855de287b298a98e101ae90b5cd7fc03c3d596e4 Mon Sep 17 00:00:00 2001 From: James Barlow Date: Tue, 19 Mar 2024 17:20:33 -0700 Subject: [PATCH 3/3] Fix test suite failure with Ghostscript >= 10.3 Ghostscript is more picky about a specific case with SMask that cannot be converted to PDF/A Details here https://github.com/ArtifexSoftware/ghostpdl/commit/4dcfae36bb4dcbc4ef3b5e5afc98bcde0d6b9ddc --- tests/test_preprocessing.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_preprocessing.py b/tests/test_preprocessing.py index a91c29ba..62678160 100644 --- a/tests/test_preprocessing.py +++ b/tests/test_preprocessing.py @@ -9,10 +9,11 @@ import pytest from PIL import Image from ocrmypdf._exec import ghostscript, tesseract +from ocrmypdf.exceptions import ExitCode from ocrmypdf.helpers import Resolution from ocrmypdf.pdfinfo import PdfInfo -from .conftest import check_ocrmypdf, have_unpaper +from .conftest import check_ocrmypdf, have_unpaper, run_ocrmypdf RENDERERS = ['hocr', 'sandwich'] @@ -107,7 +108,7 @@ def test_non_square_resolution(renderer, resources, outpdf): in_pageinfo = PdfInfo(resources / 'aspect.pdf') assert in_pageinfo[0].dpi.x != in_pageinfo[0].dpi.y - check_ocrmypdf( + proc = run_ocrmypdf( resources / 'aspect.pdf', outpdf, '--pdf-renderer', @@ -115,6 +116,10 @@ def test_non_square_resolution(renderer, resources, outpdf): '--plugin', 'tests/plugins/tesseract_cache.py', ) + # PDF/A conversion can fail for this file if Ghostscript >= 10.3, so don't test + # exit code in that case + if proc.returncode != ExitCode.pdfa_conversion_failed: + proc.check_returncode() out_pageinfo = PdfInfo(outpdf)