-1

I have a simple kivy file in which i want to cover the entire canvas with an image bgi.jpg

MainWidget:    
<MainWidget>:
        canvas.before:
            Rectangle:
                size:self.size
                source:'bgi.jpg'

and the .py file code is

from kivy.app import App
from kivy.uix.widget import Widget
class MainWidget(Widget):pass
class TestApp(App):pass
TestApp().run()

I tried to build this code into .exe file using auot-py-to-exe. i added both kivy file and the image file as a additional file and tried to build into exe but the executable file is crashing with the error as shown below

Traceback (most recent call last):
  File "kivy\lang\builder.py", line 925, in _build_canvas
  File "kivy\graphics\instructions.pyx", line 380, in kivy.graphics.instructions.VertexInstruction.source.__set__
  File "kivy\graphics\context_instructions.pyx", line 431, in kivy.graphics.context_instructions.BindTexture.source.__set__
  File "kivy\core\image\__init__.py", line 561, in __init__
  File "kivy\core\image\__init__.py", line 754, in _set_filename
  File "kivy\core\image\__init__.py", line 460, in load
  File "kivy\core\image\__init__.py", line 223, in __init__
  File "kivy\core\image\img_sdl2.py", line 47, in load
Exception: SDL2: Unable to load image

During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "Main.py", line 9, in <module>
      File "kivy\app.py", line 954, in run
      File "kivy\app.py", line 923, in _run_prepare
      File "kivy\app.py", line 696, in load_kv
      File "kivy\lang\builder.py", line 305, in load_file
      File "kivy\lang\builder.py", line 405, in load_string
      File "kivy\uix\widget.py", line 470, in apply_class_lang_rules
      File "kivy\lang\builder.py", line 540, in apply
      File "kivy\lang\builder.py", line 595, in _apply_rule
      File "kivy\lang\builder.py", line 928, in _build_canvas
    kivy.lang.builder.BuilderException: Parser: File "C:\Users\User\Documents\Python scripts\Python class\Kivy projects\exe test\Main with image\test.kv", line 6:
    ...
          4:        Rectangle:
          5:            size:self.size
    >>    6:            source:'bgi.jpg'
          7:    Label:
          8:        id:lbl
    ...
    Exception: SDL2: Unable to load image
      File "kivy\lang\builder.py", line 925, in _build_canvas
      File "kivy\graphics\instructions.pyx", line 380, in kivy.graphics.instructions.VertexInstruction.source.__set__
      File "kivy\graphics\context_instructions.pyx", line 431, in kivy.graphics.context_instructions.BindTexture.source.__set__
      File "kivy\core\image\__init__.py", line 561, in __init__
      File "kivy\core\image\__init__.py", line 754, in _set_filename
      File "kivy\core\image\__init__.py", line 460, in load
      File "kivy\core\image\__init__.py", line 223, in __init__
      File "kivy\core\image\img_sdl2.py", line 47, in load

I am fairly new to both kivy,python and pyinstaller, so i have no idea what is wrong or should image files be added in a different method

Vishnu Balaji
  • 175
  • 1
  • 11

2 Answers2

0

auto-py-to-exe is not able to create exe file from Kivy project. It's a GUI for PyInstaller with limitations which won't allow you make required modifications to generate proper exe file.

In general you should follow the Kivy documentation: https://kivy.org/doc/stable/guide/packaging-windows.html and use pure PyInstaller .

