diff --git a/src/ocrmypdf/_exec/ghostscript.py b/src/ocrmypdf/_exec/ghostscript.py index bbf13277..d96cd3c3 100644 --- a/src/ocrmypdf/_exec/ghostscript.py +++ b/src/ocrmypdf/_exec/ghostscript.py @@ -18,7 +18,6 @@ from subprocess import PIPE, CalledProcessError from typing import Optional, cast from PIL import Image -from tqdm import tqdm from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError from ocrmypdf.helpers import Resolution @@ -144,21 +143,26 @@ class GhostscriptFollower: re_process = re.compile(r"Processing pages \d+ through (\d+).") re_page = re.compile(r"Page (\d+)") - def __init__(self): + def __init__(self, progressbar_class): self.count = 0 - self.tqdm = None + self.progressbar_class = progressbar_class + self.progressbar = None def __call__(self, line): - if not self.tqdm: + if not self.progressbar_class: + return + if not self.progressbar: m = self.re_process.match(line.strip()) if m: self.count = int(m.group(1)) - self.tqdm = tqdm(total=self.count, desc="Ghostscript", unit='page') + self.progressbar = self.progressbar_class( + total=self.count, desc="PDF/A conversion", unit='page' + ) return else: m = self.re_page.match(line.strip()) if m: - self.tqdm.update() + self.progressbar.update() def generate_pdfa( @@ -167,6 +171,7 @@ def generate_pdfa( compression: str, pdf_version: str = '1.5', pdfa_part: str = '2', + progressbar_class=None, ): # Ghostscript's compression is all or nothing. We can either force all images # to JPEG, force all to Flate/PNG, or let it decide how to encode the images. @@ -240,7 +245,7 @@ def generate_pdfa( text=True, encoding='utf-8', errors='replace', - callback=GhostscriptFollower(), + callback=GhostscriptFollower(progressbar_class), ) except CalledProcessError as e: # Ghostscript does not change return code when it fails to create diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index aa55f5d4..76223ac6 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -19,6 +19,7 @@ import img2pdf import pikepdf from pikepdf.models.metadata import encode_pdf_date from PIL import Image, ImageColor, ImageDraw +from tqdm import tqdm from ocrmypdf import leptonica from ocrmypdf._exec import unpaper @@ -709,6 +710,7 @@ def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext): output_file=output_file, compression=options.pdfa_image_compression, pdfa_part=options.output_type[-1], # is pdfa-1, pdfa-2, or pdfa-3 + progressbar_class=tqdm if options.progress_bar else None, ) return output_file diff --git a/src/ocrmypdf/builtin_plugins/ghostscript.py b/src/ocrmypdf/builtin_plugins/ghostscript.py index 3be822d0..d1c6a628 100644 --- a/src/ocrmypdf/builtin_plugins/ghostscript.py +++ b/src/ocrmypdf/builtin_plugins/ghostscript.py @@ -79,12 +79,21 @@ def rasterize_pdf_page( @hookimpl -def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part): +def generate_pdfa( + pdf_pages, + pdfmark, + output_file, + compression, + pdf_version, + pdfa_part, + progressbar_class, +): ghostscript.generate_pdfa( pdf_pages=[*pdf_pages, pdfmark], output_file=output_file, compression=compression, pdf_version=pdf_version, pdfa_part=pdfa_part, + progressbar_class=progressbar_class, ) return output_file diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index b34e467f..9aee6596 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -59,7 +59,7 @@ def check_options(options: Namespace) -> None: Note: This hook will be called from the main process, and may modify global state before child worker processes are forked. - """ + """ @hookspec @@ -280,6 +280,7 @@ def generate_pdfa( compression: str, pdf_version: str, pdfa_part: str, + progressbar_class, ) -> Path: """Generate a PDF/A. @@ -302,10 +303,21 @@ def generate_pdfa( At its own discretion, the PDF/A generator may raise the version, but should not lower it. pdfa_part: The desired PDF/A compliance level, such as ``'2B'``. + progressbar_class: The class of a progress bar with a tqdm-like API. An + instance of this class will be initialized when PDF/A conversion + begins, using + ``instance = progressbar_class(total: int, desc: str, unit:str)``, + defining the number of work units, a user-visible description, + and the name of the work units ("page"). Then ``instance.update()`` + will be called when a work unit is completed. If ``None``, no + progress information is reported. Returns: Path: If successful, the hook should return ``output_file``. Note: This is a :ref:`firstresult hook`. + + See also: + https://github.com/tqdm/tqdm """ diff --git a/tests/plugins/gs_feature_elision.py b/tests/plugins/gs_feature_elision.py index 419855cb..98e78086 100644 --- a/tests/plugins/gs_feature_elision.py +++ b/tests/plugins/gs_feature_elision.py @@ -45,5 +45,6 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf compression=compression, pdf_version=pdf_version, pdfa_part=pdfa_part, + progressbar_class=None, ) return output_file diff --git a/tests/plugins/gs_pdfa_failure.py b/tests/plugins/gs_pdfa_failure.py index 8a694de9..8973dcaf 100644 --- a/tests/plugins/gs_pdfa_failure.py +++ b/tests/plugins/gs_pdfa_failure.py @@ -47,5 +47,6 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf compression=compression, pdf_version=pdf_version, pdfa_part=pdfa_part, + progressbar_class=None, ) return output_file diff --git a/tests/plugins/gs_render_failure.py b/tests/plugins/gs_render_failure.py index 2cad1f4e..dcbc6c5a 100644 --- a/tests/plugins/gs_render_failure.py +++ b/tests/plugins/gs_render_failure.py @@ -42,5 +42,6 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf compression=compression, pdf_version=pdf_version, pdfa_part=pdfa_part, + progressbar_class=None, ) return output_file