Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pygame.mixer can't load sound when frozen by Pyinstaller #1514

Closed
2 tasks done
Starbuck5 opened this issue Nov 19, 2019 · 21 comments
Closed
2 tasks done

Pygame.mixer can't load sound when frozen by Pyinstaller #1514

Starbuck5 opened this issue Nov 19, 2019 · 21 comments
Labels
hard A hard challenge to solve rwops SDL's IO loading/streaming code

Comments

@Starbuck5
Copy link
Contributor

Starbuck5 commented Nov 19, 2019

Tested on windows 10.

I tested with pygame 1.9.6 and pygame 2, and it worked in pygame 1.9.6.

Attached is my test code, compiled into executables with pygame 1.9.6 and pygame 2.
mixer_load_test.zip

Related Docs: https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Sound

Remaining work to do

  • install a hook inside pygame so it can find dlls for pyinstaller
  • confirm next pygame release(or master) works with pyinstaller on windows.
@robertpfeiffer
Copy link
Contributor

robertpfeiffer commented Nov 19, 2019

Thank you for reporting this, and for the detailed test case.
It would really help if you also posted which version of Python you used, whicgh PyGame 2.0 preview release, which version of PyInstaller, and what error message you got. I'll try to reproduce this on Linux, with Python 3.6 and PyInstaller 3.4, and then post my findings.

I have recently looked into the file loading code. It uses the SDL2 RWops. I looked at the SDL2 file loading code too, and it sometimes does "clever" platform-specific things with paths. So possibly, this also depends on the SDL2 version, or it only happens with "\" path separators. Maybe not.
@Starbuck5 : please also post the SDL2 version your PyGame uses. For now, I'll assume you used the compiled windows version of pygame from pip and didn't compile your own SDL.

Dear contributors/maintainers:
Please check if this bug, or a related bug, happens on different operating systems, with the path dividers changed from "\" to "/" if necessary. This might be a bug in PyInstaller, and if it is, we can coordinate here who will report that bug there.

@Starbuck5
Copy link
Contributor Author

Starbuck5 commented Nov 19, 2019

I tested on pygame 2 dev 6, python 3.7.5, and pyinstaller 3.5.
SDL 2.0.10

Line of code with error:
sound = pygame.mixer.Sound("sounds\\explosion1.ogg")

Error msg:
Unable to open file 'sounds\\\\explosion1.ogg'

@robertpfeiffer
Copy link
Contributor

robertpfeiffer commented Nov 20, 2019

Yeah. I ran your code on my machine. It obviously doesn't work on any Unix-like system. What if you change the line to
sound = pygame.mixer.Sound("sound/explosion1.ogg")

I also looked at your spec file. You don't even package the assets into a self-extracting .exe, something that is normally a source of problems.

@robertpfeiffer
Copy link
Contributor

can you try and paste the output of this:

import pygame
import logging, traceback
import os, os.path
import sys

def main():
    pygame.init()
    screen = pygame.display.set_mode([300,300])
    pygame.mixer.init()
    print (sys.implementation)
    os.system("pyinstaller -v")
    frozen = getattr(sys, 'frozen', None)
    if frozen:
        print('running in a PyInstaller bundle')
        print ("MEIPASS (resource path):", sys._MEIPASS)
        print (".exe:", sys.executable)
    else:
        print('running in a normal Python process')
        print("Python interpreter:", sys.executable)
    print ("working directory:", os.getcwd())
    print ("module name:", __file__)
    print ("executable name:", sys.argv[0])

    sound = pygame.mixer.Sound("sounds\\explosion1.ogg")
    #sound = pygame.mixer.Sound("sounds/explosion1.ogg")
    #sound = pygame.mixer.Sound(os.path.join("sounds", "explosion1.ogg"))
    #sound = pygame.mixer.Sound(os.path.join(os.getcwd(), "sounds", "explosion1.ogg"))

    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        screen.fill((120,49,200))
        pygame.display.flip()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                raise SystemExit
            if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
                sound.play()

