Skip to content

Commit f3a529b

Browse files
committed
Adding Sanskrit support
- Adding support to Sanskrit in the addon and the dll - Correcting Unicode data for Sinhala to correct consonant cluster recognition - Adding fix to delete duplicate voices for single language
1 parent 33693e4 commit f3a529b

File tree

6 files changed

+103
-27
lines changed

6 files changed

+103
-27
lines changed

globalPlugins/hear2readng_global_plugin/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def on_voice_update(self, lang):
209209
def _startup(self):
210210

211211
if _h2r_config[SCT_General][ID_ShowStartupPopup]:
212-
log.info("_start_checks: showNewUserMessage")
212+
# log.info("_start_checks: showNewUserMessage")
213213
startupdialog = _StartupInfoDialog()
214214
gui.runScriptModalDialog(startupdialog,
215215
callback=self._on_startupinfo_closed)
@@ -220,7 +220,7 @@ def _on_startupinfo_closed(self, res):
220220
self._perform_checks()
221221

222222
def _perform_checks(self):
223-
log.info("_perform_checks")
223+
# log.info("_perform_checks")
224224
postUpdateCheck()
225225
self._perform_voice_check()
226226

@@ -312,7 +312,7 @@ def script_h2r_review_currentCharacter(self, gesture: inputCore.InputGesture):
312312

313313
info.expand(textInfos.UNIT_CHARACTER)
314314
scriptCount = scriptHandler.getLastScriptRepeatCount()
315-
log.info(f"script_review_currentCharacter interrupt: {scriptCount}, info: {info.text}")
315+
# log.info(f"script_review_currentCharacter interrupt: {scriptCount}, info: {info.text}")
316316

317317
if scriptCount == 1:
318318
try:

globalPlugins/hear2readng_global_plugin/h2rutils.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import urllib.request
99
from dataclasses import dataclass
10+
from glob import glob
1011
from io import StringIO
1112
from threading import Thread
1213

@@ -53,6 +54,7 @@
5354
"ne":"Nepali",
5455
"or":"Odia",
5556
"pa":"Punjabi",
57+
"sa":"Sanskrit",
5658
"si":"Sinhala",
5759
"ta":"Tamil",
5860
"te":"Telugu",
@@ -376,10 +378,16 @@ def parse_server_voices(resp_str):
376378

377379

378380
def populateVoices():
379-
pathName = os.path.join(H2RNG_VOICES_DIR)
381+
"""Checks and populates voice list based on the files present in the voice
382+
directory
383+
384+
@return: Dictionary of voice files keyed by the iso2 code of the language
385+
@rtype: dict
386+
"""
387+
remove_duplicate_voices()
380388
voices = dict()
381389
#list all files in Language directory
382-
file_list = os.listdir(pathName)
390+
file_list = os.listdir(H2RNG_VOICES_DIR)
383391
#FIXME: the english voice is obsolete, maybe remove the voiceid?
384392
en_voice = EN_VOICE_ALOK
385393
voices[en_voice] = "English"
@@ -403,6 +411,29 @@ def populateVoices():
403411

404412
return voices
405413

