15

I've converted my program (written in Python 3.6.1, converted using Python 3.5.3) from a .py to an .exe using Pyinstaller. However, it is incredibly slow at loading (it takes roughly 16 seconds, compared to the <1 second when running in IDLE), even after I optimised what I though the problem was (importing tons of modules, so I changed the code to only import the parts of the modules that are necessary). That sped it up a lot when running it in IDLE, but when I created an .exe out of it it was exactly the same (and I did check that I was using the right .py file). I seems like Pyinstaller just packages all modules that you have installed on your system into the .exe, instead of only the small parts of the modules that are actually being used (when using --onefile). How can I make sure that Pyinstaller only installs the necessary parts of the modules or otherwise speed it up, while still using --onefile and packaging it into a single .exe?

Full code:

from os import path, remove
from time import sleep
from sys import exit
from getpass import getuser
from mmap import mmap, ACCESS_READ


my_file = "Text To Speech.mp3"
username = getuser()
no_choices = ["no", "nah", "nay", "course not", "don't", "dont", "not"]
yes_choices = ["yes", "yeah", "course", "ye", "yea", "yh", "do"]


def check_and_remove_file():

    active = mixer.get_init()
    if active != None:
        mixer.music.stop()
        mixer.quit()
        quit()
    if path.isfile(my_file):
        remove(my_file)


def get_pause_duration(audio_length, maximum_duration=15):

    default_pause, correction = divmod(audio_length, 12)
    return min(default_pause + bool(correction), maximum_duration)


def exiting():

    check_and_remove_file()
    print("\nGoodbye!")
    exit()


def input_for_tts(message):

    try:

        tts = gTTS(text = input(message))
        tts.save('Text To Speech.mp3')
        with open(my_file) as f:
            m = mmap(f.fileno(), 0, access=ACCESS_READ)
        audio = MP3(my_file)
        audio_length = audio.info.length
        try:
            mixer.init()
        except error:
            print("\nSorry, no audio device was detected. The code cannot complete.")
            m.close()
            exiting()   
        mixer.music.load(m)
        mixer.music.play()
        sleep(audio_length + get_pause_duration(audio_length))
        m.close()
        check_and_remove_file()

    except KeyboardInterrupt:

        exiting()


from pygame import mixer, quit, error
from gtts import gTTS
from mutagen.mp3 import MP3


check_and_remove_file()


input_for_tts("Hello there " + username + ". This program is\nused to output the user's input as speech.\nPlease input something for the program to say: ")


while True:

    try:

        answer = input("\nDo you want to repeat? ").strip().lower()
        if answer in ["n", no_choices] or any(x in answer for x in no_choices):
            exiting()
        elif answer in ["y", yes_choices] or any(x in answer for x in yes_choices):
            input_for_tts("\nPlease input something for the program to say: ")
        else:
            print("\nSorry, I didn't understand that. Please try again with yes or no.")

    except KeyboardInterrupt:

        exiting()
Foxes
  • 1,137
  • 3
  • 10
  • 19

2 Answers2

15

have a look at the documentation, i guess that explains, why it is slow: https://pyinstaller.readthedocs.io/en/stable/operating-mode.html#how-the-one-file-program-works

Short answer, a complete environment for your program needs to be extracted and written to a temporary folder.

Furthermore the one-file option is in contrast to what you expected: https://pyinstaller.readthedocs.io/en/stable/operating-mode.html#bundling-to-one-file

olisch
  • 960
  • 6
  • 11
  • 6
    So there's no way to speed it up? It's much easier to distribute one file rather than a whole folder, especially to people who probably won't understand what they're looking at if they see all of the Python files in a folder. The .exe file is somewhat hard to find in the folder. – Foxes May 29 '17 at 21:27
2

Try making a virtual environment and run your project from there. Then run pyinstaller from inside the virtual environment so you only package what you need. This will do most for you

Secondly onedir option is faster than onefile since it does not have to unpack all the files from your exe into a temp folder. Pyinstaller makes it easy to use qny other installer to move it to program files and make a shortcut in start or something.

miThom
  • 373
  • 2
  • 11
  • 2
    I thought pyinstaller only packages the dependencies imported by the script, not all installed runtime modules. If it's not like that, it should be. – MKANET Apr 16 '20 at 22:25
  • i haven't used it in a long while, but in the past it did not do so, as modules can import other modules and so on. As pip does not keep a reference structure as npm or yarn or... does, this would be very error prone, as you can have conditional imports, imports not at the top of the file, and many different import functions in python. So no easy way other than to use your whole environment. Having a venv is a good idea anyways for every python project to be fair. Certainly with ducktyping, knowing all package is near impossible with python – miThom Apr 21 '20 at 08:54