Files
OCRmyPDF/webui/options.py
T

102 lines
3.2 KiB
Python

# SPDX-FileCopyrightText: 2026 James R. Barlow
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Validated OCR options for the batch web interface.
The whole point of this module is that *nothing* the browser sends is ever
interpolated into a command line unchecked. Every option is either a fixed
enum, a bounded integer, or a language code drawn from the set of language
packs actually installed in the image.
"""
from __future__ import annotations
from enum import StrEnum
from pydantic import BaseModel, ConfigDict, Field, field_validator
from webui.config import installed_languages
class Mode(StrEnum):
"""What to do about pages that already contain text."""
normal = "normal"
skip_text = "skip-text"
force_ocr = "force-ocr"
redo_ocr = "redo-ocr"
class OutputType(StrEnum):
"""Requested output conformance level."""
pdfa = "pdfa"
pdfa_1 = "pdfa-1"
pdfa_2 = "pdfa-2"
pdfa_3 = "pdfa-3"
pdf = "pdf"
class OcrOptions(BaseModel):
"""Options applied to every file in a batch."""
model_config = ConfigDict(extra="forbid", use_enum_values=False)
languages: list[str] = Field(default_factory=lambda: ["eng"], max_length=8)
mode: Mode = Mode.skip_text
output_type: OutputType = OutputType.pdfa
optimize: int = Field(default=1, ge=0, le=3)
deskew: bool = False
clean: bool = False
rotate_pages: bool = False
image_dpi: int = Field(default=300, ge=1, le=5000)
@field_validator("languages")
@classmethod
def _known_languages(cls, value: list[str]) -> list[str]:
if not value:
return ["eng"]
available = set(installed_languages())
unknown = [lang for lang in value if lang not in available]
if unknown:
raise ValueError(
"unknown or uninstalled language(s): " + ", ".join(sorted(unknown))
)
# Preserve caller order (Tesseract weights the first language most)
# while dropping duplicates.
seen: set[str] = set()
ordered = []
for lang in value:
if lang not in seen:
seen.add(lang)
ordered.append(lang)
return ordered
def to_args(self, *, jobs: int, is_image: bool) -> list[str]:
"""Render these options as ``ocrmypdf`` command line arguments.
Args:
jobs: Value for ``--jobs``, OCRmyPDF's internal worker count.
is_image: True when the input is an image rather than a PDF, in
which case ``--image-dpi`` is meaningful.
"""
args = [
f"--language={'+'.join(self.languages)}",
f"--output-type={self.output_type.value}",
f"--optimize={self.optimize}",
f"--jobs={jobs}",
]
if self.mode is not Mode.normal:
args.append(f"--{self.mode.value}")
if self.deskew:
args.append("--deskew")
if self.clean:
args.append("--clean")
if self.rotate_pages:
args.append("--rotate-pages")
if is_image:
args.append(f"--image-dpi={self.image_dpi}")
# A soft render error on one page shouldn't sink an unattended batch.
args.append("--continue-on-soft-render-error")
return args