feat: initial release — pdf-toolkit v1.0.0
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)
This commit is contained in:
78
core/validators.py
Normal file
78
core/validators.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user