3845830a86
- New windows/ directory with build scripts, specs, and configuration - Windows-specific requirements (requirements_win.txt) - Launch and runtime scripts for Windows (run_win.py, launch_player.bat) - PyInstaller build configuration (build.spec) - Updated .gitignore to exclude windows/venv312/ - Updated config and source files for Windows compatibility - Moved working_files to proper directory
132 lines
5.5 KiB
Python
132 lines
5.5 KiB
Python
"""
|
|
PyInstaller Runtime Hook for Kiwy Signage Player
|
|
------------------------------------------------
|
|
Runs at startup of the packaged .exe to fix paths and environment.
|
|
Creates all necessary folders LOCAL to the executable's directory.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import platform
|
|
from pathlib import Path
|
|
|
|
# ── IMPORTANT: Set Windows environment BEFORE any Kivy code runs ──
|
|
# This must happen before main.py's top-level code executes, because
|
|
# main.py sets SDL_VIDEODRIVER=wayland,x11,dummy which would crash on Windows.
|
|
os.environ['SDL_VIDEODRIVER'] = 'windows'
|
|
os.environ['SDL_AUDIODRIVER'] = 'directsound'
|
|
os.environ['KIVY_WINDOW'] = 'sdl2'
|
|
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'
|
|
os.environ['KIVY_VIDEO'] = 'ffpyplayer'
|
|
os.environ['KIVY_AUDIO'] = 'ffpyplayer'
|
|
os.environ['FFPYPLAYER_CODECS'] = 'h264,h265,vp9,vp8'
|
|
os.environ['SDL_VIDEO_ALLOW_SCREENSAVER'] = '0'
|
|
os.environ['KIVY_NO_FILELOG'] = '1'
|
|
os.environ['KIVY_INPUTPROVIDERS'] = '' # Let Kivy auto-detect on Windows
|
|
|
|
# ── Capture ALL early output to a crash log ─────────────────────────
|
|
# Ensure we catch any exception that happens before Logger is available.
|
|
_startup_log_path = None
|
|
try:
|
|
_exe_dir = Path(sys.executable).parent
|
|
_startup_log_path = _exe_dir / 'logs' / 'startup_crash.log'
|
|
(_startup_log_path.parent).mkdir(parents=True, exist_ok=True)
|
|
with open(_startup_log_path, 'w') as _f:
|
|
_f.write("pyi_runtime_hook.py started\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _setup_paths():
|
|
"""Ensure the app can find its bundled files at runtime.
|
|
|
|
All data folders (config, media, playlists, logs) are created
|
|
LOCAL to the executable's directory — NOT in %%APPDATA%%.
|
|
"""
|
|
# In PyInstaller, sys.executable is the .exe path.
|
|
# sys._MEIPASS is the extraction directory (i.e. _internal/ folder).
|
|
exe_dir = Path(sys.executable).parent
|
|
internal_dir = Path(getattr(sys, '_MEIPASS', exe_dir))
|
|
|
|
# ── Change cwd to _internal so Builder.load_file('signage_player.kv')
|
|
# and other relative file references from main.py resolve ─────
|
|
os.chdir(str(internal_dir))
|
|
|
|
# Add bundled src directory to Python path
|
|
src_dir = str(internal_dir / 'src')
|
|
if os.path.isdir(src_dir) and src_dir not in sys.path:
|
|
sys.path.insert(0, src_dir)
|
|
|
|
# Add internal directory for config/media/playlists access
|
|
if str(internal_dir) not in sys.path:
|
|
sys.path.insert(0, str(internal_dir))
|
|
|
|
# ── Local folders next to the .exe ──────────────────────────────
|
|
# All data lives in the SAME folder as the executable so the user
|
|
# can copy/move the whole directory and everything still works.
|
|
os.environ['KIWY_DATA_DIR'] = str(exe_dir)
|
|
|
|
# Set KIVY_HOME to a local .kivy folder next to the .exe
|
|
kivy_home = exe_dir / '.kivy'
|
|
os.environ.setdefault('KIVY_HOME', str(kivy_home))
|
|
kivy_home.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Create local data folders next to the .exe
|
|
for sub in ['config', 'config/resources', 'media', 'playlists', 'logs']:
|
|
(exe_dir / sub).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def _copy_bundled_resources():
|
|
"""Copy bundled resource/config files to the local folders on first run."""
|
|
exe_dir = Path(sys.executable).parent
|
|
internal_dir = Path(getattr(sys, '_MEIPASS', exe_dir))
|
|
|
|
# Files to copy (source in bundle -> destination next to .exe)
|
|
files_to_copy = [
|
|
('config/app_config.json', 'config/app_config.json'),
|
|
('config/resources/access-card.png', 'config/resources/access-card.png'),
|
|
('config/resources/arrow.png', 'config/resources/arrow.png'),
|
|
('config/resources/backward.png', 'config/resources/backward.png'),
|
|
('config/resources/card-checked.png', 'config/resources/card-checked.png'),
|
|
('config/resources/edit-pen.png', 'config/resources/edit-pen.png'),
|
|
('config/resources/exit.png', 'config/resources/exit.png'),
|
|
('config/resources/forward.png', 'config/resources/forward.png'),
|
|
('config/resources/intro1.mp4', 'config/resources/intro1.mp4'),
|
|
('config/resources/pause.png', 'config/resources/pause.png'),
|
|
('config/resources/pencil.png', 'config/resources/pencil.png'),
|
|
('config/resources/play.png', 'config/resources/play.png'),
|
|
('config/resources/settings.png', 'config/resources/settings.png'),
|
|
]
|
|
|
|
for src_rel, dest_rel in files_to_copy:
|
|
src_path = internal_dir / src_rel
|
|
dest_path = exe_dir / dest_rel
|
|
if src_path.is_file() and not dest_path.exists():
|
|
try:
|
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
import shutil
|
|
shutil.copy2(str(src_path), str(dest_path))
|
|
except Exception:
|
|
pass # Non-critical; app can still run
|
|
|
|
|
|
# ── Wrap everything in try/except to capture early crashes ──────────
|
|
try:
|
|
_setup_paths()
|
|
_copy_bundled_resources()
|
|
# If we reach here, the hook finished successfully
|
|
try:
|
|
with open(_startup_log_path, 'a') as _f:
|
|
_f.write("pyi_runtime_hook.py completed successfully\n")
|
|
except Exception:
|
|
pass
|
|
except Exception as _hook_exc:
|
|
import traceback as _tb
|
|
try:
|
|
with open(_startup_log_path, 'a') as _f:
|
|
_f.write(f"pyi_runtime_hook.py CRASHED: {_hook_exc}\n")
|
|
_tb.print_exc(file=_f)
|
|
except Exception:
|
|
pass
|
|
raise # Re-raise so the .exe still fails visibly
|