Skip to content

Commit 2cf808f

Browse files
anne-o-pixelcdgriffithclaude
authored
Fix PyInstaller LD_LIBRARY_PATH contamination breaking FFmpeg on Linux (#766)
* Implement clean_env to manage environment variables Add clean_env function to remove LD_LIBRARY_PATH from environment. * Add clean_env to subprocess environment * Use clean_env() in run_check_features function Added clean_env() to subprocess calls for feature checks. * Add clean_env to subprocess calls in background_tasks Updated the run method to include a clean environment for subprocesses. * Update subprocess call to include cleaned environment Pass cleaned environment to subprocess run for thumbnail generation. * Use clean_env for thumbnail generation Updated thumbnail generation to use a cleaned environment. * Use clean environment for thumbnail generation Updated thumbnail generation to use a cleaned environment. * Add clean_env function to subprocess environment --------- Co-authored-by: Chris Griffith <chris@cdgriffith.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9f7c075 commit 2cf808f

13 files changed

Lines changed: 87 additions & 13 deletions

‎CHANGES‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* Adding warning in Data & Attachments tab and reason text when tracks are incompatible with the output format (e.g. data streams in MKV)
2626
* Adding Go-based Windows installer, launcher and uninstaller using an embeddable Python distribution (per-user and all-users installs, Add/Remove Programs integration, upgrades from previous NSIS installs) replacing the PyInstaller and NSIS Windows builds, plus a portable zip release
2727
* Changing Advanced panel layout to use QGroupBox sections (Video Details, Frame Rate, Video Processing, Color & Appearance, Color, Output) with restoration filters separated from color correction filters
28+
* Fixing #766 FFmpeg and other external tools failing on Linux PyInstaller builds (e.g. AUR fastflix-bin) with library symbol errors due to inherited bundled `LD_LIBRARY_PATH` (thanks to anne-o-pixel)
2829
* Fixing #741 VideoToolbox HEVC and H264 encoders passing invalid numeric profile index instead of profile name, causing "incorrect parameters" errors on macOS (thanks to enaveso)
2930
* Fixing #560 queue items could not be removed while encoding — now allows removing non-running items and grays out unavailable controls (move, reload, load queue) during encoding (thanks to 54x1)
3031
* Fixing #736 thumbnail and crop preview generation failing with FFmpeg 8.0+ by adding `-update 1` flag required by the image2 muxer for single-image output (thanks to kliffgomel)

‎fastflix/command_runner.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from psutil import Popen
1616

17+
from fastflix.resources import clean_env
18+
1719
try:
1820
from psutil import (
1921
HIGH_PRIORITY_CLASS,
@@ -93,6 +95,7 @@ def start_exec(self, command, work_dir: str = None, shell: bool = False, errors=
9395
stderr=stderr_handle,
9496
stdin=PIPE, # FFmpeg can try to read stdin and wrecks havoc on linux
9597
encoding="utf-8",
98+
env=clean_env(),
9699
)
97100
except PermissionError:
98101
logger.error(

‎fastflix/flix.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from fastflix.language import t
1717
from fastflix.models.config import Config
1818
from fastflix.models.fastflix_app import FastFlixApp
19+
from fastflix.shared import clean_env
1920

2021
here = os.path.abspath(os.path.dirname(__file__))
2122
re_tff = re.compile(r"TFF:\s+(\d+)")
@@ -151,6 +152,7 @@ def execute(command: List, work_dir: Union[Path, str] = None, timeout: int = Non
151152
cwd=work_dir,
152153
timeout=timeout,
153154
encoding="utf-8",
155+
env=clean_env(),
154156
)
155157

156158

@@ -361,6 +363,7 @@ def extract_attachment(ffmpeg: Path, source: Path, stream: int, work_dir: Path,
361363
stderr=PIPE,
362364
stdin=PIPE,
363365
cwd=work_dir,
366+
env=clean_env(),
364367
)
365368
try:
366369
stdout, _ = proc.communicate(timeout=3)
@@ -695,7 +698,9 @@ def get_hdr10_parser_version(config: Config) -> version:
695698
global HDR10_parser_version
696699
if HDR10_parser_version:
697700
return HDR10_parser_version
698-
HDR10_parser_version_output = check_output([str(config.hdr10plus_parser), "--version"], encoding="utf-8")
701+
HDR10_parser_version_output = check_output(
702+
[str(config.hdr10plus_parser), "--version"], encoding="utf-8", env=clean_env()
703+
)
699704

700705
_, version_string = HDR10_parser_version_output.rsplit(sep=" ", maxsplit=1)
701706
HDR10_parser_version = version.parse(version_string)
@@ -759,6 +764,7 @@ def _detect_hdr10_plus_tool(app: FastFlixApp, config: Config, stream) -> bool:
759764
stdout=PIPE,
760765
stderr=PIPE,
761766
stdin=PIPE, # FFmpeg can try to read stdin and wrecks havoc
767+
env=clean_env(),
762768
)
763769

764770
parser_version = get_hdr10_parser_version(config)
@@ -772,6 +778,7 @@ def _detect_hdr10_plus_tool(app: FastFlixApp, config: Config, stream) -> bool:
772778
stderr=PIPE,
773779
stdin=process.stdout,
774780
encoding="utf-8",
781+
env=clean_env(),
775782
)
776783

777784
try:

‎fastflix/resources.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from pathlib import Path
33
import os
4+
import sys
45
from functools import lru_cache
56

67
import importlib.resources
@@ -84,3 +85,21 @@ def get_bool_env(variable: str):
8485
if var.lower() in ("1", "on", "yes", "true", "t"):
8586
return True
8687
return False
88+
89+
90+
def clean_env() -> dict:
91+
"""Return a copy of os.environ that is safe for launching external tools (FFmpeg, FFprobe, rigaya, etc).
92+
93+
A PyInstaller onefile build points LD_LIBRARY_PATH at its bundled libraries, so system binaries such as
94+
/usr/bin/ffmpeg would load those (e.g. an incompatible libass) instead of the system ones. PyInstaller keeps
95+
the user's original value in LD_LIBRARY_PATH_ORIG, which is restored here (or the variable is removed).
96+
Kept in this Qt-free module so the conversion worker process can use it without importing PySide6.
97+
"""
98+
env = os.environ.copy()
99+
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
100+
original = env.pop("LD_LIBRARY_PATH_ORIG", None)
101+
if original is not None:
102+
env["LD_LIBRARY_PATH"] = original
103+
else:
104+
env.pop("LD_LIBRARY_PATH", None)
105+
return env

‎fastflix/rigaya_helpers.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from dataclasses import dataclass, field
33
from subprocess import run, PIPE
44

5+
from fastflix.resources import clean_env
6+
57

68
@dataclass
79
class Encoder:
@@ -72,11 +74,13 @@ def parse_qsv_devices(split_text: list[str]) -> QSVEncoder:
7274
def run_check_features(executable, is_qsv=False):
7375
outputs = []
7476
if is_qsv:
75-
result = run([executable, "--check-features"], stdout=PIPE, stderr=PIPE, encoding="utf-8")
77+
result = run([executable, "--check-features"], stdout=PIPE, stderr=PIPE, encoding="utf-8", env=clean_env())
7678
outputs.append(result.stdout.splitlines())
7779
else:
7880
for i in range(10):
79-
result = run([executable, "--check-features", str(i)], stdout=PIPE, stderr=PIPE, encoding="utf-8")
81+
result = run(
82+
[executable, "--check-features", str(i)], stdout=PIPE, stderr=PIPE, encoding="utf-8", env=clean_env()
83+
)
8084
if result.stderr:
8185
break
8286
outputs.append(result.stdout.splitlines())

‎fastflix/shared.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from PySide6 import QtCore, QtGui, QtWidgets
2020

2121
from fastflix.language import t
22-
from fastflix.resources import get_bool_env
22+
from fastflix.resources import clean_env, get_bool_env # noqa: F401 (clean_env re-exported)
2323

2424
try:
2525
# PyInstaller creates a temp folder and stores path in _MEIPASS
@@ -337,7 +337,7 @@ def open_folder(path):
337337
elif sys.platform == "darwin":
338338
run(["open", path])
339339
else:
340-
run(["xdg-open", path])
340+
run(["xdg-open", path], env=clean_env())
341341
except FileNotFoundError:
342342
logger.error(f"Do not know which command to use to open: {path}")
343343

‎fastflix/widgets/background_tasks.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ffmpeg_normalize import FFmpegNormalize
1212

1313
from fastflix.flix import extract_attachments
14+
from fastflix.shared import clean_env
1415
from fastflix.language import t
1516
from fastflix.models.fastflix_app import FastFlixApp
1617
from fastflix.shared import clean_file_string
@@ -42,7 +43,7 @@ def __init__(self, main, command=""):
4243

4344
def run(self):
4445
self.main.thread_logging_signal.emit(f"DEBUG:{t('Generating thumbnail')}: {_format_command(self.command)}")
45-
result = run(self.command, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
46+
result = run(self.command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, env=clean_env())
4647
if result.returncode > 0:
4748
output_text = result.stdout.decode(encoding="utf-8", errors="ignore")
4849
if "No such filter: 'zscale'" in output_text:
@@ -165,6 +166,7 @@ def run(self):
165166
command,
166167
stdout=PIPE,
167168
stderr=STDOUT,
169+
env=clean_env(),
168170
)
169171
stdout, _ = self._process.communicate()
170172
returncode = self._process.returncode
@@ -233,6 +235,7 @@ def _get_subtitle_format(self):
233235
stdout=PIPE,
234236
stderr=STDOUT,
235237
text=True,
238+
env=clean_env(),
236239
)
237240

