Pygame not loading png after making .exe with Pyinstaller
Asked Answered
B

3

11

I've been trying to make an .exe from my .py game and it's been really frustrating.

I'm using Python 3.5.2, Pygame 1.9.2 and Pyinstaller 3.2.

The game is running perfectly as .py, but after I enter pyinstaller --debug game.py, the .exe is built and I run it, I get the following:

debug screen

These are the lines of code from game.py that may be related to the error:

from os import path
img_dir = path.join(path.dirname(__file__), 'sprites')
title_screen = pygame.image.load(path.join(img_dir, 'title_screen.png'))

I think it must have something to do with pyinstaler not being able to get my sprites folder, because when I try to run pyinstaller --icon=/sprites/icon.ico game.py, I get this:

icon error screen

But if I use pyinstaller --icon=icon.ico game.py it loads the icon just fine.

Here's my spec file:

# -*- mode: python -*-

block_cipher = None

added_files = [
         ( '/sprites', 'sprites' ),
         ( '/music', 'music' ),
         ( 'Heavitas.ttf', '.'),
         ( 'Roboto-Light.ttf', '.'),
         ( 'high scores.csv', '.')
         ]


a = Analysis(['spec_file.py'],
             pathex=['C:\\Users\\rodri\\Documents\\Code\\The Color That Fell From The Sky'],
             binaries=None,
             datas=added_files,
             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,
          exclude_binaries=True,
          name='spec_file',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='spec_file')
Batts answered 17/12, 2016 at 23:29 Comment(1)
You could convert all the resources to base64 strings (maybe compress first), include them in the script and write/recreate the files on executionOrthopter
B
24

Finally made it!

Here's a quick guide of what I did in case you're reading this because you have a similar problem. (I used Python 3.5.2, Pygame 1.9.2 and Pyinstaller 3.2)

Prepare your code

Like C._ explains on the other answer (thanks), in case you are loading you files like this:

import os
folder_path = os.path.join(path.dirname(__file__), 'folder')
some_image = pygame.image.load(os.path.join(folder_path, 'some_image.png'))

Do this instead:

import sys
import os
# If the code is frozen, use this path:
if getattr(sys, 'frozen', False):
    CurrentPath = sys._MEIPASS
# If it's not use the path we're on now
else:
    CurrentPath = os.path.dirname(__file__)
# Look for the 'sprites' folder on the path I just gave you:
spriteFolderPath = os.path.join(CurrentPath, 'sprites')
# From the folder you just opened, load the image file 'some_image.png'
some_image = pygame.image.load(path.join(spriteFolderPath, 'some_image.png'))

It's needed because when you freeze your code, the files are gonna be moved to a different folder than the one you used before. Make sure you do this to all the files.

Here's another example:

if hasattr(sys, '_MEIPASS'):  # the same logic used to set the image directory
    font = path.join(sys._MEIPASS, 'some_font.otf')  # specially useful to make a singlefile .exe
font = pygame.font.Font(font, size)
# Don't ask me the difference between hasattr and getattr because I don't know. But it works.

Icon (optional)

If you want an icon that's not pyinstaller's default, you can choose a png image and convert it to .ico using some online converter (Google it). After that, put the .ico file on the same folder where your .py file is.

Make the spec file

At this point you should know if you want a single self contained .exe file or a bunch of separate files that you will zip and send to people. In any case, open the terminal on the folder where your .py file is.

If you want a single file, use this:

pyinstaller --onefile --icon=icon_file.ico game_file.py

If you don't, use this:

pyinstaller --icon=icon_file.ico game_file.py

If you don't want to set the icon now, just don't use the --icon part. You can change it later. What you can't change later is the --onefile option (at least as far as I know).

A file named game_file.spec will be created. It will automatically get the name from game_file.py. You can mess it up if they have different names, so don't get creative now. If you chose a single file, it should look like this:

# -*- mode: python -*-

block_cipher = None

a = Analysis(['game_file.py'],
             pathex=['C:\\some\\path\\The path where your .py and .spec are'],
             binaries=None,
             datas=None,
             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,
          exclude_binaries=True,
          name='game_file',
          debug=False,
          strip=False,
          upx=True,
          console=True , icon='icon_file.ico')

If you chose to have a bunch of files, you'll see this aditional part:

coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='game_file')

After block_cipher = None you're gonna add the files your game loads. Like this:

