nateshmbhat / pyttsx3

Offline Text To Speech synthesis for python
Mozilla Public License 2.0
2.07k stars 328 forks source link

Manually installed voice missing after compilation with PyInstaller #52

Open TypicalHog opened 5 years ago

TypicalHog commented 5 years ago

After I finally got my program to work with PyInstaller with the help of this, another issue arose. When I run the .py file there are 3 voices (2nd one has been manually installed), but when I run the .exe created by PyInstaller there are only 2 default voices (as seen in the images below).

image

This piece of code runs fine, just refuses to find the missing voice:

try:
    tts_engine = pyttsx3.init()
    for i in tts_engine.getProperty('voices'):
        if i.name not in combobox_voice['values']:
            combobox_voice['values'] += (i.name,)
except:
    listbox_logs.insert(END, f"ERROR: Couldn't get TTS voices.")
    listbox_logs.see(END)

I would greatly appreciate any suggestions that may lead to a fix or at least a better understanding of this problem.

josephalway commented 5 years ago

That code snippet doesn't work for me. I ended up doing the following:

        try:
            tts_engine = pyttsx3.init()
            temp_list = []
            for i in tts_engine.getProperty('voices'):
                if i.name not in self.combobox_voice['values']:
                    temp_list.append(i.name)
            self.combobox_voice['values'] = temp_list
        except:
            print('error')

It seems to me that you're improperly assigning the voices to the combobox.

Perhaps you could supply me with a complete example and I could be of further help.

TypicalHog commented 5 years ago

Ok, I got 3 voices when I ran this program.

import pyttsx3
from time import sleep

try:
    tts_engine = pyttsx3.init()
    for i in tts_engine.getProperty('voices'):
        print(i.name)
except:
    print('error')

sleep(60)

However, when I compiled it with PyInstaller I got only 2 voices (as I anticipated). I built it with these parameters:

pyinstaller -F test2.py --hidden-import=pyttsx3.drivers --hidden-import=pyttsx3.drivers.dummy --hidden-import=pyttsx3.drivers.espeak --hidden-import=pyttsx3.drivers.nsss --hidden-import=pyttsx3.drivers.sapi5

josephalway commented 5 years ago

Why are you using hidden imports? Those shouldn't be necessary.

I would highly recommend using a .spec file and building your program like that. Below are examples I use for a fun project I've been working on that uses pyttsx3, with generic my_app names. You won't be able to use it as a drop-in replacement for what you're doing, but it should give you something to work with.

Example my_app_build.bat file for building the executable: pyinstaller --clean --onefile --windowed my_app_build.spec

Example my_app_build.spec file referenced by my build file:

# -*- mode: python -*-

block_cipher = None

a = Analysis(['my_app.py'],
             pathex=['C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\x86'],
             binaries=[],
             datas=[('my_app_icon.ico', '.')],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          Tree('Images', prefix='Images\\'),
          Tree('Sound', prefix='Sound\\'),
          a.zipfiles,
          a.datas,
          name='name_of_my_application',
          debug=False,
          strip=False,
          upx=False,
          console=False, icon='my_app_icon.ico' )
josephalway commented 5 years ago

Hmm..., I don't specifically import pyttsx3 or it's submodules in my project, but I have apparently done that in an example or two I've run.

Why don't you try the following:

Create a pyttsx_speech_example.py file with the following code:

import pyttsx3

try:
    tts_engine = pyttsx3.init()
    for i in tts_engine.getProperty('voices'):
        print(i.name)
    input('Press enter to exit.')
except:
    print('error')

Create a pyttsx_speech_example.bat file with this command: pyinstaller --onefile --windowed pyttsx_speech_example.spec --clean

Create a pyttsx_speech_example.spec file with these parameters:

# -*- mode: python -*-
from PyInstaller.utils.hooks import collect_submodules
block_cipher = None

my_hidden_imports = collect_submodules('pyttsx3')

a = Analysis(['pyttsx_speech_example.py'],
             pathex=['C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\x64'],
             binaries=[],
             datas=[],
             hiddenimports=my_hidden_imports,
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='pyttsx_speech_example',
          debug=False,
          strip=False,
          upx=False,
          console=True)

You can remove the contents of the pathex parameter, if you don't have the Windows 10 SDK installed. Your program just won't likely run correctly on other computers.

TypicalHog commented 5 years ago