But in my opinion the amount of provided informations/scenarios may be overwhelming, so here is my tutorial:

  1. Add following lines to your main python script (usually main.py), on top of file:

     import os, sys
     if sys.__stdout__ is None or sys.__stderr__ is None:
         os.environ['KIVY_NO_CONSOLELOG'] = '1'
     from kivy.resources import resource_add_path, resource_find
     if hasattr(sys, '_MEIPASS'):
         resource_add_path(os.path.join(sys._MEIPASS))
    
  2. Download PyInstaller package

     pip install pyinstaller
    

    Currently latest PyInstaller version is 5.7.0, you can specify it if you want to:

     pip install pyinstaller==5.7.0
    
  3. Create PyInstaller spec file

     pyi-makespec --noconsole --onefile --name "main" --add-data="*.kv;." --add-data="*.jpg;." "main.py"
    

    Notes:

         - noconsole will remove console window
         - onefile will create all-in-one exe file
         - name specify spec and final exe file name
         - add-data will include specified file/files into exe. You can use wildcards here like *.jpg
         - last param is your main Python script file name
    

    pyi-makespec will create following "main.spec" file:

     # -*- mode: python ; coding: utf-8 -*-
    
     block_cipher = None
    
     a = Analysis(
         ['main.py'],
         pathex=[],
         binaries=[],
         datas=[('*.kv', '.'), ('*.jpg', '.')],
         hiddenimports=[],
         hookspath=[],
         hooksconfig={},
         runtime_hooks=[],
         excludes=[],
         win_no_prefer_redirects=False,
         win_private_assemblies=False,
         cipher=block_cipher,
         noarchive=False,
     )
     pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
    
     exe = EXE(
         pyz,
         a.scripts,
         a.binaries,
         a.zipfiles,
         a.datas,
         [],
         name='main',
         debug=False,
         bootloader_ignore_signals=False,
         strip=False,
         upx=True,
         upx_exclude=[],
         runtime_tmpdir=None,
         console=False,
         disable_windowed_traceback=False,
         argv_emulation=False,
         target_arch=None,
         codesign_identity=None,
         entitlements_file=None,
     )
    

    PyInstaller spec file is in fact Python script. You have to modify it to be able to build proper exe for Kivy based project. If you decide for example to enable console, you don't have to generate new spec file using pyi-makespec command. Just modify already generated spec file, which contain all parameters you passed to pyi-makespec first time. It's easy to match pyi-makespec arguments within "main.spec" file content. For instance if you want to enable console, then you have to change "console=False," to "console=True,". If you want to add another file to exe, like "image.png", just change "datas=[('.kv', '.'), ('.jpg', '.')]," to "datas=[('.kv', '.'), ('.jpg', '.'), ('image.png', '.')],". If you want to change final exe name from "main.exe" to "my_app.exe" just change "name='main'," to "name='my_app',".

    But if you want to create one-folder with binaries instead of all-in-one exe file, you have to use pyi-makespec command again, and remove "--onefile" argument. The spec file for onefile and onefolder is slightly different.

  4. Add Kivy specific lines to "main.spec" file

    Compared to original spec file two lines are added:

     from kivy_deps import sdl2, glew, gstreamer  # Kivy imports
     *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],  # Kivy binaries
    

    Here is modified "main.spec" file:

     # -*- mode: python ; coding: utf-8 -*-
     from kivy_deps import sdl2, glew, gstreamer  # Kivy imports
    
     block_cipher = None
    
     a = Analysis(
         ['main.py'],
         pathex=[],
         binaries=[],
         datas=[('*.kv', '.'), ('*.jpg', '.')],
         hiddenimports=[],
         hookspath=[],
         hooksconfig={},
         runtime_hooks=[],
         excludes=[],
         win_no_prefer_redirects=False,
         win_private_assemblies=False,
         cipher=block_cipher,
         noarchive=False,
     )
     pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
    
     exe = EXE(
         pyz,
         a.scripts,
         a.binaries,
         a.zipfiles,
         a.datas,
         *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],  # Kivy binaries excluding gstreamer.dep_bins
         [],
         name='main',
         debug=False,
         bootloader_ignore_signals=False,
         strip=False,
         upx=True,
         upx_exclude=[],
         runtime_tmpdir=None,
         console=False,
         disable_windowed_traceback=False,
         argv_emulation=False,
         target_arch=None,
         codesign_identity=None,
         entitlements_file=None,
     )
    

    Please keep in mind, that "Kivy binaries" line is added under "a.datas," line. It's new line, and it have to be placed here.

    If your app will use video content played by gstreamer, you will have to modify line:

     *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
    

    to

     *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins + gstreamer.dep_bins)],
    

    In result your exe file size may be bigger. You can read about different scenarios on https://kivy.org/doc/stable/guide/packaging-windows.html

  5. Create exe file

     pyinstaller --clean "main.spec"
    

    It will create "build" and "dist" folders. First one is a temporary folder used by PyInstaller to collect required files. Within second one you will find your generated exe file (main.exe).

    If you remove "--clean" argument next build attempts will be faster, because PyInstaller will not collect required packages again. But if you for example uninstall some packages using "pip uninstall" because you decide to not use it anymore in your project, the exe size will not decrease, as long previously collected dependencies will be still stored in your exe file. To execute collect step again just keep "--clean" argument.

That's all. auto-py-to-exe is not able to include required lines into spec file, as it hides generated spec file from users's eyes.

Produced exe file is standalone. There is no need to install Python or any Python package on end user PC to run such exe file.

Python interpreter with all dependencies are included into this exe. When exe is started all content is silently unpacked to temporary folder, and folder is removed when app is closed properly.

MST
  • 651
  • 1
  • 4
  • 6
  • Tried your solution and it worked thanks a lot, now my doubt is what if the .jpg files were inside a sub folder say like './A/B/bgi.jpg' ,how will the line datas=[('*.kv', '.'), ('*.jpg', '.')] change – Vishnu Balaji Jan 20 '23 at 16:16
  • Just add folder with file, for example to add all files from specified folder use: ('my_folder/*', 'my_folder'). To add only A/B/bgi.jpg use: ('A/B/bgi.jpg', 'A/B'). It'a a Python tuple with two values. First is source path to file or files, second one is path iniside exe where files will be stored. – MST Jan 20 '23 at 19:31
0

I am not very familiar with auto-py-to-exe but I was able to compile and run your project code successfully with pyinstaller with very minor alterations.

First I put the last line of your script under an if __name__ guard.

testapp.py

from kivy.app import App
from kivy.uix.widget import Widget
class MainWidget(Widget):pass
class TestApp(App):pass
if __name__ == "__main__":
    TestApp().run()

Then I fixed the indentation in your testapp.kv.

testapp.kv

MainWidget:
<MainWidget>:
    canvas.before:
        Rectangle:
            size:self.size
            source:'bgi.jpg'

Then run:

pyinstaller -F --add-data testapp.kv;. --add-data bgi.jpg;. testapp.py

Then run the executable:

dist\main.exe
Alexander
  • 16,091
  • 5
  • 13
  • 29