mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Added icons, imrpoved voice mixer gui and documentation
This commit is contained in:
@@ -163,6 +163,7 @@ Feel free to explore the code and make any changes you like.
|
|||||||
- Abogen uses [Kokoro](https://github.com/hexgrad/kokoro) for its high-quality, natural-sounding text-to-speech synthesis. Huge thanks to the Kokoro team for making this possible.
|
- Abogen uses [Kokoro](https://github.com/hexgrad/kokoro) for its high-quality, natural-sounding text-to-speech synthesis. Huge thanks to the Kokoro team for making this possible.
|
||||||
- Thanks to [@wojiushixiaobai](https://github.com/wojiushixiaobai) for [Embedded Python](https://github.com/wojiushixiaobai/Python-Embed-Win64) packages. These modified packages include pip pre-installed, enabling Abogen to function as a standalone application without requiring users to separately install Python in Windows.
|
- Thanks to [@wojiushixiaobai](https://github.com/wojiushixiaobai) for [Embedded Python](https://github.com/wojiushixiaobai/Python-Embed-Win64) packages. These modified packages include pip pre-installed, enabling Abogen to function as a standalone application without requiring users to separately install Python in Windows.
|
||||||
- Special thanks to the [PyQt](https://www.riverbankcomputing.com/software/pyqt/) team for providing the cross-platform GUI toolkit that powers Abogen's interface.
|
- Special thanks to the [PyQt](https://www.riverbankcomputing.com/software/pyqt/) team for providing the cross-platform GUI toolkit that powers Abogen's interface.
|
||||||
|
- [Female](https://icons8.com/icon/uI49hxbpxTkp/female), [Male](https://icons8.com/icon/12351/male) and [Mix](https://icons8.com/icon/21700/adjust) icons by [Icons8](https://icons8.com/).
|
||||||
|
|
||||||
## `License`
|
## `License`
|
||||||
This project is available under the MIT License - see the [LICENSE](https://github.com/denizsafak/abogen/blob/main/LICENSE) file for details.
|
This project is available under the MIT License - see the [LICENSE](https://github.com/denizsafak/abogen/blob/main/LICENSE) file for details.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 340 B |
Binary file not shown.
|
After Width: | Height: | Size: 305 B |
Binary file not shown.
|
After Width: | Height: | Size: 252 B |
+6
-1
@@ -574,7 +574,8 @@ class abogen(QWidget):
|
|||||||
|
|
||||||
# Voice formula button
|
# Voice formula button
|
||||||
self.btn_voice_formula_mixer = QPushButton(self)
|
self.btn_voice_formula_mixer = QPushButton(self)
|
||||||
self.btn_voice_formula_mixer.setText("🛠") # TODO add voice formula icon
|
mixer_icon_path = get_resource_path("abogen.assets", "voice_mixer.png")
|
||||||
|
self.btn_voice_formula_mixer.setIcon(QIcon(mixer_icon_path))
|
||||||
self.btn_voice_formula_mixer.setToolTip("Mix and match voices")
|
self.btn_voice_formula_mixer.setToolTip("Mix and match voices")
|
||||||
self.btn_voice_formula_mixer.setFixedSize(40, 36)
|
self.btn_voice_formula_mixer.setFixedSize(40, 36)
|
||||||
self.btn_voice_formula_mixer.setStyleSheet("QPushButton { padding: 6px 12px; }")
|
self.btn_voice_formula_mixer.setStyleSheet("QPushButton { padding: 6px 12px; }")
|
||||||
@@ -1318,6 +1319,7 @@ class abogen(QWidget):
|
|||||||
self.btn_preview.setEnabled(False)
|
self.btn_preview.setEnabled(False)
|
||||||
self.btn_preview.setToolTip("Loading...")
|
self.btn_preview.setToolTip("Loading...")
|
||||||
self.voice_combo.setEnabled(False)
|
self.voice_combo.setEnabled(False)
|
||||||
|
self.btn_voice_formula_mixer.setEnabled(False) # Disable mixer button
|
||||||
self.btn_start.setEnabled(False) # Disable start button during preview
|
self.btn_start.setEnabled(False) # Disable start button during preview
|
||||||
# start loading animation
|
# start loading animation
|
||||||
self.loading_movie.start()
|
self.loading_movie.start()
|
||||||
@@ -1339,6 +1341,7 @@ class abogen(QWidget):
|
|||||||
self.btn_preview.setEnabled(True)
|
self.btn_preview.setEnabled(True)
|
||||||
self.btn_preview.setToolTip("Preview selected voice")
|
self.btn_preview.setToolTip("Preview selected voice")
|
||||||
self.voice_combo.setEnabled(True)
|
self.voice_combo.setEnabled(True)
|
||||||
|
self.btn_voice_formula_mixer.setEnabled(True) # Re-enable mixer button
|
||||||
self.btn_start.setEnabled(True) # Re-enable start button on error
|
self.btn_start.setEnabled(True) # Re-enable start button on error
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1365,6 +1368,7 @@ class abogen(QWidget):
|
|||||||
self.btn_preview.setEnabled(True)
|
self.btn_preview.setEnabled(True)
|
||||||
self.btn_preview.setToolTip("Preview selected voice")
|
self.btn_preview.setToolTip("Preview selected voice")
|
||||||
self.voice_combo.setEnabled(True)
|
self.voice_combo.setEnabled(True)
|
||||||
|
self.btn_voice_formula_mixer.setEnabled(True) # Re-enable mixer button
|
||||||
self.btn_start.setEnabled(True)
|
self.btn_start.setEnabled(True)
|
||||||
return
|
return
|
||||||
# stop loading animation, switch to stop icon
|
# stop loading animation, switch to stop icon
|
||||||
@@ -1414,6 +1418,7 @@ class abogen(QWidget):
|
|||||||
self.btn_preview.setToolTip("Preview selected voice")
|
self.btn_preview.setToolTip("Preview selected voice")
|
||||||
self.btn_preview.setEnabled(True)
|
self.btn_preview.setEnabled(True)
|
||||||
self.voice_combo.setEnabled(True)
|
self.voice_combo.setEnabled(True)
|
||||||
|
self.btn_voice_formula_mixer.setEnabled(True) # Re-enable mixer button
|
||||||
self.btn_start.setEnabled(True)
|
self.btn_start.setEnabled(True)
|
||||||
|
|
||||||
def _preview_error(self, msg):
|
def _preview_error(self, msg):
|
||||||
|
|||||||
+25
-16
@@ -16,10 +16,12 @@ from PyQt5.QtWidgets import (
|
|||||||
QStyle,
|
QStyle,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QSize
|
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QSize
|
||||||
|
from PyQt5.QtGui import QPixmap
|
||||||
from constants import VOICES_INTERNAL, FLAGS
|
from constants import VOICES_INTERNAL, FLAGS
|
||||||
|
from utils import get_resource_path
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
VOICE_MIXER_WIDTH = 160
|
VOICE_MIXER_WIDTH = 120
|
||||||
SLIDER_WIDTH = 32
|
SLIDER_WIDTH = 32
|
||||||
MIN_WINDOW_WIDTH = 600
|
MIN_WINDOW_WIDTH = 600
|
||||||
MIN_WINDOW_HEIGHT = 400
|
MIN_WINDOW_HEIGHT = 400
|
||||||
@@ -119,6 +121,7 @@ class VoiceMixer(QWidget):
|
|||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.voice_name = voice_name
|
self.voice_name = voice_name
|
||||||
|
#self.setFixedWidth(VOICE_MIXER_WIDTH)
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
|
||||||
# TODO Set CSS for rounded corners
|
# TODO Set CSS for rounded corners
|
||||||
@@ -133,17 +136,18 @@ class VoiceMixer(QWidget):
|
|||||||
self.checkbox.stateChanged.connect(self.toggle_inputs)
|
self.checkbox.stateChanged.connect(self.toggle_inputs)
|
||||||
layout.addWidget(self.checkbox, alignment=Qt.AlignCenter)
|
layout.addWidget(self.checkbox, alignment=Qt.AlignCenter)
|
||||||
|
|
||||||
# Voice name label
|
# Voice name label with gender icon
|
||||||
voice_gender = (
|
is_female = self.voice_name in VOICES_INTERNAL and self.voice_name[1] == "f"
|
||||||
FEMALE
|
gender_icon_path = get_resource_path("abogen.assets", "female.png" if is_female else "male.png")
|
||||||
if self.voice_name in VOICES_INTERNAL and self.voice_name[1] == "f"
|
|
||||||
else MALE
|
|
||||||
)
|
|
||||||
name = voice_name[3:].capitalize()
|
name = voice_name[3:].capitalize()
|
||||||
name_layout = QHBoxLayout()
|
name_layout = QHBoxLayout()
|
||||||
name_layout.addWidget(
|
# Gender icon
|
||||||
QLabel(f"{language_icon} {voice_gender} {name}"), alignment=Qt.AlignCenter
|
pixmap = QPixmap(gender_icon_path)
|
||||||
)
|
if not pixmap.isNull():
|
||||||
|
gender_label = QLabel()
|
||||||
|
gender_label.setPixmap(pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||||
|
name_layout.addWidget(gender_label)
|
||||||
|
name_layout.addWidget(QLabel(f"{language_icon} {name}"), alignment=Qt.AlignCenter)
|
||||||
layout.addLayout(name_layout)
|
layout.addLayout(name_layout)
|
||||||
|
|
||||||
# Spinbox and slider
|
# Spinbox and slider
|
||||||
@@ -211,26 +215,30 @@ class HoverLabel(QLabel):
|
|||||||
self.delete_button.setStyleSheet(
|
self.delete_button.setStyleSheet(
|
||||||
"""
|
"""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: red;
|
background-color: #ff5555;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 8px;
|
border-radius: 7px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
text-align: center;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
QPushButton:hover {
|
QPushButton:hover {
|
||||||
background-color: #ff5555;
|
background-color: red;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
# Make sure the entire button is clickable, not just the text
|
||||||
|
self.delete_button.setFocusPolicy(Qt.NoFocus)
|
||||||
|
self.delete_button.setAttribute(Qt.WA_TransparentForMouseEvents, False)
|
||||||
self.delete_button.setCursor(Qt.PointingHandCursor)
|
self.delete_button.setCursor(Qt.PointingHandCursor)
|
||||||
self.delete_button.hide()
|
self.delete_button.hide()
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
self.delete_button.move(self.width() - 16, 0)
|
# Position the button in the top-right corner with a small margin
|
||||||
|
self.delete_button.move(self.width() - 16, + 0)
|
||||||
|
|
||||||
def enterEvent(self, event):
|
def enterEvent(self, event):
|
||||||
self.delete_button.show()
|
self.delete_button.show()
|
||||||
@@ -398,7 +406,8 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
# Add voice labels
|
# Add voice labels
|
||||||
for name, weight in selected:
|
for name, weight in selected:
|
||||||
percentage = weight / total * 100
|
percentage = weight / total * 100
|
||||||
voice_label = HoverLabel(f"{name}: {percentage:.1f}%", name)
|
# Make the voice name bold and include percentage
|
||||||
|
voice_label = HoverLabel(f"<b>{name}: {percentage:.1f}%</b>", name)
|
||||||
voice_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
voice_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
||||||
voice_label.delete_button.clicked.connect(
|
voice_label.delete_button.clicked.connect(
|
||||||
lambda _, vn=name: self.disable_voice_by_name(vn)
|
lambda _, vn=name: self.disable_voice_by_name(vn)
|
||||||
|
|||||||
Reference in New Issue
Block a user