WallaceBrasil's picture
Update app.py
363972c verified
# app.py — versão final p/ Hugging Face (tema + UI padronizados)
import gradio as gr
import re
from pathlib import Path
from typing import List
from processador import processar_misto # devolve [(caminho_img, legenda)], caminho_zip
# -------------------- Tema + CSS do portfólio --------------------
CUSTOM_CSS = """
:root{
--bg:#000; /* fundo geral */
--panel:#0b0b0b; /* blocos/painéis */
--panel-2:#0e0e0e; /* inputs/dropdowns */
--border:#2a2a2a; /* borda padrão */
--text:#e5e5e5; /* texto branco suave */
--muted:#a3a3a3; /* texto secundário */
--accent:#6ee7b7; /* cor do foco/seleção (verde menta) */
}
/* fonte geral (system UI) */
html, body, .gradio-container {
background: var(--bg)!important;
color: var(--text)!important;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif !important;
}
/* blocos/painéis */
.gradio-container .block,
.gradio-container .gr-box,
.gradio-container .gr-panel {
background: var(--panel) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
}
/* remover o bloco atrás do TÍTULO */
.gradio-container .block:has(h1){
background: transparent !important;
border: 0 !important;
box-shadow: none !important;
}
/* botões estilo "pílula" */
button, .gr-button{
border-radius: 9999px !important;
border: 1px solid var(--border) !important;
background: var(--panel-2) !important;
}
button:hover{ border-color:#4a4a4a !important; }
/* inputs/textarea/file/dropdown */
input, textarea, select,
.gradio-container .gr-textbox,
.gradio-container .gr-input,
.gradio-container .gradio-dropdown,
.gradio-container .gr-file,
.gradio-container .gr-file-download,
.gradio-container .gr-select-container,
.gradio-container .wrap .items-center select {
background: var(--panel-2) !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
border-radius: 12px !important;
}
select > option { background: var(--panel-2); color: var(--text); }
/* foco/seleção visível */
input:focus, textarea:focus, select:focus,
.gradio-container .gr-textbox:focus-within,
.gradio-container .gr-input:focus-within,
.gradio-container .gradio-dropdown:focus-within,
.gradio-container .gr-file:focus-within,
.gradio-container .gr-select-container:focus-within {
outline: none !important;
border-color: var(--accent) !important;
box-shadow: 0 0 0 2px rgba(110,231,183,0.18) !important;
}
/* uploader QUADRADO */
.gradio-container [data-testid="file"] .rounded-full,
.gradio-container [data-testid="files"] .rounded-full { border-radius:12px !important; }
.gradio-container [data-testid="file"] [class*="aspect-"],
.gradio-container [data-testid="files"] [class*="aspect-"] { aspect-ratio:auto !important; }
.gradio-container [data-testid="file"] .h-full.w-full,
.gradio-container [data-testid="files"] .h-full.w-full { border-radius:12px !important; }
/* gallery e saídas */
.gradio-container .gr-gallery,
.gradio-container .gr-file-download{
background: var(--panel-2) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
}
/* badges/labels */
.badge, .token { background:#0f0f0f !important; border-radius:9999px !important; }
/* esconder rodapé do Gradio */
.gradio-container .fixed.bottom-0,
.gradio-container div[class*="fixed"][class*="bottom-0"],
.gradio-container footer,
body > div.fixed.bottom-0,
div.fixed.bottom-0 {
display: none !important;
visibility: hidden !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
"""
THEME = gr.themes.Soft(primary_hue="zinc", neutral_hue="zinc")
# -------------------- Util --------------------
def _normalize_paths(arquivos) -> List[Path]:
"""gr.Files pode entregar str, Path, objeto com .name ou até dict-like."""
items = arquivos if isinstance(arquivos, list) else [arquivos]
paths: List[Path] = []
for a in items:
if isinstance(a, (str, Path)):
paths.append(Path(a))
else:
name = getattr(a, "name", None)
if name is None:
try:
name = a["name"] # alguns retornam dict-like
except Exception:
name = str(a)
paths.append(Path(name))
return paths
# -------------------- Função do app --------------------
def processar(arquivos, modo, paginas_input, formato_opcao):
if not arquivos:
return "Por favor, envie ao menos um PDF (ou .zip com PDFs).", None
paths = _normalize_paths(arquivos)
# pega "jpeg" de "jpeg [Recomendado - ...]"
formato = formato_opcao.split(" [")[0].strip().lower()
paginas = None
if "específica" in modo.lower(): # aceita "Específicas"/"Específica"
nums = re.findall(r"\d+", paginas_input or "")
if not nums:
return "Nenhuma página válida foi informada.", None
paginas = [int(n) for n in nums]
try:
galeria, zip_path = processar_misto(paths, modo, paginas, formato)
return galeria, zip_path # Gallery aceita lista de (caminho, legenda)
except Exception as e:
return f"Erro ao processar: {e}", None
# opções do dropdown (sem espaços no início)
FORMATOS = [
"jpeg [Recomendado - compacto e boa qualidade]",
"png [Alta qualidade, suporta transparência]",
"bmp [Sem compressão - ideal para edição bruta]",
"ico [Favicon para sites, atalhos e apps]",
]
demo = gr.Interface(
fn=processar,
inputs=[
gr.Files(
label="Envie PDF(s) ou .zip com PDFs",
file_count="multiple",
file_types=[".pdf", ".zip"],
),
gr.Radio(
["Extrair todas as páginas", "Extrair páginas específicas"],
label="Modo",
value="Extrair todas as páginas",
),
gr.Textbox(
label="Páginas (se usar 'Específicas')",
placeholder="Ex.: 3 - 5 - 10",
),
gr.Dropdown(choices=FORMATOS, value=FORMATOS[0], label="Formato das imagens"),
],
outputs=[
gr.Gallery(label="Imagens"),
gr.File(label="ZIP com as imagens"),
],
title="Extrator de Imagens de PDF",
allow_flagging="never",
theme=THEME,
css=CUSTOM_CSS,
)
if __name__ == "__main__":
demo.launch()