main()

If it fails, can you try again with the different successive sound= lines and post your success or failure? I still suspect it has something to do with path separators and RWops.

@Starbuck5
Copy link
Contributor Author

When run from source: (worked with every sound= line)

pygame 2.0.0.dev6 (SDL 2.0.10, python 3.7.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
namespace(cache_tag='cpython-37', hexversion=50791920, name='cpython', version=sys.version_info(major=3, minor=7, micro=5, releaselevel='final', serial=0))
running in a normal Python process
Python interpreter: C:\Users\Starbuck\AppData\Local\Programs\Python\Python37-32\pythonw.exe
working directory: C:\Users\Starbuck\Desktop\mixer_load_test2
module name: C:\Users\Starbuck\Desktop\mixer_load_test2\mixertest.py
executable name: C:\Users\Starbuck\Desktop\mixer_load_test2\mixertest.py

When run from executable: (output read by turning the console flag of the spec file to True)

pygame 2.0.0.dev6 (SDL 2.0.10, python 3.7.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
namespace(cache_tag='cpython-37', hexversion=50791920, name='cpython', version=sys.version_info(major=3, minor=7, micro=5, releaselevel='final', serial=0))
3.5
running in a PyInstaller bundle
MEIPASS (resource path): C:\Users\Starbuck\AppData\Local\Temp\_MEI121402
.exe: C:\Users\Starbuck\Desktop\mixer_load_test2\pygame2.0.0.dev6\mixertest.exe
working directory: C:\Users\Starbuck\Desktop\mixer_load_test2\pygame2.0.0.dev6
module name: mixertest.py
executable name: mixertest.exe
Traceback (most recent call last):
  File "mixertest.py", line 42, in <module>
  File "mixertest.py", line 27, in main
pygame.error: Unable to open file 'C:\\Users\\Starbuck\\Desktop\\mixer_load_test2\\pygame2.0.0.dev6\\sounds\\explosion1.ogg'
[5904] Failed to execute script mixertest

And for the other sound lines: (not including the version printouts)

Traceback (most recent call last):
  File "mixertest.py", line 42, in <module>
  File "mixertest.py", line 24, in main
pygame.error: Unable to open file 'sounds\\explosion1.ogg'
[11320] Failed to execute script mixertest
Traceback (most recent call last):
  File "mixertest.py", line 42, in <module>
  File "mixertest.py", line 25, in main
pygame.error: Unable to open file 'sounds/explosion1.ogg'
[7556] Failed to execute script mixertest
Traceback (most recent call last):
  File "mixertest.py", line 42, in <module>
  File "mixertest.py", line 26, in main
pygame.error: Unable to open file 'sounds\\explosion1.ogg'
[12892] Failed to execute script mixertest

Yeah I wasn't worrying about cross-compatibility in this, but in my actual project I have a handle_path method that converts all the paths to absolute paths that the operating system will like.

I've heard about packaging the dependencies with pyinstaller, but I've preferred bundling them in next to the exe myself. That way they are visible and editable to end users. I've had problems with this, mainly on mac, which runs unknown exes in a weird secure folder somewhere away from all its assets, but my handle_path method has been able to handle them.

Anyways the weird thing to me is that this works in pygame 1.9.6 and not pygame 2.

@Starbuck5
Copy link
Contributor Author

When you corrected the path format were you able to reproduce this on Linux?

@robertpfeiffer
Copy link
Contributor

robertpfeiffer commented Nov 21, 2019

I was not able to reproduce this on Linux. I have pyinstaller installed twice. One in my normal FS and once in a steam-runtime chroot.

BUT: I have encountered a similar bug when using python-for-android to bundle pygame games (not production ready yet, but file loading works now).

It will take a while to debug this, but I think I know where to look. Or maybe this is a bug in PyInstaller. I don't know yet. As PyInstaller is one of the few well-maintained tools that can be used to deploy PyGame games (the others being cx_freeze, and pynsist) it's important that we get this to work, even if it's a bug in PyInstaller. (My money is on SDL2 changing behaviour compared to SDL1.2, but I don't want to point fingers and assign blame here. This bug is just tricky to chase down!)

@robertpfeiffer robertpfeiffer added the rwops SDL's IO loading/streaming code label Nov 21, 2019
@mcpalmer1980
Copy link
Contributor

mcpalmer1980 commented May 8, 2020

I had a similar error when trying to freeze my games using pyinstaller, python 3.7, and pygame2. I did not have this problem with python2.7 and pygame1.96, or with python3.7 and pygame2 on linux. It was not a mixer or SDL problem, but pyinstaller that failed to include all of the necessary pygame or sdl2 components.

After I copied my pygame folder into the the dist folder the game ran without error. Not sure if this is the same problem this user had but it might be.

@MyreMylar MyreMylar added the hard A hard challenge to solve label May 15, 2020
@yudonglin
Copy link

Pyinstaller has a problem with packaging up the pygame project. You have to copy all the libxxxx.dll (especially "libogg-0.dll") from pygame files and paste them into your "main" file. This will solve the problem.

@Starbuck5
Copy link
Contributor Author

Can we get pyinstaller to do this automatically using hooks?

Pyinstaller already has a hook file for pygame here

@yudonglin
Copy link

Can we get pyinstaller to do this automatically using hooks?

Pyinstaller already has a hook file for pygame here

Yes, but it will ignore some ".dll" files, and I do not know why.

@Starbuck5
Copy link
Contributor Author

I think I fixed it!

I changed the pygame hook in my local pyinstaller to this:

#-----------------------------------------------------------------------------
# Copyright (c) 2013-2020, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------


"""
hiddenimports hook for pygame._view required for develop releases between
2011-02-08 and 2011-08-31, including prebuilt-pygame1.9.2a0
"""

hiddenimports = ['pygame._view']

"""
binaries hook for pygame seems to be required for pygame 2.0.
Otherwise some essential DLLs will not be transfered to the exe.
"""

from PyInstaller.utils.hooks import collect_dynamic_libs

pre_binaries = collect_dynamic_libs('pygame')
binaries = []

for b in pre_binaries:
    binary, location = b
    # settles all the DLLs into the top level folder, which prevents duplication
    # with the DLLs already being put there.
    binaries.append((binary, "."))

I've tested that this resolves my issue on windows in pygame 2.0.0.dev10. I've also tested it in pygame 1.9.6 and in onefile mode, and those both work as well.

Because it is using collect_dynamic_libs it should be cross platform.

@illume Do you think this is ready to submit to pyinstaller?

@illume
Copy link
Member

illume commented Jun 25, 2020

Hi,

cool! Yeah, submit a PR if it works :)

