mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
TTS config on linux works
This commit is contained in:
parent
27352f0813
commit
962e68ed19
@ -19,8 +19,9 @@ class Client:
|
|||||||
def escape_marked_text(cls, text):
|
def escape_marked_text(cls, text):
|
||||||
return prepare_string_for_xml(text)
|
return prepare_string_for_xml(text)
|
||||||
|
|
||||||
def __init__(self, dispatch_on_main_thread=lambda f: f()):
|
def __init__(self, settings, dispatch_on_main_thread=lambda f: f()):
|
||||||
self.status = {'synthesizing': False, 'paused': False}
|
self.status = {'synthesizing': False, 'paused': False}
|
||||||
|
self.settings = settings
|
||||||
self.dispatch_on_main_thread = dispatch_on_main_thread
|
self.dispatch_on_main_thread = dispatch_on_main_thread
|
||||||
self.current_marked_text = None
|
self.current_marked_text = None
|
||||||
self.last_mark = None
|
self.last_mark = None
|
||||||
@ -59,12 +60,22 @@ class Client:
|
|||||||
self.set_use_ssml(use_ssml)
|
self.set_use_ssml(use_ssml)
|
||||||
|
|
||||||
def apply_settings(self, new_settings=None):
|
def apply_settings(self, new_settings=None):
|
||||||
|
if new_settings is not None:
|
||||||
|
self.settings = new_settings
|
||||||
if self.settings_applied:
|
if self.settings_applied:
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
self.settings_applied = False
|
self.settings_applied = False
|
||||||
self.ensure_state()
|
self.ensure_state()
|
||||||
self.settings_applied = True
|
self.settings_applied = True
|
||||||
# TODO: Implement this
|
om = self.settings.get('output_module')
|
||||||
|
if om:
|
||||||
|
self.ssip_client.set_output_module(om)
|
||||||
|
voice = self.settings.get('voice')
|
||||||
|
if voice:
|
||||||
|
self.ssip_client.set_synthesis_voice(voice[0])
|
||||||
|
rate = self.settings.get('rate')
|
||||||
|
if rate:
|
||||||
|
self.ssip_client.set_rate(rate)
|
||||||
|
|
||||||
def set_use_ssml(self, on):
|
def set_use_ssml(self, on):
|
||||||
from speechd.client import DataMode, SSIPCommunicationError
|
from speechd.client import DataMode, SSIPCommunicationError
|
||||||
@ -148,6 +159,7 @@ class Client:
|
|||||||
text = self.current_marked_text
|
text = self.current_marked_text
|
||||||
else:
|
else:
|
||||||
text = self.current_marked_text[idx:]
|
text = self.current_marked_text[idx:]
|
||||||
|
self.ensure_state(use_ssml=True)
|
||||||
self.ssip_client.speak(text, callback=self.current_callback)
|
self.ssip_client.speak(text, callback=self.current_callback)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QAbstractItemView, QAbstractTableModel, QComboBox, QFontMetrics, QFormLayout, Qt,
|
QAbstractItemView, QAbstractTableModel, QComboBox, QFontMetrics, QFormLayout,
|
||||||
QTableView, QWidget, QSortFilterProxyModel, QItemSelectionModel
|
QItemSelectionModel, QSortFilterProxyModel, QSlider, Qt, QTableView, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre.gui2.preferences.look_feel import BusyCursor
|
from calibre.gui2.preferences.look_feel import BusyCursor
|
||||||
@ -57,14 +57,30 @@ class VoicesModel(QAbstractTableModel):
|
|||||||
finally:
|
finally:
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
|
|
||||||
|
def index_for_voice(self, v):
|
||||||
|
r = 0
|
||||||
|
if v != self.system_default_voice:
|
||||||
|
try:
|
||||||
|
idx = self.current_voices.index(v)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
r = idx + 1
|
||||||
|
return self.index(r, 0)
|
||||||
|
|
||||||
|
|
||||||
class Widget(QWidget):
|
class Widget(QWidget):
|
||||||
|
|
||||||
def __init__(self, tts_client, initial_backend_settings, parent=None):
|
def __init__(self, tts_client, initial_backend_settings=None, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.l = l = QFormLayout(self)
|
self.l = l = QFormLayout(self)
|
||||||
self.tts_client = tts_client
|
self.tts_client = tts_client
|
||||||
|
|
||||||
|
self.speed = s = QSlider(Qt.Orientation.Horizontal, self)
|
||||||
|
s.setMinimumWidth(200)
|
||||||
|
l.addRow(_('&Speed of speech:'), s)
|
||||||
|
s.setRange(-100, 100)
|
||||||
|
s.setSingleStep(5)
|
||||||
|
|
||||||
self.output_modules = om = QComboBox(self)
|
self.output_modules = om = QComboBox(self)
|
||||||
with BusyCursor():
|
with BusyCursor():
|
||||||
self.voice_data = self.tts_client.get_voice_data()
|
self.voice_data = self.tts_client.get_voice_data()
|
||||||
@ -72,7 +88,7 @@ class Widget(QWidget):
|
|||||||
om.addItem(_('System default'), self.system_default_output_module)
|
om.addItem(_('System default'), self.system_default_output_module)
|
||||||
for x in self.voice_data:
|
for x in self.voice_data:
|
||||||
om.addItem(x, x)
|
om.addItem(x, x)
|
||||||
l.addRow(_('Speech synthesizer:'), om)
|
l.addRow(_('Speech s&ynthesizer:'), om)
|
||||||
|
|
||||||
self.voices = v = QTableView(self)
|
self.voices = v = QTableView(self)
|
||||||
self.voices_model = VoicesModel(self.voice_data, self.system_default_output_module, parent=v)
|
self.voices_model = VoicesModel(self.voice_data, self.system_default_output_module, parent=v)
|
||||||
@ -88,8 +104,12 @@ class Widget(QWidget):
|
|||||||
v.sortByColumn(0, Qt.SortOrder.AscendingOrder)
|
v.sortByColumn(0, Qt.SortOrder.AscendingOrder)
|
||||||
om.currentIndexChanged.connect(self.output_module_changed)
|
om.currentIndexChanged.connect(self.output_module_changed)
|
||||||
l.addRow(v)
|
l.addRow(v)
|
||||||
|
|
||||||
self.backend_settings = initial_backend_settings or {}
|
self.backend_settings = initial_backend_settings or {}
|
||||||
|
|
||||||
|
def restore_to_defaults(self):
|
||||||
|
self.backend_settings = {}
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
ans = super().sizeHint()
|
ans = super().sizeHint()
|
||||||
ans.setHeight(max(ans.height(), 600))
|
ans.setHeight(max(ans.height(), 600))
|
||||||
@ -127,6 +147,15 @@ class Widget(QWidget):
|
|||||||
om = self.selected_output_module
|
om = self.selected_output_module
|
||||||
self.voices_model.change_output_module(om)
|
self.voices_model.change_output_module(om)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rate(self):
|
||||||
|
return self.speed.value()
|
||||||
|
|
||||||
|
@rate.setter
|
||||||
|
def rate(self, val):
|
||||||
|
val = int(val or 0)
|
||||||
|
self.speed.setValue(val)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def backend_settings(self):
|
def backend_settings(self):
|
||||||
ans = {}
|
ans = {}
|
||||||
@ -136,6 +165,9 @@ class Widget(QWidget):
|
|||||||
voice = self.selected_voice
|
voice = self.selected_voice
|
||||||
if voice != VoicesModel.system_default_voice:
|
if voice != VoicesModel.system_default_voice:
|
||||||
ans['voice'] = voice
|
ans['voice'] = voice
|
||||||
|
rate = self.rate
|
||||||
|
if rate:
|
||||||
|
ans['rate'] = rate
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@backend_settings.setter
|
@backend_settings.setter
|
||||||
@ -144,6 +176,7 @@ class Widget(QWidget):
|
|||||||
self.selected_output_module = om
|
self.selected_output_module = om
|
||||||
voice = val.get('voice') or VoicesModel.system_default_voice
|
voice = val.get('voice') or VoicesModel.system_default_voice
|
||||||
self.selected_voice = voice
|
self.selected_voice = voice
|
||||||
|
self.rate = val.get('rate') or 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@ -154,3 +187,4 @@ if __name__ == '__main__':
|
|||||||
w = Widget(c, {})
|
w = Widget(c, {})
|
||||||
w.show()
|
w.show()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
print(w.backend_settings)
|
||||||
|
@ -14,7 +14,7 @@ class Client:
|
|||||||
def escape_marked_text(cls, text):
|
def escape_marked_text(cls, text):
|
||||||
return text.replace('[[', ' [ [ ').replace(']]', ' ] ] ')
|
return text.replace('[[', ' [ [ ').replace(']]', ' ] ] ')
|
||||||
|
|
||||||
def __init__(self, dispatch_on_main_thread):
|
def __init__(self, settings, dispatch_on_main_thread):
|
||||||
from calibre_extensions.cocoa import NSSpeechSynthesizer
|
from calibre_extensions.cocoa import NSSpeechSynthesizer
|
||||||
self.nsss = NSSpeechSynthesizer(self.handle_message)
|
self.nsss = NSSpeechSynthesizer(self.handle_message)
|
||||||
self.current_callback = None
|
self.current_callback = None
|
||||||
|
@ -19,7 +19,7 @@ class Client:
|
|||||||
def escape_marked_text(cls, text):
|
def escape_marked_text(cls, text):
|
||||||
return prepare_string_for_xml(text)
|
return prepare_string_for_xml(text)
|
||||||
|
|
||||||
def __init__(self, dispatch_on_main_thread):
|
def __init__(self, settings, dispatch_on_main_thread):
|
||||||
from calibre.utils.windows.winsapi import ISpVoice
|
from calibre.utils.windows.winsapi import ISpVoice
|
||||||
self.sp_voice = ISpVoice()
|
self.sp_voice = ISpVoice()
|
||||||
self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True)
|
self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from PyQt5.Qt import QObject, QVBoxLayout, pyqtSignal
|
from PyQt5.Qt import QDialogButtonBox, QObject, QVBoxLayout, pyqtSignal
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.viewer.config import get_pref_group, vprefs
|
from calibre.gui2.viewer.config import get_pref_group, vprefs
|
||||||
@ -22,6 +22,12 @@ class Config(Dialog):
|
|||||||
self.config_widget = self.tts_client.config_widget(self.backend_settings, self)
|
self.config_widget = self.tts_client.config_widget(self.backend_settings, self)
|
||||||
l.addWidget(self.config_widget)
|
l.addWidget(self.config_widget)
|
||||||
l.addWidget(self.bb)
|
l.addWidget(self.bb)
|
||||||
|
self.config_widget.restore_to_defaults
|
||||||
|
b = self.bb.addButton(QDialogButtonBox.StandardButton.RestoreDefaults)
|
||||||
|
b.clicked.connect(self.restore_to_defaults)
|
||||||
|
|
||||||
|
def restore_to_defaults(self):
|
||||||
|
self.config_widget.restore_to_defaults()
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.backend_settings = self.config_widget.backend_settings
|
self.backend_settings = self.config_widget.backend_settings
|
||||||
@ -62,7 +68,7 @@ class TTS(QObject):
|
|||||||
def tts_client(self):
|
def tts_client(self):
|
||||||
if self._tts_client is None:
|
if self._tts_client is None:
|
||||||
from calibre.gui2.tts.implementation import Client
|
from calibre.gui2.tts.implementation import Client
|
||||||
self._tts_client = Client(self.dispatch_on_main_thread_signal.emit)
|
self._tts_client = Client(self.backend_settings, self.dispatch_on_main_thread_signal.emit)
|
||||||
return self._tts_client
|
return self._tts_client
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
@ -103,10 +109,23 @@ class TTS(QObject):
|
|||||||
def stop(self, data):
|
def stop(self, data):
|
||||||
self.tts_client.stop()
|
self.tts_client.stop()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def backend_settings(self):
|
||||||
|
from calibre.gui2.tts.implementation import Client
|
||||||
|
key = 'tts_' + Client.name
|
||||||
|
return vprefs.get(key) or {}
|
||||||
|
|
||||||
|
@backend_settings.setter
|
||||||
|
def backend_settings(self, val):
|
||||||
|
from calibre.gui2.tts.implementation import Client
|
||||||
|
key = 'tts_' + Client.name
|
||||||
|
val = val or {}
|
||||||
|
vprefs.set(key, val)
|
||||||
|
self.tts_client.apply_settings(val)
|
||||||
|
|
||||||
def configure(self, data):
|
def configure(self, data):
|
||||||
ui_settings = get_pref_group('tts').copy()
|
ui_settings = get_pref_group('tts').copy()
|
||||||
key = 'tts_' + self.tts_client.name
|
d = Config(self.tts_client, ui_settings, self.backend_settings, parent=self.parent())
|
||||||
d = Config(self.tts_client, ui_settings, vprefs.get(key) or {}, parent=self.parent())
|
|
||||||
if d.exec_() == d.DialogCode.Accepted:
|
if d.exec_() == d.DialogCode.Accepted:
|
||||||
vprefs[key] = d.backend_settings
|
self.backend_settings = d.backend_settings
|
||||||
self.settings_changed.emit(d.ui_settings)
|
self.settings_changed.emit(d.ui_settings)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user