SAPI.SpVoice won't read Japanese

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Asday
Posts: 5
Joined: 03 Aug 2021, 16:29
Contact:

SAPI.SpVoice won't read Japanese

03 Aug 2021, 16:36

I can't tell if it's something to do with not sending unicode data to SpVoice or if I need to set SpVoice to Japanese or something, but any Japanese text I send to .Speak() is just silence.

Code: Select all

ComObjCreate("SAPI.SpVoice").Speak("新キャラ")
Says nothing but returns successfully.

Code: Select all

ComObjCreate("SAPI.SpVoice").Speak("before 新キャラ after")
Says "before after" as if there was nothing in the middle at all.

I'm on Windows 10 with the Japanese language pack installed and the default voice set to one of the Japanese ones, and AutoHotKey 1.1.30.00.
AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: SAPI.SpVoice won't read Japanese

03 Aug 2021, 19:12

Does your windows speak Japanese outside of AHK if you use the windows text to speech?
gregster
Posts: 9014
Joined: 30 Sep 2013, 06:48

Re: SAPI.SpVoice won't read Japanese

26 Jul 2022, 15:24

Did you save your script in UTF-8 with BOM encoding? If not, you should try.
Asday
Posts: 5
Joined: 03 Aug 2021, 16:29
Contact:

Re: SAPI.SpVoice won't read Japanese

26 Jul 2022, 15:29

That's not an option in notepad. I have ANSI, Unicode, Unicode big endian, and UTF-8, the latter I have selected.

Out of curiosity I resaved it with "Unicode" and reloaded the script, tried the hotkey again and it said "before after" without the Japanese.

I've found https://www.perlmonks.org/?node_id=11109244 in the meantime which appears to have the same issue, but as it's not in AHK I have no clue how to set (what appears to be) the OLE codepage.
gregster
Posts: 9014
Joined: 30 Sep 2013, 06:48

Re: SAPI.SpVoice won't read Japanese

26 Jul 2022, 15:34

UTF-8 - without BOM - is the wrong one. That can't work in AHK v1, afaik.

You can save in UTF-8 with BOM (BOM = byte order mark) encoding in English language Notepad, I am pretty sure. At least we have it in german Windows 10:

utf8 bom.png
utf8 bom.png (4.8 KiB) Viewed 1149 times

But most editors will offer this encoding as well... you are not limited to Notepad.

Also see https://www.autohotkey.com/docs/FAQ.htm#nonascii
Asday
Posts: 5
Joined: 03 Aug 2021, 16:29
Contact:

Re: SAPI.SpVoice won't read Japanese

26 Jul 2022, 15:38

It's not "without BOM" it's just "UTF-8".

I opened the script in Notepad++ and checked the encoding and it's "UTF-8-BOM", so no issues there. Makes sense to me anyway seeing as when I reload the file I don't have mojibake.
gregster
Posts: 9014
Joined: 30 Sep 2013, 06:48

Re: SAPI.SpVoice won't read Japanese

26 Jul 2022, 15:47

Perhaps the wrong voice is selected. Try to loop through the available voices, and see if one works:
viewtopic.php?p=431416#p430292
kairushinjuu
Posts: 46
Joined: 07 May 2020, 07:02

Re: SAPI.SpVoice won't read Japanese

27 Jul 2022, 02:27

as gregster suggested, because your computer isn't in Japanese the default won't allow Japanese.

what you can do is something like this

Code: Select all

	a := ComObjCreate("SAPI.SpVoice")
	Loop % a.GetVoices().count()
		msgbox % a.GetVoices().Item(A_index).GetDescription  ; use this to find which number Japanese is assigned on your computer, for me it was three. You are looking for "haruka"
once you've found what number is Japanese on your system.

Code: Select all

	objSp := ComObjCreate("SAPI.SpVoice")
	objSp.voice := objSp.GetVoices().Item(3)
	objSp.Speak("庭には二羽ニワトリがいる。")
Asday
Posts: 5
Joined: 03 Aug 2021, 16:29
Contact:

Re: SAPI.SpVoice won't read Japanese

28 Jul 2022, 11:33

Good example code thanks.

Bizarrely, AHK's returned voices are completely different to what I have installed on my machine. Opening the "Speech Settings" control panel window shows George, Susan, Hazel, Ayumi, Haruka, and Ichiro. After touching up the code, AHK reports "Microsoft Hazel Desktop - English (Great Britain)", "Microsoft Zira Desktop - English (United States)", and "Microsoft Haruka Desktop - Japanese". This is a bit sad because I have not a single use for the English ones, actively detest that even the US keyboard keeps reinstalling itself on my machine, and prefer Ichiro's voice to Haruka's. Also to note, my default voice in those settings is currently Ayumi, so nothing makes any sense.