238241
if result.returncode != 0:
@@ -504,7 +507,9 @@ def run(self):
504507
self.ffmpeg_signal.emit("Extracting HDR10+ metadata")
505508

506509
hdr10_parser_version_output = check_output(
507-
[str(self.app.fastflix.config.hdr10plus_parser), "--version"], encoding="utf-8"
510+
[str(self.app.fastflix.config.hdr10plus_parser), "--version"],
511+
encoding="utf-8",
512+
env=clean_env(),
508513
)
509514
_, version_string = hdr10_parser_version_output.rsplit(sep=" ", maxsplit=1)
510515
hdr10_parser_version = version.parse(version_string)
@@ -539,6 +544,7 @@ def run(self):
539544
stdout=PIPE,
540545
stderr=open(self.app.fastflix.current_video.work_path / "hdr10extract_out.txt", "wb"),
541546
# stdin=PIPE, # FFmpeg can try to read stdin and wrecks havoc
547+
env=clean_env(),
542548
)
543549

544550
process_two = Popen(
@@ -548,6 +554,7 @@ def run(self):
548554
stdin=process.stdout,
549555
encoding="utf-8",
550556
cwd=str(self.app.fastflix.current_video.work_path),
557+
env=clean_env(),
551558
)
552559

553560
with open(self.app.fastflix.current_video.work_path / "hdr10extract_out.txt", "r", encoding="utf-8") as f:

