Rename OCROptions to OcrOptions for consistency
Technically OCROptions is more Pythonic but we have several pre-existing classes named OcrWhatever. Go with the local flow.
This commit is contained in:
+16
-12
@@ -9,7 +9,7 @@ from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf.pdfinfo import PdfInfo
|
||||
from ocrmypdf.pdfinfo.info import PageInfo
|
||||
|
||||
@@ -20,14 +20,16 @@ if TYPE_CHECKING:
|
||||
class PdfContext:
|
||||
"""Holds the context for a particular run of the pipeline."""
|
||||
|
||||
options: OCROptions #: The specified options for processing this PDF.
|
||||
options: OcrOptions #: The specified options for processing this PDF.
|
||||
origin: Path #: The filename of the original input file.
|
||||
pdfinfo: PdfInfo #: Detailed data for this PDF.
|
||||
plugin_manager: OcrmypdfPluginManager #: PluginManager for processing the current PDF.
|
||||
plugin_manager: (
|
||||
OcrmypdfPluginManager #: PluginManager for processing the current PDF.
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
work_folder: Path,
|
||||
origin: Path,
|
||||
pdfinfo: PdfInfo,
|
||||
@@ -66,23 +68,25 @@ class PageContext:
|
||||
Must be pickle-able, so stores only intrinsic/simple data elements or those
|
||||
capable of their serializing themselves via ``__getstate__``.
|
||||
|
||||
Note: Uses OCROptions with JSON serialization for multiprocessing compatibility.
|
||||
Note: Uses OcrOptions with JSON serialization for multiprocessing compatibility.
|
||||
"""
|
||||
|
||||
origin: Path #: The filename of the original input file.
|
||||
pageno: int #: This page number (zero-based).
|
||||
pageinfo: PageInfo #: Information on this page.
|
||||
plugin_manager: OcrmypdfPluginManager #: PluginManager for processing the current PDF.
|
||||
plugin_manager: (
|
||||
OcrmypdfPluginManager #: PluginManager for processing the current PDF.
|
||||
)
|
||||
|
||||
def __init__(self, pdf_context: PdfContext, pageno):
|
||||
self.work_folder = pdf_context.work_folder
|
||||
self.origin = pdf_context.origin
|
||||
# Store OCROptions directly instead of Namespace
|
||||
# Store OcrOptions directly instead of Namespace
|
||||
self.options = pdf_context.options
|
||||
self.pageno = pageno
|
||||
self.pageinfo = pdf_context.pdfinfo[pageno]
|
||||
self.plugin_manager = pdf_context.plugin_manager
|
||||
# Ensure no reference to PdfContext which contains OCROptions
|
||||
# Ensure no reference to PdfContext which contains OcrOptions
|
||||
self._pdf_context = None
|
||||
|
||||
def get_path(self, name: str) -> Path:
|
||||
@@ -98,7 +102,7 @@ class PageContext:
|
||||
|
||||
options_json = self.options.model_dump_json_safe()
|
||||
state['options_json'] = options_json
|
||||
# Remove the OCROptions object to avoid pickle issues
|
||||
# Remove the OcrOptions object to avoid pickle issues
|
||||
del state['options']
|
||||
|
||||
# Remove any potential references to Pydantic objects
|
||||
@@ -108,10 +112,10 @@ class PageContext:
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
|
||||
# Reconstruct OCROptions from JSON if available
|
||||
# Reconstruct OcrOptions from JSON if available
|
||||
if 'options_json' in state:
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
|
||||
self.options = OCROptions.model_validate_json_safe(state['options_json'])
|
||||
self.options = OcrOptions.model_validate_json_safe(state['options_json'])
|
||||
# Otherwise, we have a fallback Namespace (shouldn't happen in normal operation)
|
||||
# Leave it as-is for compatibility
|
||||
|
||||
@@ -90,7 +90,7 @@ def _pages_from_ranges(ranges: str) -> set[int]:
|
||||
return set(pages)
|
||||
|
||||
|
||||
class OCROptions(BaseModel):
|
||||
class OcrOptions(BaseModel):
|
||||
"""Internal options model that can masquerade as argparse.Namespace.
|
||||
|
||||
This model provides proper typing and validation while maintaining
|
||||
@@ -210,7 +210,6 @@ class OCROptions(BaseModel):
|
||||
default_factory=dict, exclude=True, alias='_extra_attrs'
|
||||
)
|
||||
|
||||
|
||||
@field_validator('languages')
|
||||
@classmethod
|
||||
def validate_languages(cls, v):
|
||||
@@ -459,7 +458,7 @@ class OCROptions(BaseModel):
|
||||
return json.dumps(serializable_data)
|
||||
|
||||
@classmethod
|
||||
def model_validate_json_safe(cls, json_str: str) -> OCROptions:
|
||||
def model_validate_json_safe(cls, json_str: str) -> OcrOptions:
|
||||
"""Reconstruct from JSON with special handling for non-serializable types."""
|
||||
data = json.loads(json_str)
|
||||
|
||||
@@ -547,25 +546,25 @@ class OCROptions(BaseModel):
|
||||
for field_name in model_class.model_fields:
|
||||
# Try namespace_field pattern first (e.g., tesseract_timeout)
|
||||
flat_name = f"{namespace}_{field_name}"
|
||||
if flat_name in OCROptions.model_fields:
|
||||
if flat_name in OcrOptions.model_fields:
|
||||
value = getattr(self, flat_name)
|
||||
if value is not None:
|
||||
kwargs[field_name] = _convert_value(value)
|
||||
# Also check direct field name (for fields like jbig2_lossy)
|
||||
elif field_name in OCROptions.model_fields:
|
||||
elif field_name in OcrOptions.model_fields:
|
||||
value = getattr(self, field_name)
|
||||
if value is not None:
|
||||
kwargs[field_name] = _convert_value(value)
|
||||
# Check for special mappings
|
||||
elif namespace == 'optimize' and field_name == 'level':
|
||||
# 'optimize' field maps to 'level' in OptimizeOptions
|
||||
if 'optimize' in OCROptions.model_fields:
|
||||
if 'optimize' in OcrOptions.model_fields:
|
||||
value = getattr(self, 'optimize')
|
||||
if value is not None:
|
||||
kwargs[field_name] = _convert_value(value)
|
||||
elif namespace == 'optimize' and field_name == 'jpeg_quality':
|
||||
# jpg_quality maps to jpeg_quality
|
||||
if 'jpg_quality' in OCROptions.model_fields:
|
||||
if 'jpg_quality' in OcrOptions.model_fields:
|
||||
value = getattr(self, 'jpg_quality')
|
||||
if value is not None:
|
||||
kwargs[field_name] = _convert_value(value)
|
||||
|
||||
@@ -28,7 +28,7 @@ from ocrmypdf._concurrent import Executor
|
||||
from ocrmypdf._exec import unpaper
|
||||
from ocrmypdf._jobcontext import PageContext, PdfContext
|
||||
from ocrmypdf._metadata import repair_docinfo_nuls
|
||||
from ocrmypdf._options import OCROptions, ProcessingMode
|
||||
from ocrmypdf._options import OcrOptions, ProcessingMode
|
||||
from ocrmypdf.exceptions import (
|
||||
DigitalSignatureError,
|
||||
DpiError,
|
||||
@@ -64,7 +64,7 @@ VECTOR_PAGE_DPI = 400
|
||||
register_heif_opener()
|
||||
|
||||
|
||||
def triage_image_file(input_file: Path, output_file: Path, options: OCROptions) -> None:
|
||||
def triage_image_file(input_file: Path, output_file: Path, options: OcrOptions) -> None:
|
||||
"""Triage the input image file.
|
||||
|
||||
If the input file is an image, check its resolution and convert it to PDF.
|
||||
@@ -163,7 +163,7 @@ def _pdf_guess_version(input_file: Path, search_window=1024) -> str:
|
||||
|
||||
|
||||
def triage(
|
||||
original_filename: str, input_file: Path, output_file: Path, options: OCROptions
|
||||
original_filename: str, input_file: Path, output_file: Path, options: OcrOptions
|
||||
) -> Path:
|
||||
"""Triage the input file. We can handle PDFs and images."""
|
||||
try:
|
||||
|
||||
@@ -30,7 +30,7 @@ from ocrmypdf._concurrent import Executor, setup_executor
|
||||
from ocrmypdf._jobcontext import PageContext, PdfContext
|
||||
from ocrmypdf._logging import PageNumberFilter
|
||||
from ocrmypdf._metadata import metadata_fixup
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._pipeline import (
|
||||
convert_to_pdfa,
|
||||
create_ocr_image,
|
||||
@@ -205,7 +205,7 @@ def worker_init(max_pixels: int | None) -> None:
|
||||
@contextmanager
|
||||
def manage_debug_log_handler(
|
||||
*,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
work_folder: Path,
|
||||
):
|
||||
remover = None
|
||||
@@ -254,8 +254,8 @@ def manage_work_folder(*, work_folder: Path, retain: bool, print_location: bool)
|
||||
|
||||
|
||||
def cli_exception_handler(
|
||||
fn: Callable[[OCROptions, OcrmypdfPluginManager], ExitCode],
|
||||
options: OCROptions,
|
||||
fn: Callable[[OcrOptions, OcrmypdfPluginManager], ExitCode],
|
||||
options: OcrOptions,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> ExitCode:
|
||||
"""Convert exceptions into command line error messages and exit codes.
|
||||
@@ -319,14 +319,14 @@ def cli_exception_handler(
|
||||
|
||||
|
||||
def setup_pipeline(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> Executor:
|
||||
# Any changes to options will not take effect for options that are already
|
||||
# bound to function parameters in the pipeline. (For example
|
||||
# options.input_file, options.pdf_renderer are already bound.)
|
||||
# Note: OCROptions is immutable, so we can't modify options.jobs directly
|
||||
# The jobs field should already be set correctly during OCROptions creation
|
||||
# Note: OcrOptions is immutable, so we can't modify options.jobs directly
|
||||
# The jobs field should already be set correctly during OcrOptions creation
|
||||
|
||||
# Apply PIL max image pixels side effect
|
||||
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
|
||||
|
||||
@@ -16,7 +16,7 @@ import PIL
|
||||
from ocrmypdf._concurrent import Executor
|
||||
from ocrmypdf._graft import OcrGrafter
|
||||
from ocrmypdf._jobcontext import PageContext, PdfContext
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._pipeline import copy_final
|
||||
from ocrmypdf._pipelines._common import (
|
||||
HOCRResult,
|
||||
@@ -104,7 +104,7 @@ def exec_hocr_to_ocr_pdf(context: PdfContext, executor: Executor) -> Sequence[st
|
||||
|
||||
|
||||
def run_hocr_to_ocr_pdf_pipeline(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
*,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> ExitCode:
|
||||
|
||||
@@ -18,7 +18,7 @@ import PIL
|
||||
from ocrmypdf._concurrent import Executor
|
||||
from ocrmypdf._graft import OcrGrafter
|
||||
from ocrmypdf._jobcontext import PageContext, PdfContext
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._pipeline import (
|
||||
copy_final,
|
||||
is_ocr_required,
|
||||
@@ -162,7 +162,7 @@ def exec_concurrent(context: PdfContext, executor: Executor) -> Sequence[str]:
|
||||
|
||||
|
||||
def _run_pipeline(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> ExitCode:
|
||||
with (
|
||||
@@ -197,7 +197,7 @@ def _run_pipeline(
|
||||
|
||||
|
||||
def run_pipeline_cli(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
*,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> ExitCode:
|
||||
@@ -212,7 +212,7 @@ def run_pipeline_cli(
|
||||
|
||||
|
||||
def run_pipeline(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
*,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> ExitCode:
|
||||
|
||||
@@ -15,7 +15,7 @@ import PIL
|
||||
|
||||
from ocrmypdf._concurrent import Executor
|
||||
from ocrmypdf._jobcontext import PageContext, PdfContext
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._pipeline import (
|
||||
is_ocr_required,
|
||||
ocr_engine_hocr,
|
||||
@@ -84,7 +84,7 @@ def exec_pdf_to_hocr(context: PdfContext, executor: Executor) -> None:
|
||||
|
||||
|
||||
def run_hocr_pipeline(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
*,
|
||||
plugin_manager: OcrmypdfPluginManager,
|
||||
) -> None:
|
||||
|
||||
@@ -20,7 +20,7 @@ from pydantic import BaseModel
|
||||
|
||||
import ocrmypdf.builtin_plugins
|
||||
from ocrmypdf import Executor, PdfContext, pluginspec
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._progressbar import ProgressBar
|
||||
from ocrmypdf.helpers import Resolution
|
||||
from ocrmypdf.pluginspec import OcrEngine
|
||||
@@ -140,7 +140,7 @@ class OcrmypdfPluginManager:
|
||||
rotation: int | None,
|
||||
filter_vector: bool,
|
||||
stop_on_soft_error: bool,
|
||||
options: OCROptions | None,
|
||||
options: OcrOptions | None,
|
||||
use_cropbox: bool,
|
||||
) -> Path | None:
|
||||
"""Rasterize one page of a PDF at specified resolution."""
|
||||
@@ -178,11 +178,11 @@ class OcrmypdfPluginManager:
|
||||
page=page, image_filename=image_filename, output_pdf=output_pdf
|
||||
)
|
||||
|
||||
def get_ocr_engine(self, *, options: OCROptions | None = None) -> OcrEngine | None:
|
||||
def get_ocr_engine(self, *, options: OcrOptions | None = None) -> OcrEngine | None:
|
||||
"""Returns an OcrEngine to use for processing.
|
||||
|
||||
Args:
|
||||
options: OCROptions to pass to the hook for engine selection.
|
||||
options: OcrOptions to pass to the hook for engine selection.
|
||||
"""
|
||||
return self._pm.hook.get_ocr_engine(options=options)
|
||||
|
||||
@@ -251,11 +251,11 @@ class OcrmypdfPluginManager:
|
||||
"""Returns plugin option models keyed by namespace."""
|
||||
return self._pm.hook.register_options()
|
||||
|
||||
def check_options(self, *, options: OCROptions) -> list[None]:
|
||||
def check_options(self, *, options: OcrOptions) -> list[None]:
|
||||
"""Called to validate options after parsing."""
|
||||
return self._pm.hook.check_options(options=options)
|
||||
|
||||
def validate(self, *, pdfinfo: PdfInfo, options: OCROptions) -> list[None]:
|
||||
def validate(self, *, pdfinfo: PdfInfo, options: OcrOptions) -> list[None]:
|
||||
"""Called to validate options and pdfinfo after PDF is loaded."""
|
||||
return self._pm.hook.validate(pdfinfo=pdfinfo, options=options)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class PluginOptionRegistry:
|
||||
"""Registry for plugin option models.
|
||||
|
||||
This registry collects option models from plugins during initialization.
|
||||
Plugin options can be accessed via nested namespaces on OCROptions
|
||||
Plugin options can be accessed via nested namespaces on OcrOptions
|
||||
(e.g., options.tesseract.timeout) or via flat field names for backward
|
||||
compatibility (e.g., options.tesseract_timeout).
|
||||
"""
|
||||
|
||||
+11
-10
@@ -17,7 +17,7 @@ import pikepdf
|
||||
|
||||
from ocrmypdf._defaults import DEFAULT_ROTATE_PAGES_THRESHOLD
|
||||
from ocrmypdf._exec import unpaper
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._plugin_manager import OcrmypdfPluginManager
|
||||
from ocrmypdf.exceptions import (
|
||||
BadArgsError,
|
||||
@@ -47,7 +47,7 @@ def check_platform() -> None:
|
||||
|
||||
|
||||
def check_options_languages(
|
||||
options: OCROptions, ocr_engine_languages: list[str]
|
||||
options: OcrOptions, ocr_engine_languages: list[str]
|
||||
) -> None:
|
||||
if not ocr_engine_languages:
|
||||
return
|
||||
@@ -72,7 +72,7 @@ def check_options_languages(
|
||||
raise MissingDependencyError(msg)
|
||||
|
||||
|
||||
def check_options_sidecar(options: OCROptions) -> None:
|
||||
def check_options_sidecar(options: OcrOptions) -> None:
|
||||
if options.sidecar == '\0':
|
||||
if options.output_file == '-':
|
||||
raise BadArgsError("--sidecar filename needed when output file is stdout.")
|
||||
@@ -87,7 +87,7 @@ def check_options_sidecar(options: OCROptions) -> None:
|
||||
)
|
||||
|
||||
|
||||
def check_options_preprocessing(options: OCROptions) -> None:
|
||||
def check_options_preprocessing(options: OcrOptions) -> None:
|
||||
if options.clean_final:
|
||||
options.clean = True
|
||||
if options.unpaper_args and not options.clean:
|
||||
@@ -114,14 +114,14 @@ def check_options_preprocessing(options: OCROptions) -> None:
|
||||
raise BadArgsError("--unpaper-args: " + str(e)) from e
|
||||
|
||||
|
||||
def _check_plugin_invariant_options(options: OCROptions) -> None:
|
||||
def _check_plugin_invariant_options(options: OcrOptions) -> None:
|
||||
check_platform()
|
||||
check_options_sidecar(options)
|
||||
check_options_preprocessing(options)
|
||||
|
||||
|
||||
def _check_plugin_options(
|
||||
options: OCROptions, plugin_manager: OcrmypdfPluginManager
|
||||
options: OcrOptions, plugin_manager: OcrmypdfPluginManager
|
||||
) -> None:
|
||||
# First, let plugins check their external dependencies
|
||||
plugin_manager.check_options(options=options)
|
||||
@@ -134,11 +134,12 @@ def _check_plugin_options(
|
||||
|
||||
# Finally, run comprehensive validation using the coordinator
|
||||
from ocrmypdf._validation_coordinator import ValidationCoordinator
|
||||
|
||||
coordinator = ValidationCoordinator(plugin_manager)
|
||||
coordinator.validate_all_options(options)
|
||||
|
||||
|
||||
def check_options(options: OCROptions, plugin_manager: OcrmypdfPluginManager) -> None:
|
||||
def check_options(options: OcrOptions, plugin_manager: OcrmypdfPluginManager) -> None:
|
||||
"""Check options for validity and consistency.
|
||||
|
||||
This function coordinates validation across the entire system:
|
||||
@@ -151,7 +152,7 @@ def check_options(options: OCROptions, plugin_manager: OcrmypdfPluginManager) ->
|
||||
_check_plugin_options(options, plugin_manager)
|
||||
|
||||
|
||||
def create_input_file(options: OCROptions, work_folder: Path) -> tuple[Path, str]:
|
||||
def create_input_file(options: OcrOptions, work_folder: Path) -> tuple[Path, str]:
|
||||
if options.input_file == '-':
|
||||
# stdin
|
||||
log.info('reading file from standard input')
|
||||
@@ -196,7 +197,7 @@ def create_input_file(options: OCROptions, work_folder: Path) -> tuple[Path, str
|
||||
raise InputFileError(msg) from e
|
||||
|
||||
|
||||
def check_requested_output_file(options: OCROptions) -> None:
|
||||
def check_requested_output_file(options: OcrOptions) -> None:
|
||||
if options.output_file == '-':
|
||||
if sys.stdout.isatty():
|
||||
raise BadArgsError(
|
||||
@@ -214,7 +215,7 @@ def check_requested_output_file(options: OCROptions) -> None:
|
||||
|
||||
|
||||
def report_output_file_size(
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
input_file: Path,
|
||||
output_file: Path,
|
||||
optimize_messages: Sequence[str] | None = None,
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
import pluggy
|
||||
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,7 +24,7 @@ class ValidationCoordinator:
|
||||
self.plugin_manager = plugin_manager
|
||||
self.registry = getattr(plugin_manager, '_option_registry', None)
|
||||
|
||||
def validate_all_options(self, options: OCROptions) -> None:
|
||||
def validate_all_options(self, options: OcrOptions) -> None:
|
||||
"""Run comprehensive validation on all options.
|
||||
|
||||
This runs validation in the correct order:
|
||||
@@ -41,7 +41,7 @@ class ValidationCoordinator:
|
||||
# Step 2: Cross-cutting validation
|
||||
self._validate_cross_cutting_concerns(options)
|
||||
|
||||
def _validate_plugin_contexts(self, options: OCROptions) -> None:
|
||||
def _validate_plugin_contexts(self, options: OcrOptions) -> None:
|
||||
"""Validate plugin options that require external context."""
|
||||
# For now, we'll run the plugin validation directly since the models
|
||||
# are still being integrated. This ensures the validation warnings
|
||||
@@ -53,7 +53,7 @@ class ValidationCoordinator:
|
||||
# Run Optimize validation
|
||||
self._validate_optimize_options(options)
|
||||
|
||||
def _validate_tesseract_options(self, options: OCROptions) -> None:
|
||||
def _validate_tesseract_options(self, options: OcrOptions) -> None:
|
||||
"""Validate Tesseract options."""
|
||||
# Check pagesegmode warning
|
||||
if options.tesseract.pagesegmode in (0, 2):
|
||||
@@ -74,6 +74,7 @@ class ValidationCoordinator:
|
||||
|
||||
# Check for blocked languages
|
||||
from ocrmypdf.exceptions import BadArgsError
|
||||
|
||||
DENIED_LANGUAGES = {'equ', 'osd'}
|
||||
if DENIED_LANGUAGES & set(options.languages):
|
||||
raise BadArgsError(
|
||||
@@ -83,19 +84,21 @@ class ValidationCoordinator:
|
||||
"Remove them from the -l/--language argument."
|
||||
)
|
||||
|
||||
def _validate_optimize_options(self, options: OCROptions) -> None:
|
||||
def _validate_optimize_options(self, options: OcrOptions) -> None:
|
||||
"""Validate optimization options."""
|
||||
# Check optimization consistency
|
||||
if options.optimize == 0 and any([
|
||||
options.png_quality and options.png_quality > 0,
|
||||
options.jpeg_quality and options.jpeg_quality > 0
|
||||
]):
|
||||
if options.optimize == 0 and any(
|
||||
[
|
||||
options.png_quality and options.png_quality > 0,
|
||||
options.jpeg_quality and options.jpeg_quality > 0,
|
||||
]
|
||||
):
|
||||
log.warning(
|
||||
"The arguments --png-quality and --jpeg-quality "
|
||||
"will be ignored because --optimize=0."
|
||||
)
|
||||
|
||||
def _validate_cross_cutting_concerns(self, options: OCROptions) -> None:
|
||||
def _validate_cross_cutting_concerns(self, options: OcrOptions) -> None:
|
||||
"""Validate cross-cutting concerns that span multiple plugins."""
|
||||
from ocrmypdf._options import ProcessingMode
|
||||
|
||||
@@ -115,7 +118,8 @@ class ValidationCoordinator:
|
||||
|
||||
# Validate output type compatibility
|
||||
if options.output_type == 'none' and str(options.output_file) not in (
|
||||
os.devnull, '-'
|
||||
os.devnull,
|
||||
'-',
|
||||
):
|
||||
raise ValueError(
|
||||
"Since you specified `--output-type none`, the output file "
|
||||
@@ -134,7 +138,7 @@ class ValidationCoordinator:
|
||||
"--output-type is one of 'pdfa', 'pdfa-1', or 'pdfa-2'"
|
||||
)
|
||||
|
||||
def _handle_deprecated_pdf_renderer(self, options: OCROptions) -> None:
|
||||
def _handle_deprecated_pdf_renderer(self, options: OcrOptions) -> None:
|
||||
"""Handle deprecated pdf_renderer values by redirecting to fpdf2."""
|
||||
if options.pdf_renderer in ('hocr', 'hocrdebug'):
|
||||
log.info(
|
||||
|
||||
+30
-30
@@ -51,7 +51,7 @@ from typing import BinaryIO
|
||||
from warnings import warn
|
||||
|
||||
from ocrmypdf._logging import PageNumberFilter
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._pipelines.hocr_to_ocr_pdf import run_hocr_to_ocr_pdf_pipeline
|
||||
from ocrmypdf._pipelines.ocr import run_pipeline, run_pipeline_cli
|
||||
from ocrmypdf._pipelines.pdf_to_hocr import run_hocr_pipeline
|
||||
@@ -120,8 +120,8 @@ def setup_plugin_infrastructure(
|
||||
registry.register_option_model(namespace, model_class)
|
||||
all_plugin_models[namespace] = model_class
|
||||
|
||||
# Register plugin models with OCROptions for dynamic nested access
|
||||
OCROptions.register_plugin_models(all_plugin_models)
|
||||
# Register plugin models with OcrOptions for dynamic nested access
|
||||
OcrOptions.register_plugin_models(all_plugin_models)
|
||||
|
||||
# Store registry in plugin manager for later access
|
||||
plugin_manager._option_registry = registry
|
||||
@@ -234,7 +234,7 @@ def configure_logging(
|
||||
|
||||
def create_options(
|
||||
*, input_file: PathOrIO, output_file: PathOrIO, parser: ArgumentParser, **kwargs
|
||||
) -> OCROptions:
|
||||
) -> OcrOptions:
|
||||
"""Construct an options object from the input/output files and keyword arguments.
|
||||
|
||||
Args:
|
||||
@@ -244,12 +244,12 @@ def create_options(
|
||||
**kwargs: Keyword arguments.
|
||||
|
||||
Returns:
|
||||
OCROptions: An options object containing the parsed arguments.
|
||||
OcrOptions: An options object containing the parsed arguments.
|
||||
|
||||
Raises:
|
||||
TypeError: If the type of a keyword argument is not supported.
|
||||
"""
|
||||
# Prepare kwargs for direct OCROptions construction
|
||||
# Prepare kwargs for direct OcrOptions construction
|
||||
options_kwargs = kwargs.copy()
|
||||
|
||||
# Set input and output files
|
||||
@@ -260,16 +260,16 @@ def create_options(
|
||||
if 'sidecar' in options_kwargs and isinstance(
|
||||
options_kwargs['sidecar'], BinaryIO | IOBase
|
||||
):
|
||||
# Keep the stream object as-is - OCROptions can handle it
|
||||
# Keep the stream object as-is - OcrOptions can handle it
|
||||
pass
|
||||
|
||||
# Remove None values to let OCROptions use its defaults
|
||||
# Remove None values to let OcrOptions use its defaults
|
||||
options_kwargs = {k: v for k, v in options_kwargs.items() if v is not None}
|
||||
|
||||
# Remove any kwargs that aren't OCROptions fields and store in extra_attrs
|
||||
# Remove any kwargs that aren't OcrOptions fields and store in extra_attrs
|
||||
extra_attrs = {}
|
||||
ocr_fields = set(OCROptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OCROptions model validator
|
||||
ocr_fields = set(OcrOptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OcrOptions model validator
|
||||
legacy_mode_flags = {'force_ocr', 'skip_text', 'redo_ocr'}
|
||||
|
||||
# Known extra attributes that should be preserved
|
||||
@@ -280,16 +280,16 @@ def create_options(
|
||||
continue
|
||||
extra_attrs[key] = options_kwargs.pop(key)
|
||||
|
||||
# Create OCROptions directly
|
||||
# Create OcrOptions directly
|
||||
try:
|
||||
options = OCROptions(**options_kwargs)
|
||||
options = OcrOptions(**options_kwargs)
|
||||
# Add any extra attributes
|
||||
if extra_attrs:
|
||||
options.extra_attrs.update(extra_attrs)
|
||||
return options
|
||||
except Exception as e:
|
||||
# If direct construction fails, provide a helpful error message
|
||||
raise TypeError(f"Failed to create OCROptions: {e}") from e
|
||||
raise TypeError(f"Failed to create OcrOptions: {e}") from e
|
||||
|
||||
|
||||
def ocr( # noqa: D417
|
||||
@@ -538,7 +538,7 @@ def _pdf_to_hocr( # noqa: D417
|
||||
else:
|
||||
plugins = list(plugins)
|
||||
|
||||
# Prepare kwargs for direct OCROptions construction
|
||||
# Prepare kwargs for direct OcrOptions construction
|
||||
options_kwargs = kwargs.copy()
|
||||
|
||||
# Set input file and handle special output_folder case
|
||||
@@ -558,16 +558,16 @@ def _pdf_to_hocr( # noqa: D417
|
||||
if plugins:
|
||||
options_kwargs['plugins'] = plugins
|
||||
|
||||
# Remove None values to let OCROptions use its defaults
|
||||
# Remove None values to let OcrOptions use its defaults
|
||||
options_kwargs = {k: v for k, v in options_kwargs.items() if v is not None}
|
||||
|
||||
# Add output_folder to options_kwargs since it's now a proper field
|
||||
options_kwargs['output_folder'] = output_folder
|
||||
|
||||
# Remove any kwargs that aren't OCROptions fields and store in extra_attrs
|
||||
# Remove any kwargs that aren't OcrOptions fields and store in extra_attrs
|
||||
extra_attrs = {}
|
||||
ocr_fields = set(OCROptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OCROptions model validator
|
||||
ocr_fields = set(OcrOptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OcrOptions model validator
|
||||
legacy_mode_flags = {'force_ocr', 'skip_text', 'redo_ocr'}
|
||||
known_extra = {'progress_bar', 'plugins'}
|
||||
|
||||
@@ -584,15 +584,15 @@ def _pdf_to_hocr( # noqa: D417
|
||||
|
||||
plugin_manager.add_options(parser=get_parser())
|
||||
|
||||
# Create OCROptions directly
|
||||
# Create OcrOptions directly
|
||||
try:
|
||||
options = OCROptions(**options_kwargs)
|
||||
options = OcrOptions(**options_kwargs)
|
||||
# Add any extra attributes
|
||||
if extra_attrs:
|
||||
options.extra_attrs.update(extra_attrs)
|
||||
except Exception as e:
|
||||
raise TypeError(
|
||||
f"Failed to create OCROptions for hOCR pipeline: {e}"
|
||||
f"Failed to create OcrOptions for hOCR pipeline: {e}"
|
||||
) from e
|
||||
|
||||
return run_hocr_pipeline(options=options, plugin_manager=plugin_manager)
|
||||
@@ -643,7 +643,7 @@ def _hocr_to_ocr_pdf( # noqa: D417
|
||||
else:
|
||||
plugins = list(plugins)
|
||||
|
||||
# Prepare kwargs for direct OCROptions construction
|
||||
# Prepare kwargs for direct OcrOptions construction
|
||||
options_kwargs = kwargs.copy()
|
||||
|
||||
# Set output file and handle special work_folder case
|
||||
@@ -663,7 +663,7 @@ def _hocr_to_ocr_pdf( # noqa: D417
|
||||
if plugins:
|
||||
options_kwargs['plugins'] = plugins
|
||||
|
||||
# Remove None values to let OCROptions use its defaults
|
||||
# Remove None values to let OcrOptions use its defaults
|
||||
options_kwargs = {k: v for k, v in options_kwargs.items() if v is not None}
|
||||
|
||||
# Warn about deprecated jbig2 options and remove from kwargs
|
||||
@@ -680,10 +680,10 @@ def _hocr_to_ocr_pdf( # noqa: D417
|
||||
# Add work_folder to options_kwargs since it's now a proper field
|
||||
options_kwargs['work_folder'] = work_folder
|
||||
|
||||
# Remove any kwargs that aren't OCROptions fields and store in extra_attrs
|
||||
# Remove any kwargs that aren't OcrOptions fields and store in extra_attrs
|
||||
extra_attrs = {}
|
||||
ocr_fields = set(OCROptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OCROptions model validator
|
||||
ocr_fields = set(OcrOptions.model_fields.keys())
|
||||
# Legacy mode flags are handled by OcrOptions model validator
|
||||
legacy_mode_flags = {'force_ocr', 'skip_text', 'redo_ocr'}
|
||||
known_extra = {'progress_bar', 'plugins'}
|
||||
|
||||
@@ -700,15 +700,15 @@ def _hocr_to_ocr_pdf( # noqa: D417
|
||||
|
||||
plugin_manager.add_options(parser=get_parser())
|
||||
|
||||
# Create OCROptions directly
|
||||
# Create OcrOptions directly
|
||||
try:
|
||||
options = OCROptions(**options_kwargs)
|
||||
options = OcrOptions(**options_kwargs)
|
||||
# Add any extra attributes
|
||||
if extra_attrs:
|
||||
options.extra_attrs.update(extra_attrs)
|
||||
except Exception as e:
|
||||
raise TypeError(
|
||||
f"Failed to create OCROptions for hOCR to PDF pipeline: {e}"
|
||||
f"Failed to create OcrOptions for hOCR to PDF pipeline: {e}"
|
||||
) from e
|
||||
|
||||
return run_hocr_to_ocr_pdf_pipeline(
|
||||
|
||||
@@ -23,7 +23,7 @@ from ocrmypdf.hocrtransform import BoundingBox, OcrClass, OcrElement
|
||||
from ocrmypdf.pluginspec import OcrEngine, OrientationConfidence
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
|
||||
|
||||
class NullOcrEngine(OcrEngine):
|
||||
@@ -39,7 +39,7 @@ class NullOcrEngine(OcrEngine):
|
||||
return "none"
|
||||
|
||||
@staticmethod
|
||||
def creator_tag(options: OCROptions) -> str:
|
||||
def creator_tag(options: OcrOptions) -> str:
|
||||
"""Return creator tag for PDF metadata."""
|
||||
return "OCRmyPDF (no OCR)"
|
||||
|
||||
@@ -48,17 +48,17 @@ class NullOcrEngine(OcrEngine):
|
||||
return "No OCR engine"
|
||||
|
||||
@staticmethod
|
||||
def languages(options: OCROptions) -> set[str]:
|
||||
def languages(options: OcrOptions) -> set[str]:
|
||||
"""Return supported languages (empty set for null engine)."""
|
||||
return set()
|
||||
|
||||
@staticmethod
|
||||
def get_orientation(input_file: Path, options: OCROptions) -> OrientationConfidence:
|
||||
def get_orientation(input_file: Path, options: OcrOptions) -> OrientationConfidence:
|
||||
"""Return neutral orientation (no rotation detected)."""
|
||||
return OrientationConfidence(angle=0, confidence=0.0)
|
||||
|
||||
@staticmethod
|
||||
def get_deskew(input_file: Path, options: OCROptions) -> float:
|
||||
def get_deskew(input_file: Path, options: OcrOptions) -> float:
|
||||
"""Return zero deskew angle."""
|
||||
return 0.0
|
||||
|
||||
@@ -70,7 +70,7 @@ class NullOcrEngine(OcrEngine):
|
||||
@staticmethod
|
||||
def generate_ocr(
|
||||
input_file: Path,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
page_number: int = 0,
|
||||
) -> tuple[OcrElement, str]:
|
||||
"""Generate empty OCR results.
|
||||
@@ -104,7 +104,7 @@ class NullOcrEngine(OcrEngine):
|
||||
input_file: Path,
|
||||
output_hocr: Path,
|
||||
output_text: Path,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
) -> None:
|
||||
"""Generate empty hOCR file.
|
||||
|
||||
@@ -137,7 +137,7 @@ class NullOcrEngine(OcrEngine):
|
||||
input_file: Path,
|
||||
output_pdf: Path,
|
||||
output_text: Path,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
) -> None:
|
||||
"""NullOcrEngine cannot generate PDFs directly.
|
||||
|
||||
|
||||
+11
-11
@@ -12,7 +12,7 @@ from typing import Any, TypeVar
|
||||
|
||||
from ocrmypdf._defaults import DEFAULT_ROTATE_PAGES_THRESHOLD
|
||||
from ocrmypdf._defaults import PROGRAM_NAME as _PROGRAM_NAME
|
||||
from ocrmypdf._options import OCROptions, ProcessingMode
|
||||
from ocrmypdf._options import OcrOptions, ProcessingMode
|
||||
from ocrmypdf._plugin_manager import OcrmypdfPluginManager
|
||||
from ocrmypdf._version import __version__ as _VERSION
|
||||
|
||||
@@ -473,8 +473,8 @@ plugins_only_parser.add_argument(
|
||||
)
|
||||
|
||||
|
||||
def namespace_to_options(ns) -> OCROptions:
|
||||
"""Convert argparse.Namespace to OCROptions.
|
||||
def namespace_to_options(ns) -> OcrOptions:
|
||||
"""Convert argparse.Namespace to OcrOptions.
|
||||
|
||||
This function encapsulates CLI-specific knowledge of how command line
|
||||
arguments map to our internal options model.
|
||||
@@ -483,14 +483,14 @@ def namespace_to_options(ns) -> OCROptions:
|
||||
known_fields = {}
|
||||
extra_attrs = {}
|
||||
|
||||
# Legacy boolean flags that map to mode - handled by OCROptions model validator
|
||||
# Legacy boolean flags that map to mode - handled by OcrOptions model validator
|
||||
legacy_mode_flags = {'force_ocr', 'skip_text', 'redo_ocr'}
|
||||
|
||||
for key, value in vars(ns).items():
|
||||
if key in OCROptions.model_fields:
|
||||
if key in OcrOptions.model_fields:
|
||||
known_fields[key] = value
|
||||
elif key in legacy_mode_flags:
|
||||
# Pass legacy flags to OCROptions for conversion to mode
|
||||
# Pass legacy flags to OcrOptions for conversion to mode
|
||||
known_fields[key] = value
|
||||
else:
|
||||
extra_attrs[key] = value
|
||||
@@ -503,15 +503,15 @@ def namespace_to_options(ns) -> OCROptions:
|
||||
if 'work_folder' in extra_attrs and 'input_file' not in known_fields:
|
||||
known_fields['input_file'] = '/dev/null' # Placeholder
|
||||
|
||||
instance = OCROptions(**known_fields)
|
||||
instance = OcrOptions(**known_fields)
|
||||
instance.extra_attrs = extra_attrs
|
||||
return instance
|
||||
|
||||
|
||||
def get_options_and_plugins(
|
||||
args=None,
|
||||
) -> tuple[OCROptions, OcrmypdfPluginManager]:
|
||||
"""Parse command line arguments and return OCROptions and plugin manager.
|
||||
) -> tuple[OcrOptions, OcrmypdfPluginManager]:
|
||||
"""Parse command line arguments and return OcrOptions and plugin manager.
|
||||
|
||||
This is the main entry point for CLI argument processing. It handles
|
||||
plugin discovery, argument parsing, and conversion to our internal
|
||||
@@ -521,7 +521,7 @@ def get_options_and_plugins(
|
||||
args: Command line arguments. If None, uses sys.argv.
|
||||
|
||||
Returns:
|
||||
Tuple of (OCROptions, PluginManager)
|
||||
Tuple of (OcrOptions, PluginManager)
|
||||
"""
|
||||
# Import here to avoid circular imports
|
||||
from ocrmypdf.api import setup_plugin_infrastructure
|
||||
@@ -539,7 +539,7 @@ def get_options_and_plugins(
|
||||
# Parse all arguments
|
||||
namespace = parser.parse_args(args=args)
|
||||
|
||||
# Convert to OCROptions
|
||||
# Convert to OcrOptions
|
||||
options = namespace_to_options(namespace)
|
||||
|
||||
return options, plugin_manager
|
||||
|
||||
@@ -721,12 +721,12 @@ def main(infile, outfile, level, jobs=1):
|
||||
from shutil import copy # pylint: disable=import-outside-toplevel
|
||||
from tempfile import TemporaryDirectory # pylint: disable=import-outside-toplevel
|
||||
|
||||
from ocrmypdf._options import OCROptions # pylint: disable=import-outside-toplevel
|
||||
from ocrmypdf._options import OcrOptions # pylint: disable=import-outside-toplevel
|
||||
|
||||
infile = Path(infile)
|
||||
|
||||
# Create OCROptions with optimization-specific settings
|
||||
options = OCROptions(
|
||||
# Create OcrOptions with optimization-specific settings
|
||||
options = OcrOptions(
|
||||
input_file=infile,
|
||||
output_file=outfile, # Required field
|
||||
jobs=jobs,
|
||||
|
||||
+13
-13
@@ -16,7 +16,7 @@ import pluggy
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ocrmypdf import Executor, PdfContext
|
||||
from ocrmypdf._options import OCROptions
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._progressbar import ProgressBar
|
||||
from ocrmypdf.helpers import Resolution
|
||||
|
||||
@@ -112,7 +112,7 @@ def register_options() -> dict[str, type[BaseModel]]:
|
||||
|
||||
|
||||
@hookspec
|
||||
def check_options(options: OCROptions) -> None:
|
||||
def check_options(options: OcrOptions) -> None:
|
||||
"""Called to ask the plugin to check all of the options.
|
||||
|
||||
The plugin may check if options that it added are valid.
|
||||
@@ -183,7 +183,7 @@ def get_progressbar_class() -> type[ProgressBar]: # type: ignore[return-value]
|
||||
|
||||
|
||||
@hookspec
|
||||
def validate(pdfinfo: PdfInfo, options: OCROptions) -> None:
|
||||
def validate(pdfinfo: PdfInfo, options: OcrOptions) -> None:
|
||||
"""Called to give a plugin an opportunity to review *options* and *pdfinfo*.
|
||||
|
||||
*options* contains the "work order" to process a particular file. *pdfinfo*
|
||||
@@ -214,7 +214,7 @@ def rasterize_pdf_page(
|
||||
rotation: int | None,
|
||||
filter_vector: bool,
|
||||
stop_on_soft_error: bool,
|
||||
options: OCROptions | None,
|
||||
options: OcrOptions | None,
|
||||
use_cropbox: bool,
|
||||
) -> Path: # type: ignore[return-value]
|
||||
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units.
|
||||
@@ -401,7 +401,7 @@ class OcrEngine(ABC):
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def creator_tag(options: OCROptions) -> str:
|
||||
def creator_tag(options: OcrOptions) -> str:
|
||||
"""Returns the creator tag to identify this software's role in creating the PDF.
|
||||
|
||||
This tag will be inserted in the XMP metadata and DocumentInfo dictionary
|
||||
@@ -422,7 +422,7 @@ class OcrEngine(ABC):
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def languages(options: OCROptions) -> Set[str]:
|
||||
def languages(options: OcrOptions) -> Set[str]:
|
||||
"""Returns the set of all languages that are supported by the engine.
|
||||
|
||||
Languages are typically given in 3-letter ISO 3166-1 codes, but actually
|
||||
@@ -431,18 +431,18 @@ class OcrEngine(ABC):
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_orientation(input_file: Path, options: OCROptions) -> OrientationConfidence:
|
||||
def get_orientation(input_file: Path, options: OcrOptions) -> OrientationConfidence:
|
||||
"""Returns the orientation of the image."""
|
||||
|
||||
@staticmethod
|
||||
def get_deskew(input_file: Path, options: OCROptions) -> float:
|
||||
def get_deskew(input_file: Path, options: OcrOptions) -> float:
|
||||
"""Returns the deskew angle of the image, in degrees."""
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def generate_hocr(
|
||||
input_file: Path, output_hocr: Path, output_text: Path, options: OCROptions
|
||||
input_file: Path, output_hocr: Path, output_text: Path, options: OcrOptions
|
||||
) -> None:
|
||||
"""Called to produce a hOCR file from a page image and sidecar text file.
|
||||
|
||||
@@ -465,7 +465,7 @@ class OcrEngine(ABC):
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def generate_pdf(
|
||||
input_file: Path, output_pdf: Path, output_text: Path, options: OCROptions
|
||||
input_file: Path, output_pdf: Path, output_text: Path, options: OcrOptions
|
||||
) -> None:
|
||||
"""Called to produce a text only PDF from a page image.
|
||||
|
||||
@@ -501,7 +501,7 @@ class OcrEngine(ABC):
|
||||
@staticmethod
|
||||
def generate_ocr(
|
||||
input_file: Path,
|
||||
options: OCROptions,
|
||||
options: OcrOptions,
|
||||
page_number: int = 0,
|
||||
) -> tuple[OcrElement, str]:
|
||||
"""Generate OCR results as an OcrElement tree.
|
||||
@@ -531,7 +531,7 @@ class OcrEngine(ABC):
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def get_ocr_engine(options: OCROptions | None) -> OcrEngine: # type: ignore[return-value]
|
||||
def get_ocr_engine(options: OcrOptions | None) -> OcrEngine: # type: ignore[return-value]
|
||||
"""Returns an OcrEngine to use for processing this file.
|
||||
|
||||
The OcrEngine may be instantiated multiple times, by both the main process
|
||||
@@ -542,7 +542,7 @@ def get_ocr_engine(options: OCROptions | None) -> OcrEngine: # type: ignore[ret
|
||||
engine. The hook caller will then try the next plugin.
|
||||
|
||||
Args:
|
||||
options: The current OCROptions, used to determine which engine
|
||||
options: The current OcrOptions, used to determine which engine
|
||||
to select. May be None for backward compatibility with external
|
||||
plugins.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user