diff --git a/CHANGELOG.md b/CHANGELOG.md
index de7a425..ec7fcfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
# v1.1.2 (pre-release)
+- Potential fix for #37 and #38, where the program was becoming slow while processing large files.
- Fixed `Open folder` and `Open file` buttons in the queue manager GUI.
# v1.1.1
diff --git a/abogen/constants.py b/abogen/constants.py
index b7074e1..8b66e6b 100644
--- a/abogen/constants.py
+++ b/abogen/constants.py
@@ -109,6 +109,7 @@ SAMPLE_VOICE_TEXTS = {
COLORS = {
"BLUE": "#007dff",
"RED": "#c0392b",
+ "ORANGE": "#FFA500",
"GREEN": "#42ad4a",
"GREEN_BG": "rgba(66, 173, 73, 0.1)",
"GREEN_BG_HOVER": "rgba(66, 173, 73, 0.15)",
diff --git a/abogen/gui.py b/abogen/gui.py
index 066ac44..4e963a5 100644
--- a/abogen/gui.py
+++ b/abogen/gui.py
@@ -52,6 +52,8 @@ from PyQt5.QtGui import (
QPolygon,
QColor,
QMovie,
+ QFont,
+ QTextCharFormat,
)
from abogen.utils import (
load_config,
@@ -123,6 +125,15 @@ class IconProvider(QFileIconProvider):
def icon(self, fileInfo):
return super().icon(fileInfo)
+LOG_COLOR_MAP = {
+ True: COLORS["GREEN"],
+ False: COLORS["RED"],
+ "red": COLORS["RED"],
+ "green": COLORS["GREEN"],
+ "orange": COLORS["ORANGE"],
+ "blue": COLORS["BLUE"],
+ None: COLORS["LIGHT_DISABLED"],
+}
class InputBox(QLabel):
# Define CSS styles as class constants
@@ -646,7 +657,7 @@ class abogen(QWidget):
None # Add new variable to track the displayed file path
)
# Max log lines
- self.log_window_max_lines = self.config.get("log_window_max_lines", 5000)
+ self.log_window_max_lines = self.config.get("log_window_max_lines", 2000)
self.selected_chapters = set()
self.last_opened_book_path = None # Track the last opened book path
self.last_output_path = None
@@ -700,9 +711,6 @@ class abogen(QWidget):
self.queued_items = []
self.current_queue_index = 0
- # Log lines
- self._log_lines = []
-
self.initUI()
self.speed_slider.setValue(int(self.config.get("speed", 1.00) * 100))
self.update_speed_label()
@@ -785,6 +793,7 @@ class abogen(QWidget):
container_layout.addWidget(self.queue_row_widget)
self.log_text = QTextEdit(self)
self.log_text.setReadOnly(True)
+ self.log_text.setUndoRedoEnabled(False)
self.log_text.setFrameStyle(QTextEdit.NoFrame)
self.log_text.setStyleSheet("QTextEdit { border: none; }")
self.log_text.hide()
@@ -1458,16 +1467,12 @@ class abogen(QWidget):
self.input_box.hide()
self.log_text.show()
self.log_text.clear()
- self._log_lines = []
QApplication.processEvents()
def restore_input_box(self):
self.log_text.hide()
self.input_box.show()
- def color_text(self, message, color):
- return f'{message.replace(chr(10), "
")}
'
-
def update_log(self, message):
# Use signal-based approach for thread-safe logging
if QThread.currentThread() != QApplication.instance().thread():
@@ -1479,38 +1484,36 @@ class abogen(QWidget):
self._update_log_main_thread(message)
def _update_log_main_thread(self, message):
- if not hasattr(self, "_log_lines"):
- self._log_lines = []
+ txt = self.log_text
+ sb = txt.verticalScrollBar()
+ at_bottom = sb.value() == sb.maximum()
- # Always produce a single HTML line (with color if tuple)
+ cursor = txt.textCursor()
+ cursor.movePosition(QTextCursor.End)
+
+ fmt = cursor.charFormat()
if isinstance(message, tuple):
text, spec = message
- color = "green" if spec is True else ("red" if spec is False else spec)
- html_line = self.color_text(text, color)
+ fmt.setForeground(QColor(LOG_COLOR_MAP.get(spec, COLORS["LIGHT_DISABLED"])))
else:
- html_line = f"{str(message).replace(chr(10), '
')}
"
+ text = str(message)
+ fmt.clearForeground()
+ cursor.setCharFormat(fmt)
+ cursor.insertText(text + "\n")
- self._log_lines.append(html_line)
+ doc = txt.document()
+ excess = doc.blockCount() - self.log_window_max_lines
+ if excess > 0:
+ start = doc.findBlockByNumber(0).position()
+ end = doc.findBlockByNumber(excess).position()
+ trim_cursor = QTextCursor(doc)
+ trim_cursor.setPosition(start)
+ trim_cursor.setPosition(end, QTextCursor.KeepAnchor)
+ trim_cursor.removeSelectedText()
- # Check if user is at bottom BEFORE inserting
- sb = self.log_text.verticalScrollBar()
- at_bottom = sb.value() >= sb.maximum() - 2
-
- # Trim buffer if needed
- if len(self._log_lines) > self.log_window_max_lines:
- self._log_lines = self._log_lines[-self.log_window_max_lines :]
- self.log_text.clear()
- for line in self._log_lines:
- self.log_text.insertHtml(line)
- else:
- self.log_text.insertHtml(html_line)
-
- # If user was at bottom before, scroll to new bottom
if at_bottom:
sb.setValue(sb.maximum())
- QApplication.processEvents()
-
def _get_queue_progress_format(self, value=None):
"""Return the progress bar format string for queue mode."""
if (
@@ -1988,7 +1991,6 @@ class abogen(QWidget):
else:
# More items in queue: clear log and reload for next item
self.log_text.clear()
- self._log_lines = []
QApplication.processEvents()
# Start new queued item, if we're using a queued conversion
@@ -2032,7 +2034,6 @@ class abogen(QWidget):
self.progress_bar.hide()
self.restore_input_box()
self.log_text.clear()
- self._log_lines = []
# Use displayed_file_path instead of selected_file for EPUBs or PDFs
display_path = (
@@ -2444,7 +2445,6 @@ class abogen(QWidget):
self.finish_widget.hide()
self.restore_input_box()
self.log_text.clear()
- self._log_lines = []
display_path = (
self.displayed_file_path
if self.displayed_file_path
@@ -2484,6 +2484,12 @@ class abogen(QWidget):
self.config["use_gpu"] = self.use_gpu
save_config(self.config)
+ def cleanup_conversion_thread(self):
+ # Stop conversion thread
+ if hasattr(self, "conversion_thread") and self.conversion_thread is not None and self.conversion_thread.isRunning():
+ self.conversion_thread.cancel()
+ self.conversion_thread.wait()
+
def closeEvent(self, event):
if self.is_converting:
box = QMessageBox(self)
@@ -2495,16 +2501,12 @@ class abogen(QWidget):
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
box.setDefaultButton(QMessageBox.No)
if box.exec_() == QMessageBox.Yes:
- if (
- hasattr(self, "conversion_thread")
- and self.conversion_thread.isRunning()
- ):
- self.conversion_thread.cancel()
- self.conversion_thread.wait()
+ self.cleanup_conversion_thread()
event.accept()
else:
event.ignore()
else:
+ self.cleanup_conversion_thread()
event.accept()
def show_chapter_options_dialog(self, chapter_count):