I am creating a program using Python 3.10.8 (Miniconda distribution) that relies on pyglet version 1.5.27. One feature of my program involves loading audio files into pyglet using this function:
pyglet.media.load("{path_to_audio_file}")
Recently, I packaged the source code for my program into both a Windows executable (.exe) and a Mac application (.app) using PyInstaller (although I used Auto Py to Exe for the Windows version).
This is the PyInstaller command I used to build the Mac version of my program (replace {path_to_repository} with the location of the repository on the local machine):
pyinstaller --noconfirm --onedir --windowed --icon "{path_to_repository}/setup_files/mac/file_icon_mac.icns" --add-data "{path_to_repository}/src/classes:classes/" --add-data "{path_to_repository}/src/images:images/" --add-data "{path_to_repository}/messages:messages/" "{path_to_repository}/src/Time Stamper.py"
The Windows command that Auto Py to Exe generates looks exactly the same as the above command except that...
the path to the Windows icon (.ico) file is provided instead of the path to the Mac icon (.icns) file
AND
all colons (":") are replaced with semicolons (";").
I am able to load and play mp3 files with pyglet just fine on the Windows version of my program as well as on the Mac version of my program, but:
pyglet cannot load mp3 files on the Mac version of my program when I launch the application (.app) by double-clicking on it.
pyglet will only load my mp3 files on the Mac version of my program when I either...
launch my Mac application (.app) by right-clicking on it and then selecting "Show Package Contents" and navigating to "Contents" -> "MacOS" and then double-clicking on the Unix executable within that folder.
OR
launch my Mac application (.app) by entering the command:
open -n {path_to_my_app} --args -AppCommandLineArg
When I try to launch the Mac application (.app) of my program by simply double clicking on the .app file, I get the following error message:
{Traceback (most recent call last): } { File "pyglet/media/codecs/wave.py", line 58, in __init__ } { File "wave.py", line 509, in open } { File "wave.py", line 163, in __init__ } { File "wave.py", line 130, in initfp } {wave.Error: file does not start with RIFF id } { During handling of the above exception, another exception occurred: } {Traceback (most recent call last): } { File "tkinter/__init__.py", line 1921, in __call__ } { File "classes/macros/macros_buttons_file.py", line 107, in button_audio_select_macro self.parent.validate_audio_player() } { File "classes/macros/macros.py", line 229, in validate_audio_player verify_audio_file(self.widgets["entry_audio_path"].get(), self.time_stamper) } { File "classes/macros/macros_helper_methods.py", line 225, in verify_audio_file time_stamper.audio_source = load(file_full_path) } { File "pyglet/media/__init__.py", line 142, in load } { File "pyglet/media/__init__.py", line 132, in load } { File "pyglet/media/codecs/wave.py", line 105, in decode } { File "pyglet/media/codecs/wave.py", line 60, in __init__ } {pyglet.media.codecs.wave.WAVEDecodeException: file does not start with RIFF id }
Also, these are the warnings that were generated by PyInstaller, which PyInstaller placed in "build/Time Stamper/warn-Time Stamper.txt" ("Time Stamper" is the name of my application):
This file lists modules PyInstaller was not able to find. This does not necessarily mean this module is required for running your program. Python and Python 3rd-party packages include a lot of conditional or optional modules. For example the module 'ntpath' only exists on Windows, whereas the module 'posixpath' only exists on Posix systems. Types if import: * top-level: imported at the top-level - look at these first * conditional: imported within an if-statement * delayed: imported within a function * optional: imported within a try-except-statement IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for tracking down the missing module yourself. Thanks! missing module named pep517 - imported by importlib.metadata (delayed) missing module named 'org.python' - imported by copy (optional), xml.sax (delayed, conditional) missing module named org - imported by pickle (optional) missing module named winreg - imported by importlib._bootstrap_external (conditional), mimetypes (optional), urllib.request (delayed, conditional, optional), platform (delayed, optional) missing module named nt - imported by os (delayed, conditional, optional), ntpath (optional), shutil (conditional), importlib._bootstrap_external (conditional), ctypes (delayed, conditional) missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional) excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional) missing module named _winapi - imported by encodings (delayed, conditional, optional), ntpath (optional), subprocess (optional), mimetypes (optional) missing module named msvcrt - imported by subprocess (optional), getpass (optional), pyglet.extlibs.png (delayed, conditional) missing module named pyogg - imported by pyglet.media.codecs.pyogg (top-level) missing module named PIL - imported by pyglet.image.codecs.pil (optional) missing module named Image - imported by pyglet.image.codecs.pil (optional) missing module named vms_lib - imported by platform (delayed, optional) missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional) missing module named java - imported by platform (delayed) missing module named _winreg - imported by platform (delayed, optional) missing module named pyglet.window.BaseWindow - imported by pyglet.window (top-level), pyglet.window.headless (top-level), pyglet.window.cocoa (top-level), pyglet.window.win32 (top-level), pyglet.window.xlib (top-level) missing module named 'gi.repository' - imported by pyglet.media.codecs.gstreamer (optional) missing module named gi - imported by pyglet.media.codecs.gstreamer (optional)
I know that pyglet works better with wav files, but I find it strange that pyglet could read my mp3 files in every case except for this very specific case on my Mac, so if possible, I would really like to get it to work there too. Also, I am running macOS Catalina 10.15.7 on an 11-inch, Mid 2012 MacBook Air with a 1.7 GHz Dual-Core Intel Core i5 processor.
Update:
I have been experimenting with this more and I believe I have identified the problem. To read mp3 files, pyglet requires ffmpeg. When I installed pyglet using the following command, the ffmpeg dependency was automatically installed to my computer:
conda install -c conda-forge pyglet
When I simply run my Mac application (.app) from the command line:
- My application (.app) defaults to my system's default Python interpreter (i.e., my base miniconda environment), and so my application has all of the Python packages I have installed using "conda/pip install" commands at its disposal.
- This means that, when I run my application (.app) from the command line, my computer is able to find both pyglet as well as the ffmpeg dependency that was installed alongside pyglet (allowing for my application to read mp3 files).
However, when I run my Mac application by double-clicking on the application (.app) generated by PyInstaller:
- My computer relies only on packages that were included in the PyInstaller application (.app) bundle.
- Since PyInstaller seems to detect that pyglet is necessary for my application (.app) to run, it includes pyglet in the final application (.app) bundle, so I would have suspected PyInstaller to automatically include ffmpeg in this application (.app) bundle as well (since ffmpeg was automatically installed alongside pyglet as a dependency when I ran the aforementioned "conda install" command). However, this does not appear to be the case.
I am almost certain that PyInstaller is not including ffmpeg in my Mac application (.app) because, while I was debugging my code, I found that the following statement...
pyglet.media.have_ffmpeg()
evaluated to True when I ran my application (.app) from the command line, but evaluated to False when I double-clicked on the application (.app) generated by PyInstaller.
So now, I am trying to figure out how to make PyInstaller include ffmpeg in my Mac application (.app) in such a way that pyglet can detect that ffmpeg is there (which, I believe, would allow pyglet to make use of ffmpeg to be able to read mp3 files), but I cannot, for the life of me, figure out how to do this.
To try and make PyInstaller include ffmpeg in my Mac application (.app), I have tried adding different permutations of the following arguments to my pyinstaller command:
--add-binary "/opt/miniconda3/bin/ffmpeg:bin/"
--add-binary "/opt/miniconda3/bin/ffmpeg:ffmpeg/"
--add-data "/opt/miniconda3/lib/python3.10/site-packages/ffmpeg"
--collect-all pyglet
--collect-all ffmpeg
--collect-all ffmpeg-python
None of these arguments, when added, seemed to make it so that PyInstaller included ffmpeg in my application (.app), or at least, if ffmpeg was included (and I don't think it was but I'm not sure), then these arguments did not allow pyglet to detect it.
Also, those last two arguments get ignored completely by PyInstaller as it says that there are no packages with either the name ffmpeg or ffmpeg-python. I even installed standalone ffmpeg packages with conda using the following commands (and then tried running the PyInstaller command again):
conda install -c conda-forge ffmpeg
conda install -c conda-forge ffmpeg-python
Installing these packages and then running PyInstaller again yielded the same problem (i.e., PyInstaller did not detect any packages with the name ffmpeg or ffmpeg-python, and so those packages were ignored and not included in the .app bundle).
So, based on my complete understanding of my problem thus far, it seems that I need to find a way to properly package ffmpeg into my application (.app) in such a way that pyglet can communicate with it. Any ideas?
CodePudding user response:
You are correct, Mac doesn't support MP3 by default in Pyglet (this will be added in an upcoming 2.x version and possibly backported to 1.5) and requires FFmpeg.
Pyglet looks for the dylib files for ffmpeg in the os.environ["DYLD_LIBRARY_PATH"]
. You could also play around with pyglet.options["search_local_libs"] = True
which will search a folder called lib
in your script directory for these lib files.
Here is some info on ffmpeg installation: https://pyglet.readthedocs.io/en/latest/programming_guide/media.html#ffmpeg-installation