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 | ||
|
|
7a8df9b34e |
@@ -1,3 +1,18 @@
|
|||||||
|
# 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
|
||||||
|
- 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.
|
||||||
|
|
||||||
# v1.1.0
|
# v1.1.0
|
||||||
- Added queue system for processing multiple items, allowing users to add multiple files and process them in a queue, mentioned by @jborza in #30 (Special thanks to @jborza for implementing this feature in PR #35)
|
- Added queue system for processing multiple items, allowing users to add multiple files and process them in a queue, mentioned by @jborza in #30 (Special thanks to @jborza for implementing this feature in PR #35)
|
||||||
- Added a feature that allows selecting multiple items in book handler (in right click menu) by @jborza in #31, that fixes #28
|
- Added a feature that allows selecting multiple items in book handler (in right click menu) by @jborza in #31, that fixes #28
|
||||||
|
|||||||
@@ -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.0
|
1.1.2
|
||||||
+84
-35
@@ -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
|
||||||
@@ -997,10 +994,21 @@ class HandlerDialog(QDialog):
|
|||||||
bookmark_item.setData(0, Qt.UserRole, page_id)
|
bookmark_item.setData(0, Qt.UserRole, page_id)
|
||||||
# only allow checking if this chapter has content
|
# only allow checking if this chapter has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
bookmark_item.setFlags(bookmark_item.flags() | Qt.ItemIsUserCheckable)
|
bookmark_item.setFlags(
|
||||||
bookmark_item.setCheckState(0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked)
|
bookmark_item.flags() | Qt.ItemIsUserCheckable
|
||||||
|
)
|
||||||
|
bookmark_item.setCheckState(
|
||||||
|
0,
|
||||||
|
(
|
||||||
|
Qt.Checked
|
||||||
|
if page_id in self.checked_chapters
|
||||||
|
else Qt.Unchecked
|
||||||
|
),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
bookmark_item.setFlags(bookmark_item.flags() & ~Qt.ItemIsUserCheckable)
|
bookmark_item.setFlags(
|
||||||
|
bookmark_item.flags() & ~Qt.ItemIsUserCheckable
|
||||||
|
)
|
||||||
# map for uncategorized pages
|
# map for uncategorized pages
|
||||||
self.bookmark_items_map[page_num] = bookmark_item
|
self.bookmark_items_map[page_num] = bookmark_item
|
||||||
|
|
||||||
@@ -1027,10 +1035,21 @@ class HandlerDialog(QDialog):
|
|||||||
page_item.setData(0, Qt.UserRole, page_id)
|
page_item.setData(0, Qt.UserRole, page_id)
|
||||||
# only allow checking if this sub-page has content
|
# only allow checking if this sub-page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
page_item.setFlags(
|
||||||
page_item.setCheckState(0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked)
|
page_item.flags() | Qt.ItemIsUserCheckable
|
||||||
|
)
|
||||||
|
page_item.setCheckState(
|
||||||
|
0,
|
||||||
|
(
|
||||||
|
Qt.Checked
|
||||||
|
if page_id in self.checked_chapters
|
||||||
|
else Qt.Unchecked
|
||||||
|
),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
page_item.setFlags(
|
||||||
|
page_item.flags() & ~Qt.ItemIsUserCheckable
|
||||||
|
)
|
||||||
|
|
||||||
added_pages.add(sub_page_num)
|
added_pages.add(sub_page_num)
|
||||||
|
|
||||||
@@ -1038,11 +1057,15 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
covered_pages = set(added_pages)
|
covered_pages = set(added_pages)
|
||||||
# attach any pages without direct bookmarks under nearest preceding chapter
|
# attach any pages without direct bookmarks under nearest preceding chapter
|
||||||
uncategorized_pages = [i for i in range(len(self.pdf_doc)) if i not in covered_pages]
|
uncategorized_pages = [
|
||||||
|
i for i in range(len(self.pdf_doc)) if i not in covered_pages
|
||||||
|
]
|
||||||
for page_num in uncategorized_pages:
|
for page_num in uncategorized_pages:
|
||||||
# find nearest previous bookmark
|
# find nearest previous bookmark
|
||||||
prev_nums = [n for n in sorted(self.bookmark_items_map) if n < page_num]
|
prev_nums = [n for n in sorted(self.bookmark_items_map) if n < page_num]
|
||||||
parent_item = self.bookmark_items_map[prev_nums[-1]] if prev_nums else self.treeWidget
|
parent_item = (
|
||||||
|
self.bookmark_items_map[prev_nums[-1]] if prev_nums else self.treeWidget
|
||||||
|
)
|
||||||
page_id = f"page_{page_num+1}"
|
page_id = f"page_{page_num+1}"
|
||||||
title = f"Page {page_num+1}"
|
title = f"Page {page_num+1}"
|
||||||
text = self.content_texts.get(page_id, "").strip()
|
text = self.content_texts.get(page_id, "").strip()
|
||||||
@@ -1055,7 +1078,9 @@ class HandlerDialog(QDialog):
|
|||||||
# only allow checking if uncategorized page has content
|
# only allow checking if uncategorized page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
||||||
page_item.setCheckState(0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked)
|
page_item.setCheckState(
|
||||||
|
0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
||||||
|
|
||||||
@@ -1081,7 +1106,9 @@ class HandlerDialog(QDialog):
|
|||||||
# only allow checking if standalone page has content
|
# only allow checking if standalone page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
||||||
page_item.setCheckState(0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked)
|
page_item.setCheckState(
|
||||||
|
0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
||||||
|
|
||||||
@@ -1501,7 +1528,9 @@ class HandlerDialog(QDialog):
|
|||||||
authors_text = ", ".join(self.book_metadata["authors"])
|
authors_text = ", ".join(self.book_metadata["authors"])
|
||||||
html_content += f"<p style='text-align: center; font-style: italic;'>By {authors_text}</p>"
|
html_content += f"<p style='text-align: center; font-style: italic;'>By {authors_text}</p>"
|
||||||
|
|
||||||
if self.book_metadata["publisher"] or self.book_metadata.get("publication_year"):
|
if self.book_metadata["publisher"] or self.book_metadata.get(
|
||||||
|
"publication_year"
|
||||||
|
):
|
||||||
pub_info = []
|
pub_info = []
|
||||||
if self.book_metadata["publisher"]:
|
if self.book_metadata["publisher"]:
|
||||||
pub_info.append(f"Published by {self.book_metadata['publisher']}")
|
pub_info.append(f"Published by {self.book_metadata['publisher']}")
|
||||||
@@ -1543,7 +1572,9 @@ class HandlerDialog(QDialog):
|
|||||||
try:
|
try:
|
||||||
author_items = self.book.get_metadata("DC", "creator")
|
author_items = self.book.get_metadata("DC", "creator")
|
||||||
if author_items:
|
if author_items:
|
||||||
metadata["authors"] = [author[0] for author in author_items if len(author) > 0]
|
metadata["authors"] = [
|
||||||
|
author[0] for author in author_items if len(author) > 0
|
||||||
|
]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Error extracting author metadata: {e}")
|
logging.warning(f"Error extracting author metadata: {e}")
|
||||||
|
|
||||||
@@ -1567,7 +1598,7 @@ class HandlerDialog(QDialog):
|
|||||||
if date_items and len(date_items) > 0:
|
if date_items and len(date_items) > 0:
|
||||||
date_str = date_items[0][0]
|
date_str = date_items[0][0]
|
||||||
# Try to extract just the year from the date string
|
# Try to extract just the year from the date string
|
||||||
year_match = re.search(r'\b(19|20)\d{2}\b', date_str)
|
year_match = re.search(r"\b(19|20)\d{2}\b", date_str)
|
||||||
if year_match:
|
if year_match:
|
||||||
metadata["publication_year"] = year_match.group(0)
|
metadata["publication_year"] = year_match.group(0)
|
||||||
else:
|
else:
|
||||||
@@ -1607,12 +1638,12 @@ class HandlerDialog(QDialog):
|
|||||||
# Try to extract publication date from PDF metadata
|
# Try to extract publication date from PDF metadata
|
||||||
if "creationDate" in pdf_info:
|
if "creationDate" in pdf_info:
|
||||||
date_str = pdf_info["creationDate"]
|
date_str = pdf_info["creationDate"]
|
||||||
year_match = re.search(r'D:(\d{4})', date_str)
|
year_match = re.search(r"D:(\d{4})", date_str)
|
||||||
if year_match:
|
if year_match:
|
||||||
metadata["publication_year"] = year_match.group(1)
|
metadata["publication_year"] = year_match.group(1)
|
||||||
elif "modDate" in pdf_info:
|
elif "modDate" in pdf_info:
|
||||||
date_str = pdf_info["modDate"]
|
date_str = pdf_info["modDate"]
|
||||||
year_match = re.search(r'D:(\d{4})', date_str)
|
year_match = re.search(r"D:(\d{4})", date_str)
|
||||||
if year_match:
|
if year_match:
|
||||||
metadata["publication_year"] = year_match.group(1)
|
metadata["publication_year"] = year_match.group(1)
|
||||||
|
|
||||||
@@ -1644,11 +1675,15 @@ class HandlerDialog(QDialog):
|
|||||||
authors = metadata.get("authors") or ["Unknown"]
|
authors = metadata.get("authors") or ["Unknown"]
|
||||||
authors_text = ", ".join(authors)
|
authors_text = ", ".join(authors)
|
||||||
album_artist = authors_text or "Unknown"
|
album_artist = authors_text or "Unknown"
|
||||||
year = metadata.get("publication_year") or current_year # Use publication year if available
|
year = (
|
||||||
|
metadata.get("publication_year") or current_year
|
||||||
|
) # Use publication year if available
|
||||||
|
|
||||||
# Count chapters/pages
|
# Count chapters/pages
|
||||||
total_chapters = len(self.checked_chapters)
|
total_chapters = len(self.checked_chapters)
|
||||||
chapter_text = f"{total_chapters} {'Chapters' if self.file_type == 'epub' else 'Pages'}"
|
chapter_text = (
|
||||||
|
f"{total_chapters} {'Chapters' if self.file_type == 'epub' else 'Pages'}"
|
||||||
|
)
|
||||||
|
|
||||||
# Format metadata tags
|
# Format metadata tags
|
||||||
metadata_tags = [
|
metadata_tags = [
|
||||||
@@ -1658,7 +1693,7 @@ class HandlerDialog(QDialog):
|
|||||||
f"<<METADATA_YEAR:{year}>>",
|
f"<<METADATA_YEAR:{year}>>",
|
||||||
f"<<METADATA_ALBUM_ARTIST:{album_artist}>>",
|
f"<<METADATA_ALBUM_ARTIST:{album_artist}>>",
|
||||||
f"<<METADATA_COMPOSER:Narrator>>",
|
f"<<METADATA_COMPOSER:Narrator>>",
|
||||||
f"<<METADATA_GENRE:Audiobook>>"
|
f"<<METADATA_GENRE:Audiobook>>",
|
||||||
]
|
]
|
||||||
|
|
||||||
return "\n".join(metadata_tags)
|
return "\n".join(metadata_tags)
|
||||||
@@ -1730,7 +1765,10 @@ class HandlerDialog(QDialog):
|
|||||||
if text:
|
if text:
|
||||||
all_content.append(text)
|
all_content.append(text)
|
||||||
included_text_ids.add(page_id)
|
included_text_ids.add(page_id)
|
||||||
return metadata_tags + "\n\n" + "\n\n".join(all_content), all_checked_identifiers
|
return (
|
||||||
|
metadata_tags + "\n\n" + "\n\n".join(all_content),
|
||||||
|
all_checked_identifiers,
|
||||||
|
)
|
||||||
|
|
||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
@@ -1788,7 +1826,10 @@ class HandlerDialog(QDialog):
|
|||||||
included_text_ids.add(identifier)
|
included_text_ids.add(identifier)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
|
|
||||||
return metadata_tags + "\n\n" + "\n\n".join([t[1] for t in section_titles]), all_checked_identifiers
|
return (
|
||||||
|
metadata_tags + "\n\n" + "\n\n".join([t[1] for t in section_titles]),
|
||||||
|
all_checked_identifiers,
|
||||||
|
)
|
||||||
|
|
||||||
def on_save_chapters_changed(self, state):
|
def on_save_chapters_changed(self, state):
|
||||||
self.save_chapters_separately = bool(state)
|
self.save_chapters_separately = bool(state)
|
||||||
@@ -1826,8 +1867,8 @@ class HandlerDialog(QDialog):
|
|||||||
print(f"Checking selected items: {state}")
|
print(f"Checking selected items: {state}")
|
||||||
self.treeWidget.blockSignals(True)
|
self.treeWidget.blockSignals(True)
|
||||||
for item in self.treeWidget.selectedItems():
|
for item in self.treeWidget.selectedItems():
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemIsUserCheckable:
|
||||||
item.setCheckState(0, Qt.Checked if state else Qt.Unchecked)
|
item.setCheckState(0, Qt.Checked if state else Qt.Unchecked)
|
||||||
self.treeWidget.blockSignals(False)
|
self.treeWidget.blockSignals(False)
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
|
|
||||||
@@ -1867,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)",
|
||||||
|
|||||||
+564
-444
File diff suppressed because it is too large
Load Diff
+428
-166
File diff suppressed because it is too large
Load Diff
+10
-1
@@ -1,16 +1,20 @@
|
|||||||
log_callback = None
|
log_callback = None
|
||||||
show_warning_signal_emitter = None # Renamed for clarity
|
show_warning_signal_emitter = None # Renamed for clarity
|
||||||
|
|
||||||
|
|
||||||
def set_log_callback(cb):
|
def set_log_callback(cb):
|
||||||
global log_callback
|
global log_callback
|
||||||
log_callback = cb
|
log_callback = cb
|
||||||
|
|
||||||
|
|
||||||
def set_show_warning_signal_emitter(emitter): # Renamed for clarity
|
def set_show_warning_signal_emitter(emitter): # Renamed for clarity
|
||||||
global show_warning_signal_emitter
|
global show_warning_signal_emitter
|
||||||
show_warning_signal_emitter = emitter
|
show_warning_signal_emitter = emitter
|
||||||
|
|
||||||
|
|
||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
|
|
||||||
|
|
||||||
def tracked_hf_hub_download(*args, **kwargs):
|
def tracked_hf_hub_download(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
local_kwargs = dict(kwargs)
|
local_kwargs = dict(kwargs)
|
||||||
@@ -22,7 +26,10 @@ def tracked_hf_hub_download(*args, **kwargs):
|
|||||||
if filename.endswith(".pth"):
|
if filename.endswith(".pth"):
|
||||||
msg = f"\nDownloading model '{filename}' from Hugging Face ({repo_id}). This may take a while. Please wait..."
|
msg = f"\nDownloading model '{filename}' from Hugging Face ({repo_id}). This may take a while. Please wait..."
|
||||||
if show_warning_signal_emitter: # Check if the emitter is set
|
if show_warning_signal_emitter: # Check if the emitter is set
|
||||||
show_warning_signal_emitter.emit("Downloading Model", f"Downloading model '{filename}' from Hugging Face repository '{repo_id}'. This may take a while, please wait.")
|
show_warning_signal_emitter.emit(
|
||||||
|
"Downloading Model",
|
||||||
|
f"Downloading model '{filename}' from Hugging Face repository '{repo_id}'. This may take a while, please wait.",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
msg = f"\nDownloading '{filename}' from Hugging Face ({repo_id}). Please wait..."
|
msg = f"\nDownloading '{filename}' from Hugging Face ({repo_id}). Please wait..."
|
||||||
if log_callback:
|
if log_callback:
|
||||||
@@ -32,5 +39,7 @@ def tracked_hf_hub_download(*args, **kwargs):
|
|||||||
print(msg, flush=True)
|
print(msg, flush=True)
|
||||||
return hf_hub_download(*args, **kwargs)
|
return hf_hub_download(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
import huggingface_hub
|
import huggingface_hub
|
||||||
|
|
||||||
huggingface_hub.hf_hub_download = tracked_hf_hub_download
|
huggingface_hub.hf_hub_download = tracked_hf_hub_download
|
||||||
+3
-1
@@ -1,18 +1,20 @@
|
|||||||
import gpustat
|
import gpustat
|
||||||
|
|
||||||
|
|
||||||
def check():
|
def check():
|
||||||
try:
|
try:
|
||||||
stats = gpustat.new_query()
|
stats = gpustat.new_query()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nvidia_keywords = ['nvidia', 'rtx', 'gtx', 'quadro', 'tesla', 'titan', 'mx']
|
nvidia_keywords = ["nvidia", "rtx", "gtx", "quadro", "tesla", "titan", "mx"]
|
||||||
for gpu in stats.gpus:
|
for gpu in stats.gpus:
|
||||||
name = gpu.name.lower()
|
name = gpu.name.lower()
|
||||||
if any(keyword in name for keyword in nvidia_keywords):
|
if any(keyword in name for keyword in nvidia_keywords):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
stats = gpustat.new_query()
|
stats = gpustat.new_query()
|
||||||
for gpu in stats.gpus:
|
for gpu in stats.gpus:
|
||||||
|
|||||||
+14
-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
|
||||||
@@ -25,6 +37,7 @@ if sys.stderr is None:
|
|||||||
if platform.system() == "Darwin" and platform.processor() == "arm":
|
if platform.system() == "Darwin" and platform.processor() == "arm":
|
||||||
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
||||||
|
|
||||||
|
|
||||||
# Custom message handler to filter out specific Qt warnings
|
# Custom message handler to filter out specific Qt warnings
|
||||||
def qt_message_handler(mode, context, message):
|
def qt_message_handler(mode, context, message):
|
||||||
if "Wayland does not support QWindow::requestActivate()" in message:
|
if "Wayland does not support QWindow::requestActivate()" in message:
|
||||||
|
|||||||
+118
-55
@@ -20,6 +20,7 @@ from abogen.constants import COLORS
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from PyQt5.QtGui import QFontMetrics
|
from PyQt5.QtGui import QFontMetrics
|
||||||
|
|
||||||
|
|
||||||
class ElidedLabel(QLabel):
|
class ElidedLabel(QLabel):
|
||||||
def __init__(self, text, parent=None):
|
def __init__(self, text, parent=None):
|
||||||
super().__init__(text, parent)
|
super().__init__(text, parent)
|
||||||
@@ -40,6 +41,7 @@ class ElidedLabel(QLabel):
|
|||||||
def fullText(self):
|
def fullText(self):
|
||||||
return self._full_text
|
return self._full_text
|
||||||
|
|
||||||
|
|
||||||
class QueueListItemWidget(QWidget):
|
class QueueListItemWidget(QWidget):
|
||||||
def __init__(self, file_name, char_count):
|
def __init__(self, file_name, char_count):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -47,6 +49,7 @@ class QueueListItemWidget(QWidget):
|
|||||||
layout.setContentsMargins(12, 0, 6, 0)
|
layout.setContentsMargins(12, 0, 6, 0)
|
||||||
layout.setSpacing(0)
|
layout.setSpacing(0)
|
||||||
import os
|
import os
|
||||||
|
|
||||||
name_label = ElidedLabel(os.path.basename(file_name))
|
name_label = ElidedLabel(os.path.basename(file_name))
|
||||||
char_label = QLabel(f"Chars: {char_count}")
|
char_label = QLabel(f"Chars: {char_count}")
|
||||||
char_label.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']};")
|
char_label.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']};")
|
||||||
@@ -56,6 +59,7 @@ class QueueListItemWidget(QWidget):
|
|||||||
layout.addWidget(char_label, 0)
|
layout.addWidget(char_label, 0)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
|
||||||
class DroppableQueueListWidget(QListWidget):
|
class DroppableQueueListWidget(QListWidget):
|
||||||
def __init__(self, parent_dialog):
|
def __init__(self, parent_dialog):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -73,7 +77,7 @@ class DroppableQueueListWidget(QListWidget):
|
|||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
if event.mimeData().hasUrls():
|
if event.mimeData().hasUrls():
|
||||||
for url in event.mimeData().urls():
|
for url in event.mimeData().urls():
|
||||||
if url.isLocalFile() and url.toLocalFile().lower().endswith('.txt'):
|
if url.isLocalFile() and url.toLocalFile().lower().endswith(".txt"):
|
||||||
self.drag_overlay.resize(self.size())
|
self.drag_overlay.resize(self.size())
|
||||||
self.drag_overlay.setVisible(True)
|
self.drag_overlay.setVisible(True)
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
@@ -84,7 +88,7 @@ class DroppableQueueListWidget(QListWidget):
|
|||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
if event.mimeData().hasUrls():
|
if event.mimeData().hasUrls():
|
||||||
for url in event.mimeData().urls():
|
for url in event.mimeData().urls():
|
||||||
if url.isLocalFile() and url.toLocalFile().lower().endswith('.txt'):
|
if url.isLocalFile() and url.toLocalFile().lower().endswith(".txt"):
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
return
|
return
|
||||||
event.ignore()
|
event.ignore()
|
||||||
@@ -96,7 +100,11 @@ class DroppableQueueListWidget(QListWidget):
|
|||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
self.drag_overlay.setVisible(False)
|
self.drag_overlay.setVisible(False)
|
||||||
if event.mimeData().hasUrls():
|
if event.mimeData().hasUrls():
|
||||||
file_paths = [url.toLocalFile() for url in event.mimeData().urls() if url.isLocalFile() and url.toLocalFile().lower().endswith('.txt')]
|
file_paths = [
|
||||||
|
url.toLocalFile()
|
||||||
|
for url in event.mimeData().urls()
|
||||||
|
if url.isLocalFile() and url.toLocalFile().lower().endswith(".txt")
|
||||||
|
]
|
||||||
if file_paths:
|
if file_paths:
|
||||||
self.parent_dialog.add_files_from_paths(file_paths)
|
self.parent_dialog.add_files_from_paths(file_paths)
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
@@ -107,14 +115,17 @@ class DroppableQueueListWidget(QListWidget):
|
|||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
if hasattr(self, 'drag_overlay'):
|
if hasattr(self, "drag_overlay"):
|
||||||
self.drag_overlay.resize(self.size())
|
self.drag_overlay.resize(self.size())
|
||||||
|
|
||||||
|
|
||||||
class QueueManager(QDialog):
|
class QueueManager(QDialog):
|
||||||
def __init__(self, parent, queue: list, title="Queue Manager", size=(600, 700)):
|
def __init__(self, parent, queue: list, title="Queue Manager", size=(600, 700)):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self._original_queue = deepcopy(queue) # Store a deep copy of the original queue
|
self._original_queue = deepcopy(
|
||||||
|
queue
|
||||||
|
) # Store a deep copy of the original queue
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
layout.setContentsMargins(15, 15, 15, 15) # set main layout margins
|
layout.setContentsMargins(15, 15, 15, 15) # set main layout margins
|
||||||
@@ -141,10 +152,12 @@ class QueueManager(QDialog):
|
|||||||
# Overlay label for empty queue
|
# Overlay label for empty queue
|
||||||
self.empty_overlay = QLabel(
|
self.empty_overlay = QLabel(
|
||||||
"Drag and drop your text files here or use the 'Add files' button.",
|
"Drag and drop your text files here or use the 'Add files' button.",
|
||||||
self.listwidget
|
self.listwidget,
|
||||||
)
|
)
|
||||||
self.empty_overlay.setAlignment(Qt.AlignCenter)
|
self.empty_overlay.setAlignment(Qt.AlignCenter)
|
||||||
self.empty_overlay.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']}; background: transparent; padding: 20px;")
|
self.empty_overlay.setStyleSheet(
|
||||||
|
f"color: {COLORS['LIGHT_DISABLED']}; background: transparent; padding: 20px;"
|
||||||
|
)
|
||||||
self.empty_overlay.setWordWrap(True)
|
self.empty_overlay.setWordWrap(True)
|
||||||
self.empty_overlay.setAttribute(Qt.WA_TransparentForMouseEvents, True)
|
self.empty_overlay.setAttribute(Qt.WA_TransparentForMouseEvents, True)
|
||||||
self.empty_overlay.hide()
|
self.empty_overlay.hide()
|
||||||
@@ -208,13 +221,14 @@ class QueueManager(QDialog):
|
|||||||
file_name = item.file_name
|
file_name = item.file_name
|
||||||
display_name = file_name
|
display_name = file_name
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if os.path.sep in file_name:
|
if os.path.sep in file_name:
|
||||||
display_name = os.path.basename(file_name)
|
display_name = os.path.basename(file_name)
|
||||||
# Get icon for the file
|
# Get icon for the file
|
||||||
icon = icon_provider.icon(QFileInfo(file_name))
|
icon = icon_provider.icon(QFileInfo(file_name))
|
||||||
list_item = QListWidgetItem()
|
list_item = QListWidgetItem()
|
||||||
# Set tooltip with detailed info
|
# Set tooltip with detailed info
|
||||||
output_folder = getattr(item, 'output_folder', '')
|
output_folder = getattr(item, "output_folder", "")
|
||||||
tooltip = (
|
tooltip = (
|
||||||
f"<b>Path:</b> {file_name}<br>"
|
f"<b>Path:</b> {file_name}<br>"
|
||||||
f"<b>Language:</b> {getattr(item, 'lang_code', '')}<br>"
|
f"<b>Language:</b> {getattr(item, 'lang_code', '')}<br>"
|
||||||
@@ -222,7 +236,7 @@ class QueueManager(QDialog):
|
|||||||
f"<b>Voice:</b> {getattr(item, 'voice', '')}<br>"
|
f"<b>Voice:</b> {getattr(item, 'voice', '')}<br>"
|
||||||
f"<b>Save Option:</b> {getattr(item, 'save_option', '')}<br>"
|
f"<b>Save Option:</b> {getattr(item, 'save_option', '')}<br>"
|
||||||
)
|
)
|
||||||
if output_folder not in (None, '', 'None'):
|
if output_folder not in (None, "", "None"):
|
||||||
tooltip += f"<b>Output Folder:</b> {output_folder}<br>"
|
tooltip += f"<b>Output Folder:</b> {output_folder}<br>"
|
||||||
tooltip += (
|
tooltip += (
|
||||||
f"<b>Subtitle Mode:</b> {getattr(item, 'subtitle_mode', '')}<br>"
|
f"<b>Subtitle Mode:</b> {getattr(item, 'subtitle_mode', '')}<br>"
|
||||||
@@ -232,8 +246,9 @@ 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)
|
||||||
self.listwidget.addItem(list_item)
|
self.listwidget.addItem(list_item)
|
||||||
self.listwidget.setItemWidget(list_item, widget)
|
self.listwidget.setItemWidget(list_item, widget)
|
||||||
@@ -244,6 +259,7 @@ class QueueManager(QDialog):
|
|||||||
if not items:
|
if not items:
|
||||||
return
|
return
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
# Remove by index to ensure correct mapping
|
# Remove by index to ensure correct mapping
|
||||||
rows = sorted([self.listwidget.row(item) for item in items], reverse=True)
|
rows = sorted([self.listwidget.row(item) for item in items], reverse=True)
|
||||||
# Warn user if removing multiple files
|
# Warn user if removing multiple files
|
||||||
@@ -253,7 +269,7 @@ class QueueManager(QDialog):
|
|||||||
"Confirm Remove",
|
"Confirm Remove",
|
||||||
f"Are you sure you want to remove {len(rows)} selected items from the queue?",
|
f"Are you sure you want to remove {len(rows)} selected items from the queue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
QMessageBox.No
|
QMessageBox.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
@@ -265,19 +281,22 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
def clear_queue(self):
|
def clear_queue(self):
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
if len(self.queue) > 1:
|
if len(self.queue) > 1:
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Confirm Clear Queue",
|
"Confirm Clear Queue",
|
||||||
f"Are you sure you want to clear {len(self.queue)} items from the queue?",
|
f"Are you sure you want to clear {len(self.queue)} items from the queue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
QMessageBox.No
|
QMessageBox.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
self.queue.clear()
|
self.queue.clear()
|
||||||
self.listwidget.clear()
|
self.listwidget.clear()
|
||||||
self.empty_overlay.resize(self.listwidget.size()) # Ensure overlay is sized correctly
|
self.empty_overlay.resize(
|
||||||
|
self.listwidget.size()
|
||||||
|
) # Ensure overlay is sized correctly
|
||||||
self.empty_overlay.show() # Show the overlay when queue is empty
|
self.empty_overlay.show() # Show the overlay when queue is empty
|
||||||
self.update_button_states()
|
self.update_button_states()
|
||||||
|
|
||||||
@@ -290,56 +309,74 @@ class QueueManager(QDialog):
|
|||||||
parent = self.parent
|
parent = self.parent
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
# lang_code: use parent's get_voice_formula and get_selected_lang
|
# lang_code: use parent's get_voice_formula and get_selected_lang
|
||||||
if hasattr(parent, 'get_voice_formula') and hasattr(parent, 'get_selected_lang'):
|
if hasattr(parent, "get_voice_formula") and hasattr(
|
||||||
|
parent, "get_selected_lang"
|
||||||
|
):
|
||||||
voice_formula = parent.get_voice_formula()
|
voice_formula = parent.get_voice_formula()
|
||||||
attrs['lang_code'] = parent.get_selected_lang(voice_formula)
|
attrs["lang_code"] = parent.get_selected_lang(voice_formula)
|
||||||
attrs['voice'] = voice_formula
|
attrs["voice"] = voice_formula
|
||||||
else:
|
else:
|
||||||
attrs['lang_code'] = getattr(parent, 'selected_lang', '')
|
attrs["lang_code"] = getattr(parent, "selected_lang", "")
|
||||||
attrs['voice'] = getattr(parent, 'selected_voice', '')
|
attrs["voice"] = getattr(parent, "selected_voice", "")
|
||||||
# speed
|
# speed
|
||||||
if hasattr(parent, 'speed_slider'):
|
if hasattr(parent, "speed_slider"):
|
||||||
attrs['speed'] = parent.speed_slider.value() / 100.0
|
attrs["speed"] = parent.speed_slider.value() / 100.0
|
||||||
else:
|
else:
|
||||||
attrs['speed'] = getattr(parent, 'speed', 1.0)
|
attrs["speed"] = getattr(parent, "speed", 1.0)
|
||||||
# save_option
|
# save_option
|
||||||
attrs['save_option'] = getattr(parent, 'save_option', '')
|
attrs["save_option"] = getattr(parent, "save_option", "")
|
||||||
# output_folder
|
# output_folder
|
||||||
attrs['output_folder'] = getattr(parent, 'selected_output_folder', '')
|
attrs["output_folder"] = getattr(parent, "selected_output_folder", "")
|
||||||
# subtitle_mode
|
# subtitle_mode
|
||||||
if hasattr(parent, 'get_actual_subtitle_mode'):
|
if hasattr(parent, "get_actual_subtitle_mode"):
|
||||||
attrs['subtitle_mode'] = parent.get_actual_subtitle_mode()
|
attrs["subtitle_mode"] = parent.get_actual_subtitle_mode()
|
||||||
else:
|
else:
|
||||||
attrs['subtitle_mode'] = getattr(parent, 'subtitle_mode', '')
|
attrs["subtitle_mode"] = getattr(parent, "subtitle_mode", "")
|
||||||
# output_format
|
# output_format
|
||||||
attrs['output_format'] = getattr(parent, 'selected_format', '')
|
attrs["output_format"] = getattr(parent, "selected_format", "")
|
||||||
# total_char_count
|
# total_char_count
|
||||||
attrs['total_char_count'] = getattr(parent, 'char_count', '')
|
attrs["total_char_count"] = getattr(parent, "char_count", "")
|
||||||
# replace_single_newlines
|
# replace_single_newlines
|
||||||
attrs['replace_single_newlines'] = getattr(parent, 'replace_single_newlines', False)
|
attrs["replace_single_newlines"] = getattr(
|
||||||
|
parent, "replace_single_newlines", False
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# fallback: empty values
|
# fallback: empty values
|
||||||
attrs = {k: '' for k in [
|
attrs = {
|
||||||
'lang_code', 'speed', 'voice', 'save_option',
|
k: ""
|
||||||
'output_folder', 'subtitle_mode', 'output_format', 'total_char_count', 'replace_single_newlines']}
|
for k in [
|
||||||
|
"lang_code",
|
||||||
|
"speed",
|
||||||
|
"voice",
|
||||||
|
"save_option",
|
||||||
|
"output_folder",
|
||||||
|
"subtitle_mode",
|
||||||
|
"output_format",
|
||||||
|
"total_char_count",
|
||||||
|
"replace_single_newlines",
|
||||||
|
]
|
||||||
|
}
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def add_files_from_paths(self, file_paths):
|
def add_files_from_paths(self, file_paths):
|
||||||
from abogen.utils import calculate_text_length
|
from abogen.utils import calculate_text_length
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
import os
|
import os
|
||||||
|
|
||||||
current_attrs = self.get_current_attributes()
|
current_attrs = self.get_current_attributes()
|
||||||
duplicates = []
|
duplicates = []
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
|
|
||||||
class QueueItem:
|
class QueueItem:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
item = QueueItem()
|
item = QueueItem()
|
||||||
item.file_name = file_path
|
item.file_name = file_path
|
||||||
for attr, value in current_attrs.items():
|
for attr, value in current_attrs.items():
|
||||||
setattr(item, attr, value)
|
setattr(item, attr, value)
|
||||||
# Read file content and calculate total_char_count using calculate_text_length
|
# Read file content and calculate total_char_count using calculate_text_length
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||||
file_content = f.read()
|
file_content = f.read()
|
||||||
item.total_char_count = calculate_text_length(file_content)
|
item.total_char_count = calculate_text_length(file_content)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -348,16 +385,26 @@ class QueueManager(QDialog):
|
|||||||
is_duplicate = False
|
is_duplicate = False
|
||||||
for queued_item in self.queue:
|
for queued_item in self.queue:
|
||||||
if (
|
if (
|
||||||
getattr(queued_item, 'file_name', None) == getattr(item, 'file_name', None) and
|
getattr(queued_item, "file_name", None)
|
||||||
getattr(queued_item, 'lang_code', None) == getattr(item, 'lang_code', None) and
|
== getattr(item, "file_name", None)
|
||||||
getattr(queued_item, 'speed', None) == getattr(item, 'speed', None) and
|
and getattr(queued_item, "lang_code", None)
|
||||||
getattr(queued_item, 'voice', None) == getattr(item, 'voice', None) and
|
== getattr(item, "lang_code", None)
|
||||||
getattr(queued_item, 'save_option', None) == getattr(item, 'save_option', None) and
|
and getattr(queued_item, "speed", None)
|
||||||
getattr(queued_item, 'output_folder', None) == getattr(item, 'output_folder', None) and
|
== getattr(item, "speed", None)
|
||||||
getattr(queued_item, 'subtitle_mode', None) == getattr(item, 'subtitle_mode', None) and
|
and getattr(queued_item, "voice", None)
|
||||||
getattr(queued_item, 'output_format', None) == getattr(item, 'output_format', None) and
|
== getattr(item, "voice", None)
|
||||||
getattr(queued_item, 'total_char_count', None) == getattr(item, 'total_char_count', None) and
|
and getattr(queued_item, "save_option", None)
|
||||||
getattr(queued_item, 'replace_single_newlines', False) == getattr(item, 'replace_single_newlines', False)
|
== getattr(item, "save_option", None)
|
||||||
|
and getattr(queued_item, "output_folder", None)
|
||||||
|
== getattr(item, "output_folder", None)
|
||||||
|
and getattr(queued_item, "subtitle_mode", None)
|
||||||
|
== getattr(item, "subtitle_mode", None)
|
||||||
|
and getattr(queued_item, "output_format", None)
|
||||||
|
== getattr(item, "output_format", None)
|
||||||
|
and getattr(queued_item, "total_char_count", None)
|
||||||
|
== getattr(item, "total_char_count", None)
|
||||||
|
and getattr(queued_item, "replace_single_newlines", False)
|
||||||
|
== getattr(item, "replace_single_newlines", False)
|
||||||
):
|
):
|
||||||
is_duplicate = True
|
is_duplicate = True
|
||||||
break
|
break
|
||||||
@@ -369,7 +416,7 @@ class QueueManager(QDialog):
|
|||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Duplicate Item(s)",
|
"Duplicate Item(s)",
|
||||||
f"Skipping {len(duplicates)} file(s) with the same attributes, already in the queue."
|
f"Skipping {len(duplicates)} file(s) with the same attributes, already in the queue.",
|
||||||
)
|
)
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
self.update_button_states()
|
self.update_button_states()
|
||||||
@@ -377,20 +424,23 @@ class QueueManager(QDialog):
|
|||||||
def add_more_files(self):
|
def add_more_files(self):
|
||||||
from PyQt5.QtWidgets import QFileDialog
|
from PyQt5.QtWidgets import QFileDialog
|
||||||
from abogen.utils import calculate_text_length # import the function
|
from abogen.utils import calculate_text_length # import the function
|
||||||
|
|
||||||
# Only allow .txt files
|
# Only allow .txt files
|
||||||
files, _ = QFileDialog.getOpenFileNames(self, "Select .txt files", "", "Text Files (*.txt)")
|
files, _ = QFileDialog.getOpenFileNames(
|
||||||
|
self, "Select .txt files", "", "Text Files (*.txt)"
|
||||||
|
)
|
||||||
if not files:
|
if not files:
|
||||||
return
|
return
|
||||||
self.add_files_from_paths(files)
|
self.add_files_from_paths(files)
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
if hasattr(self, 'empty_overlay'):
|
if hasattr(self, "empty_overlay"):
|
||||||
self.empty_overlay.resize(self.listwidget.size())
|
self.empty_overlay.resize(self.listwidget.size())
|
||||||
|
|
||||||
def update_button_states(self):
|
def update_button_states(self):
|
||||||
# Enable Remove if at least one item is selected, else disable
|
# Enable Remove if at least one item is selected, else disable
|
||||||
if hasattr(self, 'remove_button'):
|
if hasattr(self, "remove_button"):
|
||||||
selected_count = len(self.listwidget.selectedItems())
|
selected_count = len(self.listwidget.selectedItems())
|
||||||
self.remove_button.setEnabled(selected_count > 0)
|
self.remove_button.setEnabled(selected_count > 0)
|
||||||
if selected_count > 1:
|
if selected_count > 1:
|
||||||
@@ -398,7 +448,7 @@ class QueueManager(QDialog):
|
|||||||
else:
|
else:
|
||||||
self.remove_button.setText("Remove selected")
|
self.remove_button.setText("Remove selected")
|
||||||
# Disable Clear if queue is empty
|
# Disable Clear if queue is empty
|
||||||
if hasattr(self, 'clear_button'):
|
if hasattr(self, "clear_button"):
|
||||||
self.clear_button.setEnabled(bool(self.queue))
|
self.clear_button.setEnabled(bool(self.queue))
|
||||||
|
|
||||||
def show_context_menu(self, pos):
|
def show_context_menu(self, pos):
|
||||||
@@ -406,6 +456,7 @@ class QueueManager(QDialog):
|
|||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt5.QtCore import QUrl
|
||||||
import os
|
import os
|
||||||
|
|
||||||
global_pos = self.listwidget.viewport().mapToGlobal(pos)
|
global_pos = self.listwidget.viewport().mapToGlobal(pos)
|
||||||
selected_items = self.listwidget.selectedItems()
|
selected_items = self.listwidget.selectedItems()
|
||||||
menu = QMenu(self)
|
menu = QMenu(self)
|
||||||
@@ -417,35 +468,45 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
# Add Open file action
|
# Add Open file action
|
||||||
open_file_action = QAction("Open file", self)
|
open_file_action = QAction("Open file", self)
|
||||||
|
|
||||||
def open_file():
|
def open_file():
|
||||||
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(self, "File Not Found", f"The file does not exist.")
|
QMessageBox.warning(
|
||||||
|
self, "File Not Found", f"The file does not exist."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
QDesktopServices.openUrl(QUrl.fromLocalFile(q.file_name))
|
QDesktopServices.openUrl(QUrl.fromLocalFile(q.file_name))
|
||||||
break
|
break
|
||||||
|
|
||||||
open_file_action.triggered.connect(open_file)
|
open_file_action.triggered.connect(open_file)
|
||||||
menu.addAction(open_file_action)
|
menu.addAction(open_file_action)
|
||||||
|
|
||||||
# Add Go to folder action
|
# Add Go to folder action
|
||||||
go_to_folder_action = QAction("Go to folder", self)
|
go_to_folder_action = QAction("Go to folder", self)
|
||||||
|
|
||||||
def go_to_folder():
|
def go_to_folder():
|
||||||
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(self, "File Not Found", f"The file does not exist.")
|
QMessageBox.warning(
|
||||||
|
self, "File Not Found", f"The file does not exist."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
folder = os.path.dirname(q.file_name)
|
folder = os.path.dirname(q.file_name)
|
||||||
if os.path.exists(folder):
|
if os.path.exists(folder):
|
||||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
|
QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
|
||||||
break
|
break
|
||||||
|
|
||||||
go_to_folder_action.triggered.connect(go_to_folder)
|
go_to_folder_action.triggered.connect(go_to_folder)
|
||||||
menu.addAction(go_to_folder_action)
|
menu.addAction(go_to_folder_action)
|
||||||
|
|
||||||
@@ -466,6 +527,7 @@ class QueueManager(QDialog):
|
|||||||
def reject(self):
|
def reject(self):
|
||||||
# Cancel: restore original queue
|
# Cancel: restore original queue
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
# Warn if user changed a lot (e.g., more than 1 items difference)
|
# Warn if user changed a lot (e.g., more than 1 items difference)
|
||||||
original_count = len(self._original_queue)
|
original_count = len(self._original_queue)
|
||||||
current_count = len(self.queue)
|
current_count = len(self.queue)
|
||||||
@@ -475,7 +537,7 @@ class QueueManager(QDialog):
|
|||||||
"Confirm Cancel",
|
"Confirm Cancel",
|
||||||
f"Are you sure you want to cancel and discard all changes?",
|
f"Are you sure you want to cancel and discard all changes?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
QMessageBox.No
|
QMessageBox.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
@@ -485,6 +547,7 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
if event.key() == Qt.Key_Delete:
|
if event.key() == Qt.Key_Delete:
|
||||||
self.remove_item()
|
self.remove_item()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# represents a queued item - book, chapters, voice, etc.
|
# represents a queued item - book, chapters, voice, etc.
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class QueuedItem:
|
class QueuedItem:
|
||||||
file_name: str
|
file_name: str
|
||||||
|
|||||||
+24
-12
@@ -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")
|
||||||
|
|
||||||
|
|
||||||
@@ -105,15 +103,17 @@ def clean_text(text, *args, **kwargs):
|
|||||||
|
|
||||||
default_encoding = sys.getfilesystemencoding()
|
default_encoding = sys.getfilesystemencoding()
|
||||||
|
|
||||||
|
|
||||||
def create_process(cmd, stdin=None, text=True, capture_output=False):
|
def create_process(cmd, stdin=None, text=True, capture_output=False):
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Configure root logger to output to console if not already configured
|
# Configure root logger to output to console if not already configured
|
||||||
root = logging.getLogger()
|
root = logging.getLogger()
|
||||||
if not root.handlers:
|
if not root.handlers:
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
formatter = logging.Formatter('%(message)s')
|
formatter = logging.Formatter("%(message)s")
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
root.addHandler(handler)
|
root.addHandler(handler)
|
||||||
root.setLevel(logging.INFO)
|
root.setLevel(logging.INFO)
|
||||||
@@ -156,6 +156,7 @@ def create_process(cmd, stdin=None, text=True, capture_output=False):
|
|||||||
|
|
||||||
# Stream output to console in real-time if not capturing
|
# Stream output to console in real-time if not capturing
|
||||||
if proc.stdout and not capture_output:
|
if proc.stdout and not capture_output:
|
||||||
|
|
||||||
def _stream_output(stream):
|
def _stream_output(stream):
|
||||||
if text:
|
if text:
|
||||||
# For text mode, read character by character for real-time output
|
# For text mode, read character by character for real-time output
|
||||||
@@ -174,7 +175,9 @@ def create_process(cmd, stdin=None, text=True, capture_output=False):
|
|||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
# Try to decode binary data for display
|
# Try to decode binary data for display
|
||||||
sys.stdout.write(chunk.decode(default_encoding, errors='replace'))
|
sys.stdout.write(
|
||||||
|
chunk.decode(default_encoding, errors="replace")
|
||||||
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -220,6 +223,7 @@ def get_gpu_acceleration(enabled):
|
|||||||
try:
|
try:
|
||||||
import torch
|
import torch
|
||||||
from torch.cuda import is_available
|
from torch.cuda import is_available
|
||||||
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
return "CUDA GPU available but using CPU.", False
|
return "CUDA GPU available but using CPU.", False
|
||||||
if is_available():
|
if is_available():
|
||||||
@@ -227,7 +231,11 @@ def get_gpu_acceleration(enabled):
|
|||||||
# Gather more diagnostic info if CUDA is not available
|
# Gather more diagnostic info if CUDA is not available
|
||||||
try:
|
try:
|
||||||
cuda_devices = torch.cuda.device_count()
|
cuda_devices = torch.cuda.device_count()
|
||||||
cuda_error = torch.cuda.get_device_name(0) if cuda_devices > 0 else "No devices found"
|
cuda_error = (
|
||||||
|
torch.cuda.get_device_name(0)
|
||||||
|
if cuda_devices > 0
|
||||||
|
else "No devices found"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
cuda_error = str(e)
|
cuda_error = str(e)
|
||||||
return f"CUDA GPU is not available. Using CPU. ({cuda_error})", False
|
return f"CUDA GPU is not available. Using CPU. ({cuda_error})", False
|
||||||
@@ -239,6 +247,7 @@ def prevent_sleep_start():
|
|||||||
system = platform.system()
|
system = platform.system()
|
||||||
if system == "Windows":
|
if system == "Windows":
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
ctypes.windll.kernel32.SetThreadExecutionState(
|
ctypes.windll.kernel32.SetThreadExecutionState(
|
||||||
0x80000000 | 0x00000001 | 0x00000040
|
0x80000000 | 0x00000001 | 0x00000040
|
||||||
)
|
)
|
||||||
@@ -246,19 +255,22 @@ def prevent_sleep_start():
|
|||||||
_sleep_procs["Darwin"] = create_process(["caffeinate"])
|
_sleep_procs["Darwin"] = create_process(["caffeinate"])
|
||||||
elif system == "Linux":
|
elif system == "Linux":
|
||||||
# use a sleep that never exits so inhibition stays active
|
# use a sleep that never exits so inhibition stays active
|
||||||
_sleep_procs["Linux"] = create_process([
|
_sleep_procs["Linux"] = create_process(
|
||||||
"systemd-inhibit",
|
[
|
||||||
"--what=handle-lid-switch:sleep",
|
"systemd-inhibit",
|
||||||
"--mode=block",
|
"--what=handle-lid-switch:sleep",
|
||||||
"sleep",
|
"--mode=block",
|
||||||
"infinity",
|
"sleep",
|
||||||
])
|
"infinity",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prevent_sleep_end():
|
def prevent_sleep_end():
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
if system == "Windows":
|
if system == "Windows":
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000) # ES_CONTINUOUS
|
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000) # ES_CONTINUOUS
|
||||||
elif system in ("Darwin", "Linux") and _sleep_procs[system]:
|
elif system in ("Darwin", "Linux") and _sleep_procs[system]:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -230,13 +230,15 @@ class VoiceMixer(QWidget):
|
|||||||
appstyle = QApplication.instance().style().objectName().lower()
|
appstyle = QApplication.instance().style().objectName().lower()
|
||||||
if appstyle != "windowsvista":
|
if appstyle != "windowsvista":
|
||||||
# Set custom groove color for disabled state using COLORS["GREY_BACKGROUND"]
|
# Set custom groove color for disabled state using COLORS["GREY_BACKGROUND"]
|
||||||
self.slider.setStyleSheet(f"""
|
self.slider.setStyleSheet(
|
||||||
|
f"""
|
||||||
QSlider::groove:vertical:disabled {{
|
QSlider::groove:vertical:disabled {{
|
||||||
background: {COLORS.get("GREY_BACKGROUND")};
|
background: {COLORS.get("GREY_BACKGROUND")};
|
||||||
width: 4px;
|
width: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}}
|
}}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Connect controls
|
# Connect controls
|
||||||
self.slider.valueChanged.connect(lambda val: self.spin_box.setValue(val / 100))
|
self.slider.valueChanged.connect(lambda val: self.spin_box.setValue(val / 100))
|
||||||
@@ -330,7 +332,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self._original_mixed_voice_state = None
|
self._original_mixed_voice_state = None
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
self._original_profile_name = getattr(parent, "selected_profile_name", None)
|
self._original_profile_name = getattr(parent, "selected_profile_name", None)
|
||||||
self._original_mixed_voice_state = getattr(parent, "mixed_voice_state", None)
|
self._original_mixed_voice_state = getattr(
|
||||||
|
parent, "mixed_voice_state", None
|
||||||
|
)
|
||||||
profiles = load_profiles()
|
profiles = load_profiles()
|
||||||
self._virtual_new_profile = False
|
self._virtual_new_profile = False
|
||||||
if not profiles:
|
if not profiles:
|
||||||
@@ -369,7 +373,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self.profile_list = QListWidget()
|
self.profile_list = QListWidget()
|
||||||
self.profile_list.setSelectionMode(QListWidget.SingleSelection)
|
self.profile_list.setSelectionMode(QListWidget.SingleSelection)
|
||||||
self.profile_list.setSelectionBehavior(QListWidget.SelectRows)
|
self.profile_list.setSelectionBehavior(QListWidget.SelectRows)
|
||||||
self.profile_list.setStyleSheet("QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }")
|
self.profile_list.setStyleSheet(
|
||||||
|
"QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }"
|
||||||
|
)
|
||||||
icon = QIcon(get_resource_path("abogen.assets", "profile.png"))
|
icon = QIcon(get_resource_path("abogen.assets", "profile.png"))
|
||||||
if self._virtual_new_profile:
|
if self._virtual_new_profile:
|
||||||
item = QListWidgetItem(icon, "New profile")
|
item = QListWidgetItem(icon, "New profile")
|
||||||
@@ -976,6 +982,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
def _parse_rgba_to_qcolor(self, rgba_str):
|
def _parse_rgba_to_qcolor(self, rgba_str):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt5.QtGui import QColor
|
||||||
|
|
||||||
"""Helper to convert 'rgba(R,G,B,A_float)' string to QColor."""
|
"""Helper to convert 'rgba(R,G,B,A_float)' string to QColor."""
|
||||||
match = re.match(r"rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)", rgba_str)
|
match = re.match(r"rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)", rgba_str)
|
||||||
if match:
|
if match:
|
||||||
@@ -1372,6 +1379,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
|
|
||||||
def update_profile_list_colors(self):
|
def update_profile_list_colors(self):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
profiles = load_profiles()
|
profiles = load_profiles()
|
||||||
for i in range(self.profile_list.count()):
|
for i in range(self.profile_list.count()):
|
||||||
item = self.profile_list.item(i)
|
item = self.profile_list.item(i)
|
||||||
@@ -1383,7 +1391,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
||||||
item.setData(Qt.BackgroundRole, color)
|
item.setData(Qt.BackgroundRole, color)
|
||||||
else:
|
else:
|
||||||
item.setData(Qt.BackgroundRole, self.profile_list.palette().base().color())
|
item.setData(
|
||||||
|
Qt.BackgroundRole, self.profile_list.palette().base().color()
|
||||||
|
)
|
||||||
weights = profiles.get(name, {}).get("voices", [])
|
weights = profiles.get(name, {}).get("voices", [])
|
||||||
total = 0
|
total = 0
|
||||||
if isinstance(weights, list):
|
if isinstance(weights, list):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
from abogen.constants import VOICES_INTERNAL
|
from abogen.constants import VOICES_INTERNAL
|
||||||
|
|
||||||
|
|
||||||
# Calls parsing and loads the voice to gpu or cpu
|
# Calls parsing and loads the voice to gpu or cpu
|
||||||
def get_new_voice(pipeline, formula, use_gpu):
|
def get_new_voice(pipeline, formula, use_gpu):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user