web-dev-qa-db-fra.com

Pygame ne charge pas png après avoir créé .exe avec Pyinstaller

J'ai essayé de créer un fichier .exe à partir de mon jeu .py et cela a été vraiment frustrant.

J'utilise Python 3.5.2, Pygame 1.9.2 et Pyinstaller 3.2.

Le jeu fonctionne parfaitement en tant que .py, mais après avoir entré pyinstaller --debug game.py, le fichier .exe est construit et je le lance. J'obtiens ce qui suit:

écran de débogage

Voici les lignes de code de game.py pouvant être liées à l'erreur:

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

Je pense que cela doit avoir quelque chose à voir avec le fait que pyinstaler ne puisse pas obtenir mon dossier sprites, car lorsque j'essaie de lancer pyinstaller --icon=/sprites/icon.ico game.py, je reçois ceci:

Icône d'erreur d'icône

Mais si j'utilise pyinstaller --icon=icon.ico game.py, l'icône sera correctement chargée.

Voici mon fichier de spécification:

# -*- 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')
6
Rodrigo Nascimento

Enfin réussi!

Voici un guide rapide de ce que j'ai fait dans le cas où vous lisez ceci parce que vous avez un problème similaire. (J'ai utilisé Python 3.5.2, Pygame 1.9.2 et Pyinstaller 3.2)

Préparez votre code

Comme C._ l'explique dans l'autre réponse (merci), si vous chargez des fichiers comme celui-ci:

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

Faites ceci à la place:

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'))

Cela est nécessaire car lorsque vous bloquez votre code, les fichiers vont être déplacés vers un dossier différent de celui que vous utilisiez auparavant. Assurez-vous de le faire pour tous les fichiers.

Voici un autre exemple:

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.

Icône (optionnel)

Si vous voulez une icône qui ne soit pas celle par défaut de pyinstaller, vous pouvez choisir une image au format png et la convertir en .ico à l'aide d'un convertisseur en ligne (Google it). Après cela, placez le fichier .ico dans le même dossier que votre fichier .py.

Faire le fichier spec

À ce stade, vous devez savoir si vous souhaitez un fichier .exe autonome ou un groupe de fichiers distincts que vous voulez compresser et envoyer aux personnes. Dans tous les cas, ouvrez le terminal dans le dossier où se trouve votre fichier .py.

Si vous voulez un seul fichier, utilisez ceci:

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

Si vous ne le faites pas, utilisez ceci:

pyinstaller --icon=icon_file.ico game_file.py

Si vous ne souhaitez pas définir l'icône maintenant, n'utilisez simplement pas la partie --icon. Vous pouvez le changer plus tard. Ce que vous ne pouvez pas changer plus tard, c’est l’option --onefile (du moins autant que je sache).

Un fichier nommé game_file.spec sera créé. Il recevra automatiquement le nom de game_file.py. Vous pouvez vous tromper s'ils ont des noms différents, alors ne soyez pas créatif maintenant. Si vous avez choisi un seul fichier, il devrait ressembler à ceci:

# -*- 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')

Si vous avez choisi de créer plusieurs fichiers, vous verrez cette partie supplémentaire:

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

Après block_cipher = None, vous allez ajouter les fichiers que votre jeu charge. Comme ça:

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'.
         ]

Maintenant, vous devez ajouter 'added_files' ici:

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)

Vous pouvez également modifier certains paramètres:

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')

Si vous n'avez pas changé le chemin ou le nom de sortie de l'exe comme je vous l'avais dit dans les commentaires, il suffit de l'exécuter et le fichier .exe créé précédemment sera mis à jour. Entrez ceci dans la fenêtre de commande:

pyinstaller game_file.spec

Rappelez-vous que game_file.spec est le fichier que nous venons d'éditer et que "game_file" est un nom aléatoire que j'ai utilisé à titre d'exemple. Notez également qu'il n'y a pas de --some_option car ils ne fonctionneront pas avec le fichier de spécification. C'est pourquoi vous devez les modifier directement dans le script. --onefile ne fonctionne pas non plus ici et ne peut pas être créé à partir du script, c'est pourquoi je vous ai dit de le faire avant.

Vous constaterez que deux dossiers ont été créés dans le même dossier que votre fichier .spec. Celui appelé 'Dist' contient le fichier exe et si vous n'avez pas utilisé --onefile, il devrait également contenir un tas d'autres fichiers que vous devez compresser avec l'exe pour partager l'application avec d'autres personnes. Il y aura également un dossier 'Buid' mais je ne sais pas à quoi il sert, car vous n'en avez pas besoin pour utiliser l'application.

Alors c'est ça. Il devrait fonctionner pour vous.

Quand j'ai posé la question, mes erreurs étaient que je ne connaissais pas la partie sys._MEIPASS (merci encore C._), le nom de mon fichier de spécification était différent de mon fichier py. J'ai utilisé '/sprites' au lieu de 'sprites' dans added_files et je ne l'ai pas sais que je devais exécuter le fichier de spécification à la place du fichier py.

Pour plus d'informations sur Pyinstaller, consultez le manuel manual , mais comme il est loin d'être satisfaisant et parfois trompeur, il vaut mieux utiliser Google.

12
Rodrigo Nascimento

Lorsque vous compilez avec PyInstaller, tous les fichiers sont déplacés vers un répertoire différent lorsque vous exécutez votre exe. Donc, pour vous rendre à cet emplacement, ajoutez ceci au début de votre code, avant de générer des chemins

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__)

Tous vos chemins de dossiers peuvent ensuite être générés à partir de votre emplacement. 

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

Ensuite, lorsque vous avez l'emplacement où vous vous exécutez, vous pouvez obtenir tous vos fichiers à partir de cet emplacement:

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

Je vois aussi que vous avez d'autres polices, vous pouvez faire de même pour les charger

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

Pour votre icône, collez simplement un icon.ico temporaire dans votre dossier principal et tapez pyinstaller -i "icon.ico" "spec_file.spec"

Enfin, comme j’ai eu le même problème auparavant, je vous recommande de compiler votre exe simplement en exécutant pyinstaller "spec_file.spec"

1
C._