Kivy's screen module is handy for emulating different screen sizes and dpis. This means that by using your dev machine, you can check out what your Kivy application would look like on other devices without having them. This is preferable to just guessing what your app would look like, but is in no way a replacement for actually having a test device.
To add your own device, go to the screen.py module in Kivy. If you have followed our install tutorial, you can simply to go your .venv folder, find kivy, and then go here:
.venv/lib/site-packages/kivy/modules/screen.py
I did not follow any tutorial! How do I find my environment?
If you did not follow any tutorial, you can find your environment with Python only.
To add your own device, go to the screen.py module in Kivy. If you have followed our install tutorial, you can simply to go your .venv folder, find kivy, and then go here:
.venv/lib/site-packages/kivy/modules/screen.py
I did not follow any tutorial! How do I find my environment?
If you did not follow any tutorial, you can find your environment with Python only.
Tkinter and Kivy are both cross platform GUIs that are commonly used by Python users. There are other frameworks out there but they are either: in another programming language, not available on all platforms, or a combination of both.
In this blog post, you will go through the hello world steps of both GUI interfaces on as much platforms as possible to get a feel for which one is right for you.
fromkivy.appimportrunTouchAppfromkivy.langimportBuilderfromkivy.core.windowimportWindow#this is to make the Kivy window always on topWindow.always_on_top=True#set the window titleWindow.set_title('Welcome to Kivy School!')#kv language setting the main widget to be a buttonkvString='''Button: text: "Hello world!"'''#run Kivy apprunTouchApp(Builder.load_string(kvString))
Type: poetry update in the kivyhw directory. Poetry will install everything for you and AUTOSOLVE VERSION CONFLICTS, plus provide you with a virtual environment so you don't break your Windows system Python.
Type poetry shell to enter the virtual environment and proceed to the next step.
If poetry is not installed:
If poetry is not installed, you can still install requirements using pip. Be warned! If you make any mistakes with any sort of pip install, you might end up breaking your Windows Python installation and have to reinstall it over again to fix your environment. Because this is just a Hello World tutorial, you do not need to worry about it now. If you have also followed other tutorials, you might get version conflicts for libraries. This is because pip normally installs every library to your system Python unless specified. That means that conflicting versions of certifi will appear and you will have to manually resolve conflicts. If so, please try installing python-poetry and use virtual environments for each project (like the one Kivy School is providing in this tutorial with the poetry shell command).
In the kivyhw folder (NOT kivyhw/kivyhw folder!) type: pip install -r requirements.txt
From the top level kivy hw folder, run this command: python kivyhw/main.py
It is also possible to cd kivyhw and run python main.py
If poetry update was used in installation, you also have access to: task run in the top level kivyhw folder courtesy of these lines in the pyproject.toml and the taskipy library:
[tool.taskipy.tasks]run='python kivyhw/main.py'
main.py
1 2 3 4 5 6 7 8 910111213141516171819
importtkinterastk# Create the main windowroot=tk.Tk()# Set window titleroot.title("Welcome to Kivy School!")# Set min window sizeroot.minsize(450,100)# Set window to always be on top (remove this line for a regular GUI)root.attributes('-topmost',True)# Create labellabel=tk.Label(root,text="Hello, World!")# Lay out labellabel.pack()# Run forever!root.mainloop()
Type: poetry update in the tkinterhw directory. Poetry will install everything for you and AUTOSOLVE VERSION CONFLICTS, plus provide you with a virtual environment so you don't break your Windows system Python.
Type poetry shell to enter the virtual environment and proceed to the next step.
If poetry is not installed:
If poetry is not installed, you can still install requirements using pip. Be warned! If you make any mistakes with any sort of pip install, you might end up breaking your Windows Python installation and have to reinstall it over again to fix your environment. Because this is just a Hello World tutorial, you do not need to worry about it now. If you have also followed other tutorials, you might get version conflicts for libraries. This is because pip normally installs every library to your system Python unless specified. That means that conflicting versions of certifi will appear and you will have to manually resolve conflicts. If so, please try installing python-poetry and use virtual environments for each project (like the one Kivy School is providing in this tutorial with the poetry shell command).
In the tkinterhw folder (NOT tkinterhw/tkinterhw folder!) type: pip install -r requirements.txt
From the top level tkinterhw folder, run this command: python tkinterhw/main.py
It is also possible to cd tkinterhw and run python main.py
If poetry update was used in installation, you also have access to: task run in the top level tkinterhw folder courtesy of these lines in the pyproject.toml and the taskipy library:
fromkivy.appimportrunTouchAppfromkivy.langimportBuilderfromkivy.core.windowimportWindow#this is to make the Kivy window always on topWindow.always_on_top=True#set the window titleWindow.set_title('Welcome to Kivy School!')#kv language setting the main widget to be a buttonkvString='''Button: text: "Hello world!"'''#run Kivy apprunTouchApp(Builder.load_string(kvString))
Type: poetry update in the kivyhw directory. Poetry will install everything for you and AUTOSOLVE VERSION CONFLICTS, plus provide you with a virtual environment so you don't break your Mac system Python.
Type poetry shell to enter the virtual environment and proceed to the next step.
If poetry is not installed:
If poetry is not installed, you can still install requirements using pip. Be warned! If you make any mistakes with any sort of pip install, you might end up breaking your Mac Python installation and have to reinstall it over again to fix your environment. Because this is just a Hello World tutorial, you do not need to worry about it now. If you have also followed other tutorials, you might get version conflicts for libraries. This is because pip normally installs every library to your system Python unless specified. That means that conflicting versions of certifi will appear and you will have to manually resolve conflicts. If so, please try installing python-poetry and use virtual environments for each project (like the one Kivy School is providing in this tutorial with the poetry shell command).
In the kivyhw folder (NOT kivyhw/kivyhw folder!) type: pip install -r requirements.txt
Proceed to the next step.
Warning
If you cannot see the .venv folder in the kivyhw, that is because your Mac Finder settings are set to hide hidden folders.
To toggle seeing hidden folders like .git and .venv, use the command: Command ⌘ + Shift + .
From the top level kivy hw folder, run this command: python kivyhw/main.py
It is also possible to cd kivyhw and run python main.py
If poetry update was used in installation, you also have access to: task run in the top level kivyhw folder courtesy of these lines in the pyproject.toml and the taskipy library:
[tool.taskipy.tasks]run='python kivyhw/main.py'
main.py
1 2 3 4 5 6 7 8 910111213141516171819
importtkinterastk# Create the main windowroot=tk.Tk()# Set window titleroot.title("Welcome to Kivy School!")# Set min window sizeroot.minsize(450,100)# Set window to always be on top (remove this line for a regular GUI)root.attributes('-topmost',True)# Create labellabel=tk.Label(root,text="Hello, World!")# Lay out labellabel.pack()# Run forever!root.mainloop()
Type: poetry update in the tkinterhw directory. Poetry will install everything for you and AUTOSOLVE VERSION CONFLICTS, plus provide you with a virtual environment so you don't break your Mac system Python.
Type poetry shell to enter the virtual environment and proceed to the next step.
If poetry is not installed:
If poetry is not installed, you can still install requirements using pip. Be warned! If you make any mistakes with any sort of pip install, you might end up breaking your Mac Python installation and have to reinstall it over again to fix your environment. Because this is just a Hello World tutorial, you do not need to worry about it now. If you have also followed other tutorials, you might get version conflicts for libraries. This is because pip normally installs every library to your system Python unless specified. That means that conflicting versions of certifi will appear and you will have to manually resolve conflicts. If so, please try installing python-poetry and use virtual environments for each project (like the one Kivy School is providing in this tutorial with the poetry shell command).
In the tkinterhw folder (NOT tkinterhw/tkinterhw folder!) type: pip install -r requirements.txt
Proceed to the next step.
Warning
If you cannot see the .venv folder in the kivyhw, that is because your Mac Finder settings are set to hide hidden folders.
To toggle seeing hidden folders like .git and .venv, use the command: Command ⌘ + Shift + .
From the top level tkinterhw folder, run this command: python tkinterhw/main.py
It is also possible to cd tkinterhw and run python main.py
If poetry update was used in installation, you also have access to: task run in the top level tkinterhw folder courtesy of these lines in the pyproject.toml and the taskipy library:
Most tutorials just give you the Python code in 1 folder.
In a real project, there are a lot of project management files that are simply not part of the main Python code and should NEVER be shipped, like hidden secrets. In this example, there are many things that should not be given to an end user, like the .git folder, .venv virtual environment provided by Poetry, .gitignore used by git, poetry.lock and pyproject.toml used by poetry, a github README.md file and requirements.txt used by pip.
Those files are for development only! That is why the management files are in the top level, and the actual Python project is inside an inner folder.
You can see for yourself by inspecting these popular Python libraries:
If you want to play multimedia and audio in Python, there are some options for you.
One choice is VLC: It is lightweight, free and open source. VLC also works on many devices, like Windows, Linux, Mac, Android and iOS.
In this tutorial we will:
Set up a new project called BasicPyVLC and environment with Poetry
Use VLC to play audio in terminal for BasicPyVLC
Package VLC in a spec file
Package BasicPyVLC project on Windows using PyInstaller
Package BasicPyVLC project on Mac using PyInstaller
Check the packaged exe works on Windows and Mac with NO VLC previously installed (by testing on a Windows guest Virtual Machine sending the exe through shared network folders, dropbox, github, etc)
Step 1: Set up a new project (BasicPyVLC) and environment with Poetry
Goal:
Our goal is to create a new project folder and new virtual environment (virtualenv) so that we don't accidentally destroy our system's Python installation
Step 2: Create new folder and name it BasicPyVLC
How do I create a new folder?
Open File Explorer ( ⊞ Windows key > type "file explorer")
You can right click on an empty space in the folder of your choice and click "New > Folder"
In VS Code, use the default shortcut to bring up a new terminal
£C:\Users\KivySchool\ CTRL + J
Check if the terminal is in the folder you want to create your subfolder
If you need to change directory, the command is cd <folder location>
In your VS Code terminal, type
£C:\Users\KivySchool\ mkdir "KivyProjects"
You can also press ⊞ Windows key, type cmd and enter cd "Your folder location" to a folder of your choice
If not, you can skip this step and proceed with a virtualenv manager of your choice. You were warned
Poetry setup your virtual environment refresher
Open a terminal inside the BasicPyVLC folder.
How do I open a terminal in a folder?
Open VSCode
Use VSCode open folder option
The VSCode shortcut is:
CTRL + K CTRL + O
- This shortcut will open a terminal for you at the bottom of VSCode
CTRL + J
⊞ Windows key > type cmd > press Enter
Change drive if necessary (The default is C:\ drive but if you want to go to D:\ drive just type: D:)
Change directory to that folder (You can get the address by copying from the address bar of File Explorer)
cd (basicpyvlc-py3.10)D:\KivySchool\CODING\BasicPyVLC
In your terminal type:
£D:\KivySchool\CODING\BasicPyVLC poetry init
Press enter and complete the process.
Now you have a pyproject.toml file.
Next we add our dependencies, which in this case is python-vlc and PyInstaller.
If you get this error: The current project's Python requirement (>=3.10,<4.0) is not compatible with some of the required packages Python requirement:
pyinstaller requires Python <3.13,>=3.8, so it will not be satisfied for Python >=3.13,<4.0
You can fix it in two ways:
Add an upper bound to python version. This is because the upper bound is < 4.0 as a default, and PyInstaller is asking for Python version <3.13
pyproject.toml line:
python = "^3.10, <3.13"
Set a fixed Python version in pyproject.toml using python = "3.10.9"
Remember to use the correct Python version with Pyenv!
Poetry configuration pt 2
Remember to add .venv folder to your .gitignore folder so git doesn't track your environment.
Also add build and dist folders (these come from PyInstaller)
Sample .gitignore file:
.venv
build
dist
Step 4: Code + Explanation
basicpyvlc.py
1 2 3 4 5 6 7 8 9101112131415
importvlcimportosimporttimevlc_player=vlc.MediaPlayer()medianame="bigbuckbunny x265.mp4"mediapath=os.path.join(os.path.dirname(__file__),medianame)media=vlc.Media(mediapath)vlc_player.set_media(media)vlc_player.play()offset=5time.sleep(offset)length=vlc_player.get_length()#time in ms (divide by 1000 to get in seconds)newoffset=length/1000-offsettime.sleep(newoffset)
Basic imports
basicpyvlc.py
123
importvlcimportosimporttime
Initialize vlc
basicpyvlc.py
1
vlc_player=vlc.MediaPlayer()
Set name of mediafile
basicpyvlc.py
1
medianame="bigbuckbunny x265.mp4"
Here there are some filesystem tricks.
__file__ is the location of the file basicpyvlc.py in the filesystem.
os.path.dirname(__file__) gets the name of the directory where basicpyvlc.py is.
os.path.join creates a proper file path location on any OS. This is because Windows uses \ while Linux/Mac uses / as separators.
This approach also means it works on PyInstaller, because os.path.dirname(__file__) will resolve correctly to be sys._MEIPASS.
We will later add the media file to sys._MEIPASS and package VLC using PyInstaller. This way your distributable will be packaged properly and run on target machines that don't have VLC installed.
This is some timing code so that VLC plays. This is because without the initial time.sleep Python will immediately exit since there is no code keeping Python open. Since Python immediately exits, it will force VLC to exit as well, meaning you see nothing or a blank cwd window open and close.
The initial offset is to make sure VLC starts playing (since vlc_player.get_length() will return a bad value if called while VLC has not yet loaded any media) and then newoffset is created to hold Python open for the rest of the duration of the media. Newoffset might be incorrect and cut off media on slower/older systems because it assumes VLC immediately plays the video. There is time lag between the time it takes for Python to turn on VLC and for VLC to start playing the video.
basicpyvlc.py
12345
offset=5time.sleep(offset)length=vlc_player.get_length()#time in ms (divide by 1000 to get in seconds)newoffset=length/1000-offsettime.sleep(newoffset)
What happens if you comment out time.sleep?
If you comment out the first time.sleep, Python will exit early and VLC will not play at all, or you will see a cmd window open and close.
If you comment out the second time.sleep, Python will exit early and VLC will not get to finish playing the media.
Step 5: Running basicpyvlc.py in terminal in your virtualenv
Requirements
Make sure you are in the correct folder in your terminal (cd BasicPyVLCfolder),
To add libVLC.dll, append to binaries in the EXE (see example). The reason you need to add this is because the .exe will NOT work on a machine that does not have VLC installed. The .exe created WITHOUT this line WILL work on machines that DO have VLC installed.
What this line does is send medianame to the sys._MEIPASS folder. If you wanted to send it to a "media" folder in sys._MEIPASS, you would do datas=[(medianame, "media")], but then you would have to change everything else, like the mediapath in basicpyvlc.py, etc. See adding datas to specfile PyInstaller.
Since the path is os.path.dirname(__file__) for mediapath in basicpyvlc.py, it resolves properly when packaged in PyInstaller and when run from terminal.
Step 7: Use PyInstaller to Create an Exe
Now that the .spec file is finished (make sure to save), run this command:
After it's finished, check the exe out in your dist folder.
Step 8: Verifying EXE works on any target Windows machine
Goal:
Our goal is to make sure your .exe works on any Windows machine, regardless if VLC was previously installed. In order to do that we need two things: a virtual machine to test on and to package libVLC.dll in PyInstaller.
This is how I discovered that you need to package libVLC.dll to be able to run the .exe on machines that do not VLC installed. Without libVLC.dll packaged, the .exe runs on my dev machine well, BUT on the virtual machine that has no VLC installed, it crashes, saying
By making sure your .exe runs on a fresh Windows virtual machine instance (that has no pythonenv, no special modification to PATH, no VLC installed), you should be confident your .exe works anywhere.
The test was to use shared network folders to send your .exe to the VirtualBox Windows guest OS.
All you need to do is add libvlc.dll to the binaries line:
Once done, double click and run. If successful, your mediafile should play. Then copy over this .exe to your virtual machine, and it should run even without installing VLC on the virtual machine.
Step 1: Set up a new project (BasicPyVLC) and environment with Poetry
Goal:
Our goal is to create a new project folder and new virtual environment (virtualenv) so that we don't accidentally destroy our system's Python installation
If not, you can skip this step and proceed with a virtualenv manager of your choice. You were warned
Poetry setup your virtual environment refresher
Open a terminal inside the BasicPyVLC folder.
How do I open a terminal in a folder?
Open VSCode with ⌘+spacebar
Type Code, press Enter
Use VSCode open folder option
There is not open folder shortcut on Mac, you gotta do some clicking. Click File then Open Folder
This shortcut will open a terminal for you at the bottom of VSCode
⌘ + J
⌘+SPACE > type terminal > press Enter
Change drive if necessary. You can get a file location through Finder by CTRL+CLICK in a folder location > Get Info > CTRL+Click on Where, then Copy as Pathname.
Change directory to that folder
cd /Users/KivySchool/CODING/BasicPyVLC
In your terminal type:
£/Users/KivySchool/CODING/BasicPyVLC poetry init
Press enter and complete the process.
Now you have a pyproject.toml file.
Next we add our dependencies, which in this case is python-vlc and PyInstaller.
If you get this error: The current project's Python requirement (>=3.10,<4.0) is not compatible with some of the required packages Python requirement:
pyinstaller requires Python <3.13,>=3.8, so it will not be satisfied for Python >=3.13,<4.0
You can fix it in two ways:
Add an upper bound to python version. This is because the upper bound is < 4.0 as a default, and PyInstaller is asking for Python version <3.13
pyproject.toml line:
python = "^3.10, <3.13"
Set a fixed Python version in pyproject.toml using python = "3.10.9"
Remember to use the correct Python version with Pyenv!
Poetry configuration pt 2
Remember to add .venv folder to your .gitignore folder so git doesn't track your environment.
Also add build and dist folders (these come from PyInstaller)
Sample .gitignore file:
.venv
build
dist
Step 4: Code + Explanation
basicpyvlc.py
1 2 3 4 5 6 7 8 9101112131415161718192021
#vlc has a problem with mac displaying, see#problem description: https://github.com/PySimpleGUI/PySimpleGUI/issues/5581#solution: https://stackoverflow.com/a/75022685importosimportPySide6.QtWidgetsasQtWidgetsimportsysimportvlcvlc_player=vlc.MediaPlayer()medianame="bigbuckbunny x265.mp4"mediapath=os.path.join(os.path.dirname(__file__),medianame)media=vlc.Media(mediapath)vlc_player.set_media(media)vlcApp=QtWidgets.QApplication([])vlcWidget=QtWidgets.QFrame()vlcWidget.resize(700,700)vlcWidget.show()vlc_player.set_nsobject(vlcWidget.winId())vlc_player.play()#you need to play vlc first else the qtapp will just open and hold forevervlcApp.exec()
__file__ is the location of the file basicpyvlc.py in the filesystem.
os.path.dirname(__file__) gets the name of the directory where basicpyvlc.py is.
os.path.join creates a proper file path location on any OS. This is because Windows uses \ while Linux/Mac uses / as separators.
This approach also means it works on PyInstaller, because os.path.dirname(__file__) will resolve correctly to be sys._MEIPASS.
We will later add the media file to sys._MEIPASS and package VLC using PyInstaller. This way your distributable will be packaged properly and run on target machines that don't have VLC installed.
What happens if you vlcApp.exec() before playing with vlc_player.play() ?
If you do this, the Qtapp will just open and hold forever. Since VLC has not started, it will not recieve any audio data and you'll be forced to close the window manually, like with ⌘ + Q.
Step 5: Running basicpyvlc.py on Mac
Requirements
Make sure you are in the correct folder in your terminal (cd BasicPyVLCfolder),
There are three things to do to add VLC to PyInstaller:
Add VLC to pathex
Add plugins folder
Add libvlccore.dylib and libvlc.dylib (complicated, will be Step 8)
To add VLC to pathex, find VLC.app in your applications folder. You can do this by opening Finder, ⌘+SHIFT+G, typing /, and press Enter.
Then go to yourDrive/Applications/
pathex=["/Applications/VLC.app"],
To add the plugins folder, open up VLC.app by CTRL+CLICK on it, then click Open Package Contents. Then in Contents you will find the /MacOS/plugins/ folder.
Adding libvlccore.dylib and libvlc.dylib is complicated as of the time of writing because of a few bugs, see Step 8. Creating an app in this easy way means that it will NOT run on a Mac does NOT have VLC installed. You can verify this by removing VLC.app from the Applications folder. You can place VLC.app back after verifying.
What this line does is send medianame to the sys._MEIPASS folder. If you wanted to send it to a "media" folder in sys._MEIPASS, you would do datas=[(medianame, "media")], but then you would have to change everything else, like the mediapath in basicpyvlc.py, etc. See adding datas to specfile PyInstaller.
Since the path is os.path.dirname(__file__) for mediapath in basicpyvlc.py, it resolves properly when packaged in PyInstaller and when run from terminal.
After it's finished, check the .app file in your dist folder.
Step 8: Verifying EXE works on any target Mac machine
Goal:
Our goal is to make sure your .app works on any Mac machine, regardless if VLC.app is previously installed. In order to do that we need two things: a "virtual" machine to test on and to package libvlccore.dylib and libvlc.dylib in PyInstaller.
Instead, we can simulate a target Mac machine with no VLC installed by simply removing VLC.app from the Application folder. Remember to put it back after doing this tutorial!
You can always get a 2nd Mac as well ¯\_(ツ)_/¯, or get a friend that has one.
The harder part is that python-vlc itself has assumed VLC is previously installed, see this issue and the source code where vlc.py assumes that VLC.app is in the applications folder.
How do we fix this?
There is a series of problems, and I will provide an answer then go through the code.
Solution:
Fix the elif sys.platform.startswith('darwin'): block to search for libvlccore.dylib and libvlc.dylib in the proper location.
The original goal of the elif sys.platform.startswith('darwin'): block is to find libvlccore.dylib, lib/libvlc.dylib, and set the path for VLC modules and VLC plugins folders.
I've left the print statements to help you debug. All this does is set the base directory to PyInstaller's temp directory sys._MEIPASS then form a correct path in line 11 to make sure that VLC can find the modules and plugins folders.
vlc darwin replacement code.py
1 2 3 4 5 6 7 8 910111213141516
elifsys.platform.startswith('darwin'):d=sys._MEIPASSc=os.path.join(d,"libvlccore.dylib")p=os.path.join(d,"libvlc.dylib")print("paths exists and loaded?",c,p,os.path.exists(p),os.path.exists(c))ifos.path.exists(p)andos.path.exists(c):# pre-load libvlccore VLC 2.2.8+ctypes.CDLL(c)dll=ctypes.CDLL(p)forpin('modules','plugins'):p=os.path.join(d,p)print("newp?",p)ifos.path.isdir(p):plugin_path=pprint("pluginpath",plugin_path,os.path.exists(plugin_path))break
Easy Solution:
If you want to just have a quick and dirty solution, just modify vlc.py and compile it. However, now you have to track vlc.py in your .venv, and if you're ignoring your .venv folder through git, you're gonna have a BAD time when the .venv inevitably gets lost or remade.
Correct Solution:
Make a github issue on vlc-python's github (TBD).
Interim Solution:
Monkeypatching this code. This is significantly harder because not only does it have to be fixed, it has to be fixed while frozen in PyInstaller.
defmodify_and_import(module_name,package,modification_func):spec=importlib.util.find_spec(module_name,package)source=spec.loader.get_source(module_name)new_source=modification_func(source)print("new source changed?",type(source),new_source)print("check str1 in ",str1innew_source,str2innew_source)module=importlib.util.module_from_spec(spec)#this is always the killer line, because vlc runs find_lib() immediately AKA it explodes (only when packaging .pyc file with PyInstaller)codeobj=compile(new_source,module.__spec__.origin,'exec')exec(codeobj,module.__dict__)sys.modules[module_name]=modulereturnmodule#some time later...my_module=modify_and_import("vlc",None,lambdasrc:src.replace(str1,str2))importvlc
You add this line at the end of a = Analysis(, after noarchive=False,. This will force PyInstaller to collect only vlc.py and NOT vlc.pyc. This will then make source = spec.loader.get_source(module_name) return actual source code, and module = importlib.util.module_from_spec(spec) to run without executing find_lib() too early.
Note on src: src.replace(str1, str2):
This is just a string replace that replaces str1 with str2. There is room for optimization by only replacing the changed parts but dealing with matching newlines is a pain on its own. Here I just replaced the old function definition with the new function definition
str1=r'''def find_lib(): dll = None plugin_path = os.environ.get('PYTHON_VLC_MODULE_PATH', None) if 'PYTHON_VLC_LIB_PATH' in os.environ: try: dll = ctypes.CDLL(os.environ['PYTHON_VLC_LIB_PATH']) except OSError: logger.error("Cannot load lib specified by PYTHON_VLC_LIB_PATH env. variable") sys.exit(1) if plugin_path and not os.path.isdir(plugin_path): logger.error("Invalid PYTHON_VLC_MODULE_PATH specified. Please fix.") sys.exit(1) if dll is not None: return dll, plugin_path if sys.platform.startswith('win'): libname = 'libvlc.dll' p = find_library(libname) if p is None: try: # some registry settings # leaner than win32api, win32con if PYTHON3: import winreg as w else: import _winreg as w for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: try: r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') plugin_path, _ = w.QueryValueEx(r, 'InstallDir') w.CloseKey(r) break except w.error: pass except ImportError: # no PyWin32 pass if plugin_path is None: # try some standard locations. programfiles = os.environ["ProgramFiles"] homedir = os.environ["HOMEDRIVE"] for p in ('{programfiles}\\VideoLan{libname}', '{homedir}:\\VideoLan{libname}', '{programfiles}{libname}', '{homedir}:{libname}'): p = p.format(homedir = homedir, programfiles = programfiles, libname = '\\VLC\\' + libname) if os.path.exists(p): plugin_path = os.path.dirname(p) break if plugin_path is not None: # try loading # PyInstaller Windows fix if 'PyInstallerCDLL' in ctypes.CDLL.__name__: ctypes.windll.kernel32.SetDllDirectoryW(None) p = os.getcwd() os.chdir(plugin_path) # if chdir failed, this will raise an exception dll = ctypes.CDLL('.\\' + libname) # restore cwd after dll has been loaded os.chdir(p) else: # may fail dll = ctypes.CDLL('.\\' + libname) else: plugin_path = os.path.dirname(p) dll = ctypes.CDLL(p) elif sys.platform.startswith('darwin'): # FIXME: should find a means to configure path d = '/Applications/VLC.app/Contents/MacOS/' c = d + 'lib/libvlccore.dylib' p = d + 'lib/libvlc.dylib' if os.path.exists(p) and os.path.exists(c): # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL(c) dll = ctypes.CDLL(p) for p in ('modules', 'plugins'): p = d + p if os.path.isdir(p): plugin_path = p break else: # hope, some [DY]LD_LIBRARY_PATH is set... # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL('libvlccore.dylib') dll = ctypes.CDLL('libvlc.dylib') else: # All other OSes (linux, freebsd...) p = find_library('vlc') try: dll = ctypes.CDLL(p) except OSError: # may fail dll = None if dll is None: try: dll = ctypes.CDLL('libvlc.so.5') except: raise NotImplementedError('Cannot find libvlc lib') return (dll, plugin_path)'''
str2=r'''def find_lib(): dll = None plugin_path = os.environ.get('PYTHON_VLC_MODULE_PATH', None) if 'PYTHON_VLC_LIB_PATH' in os.environ: try: dll = ctypes.CDLL(os.environ['PYTHON_VLC_LIB_PATH']) except OSError: logger.error("Cannot load lib specified by PYTHON_VLC_LIB_PATH env. variable") sys.exit(1) if plugin_path and not os.path.isdir(plugin_path): logger.error("Invalid PYTHON_VLC_MODULE_PATH specified. Please fix.") sys.exit(1) if dll is not None: return dll, plugin_path if sys.platform.startswith('win'): libname = 'libvlc.dll' p = find_library(libname) if p is None: try: # some registry settings # leaner than win32api, win32con if PYTHON3: import winreg as w else: import _winreg as w for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: try: r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') plugin_path, _ = w.QueryValueEx(r, 'InstallDir') w.CloseKey(r) break except w.error: pass except ImportError: # no PyWin32 pass if plugin_path is None: # try some standard locations. programfiles = os.environ["ProgramFiles"] homedir = os.environ["HOMEDRIVE"] for p in ('{programfiles}\\VideoLan{libname}', '{homedir}:\\VideoLan{libname}', '{programfiles}{libname}', '{homedir}:{libname}'): p = p.format(homedir = homedir, programfiles = programfiles, libname = '\\VLC\\' + libname) if os.path.exists(p): plugin_path = os.path.dirname(p) break if plugin_path is not None: # try loading # PyInstaller Windows fix if 'PyInstallerCDLL' in ctypes.CDLL.__name__: ctypes.windll.kernel32.SetDllDirectoryW(None) p = os.getcwd() os.chdir(plugin_path) # if chdir failed, this will raise an exception dll = ctypes.CDLL('.\\' + libname) # restore cwd after dll has been loaded os.chdir(p) else: # may fail dll = ctypes.CDLL('.\\' + libname) else: plugin_path = os.path.dirname(p) dll = ctypes.CDLL(p) elif sys.platform.startswith('darwin'): d = sys._MEIPASS c = os.path.join(d, "libvlccore.dylib") p = os.path.join(d, "libvlc.dylib") print("paths exists and loaded?", c, p, os.path.exists(p), os.path.exists(c)) if os.path.exists(p) and os.path.exists(c): # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL(c) dll = ctypes.CDLL(p) for p in ('modules', 'plugins'): p = os.path.join(d, p) print("newp?", p) if os.path.isdir(p): plugin_path = p print("pluginpath", plugin_path, os.path.exists(plugin_path)) break else: # hope, some [DY]LD_LIBRARY_PATH is set... # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL('libvlccore.dylib') dll = ctypes.CDLL('libvlc.dylib') else: # All other OSes (linux, freebsd...) p = find_library('vlc') try: dll = ctypes.CDLL(p) except OSError: # may fail dll = None if dll is None: try: dll = ctypes.CDLL('libvlc.so.5') except: raise NotImplementedError('Cannot find libvlc lib') return (dll, plugin_path) '''
Another note: it's actually lucky that vlc.py is a monolith py file and doesn't really have any external dependencies to package besides 2 dylib files. This approach might be harder for larger modules and/or there will be other loading issues.
Example basicpyvlc.py
basicpyvlc.py
#vlc has a problem with mac displaying, see#problem description: https://github.com/PySimpleGUI/PySimpleGUI/issues/5581#solution: https://stackoverflow.com/a/75022685importosimportPySide6.QtWidgetsasQtWidgetsimportsysstr1=r'''def find_lib(): dll = None plugin_path = os.environ.get('PYTHON_VLC_MODULE_PATH', None) if 'PYTHON_VLC_LIB_PATH' in os.environ: try: dll = ctypes.CDLL(os.environ['PYTHON_VLC_LIB_PATH']) except OSError: logger.error("Cannot load lib specified by PYTHON_VLC_LIB_PATH env. variable") sys.exit(1) if plugin_path and not os.path.isdir(plugin_path): logger.error("Invalid PYTHON_VLC_MODULE_PATH specified. Please fix.") sys.exit(1) if dll is not None: return dll, plugin_path if sys.platform.startswith('win'): libname = 'libvlc.dll' p = find_library(libname) if p is None: try: # some registry settings # leaner than win32api, win32con if PYTHON3: import winreg as w else: import _winreg as w for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: try: r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') plugin_path, _ = w.QueryValueEx(r, 'InstallDir') w.CloseKey(r) break except w.error: pass except ImportError: # no PyWin32 pass if plugin_path is None: # try some standard locations. programfiles = os.environ["ProgramFiles"] homedir = os.environ["HOMEDRIVE"] for p in ('{programfiles}\\VideoLan{libname}', '{homedir}:\\VideoLan{libname}', '{programfiles}{libname}', '{homedir}:{libname}'): p = p.format(homedir = homedir, programfiles = programfiles, libname = '\\VLC\\' + libname) if os.path.exists(p): plugin_path = os.path.dirname(p) break if plugin_path is not None: # try loading # PyInstaller Windows fix if 'PyInstallerCDLL' in ctypes.CDLL.__name__: ctypes.windll.kernel32.SetDllDirectoryW(None) p = os.getcwd() os.chdir(plugin_path) # if chdir failed, this will raise an exception dll = ctypes.CDLL('.\\' + libname) # restore cwd after dll has been loaded os.chdir(p) else: # may fail dll = ctypes.CDLL('.\\' + libname) else: plugin_path = os.path.dirname(p) dll = ctypes.CDLL(p) elif sys.platform.startswith('darwin'): # FIXME: should find a means to configure path d = '/Applications/VLC.app/Contents/MacOS/' c = d + 'lib/libvlccore.dylib' p = d + 'lib/libvlc.dylib' if os.path.exists(p) and os.path.exists(c): # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL(c) dll = ctypes.CDLL(p) for p in ('modules', 'plugins'): p = d + p if os.path.isdir(p): plugin_path = p break else: # hope, some [DY]LD_LIBRARY_PATH is set... # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL('libvlccore.dylib') dll = ctypes.CDLL('libvlc.dylib') else: # All other OSes (linux, freebsd...) p = find_library('vlc') try: dll = ctypes.CDLL(p) except OSError: # may fail dll = None if dll is None: try: dll = ctypes.CDLL('libvlc.so.5') except: raise NotImplementedError('Cannot find libvlc lib') return (dll, plugin_path)'''str2=r'''def find_lib(): dll = None plugin_path = os.environ.get('PYTHON_VLC_MODULE_PATH', None) if 'PYTHON_VLC_LIB_PATH' in os.environ: try: dll = ctypes.CDLL(os.environ['PYTHON_VLC_LIB_PATH']) except OSError: logger.error("Cannot load lib specified by PYTHON_VLC_LIB_PATH env. variable") sys.exit(1) if plugin_path and not os.path.isdir(plugin_path): logger.error("Invalid PYTHON_VLC_MODULE_PATH specified. Please fix.") sys.exit(1) if dll is not None: return dll, plugin_path if sys.platform.startswith('win'): libname = 'libvlc.dll' p = find_library(libname) if p is None: try: # some registry settings # leaner than win32api, win32con if PYTHON3: import winreg as w else: import _winreg as w for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: try: r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') plugin_path, _ = w.QueryValueEx(r, 'InstallDir') w.CloseKey(r) break except w.error: pass except ImportError: # no PyWin32 pass if plugin_path is None: # try some standard locations. programfiles = os.environ["ProgramFiles"] homedir = os.environ["HOMEDRIVE"] for p in ('{programfiles}\\VideoLan{libname}', '{homedir}:\\VideoLan{libname}', '{programfiles}{libname}', '{homedir}:{libname}'): p = p.format(homedir = homedir, programfiles = programfiles, libname = '\\VLC\\' + libname) if os.path.exists(p): plugin_path = os.path.dirname(p) break if plugin_path is not None: # try loading # PyInstaller Windows fix if 'PyInstallerCDLL' in ctypes.CDLL.__name__: ctypes.windll.kernel32.SetDllDirectoryW(None) p = os.getcwd() os.chdir(plugin_path) # if chdir failed, this will raise an exception dll = ctypes.CDLL('.\\' + libname) # restore cwd after dll has been loaded os.chdir(p) else: # may fail dll = ctypes.CDLL('.\\' + libname) else: plugin_path = os.path.dirname(p) dll = ctypes.CDLL(p) elif sys.platform.startswith('darwin'): d = sys._MEIPASS c = os.path.join(d, "libvlccore.dylib") p = os.path.join(d, "libvlc.dylib") print("paths exists and loaded?", c, p, os.path.exists(p), os.path.exists(c)) if os.path.exists(p) and os.path.exists(c): # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL(c) dll = ctypes.CDLL(p) for p in ('modules', 'plugins'): p = os.path.join(d, p) print("newp?", p) if os.path.isdir(p): plugin_path = p print("pluginpath", plugin_path, os.path.exists(plugin_path)) break else: # hope, some [DY]LD_LIBRARY_PATH is set... # pre-load libvlccore VLC 2.2.8+ ctypes.CDLL('libvlccore.dylib') dll = ctypes.CDLL('libvlc.dylib') else: # All other OSes (linux, freebsd...) p = find_library('vlc') try: dll = ctypes.CDLL(p) except OSError: # may fail dll = None if dll is None: try: dll = ctypes.CDLL('libvlc.so.5') except: raise NotImplementedError('Cannot find libvlc lib') return (dll, plugin_path)'''importimportlib# https://stackoverflow.com/questions/41858147/how-to-modify-imported-source-code-on-the-flydefmodify_and_import(module_name,package,modification_func):spec=importlib.util.find_spec(module_name,package)source=spec.loader.get_source(module_name)new_source=modification_func(source)print("new source changed?",type(source),new_source)print("check str1 in ",str1innew_source,str2innew_source)module=importlib.util.module_from_spec(spec)#this is always the killer line, because vlc runs find_lib() immediately AKA it explodes (only when packaging .pyc file with PyInstaller)codeobj=compile(new_source,module.__spec__.origin,'exec')exec(codeobj,module.__dict__)sys.modules[module_name]=modulereturnmoduleprint("trying mod!",flush=True)#checking to see if pyinstaller module_collection_mode py sends the py file to tmpdirmy_module=modify_and_import("vlc",None,lambdasrc:src.replace(str1,str2))importvlcvlc_player=vlc.MediaPlayer()medianame="bigbuckbunny x265.mp4"mediapath=os.path.join(os.path.dirname(__file__),medianame)media=vlc.Media(mediapath)vlc_player.set_media(media)vlcApp=QtWidgets.QApplication([])vlcWidget=QtWidgets.QFrame()vlcWidget.resize(700,700)vlcWidget.show()vlc_player.set_nsobject(vlcWidget.winId())vlc_player.play()#you need to play vlc first else the qtapp will just open and hold forevervlcApp.exec()
Now the last step is to fix the locations of pathex=, binaries=, a.binaries=, and add the bundle line in the .specfile.
pathex= wants the location of VLC.app, it can be found by searching through Finder.
binaries= wants the location of the plugins folder, it can be found by opening up VLC.app by CTRL+CLICK on it, then click Open Package Contents. Then in Contents you will find the /MacOS/plugins/ folder.
a.binaries= wants the location of two files: libvlc.dylib and libvlccore.dylib. Those can be found in the VLC.app/Contents/MacOS/lib/ folder after opening up VLC.app using CTRL+CLICK and Open Package Contents.
Now with those changes, compile the .app with this command
Then on your machine with VLC.app removed from the Application folder, it will work:
Since this was done with VLC.app removed from the Applications folder, it will also work on target Mac machines that do not have VLC.app installed.
Once done, double click and run. If successful, your mediafile should play.
After these changes, put back Vlc.app to the Applications folder and fix filepaths accordingly.
On Windows, you can run .exe in a cmd window by typing something like BasicPyVLC.exe when your current working directory contains BasicPyVLC.exe. That way if it shows up and disappears you can still see the logs and dont have to screen record/print screen/add time.sleep code.