Spaces:
Sleeping
Sleeping
| # 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() | |