Code: Select all

F2::
spVoice:=ComObjCreate("SAPI.SpVoice")
voices:=spVoice.GetVoices()
for voice in voices {
	description:=voice.GetDescription
	MsgBox %description%
}
return
Whatever.

Final code to TTS the clipboard:

Code: Select all

F1::
spVoice:=ComObjCreate("SAPI.SpVoice")
voices:=spVoice.GetVoices()
voiceSet=0
for voice in voices {
	description:=voice.GetDescription
	if (InStr(description, "Japanese")) {
		spVoice.voice:=voice
		voiceSet=1
		break
	}
}
if (voiceSet) {
	spVoice.Speak(clipboard)
} else {
	MsgBox No Japanese TTS support.
}
return
Future improvements: keep the SpVoice object around instead of discarding and recreating it; figure out why I can't select the voice I want; add a hotkey to stop it talking if I've had enough; detect/split the clipboard contents into languages to feed to different voices/the most appropriate voice. Not motivated enough to do it now.

Thanks for your help. This will make keeping up with my friends on twitter a lot easier, as - clearly - I have an adult literacy problem.
garry
Posts: 3770
Joined: 22 Dec 2013, 12:50

Re: SAPI.SpVoice won't read Japanese

28 Jul 2022, 13:37

see also scripts from user @teadrinker , here I tried to add a GUI

Code: Select all

;-------- saved at 星期一 七月 2020-07-27  11:55 UTC --------------
;- Using Google Translate to automate text translation 
;- https://www.autohotkey.com/boards/viewtopic.php?f=6&t=63835
;- https://www.autohotkey.com/boards/viewtopic.php?f=76&t=71993 ---
;- translator from user teadrinker / with GUI 
/*
modified=20210123  modified language https://cloud.google.com/translate/docs/languages
modified=20200726  added AUDIO
modified=20200329  ( teadrinker ) edited 2 times in total.
modified=20200130  translate clipboard again in other language with dropdownlist (ddl1)
modified=20191019  ( teadrinker ) edited 1 time in total. 
modified=20190422  EDIT CreateScriptObj() ( teadrinker )
created =20190419
-select language , copy marked text ctrl+c > see translation in selected language
-click AUDIO button to hear the text
*/
;-------------------------------------------------------------------------------
#NoEnv
;#Warn
setworkingdir,%a_scriptdir%
tl1:=""
Gui,1:default
Gui,1: +AlwaysOnTop  
Gui,1: -DPIScale
SS_REALSIZECONTROL := 0x40
wa:=a_screenwidth,ha:=a_screenheight,xx:=100
clipboard=
cl=
transform,s,chr,32
gosub,language
SplitPath,a_scriptname, name, dir, ext, name_no_ext, drive
rssini=%a_scriptdir%\%name_no_ext%.ini
ifnotexist,%rssini%    ;- first run
    {
    translateto=ja
    IniWrite,%translateto%, %rssini% ,Lang1  ,key1
    }
Gui,1:Color,Black,Black
Gui,1:Font,s14 cYellow ,Lucida Console 
IniRead, tl1, %rssini%,Lang1 ,key1
global tl1,JS,txt22,h1
x:=(wa*.5)/xx,W :=(wa*18)/xx,H :=(ha*10)/xx,y:=(ha*.5)/xx
Gui,add,dropdownlist, x%x% y%y% w%w% vDDL1 gddl2 ,%e5x%
x:=(wa*20)/xx,W :=(wa*8)/xx,H :=(ha*2.4)/xx,y:=(ha*.5)/xx
Gui,add,button,x%x% y%y%  w%w% h%h% gAudio,AUDIO
x:=(wa*.5)/xx,W :=(wa*29)/xx , H :=(ha*88)/xx , y:=(ha*3.5)/xx
Gui,add,edit,x%x% y%y%  w%w% h%h% vED1  -border -E0x200,
W :=(wa*30)/xx , H :=(ha*91)/xx  , x:=(wa-w),y:=(ha*2)/xx
Gui, Show,x%x% y%y% w%w% h%h% ,TRANSLATE
GuiControl,1:Choose,ddl1,%tl1%
GuiControl, Focus,ED1
WinID := WinExist("A")
WinSetTitle, ahk_id %WinID%,, TRANSLATE_to_%tl1%     TEADRINKER
E0x200 = WS_EX_CLIENTEDGE
RETURN
;--------------------------
esc::exitapp
;--------------------------
Guiclose:
cl=
clipboard=
exitapp
;-----  CTRL+C Clipboardchange ---------------------
OnClipboardChange:
If (A_EventInfo=1)
 {
 Gui, Show,
 GuiControl, Focus,ED1
 ClipWait,
 if (!ErrorLevel)
  {
  cl:=clipboard
  aa:=GoogleTranslate(cl)
  ControlSetText,edit1,%aa%, ahk_class AutoHotkeyGUI
  txt22:=aa
  aa=
  GuiControl, Focus,ED1
  }
 }