No idea why 2.0.dev6+ requires that though. I wonder if any pygame 2 versions before dev6 work (dev1-dev5)? If so, that could help us fix the problem in pygame itself, rather than have to wait for the PR to be merged and a new release made. Because the problem seems to have started between pygame 1.9.6 and 2.0.0.dev6.

I guess people can use the hook you made before/if a new pyinstaller is released :) There's a command line option to tell it to look in a particular folder right? I can't seem to find a way for pygame itself to provide the hook.

@MyreMylar
Copy link
Contributor

I guess the main thing to check would be whether this works to create frozen executable code on macs and linux with a test project. I'm assuming that you are on Windows due to the mention of .dlls and .exe files.

I don't know what the standards are for submitting pull requests to PyInstaller but I expect that kind of testing is what I would want to know if I was in charge over there.

From my eyeballing of your changes and a look at other hook files in the PyInstaller repo, what you've done doesn't seem wildly out of step with other project's hook files. If it makes the situation better on windows (and it sounds like it does) and doesn't make it worse for mac and linux users then I have no objection. Heck knows I've answered enough pygame on PyInstaller questions so anything to reduce those a little would be welcome.

@Starbuck5
Copy link
Contributor Author

Starbuck5 commented Jun 26, 2020

I tested it on pygame 2.0.0.dev1 and the problem started then. Maybe it's something inside SDL2 making them not "included/imported" in PyInstaller's view.

