Core operations: - Compress PDF (3 presets: screen, ebook, printer, prepress) - PDF/A detection and conversion to regular PDF - Compress while preserving PDF/A structure - Merge multiple PDFs - Split/extract pages by range - Rotate pages - Extract text and images Interface: - Streamlit web GUI (app.py) - FastAPI REST API with auto OpenAPI docs (api.py) Docs: - README.md, ARCHITECTURE.md, API.md - Unit tests (15/15 passing)
78 lines
1.9 KiB
Python
78 lines
1.9 KiB
Python
"""
|
|
Validação de arquivos PDF recebidos.
|
|
"""
|
|
|
|
import io
|
|
import struct
|
|
from pathlib import Path
|
|
from typing import Union
|
|
|
|
from pypdf import PdfReader
|
|
|
|
|
|
PDF_MAGIC = b"%PDF"
|
|
|
|
|
|
def is_pdf_stream(data: bytes) -> bool:
|
|
"""Verifica se o buffer começa com a magic number de PDF."""
|
|
return data[:4] == PDF_MAGIC
|
|
|
|
|
|
def is_pdf_file(path: Union[str, Path]) -> bool:
|
|
"""Verifica se o arquivo é um PDF válido."""
|
|
path = Path(path)
|
|
if not path.exists() or not path.is_file():
|
|
return False
|
|
with open(path, "rb") as f:
|
|
header = f.read(4)
|
|
return header == PDF_MAGIC
|
|
|
|
|
|
def validate_pdf(data: bytes, filename: str = "") -> dict:
|
|
"""
|
|
Valida um PDF em memória. Retorna dict com:
|
|
- valid: bool
|
|
- pages: int (se válido)
|
|
- error: str (se inválido)
|
|
- is_pdfa: bool (provável PDF/A)
|
|
- file_size_mb: float
|
|
"""
|
|
result = {
|
|
"valid": False,
|
|
"pages": 0,
|
|
"error": "",
|
|
"is_pdfa": False,
|
|
"file_size_mb": round(len(data) / (1024 * 1024), 2),
|
|
"filename": filename,
|
|
}
|
|
|
|
if not is_pdf_stream(data):
|
|
result["error"] = "Arquivo não é um PDF (magic number inválido)"
|
|
return result
|
|
|
|
try:
|
|
reader = PdfReader(io.BytesIO(data))
|
|
result["pages"] = len(reader.pages)
|
|
result["valid"] = True
|
|
|
|
# Detecção básica de PDF/A via metadados XMP
|
|
if reader.metadata:
|
|
xmp = str(reader.metadata.raw_data or "")
|
|
if "/pdfaid:part" in xmp.lower() or "pdfa" in xmp.lower():
|
|
result["is_pdfa"] = True
|
|
|
|
except Exception as e:
|
|
result["error"] = f"Erro ao ler PDF: {str(e)}"
|
|
|
|
return result
|
|
|
|
|
|
def assert_safe_filename(name: str) -> str:
|
|
"""Remove caracteres perigosos de nomes de arquivo."""
|
|
import re
|
|
safe = re.sub(r'[^\w\-. ]', "_", name)
|
|
if not safe:
|
|
safe = "documento"
|
|
if not safe.lower().endswith(".pdf"):
|
|
safe += ".pdf"
|
|
return safe |