67 lines
2.7 KiB
Python
67 lines
2.7 KiB
Python
"""PDF -> PDF passthrough (.pdf uploads).
|
|
|
|
A valid, unencrypted PDF is validated with pypdf and passed through to the
|
|
output with the standard naming/collision contract, re-stamping /Title from
|
|
the uploaded filename stem. It reuses common.finalize_pdf so the atomic
|
|
write path (.tmp + os.replace) and metadata handling are identical to every
|
|
other converter.
|
|
|
|
Rejections (all ValueError -> surfaced verbatim as a job error / CLI error):
|
|
- corrupt / unparseable -> "Invalid PDF: ... could not be parsed"
|
|
- encrypted / password -> "PDF rejected: ... encrypted or password-protected"
|
|
- more than MAX_PAGES -> "PDF too large: ... pages (max N)"
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
from pathlib import Path
|
|
|
|
from pypdf import PdfReader
|
|
|
|
from . import common
|
|
|
|
# req 1 (pdf passthrough): upper bound on page count for a pass-through PDF.
|
|
MAX_PAGES = 500
|
|
|
|
|
|
def convert(path: Path, job_id: str, progress_cb=None, output_dir=None,
|
|
source_name=None) -> str:
|
|
"""Validate a PDF and pass it through to a same-named output PDF.
|
|
|
|
Satisfies: req 1 (pdf passthrough — validate, stamp /Title, standard
|
|
naming/collision, atomic write).
|
|
"""
|
|
name = source_name or path.name
|
|
stem = Path(name).stem or "book"
|
|
raw = path.read_bytes()
|
|
|
|
# req 1: validate with pypdf. Construction raises on header/body corruption.
|
|
try:
|
|
reader = PdfReader(io.BytesIO(raw))
|
|
except Exception as exc: # noqa: BLE001 - pypdf raises many corrupt-file types
|
|
raise ValueError(f"Invalid PDF: {name} could not be parsed ({exc})") from exc
|
|
|
|
# req 1: reject encrypted / password-protected BEFORE touching .pages
|
|
# (accessing pages on an encrypted reader raises, not a clean error).
|
|
if reader.is_encrypted:
|
|
raise ValueError(f"PDF rejected: {name} is encrypted or password-protected")
|
|
|
|
# req 1: count pages; a corrupt body beyond a valid header fails here.
|
|
try:
|
|
page_count = len(reader.pages)
|
|
except Exception as exc: # noqa: BLE001
|
|
raise ValueError(f"Invalid PDF: {name} could not be parsed ({exc})") from exc
|
|
|
|
# req 1: reject oversized documents (clear error, distinct from corrupt).
|
|
if page_count > MAX_PAGES:
|
|
raise ValueError(
|
|
f"PDF too large: {name} has {page_count} pages (max {MAX_PAGES})"
|
|
)
|
|
|
|
# req 1: stamp /Title from the uploaded filename stem; standard naming/collision.
|
|
common.report(progress_cb, 30.0, f"Passing through {name} (1/1)")
|
|
out_dir = common.default_output_dir(output_dir)
|
|
filename = common.derive_export_name(name, out_dir)
|
|
return common.finalize_pdf(raw, out_dir, filename, job_id,
|
|
progress_cb=progress_cb, title=stem)
|