These are the DLLs that are present in the project when built with pygame 1.9.6 but not with pygame 2:

libogg-0.dll
libmpg123-0.dll
libvorbis-0.dll
libvorbisfile-3.dll
libtiff-5.dll
libwebp-5.dill
libgcc-5-5jlj1.dll

I tested my solution on mac and it didn't affect anything. But to avoid any problems on other operating systems I'll make my solution test whether it's on windows first.

There's a command line to add local hooks, and there's an option in spec files for it as well.

I wish modules could embed their hooks in their folders. Edit: looks like they're going this direction

Final hook solution:

"""
binaries hook for pygame seems to be required for pygame 2.0.
Otherwise some essential DLLs will not be transfered to the exe.
"""

import platform

if platform.system() == "Windows":
    
    from PyInstaller.utils.hooks import collect_dynamic_libs

    pre_binaries = collect_dynamic_libs('pygame')
    binaries = []

    for b in pre_binaries:
        binary, location = b
        # settles all the DLLs into the top level folder, which prevents duplication
        # with the DLLs already being put there.
        binaries.append((binary, "."))

@MyreMylar
Copy link
Contributor

MyreMylar commented Jun 26, 2020

Looks good to me 👍

Hmm... if the next release of PyInstaller will allow package hooks, and this hook won't make it into PyInstaller until the next release anyway - should we just hold fire on this and incorporate it into pygame when the new system is ready?

Though even if we went that route, if they are keeping the existing hooks in the Pyinstaller project anyway we'd probably want to replace that one with one that works on windows anyway, just in case someone uses an old version of pygame 2 with a newer version of PyInstaller.

Perhaps @illume might be against adding PyInstaller specific config files into the repo on principle - I know they prefer CxFreeze over PyInstaller for several reasons.

Anyway if we do go the 'Pyinstaller hook in pygame repo route' it at least looks like the hooks can be tucked away neatly via setup.cfg. See this example project:
https://github.com/pyinstaller/hooksample

And the documentation from that pull request:
pyinstaller/pyinstaller@cd3d805?short_path=37b04d2#diff-37b04d2c31d218bbf6626e77f6fa1d49

Looks like that example project also has an example of testing PyInstaller freezing automatically on CI which is vaguely interesting too:

https://github.com/pyinstaller/hooksample/blob/master/src/pyi_hooksample/__pyinstaller/test_hooksample_packaging.py

@illume
Copy link
Member

illume commented Jun 26, 2020 via email

@illume
Copy link
Member

illume commented Aug 30, 2020

I'm not against the pyinstaller hook, and if it works... great.

@illume illume closed this as completed in a64cc9a Aug 30, 2020
illume added a commit that referenced this issue Aug 30, 2020
Internal pyinstaller hook (Fixes #1514)
@illume illume reopened this Aug 30, 2020
@illume
Copy link
Member

illume commented Aug 30, 2020

The PR was merged in. Leaving this open until someone can confirm the next release works with pyinstaller on windows.

@Starbuck5
Copy link
Contributor Author

Ankith confirmed that the hook works on discord, so I'll close this.

@Lerduzz
Copy link

Lerduzz commented Apr 28, 2024

This works for me using pyinstaller and pygame.mixer.

`
import os

os.chdir(os.path.dirname(os.path.abspath(file)))
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hard A hard challenge to solve rwops SDL's IO loading/streaming code
Projects
None yet
Development

No branches or pull requests

7 participants