3

I have Apache2.4 running on a Windows Server 2019 VM. Apache is set up so I can use python in my cgi-bin scripts. I have a script that does

#!C:/Python39/python.exe

text = "This is some example text."
engine = pyttsx3.init("sapi5")
engine.save_to_file(text, "test.wav")
engine.runAndWait()

print("Content-type: audio/wav")
print()

with open("test.wav", "rb") as file:
    print(file.read())

If I call that script from the command line, it creates a 115kb file that contains the spoken version of my text. However, if I invoke that same script via a web browser (i.e., by browsing to http://whatever/cgi-bin/test.py) it instead creates a 46b file that has what appears to be a valid WAV header, but no actual data -- that is, it's created a valid empty sound file. There are no errors raised that I can see, neither written to the screen, in apache's error log, nor in Windows Logs, so far as I have been able to tell.

(The same code appears to work when running WAMP on a Windows 10 machine on my local network. I've also tried installing PHP and having Apache use that to call Python is a shell command, which makes no difference. (I don't know why I thought it would!))

How can I make this work the same under cgi/Apache as it does from the command line?


I don't know if this is useful information, but here are the versions of some of the things installed on these machines (ETA, I've tried upgrading on Windows 10 to better match Windows Server 2019):

WINDOWS 10

HTTPD: 2.4.35 Upgraded to 2.4.48, still works

PHP: PHP Version 7.2.10 (but Python is being called as CGI in my test, so this shouldn't matter) Upgraded to 8.0.6, still works

PYTHON: 3.9.2rc1 (tags/v3.9.2rc1:4064156, Feb 17 2021, 11:25:18) [MSC v.1928 64 bit (AMD64)] Upgraded to 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)], still works

.NET: Running "Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -Name version -EA 0 | Where { $_.PSChildName -Match '^(?!S)\p{L}'} | Select PSChildName, version" produces:

PSChildName                      Version
-----------                      -------
v2.0.50727                       2.0.50727.4927
v3.0                             3.0.30729.4926
Windows Communication Foundation 3.0.4506.4926
Windows Presentation Foundation  3.0.6920.4902
v3.5                             3.5.30729.4926
Client                           4.8.04084
Full                             4.8.04084
Client                           4.0.0.0

SERVER 2019

HTTPD: 2.4.48

PHP: PHP Version 8.0.9 (but Python is being called as CGI in my test, so this shouldn't matter)

PYTHON: 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]

.NET: Running "Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -Name version -EA 0 | Where { $_.PSChildName -Match '^(?!S)\p{L}'} | Select PSChildName, version" produces:

PSChildName                      Version
-----------                      -------
v2.0.50727                       2.0.50727.4927
v3.0                             3.0.30729.4926
Windows Communication Foundation 3.0.4506.4926
Windows Presentation Foundation  3.0.6920.4902
v3.5                             3.5.30729.4926
Client                           4.8.03761
Full                             4.8.03761
Client                           4.0.0.0

with debug=True, in the Apache error_log I see

Traceback (most recent call last):
  File "pyttsx3\\driver.py", line 90, in _pump
    cmd[0](*cmd[1])
  File "pyttsx3\\drivers\\sapi5.py", line 69, in save_to_file
    temp_stream = self._tts.AudioOutputStream
_ctypes.COMError: (-2147200966, None, (None, None, None, 0, None))

-2147200966 appears to be SPERR_NOT_FOUND which is "The requested data item (data key, value, etc.) was not found."


Moving to IIS from Apache didn't make a difference.

In the system settings, I tried allowing apps access to anything audio related. I used o&o ShutUp10++ to also allow everything I could. This didn't make a difference.

Still running via IIS, using procmon64, I discovered that python.exe was complaining about access to certain registry keys, and that it was running as IUSR, so I gave IUSR full control over HKLM\Software\Microsoft\Speech and Speech_OneCore and the same in HKU.DEFAULT\Software\Microsoft. I also read that I might need read permission on C:\windows\system32\config\systemprofile\appdata\roaming and/or the system profile in sysWOW64, so I gave IUSR read permission on both of those as well.

I still can't run my script. I do however seem to have changed the error message, which is now:

-2147221164, 'Class not registered', (None, None, None, 0, None))

