d0ea94447a
- Added kivy.lib.gstplayer to excluded_imports (we use ffpyplayer)
- Added all cefpython3_py{27,34-311}.pyd to excluded_imports
- Removes ~300 lines of library-not-found warnings from build output
- Reduces .exe size from 143 MB to 96 MB
257 lines
7.1 KiB
RPMSpec
257 lines
7.1 KiB
RPMSpec
# -*- mode: python ; coding: utf-8 -*-
|
|
"""
|
|
PyInstaller spec file for Kiwy Signage Player (Windows .exe)
|
|
|
|
Build command (from windows/ directory):
|
|
pyinstaller build.spec --clean --noconfirm
|
|
|
|
OR use the build script:
|
|
build_win.bat
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# --- Paths -----------------------------------------------------------
|
|
# This spec file is in windows/build.spec, so the project root is
|
|
# always two levels up from this file's real location.
|
|
# __file__ may not be available in PyInstaller spec context fallback to cwd.
|
|
try:
|
|
_spec_dir = Path(__file__).resolve().parent
|
|
except NameError:
|
|
_spec_dir = Path(os.getcwd()).resolve()
|
|
# _spec_dir is now the absolute path to the windows/ directory
|
|
BUILD_DIR = _spec_dir
|
|
ROOT_DIR = BUILD_DIR.parent
|
|
SRC_DIR = ROOT_DIR / 'src'
|
|
CONFIG_DIR = ROOT_DIR / 'config'
|
|
RESOURCES_DIR = CONFIG_DIR / 'resources'
|
|
|
|
# --- Determine hidden imports that PyInstaller might miss -------------
|
|
hidden_imports = [
|
|
# Kivy core modules
|
|
'kivy.core.window',
|
|
'kivy.core.video',
|
|
'kivy.core.audio',
|
|
'kivy.core.text',
|
|
'kivy.core.image',
|
|
'kivy.core.gl',
|
|
'kivy.core.camera',
|
|
'kivy.core.clipboard',
|
|
'kivy.core.spelling',
|
|
'kivy.core.text.markup',
|
|
'kivy.core.window.window_sdl2',
|
|
'kivy.core.image.img_sdl2',
|
|
'kivy.core.video.video_ffpyplayer',
|
|
'kivy.core.audio.audio_ffpyplayer',
|
|
# Kivy modules
|
|
'kivy.uix.video',
|
|
'kivy.uix.vkeyboard',
|
|
'kivy.uix.popup',
|
|
'kivy.uix.image',
|
|
'kivy.uix.button',
|
|
'kivy.uix.label',
|
|
'kivy.uix.textinput',
|
|
'kivy.uix.boxlayout',
|
|
'kivy.uix.floatlayout',
|
|
'kivy.uix.slider',
|
|
'kivy.uix.widget',
|
|
'kivy.uix.checkbox',
|
|
'kivy.graphics',
|
|
'kivy.graphics.texture',
|
|
'kivy.graphics.vertex_instructions',
|
|
'kivy.graphics.context_instructions',
|
|
'kivy.clock',
|
|
'kivy.loader',
|
|
'kivy.animation',
|
|
'kivy.lang',
|
|
'kivy.logger',
|
|
'kivy.config',
|
|
'kivy.properties',
|
|
'kivy.metrics',
|
|
'kivy.factory',
|
|
# Graphics providers
|
|
'kivy.graphics.opengl',
|
|
'kivy.graphics.opengl_utils',
|
|
'kivy.graphics.fbo',
|
|
'kivy.graphics.gl_instructions',
|
|
'kivy.graphics.stencil_instructions',
|
|
'kivy.graphics.scissor_instructions',
|
|
'kivy.graphics.buffer',
|
|
'kivy.graphics.vbo',
|
|
'kivy.graphics.shader',
|
|
'kivy.graphics.compiler',
|
|
# ffpyplayer
|
|
'ffpyplayer',
|
|
'ffpyplayer.player',
|
|
'ffpyplayer.pic',
|
|
'ffpyplayer.writer',
|
|
# Networking
|
|
'requests',
|
|
'aiohttp',
|
|
'urllib3',
|
|
'certifi',
|
|
'bcrypt',
|
|
# Platform
|
|
'ctypes',
|
|
'ctypes.wintypes',
|
|
'subprocess',
|
|
'shutil',
|
|
'glob',
|
|
'selectors',
|
|
'tempfile',
|
|
# Windows-specific
|
|
'cef_browser',
|
|
'win32gui',
|
|
'win32con',
|
|
]
|
|
|
|
# Exclude Linux-only modules
|
|
excluded_imports = [
|
|
'gi', # GTK introspection (Linux)
|
|
'gi.repository',
|
|
'evdev', # We inject a fake evdev module in run_win.py
|
|
# GStreamer — we use ffpyplayer, not GStreamer
|
|
'kivy.lib.gstplayer',
|
|
# cefpython3: keep only Python 3.12 .pyd, exclude other version .pyd files
|
|
'cefpython3.cefpython_py27',
|
|
'cefpython3.cefpython_py34',
|
|
'cefpython3.cefpython_py35',
|
|
'cefpython3.cefpython_py36',
|
|
'cefpython3.cefpython_py37',
|
|
'cefpython3.cefpython_py38',
|
|
'cefpython3.cefpython_py39',
|
|
'cefpython3.cefpython_py310',
|
|
'cefpython3.cefpython_py311',
|
|
]
|
|
|
|
# --- Application data files to bundle --------------------------------
|
|
# Resources (icons, intro video, etc.)
|
|
resources_data = []
|
|
for item in RESOURCES_DIR.iterdir():
|
|
if item.is_file():
|
|
target_dir = 'config/resources'
|
|
resources_data.append((str(item), target_dir))
|
|
|
|
# Config directory (app_config.json)
|
|
config_data = []
|
|
config_file = CONFIG_DIR / 'app_config.json'
|
|
if config_file.exists():
|
|
config_data.append((str(config_file), 'config'))
|
|
|
|
# Source files - .kv file
|
|
kv_file = SRC_DIR / 'signage_player.kv'
|
|
kv_data = []
|
|
if kv_file.exists():
|
|
kv_data.append((str(kv_file), '.'))
|
|
|
|
# Bundle the entire src directory as a tree
|
|
source_tree = Tree(str(SRC_DIR), prefix='', excludes=['*.pyc', '__pycache__', '*.ini'])
|
|
|
|
# --- Collect binary DLLs from kivy_deps and ffpyplayer ----------------
|
|
import importlib.util
|
|
from pathlib import Path as _Path
|
|
|
|
def _find_share_dlls(package_path, subdir='bin'):
|
|
"""Find .dll files under a package's share/ directory."""
|
|
if not package_path:
|
|
return []
|
|
base = _Path(package_path).parent
|
|
# Check: share/<pkg>/bin/ relative to parent
|
|
share = base / 'share'
|
|
if share.is_dir():
|
|
results = []
|
|
for root, dirs, files in os.walk(share):
|
|
for f in files:
|
|
if f.endswith('.dll'):
|
|
results.append((os.path.join(root, f), '.'))
|
|
return results
|
|
return []
|
|
|
|
# SDL2 DLLs
|
|
_sdl2_spec = importlib.util.find_spec('kivy_deps.sdl2')
|
|
_sdl2_dlls = _find_share_dlls(_sdl2_spec.origin if _sdl2_spec else None)
|
|
|
|
# ANGLE DLLs
|
|
_angle_spec = importlib.util.find_spec('kivy_deps.angle')
|
|
_angle_dlls = _find_share_dlls(_angle_spec.origin if _angle_spec else None)
|
|
|
|
# GLEW DLLs
|
|
_glew_spec = importlib.util.find_spec('kivy_deps.glew')
|
|
_glew_dlls = _find_share_dlls(_glew_spec.origin if _glew_spec else None)
|
|
|
|
# ffpyplayer FFmpeg DLLs
|
|
_ffpy_spec = importlib.util.find_spec('ffpyplayer')
|
|
_ffpy_dlls = _find_share_dlls(_ffpy_spec.origin if _ffpy_spec else None)
|
|
|
|
_all_binaries = _sdl2_dlls + _angle_dlls + _glew_dlls + _ffpy_dlls
|
|
|
|
if not _all_binaries:
|
|
print("=" * 70)
|
|
print("WARNING: No Kivy/ffpyplayer DLLs found via share/ directories.")
|
|
print("PyInstaller may still auto-detect them, but if the .exe")
|
|
print("fails with 'SDL2.dll not found' or similar, you will need")
|
|
print("to manually add the DLL paths to the spec file.")
|
|
print("=" * 70)
|
|
|
|
# --- Build the .exe --------------------------------------------------
|
|
a = Analysis(
|
|
['run_win.py'], # Entry point (relative to this spec)
|
|
pathex=[str(BUILD_DIR), str(SRC_DIR)], # Where to find modules
|
|
binaries=_all_binaries,
|
|
datas=resources_data + config_data + kv_data,
|
|
hiddenimports=hidden_imports,
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[str(BUILD_DIR / 'pyi_runtime_hook.py')],
|
|
excludes=excluded_imports,
|
|
noarchive=False,
|
|
module_collection_mode={
|
|
'kivy': 'pyz',
|
|
'kivy.core': 'pyz',
|
|
'kivy.uix': 'pyz',
|
|
'kivy.graphics': 'pyz',
|
|
},
|
|
)
|
|
|
|
# Add the source tree (main.py, etc.)
|
|
a.datas += source_tree
|
|
|
|
pyz = PYZ(a.pure)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
a.binaries,
|
|
a.zipfiles,
|
|
a.datas,
|
|
[],
|
|
name='KiwySignagePlayer',
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
upx=True,
|
|
upx_exclude=[],
|
|
runtime_tmpdir=None,
|
|
console=True, # Show console for debugging startup errors
|
|
disable_windowed_traceback=False,
|
|
argv_emulation=False,
|
|
target_arch=None,
|
|
codesign_identity=None,
|
|
entitlements_file=None,
|
|
icon=str(RESOURCES_DIR / 'app_icon.ico') if (RESOURCES_DIR / 'app_icon.ico').exists() else None,
|
|
)
|
|
|
|
# --- COLLECT everything into a single folder -------------------------
|
|
coll = COLLECT(
|
|
exe,
|
|
a.binaries,
|
|
a.zipfiles,
|
|
a.datas,
|
|
strip=False,
|
|
upx=True,
|
|
upx_exclude=[],
|
|
name='KiwySignagePlayer',
|
|
)
|