mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0195788a54 | ||
|
|
1fd8fe21b7 | ||
|
|
89a7116c87 | ||
|
|
6b85a08ccb | ||
|
|
b9c3fd7c6c | ||
|
|
a35bea9053 | ||
|
|
e2802a2737 | ||
|
|
e86ad435e5 | ||
|
|
f2b42ef00e |
@@ -1,3 +1,14 @@
|
|||||||
|
# v1.1.2
|
||||||
|
- Now you can play the audio files while they are processing.
|
||||||
|
- Audio and subtitle files are now written directly to disk during generation, which significantly reduces memory usage.
|
||||||
|
- Added a better logic for detecting chapters from the epub, mentioned by @jefro108 in #33
|
||||||
|
- Added a new option: `Reset to default settings`, allowing users to reset all settings to their default values.
|
||||||
|
- Added a new option: `Disable Kokoro's internet access`. This lets you prevent Kokoro from downloading models or voices from HuggingFace Hub, which can help avoid long waiting times if your computer is offline.
|
||||||
|
- HuggingFace Hub telemetry is now disabled by default for improved privacy. (HuggingFace Hub is used by Kokoro to download its models)
|
||||||
|
- 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.
|
||||||
|
- Improvements in code structure.
|
||||||
|
|
||||||
# v1.1.1
|
# v1.1.1
|
||||||
- Fixed adding wrong file in queue for EPUB and PDF files, ensuring the correct file is added to the queue.
|
- Fixed adding wrong file in queue for EPUB and PDF files, ensuring the correct file is added to the queue.
|
||||||
- Reformatted the code using Black.
|
- Reformatted the code using Black.
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ Here’s Abogen in action: in this demo, it processes ∼3,000 characters of tex
|
|||||||
| **Open temp directory** | Opens the temporary directory where converted text files are stored. |
|
| **Open temp directory** | Opens the temporary directory where converted text files are stored. |
|
||||||
| **Clear temporary files** | Deletes temporary files created during the conversion or preview. |
|
| **Clear temporary files** | Deletes temporary files created during the conversion or preview. |
|
||||||
| **Check for updates at startup** | Automatically checks for updates when the program starts. |
|
| **Check for updates at startup** | Automatically checks for updates when the program starts. |
|
||||||
|
| **Disable Kokoro's internet access** | Prevents Kokoro from downloading models or voices from HuggingFace Hub, useful for offline use. |
|
||||||
|
| **Reset to default settings** | Resets all settings to their default values. |
|
||||||
|
|
||||||
## `Voice Mixer`
|
## `Voice Mixer`
|
||||||
<img title="Abogen Voice Mixer" src='https://raw.githubusercontent.com/denizsafak/abogen/refs/heads/main/demo/voice_mixer.png'>
|
<img title="Abogen Voice Mixer" src='https://raw.githubusercontent.com/denizsafak/abogen/refs/heads/main/demo/voice_mixer.png'>
|
||||||
|
|||||||
+2
-2
@@ -230,7 +230,7 @@ if errorlevel 1 (
|
|||||||
|
|
||||||
:: Install setup requirements
|
:: Install setup requirements
|
||||||
echo Installing setup requirements...
|
echo Installing setup requirements...
|
||||||
%PYTHON_CONSOLE_PATH% -m pip install --upgrade setuptools setuptools-scm wheel sphinx hatchling --no-warn-script-location
|
%PYTHON_CONSOLE_PATH% -m pip install --upgrade setuptools setuptools-scm wheel sphinx hatchling editables --no-warn-script-location
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo Failed to install setup requirements.
|
echo Failed to install setup requirements.
|
||||||
pause
|
pause
|
||||||
@@ -250,7 +250,7 @@ if errorlevel 1 (
|
|||||||
echo Checking and installing project dependencies...
|
echo Checking and installing project dependencies...
|
||||||
if exist %PYPROJECT_FILE% (
|
if exist %PYPROJECT_FILE% (
|
||||||
echo Installing project from pyproject.toml...
|
echo Installing project from pyproject.toml...
|
||||||
%PYTHON_CONSOLE_PATH% -m pip install . --no-warn-script-location
|
%PYTHON_CONSOLE_PATH% -m pip install -e . --no-warn-script-location
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo Failed to install from pyproject.toml.
|
echo Failed to install from pyproject.toml.
|
||||||
pause
|
pause
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.1.1
|
1.1.2
|
||||||
+18
-13
@@ -22,7 +22,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QLabel,
|
QLabel,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from utils import clean_text, calculate_text_length
|
from abogen.utils import clean_text, calculate_text_length
|
||||||
import os
|
import os
|
||||||
import logging # Add logging
|
import logging # Add logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -240,20 +240,17 @@ class HandlerDialog(QDialog):
|
|||||||
tag.decompose()
|
tag.decompose()
|
||||||
text = clean_text(soup.get_text()).strip()
|
text = clean_text(soup.get_text()).strip()
|
||||||
if text:
|
if text:
|
||||||
# Use doc_href as the identifier
|
|
||||||
self.content_texts[doc_href] = text
|
self.content_texts[doc_href] = text
|
||||||
self.content_lengths[doc_href] = len(text)
|
self.content_lengths[doc_href] = len(text)
|
||||||
# Create a synthetic TOC entry
|
|
||||||
title = f"Chapter {i+1}: {doc_href}"
|
|
||||||
# Try to get a better title from <h1> or <title>
|
|
||||||
h1 = soup.find("h1")
|
|
||||||
if h1 and h1.get_text(strip=True):
|
|
||||||
title = h1.get_text(strip=True)
|
|
||||||
else:
|
|
||||||
title_tag = soup.find("title")
|
|
||||||
if title_tag and title_tag.get_text(strip=True):
|
|
||||||
title = title_tag.get_text(strip=True)
|
|
||||||
|
|
||||||
|
title = None
|
||||||
|
if soup.title and soup.title.string:
|
||||||
|
title = soup.title.string.strip()
|
||||||
|
elif (h1 := soup.find("h1")) and h1.get_text(strip=True):
|
||||||
|
title = h1.get_text(strip=True)
|
||||||
|
|
||||||
|
if not title:
|
||||||
|
title = f"Untitled Chapter {i+1}"
|
||||||
synthetic_toc.append(
|
synthetic_toc.append(
|
||||||
(epub.Link(doc_href, title, doc_href), [])
|
(epub.Link(doc_href, title, doc_href), [])
|
||||||
) # Wrap in tuple and empty list for compatibility
|
) # Wrap in tuple and empty list for compatibility
|
||||||
@@ -1911,5 +1908,13 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
if self.pdf_doc is not None:
|
if self.pdf_doc is not None:
|
||||||
self.pdf_doc.close()
|
try:
|
||||||
|
if hasattr(self.pdf_doc, "is_closed"):
|
||||||
|
if not self.pdf_doc.is_closed:
|
||||||
|
self.pdf_doc.close()
|
||||||
|
else:
|
||||||
|
# Fallback: try/except close
|
||||||
|
self.pdf_doc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ VERSION = get_version()
|
|||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
CHAPTER_OPTIONS_COUNTDOWN = 30 # Countdown seconds for chapter options
|
CHAPTER_OPTIONS_COUNTDOWN = 30 # Countdown seconds for chapter options
|
||||||
|
SUBTITLE_FORMATS = [
|
||||||
|
("srt", "SRT (standard)"),
|
||||||
|
("ass_wide", "ASS (wide)"),
|
||||||
|
("ass_narrow", "ASS (narrow)"),
|
||||||
|
("ass_centered_wide", "ASS (centered wide)"),
|
||||||
|
("ass_centered_narrow", "ASS (centered narrow)"),
|
||||||
|
]
|
||||||
|
|
||||||
# Language description mapping
|
# Language description mapping
|
||||||
LANGUAGE_DESCRIPTIONS = {
|
LANGUAGE_DESCRIPTIONS = {
|
||||||
@@ -109,6 +116,7 @@ SAMPLE_VOICE_TEXTS = {
|
|||||||
COLORS = {
|
COLORS = {
|
||||||
"BLUE": "#007dff",
|
"BLUE": "#007dff",
|
||||||
"RED": "#c0392b",
|
"RED": "#c0392b",
|
||||||
|
"ORANGE": "#FFA500",
|
||||||
"GREEN": "#42ad4a",
|
"GREEN": "#42ad4a",
|
||||||
"GREEN_BG": "rgba(66, 173, 73, 0.1)",
|
"GREEN_BG": "rgba(66, 173, 73, 0.1)",
|
||||||
"GREEN_BG_HOVER": "rgba(66, 173, 73, 0.15)",
|
"GREEN_BG_HOVER": "rgba(66, 173, 73, 0.15)",
|
||||||
|
|||||||
+499
-519
File diff suppressed because it is too large
Load Diff
+124
-49
@@ -75,6 +75,7 @@ from abogen.constants import (
|
|||||||
VOICES_INTERNAL,
|
VOICES_INTERNAL,
|
||||||
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
||||||
COLORS,
|
COLORS,
|
||||||
|
SUBTITLE_FORMATS,
|
||||||
)
|
)
|
||||||
import threading
|
import threading
|
||||||
from abogen.voice_formula_gui import VoiceFormulaDialog
|
from abogen.voice_formula_gui import VoiceFormulaDialog
|
||||||
@@ -124,6 +125,18 @@ class IconProvider(QFileIconProvider):
|
|||||||
return super().icon(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"],
|
||||||
|
"grey": COLORS["LIGHT_DISABLED"],
|
||||||
|
None: COLORS["LIGHT_DISABLED"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InputBox(QLabel):
|
class InputBox(QLabel):
|
||||||
# Define CSS styles as class constants
|
# Define CSS styles as class constants
|
||||||
STYLE_DEFAULT = f"border:2px dashed #aaa; border-radius:5px; padding:20px; background:{COLORS['BLUE_BG']}; min-height:100px;"
|
STYLE_DEFAULT = f"border:2px dashed #aaa; border-radius:5px; padding:20px; background:{COLORS['BLUE_BG']}; min-height:100px;"
|
||||||
@@ -646,7 +659,7 @@ class abogen(QWidget):
|
|||||||
None # Add new variable to track the displayed file path
|
None # Add new variable to track the displayed file path
|
||||||
)
|
)
|
||||||
# Max log lines
|
# 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.selected_chapters = set()
|
||||||
self.last_opened_book_path = None # Track the last opened book path
|
self.last_opened_book_path = None # Track the last opened book path
|
||||||
self.last_output_path = None
|
self.last_output_path = None
|
||||||
@@ -700,9 +713,6 @@ class abogen(QWidget):
|
|||||||
self.queued_items = []
|
self.queued_items = []
|
||||||
self.current_queue_index = 0
|
self.current_queue_index = 0
|
||||||
|
|
||||||
# Log lines
|
|
||||||
self._log_lines = []
|
|
||||||
|
|
||||||
self.initUI()
|
self.initUI()
|
||||||
self.speed_slider.setValue(int(self.config.get("speed", 1.00) * 100))
|
self.speed_slider.setValue(int(self.config.get("speed", 1.00) * 100))
|
||||||
self.update_speed_label()
|
self.update_speed_label()
|
||||||
@@ -785,6 +795,7 @@ class abogen(QWidget):
|
|||||||
container_layout.addWidget(self.queue_row_widget)
|
container_layout.addWidget(self.queue_row_widget)
|
||||||
self.log_text = QTextEdit(self)
|
self.log_text = QTextEdit(self)
|
||||||
self.log_text.setReadOnly(True)
|
self.log_text.setReadOnly(True)
|
||||||
|
self.log_text.setUndoRedoEnabled(False)
|
||||||
self.log_text.setFrameStyle(QTextEdit.NoFrame)
|
self.log_text.setFrameStyle(QTextEdit.NoFrame)
|
||||||
self.log_text.setStyleSheet("QTextEdit { border: none; }")
|
self.log_text.setStyleSheet("QTextEdit { border: none; }")
|
||||||
self.log_text.hide()
|
self.log_text.hide()
|
||||||
@@ -944,14 +955,7 @@ class abogen(QWidget):
|
|||||||
self.subtitle_format_combo.setSizePolicy(
|
self.subtitle_format_combo.setSizePolicy(
|
||||||
QSizePolicy.Expanding, QSizePolicy.Fixed
|
QSizePolicy.Expanding, QSizePolicy.Fixed
|
||||||
)
|
)
|
||||||
subtitle_formats = [
|
for value, text in SUBTITLE_FORMATS:
|
||||||
("srt", "SRT (standard)"),
|
|
||||||
("ass_wide", "ASS (wide)"),
|
|
||||||
("ass_narrow", "ASS (narrow)"),
|
|
||||||
("ass_centered_wide", "ASS (centered wide)"),
|
|
||||||
("ass_centered_narrow", "ASS (centered narrow)"),
|
|
||||||
]
|
|
||||||
for value, text in subtitle_formats:
|
|
||||||
self.subtitle_format_combo.addItem(text, value)
|
self.subtitle_format_combo.addItem(text, value)
|
||||||
subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow")
|
subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow")
|
||||||
idx = self.subtitle_format_combo.findData(subtitle_format)
|
idx = self.subtitle_format_combo.findData(subtitle_format)
|
||||||
@@ -1458,16 +1462,12 @@ class abogen(QWidget):
|
|||||||
self.input_box.hide()
|
self.input_box.hide()
|
||||||
self.log_text.show()
|
self.log_text.show()
|
||||||
self.log_text.clear()
|
self.log_text.clear()
|
||||||
self._log_lines = []
|
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
|
|
||||||
def restore_input_box(self):
|
def restore_input_box(self):
|
||||||
self.log_text.hide()
|
self.log_text.hide()
|
||||||
self.input_box.show()
|
self.input_box.show()
|
||||||
|
|
||||||
def color_text(self, message, color):
|
|
||||||
return f'<span style="color:{color};">{message.replace(chr(10), "<br>")}</span><br>'
|
|
||||||
|
|
||||||
def update_log(self, message):
|
def update_log(self, message):
|
||||||
# Use signal-based approach for thread-safe logging
|
# Use signal-based approach for thread-safe logging
|
||||||
if QThread.currentThread() != QApplication.instance().thread():
|
if QThread.currentThread() != QApplication.instance().thread():
|
||||||
@@ -1479,38 +1479,36 @@ class abogen(QWidget):
|
|||||||
self._update_log_main_thread(message)
|
self._update_log_main_thread(message)
|
||||||
|
|
||||||
def _update_log_main_thread(self, message):
|
def _update_log_main_thread(self, message):
|
||||||
if not hasattr(self, "_log_lines"):
|
txt = self.log_text
|
||||||
self._log_lines = []
|
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):
|
if isinstance(message, tuple):
|
||||||
text, spec = message
|
text, spec = message
|
||||||
color = "green" if spec is True else ("red" if spec is False else spec)
|
fmt.setForeground(QColor(LOG_COLOR_MAP.get(spec, COLORS["LIGHT_DISABLED"])))
|
||||||
html_line = self.color_text(text, color)
|
|
||||||
else:
|
else:
|
||||||
html_line = f"{str(message).replace(chr(10), '<br>')}<br>"
|
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:
|
if at_bottom:
|
||||||
sb.setValue(sb.maximum())
|
sb.setValue(sb.maximum())
|
||||||
|
|
||||||
QApplication.processEvents()
|
|
||||||
|
|
||||||
def _get_queue_progress_format(self, value=None):
|
def _get_queue_progress_format(self, value=None):
|
||||||
"""Return the progress bar format string for queue mode."""
|
"""Return the progress bar format string for queue mode."""
|
||||||
if (
|
if (
|
||||||
@@ -1889,7 +1887,7 @@ class abogen(QWidget):
|
|||||||
f"<span style='color:{COLORS['GREEN']}; font-weight:bold;'>{idx}) {os.path.basename(item.file_name)}</span><br>"
|
f"<span style='color:{COLORS['GREEN']}; font-weight:bold;'>{idx}) {os.path.basename(item.file_name)}</span><br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Language:</span> {item.lang_code}<br>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Language:</span> {item.lang_code}<br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Voice:</span> {item.voice}<br>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Voice:</span> {item.voice}<br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Speed:</span> {item.speed * 100:.1f}%<br>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Speed:</span> {item.speed}<br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Characters:</span> {item.total_char_count}<br>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Characters:</span> {item.total_char_count}<br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Input:</span> {item.file_name}<br>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Input:</span> {item.file_name}<br>"
|
||||||
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Output:</span> {output}</span>"
|
f"<span style='color:{COLORS['LIGHT_DISABLED']};'>Output:</span> {output}</span>"
|
||||||
@@ -1988,7 +1986,6 @@ class abogen(QWidget):
|
|||||||
else:
|
else:
|
||||||
# More items in queue: clear log and reload for next item
|
# More items in queue: clear log and reload for next item
|
||||||
self.log_text.clear()
|
self.log_text.clear()
|
||||||
self._log_lines = []
|
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
|
|
||||||
# Start new queued item, if we're using a queued conversion
|
# Start new queued item, if we're using a queued conversion
|
||||||
@@ -2032,7 +2029,6 @@ class abogen(QWidget):
|
|||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
self.restore_input_box()
|
self.restore_input_box()
|
||||||
self.log_text.clear()
|
self.log_text.clear()
|
||||||
self._log_lines = []
|
|
||||||
|
|
||||||
# Use displayed_file_path instead of selected_file for EPUBs or PDFs
|
# Use displayed_file_path instead of selected_file for EPUBs or PDFs
|
||||||
display_path = (
|
display_path = (
|
||||||
@@ -2444,7 +2440,6 @@ class abogen(QWidget):
|
|||||||
self.finish_widget.hide()
|
self.finish_widget.hide()
|
||||||
self.restore_input_box()
|
self.restore_input_box()
|
||||||
self.log_text.clear()
|
self.log_text.clear()
|
||||||
self._log_lines = []
|
|
||||||
display_path = (
|
display_path = (
|
||||||
self.displayed_file_path
|
self.displayed_file_path
|
||||||
if self.displayed_file_path
|
if self.displayed_file_path
|
||||||
@@ -2484,6 +2479,16 @@ class abogen(QWidget):
|
|||||||
self.config["use_gpu"] = self.use_gpu
|
self.config["use_gpu"] = self.use_gpu
|
||||||
save_config(self.config)
|
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):
|
def closeEvent(self, event):
|
||||||
if self.is_converting:
|
if self.is_converting:
|
||||||
box = QMessageBox(self)
|
box = QMessageBox(self)
|
||||||
@@ -2495,16 +2500,12 @@ class abogen(QWidget):
|
|||||||
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||||
box.setDefaultButton(QMessageBox.No)
|
box.setDefaultButton(QMessageBox.No)
|
||||||
if box.exec_() == QMessageBox.Yes:
|
if box.exec_() == QMessageBox.Yes:
|
||||||
if (
|
self.cleanup_conversion_thread()
|
||||||
hasattr(self, "conversion_thread")
|
|
||||||
and self.conversion_thread.isRunning()
|
|
||||||
):
|
|
||||||
self.conversion_thread.cancel()
|
|
||||||
self.conversion_thread.wait()
|
|
||||||
event.accept()
|
event.accept()
|
||||||
else:
|
else:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
else:
|
else:
|
||||||
|
self.cleanup_conversion_thread()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
def show_chapter_options_dialog(self, chapter_count):
|
def show_chapter_options_dialog(self, chapter_count):
|
||||||
@@ -2779,6 +2780,17 @@ class abogen(QWidget):
|
|||||||
# Add seperator"
|
# Add seperator"
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
|
|
||||||
|
# Add "Disable Kokoro's internet access" option
|
||||||
|
disable_kokoro_action = QAction("Disable Kokoro's internet access", self)
|
||||||
|
disable_kokoro_action.setCheckable(True)
|
||||||
|
disable_kokoro_action.setChecked(
|
||||||
|
self.config.get("disable_kokoro_internet", False)
|
||||||
|
)
|
||||||
|
disable_kokoro_action.triggered.connect(
|
||||||
|
lambda checked: self.toggle_kokoro_internet_access(checked)
|
||||||
|
)
|
||||||
|
menu.addAction(disable_kokoro_action)
|
||||||
|
|
||||||
# Add check for updates option
|
# Add check for updates option
|
||||||
check_updates_action = QAction("Check for updates at startup", self)
|
check_updates_action = QAction("Check for updates at startup", self)
|
||||||
check_updates_action.setCheckable(True)
|
check_updates_action.setCheckable(True)
|
||||||
@@ -2786,6 +2798,11 @@ class abogen(QWidget):
|
|||||||
check_updates_action.triggered.connect(self.toggle_check_updates)
|
check_updates_action.triggered.connect(self.toggle_check_updates)
|
||||||
menu.addAction(check_updates_action)
|
menu.addAction(check_updates_action)
|
||||||
|
|
||||||
|
# Add "Reset to default settings" option
|
||||||
|
reset_defaults_action = QAction("Reset to default settings", self)
|
||||||
|
reset_defaults_action.triggered.connect(self.reset_to_default_settings)
|
||||||
|
menu.addAction(reset_defaults_action)
|
||||||
|
|
||||||
# Add about action
|
# Add about action
|
||||||
about_action = QAction("About", self)
|
about_action = QAction("About", self)
|
||||||
about_action.triggered.connect(self.show_about_dialog)
|
about_action.triggered.connect(self.show_about_dialog)
|
||||||
@@ -2798,6 +2815,64 @@ class abogen(QWidget):
|
|||||||
self.config["replace_single_newlines"] = enabled
|
self.config["replace_single_newlines"] = enabled
|
||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
|
|
||||||
|
def toggle_kokoro_internet_access(self, disabled):
|
||||||
|
if disabled:
|
||||||
|
message = (
|
||||||
|
"Disabling Kokoro's internet access will block downloads of models and voices from Hugging Face Hub. "
|
||||||
|
"This can make processing faster when there is no internet connection, since no requests will be made. "
|
||||||
|
"The app needs to restart to apply this change.\n\nDo you want to continue?"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
message = (
|
||||||
|
"Enabling Kokoro's internet access will allow it to download models and voices from Hugging Face Hub. "
|
||||||
|
"The app needs to restart to apply this change.\n\nDo you want to continue?"
|
||||||
|
)
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Restart Required",
|
||||||
|
message,
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No,
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
self.config["disable_kokoro_internet"] = disabled
|
||||||
|
save_config(self.config)
|
||||||
|
try:
|
||||||
|
from PyQt5.QtCore import QProcess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
QProcess.startDetached(sys.executable, sys.argv)
|
||||||
|
QApplication.quit()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(
|
||||||
|
self, "Restart Failed", f"Failed to restart the application:\n{e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def reset_to_default_settings(self):
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Reset Settings",
|
||||||
|
"This will reset all settings to their default values and restart the application.\n\nDo you want to continue?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No,
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
from abogen.utils import get_user_config_path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
config_path = get_user_config_path()
|
||||||
|
try:
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
os.remove(config_path)
|
||||||
|
from PyQt5.QtCore import QProcess
|
||||||
|
|
||||||
|
QProcess.startDetached(sys.executable, sys.argv)
|
||||||
|
QApplication.quit()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(
|
||||||
|
self, "Reset Error", f"Could not reset settings:\n{e}"
|
||||||
|
)
|
||||||
|
|
||||||
def reveal_config_in_explorer(self):
|
def reveal_config_in_explorer(self):
|
||||||
"""Open the configuration file location in file explorer."""
|
"""Open the configuration file location in file explorer."""
|
||||||
from abogen.utils import get_user_config_path
|
from abogen.utils import get_user_config_path
|
||||||
|
|||||||
+13
-1
@@ -1,3 +1,4 @@
|
|||||||
|
from json import load
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
@@ -7,8 +8,19 @@ from PyQt5.QtCore import qInstallMessageHandler, QtMsgType
|
|||||||
|
|
||||||
# Add the directory to Python path
|
# Add the directory to Python path
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
from abogen.utils import get_resource_path, load_config
|
||||||
|
|
||||||
|
# Set Hugging Face Hub environment variables
|
||||||
|
os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1" # Disable Hugging Face telemetry
|
||||||
|
os.environ["HF_HUB_ETAG_TIMEOUT"] = "10" # Metadata request timeout (seconds)
|
||||||
|
os.environ["HF_HUB_DOWNLOAD_TIMEOUT"] = "10" # File download timeout (seconds)
|
||||||
|
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" # Disable symlinks warning
|
||||||
|
if load_config().get("disable_kokoro_internet", False):
|
||||||
|
print("INFO: Kokoro's internet access is disabled.")
|
||||||
|
os.environ["HF_HUB_OFFLINE"] = "1" # Disable Hugging Face Hub internet access
|
||||||
|
|
||||||
from abogen.gui import abogen
|
from abogen.gui import abogen
|
||||||
from abogen.utils import get_resource_path
|
|
||||||
from abogen.constants import PROGRAM_NAME, VERSION
|
from abogen.constants import PROGRAM_NAME, VERSION
|
||||||
|
|
||||||
# Set environment variables for AMD ROCm
|
# Set environment variables for AMD ROCm
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ class QueueManager(QDialog):
|
|||||||
)
|
)
|
||||||
list_item.setToolTip(tooltip)
|
list_item.setToolTip(tooltip)
|
||||||
list_item.setIcon(icon)
|
list_item.setIcon(icon)
|
||||||
|
list_item.setData(Qt.UserRole, file_name)
|
||||||
# Use custom widget for display
|
# Use custom widget for display
|
||||||
char_count = getattr(item, "total_char_count", 0)
|
char_count = getattr(item, "total_char_count", 0)
|
||||||
widget = QueueListItemWidget(file_name, char_count)
|
widget = QueueListItemWidget(file_name, char_count)
|
||||||
@@ -472,9 +473,9 @@ class QueueManager(QDialog):
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
item = selected_items[0]
|
item = selected_items[0]
|
||||||
display_name = item.text()
|
file_path = item.data(Qt.UserRole)
|
||||||
for q in self.queue:
|
for q in self.queue:
|
||||||
if os.path.basename(q.file_name) == display_name:
|
if q.file_name == file_path:
|
||||||
if not os.path.exists(q.file_name):
|
if not os.path.exists(q.file_name):
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, "File Not Found", f"The file does not exist."
|
self, "File Not Found", f"The file does not exist."
|
||||||
@@ -493,9 +494,9 @@ class QueueManager(QDialog):
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
item = selected_items[0]
|
item = selected_items[0]
|
||||||
display_name = item.text()
|
file_path = item.data(Qt.UserRole)
|
||||||
for q in self.queue:
|
for q in self.queue:
|
||||||
if os.path.basename(q.file_name) == display_name:
|
if q.file_name == file_path:
|
||||||
if not os.path.exists(q.file_name):
|
if not os.path.exists(q.file_name):
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, "File Not Found", f"The file does not exist."
|
self, "File Not Found", f"The file does not exist."
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import subprocess
|
|||||||
import re
|
import re
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
# suppress warnings and disable HF hub symlink warnings
|
|
||||||
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
|
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user