The code you posted above does work, but the 3rd voice is still not there when I run the .exe.

josephalway commented 5 years ago

Assuming, the code works, and it lists the 3rd voice correctly.

That would make me take a serious look at pyinstaller as the code works, but the compiled executable doesn't.

I would recommend reviewing this page https://pythonhosted.org/PyInstaller/hooks.html. Then post an issue here https://github.com/pyinstaller/pyinstaller/issues. In the event that someone there might be able to help you track down the issue.

josephalway commented 5 years ago

I've reviewed the code as pertaining to how it uses the SAPI driver for Windows and loads the voice(s). The SAPI5.py file in the driver folder specifies a few common voices, but those variables aren't used to load the voice(s).

You may need to create a hook, or add the additional voice as a data file and load it manually, or something else. As far as I can tell it should "just work" with the additional voice(s).

josephalway commented 5 years ago

I successfully installed the English (Great Britain) language-pack and it added the Hazel voice to my list of voices. https://support.microsoft.com/en-us/help/22805 I had to go into the language settings and choose download language pack to get it to install.

When I go to Settings->Ease of Access->Narrator->Choose a Voice I get several voices to choose from. Including George and Susan.

Interestingly enough, only Microsoft David, Zira, and Hazel show up when I run the code mentioned previously. I'm guessing, because those are the only SAPI5 voices that are installed.

When I compile the code with no special additions, etc. with pyinstaller. It lists those 3 voices.

Microsoft David Desktop - English (United States)
Microsoft Hazel Desktop - English (Great Britain)
Microsoft Zira Desktop - English (United States)
Press enter to exit.

Example that prints the above and uses the female british voice to say "This is just a test":

import pyttsx3

try:
    tts_engine = pyttsx3.init()
    voices = tts_engine.getProperty('voices')
    for voice in voices:
        print(voice.name)
    tts_engine.setProperty('voice', voices[1].id)
    tts_engine.say('This is just a test.')
    tts_engine.runAndWait()
    input('Press enter to exit.')
except:
    print('error')
TypicalHog commented 5 years ago

Maybe it has something to do with the fact the voice is from a 3rd party and not officially from Microsoft. Anyway, thank You so much for all the time you took to look into this problem. I might try to find a solution by messing with PyInstaller some more in the future.

Aviv05423 commented 3 years ago

Ok, I got 3 voices when I ran this program.

import pyttsx3
from time import sleep

try:
    tts_engine = pyttsx3.init()
    for i in tts_engine.getProperty('voices'):
        print(i.name)
except:
    print('error')

sleep(60)

However, when I compiled it with PyInstaller I got only 2 voices (as I anticipated). I built it with these parameters:

pyinstaller -F test2.py --hidden-import=pyttsx3.drivers --hidden-import=pyttsx3.drivers.dummy --hidden-import=pyttsx3.drivers.espeak --hidden-import=pyttsx3.drivers.nsss --hidden-import=pyttsx3.drivers.sapi5

thxxxxxx you are king

starflash commented 1 year ago

I successfully installed the English (Great Britain) language-pack and it added the Hazel voice to my list of voices. https://support.microsoft.com/en-us/help/22805 I had to go into the language settings and choose download language pack to get it to install.

When I go to Settings->Ease of Access->Narrator->Choose a Voice I get several voices to choose from. Including George and Susan.

Interestingly enough, only Microsoft David, Zira, and Hazel show up when I run the code mentioned previously. I'm guessing, because those are the only SAPI5 voices that are installed.

When I compile the code with no special additions, etc. with pyinstaller. It lists those 3 voices.

Microsoft David Desktop - English (United States)
Microsoft Hazel Desktop - English (Great Britain)
Microsoft Zira Desktop - English (United States)
Press enter to exit.

Example that prints the above and uses the female british voice to say "This is just a test":

import pyttsx3

try:
    tts_engine = pyttsx3.init()
    voices = tts_engine.getProperty('voices')
    for voice in voices:
        print(voice.name)
    tts_engine.setProperty('voice', voices[1].id)
    tts_engine.say('This is just a test.')
    tts_engine.runAndWait()
    input('Press enter to exit.')
except:
    print('error')

I have also encountered this problem and I found the solution

https://www.ghacks.net/2018/08/11/unlock-all-windows-10-tts-voices-system-wide-to-get-more-of-them/