Refactor windowed mode and subprocess handling
Removed redundant windowed mode setup from main.py and simplified subprocess creation flags logic across core and GUI modules. Centralized subprocess creation flags usage, eliminated custom run_subprocess_hidden wrapper, and improved logging handler setup for better compatibility with windowed applications.
This commit is contained in:
@@ -1,50 +1,4 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Early detection and handling of windowed mode to prevent console window flicker
|
||||
def setup_windowed_mode():
|
||||
"""
|
||||
Setup windowed mode handling to prevent console window flickering.
|
||||
This must be called as early as possible in the application startup.
|
||||
"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Running as compiled executable (PyInstaller)
|
||||
if sys.stdout is None or sys.stderr is None:
|
||||
# Windowed mode - completely suppress console output
|
||||
import io
|
||||
null_stream = io.StringIO()
|
||||
if sys.stdout is None:
|
||||
sys.stdout = null_stream
|
||||
if sys.stderr is None:
|
||||
sys.stderr = null_stream
|
||||
else:
|
||||
# Console mode but compiled - redirect to null to prevent window flicker
|
||||
if os.name == 'nt': # Windows
|
||||
try:
|
||||
import io
|
||||
# Create a null output stream
|
||||
null_stream = io.StringIO()
|
||||
# Only redirect if we detect this might cause console window issues
|
||||
# Check if we're not already in a console
|
||||
try:
|
||||
# Try to get console window handle - if this fails, we're likely windowed
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
console_window = kernel32.GetConsoleWindow()
|
||||
if console_window == 0:
|
||||
# No console window exists, redirect to prevent one from appearing
|
||||
sys.stdout = null_stream
|
||||
sys.stderr = null_stream
|
||||
except:
|
||||
# If console detection fails, err on the side of caution
|
||||
sys.stdout = null_stream
|
||||
sys.stderr = null_stream
|
||||
except:
|
||||
# If all else fails, continue normally
|
||||
pass
|
||||
|
||||
# Call windowed mode setup immediately
|
||||
setup_windowed_mode()
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
|
||||
+19
-34
@@ -94,25 +94,9 @@ def setup_logging():
|
||||
|
||||
# Console handler - INFO and above, with colors
|
||||
# Check if stdout is available (it might be None in PyInstaller windowed apps)
|
||||
# Also check if we're running in a windowed mode (likely PyInstaller --windowed)
|
||||
stdout_available = sys.stdout is not None
|
||||
is_windowed_app = getattr(sys, 'frozen', False) and sys.stdout is None
|
||||
|
||||
# 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:
|
||||
|
||||
if stdout_available:
|
||||
try:
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
@@ -133,13 +117,13 @@ def setup_logging():
|
||||
except Exception:
|
||||
stdout_available = False
|
||||
|
||||
# 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):
|
||||
# If stdout is not available, try stderr or skip console logging entirely
|
||||
if not stdout_available:
|
||||
try:
|
||||
if sys.stderr is not None:
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
level="ERROR", # Only errors to stderr to minimize console window appearance
|
||||
level="WARNING", # Only warnings and errors to stderr
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}",
|
||||
catch=True,
|
||||
)
|
||||
@@ -175,27 +159,28 @@ def setup_logging():
|
||||
# If file logging fails, just log to console
|
||||
logger.warning(f"Could not set up file logging: {e}")
|
||||
|
||||
# Log startup message
|
||||
try:
|
||||
# Log startup message if we have any handlers
|
||||
if logger._core.handlers:
|
||||
logger.info("YTSage logging system initialized")
|
||||
if log_dir and log_dir.exists():
|
||||
logger.debug(f"Log directory: {log_dir}")
|
||||
else:
|
||||
logger.warning("File logging disabled - could not create log directory")
|
||||
except Exception:
|
||||
# If logging fails, continue silently
|
||||
pass
|
||||
|
||||
# If no handlers were successfully added, add a null handler to prevent errors
|
||||
try:
|
||||
# Try to add a minimal fallback handler if needed
|
||||
if not logger._core.handlers:
|
||||
# Add a minimal handler that just discards messages
|
||||
# This prevents loguru from complaining about no handlers
|
||||
import tempfile
|
||||
temp_log = Path(tempfile.gettempdir()) / "ytsage_temp.log"
|
||||
logger.add(temp_log, level="ERROR", catch=True)
|
||||
except Exception:
|
||||
# If even that fails, we're in a very restricted environment
|
||||
# loguru should handle this gracefully with its internal fallbacks
|
||||
pass
|
||||
|
||||
try:
|
||||
# Try to add a temporary file handler as last resort
|
||||
temp_log = Path(tempfile.gettempdir()) / "ytsage_temp.log"
|
||||
logger.add(temp_log, level="ERROR", catch=True)
|
||||
except Exception:
|
||||
# If even that fails, we're in a very restricted environment
|
||||
# loguru should handle this gracefully with its internal fallbacks
|
||||
pass
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
+11
-43
@@ -4,12 +4,8 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
# Suppress the pkg_resources deprecation warning to prevent console window flicker
|
||||
warnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", category=UserWarning)
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
from packaging import version
|
||||
@@ -36,37 +32,6 @@ _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:
|
||||
@@ -227,8 +192,8 @@ def get_ytdlp_version_direct(yt_dlp_path=None) -> str:
|
||||
return "Not found"
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
result = run_subprocess_hidden(
|
||||
[yt_dlp_path, "--version"], timeout=10
|
||||
result = subprocess.run(
|
||||
[yt_dlp_path, "--version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
@@ -244,8 +209,8 @@ def get_ffmpeg_version_direct() -> str:
|
||||
"""Get FFmpeg version directly without caching."""
|
||||
try:
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
result = run_subprocess_hidden(
|
||||
["ffmpeg", "-version"], timeout=10
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
@@ -273,8 +238,8 @@ def get_ffmpeg_version_direct() -> str:
|
||||
ffmpeg_exe = Path(ffmpeg_path).joinpath("ffmpeg")
|
||||
|
||||
if ffmpeg_exe.exists():
|
||||
result = run_subprocess_hidden(
|
||||
[ffmpeg_exe, "-version"], timeout=10
|
||||
result = subprocess.run(
|
||||
[ffmpeg_exe, "-version"], capture_output=True, text=True, timeout=10, creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
@@ -520,7 +485,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 = run_subprocess_hidden(
|
||||
update_result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
@@ -529,7 +494,10 @@ def update_yt_dlp() -> bool:
|
||||
"--upgrade",
|
||||
"yt-dlp",
|
||||
],
|
||||
check=False
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
)
|
||||
if update_result.returncode == 0:
|
||||
logger.info("yt-dlp successfully updated")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -501,22 +500,10 @@ 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
|
||||
# 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
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
|
||||
result = subprocess.run(
|
||||
["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
|
||||
["where", "yt-dlp"], capture_output=True, text=True, check=False, creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
yt_dlp_path = result.stdout.strip().split("\n")[0]
|
||||
@@ -547,22 +534,9 @@ def check_ytdlp_installed() -> bool:
|
||||
if ytdlp_path:
|
||||
# Try to run yt-dlp --version to verify it's working
|
||||
try:
|
||||
# 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
|
||||
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
result = subprocess.run(
|
||||
[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
|
||||
[ytdlp_path, "--version"], capture_output=True, text=True, timeout=5, creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
@@ -1736,21 +1735,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
|
||||
|
||||
# Execute command with hidden console window on Windows
|
||||
# Extra logic moved to src\utils\ytsage_constants.py
|
||||
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
|
||||
)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, creationflags=SUBPROCESS_CREATIONFLAGS)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"yt-dlp failed: {result.stderr}")
|
||||
|
||||
Reference in New Issue
Block a user