pipeline: add further typing details

This commit is contained in:
James R. Barlow
2022-09-19 20:19:15 -07:00
parent 2e55cb5980
commit 1709e23701
2 changed files with 46 additions and 42 deletions
+45 -41
View File
@@ -14,7 +14,7 @@ from contextlib import suppress
from datetime import datetime, timezone
from pathlib import Path
from shutil import copyfileobj
from typing import Iterable
from typing import Any, Iterable, Sequence
import img2pdf
import pikepdf
@@ -36,7 +36,7 @@ from ocrmypdf.exceptions import (
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution, safe_symlink
from ocrmypdf.hocrtransform import HocrTransform
from ocrmypdf.pdfa import generate_pdfa_ps
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
from ocrmypdf.pdfinfo import Colorspace, Encoding, PageInfo, PdfInfo
# Remove this workaround when we require Pillow >= 10
try:
@@ -50,7 +50,7 @@ log = logging.getLogger(__name__)
VECTOR_PAGE_DPI = 400
def triage_image_file(input_file, output_file, options):
def triage_image_file(input_file: Path, output_file: Path, options) -> None:
log.info("Input file is not a PDF, checking if it is an image...")
try:
im = Image.open(input_file)
@@ -114,7 +114,7 @@ def triage_image_file(input_file, output_file, options):
raise UnsupportedImageFormatError() from e
def _pdf_guess_version(input_file, search_window=1024):
def _pdf_guess_version(input_file: Path, search_window=1024) -> str:
"""Try to find version signature at start of file.
Not robust enough to deal with appended files.
@@ -126,11 +126,13 @@ def _pdf_guess_version(input_file, search_window=1024):
signature = f.read(search_window)
m = re.search(br'%PDF-(\d\.\d)', signature)
if m:
return m.group(1)
return m.group(1).decode('ascii')
return ''
def triage(original_filename, input_file, output_file, options):
def triage(
original_filename: str, input_file: Path, output_file: Path, options
) -> Path:
try:
if _pdf_guess_version(input_file):
if options.image_dpi:
@@ -154,9 +156,9 @@ def get_pdfinfo(
input_file,
*,
executor: Executor,
detailed_analysis=False,
progbar=False,
max_workers=None,
detailed_analysis: bool = False,
progbar: bool = False,
max_workers: int | None = None,
check_pages=None,
) -> PdfInfo:
try:
@@ -174,7 +176,7 @@ def get_pdfinfo(
raise InputFileError() from e
def validate_pdfinfo_options(context: PdfContext):
def validate_pdfinfo_options(context: PdfContext) -> None:
pdfinfo = context.pdfinfo
options = context.options
@@ -215,11 +217,11 @@ def validate_pdfinfo_options(context: PdfContext):
context.plugin_manager.hook.validate(pdfinfo=pdfinfo, options=options)
def _vector_page_dpi(pageinfo):
return VECTOR_PAGE_DPI if pageinfo.has_vector or pageinfo.has_text else 0.0
def _vector_page_dpi(pageinfo: PageInfo) -> int:
return VECTOR_PAGE_DPI if pageinfo.has_vector or pageinfo.has_text else 0
def get_page_dpi(pageinfo, options):
def get_page_dpi(pageinfo: PageInfo, options) -> Resolution:
"Get the DPI when nonsquare DPI is tolerable"
xres = max(
pageinfo.dpi.x or VECTOR_PAGE_DPI,
@@ -234,7 +236,7 @@ def get_page_dpi(pageinfo, options):
return Resolution(float(xres), float(yres))
def get_page_square_dpi(pageinfo, options) -> Resolution:
def get_page_square_dpi(pageinfo: PageInfo, options) -> Resolution:
"Get the DPI when we require xres == yres, scaled to physical units"
xres = pageinfo.dpi.x or 0.0
yres = pageinfo.dpi.y or 0.0
@@ -250,7 +252,7 @@ def get_page_square_dpi(pageinfo, options) -> Resolution:
return Resolution(units, units)
def get_canvas_square_dpi(pageinfo, options) -> Resolution:
def get_canvas_square_dpi(pageinfo: PageInfo, options) -> Resolution:
"""Get the DPI when we require xres == yres, in Postscript units"""
units = float(
max(
@@ -263,7 +265,7 @@ def get_canvas_square_dpi(pageinfo, options) -> Resolution:
return Resolution(units, units)
def is_ocr_required(page_context: PageContext):
def is_ocr_required(page_context: PageContext) -> bool:
pageinfo = page_context.pageinfo
options = page_context.options
@@ -338,7 +340,7 @@ def is_ocr_required(page_context: PageContext):
return ocr_required
def rasterize_preview(input_file: Path, page_context: PageContext):
def rasterize_preview(input_file: Path, page_context: PageContext) -> Path:
output_file = page_context.get_path('rasterize_preview.jpg')
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
@@ -355,7 +357,7 @@ def rasterize_preview(input_file: Path, page_context: PageContext):
return output_file
def describe_rotation(page_context: PageContext, orient_conf, correction: int):
def describe_rotation(page_context: PageContext, orient_conf, correction: int) -> str:
"""
Describe the page rotation we are going to perform.
"""
@@ -384,7 +386,7 @@ def describe_rotation(page_context: PageContext, orient_conf, correction: int):
return f"{facing}, confidence {orient_conf.confidence:.2f} - {action}"
def get_orientation_correction(preview: Path, page_context: PageContext):
def get_orientation_correction(preview: Path, page_context: PageContext) -> int:
"""Work out orientation correct for each page.
We ask Ghostscript to draw a preview page, which will rasterize with the
@@ -470,18 +472,17 @@ def rasterize(
return output_file
def preprocess_remove_background(input_file: Path, page_context: PageContext):
def preprocess_remove_background(input_file: Path, page_context: PageContext) -> Path:
if any(image.bpc > 1 for image in page_context.pageinfo.images):
raise NotImplementedError("--remove-background is temporarily not implemented")
# output_file = page_context.get_path('pp_rm_bg.png')
# leptonica.remove_background(input_file, output_file)
# return output_file
else:
log.info("background removal skipped on mono page")
return input_file
log.info("background removal skipped on mono page")
return input_file
def preprocess_deskew(input_file: Path, page_context: PageContext):
def preprocess_deskew(input_file: Path, page_context: PageContext) -> Path:
output_file = page_context.get_path('pp_deskew.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
@@ -501,7 +502,7 @@ def preprocess_deskew(input_file: Path, page_context: PageContext):
return output_file
def preprocess_clean(input_file: Path, page_context: PageContext):
def preprocess_clean(input_file: Path, page_context: PageContext) -> Path:
output_file = page_context.get_path('pp_clean.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
return unpaper.clean(
@@ -512,7 +513,7 @@ def preprocess_clean(input_file: Path, page_context: PageContext):
)
def create_ocr_image(image: Path, page_context: PageContext):
def create_ocr_image(image: Path, page_context: PageContext) -> Path:
"""Create the image we send for OCR. May not be the same as the display
image depending on preprocessing. This image will never be shown to the
user."""
@@ -560,7 +561,7 @@ def create_ocr_image(image: Path, page_context: PageContext):
return output_file
def ocr_engine_hocr(input_file: Path, page_context: PageContext):
def ocr_engine_hocr(input_file: Path, page_context: PageContext) -> tuple[Path, Path]:
hocr_out = page_context.get_path('ocr_hocr.hocr')
hocr_text_out = page_context.get_path('ocr_hocr.txt')
options = page_context.options
@@ -575,7 +576,7 @@ def ocr_engine_hocr(input_file: Path, page_context: PageContext):
return (hocr_out, hocr_text_out)
def should_visible_page_image_use_jpg(pageinfo):
def should_visible_page_image_use_jpg(pageinfo: PageInfo) -> bool:
# If all images were JPEGs originally, produce a JPEG as output
return pageinfo.images and all(im.enc == Encoding.jpeg for im in pageinfo.images)
@@ -600,8 +601,8 @@ def create_visible_page_jpg(image: Path, page_context: PageContext) -> Path:
def create_pdf_page_from_image(
image: Path, page_context: PageContext, orientation_correction
):
image: Path, page_context: PageContext, orientation_correction: int
) -> Path:
# We rasterize a square DPI version of each page because most image
# processing tools don't support rectangular DPI. Use the square DPI as it
# accurately describes the image. It would be possible to resample the image
@@ -629,11 +630,10 @@ def create_pdf_page_from_image(
output_file = page_context.plugin_manager.hook.filter_pdf_page(
page=page_context, image_filename=image, output_pdf=output_file
)
return output_file
def render_hocr_page(hocr: Path, page_context: PageContext):
def render_hocr_page(hocr: Path, page_context: PageContext) -> Path:
options = page_context.options
output_file = page_context.get_path('ocr_hocr.pdf')
dpi = get_page_square_dpi(page_context.pageinfo, options)
@@ -650,7 +650,9 @@ def render_hocr_page(hocr: Path, page_context: PageContext):
return output_file
def ocr_engine_textonly_pdf(input_image: Path, page_context: PageContext):
def ocr_engine_textonly_pdf(
input_image: Path, page_context: PageContext
) -> tuple[Path, Path]:
output_pdf = page_context.get_path('ocr_tess.pdf')
output_text = page_context.get_path('ocr_tess.txt')
options = page_context.options
@@ -696,13 +698,13 @@ def get_docinfo(base_pdf: pikepdf.Pdf, context: PdfContext) -> dict[str, str]:
return pdfmark
def generate_postscript_stub(context: PdfContext):
def generate_postscript_stub(context: PdfContext) -> Path:
output_file = context.get_path('pdfa.ps')
generate_pdfa_ps(output_file)
return output_file
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext) -> Path:
options = context.options
input_pdfinfo = context.pdfinfo
fix_docinfo_file = context.get_path('fix_docinfo.pdf')
@@ -749,14 +751,14 @@ def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
return output_file
def should_linearize(working_file: Path, context: PdfContext):
def should_linearize(working_file: Path, context: PdfContext) -> bool:
filesize = os.stat(working_file).st_size
if filesize > (context.options.fast_web_view * 1_000_000):
return True
return False
def get_pdf_save_settings(output_type: str):
def get_pdf_save_settings(output_type: str) -> dict[str, Any]:
if output_type == 'pdfa-1':
# Trigger recompression to ensure object streams are removed, because
# Acrobat complains about them in PDF/A-1b validation.
@@ -774,7 +776,7 @@ def get_pdf_save_settings(output_type: str):
)
def metadata_fixup(working_file: Path, context: PdfContext):
def metadata_fixup(working_file: Path, context: PdfContext) -> Path:
output_file = context.get_path('metafix.pdf')
options = context.options
@@ -830,7 +832,9 @@ def metadata_fixup(working_file: Path, context: PdfContext):
return output_file
def optimize_pdf(input_file: Path, context: PdfContext, executor: Executor):
def optimize_pdf(
input_file: Path, context: PdfContext, executor: Executor
) -> tuple[Path, Sequence[str]]:
output_file = context.get_path('optimize.pdf')
output_pdf, messages = context.plugin_manager.hook.optimize_pdf(
input_pdf=input_file,
@@ -866,7 +870,7 @@ def enumerate_compress_ranges(iterable):
yield (skipped_from, index), None
def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext):
def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext) -> Path:
output_file = context.get_path('sidecar.txt')
with open(output_file, 'w', encoding="utf-8") as stream:
for (from_, to_), txt_file in enumerate_compress_ranges(txt_files):
@@ -891,7 +895,7 @@ def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext):
return output_file
def copy_final(input_file, output_file, _context: PdfContext):
def copy_final(input_file, output_file, _context: PdfContext) -> None:
log.debug('%s -> %s', input_file, output_file)
with open(input_file, 'rb') as input_stream:
if output_file == '-':
+1 -1
View File
@@ -6,4 +6,4 @@
from __future__ import annotations
from ocrmypdf.pdfinfo.info import Colorspace, Encoding, PdfInfo
from ocrmypdf.pdfinfo.info import Colorspace, Encoding, PageInfo, PdfInfo