bastibe / SoundCard

A Pure-Python Real-Time Audio Library
https://soundcard.readthedocs.io
BSD 3-Clause "New" or "Revised" License
689 stars 70 forks source link

SoundCard fails to load mediafoundation.py.h when app built as a frozen/standalone exe #77

Closed Dr3wBr3w closed 4 years ago

Dr3wBr3w commented 4 years ago

When I attempt to run a compiled standalone "onefile" python exe created with PyInstaller + a python file that uses SoundCard, it crashes due to a file not found error. e.g. FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\YOUR-NAME\\YOUR-APP\\dist\\YOUR-APP-NAME\\soundcard\\mediafoundation.py.h'

The error is caused by mediafoundation.py, specifically this part near the start where it loads mediafoundation.py.h:

_ffi = cffi.FFI()
_package_dir, _ = os.path.split(__file__)
with open(os.path.join(_package_dir, 'mediafoundation.py.h'), 'rt') as f:
    _ffi.cdef(f.read())

There needs to be a little bit more code added to check if it is running as a frozen exe (_MEIPASS) or via the normal/script way so the file can be located. Here's the documentation which explains it better and has a code example: https://pyinstaller.readthedocs.io/en/stable/runtime-information.html

Another good explanation + code example: https://stackoverflow.com/a/13790741

Dr3wBr3w commented 4 years ago

I was able to build my app with PyInstaller onefile mode by replacing this line in mediafoundation.py (just before with open):

_package_dir, _ = os.path.split(__file__)

with this:

import sys

if getattr(sys, 'frozen', False):
    _package_dir = sys._MEIPASS
else:
    _package_dir, _ = os.path.split(__file__)

Then in the pyinstaller spec file for my app, edit the a = Analysissection datas=[], to be this (for Python 3.7):

datas=[((os.environ['localappdata'] + '\\Programs\\Python\\Python37\\Lib\\site-packages\\soundcard\\mediafoundation.py.h'), '.'), ('chi.ico', '.')],

(the chi.ico file is specific to my app)

bastibe commented 4 years ago

Is there a way to change SoundCard so it will work "out of the box" with pyinstaller?

If so, I'd be grateful for a pull request.

Bob-Thomas commented 4 years ago

@Dr3wBr3w you could also write a pyinstaller hook for this. Currently, looking into it as well.

Dr3wBr3w commented 4 years ago

@bastibe @Bob-Thomas I'm just starting out and new to programming (I had to google what a "pull" request is! :) ), so I'm probably not the best person to do the request. I like the hook idea, if that can be done that will be easier than having to manually edit the spec file etc. Cheers!

Bob-Thomas commented 4 years ago

I can make a small pull request but instead of pull requesting soundcard which isn't really causing the issue I was thinking of making a pull request for pyinstaller with a simple hook-soundcard.py

from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs

datas = collect_data_files('soundcard')
bastibe commented 4 years ago

Cool! That would be wonderful!

Bob-Thomas commented 4 years ago

https://github.com/pyinstaller/pyinstaller/pull/4834

bastibe commented 4 years ago

Beautiful! Thank you!