Executando verificação de segurança...
1

O melhor leitor de PDF off-line

Por que este Código é a Solução Definitiva para Transformar PDFs em Áudio
Em um mundo onde a multitarefa é essencial, a capacidade de consumir conteúdo escrito através de áudio não é apenas um luxo, é uma necessidade. Existem muitas ferramentas no mercado, mas o código apresentado aqui se destaca como a melhor solução personalizada para conversão de PDF em fala.
Aqui estão os três pilares que tornam este script imbatível:

  1. Controle Granular de Conteúdo (A "Logica Inteligente")
    A maioria dos leitores de PDF "text-to-speech" tem um defeito fatal: ou leem o documento inteiro (incluindo índices e bibliografias chatas) ou exigem que você copie e cole o texto manualmente.
    Este código resolve isso brilhantemente com uma interface gráfica (GUI) construída em PyQt5. A implementação das variáveis start e stop permite ao usuário definir exatamente onde começar e onde parar.
    • Quer ler apenas o Capítulo 3? Coloque o intervalo de páginas.
    • Quer ler do meio até o fim? O código entende (elif stop == "").
    • O Grande Trunfo: A lógica de indexação (int(start)-1) traduz a contagem de páginas humana para a linguagem da máquina, garantindo que a leitura comece exatamente na página visualizada, sem erros de cálculo.
  2. O Motor de Extração de Elite: PyMuPDF (fitz)
    Enquanto muitos scripts amadores utilizam bibliotecas antigas e lentas (como o PyPDF2), este código utiliza o PyMuPDF (fitz).
    Por que isso importa?
    • Velocidade: O fitz é incrivelmente rápido na renderização e extração de texto.
    • Precisão: Ele preserva a estrutura do texto muito melhor do que os concorrentes, garantindo que o áudio final não fique "engasgado" com quebras de linha erradas ou caracteres estranhos. O método page.get_text() garante uma limpeza de dados superior antes mesmo de a voz começar.
  3. Independência Total e Privacidade (pyttsx3)
    Ao contrário de soluções que dependem de APIs pagas do Google ou da Amazon (que exigem internet e envio de dados para a nuvem), este código usa o pyttsx3.
    Isso torna o software:
    • 100% Offline: Funciona em um avião, em áreas remotas ou sem wi-fi.
    • Privado: Seus documentos confidenciais nunca saem da sua máquina.
    • Custo Zero: Sem assinaturas mensais ou limites de caracteres.

Codigo

import pyttsx3
import fitz
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QFileDialog, QVBoxLayout, QLabel, QHBoxLayout, QLineEdit
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QIcon

1. Classe Worker: Executa o trabalho pesado em segundo plano

class SpeechWorker(QThread):
finished = pyqtSignal()
status = pyqtSignal(str)

def __init__(self, file_path, start, stop):
    super().__init__()
    self.file_path = file_path
    self.start_page = start
    self.stop_page = stop
    self.engine = None
    self.is_running = True

def run(self):
    try:
        self.status.emit("Extraindo texto...")
        doc = fitz.open(self.file_path)
        text = ""

        # Lógica de paginação original mantida
        if self.start_page == "" and self.stop_page == "":
            iterator = range(len(doc))
        elif self.start_page == "":
            iterator = range(0, int(self.stop_page))
        elif self.stop_page == "":
            iterator = range(int(self.start_page)-1, len(doc))
        else:
            iterator = range(int(self.start_page)-1, int(self.stop_page))

        for i in iterator:
            if not self.is_running: break # Permite parar durante a extração
            page = doc.load_page(i)
            text += page.get_text()

        if self.is_running and text:
            self.status.emit("Lendo áudio...")
            self.engine = pyttsx3.init()
            self.engine.say(text)
            self.engine.runAndWait()
        
    except Exception as e:
        self.status.emit(f"Erro: {str(e)}")
    finally:
        self.finished.emit()

def stop(self):
    self.is_running = False
    if self.engine:
        self.engine.stop()

2. Classe Principal da Interface

class Reader(QWidget):
def init(self):
super().init()
self.worker = None # Variável para guardar a thread
self.initUI()

def initUI(self):
    self.layout = QVBoxLayout()

    self.label = QLabel("No file selected", self)
    self.label.setStyleSheet("font-size: 16px; color: #e0e5ec; qproperty-alignment: AlignCenter;")
    self.layout.addWidget(self.label)

    self.layout2 = QHBoxLayout()
    self.layout.addLayout(self.layout2)

    self.input1 = QLineEdit()
    self.input1.setPlaceholderText("Start Page")
    self.input1.setStyleSheet("font-size: 16px; background-color: #e0e5ec; border: 1px ; padding: 10px; border-radius: 10px; ")
    self.layout2.addWidget(self.input1)

    self.input2 = QLineEdit()
    self.input2.setPlaceholderText("End Page")
    self.input2.setStyleSheet("font-size: 16px; background-color: #e0e5ec; border: 1px ; padding: 10px; border-radius: 10px; ")
    self.layout2.addWidget(self.input2)

    # Botão de Selecionar/Tocar
    self.btn = QPushButton("Select File & Play", self)
    self.btn.setStyleSheet("font-size: 16px; background-color: #e0e5ec; border: 1px ; padding: 10px; border-radius: 10px; ")
    self.btn.clicked.connect(self.start_reading)
    self.layout.addWidget(self.btn)

    # Novo Botão de Parar
    self.stop_btn = QPushButton("Stop", self)
    self.stop_btn.setStyleSheet("font-size: 16px; background-color: #ffcccc; border: 1px ; padding: 10px; border-radius: 10px; color: #990000")
    self.stop_btn.clicked.connect(self.stop_reading)
    self.stop_btn.setEnabled(False) # Começa desativado
    self.layout.addWidget(self.stop_btn)

    self.setLayout(self.layout)
    self.setWindowTitle("Reader Pro")
    self.resize(400, 450)

def start_reading(self):
    options = QFileDialog.Options()
    file_path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "PDF Files (*.pdf);;All Files (*)", options=options)

    if file_path:
        start = self.input1.text()
        stop = self.input2.text()
        
        # Bloqueia botão de iniciar e libera o de parar
        self.btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        
        # Inicia a Thread
        self.worker = SpeechWorker(file_path, start, stop)
        self.worker.status.connect(self.update_status)
        self.worker.finished.connect(self.reading_finished)
        self.worker.start()

def stop_reading(self):
    if self.worker:
        self.label.setText("Parando...")
        self.worker.stop()

def update_status(self, message):
    self.label.setText(message)

def reading_finished(self):
    self.label.setText("Pronto / Selecione outro arquivo")
    self.btn.setEnabled(True)
    self.stop_btn.setEnabled(False)

if name == "main":
app = QApplication(sys.argv)
ex = Reader()
ex.setStyleSheet("background-color: #043e7d")
# ex.setWindowIcon(QIcon("app_icon.png")) # Comentei para evitar erro se não tiver o ícone
ex.show()
sys.exit(app.exec_())

Carregando publicação patrocinada...