added_files = [
         ( 'a folder', 'b folder' ),  # Loads the 'a folder' folder (left) and creates
                                      # an equivalent folder called 'b folder' (right)
                                      # on the destination path
         ( 'level_1/level2', 'level_2' ),  # Loads the 'level_2' folder
                                           # that's inside the 'level_1' folder
                                           # and outputs it on the root folder
         ( 'comic_sans.ttf', '.'),  # Loads the 'comic_sans.ttf' file from
                                    # your root folder and outputs it with
                                    # the same name on the same place.
         ( 'folder/*.mp3', '.')  # Loads all the .mp3 files from 'folder'.
         ]

Now you have to add 'added_files' here:

a = Analysis(['game_file.py'],
                 pathex=['C:\\some\\path\\The path where your .py and .spec are'],
                 binaries=None,
                 datas=added_files,  # Change 'None' to 'added_files' here
                                     # Leave everything else the way it is.
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)

You can also change some settings:

exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='game_file',  # Name of the output file. Equivalent to '--name'
                                 # Don't change it.
              debug=False,  # If True shows a debug screen on start. Equivalent to '--debug'.
              strip=False,
              upx=True,  # Compresses executable files and libraries
              # If console=True, a console screen will be shown on start up.
              # icon= is the location of the icon of the exe.
              console=True , icon='icon_file.ico')

If you didn't change the path or the output name of the exe like I told you not to on the comments, we just need to run it and the .exe file that was previously created will be updated. Enter this on the command window:

pyinstaller game_file.spec

Remember that game_file.spec is the file we just edited and 'game_file' is a random name I used as an example. Also notice that there's no --some_option because they won't work with the spec file. This is why you have to change them directly in the script. --onefile also doesn't work here and can't be done from within the script, that's why I told you to do it before.

You will se that two folders were created on the same folder where you .spec file is. The one called 'Dist' contains the exe file and if you didn't use --onefile, it should also have a bunch of other files that you need to zip along with the exe to share the application with other people. There will also be a 'Buid' folder but I don't know what it's for, since you don't need it to use the application.

So this is it. It should work for you.

My mistakes when I made the question was that I didn't know the sys._MEIPASS part (thanks again C._), the name of my spec file was different from my py file, I used '/sprites' instead of 'sprites' in added_files and didn't know I was supposed to run the spec file instead of the py file.

For more information on Pyinstaller, take a look at the manual, but since it's far from good and sometimes misleading, you're better off with Google.

Batts answered 19/12, 2016 at 3:9 Comment(0)
S
2

When you compile with PyInstaller, all the files are moved to a different directory when you run your exe. So, to get to this location, add this to the beginning of your code, before you generate paths

import sys

if getattr(sys, 'frozen', False): # PyInstaller adds this attribute
    # Running in a bundle
    CurrentPath = sys._MEIPASS
else:
    # Running in normal Python environment
    CurrentPath = os.path.dirname(__file__)

All your folder paths can then be generated from your location

spriteFolderPath = path.join(CurrentPath, 'sprites') # Do the same for all your other files

Then, when you've got the location you're executing in, you can get all your files from there :

title_screen = pygame.image.load(path.join(spriteFolderPath, 'title_screen.png')) # Use spriteFolderPath instead of img_dir

I also see that you've got other fonts/stuff, you can do the same to load them

fontRobotoLight = pygame.font.Font(path.join(CurrentPath, 'Roboto-Light.ttf'))

For your icon, just paste a temporary icon.ico in your main folder and type pyinstaller -i "icon.ico" "spec_file.spec"

Finally, as I've had the same problem before, I recommend you compile your exe simply by running pyinstaller "spec_file.spec"

Suffix answered 18/12, 2016 at 4:43 Comment(3)
The icon suggestion works, but I'm still getting the same error saying it can't load title_screen.png. Also, pyinstaller "spec_file.spec" returns ValueError: script 'spec_file.py' not found. But thanks anyway.Batts
@RodrigoNascimento Went I wrote 'spec_file', I mean enter the name of your spec file between the "". I'm sorry, it was quite ambiguous. Otherwise, it should then work.Suffix
I know. My specs file is actually named 'spec_file.spec'.Batts
C
-1

i'm a beginner too. and i have this problem, after using (pyinstaller --onefile maingame.py --windowed) i started to take back step by step. i found out my problem was in the folder i do this command . in my opinion do it in specific folder in your desktop . in visual studio put your work folder on it and when addressing your image or your sound in address them just as (suond_fsx_ad="sound/123.mp3") (don't copy address from folder bar) after using above command. copy exe file from dist and paste it on main folder. I have done this and its work perfectly

Complication answered 29/1, 2024 at 15:34 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Berti

© 2022 - 2025 — McMap. All rights reserved.