""" pdfmd.py Author: Descomplicar® Crescimento Digital Link: https://descomplicar.pt Copyright: 2025 Descomplicar® """ import re import pypdf import requests import time from pathlib import Path from datetime import datetime # Configurações da API import os from dotenv import load_dotenv load_dotenv() BASE_URL = "https://openrouter.ai/api/v1/chat/completions" API_KEY = os.environ.get("OPENROUTER_API_KEY", "") if not API_KEY: print("ERRO: OPENROUTER_API_KEY não configurada! Criar ficheiro .env com OPENROUTER_API_KEY=...") exit(1) def extrair_nome_capitulo(texto): """Extrai o nome do capítulo do texto.""" padrao = r'(?:Chapter|Capítulo|Parte|Section)\s*\d*\s*[:\-]?\s*([^\n]+)' match = re.search(padrao, texto, re.IGNORECASE) if match: return match.group(1).strip() return "Sem título" def processar_texto(texto, titulo, max_retries=3, retry_delay=5): # Remover a linha que redefine retry_delay """Processa o texto extraído do PDF e formata em Markdown.""" prompt = f"""Analise este texto de '{titulo}' e crie: 1. Um resumo do capítulo (3-4 parágrafos) 2. 10 tags técnicas relevantes para o conteúdo 3. O texto completo, mantendo a estrutura e formatação original 4. 10 perguntas e respostas (FAQs) detalhadas sobre o conteúdo Mantenha o texto no idioma original do documento. Texto para processar: {texto}""" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } data = { "model": "mistralai/ministral-3b", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3 } for attempt in range(max_retries): try: response = requests.post( BASE_URL, json=data, headers=headers, timeout=30 ) if response.status_code == 200: return response.json()['choices'][0]['message']['content'].strip() elif response.status_code == 429: wait_time = retry_delay * (attempt + 1) print(f"Rate limit atingido. A aguardar {wait_time}s...") time.sleep(wait_time) else: print(f"Erro API: {response.status_code}") print(f"Resposta: {response.text}") time.sleep(retry_delay) except requests.exceptions.RequestException as e: print(f"Erro na requisição: {str(e)}") time.sleep(retry_delay) return None def extrair_capitulos(pdf_path): """Extrai capítulos do PDF com seus títulos.""" reader = pypdf.PdfReader(pdf_path) texto_completo = [] # Primeiro, extrair todo o texto do PDF e formatar em Markdown print("\nExtraindo texto do PDF...") for i, page in enumerate(reader.pages): try: texto = page.extract_text() if texto: # Formatação básica para Markdown texto = texto.replace('\n', ' \n') # Adiciona quebra de linha em Markdown texto_completo.append(texto) print(f" Página {i+1}: {len(texto)} caracteres") else: print(f" Página {i+1}: sem texto") except Exception as e: print(f" Erro na página {i+1}: {str(e)}") continue # Verifica se conseguiu extrair algum texto if not texto_completo: print("AVISO: Não foi possível extrair texto do PDF.") return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': ''}] texto_documento = '\n'.join(texto_completo) print(f"\nTotal extraído: {len(texto_documento)} caracteres") # Padrões para identificar capítulos padroes_capitulo = [ r'(?:Chapter|Capítulo|Parte|Section)\s*\d+', r'\d+\.\s+[A-Z]', r'[A-Z]+\s*\d+\s*[:\-]', r'(?:Introdução|Introduction)', r'(?:Conclusão|Conclusion)', r'(?:Anexo|Appendix)\s*[A-Z]', r'(?:Bibliografia|References)', r'(?:Índice|Index)' ] # Tenta identificar capítulos print("\nProcurando divisões de capítulos...") padrao_combinado = '|'.join(f'({p})' for p in padroes_capitulo) matches = list(re.finditer(padrao_combinado, texto_documento, re.IGNORECASE | re.MULTILINE)) # Se não encontrou divisões, retorna documento completo if not matches: print("Nenhuma divisão de capítulo encontrada - tratando como documento único") return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': texto_documento}] # Processa cada capítulo encontrado capitulos = [] print("\nProcessando capítulos...") # Adiciona o texto inicial antes do primeiro capítulo, se houver if matches[0].start() > 0: texto_inicial = texto_documento[:matches[0].start()].strip() if texto_inicial: capitulos.append({ 'numero': 1, 'titulo': 'Conteúdo Inicial', 'texto': texto_inicial }) print(f" Adicionado conteúdo inicial: {len(texto_inicial)} caracteres") # Processa os capítulos identificados for i, match in enumerate(matches): inicio = match.start() if i < len(matches) - 1: fim = matches[i+1].start() else: fim = len(texto_documento) texto_capitulo = texto_documento[inicio:fim].strip() # Pega a primeira linha como título linhas = texto_capitulo.split('\n') titulo = linhas[0].strip() numero = len(capitulos) + 1 capitulos.append({ 'numero': numero, 'titulo': titulo, 'texto': texto_capitulo }) print(f" Capítulo {numero}: {titulo} ({len(texto_capitulo)} caracteres)") # Verifica se todo o texto foi incluído texto_capitulos = ''.join(cap['texto'] for cap in capitulos) if len(texto_capitulos) < len(texto_documento) * 0.9: # Margem de 10% para diferenças de formatação print("\nAVISO: Possível perda de conteúdo detectada!") print(f" Texto original: {len(texto_documento)} caracteres") print(f" Texto nos capítulos: {len(texto_capitulos)} caracteres") print("Retornando documento completo para garantir preservação...") return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': texto_documento}] print(f"\nProcessamento concluído: {len(capitulos)} capítulos identificados") return capitulos def processar_pdf(arquivo_pdf): """Processa um arquivo PDF.""" print(f"\nProcessando: {arquivo_pdf}") try: capitulos = extrair_capitulos(arquivo_pdf) print(f"Encontrados {len(capitulos)} capítulos") md_content = f"""# {arquivo_pdf.stem} Data de processamento: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} """ for capitulo in capitulos: print(f"Processando capítulo {capitulo['numero']}: {capitulo['titulo']}") conteudo_processado = processar_texto( capitulo['texto'], f"Capítulo {capitulo['numero']} - {capitulo['titulo']}" ) if conteudo_processado: md_content += f"\n{conteudo_processado}\n\n---\n" else: print(f"Erro ao processar capítulo {capitulo['numero']}") # Remover o delay entre capítulos para evitar interrupções # Salvar arquivo caminho_saida = Path('output') / arquivo_pdf.parent.relative_to('input') caminho_saida.mkdir(parents=True, exist_ok=True) arquivo_saida = caminho_saida / f"{arquivo_pdf.stem}.md" with open(arquivo_saida, 'w', encoding='utf-8') as f: f.write(md_content) print(f"Arquivo salvo: {arquivo_saida}") return True except Exception as e: print(f"Erro ao processar {arquivo_pdf}: {str(e)}") return False def main(): print("Iniciando o processamento...") start_time = datetime.now() Path('input').mkdir(exist_ok=True) Path('output').mkdir(exist_ok=True) pdfs = list(Path('input').rglob('*.pdf')) total = len(pdfs) print(f"Encontrados {total} PDFs") falhas = [] sucessos = [] for i, pdf in enumerate(pdfs, 1): print(f"\nProcessando {i}/{total}: {pdf}") try: if processar_pdf(pdf): sucessos.append(pdf) else: falhas.append(pdf) except Exception as e: print(f"Erro ao processar {pdf}: {str(e)}") falhas.append(pdf) sucessos.append(pdf) else: falhas.append(pdf) time.sleep(5) print(f"\n=== Relatório Final ===") print(f"Data de início: {start_time}") print(f"Data de término: {datetime.now()}") print(f"Duração: {datetime.now() - start_time}") print(f"PDFs processados com sucesso: {len(sucessos)}") print(f"PDFs com falha: {len(falhas)}") if falhas: print("\nFalhas:") for f in falhas: print(f"- {f}") if __name__ == "__main__": main()