1# -*- coding: utf-8; mode: Python; indent-tabs-mode: t -*- 2# Copyright (C) 2010, 2011, 2012, 2013, 2018, 2019 Olga Yakovleva <yakovleva.o.v@gmail.com> 3# Copyright (C) 2019 Beqa Gozalishvili <beqaprogger@gmail.com> 4 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 2 of the License, or 8# (at your option) any later version. 9 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18import sys 19import os.path 20try: 21 import Queue 22except ImportError: 23 import queue as Queue 24from collections import OrderedDict,defaultdict 25import threading 26import ctypes 27from ctypes import c_char_p,c_wchar_p,c_void_p,c_short,c_int,c_uint,c_double,POINTER,Structure,sizeof,string_at,CFUNCTYPE,byref,cast 28import re 29import copy 30 31try: 32 from io import StringIO 33except ImportError: 34 from StringIO import StringIO 35 36PY2 = sys.version_info[0] == 2 37 38import config 39import globalVars 40import nvwave 41from logHandler import log 42import synthDriverHandler 43import speech 44import languageHandler 45import addonHandler 46 47api_version_0=(0,0,0) 48api_version_2019_3=(2019,3,0) 49api_version=api_version_0 50try: 51 import AddonAPIVersion 52 api_version=addonAPIVersion.CURRENT 53except ImportError: 54 pass 55 56if PY2: 57 # Convert __file__ to unicode to avoid problems on Windows 58 # https://stackoverflow.com/questions/35117936/save-file-with-russian-letters-in-the-file-name 59 fs_encoding = sys.getfilesystemencoding() 60 module_dir=os.path.dirname(__file__.decode(fs_encoding)) 61else: 62 module_dir=os.path.dirname(__file__) 63lib_path=os.path.join(module_dir,"RHVoice.dll") 64config_path=os.path.join(globalVars.appArgs.configPath,"RHVoice-config") 65 66try: 67 basestring 68 unicode 69except NameError: 70 basestring = str 71 unicode = str 72 73class nvda_notification_wrapper(object): 74 def __init__(self,name,lst): 75 self.name=name 76 try: 77 self.target=getattr(synthDriverHandler,name) 78 lst.append(self.target) 79 except AttributeError: 80 self.target=None 81 82 def is_supported(self): 83 return (self.target is not None) 84 85 def notify(self,synth,**kw0): 86 if not self.is_supported(): 87 return; 88 kw={"synth":synth} 89 kw.update(kw0) 90 self.target.notify(**kw) 91 92nvda_notifications=[] 93nvda_notification_synthIndexReached=nvda_notification_wrapper("synthIndexReached",nvda_notifications) 94nvda_notification_synthDoneSpeaking=nvda_notification_wrapper("synthDoneSpeaking",nvda_notifications) 95 96data_addon_name_pattern=re.compile("^RHVoice-.*(voice|language).*") 97 98class RHVoice_tts_engine_struct(Structure): 99 pass 100RHVoice_tts_engine=POINTER(RHVoice_tts_engine_struct) 101 102class RHVoice_message_struct(Structure): 103 pass 104RHVoice_message=POINTER(RHVoice_message_struct) 105 106class RHVoice_callback_types: 107 set_sample_rate=CFUNCTYPE(c_int,c_int,c_void_p) 108 play_speech=CFUNCTYPE(c_int,POINTER(c_short),c_uint,c_void_p) 109 process_mark=CFUNCTYPE(c_int,c_char_p,c_void_p) 110 word_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p) 111 word_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p) 112 sentence_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p) 113 sentence_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p) 114 play_audio=CFUNCTYPE(c_int,c_char_p,c_void_p) 115 done=CFUNCTYPE(None,c_void_p) 116 117class RHVoice_callbacks(Structure): 118 _fields_=[("set_sample_rate",RHVoice_callback_types.set_sample_rate), 119 ("play_speech",RHVoice_callback_types.play_speech), 120 ("process_mark",RHVoice_callback_types.process_mark), 121 ("word_starts",RHVoice_callback_types.word_starts), 122 ("word_ends",RHVoice_callback_types.word_ends), 123 ("sentence_starts",RHVoice_callback_types.sentence_starts), 124 ("sentence_ends",RHVoice_callback_types.sentence_ends), 125 ("play_audio",RHVoice_callback_types.play_audio), 126 ("done",RHVoice_callback_types.done)] 127 128class RHVoice_init_params(Structure): 129 _fields_=[("data_path",c_char_p), 130 ("config_path",c_char_p), 131 ("resource_paths",POINTER(c_char_p)), 132 ("callbacks",RHVoice_callbacks), 133 ("options",c_uint)] 134 135class RHVoice_message_type: 136 text=0 137 ssml=1 138 characters=2 139 140class RHVoice_voice_gender: 141 unknown=0 142 male=1 143 female=2 144 145class RHVoice_voice_info(Structure): 146 _fields_=[("language",c_char_p), 147 ("name",c_char_p), 148 ("gender",c_int), 149 ("country",c_char_p)] 150 151class RHVoice_punctuation_mode: 152 default=0 153 none=1 154 all=2 155 some=3 156 157class RHVoice_capitals_mode: 158 default=0 159 off=1 160 word=2 161 pitch=3 162 sound=4 163 164class RHVoice_synth_flag: 165 dont_clip_rate=1 166 167class RHVoice_synth_params(Structure): 168 _fields_=[("voice_profile",c_char_p), 169 ("absolute_rate",c_double), 170 ("absolute_pitch",c_double), 171 ("absolute_volume",c_double), 172 ("relative_rate",c_double), 173 ("relative_pitch",c_double), 174 ("relative_volume",c_double), 175 ("punctuation_mode",c_int), 176 ("punctuation_list",c_char_p), 177 ("capitals_mode",c_int), 178 ("flags",c_int)] 179 180def load_tts_library(): 181 lib=ctypes.CDLL(lib_path) 182 lib.RHVoice_get_version.restype=c_char_p 183 lib.RHVoice_new_tts_engine.argtypes=(POINTER(RHVoice_init_params),) 184 lib.RHVoice_new_tts_engine.restype=RHVoice_tts_engine 185 lib.RHVoice_delete_tts_engine.argtypes=(RHVoice_tts_engine,) 186 lib.RHVoice_delete_tts_engine.restype=None 187 lib.RHVoice_get_number_of_voices.argtypes=(RHVoice_tts_engine,) 188 lib.RHVoice_get_number_of_voices.restype=c_uint 189 lib.RHVoice_get_voices.argtypes=(RHVoice_tts_engine,) 190 lib.RHVoice_get_voices.restype=POINTER(RHVoice_voice_info) 191 lib.RHVoice_get_number_of_voice_profiles.argtypes=(RHVoice_tts_engine,) 192 lib.RHVoice_get_number_of_voice_profiles.restype=c_uint 193 lib.RHVoice_get_voice_profiles.argtypes=(RHVoice_tts_engine,) 194 lib.RHVoice_get_voice_profiles.restype=POINTER(c_char_p) 195 lib.RHVoice_are_languages_compatible.argtypes=(RHVoice_tts_engine,c_char_p,c_char_p) 196 lib.RHVoice_are_languages_compatible.restype=c_int 197 lib.RHVoice_new_message.argtypes=(RHVoice_tts_engine,c_char_p,c_uint,c_int,POINTER(RHVoice_synth_params),c_void_p) 198 lib.RHVoice_new_message.restype=RHVoice_message 199 lib.RHVoice_delete_message.arg_types=(RHVoice_message,) 200 lib.RHVoice_delete_message.restype=None 201 lib.RHVoice_speak.argtypes=(RHVoice_message,) 202 lib.RHVoice_speak.restype=c_int 203 return lib 204 205def escape_text(text): 206 parts=list() 207 for c in text: 208 if c.isspace(): 209 part=u"&#{};".format(ord(c)) 210 elif c=="<": 211 part=u"<" 212 elif c==">": 213 part=u">" 214 elif c=="&": 215 part=u"&" 216 elif c=="'": 217 part=u"'" 218 elif c=='"': 219 part=u""" 220 else: 221 part=c 222 parts.append(part) 223 return u"".join(parts) 224 225class audio_player(object): 226 def __init__(self,synth,cancel_flag): 227 self.__synth=synth 228 self.__cancel_flag=cancel_flag 229 self.__sample_rate=0 230 self.__players={} 231 self.__lock=threading.Lock() 232 self.__closed=False 233 234 def do_get_player(self): 235 if self.__closed: 236 return None 237 if self.__sample_rate==0: 238 return None 239 player=self.__players.get(self.__sample_rate,None) 240 if player is None: 241 player=nvwave.WavePlayer(channels=1,samplesPerSec=self.__sample_rate,bitsPerSample=16,outputDevice=config.conf["speech"]["outputDevice"]) 242 self.__players[self.__sample_rate]=player 243 return player 244 245 def get_player(self): 246 with self.__lock: 247 return self.do_get_player() 248 249 def close(self): 250 with self.__lock: 251 self.__closed=True 252 players=list(self.__players.values()) 253 for p in players: 254 p.close() 255 256 def set_sample_rate(self,sr): 257 with self.__lock: 258 if self.__closed: 259 return 260 if self.__sample_rate==0: 261 self.__sample_rate=sr 262 return 263 if self.__sample_rate==sr: 264 return 265 old_player=self.__players.get(self.__sample_rate,None) 266 if old_player is not None: 267 old_player.idle() 268 with self.__lock: 269 self.__sample_rate=sr 270 271 def do_play(self,data,index=None): 272 player=self.get_player() 273 if player is not None and not self.__cancel_flag.is_set(): 274 if index is None: 275 player.feed(data) 276 else: 277 player.feed(data,onDone=lambda synth=self.__synth,next_index=index: nvda_notification_synthIndexReached.notify(synth,index=next_index)) 278 if self.__cancel_flag.is_set(): 279 player.stop() 280 281 def play(self,data): 282 if self.__prev_data is None: 283 self.__prev_data=data 284 return 285 self.do_play(self.__prev_data) 286 self.__prev_data=data 287 288 def stop(self): 289 player=self.get_player() 290 if player is not None: 291 player.stop() 292 293 def pause(self,switch): 294 player=self.get_player() 295 if player is not None: 296 player.pause(switch) 297 298 def idle(self): 299 player=self.get_player() 300 if player is not None: 301 player.idle() 302 303 def on_new_message(self): 304 self.__prev_data=None 305 306 def on_done(self): 307 if self.__prev_data is None: 308 return 309 self.do_play(self.__prev_data) 310 self.__prev_data=None 311 312 def on_index(self,index): 313 data=self.__prev_data 314 self.__prev_data=None 315 if data is None: 316 data=b"" 317 self.do_play(data,index) 318 319class sample_rate_callback(object): 320 def __init__(self,lib,player): 321 self.__lib=lib 322 self.__player=player 323 324 def __call__(self,sample_rate,user_data): 325 try: 326 self.__player.set_sample_rate(sample_rate) 327 return 1 328 except: 329 log.error("RHVoice sample rate callback",exc_info=True) 330 return 0 331 332class speech_callback(object): 333 def __init__(self,lib,player,cancel_flag): 334 self.__lib=lib 335 self.__player=player 336 self.__cancel_flag=cancel_flag 337 338 def __call__(self,samples,count,user_data): 339 try: 340 if self.__cancel_flag.is_set(): 341 return 0 342 try: 343 self.__player.play(string_at(samples,count*sizeof(c_short))) 344 except: 345 log.debugWarning("Error feeding audio to nvWave",exc_info=True) 346 return 1 347 except: 348 log.error("RHVoice speech callback",exc_info=True) 349 return 0 350 351class mark_callback(object): 352 def __init__(self,lib,player): 353 self.__lib=lib 354 self.__player=player 355 self.__lock=threading.Lock() 356 self.__index=None 357 358 @property 359 def index(self): 360 with self.__lock: 361 return self.__index 362 363 @index.setter 364 def index(self,value): 365 with self.__lock: 366 self.__index=value 367 368 def __call__(self,name,user_data): 369 try: 370 index=int(name) 371 if nvda_notification_synthIndexReached.is_supported(): 372 self.__player.on_index(index) 373 else: 374 self.index=index 375 return 1 376 except: 377 log.error("RHVoice mark callback",exc_info=True) 378 return 0 379 380class done_callback(object): 381 def __init__(self,synth,lib,player,cancel_flag): 382 self.__synth=synth 383 self.__lib=lib 384 self.__player=player 385 self.__cancel_flag=cancel_flag 386 387 def __call__(self,user_data): 388 try: 389 if self.__cancel_flag.is_set(): 390 return 391 self.__player.on_done() 392 self.__player.idle() 393 if self.__cancel_flag.is_set(): 394 return 395 nvda_notification_synthDoneSpeaking.notify(self.__synth) 396 except: 397 log.error("RHVoice done callback",exc_info=True) 398 399class speak_text(object): 400 def __init__(self,lib,tts_engine,text,cancel_flag,player): 401 self.__lib=lib 402 self.__tts_engine=tts_engine 403 self.__text=text.encode("utf-8", errors="ignore") 404 self.__cancel_flag=cancel_flag 405 self.__player=player 406 self.__synth_params=RHVoice_synth_params(voice_profile=None, 407 absolute_rate=0, 408 relative_rate=1, 409 absolute_pitch=0, 410 relative_pitch=1, 411 absolute_volume=0, 412 relative_volume=1, 413 punctuation_mode=RHVoice_punctuation_mode.default, 414 punctuation_list=None, 415 capitals_mode=RHVoice_capitals_mode.default, 416 flags=0) 417 418 def set_rate(self,rate): 419 self.__synth_params.absolute_rate=rate/50.0-1 420 421 def set_pitch(self,pitch): 422 self.__synth_params.absolute_pitch=pitch/50.0-1 423 424 def set_volume(self,volume): 425 self.__synth_params.absolute_volume=volume/50.0-1 426 427 def set_voice_profile(self,name): 428 self.__synth_params.voice_profile=name.encode("utf-8") 429 430 def configure_rate_boost(self,flag): 431 if not flag: 432 return 433 self.__synth_params.relative_rate=2.5 434 self.__synth_params.flags|=RHVoice_synth_flag.dont_clip_rate 435 436 def __call__(self): 437 if self.__cancel_flag.is_set(): 438 return 439 msg=self.__lib.RHVoice_new_message(self.__tts_engine, 440 self.__text, 441 len(self.__text), 442 RHVoice_message_type.ssml, 443 byref(self.__synth_params), 444 None) 445 if msg: 446 self.__player.on_new_message() 447 self.__lib.RHVoice_speak(msg) 448 self.__player.idle() 449 self.__lib.RHVoice_delete_message(msg) 450 451class TTSThread(threading.Thread): 452 def __init__(self,tts_queue): 453 self.__queue=tts_queue 454 threading.Thread.__init__(self) 455 self.daemon=True 456 457 def run(self): 458 while True: 459 try: 460 task=self.__queue.get() 461 if task is None: 462 break 463 else: 464 task() 465 except: 466 log.error("RHVoice: error while executing a tts task",exc_info=True) 467 468class nvda_speak_argument_converter(object): 469 def outputs_element(self): 470 return True 471 472 def get_ssml_tag_name(self): 473 raise NotImplementedError 474 475 def get_ssml_attributes(self): 476 return {} 477 478 def get_text(self): 479 return u"" 480 481 def write(self,out): 482 txt=self.get_text() 483 if txt: 484 out.write(txt) 485 return 486 if not self.outputs_element(): 487 for child in self.children: 488 child.write(out) 489 return 490 tag=self.get_ssml_tag_name() 491 out.write(u'<') 492 out.write(tag) 493 a=self.get_ssml_attributes() 494 for k,v in a.items(): 495 out.write(u' {}="{}"'.format(k,v)) 496 if len(self.children)==0: 497 out.write(u'/>') 498 return 499 out.write(u'>') 500 for child in self.children: 501 child.write(out) 502 out.write(u'</') 503 out.write(tag) 504 out.write(u'>') 505 506class nvda_speech_item_converter(nvda_speak_argument_converter): 507 def check_item_class(self): 508 try: 509 cls=self.get_item_class() 510 return True 511 except AttributeError: 512 return False 513 514 def accepts(self,item): 515 return isinstance(item,self.get_item_class()) 516 517 def get_item_class(self): 518 raise NotImplementedError 519 520 def converts_speech_command(self): 521 return False 522 523 def on_create(self): 524 pass 525 526 def create(self,synthDriver,item=None): 527 obj=type(self)() 528 obj.synthDriver=synthDriver 529 obj.item=item 530 obj.children=[] 531 obj.on_create() 532 return obj 533 534 def converts_mode_command(self): 535 return False 536 537class nvda_speech_command_converter(nvda_speech_item_converter): 538 def converts_speech_command(self): 539 return True 540 541class nvda_text_item_converter(nvda_speech_item_converter): 542 def get_item_class(self): 543 return basestring 544 545 def outputs_element(self): 546 return False 547 548 def get_text(self): 549 return escape_text(unicode(self.item)) 550 551class nvda_index_command_converter(nvda_speech_command_converter): 552 def get_item_class(self): 553 return speech.IndexCommand 554 555 def get_ssml_tag_name(self): 556 return u"mark" 557 558 def get_ssml_attributes(self): 559 return {"name":self.item.index} 560 561class nvda_speech_mode_command_converter(nvda_speech_command_converter): 562 def converts_mode_command(self): 563 return True 564 565 def is_default(self): 566 return (self.item is None) 567 568 def outputs_element(self): 569 return (not self.is_default()) 570 571 def is_empty(self): 572 n=len(self.children) 573 if n==0: 574 return True 575 if n>1: 576 return False 577 if not self.children[0].converts_mode_command(): 578 return False 579 return self.children[0].is_empty() 580 581 def on_clone(self,src): 582 pass 583 584 def clone(self,item=None): 585 obj=type(self)() 586 obj.children=[] 587 obj.synthDriver=self.synthDriver 588 if item: 589 obj.item=item 590 obj.on_create() 591 else: 592 obj.item=self.item 593 obj.on_clone(self) 594 if len(self.children)>0 and self.children[-1].converts_mode_command(): 595 obj.children.append(self.children[-1].clone()) 596 return obj 597 598class nvda_char_mode_command_converter(nvda_speech_mode_command_converter): 599 def get_item_class(self): 600 return speech.CharacterModeCommand 601 602 def is_default(self): 603 return not (self.item is not None and self.item.state) 604 605 def get_ssml_tag_name(self): 606 return u"say-as" 607 608 def get_ssml_attributes(self): 609 return {"interpret-as":"characters"} 610 611class nvda_lang_change_command_converter(nvda_speech_mode_command_converter): 612 def get_item_class(self): 613 return speech.LangChangeCommand 614 615 def get_ssml_tag_name(self): 616 return u"voice" 617 618 def get_ssml_attributes(self): 619 return {"xml:lang":self.lang} 620 621 def is_default(self): 622 return (self.lang is None) 623 624 def on_create(self): 625 self.lang=None 626 if self.item is None: 627 return 628 if not self.item.lang: 629 return 630 lang="_".join(self.item.lang.split("_")[:2]) 631 if lang not in self.synthDriver._SynthDriver__languages: 632 return 633 if self.synthDriver._SynthDriver__languages_match(lang,self.synthDriver._SynthDriver__voice_languages[self.synthDriver._SynthDriver__profile.split("+")[0]]): 634 return 635 self.lang=lang.replace("_","-") 636 637 def on_clone(self,src): 638 self.lang=src.lang 639 640class nvda_prosody_command_converter(nvda_speech_mode_command_converter): 641 def get_ssml_tag_name(self): 642 return u"prosody" 643 644 def get_ssml_attribute_name(self): 645 raise NotImplementedError 646 647 def get_ssml_attributes(self): 648 value="{}%".format(int(round(self.item.multiplier*100.0))) 649 return {self.get_ssml_attribute_name():value} 650 651 def is_default(self): 652 return (self.item is None or self.item.multiplier==1) 653 654class nvda_pitch_command_converter(nvda_prosody_command_converter): 655 def get_item_class(self): 656 return speech.PitchCommand 657 658 def get_ssml_attribute_name(self): 659 return "pitch" 660 661class nvda_volume_command_converter(nvda_prosody_command_converter): 662 def get_item_class(self): 663 return speech.VolumeCommand 664 665 def get_ssml_attribute_name(self): 666 return "volume" 667 668class nvda_rate_command_converter(nvda_prosody_command_converter): 669 def get_item_class(self): 670 return speech.RateCommand 671 672 def get_ssml_attribute_name(self): 673 return "rate" 674 675all_speech_item_converters=[nvda_lang_change_command_converter(),nvda_pitch_command_converter(),nvda_rate_command_converter(),nvda_volume_command_converter(),nvda_char_mode_command_converter(),nvda_index_command_converter(),nvda_text_item_converter()] 676speech_item_converters=[cnv for cnv in all_speech_item_converters if cnv.check_item_class()] 677speech_command_converters=[cnv for cnv in speech_item_converters if cnv.converts_speech_command()] 678mode_command_converters=[cnv for cnv in speech_command_converters if cnv.converts_mode_command()] 679content_item_converters=[cnv for cnv in speech_item_converters if not cnv.converts_mode_command()] 680 681class nvda_speech_sequence_converter(nvda_speak_argument_converter): 682 def __init__(self,synthDriver): 683 self.synthDriver=synthDriver 684 self.children=[] 685 self.current=self 686 for cnv in mode_command_converters: 687 self.current.children.append(cnv.create(self.synthDriver)) 688 self.current=self.current.children[-1] 689 690 def append(self,item): 691 for cnv in content_item_converters: 692 if cnv.accepts(item): 693 self.current.children.append(cnv.create(self.synthDriver,item)) 694 return 695 found=False 696 p=self 697 while p.children and p.children[-1].converts_mode_command(): 698 n=p.children[-1] 699 if found: 700 self.current=n 701 elif n.accepts(item): 702 found=True 703 if n.is_empty(): 704 n.item=item 705 n.on_create() 706 return 707 else: 708 n=n.clone(item) 709 p.children.append(n) 710 self.current=n 711 p=n 712 if not found: 713 log.debugWarning(u"RHVoice: unsupported item: {}".format(item)) 714 715 def get_ssml_tag_name(self): 716 return u"speak" 717 718class SynthDriver(synthDriverHandler.SynthDriver): 719 name="RHVoice" 720 description="RHVoice" 721 722 supportedSettings=[synthDriverHandler.SynthDriver.VoiceSetting(), 723 synthDriverHandler.SynthDriver.RateSetting(), 724 synthDriverHandler.SynthDriver.PitchSetting(), 725 synthDriverHandler.SynthDriver.VolumeSetting()] 726 if hasattr(synthDriverHandler.SynthDriver,"RateBoostSetting"): 727 supportedSettings.append(synthDriverHandler.SynthDriver.RateBoostSetting()) 728 729 if api_version>=api_version_2019_3: 730 supportedCommands=frozenset([cnv.get_item_class() for cnv in speech_command_converters]) 731 supportedNotifications=frozenset(nvda_notifications) 732 733 @classmethod 734 def check(cls): 735 return os.path.isfile(lib_path) 736 737 def __languages_match(self,code1,code2,full=True): 738 lang1=code1.split("_") 739 lang2=code2.split("_") 740 if lang1[0]!=lang2[0]: 741 return False 742 if len(lang1)<2 or len(lang2)<2: 743 return True 744 if not full: 745 return True 746 return (lang1[1]==lang2[1]) 747 748 def __get_resource_paths(self): 749 paths=[] 750 for addon in addonHandler.getRunningAddons(): 751 if not data_addon_name_pattern.match(addon.name): 752 continue 753 for name in ["data","langdata"]: 754 data_path=os.path.join(addon.path,name) 755 if os.path.isdir(data_path): 756 paths.append(data_path.encode("UTF-8")) 757 return paths 758 759 def __init__(self): 760 self.__lib=load_tts_library() 761 self.__cancel_flag=threading.Event() 762 self.__player=audio_player(self,self.__cancel_flag) 763 self.__sample_rate_callback=sample_rate_callback(self.__lib,self.__player) 764 self.__c_sample_rate_callback=RHVoice_callback_types.set_sample_rate(self.__sample_rate_callback) 765 self.__speech_callback=speech_callback(self.__lib,self.__player,self.__cancel_flag) 766 self.__c_speech_callback=RHVoice_callback_types.play_speech(self.__speech_callback) 767 self.__mark_callback=mark_callback(self.__lib,self.__player) 768 self.__c_mark_callback=RHVoice_callback_types.process_mark(self.__mark_callback) 769 self.__done_callback=done_callback(self,self.__lib,self.__player,self.__cancel_flag) 770 self.__c_done_callback=RHVoice_callback_types.done(self.__done_callback) 771 resource_paths=self.__get_resource_paths() 772 c_resource_paths=(c_char_p*(len(resource_paths)+1))(*(resource_paths+[None])) 773 init_params=RHVoice_init_params(None, 774 config_path.encode("utf-8"), 775 c_resource_paths, 776 RHVoice_callbacks(self.__c_sample_rate_callback, 777 self.__c_speech_callback, 778 self.__c_mark_callback, 779 cast(None,RHVoice_callback_types.word_starts), 780 cast(None,RHVoice_callback_types.word_ends), 781 cast(None,RHVoice_callback_types.sentence_starts), 782 cast(None,RHVoice_callback_types.sentence_ends), 783 cast(None,RHVoice_callback_types.play_audio), 784 self.__c_done_callback), 785 0) 786 self.__tts_engine=self.__lib.RHVoice_new_tts_engine(byref(init_params)) 787 if not self.__tts_engine: 788 raise RuntimeError("RHVoice: initialization error") 789 nvda_language=languageHandler.getLanguage() 790 number_of_voices=self.__lib.RHVoice_get_number_of_voices(self.__tts_engine) 791 native_voices=self.__lib.RHVoice_get_voices(self.__tts_engine) 792 self.__voice_languages=dict() 793 self.__languages=set() 794 for i in range(number_of_voices): 795 native_voice=native_voices[i] 796 voice_language=native_voice.language.decode("utf-8") 797 if native_voice.country: 798 self.__languages.add(voice_language) 799 voice_language=voice_language+"_"+native_voice.country.decode("utf-8") 800 self.__voice_languages[native_voice.name.decode("utf-8")]=voice_language 801 self.__languages.add(voice_language) 802 self.__profile=None 803 self.__profiles=list() 804 number_of_profiles=self.__lib.RHVoice_get_number_of_voice_profiles(self.__tts_engine) 805 native_profile_names=self.__lib.RHVoice_get_voice_profiles(self.__tts_engine) 806 for i in range(number_of_profiles): 807 name=native_profile_names[i].decode("utf-8") 808 self.__profiles.append(name) 809 if (self.__profile is None) and self.__languages_match(nvda_language,self.__voice_languages[name.split("+")[0]]): 810 self.__profile=name 811 if self.__profile is None: 812 self.__profile=self.__profiles[0] 813 self.__rate=50 814 self.__pitch=50 815 self.__volume=50 816 self.__rate_boost=False 817 self.__tts_queue=Queue.Queue() 818 self.__tts_thread=TTSThread(self.__tts_queue) 819 self.__tts_thread.start() 820 log.info("Using RHVoice version {}".format(self.__lib.RHVoice_get_version())) 821 822 def terminate(self): 823 self.cancel() 824 self.__tts_queue.put(None) 825 self.__tts_thread.join() 826 self.__player.close() 827 self.__lib.RHVoice_delete_tts_engine(self.__tts_engine) 828 self.__tts_engine=None 829 830 def speak(self,speech_sequence): 831 cnv=nvda_speech_sequence_converter(self) 832 for item in speech_sequence: 833 cnv.append(item) 834 out=StringIO() 835 cnv.write(out) 836 text=out.getvalue() 837 out.close() 838 task=speak_text(self.__lib,self.__tts_engine,text,self.__cancel_flag,self.__player) 839 task.set_voice_profile(self.__profile) 840 task.set_rate(self.__rate) 841 task.set_pitch(self.__pitch) 842 task.set_volume(self.__volume) 843 task.configure_rate_boost(self.__rate_boost) 844 self.__tts_queue.put(task) 845 846 def pause(self,switch): 847 self.__player.pause(switch) 848 849 def cancel(self): 850 try: 851 while True: 852 self.__tts_queue.get_nowait() 853 except Queue.Empty: 854 self.__cancel_flag.set() 855 self.__tts_queue.put(self.__cancel_flag.clear) 856 self.__player.stop() 857 858 def _get_lastIndex(self): 859 return self.__mark_callback.index 860 861 def _get_availableVoices(self): 862 return OrderedDict((profile,synthDriverHandler.VoiceInfo(profile,profile,self.__voice_languages[profile.split("+")[0]])) for profile in self.__profiles) 863 864 def _get_language(self): 865 return self.__voice_languages[self.__profile.split("+")[0]] 866 867 def _get_rate(self): 868 return self.__rate 869 870 def _set_rate(self,rate): 871 self.__rate=max(0,min(100,rate)) 872 873 def _get_pitch(self): 874 return self.__pitch 875 876 def _set_pitch(self,pitch): 877 self.__pitch=max(0,min(100,pitch)) 878 879 def _get_volume(self): 880 return self.__volume 881 882 def _set_volume(self,volume): 883 self.__volume=max(0,min(100,volume)) 884 885 def _get_voice(self): 886 return self.__profile 887 888 def _set_voice(self,voice): 889 if voice in self.__profiles: 890 self.__profile=voice 891 892 def _get_rateBoost(self): 893 return self.__rate_boost 894 895 def _set_rateBoost(self,flag): 896 self.__rate_boost=flag 897