Improve console suppression for windowed mode on Windows

Refactored console window suppression logic to better handle PyInstaller windowed applications and prevent console flicker. Added creation flags and redirected subprocess streams in multiple modules to ensure hidden console windows when running subprocesses. Introduced a utility function for running subprocesses with hidden console and updated all relevant subprocess calls to use it.
This commit is contained in:
Your Name
2025-08-27 22:46:38 +03:00
parent a6a175b3d7
commit b23be4335e
5 changed files with 146 additions and 28 deletions
+17 -4
View File
@@ -98,8 +98,21 @@ def setup_logging():
stdout_available = sys.stdout is not None
is_windowed_app = getattr(sys, 'frozen', False) and sys.stdout is None
# Skip console output if we're in a windowed application to prevent brief console window
if stdout_available and not is_windowed_app:
# Additional check for windowed mode - check if console window exists on Windows
is_console_hidden = False
if sys.platform == 'win32' and getattr(sys, 'frozen', False):
try:
import ctypes
kernel32 = ctypes.windll.kernel32
console_window = kernel32.GetConsoleWindow()
if console_window == 0:
is_console_hidden = True
except:
# If console detection fails, assume we might be windowed
is_console_hidden = True
# Skip console output if we're in a windowed application or console is hidden
if stdout_available and not is_windowed_app and not is_console_hidden:
try:
logger.add(
sys.stdout,
@@ -120,8 +133,8 @@ def setup_logging():
except Exception:
stdout_available = False
# If stdout is not available or we're in windowed mode, try stderr for critical messages only
if (not stdout_available or is_windowed_app) and not is_windowed_app:
# If stdout is not available or we're in windowed mode, skip stderr too to prevent console windows
if (not stdout_available or is_windowed_app or is_console_hidden) and not (is_windowed_app or is_console_hidden):
try:
if sys.stderr is not None:
logger.add(
+39 -11
View File
@@ -36,6 +36,37 @@ _version_cache = {
CACHE_EXPIRY = 300
def run_subprocess_hidden(cmd_args, **kwargs):
"""
Run subprocess with proper console window hiding for windowed applications.
Args:
cmd_args: Command arguments list
**kwargs: Additional subprocess.run arguments
Returns:
subprocess.CompletedProcess result
"""
# Set up creation flags to hide console window
creation_flags = SUBPROCESS_CREATIONFLAGS
if getattr(sys, 'frozen', False):
# Running as compiled executable - use additional flags to prevent console flicker
creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
# Set default parameters to suppress console
default_kwargs = {
'capture_output': True,
'text': True,
'creationflags': creation_flags,
'stdin': subprocess.DEVNULL
}
# Merge with provided kwargs (user kwargs take precedence)
default_kwargs.update(kwargs)
return subprocess.run(cmd_args, **default_kwargs)
def get_file_mtime(filepath) -> float:
"""Get file modification time safely."""
try:
@@ -196,8 +227,8 @@ def get_ytdlp_version_direct(yt_dlp_path=None) -> str:
return "Not found"
# Extra logic moved to src\utils\ytsage_constants.py
result = subprocess.run(
[yt_dlp_path, "--version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
result = run_subprocess_hidden(
[yt_dlp_path, "--version"], timeout=10
)
if result.returncode == 0:
@@ -213,8 +244,8 @@ def get_ffmpeg_version_direct() -> str:
"""Get FFmpeg version directly without caching."""
try:
# Extra logic moved to src\utils\ytsage_constants.py
result = subprocess.run(
["ffmpeg", "-version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
result = run_subprocess_hidden(
["ffmpeg", "-version"], timeout=10
)
if result.returncode == 0:
@@ -242,8 +273,8 @@ def get_ffmpeg_version_direct() -> str:
ffmpeg_exe = Path(ffmpeg_path).joinpath("ffmpeg")
if ffmpeg_exe.exists():
result = subprocess.run(
[ffmpeg_exe, "-version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
result = run_subprocess_hidden(
[ffmpeg_exe, "-version"], timeout=10
)
if result.returncode == 0:
@@ -489,7 +520,7 @@ def update_yt_dlp() -> bool:
# Compare versions and update if needed
if version.parse(latest_version) > version.parse(current_version):
logger.info(f"Updating yt-dlp from {current_version} to {latest_version}...")
update_result = subprocess.run(
update_result = run_subprocess_hidden(
[
sys.executable,
"-m",
@@ -498,10 +529,7 @@ def update_yt_dlp() -> bool:
"--upgrade",
"yt-dlp",
],
capture_output=True,
text=True,
check=False,
creationflags=SUBPROCESS_CREATIONFLAGS,
check=False
)
if update_result.returncode == 0:
logger.info("yt-dlp successfully updated")
+30 -4
View File
@@ -1,6 +1,7 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Optional
@@ -500,10 +501,22 @@ def check_ytdlp_binary() -> Optional[Path]:
# Use subprocess to check if yt-dlp is available
if OS_NAME == "Windows":
# On Windows, use 'where' command and hide console window
# Extra logic moved to src\utils\ytsage_constants.py
# For windowed applications, use additional flags to prevent console window flicker
creation_flags = SUBPROCESS_CREATIONFLAGS
if getattr(sys, 'frozen', False):
# Running as compiled executable - use additional flags
creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
result = subprocess.run(
["where", "yt-dlp"], capture_output=True, text=True, check=False, creationflags=SUBPROCESS_CREATIONFLAGS
["where", "yt-dlp"],
capture_output=True,
text=True,
check=False,
creationflags=creation_flags,
# Additional parameters to suppress console
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
if result.returncode == 0 and result.stdout.strip():
yt_dlp_path = result.stdout.strip().split("\n")[0]
@@ -534,9 +547,22 @@ def check_ytdlp_installed() -> bool:
if ytdlp_path:
# Try to run yt-dlp --version to verify it's working
try:
# Extra logic moved to src\utils\ytsage_constants.py
# For windowed applications, use additional flags to prevent console window flicker
creation_flags = SUBPROCESS_CREATIONFLAGS
if getattr(sys, 'frozen', False):
# Running as compiled executable - use additional flags
creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
result = subprocess.run(
[ytdlp_path, "--version"], capture_output=True, text=True, timeout=5, creationflags=SUBPROCESS_CREATIONFLAGS
[ytdlp_path, "--version"],
capture_output=True,
text=True,
timeout=5,
creationflags=creation_flags,
# Additional parameters to suppress console
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
return result.returncode == 0
except Exception:
+16 -1
View File
@@ -1,5 +1,6 @@
import json
import subprocess
import sys
import threading
import webbrowser
from pathlib import Path
@@ -1735,7 +1736,21 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
# Execute command with hidden console window on Windows
# Extra logic moved to src\utils\ytsage_constants.py
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, creationflags=SUBPROCESS_CREATIONFLAGS)
creation_flags = SUBPROCESS_CREATIONFLAGS
if getattr(sys, 'frozen', False):
# Running as compiled executable - use additional flags to prevent console flicker
creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60,
creationflags=creation_flags,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
if result.returncode != 0:
raise Exception(f"yt-dlp failed: {result.stderr}")