Python PDF Zip Files


Nota: Este script exige conhecimento avançado, level Full Stack. Se precisar de ajuda, não hesite, entre em contato.


Abaixo você encontra os procedimentos de configuração e o código-fonte em Python desenvolvido para agrupar e compactar arquivos PDF em volumes ZIP respeitando um limite máximo de tamanho por arquivo ZIP (padrão: 15 MB).


Procedimento Operacional Padrão (POP)

1. Criar o ambiente virtual (venv) no Windows

Ainda não criou o seu ambiente localhost? Veja no link abaixo como fazer.


2. Requisitos de Bibliotecas

Este script utiliza exclusivamente bibliotecas nativas da biblioteca padrão do Python (zipfile, importlib, pathlib, tempfile e typing). Portanto, não é necessária a instalação de pacotes externos via pip para sua execução.

3. Como configurar e executar o script


Código-Fonte

# REQUEST LIB PYTHON
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ===========================
# IMPORTS
# ===========================

import zipfile
import importlib.util
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import List


# ===========================
# LOAD ROUTES
# ===========================


def load_routes_module() -> list:
    root = Path(__file__).parents[3]
    routes_path = root / "app" / "builder" / "routes.py"
    if not routes_path.exists():
        raise FileNotFoundError(f"routes.py não encontrado em {routes_path}")

    spec = importlib.util.spec_from_file_location("routes", str(routes_path))
    if spec is None or spec.loader is None:
        raise ImportError("Falha ao criar ModuleSpec para routes.py")

    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)  # type: ignore
    return getattr(module, "routes", [])


def get_io_paths(app_name: str) -> tuple:
    routes = load_routes_module()
    for r in routes:
        if r.get("app", "") == app_name:
            input_path = r["input"]["path"] or ""
            output_path = r["output"]["path"] or ""
            return Path(input_path), Path(output_path)
    raise RuntimeError(f"Rota não encontrada para {app_name}")


# ===========================
# CONFIG
# ===========================

APP_NAME = "PDFZipper"
INPUT_DIR, OUTPUT_DIR = get_io_paths(APP_NAME)
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

SIZE_LIMIT_MB = 15  # <--- alterado de 19.5 para 15
SIZE_LIMIT_BYTES = int(SIZE_LIMIT_MB * 1024 * 1024)


# ===========================
# CORE FUNCTIONS
# ===========================


def get_pdf_files(path: Path) -> List[Path]:
    return [f for f in path.glob("*.pdf") if f.is_file()]


def calculate_zip_size(file_list: List[Path]) -> int:
    with TemporaryDirectory() as temp_dir:
        temp_zip_path = Path(temp_dir) / "temp.zip"
        with zipfile.ZipFile(temp_zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
            for file in file_list:
                zipf.write(file, arcname=file.name)
        return temp_zip_path.stat().st_size


def zip_pdfs():
    files = get_pdf_files(INPUT_DIR)

    if not files:
        print("Nenhum arquivo PDF encontrado.")
        return

    folder_counter = 1

    while files:
        group = []
        for file in files:
            test_group = group + [file]
            zip_size = calculate_zip_size(test_group)

            if zip_size <= SIZE_LIMIT_BYTES:
                group.append(file)
            else:
                break  # Atingiu o limite

        if not group:
            print(
                f"Aviso: Arquivo {files[0].name} excede sozinho o limite de {SIZE_LIMIT_MB} MB compactado."
            )
            group = [files[0]]

        # zip_filename = OUTPUT_DIR / f"Pasta{folder_counter:03}.zip"
        # zip_filename = OUTPUT_DIR / f"DecretosIndividuais{folder_counter:03}.zip"
        # zip_filename = OUTPUT_DIR / f"DecretosAdmissao{folder_counter:03}.zip"
        # zip_filename = OUTPUT_DIR / f"DecretosDemissao{folder_counter:03}.zip"
        zip_filename = OUTPUT_DIR / f"DOEM_02012009_12062025_{folder_counter:03}.zip"

        with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
            for file in group:
                zipf.write(file, arcname=file.name)

        print(f"✅ {zip_filename.name} criado com {len(group)} arquivos.")

        # Remove os processados da lista
        files = [f for f in files if f not in group]
        folder_counter += 1

    print("✅ Processamento concluído.")


# ===========================
# MAIN
# ===========================

if __name__ == "__main__":
    zip_pdfs()