Ben Williams
  • 6,027
  • 2
  • 30
  • 54
  • 2
    For what it's worth, I'm able to reproduce your problem on Windows Server 2019, Python 3.9.7 and Apache 2.4.48 (no PHP involved; only `cgi-bin`) -- only a 46b header is produced. And yes, on Win 10, I get the full 83.5K WAV file. My guess is that there is some problem with the way the event loop in the `SAPI.SpVoice` or `SAPI.SpFileStream` in `save_to_file` (here: https://github.com/nateshmbhat/pyttsx3/blob/master/pyttsx3/drivers/sapi5.py) interacts with the CGI handler, although I can't imagine why it's OS-dependent. I still can't pinpoint the issue, though. – Salmonstrikes Sep 04 '21 at 14:33
  • 1
    Strange indeed... Does running `pyttsx.init()` _without_ `'sapi5'` argument help? – Adam Jenča Sep 09 '21 at 18:16
  • @AdamJenča - not providing sapi5 makes no difference. – Ben Williams Sep 10 '21 at 10:25
  • Still need some help or have you sorted out the issue ? – Orsiris de Jong Sep 19 '21 at 09:28

1 Answers1

1
  1. Just a random idea here: Might be environment dependant I am saying this because producing an empty file just sounds like python cannot reach the tts core.

You could check that the %PATH% variable from cmd matches the one you'd get in your script when executed as cgi.

Obtain the %PATH% variable contents by running set in a commandline window. Obtain the path your script has by adding

import sys

print(sys.path)

Add missing paths with something like the following statement directly into your script

sys.path.append(r'C:\Python38-64\lib\site-packages\win32\lib')
  1. tts core cannot be imported properly

Try adding something like the following to your script. Depending if you see the script output when run via cgi, you might want to replace print statements with log statements.

try:
   import pyttsx3.drivers.sapi5
except ImportError as e:
   print('I could not import my driver')
else:
   print('Driver imported')

Also try to replace sapi5 driver with espeak

  1. Your python instance is not allowed to communicate with the system when run as cgi

Disable AV & firewall software for starters and try again. If nothing helps, try to see what ports are used by the python script (run as cmd) using netstat -natp | findstr /I python. From there, repeat while running as cgi and see whether those ports are opened.

  1. No other longshots

If all of the above fails, try running your cgi with IIS just to isolate whether the problem comes from apache or Windows.

[EDIT] Updates

Try running your engine with debug for more info, eg engine = pyttsx3.init("sapi5", debug=True)

Don't forget to have a basicLogger config ready for the debug to happen properly.

You can also try to swap your apache MPM in case your cgi script is not thread safe. To do so, find a file named something like 00-mpm.conf in httpd/conf.modules.d (or grep/findstr your way through by searching mpd_mpm to find the conf file)

You should have mpm_prefork or mpm_event (default in apache 2.4). Some cgi stuff doesn't work properly with mpm_event. Try commenting it out and use prefork instead. Don't forget to restart apache then. Be aware that this would render php-fpm unusable, but for the sake of tests, it will help alot.

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
#LoadModule mpm_event_module modules/mod_mpm_event.so

[/EDIT]

[EDIT 2] With the debugs, you now know that you have error -2147200966, translated into 0x8004503A, which in points in various directions being:

  • You don't have access to the TTS engine because of OS restrictions (file ACLs, confidentiality issues).

You might try to stop the apache service, change the service to have it running as system, and try again. Alternatively, you may stop the service, launch an UAC window, and try to launch apache manually from the bin path with the corresponding config file.

  • Your TTS engine lacks files

Highly improbable since your non cgi version works, but you might want to reinstall the engine + language packs to make sure all components are properly registered

  • Confidentiality problem

Check in the control panel that nothing prevents mic/phone usage. Alternatively, you might launch o&oshutup10 to see whether some privacy settings are enforced (can even be a GPO since your environment is enterprise)

[/EDIT 2]

Orsiris de Jong
  • 2,819
  • 1
  • 26
  • 48
  • %PATH% is the same whether CGI or CLI. Python reports the driver imported when run as CGI. Disabling the firewall had no effect. It doesn't seem to be a port issue, so far as I can tell. – Ben Williams Sep 10 '21 at 10:28
  • I've updated my answer with a couple more possible solutions and helpers. Post your debug results if interesting ;) – Orsiris de Jong Sep 10 '21 at 10:58
  • No, but I do seem to have an actual error now, so you are helping! I've edited the post to include the last trace. – Ben Williams Sep 10 '21 at 12:47
  • If I run this via IIS instead of Apache, the error is "(-2147024891, 'Access is denied.', (None, None, None, 0, None))" so this is clearly a more general Windows web server permissions problem (not something about Apache), but I don't understand how to give IIS (or Apache) permission for sapi5. – Ben Williams Sep 10 '21 at 15:26
  • Still an issue ? Have you played with the confidentiality settings yet ? – Orsiris de Jong Sep 13 '21 at 10:15
  • Unfortunately, no permission/setting change I have tried has made a difference. – Ben Williams Oct 07 '21 at 16:03
  • So still not working ? Can you edit your post to give the details of what you tried ? – Orsiris de Jong Oct 08 '21 at 07:26
  • Apologies for the slow responses. I've updated with a summary of changes I've tried. – Ben Williams Oct 11 '21 at 18:49
  • Just for the sake of sanity, can you run your webserver as local account ? (Or add IIS_IUSR to Users group) ? – Orsiris de Jong Oct 11 '21 at 19:06