mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Configuration of TTS in browser viewer now works
This commit is contained in:
parent
a5eca3843b
commit
fbde6307c3
@ -6,7 +6,9 @@ from elementmaker import E
|
|||||||
|
|
||||||
from dom import unique_id
|
from dom import unique_id
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
from book_list.globals import get_session_data
|
||||||
from modals import create_custom_dialog, error_dialog
|
from modals import create_custom_dialog, error_dialog
|
||||||
|
from widgets import create_button
|
||||||
|
|
||||||
|
|
||||||
def escaper():
|
def escaper():
|
||||||
@ -24,13 +26,15 @@ escape_for_xml = escaper()
|
|||||||
class Client:
|
class Client:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.stop_requested_at = None
|
||||||
self.status = {'synthesizing': False, 'paused': False}
|
self.status = {'synthesizing': False, 'paused': False}
|
||||||
self.queue = v'[]'
|
self.queue = v'[]'
|
||||||
self.last_reached_mark = None
|
self.last_reached_mark = None
|
||||||
self.onevent = def():
|
self.onevent = def():
|
||||||
pass
|
pass
|
||||||
self.current_voice_name = ''
|
data = get_session_data().get('tts_backend')
|
||||||
self.current_rate = None
|
self.current_voice_uri = data.voice or ''
|
||||||
|
self.current_rate = data.rate or None
|
||||||
|
|
||||||
def create_utterance(self, text_or_ssml, wrap_in_ssml):
|
def create_utterance(self, text_or_ssml, wrap_in_ssml):
|
||||||
if wrap_in_ssml:
|
if wrap_in_ssml:
|
||||||
@ -45,9 +49,9 @@ class Client:
|
|||||||
ut.onerror = self.utterance_failed
|
ut.onerror = self.utterance_failed
|
||||||
ut.onmark = self.utterance_mark_reached
|
ut.onmark = self.utterance_mark_reached
|
||||||
ut.onresume = self.utterance_resumed
|
ut.onresume = self.utterance_resumed
|
||||||
if self.current_voice_name:
|
if self.current_voice_uri:
|
||||||
for voice in window.speechSynthesis.getVoices():
|
for voice in window.speechSynthesis.getVoices():
|
||||||
if voice.name is self.current_voice_name:
|
if voice.voiceURI is self.current_voice_uri:
|
||||||
ut.voice = voice
|
ut.voice = voice
|
||||||
break
|
break
|
||||||
if self.current_rate:
|
if self.current_rate:
|
||||||
@ -65,6 +69,9 @@ class Client:
|
|||||||
|
|
||||||
def utterance_ended(self, event):
|
def utterance_ended(self, event):
|
||||||
self.status = {'synthesizing': False, 'paused': False}
|
self.status = {'synthesizing': False, 'paused': False}
|
||||||
|
if self.stop_requested_at? and window.performance.now() - self.stop_requested_at < 1000:
|
||||||
|
self.stop_requested_at = None
|
||||||
|
return
|
||||||
self.queue.splice(0, 1)
|
self.queue.splice(0, 1)
|
||||||
if self.queue.length:
|
if self.queue.length:
|
||||||
window.speechSynthesis.speak(self.queue[0])
|
window.speechSynthesis.speak(self.queue[0])
|
||||||
@ -92,9 +99,14 @@ class Client:
|
|||||||
def resume(self):
|
def resume(self):
|
||||||
window.speechSynthesis.resume()
|
window.speechSynthesis.resume()
|
||||||
|
|
||||||
|
def resume_after_configure(self):
|
||||||
|
if self.queue.length:
|
||||||
|
window.speechSynthesis.speak(self.queue[0])
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
window.speechSynthesis.cancel()
|
|
||||||
self.queue = v'[]'
|
self.queue = v'[]'
|
||||||
|
self.stop_requested_at = window.performance.now()
|
||||||
|
window.speechSynthesis.cancel()
|
||||||
self.status = {'synthesizing': False, 'paused': False}
|
self.status = {'synthesizing': False, 'paused': False}
|
||||||
|
|
||||||
def speak_simple_text(self, text):
|
def speak_simple_text(self, text):
|
||||||
@ -135,9 +147,14 @@ class Client:
|
|||||||
voice_id = unique_id()
|
voice_id = unique_id()
|
||||||
rate_id = unique_id()
|
rate_id = unique_id()
|
||||||
default_voice = None
|
default_voice = None
|
||||||
|
|
||||||
|
def restore_defaults():
|
||||||
|
document.getElementById(voice_id).selectedIndex = -1
|
||||||
|
document.getElementById(rate_id).value = 10
|
||||||
|
|
||||||
create_custom_dialog(_('Configure Text-to-Speech'), def (parent_div, close_modal):
|
create_custom_dialog(_('Configure Text-to-Speech'), def (parent_div, close_modal):
|
||||||
nonlocal default_voice
|
nonlocal default_voice
|
||||||
select = E.select(size='10', id=voice_id)
|
select = E.select(size='5', id=voice_id)
|
||||||
voices = window.speechSynthesis.getVoices()
|
voices = window.speechSynthesis.getVoices()
|
||||||
voices.sort(def (a, b):
|
voices.sort(def (a, b):
|
||||||
a = a.name.toLowerCase()
|
a = a.name.toLowerCase()
|
||||||
@ -147,19 +164,23 @@ class Client:
|
|||||||
for voice in voices:
|
for voice in voices:
|
||||||
dflt = ''
|
dflt = ''
|
||||||
if voice.default:
|
if voice.default:
|
||||||
default_voice = voice.name
|
default_voice = voice.voiceURI
|
||||||
dflt = '-- {}'.format(_('default'))
|
dflt = '-- {}'.format(_('default'))
|
||||||
option = E.option(f'{voice.name} ({voice.lang}){dflt}', data_name=voice.name)
|
option = E.option(f'{voice.name} ({voice.lang}){dflt}', value=voice.voiceURI)
|
||||||
if (self.current_voice_name and voice.name is self.current_voice_name) or (not self.current_voice_name and voice.default):
|
if (self.current_voice_uri and voice.voiceURI is self.current_voice_uri) or (not self.current_voice_uri and voice.default):
|
||||||
option.setAttribute('selected', 'selected')
|
option.setAttribute('selected', 'selected')
|
||||||
select.appendChild(option)
|
select.appendChild(option)
|
||||||
parent_div.appendChild(E.div(_('Speed of speech:')))
|
parent_div.appendChild(E.div(_('Speed of speech:')))
|
||||||
parent_div.appendChild(E.input(type='range', id=rate_id, min='1', max='20', value=((self.current_rate or 1) * 10) + ''))
|
parent_div.appendChild(E.input(type='range', id=rate_id, min='1', max='20', value=((self.current_rate or 1) * 10) + ''))
|
||||||
parent_div.appendChild(E.div(_('Pick a voice below:')))
|
parent_div.appendChild(E.div(_('Pick a voice below:')))
|
||||||
parent_div.appendChild(select)
|
parent_div.appendChild(select)
|
||||||
opt = select.querySelector(':selected')
|
if select.options.selectedIndex?:
|
||||||
if opt:
|
select.options[select.options.selectedIndex].scrollIntoView()
|
||||||
opt.scrollIntoView()
|
parent_div.appendChild(E.div(
|
||||||
|
style='margin-top: 1rem; display: flex; justify-content: space-between; align-items: flex-start',
|
||||||
|
create_button(_('Restore defaults'), action=restore_defaults),
|
||||||
|
create_button(_('Close'), action=close_modal)
|
||||||
|
))
|
||||||
|
|
||||||
, on_close=def():
|
, on_close=def():
|
||||||
voice = document.getElementById(voice_id).value
|
voice = document.getElementById(voice_id).value
|
||||||
@ -168,10 +189,19 @@ class Client:
|
|||||||
rate = None
|
rate = None
|
||||||
if voice is default_voice:
|
if voice is default_voice:
|
||||||
voice = ''
|
voice = ''
|
||||||
changed = voice is not self.current_voice_name or rate is not self.current_rate
|
changed = voice is not self.current_voice_uri or rate is not self.current_rate
|
||||||
if changed:
|
if changed:
|
||||||
self.current_voice_name = voice
|
self.current_voice_uri = voice
|
||||||
self.current_rate = rate
|
self.current_rate = rate
|
||||||
|
sd = get_session_data()
|
||||||
|
sd.set('tts_backend', {'voice': voice, 'rate': rate})
|
||||||
|
existing = self.queue
|
||||||
|
if self.queue and self.queue.length:
|
||||||
|
if self.status.paused:
|
||||||
|
window.speechSynthesis.resume()
|
||||||
|
self.stop()
|
||||||
|
for ut in existing:
|
||||||
|
self.create_utterance(ut.text)
|
||||||
|
|
||||||
self.view.read_aloud.handle_tts_event('configured', None)
|
self.onevent('configured')
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user