← Files Pesquisa MirandasTechARCHIVED FILE

scripts/apresentacao_defesa.py

5.38 KB · Oct 2, 2026 · 00:33 UTC

↓ Download file

#!/usr/bin/env python3
r"""Gera o esqueleto da apresentação de qualificação ou defesa em Beamer (LaTeX) com um slide por bloco do roteiro e o tempo de cada um, e compila se houver LaTeX.

    python3 scripts/apresentacao_defesa.py <pasta_saida> --titulo "…" --autor "…" --orientador "…" --programa "…" --instituicao "…" --tipo qualificacao|defesa --minutos 30 [--capitulos "Introdução;Revisão;Método;Resultados;Discussão;Conclusão"] [--compilar]

Roteiro padrão (proporções do tempo total): abertura e pergunta (10 %), contexto e lacuna (15 %), objetivos (5 %), método (20 %), resultados (30 %), discussão e limites (10 %), conclusão e próximos passos (10 %). Cada frame traz o tempo previsto no rodapé e uma nota do apresentador (`\note{}`) com o que dizer. Regra: um resultado por slide, número com fonte, figura gerada por script. Compila com latexmk (pdflatex) quando `--compilar` e o `latexmk` existir; senão deixa o `.tex` para o Overleaf.
"""
import argparse, shutil, subprocess, sys
from pathlib import Path

ROTEIRO = [("Abertura e pergunta de pesquisa", 0.10, "Quem sou, título, a pergunta em uma frase e por que ela importa."),
           ("Contexto e lacuna", 0.15, "O que já se sabe (3 a 5 referências-chave), o que falta, a lacuna que este trabalho preenche."),
           ("Objetivos", 0.05, "Objetivo geral e específicos, na mesma ordem em que os resultados aparecerão."),
           ("Método", 0.20, "Desenho, participantes/amostra ou corpus, instrumentos, procedimentos, análise; ética (CEP/CAAE) e pré-registro se houver."),
           ("Resultados", 0.30, "Um resultado por slide: tabela ou figura, n, efeito e IC; o número lido em voz alta é o mesmo do texto."),
           ("Discussão e limites", 0.10, "O que os resultados dizem frente à literatura; limitações ditas por você antes que a banca diga."),
           ("Conclusão e próximos passos", 0.10, "Resposta à pergunta, contribuições, o que falta (na qualificação: cronograma até a defesa).")]

def esc(s): return s.replace("&", "\\&").replace("%", "\\%").replace("_", "\\_").replace("#", "\\#")

def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("pasta"); ap.add_argument("--titulo", required=True); ap.add_argument("--autor", required=True); ap.add_argument("--orientador", default=""); ap.add_argument("--programa", default=""); ap.add_argument("--instituicao", default="")
    ap.add_argument("--tipo", choices=["qualificacao", "defesa"], default="defesa"); ap.add_argument("--minutos", type=int, default=30); ap.add_argument("--capitulos", default=""); ap.add_argument("--compilar", action="store_true")
    a = ap.parse_args(); pasta = Path(a.pasta); pasta.mkdir(parents=True, exist_ok=True)
    evento = "Exame de qualificação" if a.tipo == "qualificacao" else "Defesa"
    frames = []; acumulado = 0.0
    for nome, frac, nota in ROTEIRO:
        minutos = a.minutos * frac; acumulado += minutos
        frames.append(f"\\begin{{frame}}{{{esc(nome)}}}\n  \\begin{{itemize}}\n    \\item [preencher]\n  \\end{{itemize}}\n  \\vfill\\hfill{{\\tiny previsto: {minutos:.1f} min (acumulado {acumulado:.0f} min)}}\n  \\note{{{esc(nota)}}}\n\\end{{frame}}\n")
    if a.capitulos:
        caps = [c.strip() for c in a.capitulos.split(";") if c.strip()]
        frames.insert(1, "\\begin{frame}{Estrutura do trabalho}\n  \\tableofcontents\n\\end{frame}\n")
        for i, c in enumerate(caps): frames.insert(2 + i, f"\\section{{{esc(c)}}}\n")
    tex = f"""\\documentclass[aspectratio=169,11pt]{{beamer}}
\\usetheme{{Boadilla}}
\\usepackage[T1]{{fontenc}}
\\usepackage[utf8]{{inputenc}}
\\usepackage[brazilian]{{babel}}
\\usepackage{{graphicx,booktabs}}
\\setbeamertemplate{{navigation symbols}}{{}}
\\setbeamertemplate{{footline}}[frame number]
\\title{{{esc(a.titulo)}}}
\\subtitle{{{evento} — {esc(a.programa)}}}
\\author{{{esc(a.autor)}{(' \\\\ Orientação: ' + esc(a.orientador)) if a.orientador else ''}}}
\\institute{{{esc(a.instituicao)}}}
\\date{{\\today}}
\\begin{{document}}
\\frame{{\\titlepage}}
{''.join(frames)}
\\begin{{frame}}{{Referências}}
  \\footnotesize [as referências citadas nos slides, no padrão do trabalho]
\\end{{frame}}
\\begin{{frame}}[plain]
  \\centering \\Large Obrigado. \\\\[1em] \\normalsize Perguntas da banca.
\\end{{frame}}
\\end{{document}}
"""
    (pasta / "apresentacao.tex").write_text(tex, encoding="utf-8")
    total = sum(f for _, f, _ in ROTEIRO)
    print(f"gravado: {pasta/'apresentacao.tex'} — {len(ROTEIRO)} blocos para {a.minutos} min ({evento})")
    for nome, frac, _ in ROTEIRO: print(f"  {nome:32} {a.minutos*frac:5.1f} min")
    if a.compilar:
        if not shutil.which("latexmk"): print("latexmk não encontrado: abra apresentacao.tex no Overleaf ou instale o TinyTeX"); return
        r = subprocess.run(["latexmk", "-pdf", "-f", "-interaction=nonstopmode", "apresentacao.tex"], cwd=pasta, capture_output=True, text=True)
        pdf = pasta / "apresentacao.pdf"
        if pdf.exists():
            n = "?"
            if shutil.which("pdfinfo"):
                info = subprocess.run(["pdfinfo", str(pdf)], capture_output=True, text=True).stdout
                for l in info.splitlines():
                    if l.startswith("Pages:"): n = l.split()[-1]
            print(f"ok: {pdf} ({n} páginas)")
        else: print("erro na compilação; veja apresentacao.log"); sys.exit(1)

if __name__ == "__main__": main()

SHA-256: 5230c1eacb5e33ed51eabe819c96460cf39e9f9c9f6ee0767130b3aed3b37305