From 4d90b3fcf8709ebc34ee0b157c408f00b6154e47 Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 07:06:13 +0000 Subject: [PATCH 1/6] fix for #384 nb; using locale to get correct lang codes that are understandable --- pyttsx3/drivers/sapi5.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index af07c08..9fc0e33 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -15,6 +15,7 @@ import os import time import weakref +import locale import pythoncom @@ -34,6 +35,15 @@ def buildDriver(proxy): return SAPI5Driver(proxy) +def lcid_to_locale(language_code): + primary, sub = language_code.split("-") + lcid = (int(sub) << 10) | int(primary) + try: + return locale.windows_locale[lcid].replace("_", "-") + except KeyError: + return "Unknown Locale" + + # noinspection PyPep8Naming,PyShadowingNames class SAPI5Driver: def __init__(self, proxy): @@ -88,7 +98,31 @@ def save_to_file(self, text, filename): @staticmethod def _toVoice(attr): - return Voice(attr.Id, attr.GetDescription()) + # Retrieve voice ID and description (name) + voice_id = attr.Id + voice_name = attr.GetDescription() + + # Retrieve and convert language code + language_attr = attr.GetAttribute("Language") + language_code = int(language_attr, 16) + primary_sub_code = f"{language_code & 0x3FF}-{(language_code >> 10) & 0x3FF}" + languages = [lcid_to_locale(primary_sub_code)] + + # Retrieve gender + gender_attr = attr.GetAttribute("Gender") + gender = ( + "male" if gender_attr == "1" else "female" if gender_attr == "2" else None + ) + + # Retrieve age + age_attr = attr.GetAttribute("Age") + age_map = {"1": "Child", "2": "Teen", "3": "Adult", "4": "Senior"} + age = age_map.get(age_attr, None) + + # Create and return the Voice object with additional attributes + return Voice( + id=voice_id, name=voice_name, languages=languages, gender=gender, age=age + ) def _tokenFromId(self, id_): tokens = self._tts.GetVoices() From e7c60fbd4828132b1958cd3e58fe0cf687ee8bbf Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 07:15:09 +0000 Subject: [PATCH 2/6] update to get gender correctly --- pyttsx3/drivers/sapi5.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index 9fc0e33..8e883f8 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -111,12 +111,21 @@ def _toVoice(attr): # Retrieve gender gender_attr = attr.GetAttribute("Gender") gender = ( - "male" if gender_attr == "1" else "female" if gender_attr == "2" else None + "Male" + if gender_attr.lower() == "male" + else "Female" + if gender_attr.lower() == "female" + else None ) # Retrieve age age_attr = attr.GetAttribute("Age") - age_map = {"1": "Child", "2": "Teen", "3": "Adult", "4": "Senior"} + age_map = { + "Child": "Child", + "Teen": "Teen", + "Adult": "Adult", + "Senior": "Senior", + } age = age_map.get(age_attr, None) # Create and return the Voice object with additional attributes From 476ea3006d447212d686b1e3230e2b32aa476a1b Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 08:59:08 +0000 Subject: [PATCH 3/6] make gender title case more logical Co-authored-by: Christian Clauss --- pyttsx3/drivers/sapi5.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index 8e883f8..622ff1d 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -110,13 +110,8 @@ def _toVoice(attr): # Retrieve gender gender_attr = attr.GetAttribute("Gender") - gender = ( - "Male" - if gender_attr.lower() == "male" - else "Female" - if gender_attr.lower() == "female" - else None - ) + gender_title_case = (gender or "").title() + gender = gender_title_case if gender_title_case in {"Male", "Female"} else None # Retrieve age age_attr = attr.GetAttribute("Age") From 4d5ee1a55131c27b25e399e23045c00eb88bff9a Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 13:05:42 +0000 Subject: [PATCH 4/6] Update sapi5.py Co-authored-by: Christian Clauss --- pyttsx3/drivers/sapi5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index 622ff1d..404298a 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -110,7 +110,7 @@ def _toVoice(attr): # Retrieve gender gender_attr = attr.GetAttribute("Gender") - gender_title_case = (gender or "").title() + gender_title_case = (gender_attr or "").title() gender = gender_title_case if gender_title_case in {"Male", "Female"} else None # Retrieve age From 97dc231d11e7a5c43139308a973e9b8583eb9f90 Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 13:05:53 +0000 Subject: [PATCH 5/6] Update sapi5.py Co-authored-by: Christian Clauss --- pyttsx3/drivers/sapi5.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index 404298a..079956a 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -115,13 +115,7 @@ def _toVoice(attr): # Retrieve age age_attr = attr.GetAttribute("Age") - age_map = { - "Child": "Child", - "Teen": "Teen", - "Adult": "Adult", - "Senior": "Senior", - } - age = age_map.get(age_attr, None) + age = age_attr if age_attr in {"Child", "Teen", "Adult", "Senior"} else None # Create and return the Voice object with additional attributes return Voice( From c1ad3e76a8af9b90ab1fa3acaf456c09491fea05 Mon Sep 17 00:00:00 2001 From: will wade Date: Thu, 14 Nov 2024 16:00:37 +0000 Subject: [PATCH 6/6] locale id defined Co-authored-by: Christian Clauss --- pyttsx3/drivers/sapi5.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyttsx3/drivers/sapi5.py b/pyttsx3/drivers/sapi5.py index 079956a..c93d3d5 100644 --- a/pyttsx3/drivers/sapi5.py +++ b/pyttsx3/drivers/sapi5.py @@ -37,11 +37,11 @@ def buildDriver(proxy): def lcid_to_locale(language_code): primary, sub = language_code.split("-") - lcid = (int(sub) << 10) | int(primary) + locale_id = (int(sub) << 10) | int(primary) try: - return locale.windows_locale[lcid].replace("_", "-") + return locale.windows_locale[locale_id].replace("_", "-") except KeyError: - return "Unknown Locale" + return f"Unknown Locale: {locale_id}" # noinspection PyPep8Naming,PyShadowingNames