414+
def remove_duplicate_voices():
415+
"""Ensures only one voice per language is present. Retains only the last
416+
voice file when sorted alphabetically
417+
"""
418+
file_list = glob("*.onnx", root_dir=H2RNG_VOICES_DIR)
419+
iso2_set = set()
420+
for file in file_list:
421+
iso2 = file.split("-")[0]
422+
iso2_set.add(iso2)
423+
424+
# log.info(f"Hear2Read NG: got lang list: {iso2_set}")
425+
426+
for iso2 in iso2_set:
427+
lang_voices = sorted(glob(f"{iso2}*.onnx", root_dir=H2RNG_VOICES_DIR))
428+
if len(lang_voices) > 1:
429+
for f in lang_voices[:-1]:
430+
json_file = f + ".json"
431+
log.warn(f"Hear2Read NG: Found duplicate voice, deleting: {f}")
432+
os.remove(os.path.join(H2RNG_VOICES_DIR, f))
433+
if os.path.exists(os.path.join(H2RNG_VOICES_DIR, json_file)):
434+
os.remove(os.path.join(H2RNG_VOICES_DIR, json_file))
435+
436+
406437
def move_old_voices():
407438
"""Tries to move voices downloaded in addon version 1.4 and lower to the
408439
new dir structure to be usable by this addon. This is slightly different

globalPlugins/hear2readng_global_plugin/voice_manager.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import gui
1717
import synthDriverHandler
1818
import wx
19+
from addonHandler import getCodeAddon
1920
from logHandler import log
2021

2122
from synthDrivers._H2R_NG_Speak import H2RNG_DATA_DIR, H2RNG_VOICES_DIR
@@ -109,6 +110,16 @@ def __init__(self, parent=gui.mainFrame, title="Hear2Read Indic Voice Manager"):
109110
# progress dialog to show download progress
110111
self.progress_dialog = None
111112

113+
try:
114+
version = getCodeAddon().manifest.version
115+
except:
116+
log.warn("Hear2Read NG: Unable to read manifest, assuming default version number")
117+
version = "1.7.3"
118+
119+
version_split = version.split(".")
120+
self.major_version = version_split[0]
121+
self.minor_version = version_split[1]
122+
112123
self.get_display_voices()
113124

114125
if not self.display_voices:
@@ -621,6 +632,13 @@ def get_display_voices(self):
621632

622633
for key in set(self.installed_voices.keys()).union(
623634
self.server_voices.keys()):
635+
636+
# TODO: this is redundant now as it will be updated post fact. will
637+
# need a file on the server informing this. Maybe move voices to a
638+
# new location to prevent access by old versions?
639+
if key == "sa" and self.major_version > 0 and self.minor_version > 7:
640+
continue
641+
624642
local_voice = self.installed_voices.get(key)
625643
server_voice = self.server_voices.get(key)
626644

manifest.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name = "Hear2ReadNG"
22
summary = "Hear2Read Indic Speech Synthesizer"
3-
version = "1.7.3"
3+
version = "1.8.0"
44
description = "This is a speech synthesizer for 11 Indic languages and English (with Indian accent) that generates natural human speech. It is based on the work done by the piper TTS team (https://github.com/rhasspy/piper). The addon includes a voice manager for installing one or more Indic voices. Supported languages: Assamese, Bengali, Gujarati, Hindi, Kannada, Malayalam, Nepali, Odia, Punjabi, Tamil, Telugu"
55
author = "Hear2Read Contributers<info@Hear2Read.org>"
66
url = "https://hear2read.org"
77
docFileName = "README.txt"
88
minimumNVDAVersion = "2022.1.0"
9-
lastTestedNVDAVersion = "2025.1"
9+
lastTestedNVDAVersion = "2025.3"

synthDrivers/Hear2ReadNG.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from synthDriverHandler import (
3131
SynthDriver,
3232
VoiceInfo,
33+
getSynth,
3334
synthDoneSpeaking,
3435
synthIndexReached,
3536
)
@@ -89,7 +90,7 @@ class SynthDriver(SynthDriver):
8990
# SynthDriver.VariantSetting(),
9091
SynthDriver.RateSetting(),
9192
# SynthDriver.RateBoostSetting(),
92-
# SynthDriver.PitchSetting(),
93+
# SynthDriver.PitchSetting(),
9394
# SynthDriver.InflectionSetting(),
9495
SynthDriver.VolumeSetting(),
9596
)
@@ -124,27 +125,26 @@ def __init__(self):
124125
if not self.check():
125126
return
126127
log.info("H2R NG: init started")
127-
# confspec = {
128-
# "engSynth": "string(default='oneCore')",
129-
# "engVoice": "string(default='')",
130-
# "engVariant": "string(default='')",
131-
# "engRate": "integer(default=50)",
132-
# "engPitch": "integer(default=50)",
133-
# "engVolume": "integer(default=100)",
134-
# "engInflection": "integer(default=80)",
135-
# "showStartupMsg": "boolean(default=True)"
136-
# }
137-
138-
# config.conf.spec["hear2read"] = confspec
139-
# config.conf.save()
140128

141129
# Have H2R pitch be set to the engsynth value to allow PitchCommand
142130
# to be used for capitals
131+
confspec_default = {
132+
"voice": f"string(default='{_H2R_NG_Speak.en_voice}')",
133+
"rate": "integer(default=50)",
134+
"pitch": "integer(default=50)",
135+
"volume": "integer(default=100)",
136+
"capPitchChange": "integer(default=30)",
137+
}
138+
config.conf.spec["speech"][self.name] = confspec_default
139+
config.conf.save()
140+
143141
try:
144-
config.conf["speech"][self.name]["pitch"] = _h2r_config[SCT_EngSynth][ID_EnglishSynthPitch]
142+
tempPitch = _h2r_config[SCT_EngSynth][ID_EnglishSynthPitch]
143+
# log.info(f"H2R NG got eng pitch: {tempPitch}, {type(tempPitch)}")
144+
config.conf["speech"][self.name]["pitch"] = int(tempPitch)#_h2r_config[SCT_EngSynth][ID_EnglishSynthPitch]
145145
except KeyError as e:
146146
if self.name in str(e):
147-
log.warn("Hear2Read no config found, updating default config")
147+
log.warn("Hear2Read no config found, retrying default config")
148148
confspec_default = {
149149
"voice": f"string(default='{_H2R_NG_Speak.en_voice}')",
150150
"rate": "integer(default=50)",
@@ -154,6 +154,12 @@ def __init__(self):
154154
}
155155
config.conf.spec["speech"][self.name] = confspec_default
156156
config.conf.save()
157+
158+
h2rpitch = config.conf["speech"][self.name]["pitch"]
159+
160+
# log.info(f"Hear2ReadNG: set pitch to: {h2rpitch}, {type(h2rpitch)}")
161+
162+
config.conf.save()
157163

158164
_H2R_NG_Speak.initialize(self._onIndexReached)
159165

@@ -206,6 +212,7 @@ def speak(self, speechSequence: SpeechSequence):
206212
# log.info("H2R speak")
207213
# log.info(f"speech sequence: {speechSequence}")
208214
self.subsequences = []
215+
self.first_subseq = True
209216
if self.is_curr_voice_eng() or not self._get_voice():
210217
# self.subsequences.append(speechSequence)
211218
_H2R_NG_Speak.speak_eng(speech_sequence=speechSequence)
@@ -282,8 +289,12 @@ def speak(self, speechSequence: SpeechSequence):
282289
subSequence.append(item)
283290
# pass
284291
elif isinstance(item, PitchCommand):
285-
# log.info(f"Hear2Read got PitchCommand: {PitchCommand}")
292+
# log.info(f"Hear2Read got PitchCommand: {item}")
286293
subSequence.append(item)
294+
# synth = getSynth()
295+
# synthConf = config.conf["speech"][synth.name]
296+
# h2rpitch = synthConf["pitch"]
297+
# log.info(f"Hear2ReadNG: current pitch at: {h2rpitch}, {type(h2rpitch)}")
287298
# pass
288299
elif isinstance(item, VolumeCommand):
289300
subSequence.append(item)
@@ -389,6 +400,12 @@ def _processSubSequences(self):
389400
# log.info(f"_processSubSequences: isASCII: {isASCII}")
390401
# log.info(f"_processSubsequence: subsequence {subSequence}")
391402

403+
# Play a short silence while switching from Indian language to English
404+
if not self.first_subseq and isASCII:
405+
_H2R_NG_Speak.speak_silence(500)
406+
407+
self.first_subseq = False
408+
392409
if isinstance(subSequence[-1], IndexCommand):
393410
self.currIndex = subSequence[-1].index
394411
# log.info(f"index boundary at: {self.currIndex}")
@@ -403,7 +420,7 @@ def _process_non_native_unicode(self, text):
403420
split_texts = []
404421
prev_range = ()
405422
text_bit = ""
406-
has_curr_lang = False
423+
# has_curr_lang = False
407424
is_prev_valid_lang = False
408425

409426
# log.info(f"_process_non_native_unicode: {text}")
@@ -413,7 +430,7 @@ def _process_non_native_unicode(self, text):
413430
if self._script_range[0] <= ord(c) <=self._script_range[1] or c in "।॥":
414431
# log.info(f"adding: {c}")
415432
text_bit += c
416-
has_curr_lang = True
433+
# has_curr_lang = True
417434
is_prev_valid_lang = True
418435
continue
419436

@@ -650,7 +667,7 @@ def _set_script_range(self):
650667
self._script_range = unicode_ranges["english"]
651668
return
652669

653-
if lang_iso in ["hi", "mr", "ne"]:
670+
if lang_iso in ["hi", "mr", "ne", "sa"]:
654671
self._script_range = unicode_ranges["devanagari"]
655672
elif lang_iso in ["as", "bn"]:
656673
self._script_range = unicode_ranges["bengali"]

synthDrivers/_H2R_NG_Speak.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,16 @@ def speak(text, params):
352352
_execWhenDone(_speak, text.replace("।", "."), params, mustBeAsync=True)
353353
return
354354

355+
def speak_silence(time: int):
356+
"""Speaks silence. Used for break between Indic and English text
357+
358+
@param time: break time in milliseconds
359+
@type time: int
360+
"""
361+
one_sec = qual_to_hz[curr_qual]
362+
# log.info(f"H2R playing silence frames: {int(one_sec * time/1000)}")
363+
player.feed(bytes(int(one_sec * time/1000)))
364+
355365
def stop():
356366
global isSpeaking
357367
# Kill all speech from now.

0 commit comments

Comments
 (0)