diff --git a/src/clipper/cache_manager.py b/src/clipper/cache_manager.py index e224ebe..b23b4b7 100644 --- a/src/clipper/cache_manager.py +++ b/src/clipper/cache_manager.py @@ -6,6 +6,7 @@ import hashlib import sqlite3 import subprocess +import sys import threading import time from datetime import datetime, timedelta @@ -253,6 +254,20 @@ def _build_clipper_state(self, video_id: str, url: str, cache_path_template = self.cache_dir / cache_filename clipper_paths = ClipperPaths() + # In frozen (PyInstaller) builds, point to bundled binaries by default + if getattr(sys, "frozen", False): + bin_dir = "./bin" + ext = ".exe" if sys.platform == "win32" else "" + clipper_paths.ffmpegPath = f"{bin_dir}/ffmpeg{ext}" + clipper_paths.ffprobePath = f"{bin_dir}/ffprobe{ext}" + clipper_paths.ffplayPath = f"{bin_dir}/ffplay{ext}" + clipper_paths.ytdlPath = f"{bin_dir}/yt-dlp{ext}" + # Normalize slashes for subprocess readability + clipper_paths.ffmpegPath = clipper_paths.ffmpegPath.replace("\\", "/") + clipper_paths.ffprobePath = clipper_paths.ffprobePath.replace("\\", "/") + clipper_paths.ffplayPath = clipper_paths.ffplayPath.replace("\\", "/") + clipper_paths.ytdlPath = clipper_paths.ytdlPath.replace("\\", "/") + if ytdl_location: clipper_paths.ytdlPath = ytdl_location logger.info(f"Using custom yt-dlp location: {ytdl_location}") diff --git a/src/clipper/clip_maker.py b/src/clipper/clip_maker.py index 328c480..91504ab 100644 --- a/src/clipper/clip_maker.py +++ b/src/clipper/clip_maker.py @@ -1468,37 +1468,37 @@ def getDefaultEncodeSettings(videobr: int) -> DictStrAny: } elif videobr <= 4000: encodeSettings = { - "crf": 24, + "crf": 22, "autoTargetMaxBitrate": int(1.6 * videobr), "twoPass": False, } elif videobr <= 6000: encodeSettings = { - "crf": 26, + "crf": 24, "autoTargetMaxBitrate": int(1.4 * videobr), "twoPass": False, } elif videobr <= 10000: encodeSettings = { - "crf": 28, + "crf": 26, "autoTargetMaxBitrate": int(1.2 * videobr), "twoPass": False, } elif videobr <= 14000: encodeSettings = { - "crf": 30, + "crf": 26, "autoTargetMaxBitrate": int(1.1 * videobr), "twoPass": False, } elif videobr <= 18000: encodeSettings = { - "crf": 30, + "crf": 26, "autoTargetMaxBitrate": int(1.0 * videobr), "twoPass": False, } elif videobr <= 25000: encodeSettings = { - "crf": 32, + "crf": 30, "autoTargetMaxBitrate": int(0.9 * videobr), "twoPass": False, } diff --git a/src/clipper/gui/engine.py b/src/clipper/gui/engine.py index 8677763..2b2c374 100644 --- a/src/clipper/gui/engine.py +++ b/src/clipper/gui/engine.py @@ -49,8 +49,6 @@ def process_files(self, markup_path: Optional[str] = None, video_path: Optional[ return {"status": "error", "message": "Either markup_path or markup_data must be provided"} try: - print(f"DEBUG: Starting file processing with CLI-identical logic and GUI settings...") - # Validate inputs - either markup_path OR markup_data must be provided if markup_path: markup_file = Path(markup_path) @@ -64,13 +62,11 @@ def process_files(self, markup_path: Optional[str] = None, video_path: Optional[ # Create a fresh clipper state for this processing session self.cs = clipper_types.ClipperState() - print("DEBUG: Created fresh ClipperState for processing") # Get comprehensive GUI settings from clipper.gui.settings_manager import SettingsManager settings_manager = SettingsManager() gui_settings = settings_manager.get_combined_settings() - print(f"DEBUG: Loaded GUI settings: {len(gui_settings)} settings") # Build minimal argv for required arguments only (no settings) simulated_argv = ["yt_clipper"] @@ -171,8 +167,6 @@ def process_files(self, markup_path: Optional[str] = None, video_path: Optional[ ytc_settings.getInputVideo(self.cs) ytc_settings.getGlobalSettings(self.cs) - print("DEBUG: Starting clip processing...") - # Process clips exactly like CLI if not self.cs.settings.get("preview", False): clip_maker.makeClips(self.cs) diff --git a/src/clipper/ytc_logger.py b/src/clipper/ytc_logger.py index 715a0cb..025e166 100644 --- a/src/clipper/ytc_logger.py +++ b/src/clipper/ytc_logger.py @@ -2,7 +2,7 @@ import logging from pathlib import Path from types import TracebackType -from typing import IO, Dict +from typing import IO, Mapping import coloredlogs import verboselogs @@ -63,15 +63,36 @@ def log( | BaseException = None, stack_info: bool = False, stacklevel: int = 1, - extra: Dict[str, object] | None = None, + extra: Mapping[str, object] | None = None, ) -> None: if not self.no_rich_logs: - level_name = logging.getLevelName(level) - color = self.console.get_style(f"logging.level.{level_name.lower()}") - - msg = f"[{color}]{msg}" - if extra is None: - extra = {} + # Map numeric levels to known Rich theme style keys to avoid invalid style names + # that can occur with custom levels (e.g., "Level 34"). + style_by_level = { + logging.DEBUG: "logging.level.debug", + verboselogs.VERBOSE: "logging.level.verbose", + logging.INFO: "logging.level.info", + # Some code paths use 'success' level via verboselogs + getattr(verboselogs, "SUCCESS", 25): "logging.level.success", + IMPORTANT: "logging.level.important", + NOTICE: "logging.level.notice", + HEADER: "logging.level.header", + REPORT: "logging.level.report", + logging.WARNING: "logging.level.warning", + logging.ERROR: "logging.level.error", + logging.CRITICAL: "logging.level.error", + } + + style_key = style_by_level.get(level) + if style_key is None: + # Fall back to a best-effort mapping based on the level name + level_name = str(logging.getLevelName(level)).lower().replace(" ", "_") + candidate = f"logging.level.{level_name}" + style_key = candidate if candidate in THEME_COLORS_LOG_LEVELS else "logging.level.info" + + # Wrap the message with the resolved style name; Rich will pick it from the theme + msg = f"[{style_key}]{msg}" + extra = {} if extra is None else dict(extra) extra["markup"] = True return super().log( diff --git a/src/clipper/ytdl.py b/src/clipper/ytdl.py index 962d99a..d0c35b1 100644 --- a/src/clipper/ytdl.py +++ b/src/clipper/ytdl.py @@ -2,6 +2,7 @@ import shlex import subprocess import sys +from pathlib import Path from typing import Dict, List, Tuple from clipper.clipper_types import ClipperPaths, ClipperState @@ -44,7 +45,18 @@ def ytdl_bin_get_args_base(cs: ClipperState) -> List[str]: ytdl_args.extend(["--output", shlex.quote(f'{settings["downloadVideoPath"]}')]) if getattr(sys, "frozen", False): - ytdl_args.extend(["--ffmpeg-location", shlex.quote(cp.ffmpegPath)]) + # Only pass --ffmpeg-location if it points to an existing executable + ffmpeg_path = cp.ffmpegPath + try: + exists = Path(ffmpeg_path).is_file() + except Exception: + exists = False + if exists: + ytdl_args.extend(["--ffmpeg-location", shlex.quote(ffmpeg_path)]) + else: + logger.debug( + f"Skipping --ffmpeg-location: path not found -> {ffmpeg_path!r}", + ) cookies = settings["cookiefile"] diff --git a/src/gui-frontend/src/components/ColorControls.vue b/src/gui-frontend/src/components/ColorControls.vue new file mode 100644 index 0000000..bca5baa --- /dev/null +++ b/src/gui-frontend/src/components/ColorControls.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/src/gui-frontend/src/components/ColorGradingPanel.vue b/src/gui-frontend/src/components/ColorGradingPanel.vue index bd2e862..2da672e 100644 --- a/src/gui-frontend/src/components/ColorGradingPanel.vue +++ b/src/gui-frontend/src/components/ColorGradingPanel.vue @@ -1,284 +1,37 @@ diff --git a/src/gui-frontend/src/components/SettingsPanel.vue b/src/gui-frontend/src/components/SettingsPanel.vue index cc373d4..8c625be 100644 --- a/src/gui-frontend/src/components/SettingsPanel.vue +++ b/src/gui-frontend/src/components/SettingsPanel.vue @@ -138,7 +138,7 @@ (fn: (...args: A) => void, wait = 200) { + let t: ReturnType + return (...args: A): void => { + clearTimeout(t) + t = setTimeout(() => fn(...args), wait) + } +}