I wanted to package a PyQt script as a standalone Windows .exe using PyInstaller, and I wanted the app to display a jpeg.
It turned out that displaying a PNG was fine, but displaying a JPEG took a while to get working. PyQt uses a dll called `qjpeg4.dll` for decoding the jpeg, and PyInstaller doesn’t automatically bundle it into the package, so we have to tell PyInstaller to do so.
There is still one problem though: when you run the .exe, where does PyQt try to load `qjpeg4.dll` from? It turns out that *it depends*..
If you use the `–onedir` option for PyInstaller, the `imageformats/qjpeg4.dll` just needs to be in a directory called `imageformats`. No problem.
If you use the `–onefile` option, PyInstaller unzips the binaries into a temp directory, and loads dlls from there. But for some reason, in the `–onefile` case, PyQt tries to load the dll from `qt4_plugins/imageformats/qjpeg4.dll`.
I used SysInternals Process Monitor to figure this out…
To make things easy, I just added an `imageformats` directory to my repo, and checked in the qjpeg4.dll.
I also wanted to load a dll called `discid.dll`, required by python-discid. I checked into the root level of the repo, but couldn’t get the python package to load the dll properly, until I modified the PATH environment variable:
#fix for loading discid.dll if getattr(sys, 'frozen', None): BASE_DIR = sys._MEIPASS else: BASE_DIR = os.path.dirname(__file__) os.environ['PATH'] = BASE_DIR + '\;' + os.environ.get('PATH', '') import discid |
My PyInstaller .spec file for the `–onefile` case looks like this:
# -*- mode: python -*- a = Analysis(['wizard.py'], pathex=['y:\\archivecd'], hiddenimports=[], hookspath=None, runtime_hooks=None) onefile_binaries = a.binaries + [('discid.dll', 'discid.dll', 'BINARY'), ('qt4_plugins/imageformats/qjpeg4.dll', 'imageformats/qjpeg4.dll', 'BINARY'), ] pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, Tree('images', prefix='images'), onefile_binaries, a.zipfiles, a.datas, name='wizard.exe', debug=False, strip=None, upx=True, console=True ) |