return
;--------------------------
;--------------------------
ddl2:
Gui,1:submit,nohide
h1:=""
h2:=""
if DDL1<>
{
StringSplit,h,ddl1,`_
if h1<>
  {
  IniWrite,%h1%, %rssini% ,Lang1  ,key1
  tl1:=h1
  WinSetTitle, ahk_id %WinID%,, TRANSLATE_to_%h2%     TEADRINKER
  gosub,translateddlchange
  }
}
return
;----------------------------------------
;------- translate changed language -----
translateddlchange:
Guicontrolget,ed1
if ed1<>
{
aa:=GoogleTranslate(cl)      ;- translate clipboard again in other language
ControlSetText,edit1,%aa%, ahk_class AutoHotkeyGUI
txt22:=aa
aa=
}
return
;---------------------------------------


;====================== TRANSLATE user teadrinker =============================
;-------- saved at 星期日 七月 2020-07-26  19:57 UTC --------------
;- Using Google Translate to automate text translation 
;- https://www.autohotkey.com/boards/viewtopic.php?f=6&t=63835
;MsgBox, % GoogleTranslate("今日の天気はとても良いです")
;MsgBox, % GoogleTranslate("Hello, World!", "en", "ru")
GoogleTranslate(str, from := "auto", to := "en")  {
   trans:=""
   static JS := CreateScriptObj(), _ := JS.( GetJScript() ) := JS.("delete ActiveXObject; delete GetObject;")
   
   json := SendRequest(JS, str, to, from, proxy := "")
   oJSON := JS.("(" . json . ")")
   if !IsObject(oJSON[1])  {
      Loop % oJSON[0].length
         trans .= oJSON[0][A_Index - 1][0]
   }
   else  {
      MainTransText := oJSON[0][0][0]
      Loop % oJSON[1].length  {
         trans .= "`n+"
         obj := oJSON[1][A_Index-1][1]
         Loop % obj.length  {
            txt := obj[A_Index - 1]
            trans .= (MainTransText = txt ? "" : "`n" txt)
         }
      }
   }
   if !IsObject(oJSON[1])
      MainTransText := trans := Trim(trans, ",+`n ")
   else
      trans := MainTransText . "`n+`n" . Trim(trans, ",+`n ")
   from := oJSON[2]
   trans := Trim(trans, ",+`n ")
   Return trans
}
SendRequest(JS, str, tl, sl, proxy) {
   static http
   ComObjError(false)
   if !http
   {
      http := ComObjCreate("WinHttp.WinHttpRequest.5.1")
      ( proxy && http.SetProxy(2, proxy) )
      http.open( "get", "https://translate.google.com", 1 )
      http.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0")
      http.send()
      http.WaitForResponse(-1)
   }
   http.open( "POST", "https://translate.google.com/translate_a/single?client=webapp&sl="
      . sl . "&tl=" . tl1 . "&hl=" . tl1
      . "&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&ie=UTF-8&oe=UTF-8&otf=0&ssel=0&tsel=0&pc=1&kc=1"
      . "&tk=" . JS.("tk").(str), 1 )
   http.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
   http.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0")
   http.send("q=" . URIEncode(str))
   http.WaitForResponse(-1)
   Return http.responsetext
}
URIEncode(str, encoding := "UTF-8")  {
   urlstr:=""
   VarSetCapacity(var, StrPut(str, encoding))
   StrPut(str, &var, encoding)
   While code := NumGet(Var, A_Index - 1, "UChar")  {
      bool := (code > 0x7F || code < 0x30 || code = 0x3D)
      UrlStr .= bool ? "%" . Format("{:02X}", code) : Chr(code)
   }
   Return UrlStr
}
GetJScript()
{
   script =
   (
      var TKK = ((function() {
        var a = 561666268;
        var b = 1526272306;
        return 406398 + '.' + (a + b);
      })());
      function b(a, b) {
        for (var d = 0; d < b.length - 2; d += 3) {
            var c = b.charAt(d + 2),
                c = "a" <= c ? c.charCodeAt(0) - 87 : Number(c),
                c = "+" == b.charAt(d + 1) ? a >>> c : a << c;
            a = "+" == b.charAt(d) ? a + c & 4294967295 : a ^ c
        }
        return a
      }
      function tk(a) {
          for (var e = TKK.split("."), h = Number(e[0]) || 0, g = [], d = 0, f = 0; f < a.length; f++) {
              var c = a.charCodeAt(f);
              128 > c ? g[d++] = c : (2048 > c ? g[d++] = c >> 6 | 192 : (55296 == (c & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ?
              (c = 65536 + ((c & 1023) << 10) + (a.charCodeAt(++f) & 1023), g[d++] = c >> 18 | 240,
              g[d++] = c >> 12 & 63 | 128) : g[d++] = c >> 12 | 224, g[d++] = c >> 6 & 63 | 128), g[d++] = c & 63 | 128)
          }
          a = h;
          for (d = 0; d < g.length; d++) a += g[d], a = b(a, "+-a^+6");
          a = b(a, "+-3^+b+-f");
          a ^= Number(e[1]) || 0;
          0 > a && (a = (a & 2147483647) + 2147483648);
          a `%= 1E6;
          return a.toString() + "." + (a ^ h)
      }
   )
   Return script
}
CreateScriptObj() {
   static doc
   doc := ComObjCreate("htmlfile")
   doc.write("<meta http-equiv='X-UA-Compatible' content='IE=9'>")
   Return ObjBindMethod(doc.parentWindow, "eval")
}
;============================================================================




;=================================   AUDIO ==================================
AUDIO:
Gui,submit,nohide
global aa2,TL1,text
F1 := a_desktop . "\test.mp3"
tl1=%tl1%
;TL1:="de"
;text:="Good evening , how are you ?"
text:=txt22
;msgbox, 262208, ,LNG=`n%lng%`n---------------------`nTEXT=`n%text%
;return
aa2:= GetAudioFromGoogle(text, TL1, F1)
if (aa2<>1)
  {
  msgbox, 262208, ,AA2=%aa2%`nLanguage = "%Tl1%"
  return
  }
soundplay,%f1%
filedelete,%f1%
return
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
GetAudioFromGoogle(text,TL1, mp3filePath)  {
   url := CreateUrl(text,TL1)
   data := sendrequest2(url)
   if SubStr(data, 1, 6) = "Error!"
      Return data
   
   Return SaveDataToFile(data, mp3filePath)
}
CreateUrl(text, TL1)  {
   JS := CreateScriptObj2(), JS.( GetJscript2() )
   url := "https://translate.google.ru/translate_tts?ie=UTF-8&tl="
         . TL1 . "&total=1&idx=0&client=t&prev=input&textlen="
         . StrLen(text) . "&tk=" . JS.("tk").(text) . "&q=" . uriencode2(text)
   Return url
}
sendrequest2(url) {
   whr := ComObjCreate("Msxml2.XMLHTTP.6.0")
   whr.Open("GET", url, false)
   whr.Send()
   
   if (whr.Status != 200)
      Return "Error! Status: " . whr.Status . "`n`n" . whr.responseBody
   Return whr.responseBody
}
SaveDataToFile(data, filePath) {
   stream := ComObjCreate("ADODB.Stream")
   stream.type := 1  ; Binary data
   stream.Open
   stream.Write(data)
   stream.SaveToFile(filePath, 2)
   stream.Close
   Return true
}
;=====================================================================================
GetJscript2()
{
   script =
   (
      var TKK = ((function() {
        var a = 561666268;
        var b = 1526272306;
        return 406398 + '.' + (a + b);
      })());
      function b(a, b) {
        for (var d = 0; d < b.length - 2; d += 3) {
            var c = b.charAt(d + 2),
                c = "a" <= c ? c.charCodeAt(0) - 87 : Number(c),
                c = "+" == b.charAt(d + 1) ? a >>> c : a << c;
            a = "+" == b.charAt(d) ? a + c & 4294967295 : a ^ c
        }
        return a
      }
      function tk(a) {
          for (var e = TKK.split("."), h = Number(e[0]) || 0, g = [], d = 0, f = 0; f < a.length; f++) {
              var c = a.charCodeAt(f);
              128 > c ? g[d++] = c : (2048 > c ? g[d++] = c >> 6 | 192 : (55296 == (c & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ?
              (c = 65536 + ((c & 1023) << 10) + (a.charCodeAt(++f) & 1023), g[d++] = c >> 18 | 240,
              g[d++] = c >> 12 & 63 | 128) : g[d++] = c >> 12 | 224, g[d++] = c >> 6 & 63 | 128), g[d++] = c & 63 | 128)
          }
          a = h;
          for (d = 0; d < g.length; d++) a += g[d], a = b(a, "+-a^+6");
          a = b(a, "+-3^+b+-f");
          a ^= Number(e[1]) || 0;
          0 > a && (a = (a & 2147483647) + 2147483648);
          a `%= 1E6;
          return a.toString() + "." + (a ^ h)
      }
   )
   Return script
}
UriEncode2(str, encoding := "UTF-8")  {
   urlstr:=""
   VarSetCapacity(var, StrPut(str, encoding))
   StrPut(str, &var, encoding)
   While code := NumGet(Var, A_Index - 1, "UChar")  {
      bool := (code > 0x7F || code < 0x30 || code = 0x3D)
      UrlStr .= bool ? "%" . Format("{:02X}", code) : Chr(code)
   }
   Return UrlStr
}
CreateScriptObj2() {
   static doc
   doc := ComObjCreate("htmlfile")
   doc.write("<meta http-equiv='X-UA-Compatible' content='IE=9'>")
   Return ObjBindMethod(doc.parentWindow, "eval")
}
;====================== END AUDIO ========================================



;====================== LANGUAGEs ========================================
;-- some examples to select 
;-https://cloud.google.com/translate/docs/languages
language:
e5x:=""
e5x=
(Ltrim join|
af_Afrikaans
sq_Albania
am_Amharisch
ar_Arab
hy_Armenia
az_Aserbaijan
eu_Baskisch
be_Belarus
bn_Bengal
bs_Bosnia
bg_Bulgaria
ca_Catalan
ceb_Cebuano (ISO-639-2)
zh-CN_China or zh (BCP-47)
zh-TW_China trad (BCP-47)
co_Corsica
hr_Croatia
cs_Cesko
da_Danmark
nl_Nederland
en_English
eo_Esperanto
et_Estonia
fi_Finland Suomi
fr_France
fy_Fryslan
gl_Galicia
ka_Georgia
de_German
el_Greece Elliniki
gu_Gujarati
ht_Creol Haiti
ha_Haussa
haw_Hawaii(ISO-639-2)
he_Hebräisch
iw_Hebräisch
hi_Hindi
hmn_Hmong (ISO-639-2)
hu_Hungarian Magyar
is_Iceland
ig_Igbo
id_Indonesia
ga_Irland Eire
it_Italia
ja_Japan Nippon
jv_Java
kn_Kannada
kk_Kasach
km_Khmer
rw_Kinyarwanda
ko_Korea
ku_Kurdistan
ky_Kirgisian
lo_Lao
la_Latin
lv_Lettland Latvija
lt_Litvanija Lietuva
lb_Luxemburg
mk_Mazedonia
mg_Madagascar
ms_Malaia
ml_Malayalam
mt_Malta
mi_Maori
mr_Marathi
mn_Mongolia
my_Myanmar
ne_Nepal
no_Norge
ny_Nyanja (Chichewa)
or_Odia (Oriya)
ps_Pashtun
fa_Persia
pl_Poland
pt_Portugal Brasilia
pa_Pandschabi
ro_Romania
ru_Rossija
sm_Samoa
gd_Scotia gael
sr_Serbia
st_Sesotho
sn_Shona
sd_Sindhi
si_Singhal
sk_Slovakia
sl_Slovenia
so_Somalia
es_Espana
su_Sundanesisch
sw_Swahili
sv_Sverige
tl_Tagalog (Philippines)
tg_Tadschikistan
ta_Tamil
tt_Tatar
te_Telugu
th_Thai
tr_Turkiye
tk_Turkmenistan
uk_Ukraina
ur_Urdu
ug_Uigur
uz_Uzbekistan
vi_Vietnam
cy_Wales GB
xh_Xhosa
yi_Jiddisch
yo_Yoruba
zu_Zulu
%s%
%s%
)
return
;====================== END SCRIPT ==================================================

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: mikeyww and 185 guests