From 9d72a509168ef3d636c97d0109db86157823fdb7 Mon Sep 17 00:00:00 2001 From: benexl Date: Wed, 31 Dec 2025 14:51:50 +0300 Subject: [PATCH] fix: replace sys.executable with get_python_executable for better compatibility --- .../interactive/menu/media/dynamic_search.py | 4 +-- viu_media/cli/utils/preview.py | 12 ++++----- viu_media/core/utils/detect.py | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/viu_media/cli/interactive/menu/media/dynamic_search.py b/viu_media/cli/interactive/menu/media/dynamic_search.py index 9fda642..661fdfe 100644 --- a/viu_media/cli/interactive/menu/media/dynamic_search.py +++ b/viu_media/cli/interactive/menu/media/dynamic_search.py @@ -1,9 +1,9 @@ import json import logging -import sys from pathlib import Path from .....core.constants import APP_CACHE_DIR, SCRIPTS_DIR +from .....core.utils.detect import get_python_executable from .....libs.media_api.params import MediaSearchParams from ...session import Context, session from ...state import InternalDirective, MediaApiState, MenuName, State @@ -57,7 +57,7 @@ def dynamic_search(ctx: Context, state: State) -> State | InternalDirective: # Make the search script executable by calling it with python3 # fzf will pass the query as {q} which becomes the first argument search_command_final = ( - f"{Path(sys.executable).as_posix()} {search_script_file.as_posix()} {{q}}" + f"{Path(get_python_executable()).as_posix()} {search_script_file.as_posix()} {{q}}" ) try: diff --git a/viu_media/cli/utils/preview.py b/viu_media/cli/utils/preview.py index 5d4cb52..ddfe392 100644 --- a/viu_media/cli/utils/preview.py +++ b/viu_media/cli/utils/preview.py @@ -2,7 +2,6 @@ import logging from pathlib import Path import re from hashlib import sha256 -import sys from typing import Dict, List, Optional import httpx @@ -11,6 +10,7 @@ from viu_media.core.utils import formatter from ...core.config import AppConfig from ...core.constants import APP_CACHE_DIR, SCRIPTS_DIR +from ...core.utils.detect import get_python_executable from ...core.utils.file import AtomicWriter from ...libs.media_api.types import ( AiringScheduleResult, @@ -327,7 +327,7 @@ def get_anime_preview( preview_file.write_text(preview_script, encoding="utf-8") preview_script_final = ( - f"{Path(sys.executable).as_posix()} {preview_file.as_posix()} {{}}" + f"{Path(get_python_executable()).as_posix()} {preview_file.as_posix()} {{}}" ) return preview_script_final @@ -387,7 +387,7 @@ def get_episode_preview( preview_file.write_text(preview_script, encoding="utf-8") preview_script_final = ( - f"{Path(sys.executable).as_posix()} {preview_file.as_posix()} {{}}" + f"{Path(get_python_executable()).as_posix()} {preview_file.as_posix()} {{}}" ) return preview_script_final @@ -435,7 +435,7 @@ def get_character_preview(choice_map: Dict[str, Character], config: AppConfig) - preview_file.write_text(preview_script, encoding="utf-8") preview_script_final = ( - f"{Path(sys.executable).as_posix()} {preview_file.as_posix()} {{}}" + f"{Path(get_python_executable()).as_posix()} {preview_file.as_posix()} {{}}" ) return preview_script_final @@ -483,7 +483,7 @@ def get_review_preview(choice_map: Dict[str, MediaReview], config: AppConfig) -> preview_file.write_text(preview_script, encoding="utf-8") preview_script_final = ( - f"{Path(sys.executable).as_posix()} {preview_file.as_posix()} {{}}" + f"{Path(get_python_executable()).as_posix()} {preview_file.as_posix()} {{}}" ) return preview_script_final @@ -599,7 +599,7 @@ def get_dynamic_anime_preview(config: AppConfig) -> str: # Return the command to execute the preview script preview_script_final = ( - f"{Path(sys.executable).as_posix()} {preview_file.as_posix()} {{}}" + f"{Path(get_python_executable()).as_posix()} {preview_file.as_posix()} {{}}" ) return preview_script_final diff --git a/viu_media/core/utils/detect.py b/viu_media/core/utils/detect.py index 15c85a3..0b315e6 100644 --- a/viu_media/core/utils/detect.py +++ b/viu_media/core/utils/detect.py @@ -56,3 +56,30 @@ def is_running_kitty_terminal() -> bool: def has_fzf() -> bool: return True if shutil.which("fzf") else False + + +def is_frozen() -> bool: + """Check if running as a PyInstaller frozen executable.""" + return getattr(sys, "frozen", False) + + +def get_python_executable() -> str: + """ + Get the Python executable path. + + In frozen (PyInstaller) apps, sys.executable points to the .exe, + so we need to find the system Python instead. + + Returns: + Path to a Python executable. + """ + if is_frozen(): + # We're in a frozen app - find system Python + for python_name in ["python3", "python", "py"]: + python_path = shutil.which(python_name) + if python_path: + return python_path + # Fallback - this likely won't work but is the best we can do + return "python" + else: + return sys.executable