mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add slower and faster buttons to the TTS bar
This commit is contained in:
parent
fbde6307c3
commit
52e855b130
3
imgsrc/srv/faster.svg
Normal file
3
imgsrc/srv/faster.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 576 512">
|
||||||
|
<path d="M135.52 412.67a15.99 15.99 0 0 0-7.52 13.57v37.74c0 12.57 13.82 20.23 24.48 13.57l66.69-39.87-47.85-47.85-35.8 22.84zm416.76-195.42l-56.42-34.62c-.06-13.95-2.28-30.77-7.07-48.67-11.32-42.24-31.91-73.43-45.99-69.66-14.08 3.77-16.32 41.08-5 83.32.65 2.44 1.44 4.7 2.15 7.06-4.89-6.08-10.23-12.23-16.31-18.32-30.93-30.92-64.35-47.64-74.66-37.33s6.4 43.73 37.33 74.66c12.67 12.67 25.67 22.77 37.42 29.78-3.14 5.76-5.71 12.06-6.89 19.32-.49 3.03-.71 6.15-.75 9.31C364.55 195.11 261.59 128 192 128c-52.08 0-85.21 28.24-104.72 54.09-1.77-2.7-2.96-5.66-5.33-8.03-18.75-18.75-49.14-18.75-67.88 0-18.74 18.74-18.74 49.14 0 67.88 16.4 16.39 41.29 17.57 59.92 5.29l191.4 191.4c6 6 14.14 9.37 22.63 9.37h144c8.84 0 16-7.16 16-16v-16c0-17.67-14.33-32-32-32h-96v-55.59c0-35.53-23.86-67.16-58.02-76.91l-42.38-12.11c-8.5-2.44-13.42-11.3-11-19.78 2.44-8.52 11.41-13.33 19.78-11l42.38 12.11C318.59 234.38 352 278.66 352 328.41V352l64-32h103.35c31.29 0 56.65-25.36 56.65-56.65a56.66 56.66 0 0 0-23.72-46.1zM496 256c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
3
imgsrc/srv/slower.svg
Normal file
3
imgsrc/srv/slower.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 576 512">
|
||||||
|
<path d="M68.25 256h279.51c23.54 0 40.97-19.8 35.1-40.04C362.84 146.97 292.33 64 208.41 64h-.82c-83.91 0-154.43 82.97-174.44 151.96C27.27 236.2 44.71 256 68.25 256zm484.03-118.75l-48.65-34.75c-35.17-17.42-80.49 1.57-86.81 40.31-.54 3.32-.82 6.72-.82 10.19v71.22c-.03 13.88-4.6 27.18-13.27 38.44-12.42 16.11-31.25 25.34-51.68 25.34H18.6C8.33 288 0 296.33 0 306.6c0 8 5.12 15.11 12.71 17.64l98.29 22.1L66.17 424c-6.16 10.67 1.54 24 13.86 24h36.95c5.71 0 11-3.05 13.86-8l40.3-69.8c25.99 8.52 45.55 13.8 84.87 13.8s58.89-5.28 84.87-13.8l40.3 69.8c2.86 4.95 8.14 8 13.86 8h36.95c12.32 0 20.01-13.33 13.86-24l-47.21-81.76c21.25-8.42 40.36-21.78 54.81-40.53 14.08-18.28 22.47-39.4 25.29-61.7h40.62c31.29 0 56.65-25.36 56.65-56.65a56.7 56.7 0 0 0-23.73-46.11zM480 176c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 868 B |
@ -19,6 +19,8 @@ class Client:
|
|||||||
|
|
||||||
mark_template = '<mark name="{}"/>'
|
mark_template = '<mark name="{}"/>'
|
||||||
name = 'speechd'
|
name = 'speechd'
|
||||||
|
min_rate = -100
|
||||||
|
max_rate = 100
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def escape_marked_text(cls, text):
|
def escape_marked_text(cls, text):
|
||||||
@ -71,7 +73,6 @@ class Client:
|
|||||||
self.shutdown()
|
self.shutdown()
|
||||||
self.settings_applied = False
|
self.settings_applied = False
|
||||||
self.ensure_state()
|
self.ensure_state()
|
||||||
self.settings_applied = True
|
|
||||||
om = self.settings.get('output_module')
|
om = self.settings.get('output_module')
|
||||||
if om:
|
if om:
|
||||||
self.ssip_client.set_output_module(om)
|
self.ssip_client.set_output_module(om)
|
||||||
@ -81,6 +82,7 @@ class Client:
|
|||||||
rate = self.settings.get('rate')
|
rate = self.settings.get('rate')
|
||||||
if rate:
|
if rate:
|
||||||
self.ssip_client.set_rate(rate)
|
self.ssip_client.set_rate(rate)
|
||||||
|
self.settings_applied = True
|
||||||
|
|
||||||
def set_use_ssml(self, on):
|
def set_use_ssml(self, on):
|
||||||
from speechd.client import DataMode, SSIPCommunicationError
|
from speechd.client import DataMode, SSIPCommunicationError
|
||||||
@ -190,3 +192,18 @@ class Client:
|
|||||||
ans[om] = tuple(self.ssip_client.list_synthesis_voices())
|
ans[om] = tuple(self.ssip_client.list_synthesis_voices())
|
||||||
self.ssip_client.set_output_module(output_module)
|
self.ssip_client.set_output_module(output_module)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def change_rate(self, steps=1):
|
||||||
|
rate = current_rate = self.settings.get('rate') or 0
|
||||||
|
step_size = (self.max_rate - self.min_rate) // 10
|
||||||
|
rate += steps * step_size
|
||||||
|
rate = max(self.min_rate, min(rate, self.max_rate))
|
||||||
|
if rate != current_rate:
|
||||||
|
self.settings['rate'] = rate
|
||||||
|
prev_state = self.status.copy()
|
||||||
|
self.apply_settings()
|
||||||
|
if prev_state['synthesizing'] and not prev_state['paused']:
|
||||||
|
self.status['synthesizing'] = True
|
||||||
|
self.status['paused'] = True
|
||||||
|
self.resume_after_configure()
|
||||||
|
return self.settings
|
||||||
|
@ -76,10 +76,12 @@ class Widget(QWidget):
|
|||||||
self.tts_client = tts_client
|
self.tts_client = tts_client
|
||||||
|
|
||||||
self.speed = s = QSlider(Qt.Orientation.Horizontal, self)
|
self.speed = s = QSlider(Qt.Orientation.Horizontal, self)
|
||||||
|
s.setTickPosition(QSlider.TickPosition.TicksAbove)
|
||||||
s.setMinimumWidth(200)
|
s.setMinimumWidth(200)
|
||||||
l.addRow(_('&Speed of speech:'), s)
|
l.addRow(_('&Speed of speech:'), s)
|
||||||
s.setRange(-100, 100)
|
s.setRange(self.tts_client.min_rate, self.tts_client.max_rate)
|
||||||
s.setSingleStep(5)
|
s.setSingleStep(10)
|
||||||
|
s.setTickInterval((self.tts_client.max_rate - self.tts_client.min_rate) // 2)
|
||||||
|
|
||||||
self.output_modules = om = QComboBox(self)
|
self.output_modules = om = QComboBox(self)
|
||||||
with BusyCursor():
|
with BusyCursor():
|
||||||
@ -183,7 +185,7 @@ if __name__ == '__main__':
|
|||||||
from calibre.gui2 import Application
|
from calibre.gui2 import Application
|
||||||
from calibre.gui2.tts.implementation import Client
|
from calibre.gui2.tts.implementation import Client
|
||||||
app = Application([])
|
app = Application([])
|
||||||
c = Client()
|
c = Client({})
|
||||||
w = Widget(c, {})
|
w = Widget(c, {})
|
||||||
w.show()
|
w.show()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
@ -34,13 +34,12 @@ class Config(Dialog):
|
|||||||
return super().accept()
|
return super().accept()
|
||||||
|
|
||||||
|
|
||||||
def add_markup(text_parts):
|
def add_markup(text_parts, mark_template):
|
||||||
from calibre.gui2.tts.implementation import Client
|
from calibre.gui2.tts.implementation import Client
|
||||||
buf = []
|
buf = []
|
||||||
bm = Client.mark_template
|
|
||||||
for x in text_parts:
|
for x in text_parts:
|
||||||
if isinstance(x, int):
|
if isinstance(x, int):
|
||||||
buf.append(bm.format(x))
|
buf.append(mark_template.format(x))
|
||||||
else:
|
else:
|
||||||
buf.append(Client.escape_marked_text(x))
|
buf.append(Client.escape_marked_text(x))
|
||||||
return ''.join(buf)
|
return ''.join(buf)
|
||||||
@ -64,11 +63,15 @@ class TTS(QObject):
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tts_client_class(self):
|
||||||
|
from calibre.gui2.tts.implementation import Client
|
||||||
|
return Client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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
|
self._tts_client = self.tts_client_class(self.backend_settings, 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):
|
||||||
@ -91,7 +94,7 @@ class TTS(QObject):
|
|||||||
return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True)
|
return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True)
|
||||||
|
|
||||||
def play(self, data):
|
def play(self, data):
|
||||||
marked_text = add_markup(data['marked_text'])
|
marked_text = add_markup(data['marked_text'], self.tts_client_class.mark_template)
|
||||||
self.tts_client.speak_marked_text(marked_text, self.callback)
|
self.tts_client.speak_marked_text(marked_text, self.callback)
|
||||||
|
|
||||||
def pause(self, data):
|
def pause(self, data):
|
||||||
@ -114,23 +117,31 @@ class TTS(QObject):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def backend_settings(self):
|
def backend_settings(self):
|
||||||
from calibre.gui2.tts.implementation import Client
|
key = 'tts_' + self.tts_client_class.name
|
||||||
key = 'tts_' + Client.name
|
|
||||||
return vprefs.get(key) or {}
|
return vprefs.get(key) or {}
|
||||||
|
|
||||||
@backend_settings.setter
|
@backend_settings.setter
|
||||||
def backend_settings(self, val):
|
def backend_settings(self, val):
|
||||||
from calibre.gui2.tts.implementation import Client
|
key = 'tts_' + self.tts_client_class.name
|
||||||
key = 'tts_' + Client.name
|
vprefs.set(key, val or {})
|
||||||
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()
|
||||||
d = Config(self.tts_client, ui_settings, self.backend_settings, parent=self.parent())
|
d = Config(self.tts_client, ui_settings, self.backend_settings, parent=self.parent())
|
||||||
if d.exec_() == QDialog.DialogCode.Accepted:
|
if d.exec_() == QDialog.DialogCode.Accepted:
|
||||||
self.backend_settings = d.backend_settings
|
s = d.backend_settings
|
||||||
|
self.backend_settings = s
|
||||||
|
self.tts_client.apply_settings(s)
|
||||||
self.settings_changed.emit(d.ui_settings)
|
self.settings_changed.emit(d.ui_settings)
|
||||||
else:
|
else:
|
||||||
self.settings_changed.emit(None)
|
self.settings_changed.emit(None)
|
||||||
|
|
||||||
|
def slower(self, data):
|
||||||
|
settings = self.tts_client.change_rate(steps=-1)
|
||||||
|
if settings is not None:
|
||||||
|
self.backend_settings = settings
|
||||||
|
|
||||||
|
def faster(self, data):
|
||||||
|
settings = self.tts_client.change_rate(steps=1)
|
||||||
|
if settings is not None:
|
||||||
|
self.backend_settings = settings
|
||||||
|
@ -117,6 +117,8 @@ class ReadAloud:
|
|||||||
bar.appendChild(cb(None, 'hourglass', _('Pause reading')))
|
bar.appendChild(cb(None, 'hourglass', _('Pause reading')))
|
||||||
else:
|
else:
|
||||||
bar.appendChild(cb('play', 'play', _('Start reading') if self.state is STOPPED else _('Resume reading')))
|
bar.appendChild(cb('play', 'play', _('Start reading') if self.state is STOPPED else _('Resume reading')))
|
||||||
|
bar.appendChild(cb('slower', 'slower', _('Slow down speech')))
|
||||||
|
bar.appendChild(cb('faster', 'faster', _('Speed up speech')))
|
||||||
bar.appendChild(cb('configure', 'cogs', _('Configure Read aloud')))
|
bar.appendChild(cb('configure', 'cogs', _('Configure Read aloud')))
|
||||||
bar.appendChild(cb('hide', 'close', _('Close Read aloud')))
|
bar.appendChild(cb('hide', 'close', _('Close Read aloud')))
|
||||||
if self.state is not WAITING_FOR_PLAY_TO_START:
|
if self.state is not WAITING_FOR_PLAY_TO_START:
|
||||||
@ -135,6 +137,12 @@ class ReadAloud:
|
|||||||
self.waiting_for_configure = True
|
self.waiting_for_configure = True
|
||||||
ui_operations.tts('configure')
|
ui_operations.tts('configure')
|
||||||
|
|
||||||
|
def slower(self):
|
||||||
|
ui_operations.tts('slower')
|
||||||
|
|
||||||
|
def faster(self):
|
||||||
|
ui_operations.tts('faster')
|
||||||
|
|
||||||
def play(self):
|
def play(self):
|
||||||
if self.state is PAUSED:
|
if self.state is PAUSED:
|
||||||
ui_operations.tts('resume_after_configure' if self.waiting_for_configure else 'resume')
|
ui_operations.tts('resume_after_configure' if self.waiting_for_configure else 'resume')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user