‎fastflix/widgets/container.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from PySide6 import QtCore, QtGui, QtWidgets
1313
from PySide6.QtGui import QAction
1414

15+
from fastflix.resources import clean_env
1516
from fastflix.exceptions import FastFlixInternalException
1617
from fastflix.language import t
1718
from fastflix.version import __version__
@@ -732,7 +733,7 @@ def run(self):
732733
elif sys.platform == "darwin":
733734
run(["open", self.path])
734735
else:
735-
run(["xdg-open", self.path])
736+
run(["xdg-open", self.path], env=clean_env())
736737
except FileNotFoundError:
737738
logger.error(f"Do not know which command to use to open: {self.path}")
738739

‎fastflix/widgets/main_post_encode.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def _record_history(self, video: Video, success: bool = True):
141141
from subprocess import run as subprocess_run
142142

143143
from fastflix.flix import generate_thumbnail_command
144+
from fastflix.shared import clean_env
144145

145146
thumb_command = generate_thumbnail_command(
146147
config=self.app.fastflix.config,
@@ -150,7 +151,7 @@ def _record_history(self, video: Video, success: bool = True):
150151
start_time=video.video_settings.start_time or 0,
151152
input_track=video.video_settings.selected_track,
152153
)
153-
result = subprocess_run(thumb_command, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
154+
result = subprocess_run(thumb_command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, env=clean_env())
154155
if result.returncode != 0 or not thumb_output.exists():
155156
logger.warning("Failed to generate thumbnail for history entry")
156157
entry.thumbnail_filename = ""

‎fastflix/widgets/windows/crop_window.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from fastflix.encoders.common import helpers
1111
from fastflix.flix import generate_thumbnail_command
12+
from fastflix.shared import clean_env
1213
from fastflix.language import t
1314
from fastflix.shared import time_to_number
1415
from fastflix.ui_scale import scaler
@@ -691,7 +692,7 @@ def generate_image(self, with_crop=False):
691692

692693
logger.info(f"Generating crop preview: {thumb_command}")
693694

694-
thumb_run = run(thumb_command, shell=True, stderr=PIPE, stdout=PIPE)
695+
thumb_run = run(thumb_command, shell=True, stderr=PIPE, stdout=PIPE, env=clean_env())
695696
if thumb_run.returncode > 0:
696697
stderr_text = thumb_run.stderr.decode(encoding="utf-8", errors="ignore")
697698
if settings.get("remove_hdr") and "no path between colorspaces" in stderr_text:
@@ -710,7 +711,7 @@ def generate_image(self, with_crop=False):
710711
start_time=self._get_preview_time(),
711712
input_track=video.video_settings.selected_track,
712713
)
713-
thumb_run = run(thumb_command, shell=True, stderr=PIPE, stdout=PIPE)
714+
thumb_run = run(thumb_command, shell=True, stderr=PIPE, stdout=PIPE, env=clean_env())
714715
if thumb_run.returncode > 0:
715716
logger.warning(f"Could not generate crop preview: {thumb_run.stdout} |----| {thumb_run.stderr}")
716717
return

0 commit comments

Comments
 (0)