diff --git a/viu_media/cli/utils/image.py b/viu_media/cli/utils/image.py index 3d0375d..7992e95 100644 --- a/viu_media/cli/utils/image.py +++ b/viu_media/cli/utils/image.py @@ -9,6 +9,8 @@ import importlib.util import click import httpx +from viu_media.core.utils import detect + logger = logging.getLogger(__name__) @@ -138,6 +140,7 @@ def render(url: str, capture: bool = False, size: str = "30x30") -> Optional[str [icat_executable, "--align", "left", url], capture_output=capture, text=capture, + env=detect.get_clean_env(), ) if process.returncode == 0: return process.stdout if capture else None diff --git a/viu_media/core/downloader/default.py b/viu_media/core/downloader/default.py index 9d74825..c70e9ce 100644 --- a/viu_media/core/downloader/default.py +++ b/viu_media/core/downloader/default.py @@ -21,7 +21,7 @@ from rich.progress import ( ) from rich.prompt import Confirm from ..utils.file import sanitize_filename - +from ..utils.detect import get_clean_env from ..exceptions import ViuError from ..patterns import TORRENT_REGEX from ..utils.networking import get_remote_filename @@ -372,6 +372,7 @@ class DefaultDownloader(BaseDownloader): capture_output=params.silent, # Only suppress ffmpeg output if silent text=True, check=True, + env=get_clean_env(), ) final_output_path = video_path.parent / merged_filename diff --git a/viu_media/core/downloader/yt_dlp.py b/viu_media/core/downloader/yt_dlp.py index 8d95ff0..ed40edc 100644 --- a/viu_media/core/downloader/yt_dlp.py +++ b/viu_media/core/downloader/yt_dlp.py @@ -11,7 +11,7 @@ from rich.prompt import Confirm import yt_dlp from yt_dlp.utils import sanitize_filename - +from ..utils.detect import get_clean_env from ..exceptions import ViuError from ..patterns import TORRENT_REGEX from ..utils.networking import get_remote_filename @@ -224,7 +224,7 @@ class YtDLPDownloader(BaseDownloader): # Run the ffmpeg command try: - subprocess.run(args) + subprocess.run(args, env=get_clean_env()) final_output_path = video_path.parent / merged_filename if final_output_path.exists(): diff --git a/viu_media/core/utils/detect.py b/viu_media/core/utils/detect.py index 0b315e6..014be3b 100644 --- a/viu_media/core/utils/detect.py +++ b/viu_media/core/utils/detect.py @@ -83,3 +83,21 @@ def get_python_executable() -> str: return "python" else: return sys.executable + + +def get_clean_env() -> dict[str, str]: + """ + Returns a copy of the environment with LD_LIBRARY_PATH fixed for system subprocesses + when running as a PyInstaller frozen application. + This prevents system binaries (like mpv, ffmpeg) from loading incompatible + libraries from the PyInstaller bundle. + """ + env = os.environ.copy() + if is_frozen(): + # PyInstaller saves the original LD_LIBRARY_PATH in LD_LIBRARY_PATH_ORIG + if "LD_LIBRARY_PATH_ORIG" in env: + env["LD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH_ORIG"] + else: + # If orig didn't exist, LD_LIBRARY_PATH shouldn't exist for the subprocess + env.pop("LD_LIBRARY_PATH", None) + return env \ No newline at end of file diff --git a/viu_media/libs/player/mpv/player.py b/viu_media/libs/player/mpv/player.py index fca9266..18ccc8a 100644 --- a/viu_media/libs/player/mpv/player.py +++ b/viu_media/libs/player/mpv/player.py @@ -97,7 +97,7 @@ class MpvPlayer(BasePlayer): "is.xyz.mpv/.MPVActivity", ] - subprocess.run(args) + subprocess.run(args,env=detect.get_clean_env()) return PlayerResult(params.episode) @@ -146,6 +146,7 @@ class MpvPlayer(BasePlayer): text=True, encoding="utf-8", check=False, + env=detect.get_clean_env(), ) if proc.stdout: for line in reversed(proc.stdout.split("\n")): @@ -185,7 +186,7 @@ class MpvPlayer(BasePlayer): logger.info(f"Starting MPV with IPC socket: {socket_path}") - process = subprocess.Popen(pre_args + mpv_args) + process = subprocess.Popen(pre_args + mpv_args,env=detect.get_clean_env()) return process @@ -210,7 +211,7 @@ class MpvPlayer(BasePlayer): args.append("--player-args") args.extend(mpv_args) - subprocess.run(args) + subprocess.run(args,env=detect.get_clean_env()) return PlayerResult(params.episode) def _stream_on_desktop_with_syncplay(self, params: PlayerParams) -> PlayerResult: @@ -232,7 +233,7 @@ class MpvPlayer(BasePlayer): if mpv_args := self._create_mpv_cli_options(params): args.append("--") args.extend(mpv_args) - subprocess.run(args) + subprocess.run(args,env=detect.get_clean_env()) return PlayerResult(params.episode) diff --git a/viu_media/libs/player/vlc/player.py b/viu_media/libs/player/vlc/player.py index 2d49ded..7bbc327 100644 --- a/viu_media/libs/player/vlc/player.py +++ b/viu_media/libs/player/vlc/player.py @@ -103,7 +103,7 @@ class VlcPlayer(BasePlayer): params.title, ] - subprocess.run(args) + subprocess.run(args,env=detect.get_clean_env()) return PlayerResult(episode=params.episode) @@ -134,7 +134,7 @@ class VlcPlayer(BasePlayer): if self.config.args: args.extend(self.config.args.split(",")) - subprocess.run(args, encoding="utf-8") + subprocess.run(args, encoding="utf-8",env=detect.get_clean_env()) return PlayerResult(episode=params.episode) def _stream_on_desktop_with_webtorrent_cli( @@ -159,7 +159,7 @@ class VlcPlayer(BasePlayer): args.append("--player-args") args.extend(self.config.args.split(",")) - subprocess.run(args) + subprocess.run(args,env=detect.get_clean_env()) return PlayerResult(episode=params.episode) diff --git a/viu_media/libs/selectors/fzf/selector.py b/viu_media/libs/selectors/fzf/selector.py index fa52c87..9dbe59a 100644 --- a/viu_media/libs/selectors/fzf/selector.py +++ b/viu_media/libs/selectors/fzf/selector.py @@ -5,6 +5,8 @@ import subprocess from rich.prompt import Prompt +from viu_media.core.utils import detect + from ....core.config import FzfConfig from ....core.exceptions import ViuError from ..base import BaseSelector @@ -49,6 +51,7 @@ class FzfSelector(BaseSelector): stdout=subprocess.PIPE, text=True, encoding="utf-8", + env=detect.get_clean_env(), ) if result.returncode != 0: return None @@ -76,6 +79,7 @@ class FzfSelector(BaseSelector): stdout=subprocess.PIPE, text=True, encoding="utf-8", + env=detect.get_clean_env(), ) if result.returncode != 0: return [] @@ -117,7 +121,16 @@ class FzfSelector(BaseSelector): lines = result.stdout.strip().splitlines() return lines[-1] if lines else (default or "") - def search(self, prompt, search_command, *, preview=None, header=None, initial_query=None, initial_results=None): + def search( + self, + prompt, + search_command, + *, + preview=None, + header=None, + initial_query=None, + initial_results=None, + ): """Enhanced search using fzf's --reload flag for dynamic search.""" # Build the header with optional custom header line display_header = self.header @@ -156,6 +169,7 @@ class FzfSelector(BaseSelector): stdout=subprocess.PIPE, text=True, encoding="utf-8", + env=detect.get_clean_env(), ) if result.returncode != 0: return None diff --git a/viu_media/libs/selectors/rofi/selector.py b/viu_media/libs/selectors/rofi/selector.py index e1a78b4..8839514 100644 --- a/viu_media/libs/selectors/rofi/selector.py +++ b/viu_media/libs/selectors/rofi/selector.py @@ -43,6 +43,7 @@ class RofiSelector(BaseSelector): input=rofi_input, stdout=subprocess.PIPE, text=True, + env=detect.get_clean_env() ) if result.returncode == 0: @@ -106,6 +107,7 @@ class RofiSelector(BaseSelector): input=rofi_input, stdout=subprocess.PIPE, text=True, + env=detect.get_clean_env() ) if result.returncode == 0: