def create_voice(self): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start()
def __init__(self): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() self.current_stream_number = None self.current_callback = None
def __init__(self, dispatch_on_main_thread): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() self.current_stream_number = None self.current_callback = None self.dispatch_on_main_thread = dispatch_on_main_thread self.status = {'synthesizing': False, 'paused': False}
class Client: def __init__(self): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() self.current_stream_number = None self.current_callback = None def __del__(self): if self.sp_voice is not None: self.sp_voice.shutdown_event_loop() self.events_thread.join(5) self.sp_voice = None shutdown = __del__ def wait_for_events(self): while True: if self.sp_voice.wait_for_event() is False: break c = self.current_callback if c is not None: c() def get_events(self): from calibre_extensions.winsapi import SPEI_TTS_BOOKMARK, SPEI_START_INPUT_STREAM, SPEI_END_INPUT_STREAM ans = [] for (stream_number, event_type, event_data) in self.sp_voice.get_events(): if stream_number == self.current_stream_number: if event_type == SPEI_TTS_BOOKMARK: event = Event(EventType.mark, event_data) elif event_type == SPEI_START_INPUT_STREAM: event = Event(EventType.begin) elif event_type == SPEI_END_INPUT_STREAM: event = Event(EventType.end) else: continue ans.append(event) return ans def speak_simple_text(self, text): from calibre_extensions.winsapi import SPF_ASYNC, SPF_PURGEBEFORESPEAK, SPF_IS_NOT_XML self.current_callback = None self.current_stream_number = self.sp_voice.speak( text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML) def speak_marked_text(self, text, callback): from calibre_extensions.winsapi import SPF_ASYNC, SPF_PURGEBEFORESPEAK, SPF_IS_XML self.current_callback = callback self.current_stream_number = self.sp_voice.speak( text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_XML, True)
class Client: mark_template = '<bookmark mark="{}"/>' name = 'sapi' min_rate = -10 max_rate = 10 @classmethod def escape_marked_text(cls, text): return prepare_string_for_xml(text) def __init__(self, settings=None, dispatch_on_main_thread=lambda f: f()): self.create_voice() self.ignore_next_stop_event = self.ignore_next_start_event = False self.default_system_rate = self.sp_voice.get_current_rate() self.default_system_voice = self.sp_voice.get_current_voice() self.default_system_sound_output = self.sp_voice.get_current_sound_output( ) self.current_stream_number = None self.current_callback = None self.dispatch_on_main_thread = dispatch_on_main_thread self.current_marked_text = self.last_mark = None self.status = {'synthesizing': False, 'paused': False} self.settings = settings or {} self.apply_settings() def create_voice(self): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() def __del__(self): if self.sp_voice is not None: self.sp_voice.shutdown_event_loop() self.events_thread.join(5) self.sp_voice = None shutdown = __del__ def apply_settings(self, new_settings=None): if self.status['paused']: self.sp_voice.resume() self.ignore_next_stop_event = True self.status = {'synthesizing': False, 'paused': False} if new_settings is not None: self.settings = new_settings self.sp_voice.set_current_rate( self.settings.get('rate', self.default_system_rate)) self.sp_voice.set_current_voice( self.settings.get('voice') or self.default_system_voice) self.sp_voice.set_current_sound_output( self.settings.get('sound_output') or self.default_system_sound_output) def wait_for_events(self): while True: if self.sp_voice.wait_for_event() is False: break self.dispatch_on_main_thread(self.handle_events) def handle_events(self): from calibre_extensions.winsapi import (SPEI_END_INPUT_STREAM, SPEI_START_INPUT_STREAM, SPEI_TTS_BOOKMARK) c = self.current_callback for (stream_number, event_type, event_data) in self.sp_voice.get_events(): if event_type == SPEI_TTS_BOOKMARK: self.last_mark = event_data event = Event(EventType.mark, event_data) elif event_type == SPEI_START_INPUT_STREAM: if self.ignore_next_start_event: self.ignore_next_start_event = False continue event = Event(EventType.begin) self.status = {'synthesizing': True, 'paused': False} elif event_type == SPEI_END_INPUT_STREAM: if self.ignore_next_stop_event: self.ignore_next_stop_event = False continue event = Event(EventType.end) self.status = {'synthesizing': False, 'paused': False} else: continue if c is not None and stream_number == self.current_stream_number: try: c(event) except Exception: import traceback traceback.print_exc() def speak(self, text, is_xml=False, want_events=True): from calibre_extensions.winsapi import (SPF_ASYNC, SPF_IS_NOT_XML, SPF_PURGEBEFORESPEAK, SPF_IS_XML) flags = SPF_IS_XML if is_xml else SPF_IS_NOT_XML self.current_stream_number = self.sp_voice.speak( text, flags | SPF_PURGEBEFORESPEAK | SPF_ASYNC, want_events) return self.current_stream_number def speak_simple_text(self, text): self.current_callback = None self.current_marked_text = self.last_mark = None self.speak(text) def speak_marked_text(self, text, callback): self.current_marked_text = text self.last_mark = None self.current_callback = callback self.speak(text, is_xml=True) def stop(self): from calibre_extensions.winsapi import SPF_PURGEBEFORESPEAK if self.status['paused']: self.sp_voice.resume() self.sp_voice.speak('', SPF_PURGEBEFORESPEAK, False) self.status = {'synthesizing': False, 'paused': False} if self.current_callback is not None: self.current_callback(Event(EventType.cancel)) self.current_callback = None def pause(self): if self.status['synthesizing'] and not self.status['paused']: self.sp_voice.pause() self.status = {'synthesizing': True, 'paused': True} if self.current_callback is not None: self.current_callback(Event(EventType.pause)) def resume(self): if self.status['paused']: self.sp_voice.resume() self.status = {'synthesizing': True, 'paused': False} if self.current_callback is not None: self.current_callback(Event(EventType.resume)) def resume_after_configure(self): if self.status['paused']: self.resume() return if self.last_mark is None: idx = -1 else: mark = self.mark_template.format(self.last_mark) idx = self.current_marked_text.find(mark) if idx == -1: text = self.current_marked_text else: text = self.current_marked_text[idx:] self.ignore_next_start_event = True if self.current_callback is not None: self.current_callback(Event(EventType.resume)) self.speak(text, is_xml=True) self.status = {'synthesizing': True, 'paused': False} def get_voice_data(self): ans = getattr(self, 'voice_data', None) if ans is None: ans = self.voice_data = self.sp_voice.get_all_voices() return ans def get_sound_outputs(self): ans = getattr(self, 'sound_outputs', None) if ans is None: ans = self.sound_outputs = self.sp_voice.get_all_sound_outputs() return ans def config_widget(self, backend_settings, parent): from calibre.gui2.tts.windows_config import Widget return Widget(self, backend_settings, parent) def change_rate(self, steps=1): rate = current_rate = self.settings.get('rate', self.default_system_rate) 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.pause() self.apply_settings() if prev_state['synthesizing']: self.status = {'synthesizing': True, 'paused': False} self.resume_after_configure() return self.settings
class Client: mark_template = '<bookmark mark="{}"/>' name = 'sapi' @classmethod def escape_marked_text(cls, text): return prepare_string_for_xml(text) def __init__(self, dispatch_on_main_thread): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() self.current_stream_number = None self.current_callback = None self.dispatch_on_main_thread = dispatch_on_main_thread self.status = {'synthesizing': False, 'paused': False} def __del__(self): if self.sp_voice is not None: self.sp_voice.shutdown_event_loop() self.events_thread.join(5) self.sp_voice = None shutdown = __del__ def wait_for_events(self): while True: if self.sp_voice.wait_for_event() is False: break self.dispatch_on_main_thread(self.handle_events) def handle_events(self): from calibre_extensions.winsapi import (SPEI_END_INPUT_STREAM, SPEI_START_INPUT_STREAM, SPEI_TTS_BOOKMARK) c = self.current_callback for (stream_number, event_type, event_data) in self.sp_voice.get_events(): if event_type == SPEI_TTS_BOOKMARK: event = Event(EventType.mark, event_data) elif event_type == SPEI_START_INPUT_STREAM: event = Event(EventType.begin) self.status = {'synthesizing': True, 'paused': False} elif event_type == SPEI_END_INPUT_STREAM: event = Event(EventType.end) self.status = {'synthesizing': False, 'paused': False} else: continue if c is not None and stream_number == self.current_stream_number: try: c(event) except Exception: import traceback traceback.print_exc() def speak_simple_text(self, text): from calibre_extensions.winsapi import (SPF_ASYNC, SPF_IS_NOT_XML, SPF_PURGEBEFORESPEAK) self.current_callback = None self.current_stream_number = self.sp_voice.speak( text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_NOT_XML, True) def speak_marked_text(self, text, callback): from calibre_extensions.winsapi import (SPF_ASYNC, SPF_IS_XML, SPF_PURGEBEFORESPEAK) self.current_callback = callback self.current_stream_number = self.sp_voice.speak( text, SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_XML, True) def stop(self): from calibre_extensions.winsapi import SPF_PURGEBEFORESPEAK if self.status['paused']: self.sp_voice.resume() self.sp_voice.speak('', SPF_PURGEBEFORESPEAK, False) self.status = {'synthesizing': False, 'paused': False} if self.current_callback is not None: self.current_callback(Event(EventType.cancel)) self.current_callback = None def pause(self): if self.status['synthesizing'] and not self.status['paused']: self.sp_voice.pause() self.status = {'synthesizing': True, 'paused': True} if self.current_callback is not None: self.current_callback(Event(EventType.pause)) def resume(self): if self.status['paused']: self.sp_voice.resume() self.status = {'synthesizing': True, 'paused': False} if self.current_callback is not None: self.current_callback(Event(EventType.resume))
class Client: mark_template = '<bookmark mark="{}"/>' name = 'sapi' min_rate = -10 max_rate = 10 chunk_size = 128 * 1024 @classmethod def escape_marked_text(cls, text): return prepare_string_for_xml(text) def __init__(self, settings=None, dispatch_on_main_thread=lambda f: f()): self.create_voice() self.ignore_next_stop_event = None self.ignore_next_start_event = False self.default_system_rate = self.sp_voice.get_current_rate() self.default_system_voice = self.sp_voice.get_current_voice() self.default_system_sound_output = self.sp_voice.get_current_sound_output( ) self.current_stream_queue = SpeechQueue() self.current_callback = None self.dispatch_on_main_thread = dispatch_on_main_thread self.synthesizing = False self.pause_count = 0 self.settings = settings or {} self.apply_settings() @property def status(self): return { 'synthesizing': self.synthesizing, 'paused': self.pause_count > 0 } def clear_pauses(self): while self.pause_count: self.sp_voice.resume() self.pause_count -= 1 def create_voice(self): from calibre.utils.windows.winsapi import ISpVoice self.sp_voice = ISpVoice() self.events_thread = Thread(name='SAPIEvents', target=self.wait_for_events, daemon=True) self.events_thread.start() def __del__(self): if self.sp_voice is not None: self.sp_voice.shutdown_event_loop() self.events_thread.join(5) self.sp_voice = None shutdown = __del__ def apply_settings(self, new_settings=None): if self.pause_count: self.clear_pauses() self.ignore_next_stop_event = monotonic() self.synthesizing = False if new_settings is not None: self.settings = new_settings self.sp_voice.set_current_rate( self.settings.get('rate', self.default_system_rate)) self.sp_voice.set_current_voice( self.settings.get('voice') or self.default_system_voice) self.sp_voice.set_current_sound_output( self.settings.get('sound_output') or self.default_system_sound_output) def wait_for_events(self): while True: if self.sp_voice.wait_for_event() is False: break self.dispatch_on_main_thread(self.handle_events) def handle_events(self): from calibre_extensions.winsapi import (SPEI_END_INPUT_STREAM, SPEI_START_INPUT_STREAM, SPEI_TTS_BOOKMARK) c = self.current_callback for (stream_number, event_type, event_data) in self.sp_voice.get_events(): if event_type == SPEI_TTS_BOOKMARK: self.current_stream_queue.last_mark = event_data event = Event(EventType.mark, event_data) elif event_type == SPEI_START_INPUT_STREAM: self.current_stream_queue.start(stream_number) if self.ignore_next_start_event: self.ignore_next_start_event = False continue self.synthesizing = True if not self.current_stream_queue.is_at_start: continue event = Event(EventType.begin) elif event_type == SPEI_END_INPUT_STREAM: if self.ignore_next_stop_event is not None and monotonic( ) - self.ignore_next_stop_event < 2: self.ignore_next_stop_event = None continue self.synthesizing = False if not self.current_stream_queue.is_at_end: continue event = Event(EventType.end) else: continue if c is not None and stream_number == self.current_stream_queue.current_stream_number: try: c(event) except Exception: import traceback traceback.print_exc() def speak(self, text, is_xml=False, want_events=True, purge=True): from calibre_extensions.winsapi import (SPF_ASYNC, SPF_IS_NOT_XML, SPF_PURGEBEFORESPEAK, SPF_IS_XML) flags = SPF_IS_XML if is_xml else SPF_IS_NOT_XML if purge: flags |= SPF_PURGEBEFORESPEAK return self.sp_voice.speak(text, flags | SPF_ASYNC, want_events) def purge(self): from calibre_extensions.winsapi import SPF_PURGEBEFORESPEAK self.sp_voice.speak('', SPF_PURGEBEFORESPEAK, False) self.synthesizing = False def speak_simple_text(self, text): self.current_callback = None self.current_stream_queue.clear() number = self.speak(text) self.clear_pauses() self.current_stream_queue.add(number, text) def speak_marked_text(self, text, callback): self.clear_pauses() self.current_stream_queue.clear() if self.synthesizing: self.ignore_next_stop_event = monotonic() self.current_callback = callback for i, chunk in enumerate( add_markup(text, self.mark_template, self.escape_marked_text, self.chunk_size)): number = self.speak(chunk, is_xml=True, purge=i == 0) self.current_stream_queue.add(number, chunk) def stop(self): self.clear_pauses() self.purge() if self.current_callback is not None: self.current_callback(Event(EventType.cancel)) self.current_callback = None def pause(self): self.sp_voice.pause() self.pause_count += 1 if self.current_callback is not None: self.current_callback(Event(EventType.pause)) def resume(self): if self.pause_count: self.clear_pauses() if self.current_callback is not None: self.current_callback(Event(EventType.resume)) def resume_after_configure(self): if self.pause_count: self.clear_pauses() return chunks = tuple( self.current_stream_queue.resume_from_last_mark( self.mark_template)) self.ignore_next_start_event = True self.current_stream_queue.clear(keep_mark=True) self.purge() for chunk in chunks: number = self.speak(chunk, is_xml=True, purge=False) self.current_stream_queue.add(number, chunk) if self.current_callback is not None: self.current_callback(Event(EventType.resume)) self.synthesizing = bool(chunks) def get_voice_data(self): ans = getattr(self, 'voice_data', None) if ans is None: ans = self.voice_data = self.sp_voice.get_all_voices() return ans def get_sound_outputs(self): ans = getattr(self, 'sound_outputs', None) if ans is None: ans = self.sound_outputs = self.sp_voice.get_all_sound_outputs() return ans def config_widget(self, backend_settings, parent): from calibre.gui2.tts.windows_config import Widget return Widget(self, backend_settings, parent) def change_rate(self, steps=1): rate = current_rate = self.settings.get('rate', self.default_system_rate) 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 was_synthesizing = self.synthesizing self.pause() self.apply_settings() if was_synthesizing: self.synthesizing = True self.resume_after_configure() return self.settings