diff --git a/CHANGELOG.md b/CHANGELOG.md index bab32fa..209d643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +# 📝 CHANGELOG — Warlock-Studio v2.1 + +**Release Date:** June 23, 2025 + +--- + +## 🚀 Major Enhancements & Stability Overhaul + +This version focuses on massive improvements to stability, error handling, and code robustness, ensuring a smoother and more reliable user experience. + +- 🛡️ **Robust Error Handling:** + + - Implemented comprehensive `try...except` blocks for AI model loading (`AI_upscale` & `AI_interpolation`) to prevent crashes if a model file is missing or corrupt. + - Enhanced video frame extraction (`extract_video_frames`) with checks for file existence, successful video opening, and valid frame counts. + - Made video encoding (`video_encoding`) more resilient by handling FFmpeg subprocess errors gracefully and providing clearer error messages. + - Added a fallback for audio passthrough failures; the application now saves the video without audio instead of failing the entire process. + +- 🧵 **Safe Thread & Process Management:** + + - Replaced the unsafe thread-stopping mechanism (which intentionally raised an error) with a modern, safe `threading.Event` (`stop_thread_flag`). + - Ensures clean and predictable termination of background monitoring threads. + +- ⚙️ **Resilient Core Processing:** + - Added pre-flight checks to the metadata copy function (`copy_file_metadata`) to ensure `exiftool.exe` and source/destination files exist before execution. + +--- + +## 🎨 UI/UX Refinements + +- 🎨 **Refined Color Palette:** + - Updated the main application theme for a new aesthetic. + - App Name Color (`app_name_color`) changed to a golden yellow (`#ECD125`). + - Widget Background Color (`widget_background_color`) changed to a deep red (`#960707`). + - Default active button border color updated to red to match the new theme. + +--- + +## 🔧 Code & Maintainability Improvements + +- 🧹 **Improved Code Organization:** + + - Refactored file extension lists into clearer, separate categories: `supported_image_extensions` and `supported_video_extensions`. + +- 📦 **Dependency and Initialization:** + - Added new standard library imports (`shutil.move`, `subprocess.CalledProcessError`, `threading.Event`) to support the stability enhancements. + - Ensured safer initialization of global variables at startup. + +--- + # 📝 **CHANGELOG — Warlock-Studio v2.0** **Release Date:** June 6, 2025 @@ -13,7 +62,6 @@ - Enables temporal upscaling of video via AI. - 🎥 **RIFE Models Integration:** - - Added `RIFE` and `RIFE_Lite` to supported models. - Interpolation model list introduced: `RIFE_models_list`. - Extended `AI_models_list` to include all model types: SRVGGNetCompact, BSRGAN, IRCNN, and RIFE. @@ -45,7 +93,6 @@ - Ensures broader compatibility with input formats. - 🚀 **Improved GPU Execution Support:** - - Enhanced logic for selecting GPU via `DirectML`. - Supports up to 4 GPUs (`Auto`, `GPU 1` to `GPU 4`) via `provider_options`. @@ -63,7 +110,6 @@ - Support for dynamic multi-frame generation with tree-based logic (e.g. A-B-C from D). - 📊 **Improved Numeric Precision and Postprocessing:** - - Improved handling of floating-point range and normalization. - Enhanced logic for RGB/RGBA conversion and alpha blending. @@ -76,7 +122,6 @@ - MessageBox window can now be resized by the user (`resizable(True, True)`). - 👌 **Improved Dialog Formatting:** - - Better spacing and ordering of message elements. - Cleaner font use and default value display. diff --git a/README.md b/README.md index 5ada673..0d6c348 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,178 @@ -## **Download the installer** from [WarlockHub](https://warlockhub-17vu0fo.gamma.site/warlockhub) +### 🚀Get Warlock-Studio Installer + +You can download the latest version **2.1** from any of this platforms: + + + + + + + +
+ + Download from SourceForge + + + + Download from Itch.io + + + + Download from Google Drive + +
![Warlock-Studio logo](rsc/banner.png) +![Build Status](https://img.shields.io/badge/build-Stable_Release-blue?style=for-the-badge) +![Version](https://img.shields.io/badge/%20Version-2.1-darkred?style=for-the-badge) +![Last Update](https://img.shields.io/badge/Last%20Update-06%2023%202025-purple?style=for-the-badge) -### AI-Powered Media Enhancement & Upscaling Suite 2.0 +### AI-Powered Media Enhancement & Upscaling Suite 2.1 -Warlock-Studio is an **open-source desktop application** that unifies the power of [**MedIA-Witch**](https://github.com/Ivan-Ayub97/MedIA-Witch.git) and [**MedIA-Wizard**](https://github.com/Ivan-Ayub97/MedIA-Wizard.git) into a single, seamless platform for AI-driven image and video enhancement. Featuring support for the latest upscaling, restoration, and interpolation models with a sleek, intuitive interface, Warlock-Studio brings professional-grade media processing to everyone. +**Warlock-Studio** is an **open-source desktop application** that consolidates the power of [**MedIA-Witch**](https://github.com/Ivan-Ayub97/MedIA-Witch.git) and [**MedIA-Wizard**](https://github.com/Ivan-Ayub97/MedIA-Wizard.git) into a single, seamless platform for AI-driven image and video enhancement. -Now with advanced **AI-based frame interpolation** (RIFE), support for **slow-motion video generation**, refined **GPU management**, and a more modular, scalable UI—Warlock-Studio 2.0 is built for the future of creative enhancement. +It features integration with state-of-the-art models for upscaling, restoration, and frame interpolation—all within an intuitive and streamlined user interface. Warlock-Studio delivers **professional-grade media processing** capabilities to everyone. -### New icon -![icon](Assets/logo.ico) +Version 2.1 introduces major improvements, including: + +- Advanced **AI frame interpolation** using **RIFE** +- **Slow-motion video generation** +- Optimized **GPU management** +- A **modular and scalable UI** architecture for better flexibility and performance --- -## Captures +## 📸 Interface Previews -- General UI +### 🔹 Main Interface ![Screenshot of Warlock-Studio](rsc/Capture.png) -- RIFE Options UI +### 🔹 RIFE (Frame Interpolation) Options ![Screenshot of Warlock-Studio](rsc/CaptureRIFE.png) +### 🔹 Icon App + +## ![Screenshot of Warlock-Studio](logo.ico) + +## 🛠️ Development Status — v2.1 + +| Component | Status | Notes | +| ----------------------------------- | ---------------- | -------------------------------------------------------------------- | +| **Upscaling Models (ESRGAN, etc.)** | 🟢 **Stable** | Fully integrated support for key enhancement and restoration models. | +| **Frame Interpolation (RIFE)** | 🟢 **New** | Includes slow-motion and intermediate frame generation capabilities. | +| **Batch Processing** | 🟢 **Stable** | Reliable processing for multiple files at once. | +| **User Interface (UI/UX)** | 🟢 **Improved** | Modular and scalable interface enhanced in version 2.0. | +| **GPU Management** | 🟢 **Optimized** | Refined resource handling and device support. | +| **Installer and Packaging** | 🟢 **Stable** | Easy-to-use installer for Windows platforms. | + --- +## ✨ Recent Enhancements (v2.1) + +- ✅ **Stability Overhaul:** Major improvements in error handling for model loading, frame extraction, and video encoding. +- ✅ **Resilient Processing:** Added fallbacks for video encoding and pre-checks for file operations to prevent crashes. +- ✅ **Safe Thread Management:** Upgraded to a safe `threading.Event` for stopping background tasks reliably. +- ✅ **UI Refinements:** Updated color palette for a new look and feel. + +--- + +## 🗂️ Project Structure + +``` +Warlock-Studio/ +├──AI-onnx/ + │ + └──├──BSRGANx2_fp16.onnx + ├──BSRGANx4_fp16.onnx + ├──IRCNN_Lx1_fp16.onnx + ├──IRCNN_Mx1_fp16.onnx + ├──RealESR_Animex4_fp16.onnx + ├──RealESR_Gx4_fp16.onnx + ├──RealESRGANx4_fp16.onnx + ├──RealESRNetx4_fp16.onnx + ├──RealSRx4_Anime_fp16.onnx + ├──RIFE_fp32.onnx + └──RIFE_Lite_fp32.onnx +├──Assets/ + │ + └──├──clear_icon.png + ├──exiftool.exe + ├──ffmpeg.exe + ├──info_icon.png + ├──logo.ico + ├──logo.png + ├──stop_icon.png + └──upscale_icon.png +│ +├──rsc/ + │ + └──├──banner.png + ├──Capture.png + ├──CaptureRIFE.png + └──Image_comparison.png +│ +├──CHANGELOG.md +├──CODE_OF_CONDUCT.md +├──CONTRIBUTING.md +├──LICENSE +├──NOTICE.md +├──README.md # This File +├──SECURITY.md +├──Setup.iss +├──Warlock-Studio.py # Main +├──Warlock-Studio.spec +├──Warlock-Studio.py +└──logo.ico + + + +``` + ## Installation -Follow these steps to get up and running with Warlock-Studio: +To get started with Warlock-Studio: -1. **Run the installer** and follow the on-screen prompts. -2. **Launch the app:** open `Warlock-Studio.exe` on Windows. -3. **Start enhancing** your images and videos with a few clicks! +1. **Run the installer** and follow the setup instructions. +2. **Launch the application** by opening `Warlock-Studio.exe`. +3. **Begin enhancing** your images and videos with just a few clicks! -Warlock-Studio leverages [PyInstaller](https://www.pyinstaller.org/) and [Inno Setup](http://www.jrsoftware.org/isinfo.php) for effortless packaging and installation. - ---- +Warlock-Studio uses [PyInstaller](https://www.pyinstaller.org/) and [Inno Setup](http://www.jrsoftware.org/isinfo.php) for a seamless packaging and installation experience. ## Key Features -- **State-of-the-Art AI Models:** - Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, **RIFE** and more for noise reduction, resolution boost, high-fidelity restoration, and smooth frame interpolation. +- **State-of-the-Art AI Models** + Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, **RIFE**, and others for denoising, resolution enhancement, detail restoration, and smooth frame interpolation. -- **AI Frame Interpolation & Slow Motion Generation:** - Generate intermediate frames between existing video frames using RIFE. Create smooth **x2/x4/x8** transitions or cinematic slow motion effects. +- **AI Frame Interpolation & Slow Motion** + Generate new in-between frames using RIFE to create smooth **2x/4x/8x** motion or dramatic slow-motion effects. -- **Batch Processing:** - Upscale, interpolate, and enhance multiple images or videos in one go—ideal for large collections. +- **Batch Processing** + Simultaneously process multiple images or videos—ideal for large-scale media projects. -- **Customizable Workflows:** - Pick your AI model, output resolution, file format (PNG, JPEG, MP4, etc.), and quality settings to suit any project. +- **Customizable Workflows** + Choose your preferred AI model, output resolution, format (PNG, JPEG, MP4, etc.), and quality settings for full creative control. -- **Intuitive UI:** - A clean, user-friendly interface designed for both novices and pros—everything you need is a click away. +- **Intuitive Interface** + Designed for both beginners and professionals—simple, clean, and efficient. -- **Open-Source & Extensible:** - Licensed under the MIT License. Additional conditions are described in the [NOTICE](NOTICE) file. +- **Open-Source & Extensible** + Licensed under the MIT License. Additional usage terms can be found in the [NOTICE](NOTICE) file. --- ## How to Use -1. **Run as Administrator** (optional but recommended for best performance). -2. **Load Media:** drag & drop images, videos, or folders into the app. -3. **Configure Settings:** +1. **Run as Administrator** (optional but recommended for optimal performance). +2. **Load your media**: drag and drop images, videos, or folders directly into the app. +3. **Configure settings**: - - **Choose AI Model** (Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, **RIFE**, etc.) - - **Set Output Resolution**, **Format**, and optionally enable **interpolation** or **slow motion** + - Select an **AI Model** (e.g., Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, RIFE) + - Set the **output resolution**, **file format**, and toggle features such as **interpolation** or **slow-motion** -4. **Start Processing:** hit **Start** and let the magic happen. -5. **Retrieve Results:** the enhanced files will appear in your selected output folder. +4. **Start Processing**: click **Start** to begin enhancement. +5. **Retrieve your files**: processed outputs will be saved in your chosen destination folder. --- @@ -81,10 +184,10 @@ Warlock-Studio leverages [PyInstaller](https://www.pyinstaller.org/) and [Inno S ## System Requirements -- **OS:** Windows 10 or later -- **RAM:** 4 GB minimum (8 GB+ recommended) -- **GPU:** NVIDIA or DirectML-compatible GPU highly recommended for speed and compatibility -- **Storage:** Ample space for your media files and outputs +- **Operating System:** Windows 10 or later +- **Memory (RAM):** Minimum 4 GB (8 GB or more recommended) +- **Graphics Card:** NVIDIA or DirectML-compatible GPU highly recommended for performance +- **Storage:** Sufficient disk space for input and output media files --- @@ -102,13 +205,13 @@ Warlock-Studio leverages [PyInstaller](https://www.pyinstaller.org/) and [Inno S | RIFE | Apache 2.0 | [hzwer](https://github.com/hzwer) | [GitHub](https://github.com/megvii-research/ECCV2022-RIFE) | | SRGAN | CC BY-NC-SA 4.0 (Non-Commercial) | [TensorLayer Community](https://github.com/tensorlayer) | [GitHub](https://github.com/tensorlayer/srgan) | | BSRGAN | Apache 2.0 | [Kai Zhang](https://github.com/cszn) | [GitHub](https://github.com/cszn/BSRGAN) | -| IRCNN | BSD / Other (Mixed) | [Kai Zhang](https://github.com/cszn) | [GitHub](https://github.com/cszn/IRCNN) | +| IRCNN | BSD / Mixed | [Kai Zhang](https://github.com/cszn) | [GitHub](https://github.com/cszn/IRCNN) | | Anime4K | MIT | [Tianyang Zhang (bloc97)](https://github.com/bloc97) | [GitHub](https://github.com/bloc97/Anime4K) | | ONNX Runtime | MIT | [Microsoft](https://github.com/microsoft) | [GitHub](https://github.com/microsoft/onnxruntime) | | PyTorch | BSD 3-Clause | [Meta AI](https://pytorch.org/) | [GitHub](https://github.com/pytorch/pytorch) | | FFmpeg | LGPL-2.1 / GPL (varies) | [FFmpeg Team](https://ffmpeg.org/) | [Official Site](https://ffmpeg.org) | | ExifTool | Perl Artistic License 1.0 | [Phil Harvey](https://exiftool.org/) | [Official Site](https://exiftool.org/) | -| DirectML | MIT | [Microsoft](https://github.com/microsoft/) | [Official Site](https://github.com/microsoft/DirectML) | +| DirectML | MIT | [Microsoft](https://github.com/microsoft/) | [GitHub](https://github.com/microsoft/DirectML) | | Python | Python Software Foundation (PSF) | [Python Software Foundation](https://www.python.org/) | [Official Site](https://www.python.org) | | PyInstaller | GPLv2+ | [PyInstaller Team](https://github.com/pyinstaller) | [GitHub](https://github.com/pyinstaller/pyinstaller) | | Inno Setup | Custom Inno License | [Jordan Russell](http://www.jrsoftware.org/) | [Official Site](http://www.jrsoftware.org/isinfo.php) | @@ -117,19 +220,19 @@ Warlock-Studio leverages [PyInstaller](https://www.pyinstaller.org/) and [Inno S ## Contributions -We welcome your contributions! +We warmly welcome community contributions! -1. **Fork** the repo. +1. **Fork** this repository. 2. **Create a branch** for your feature or fix. -3. **Submit a Pull Request** with a clear description of your changes. +3. **Submit a Pull Request** with a detailed explanation of your changes. -For bug reports, suggestions or questions, reach out at **[negroayub97@gmail.com](mailto:negroayub97@gmail.com)**. +For bug reports, feature suggestions, or inquiries, contact us at: **[negroayub97@gmail.com](mailto:negroayub97@gmail.com)** -Warlock-Studio combines cutting-edge AI with a powerful yet user-friendly interface—take your media to the next level! 🧙‍♂️ +**Warlock-Studio** merges cutting-edge artificial intelligence with a powerful yet accessible interface—empowering creators to elevate their media effortlessly. 🧙‍♂️ --- ## License © 2025 Iván Eduardo Chavez Ayub -Licensed under the MIT License. Additional conditions are described in the [NOTICE](NOTICE.md) file. +Distributed under the MIT License. Additional terms are available in the [NOTICE](NOTICE.md) file. diff --git a/Setup.iss b/Setup.iss index cd68229..cbed48a 100644 --- a/Setup.iss +++ b/Setup.iss @@ -1,7 +1,7 @@ [Setup] ; Basic installation configuration -AppName=Warlock-Studio -AppVersion=2.0 +AppName=Warlock-Studio 2.1 +AppVersion=2.1 DefaultDirName={pf}\Warlock-Studio DefaultGroupName=Warlock-Studio OutputDir=.\Output @@ -49,11 +49,11 @@ begin MsgBox('© 2025 Iván Eduardo Chavez Ayub'#13#10 + 'Licensed under the MIT License. Additional conditions are described in the NOTICE file.'#13#10#13#10 + - 'This software, Warlock-Studio, is distributed under the MIT License and extended with an additional NOTICE file.'#13#10 + + 'This software, Warlock-Studio 2.1, is distributed under the MIT License and extended with an additional NOTICE file.'#13#10 + 'By installing or using this software, you agree to comply with both the MIT License and the additional terms specified in the NOTICE document.'#13#10#13#10 + '*** PROJECT OVERVIEW ***'#13#10 + - 'Warlock-Studio unifies the MedIA-Wizard and MedIA-Witch tools. It is developed by Iván Eduardo Chavez Ayub ("Ivan-Ayub97"), and is inspired by tools such as QualityScaler, FluidFrames, and RealScaler (originally developed by Djdefrag).'#13#10 + + 'Warlock-Studio unifies the MedIA-Wizard and MedIA-Witch tools. It is developed by Iván Eduardo Chavez Ayub (@Ivan-Ayub97 on GitHub), and is based on tools such as QualityScaler, FluidFrames, and RealScaler originally developed by Djdefrag (@Djdefrag on GitHub).'#13#10 + 'Its main goal is to improve image resolution using AI-powered models with an intuitive interface.'#13#10#13#10 + '*** INTEGRATED TECHNOLOGIES & LICENSES ***'#13#10 + diff --git a/Warlock-Studio.py b/Warlock-Studio.py index b0b35ec..61074e7 100644 --- a/Warlock-Studio.py +++ b/Warlock-Studio.py @@ -26,9 +26,11 @@ from os.path import exists as os_path_exists from os.path import expanduser as os_path_expanduser from os.path import join as os_path_join from os.path import splitext as os_path_splitext +from shutil import move as shutil_move from shutil import rmtree as remove_directory +from subprocess import CalledProcessError from subprocess import run as subprocess_run -from threading import Thread +from threading import Event, Thread from time import sleep from timeit import default_timer as timer # GUI imports @@ -71,6 +73,13 @@ from onnxruntime import InferenceSession from PIL.Image import fromarray as pillow_image_fromarray from PIL.Image import open as pillow_image_open +# Define supported file extensions +supported_image_extensions = [".jpg", ".jpeg", + ".png", ".bmp", ".tiff", ".tif", ".webp"] +supported_video_extensions = [".mp4", ".avi", + ".mkv", ".mov", ".wmv", ".flv", ".webm"] +supported_file_extensions = supported_image_extensions + supported_video_extensions + if sys.stdout is None: sys.stdout = open(os_devnull, "w") if sys.stderr is None: @@ -84,12 +93,12 @@ def find_by_relative_path(relative_path: str) -> str: app_name = "Warlock-Studio" -version = "2.0" +version = "2.1" background_color = "#121212" # Negro grisáceo profundo -app_name_color = "#FF0E0E" # Blanco puro para el nombre de la app -widget_background_color = "#454242" # Rojo oscuro (Dark Red) -text_color = "#FFFFFF" # Blanco opaco para texto legible +app_name_color = "#ECD125" # Blanco puro para el nombre de la app +widget_background_color = "#960707" # Rojo oscuro (Dark Red) +text_color = "#F0EEEE" # Blanco opaco para texto legible VRAM_model_usage = { 'RealESR_Gx4': 2.2, @@ -219,21 +228,7 @@ little_textbox_width = 74 little_menu_width = 98 -supported_file_extensions = [ - '.heic', '.jpg', '.jpeg', '.JPG', '.JPEG', '.png', - '.PNG', '.webp', '.WEBP', '.bmp', '.BMP', '.tif', - '.tiff', '.TIF', '.TIFF', '.mp4', '.MP4', '.webm', - '.WEBM', '.mkv', '.MKV', '.flv', '.FLV', '.gif', - '.GIF', '.m4v', ',M4V', '.avi', '.AVI', '.mov', - '.MOV', '.qt', '.3gp', '.mpg', '.mpeg', ".vob" -] - -supported_video_extensions = [ - '.mp4', '.MP4', '.webm', '.WEBM', '.mkv', '.MKV', - '.flv', '.FLV', '.gif', '.GIF', '.m4v', ',M4V', - '.avi', '.AVI', '.mov', '.MOV', '.qt', '.3gp', - '.mpg', '.mpeg', ".vob" -] +# Remove duplicate definitions - using the ones defined earlier # AI ------------------- @@ -273,23 +268,35 @@ class AI_upscale: return 4 def _load_inferenceSession(self) -> None: + try: + # Check if model file exists + if not os_path_exists(self.AI_model_path): + raise FileNotFoundError( + f"AI model file not found: {self.AI_model_path}") - providers = ['DmlExecutionProvider'] + providers = ['DmlExecutionProvider'] - match self.directml_gpu: - case 'Auto': provider_options = [{"performance_preference": "high_performance"}] - case 'GPU 1': provider_options = [{"device_id": "0"}] - case 'GPU 2': provider_options = [{"device_id": "1"}] - case 'GPU 3': provider_options = [{"device_id": "2"}] - case 'GPU 4': provider_options = [{"device_id": "3"}] + match self.directml_gpu: + case 'Auto': provider_options = [{"performance_preference": "high_performance"}] + case 'GPU 1': provider_options = [{"device_id": "0"}] + case 'GPU 2': provider_options = [{"device_id": "1"}] + case 'GPU 3': provider_options = [{"device_id": "2"}] + case 'GPU 4': provider_options = [{"device_id": "3"}] - inference_session = InferenceSession( - path_or_bytes=self.AI_model_path, - providers=providers, - provider_options=provider_options, - ) + inference_session = InferenceSession( + path_or_bytes=self.AI_model_path, + providers=providers, + provider_options=provider_options, + ) - self.inferenceSession = inference_session + self.inferenceSession = inference_session + print( + f"[AI] Successfully loaded model: {os_path_basename(self.AI_model_path)}") + + except Exception as e: + error_msg = f"Failed to load AI model {os_path_basename(self.AI_model_path)}: {str(e)}" + print(f"[AI ERROR] {error_msg}") + raise RuntimeError(error_msg) # INTERNAL CLASS FUNCTIONS @@ -593,34 +600,48 @@ class AI_interpolation: self.inferenceSession = self._load_inferenceSession() def _load_inferenceSession(self) -> InferenceSession: + try: + # Check if model file exists + if not os_path_exists(self.AI_model_path): + raise FileNotFoundError( + f"AI model file not found: {self.AI_model_path}") - providers = ['DmlExecutionProvider'] + providers = ['DmlExecutionProvider'] - match self.directml_gpu: - case 'Auto': provider_options = [{"performance_preference": "high_performance"}] - case 'GPU 1': provider_options = [{"device_id": "0"}] - case 'GPU 2': provider_options = [{"device_id": "1"}] - case 'GPU 3': provider_options = [{"device_id": "2"}] - case 'GPU 4': provider_options = [{"device_id": "3"}] + match self.directml_gpu: + case 'Auto': provider_options = [{"performance_preference": "high_performance"}] + case 'GPU 1': provider_options = [{"device_id": "0"}] + case 'GPU 2': provider_options = [{"device_id": "1"}] + case 'GPU 3': provider_options = [{"device_id": "2"}] + case 'GPU 4': provider_options = [{"device_id": "3"}] - inference_session = InferenceSession( - path_or_bytes=self.AI_model_path, - providers=providers, - provider_options=provider_options - ) + inference_session = InferenceSession( + path_or_bytes=self.AI_model_path, + providers=providers, + provider_options=provider_options + ) - return inference_session + print( + f"[AI] Successfully loaded interpolation model: {os_path_basename(self.AI_model_path)}") + return inference_session + + except Exception as e: + error_msg = f"Failed to load AI interpolation model {os_path_basename(self.AI_model_path)}: {str(e)}" + print(f"[AI ERROR] {error_msg}") + raise RuntimeError(error_msg) # INTERNAL CLASS FUNCTIONS def get_image_mode(self, image: numpy_ndarray) -> str: - match image.shape: - case (rows, cols): - return "Grayscale" - case (rows, cols, channels) if channels == 3: - return "RGB" - case (rows, cols, channels) if channels == 4: - return "RGBA" + shape = image.shape + if len(shape) == 2: # Grayscale: 2D array (rows, cols) + return "Grayscale" + # RGB: 3D array with 3 channels + elif len(shape) == 3 and shape[2] == 3: + return "RGB" + # RGBA: 3D array with 4 channels + elif len(shape) == 3 and shape[2] == 4: + return "RGBA" def get_image_resolution(self, image: numpy_ndarray) -> tuple: height = image.shape[0] @@ -1137,14 +1158,14 @@ def get_values_for_file_widget() -> tuple: try: input_resize_factor = int( float(str(selected_input_resize_factor.get()))) - except: + except (ValueError, TypeError): input_resize_factor = 0 # Output resolution % try: output_resize_factor = int( float(str(selected_output_resize_factor.get()))) - except: + except (ValueError, TypeError): output_resize_factor = 0 return upscale_factor, input_resize_factor, output_resize_factor @@ -1152,9 +1173,8 @@ def get_values_for_file_widget() -> tuple: def update_file_widget(a, b, c) -> None: try: - global file_widget - file_widget - except: + selected_file_list = file_widget.get_selected_file_list() + except Exception: return upscale_factor, input_resize_factor, output_resize_factor = get_values_for_file_widget() @@ -1303,7 +1323,7 @@ def create_active_button( icon: CTkImage = None, width: int = 140, height: int = 30, - border_color: str = "#0096FF" + border_color: str = "#C11919" ) -> CTkButton: return CTkButton( @@ -1331,7 +1351,10 @@ def create_dir(name_dir: str) -> None: os_makedirs(name_dir, mode=0o777) -def stop_thread() -> None: stop = 1 + "x" +def stop_thread() -> None: + """Notifica al hilo de monitoreo que debe detenerse de forma segura.""" + global stop_thread_flag + stop_thread_flag.set() def image_read(file_path: str) -> numpy_ndarray: @@ -1344,23 +1367,42 @@ def image_write(file_path: str, file_data: numpy_ndarray, file_extension: str = def copy_file_metadata(original_file_path: str, upscaled_file_path: str) -> None: - - exiftool_cmd = [ - EXIFTOOL_EXE_PATH, - '-fast', - '-TagsFromFile', - original_file_path, - '-overwrite_original', - '-all:all', - '-unsafe', - '-largetags', - upscaled_file_path - ] - try: - subprocess_run(exiftool_cmd, check=True, shell="False") - except: - pass + # Check if exiftool exists + if not os_path_exists(EXIFTOOL_EXE_PATH): + print("[ExifTool] ExifTool not found, skipping metadata copy") + return + + # Check if files exist + if not os_path_exists(original_file_path): + print(f"[ExifTool] Original file not found: {original_file_path}") + return + + if not os_path_exists(upscaled_file_path): + print(f"[ExifTool] Upscaled file not found: {upscaled_file_path}") + return + + exiftool_cmd = [ + EXIFTOOL_EXE_PATH, + '-fast', + '-TagsFromFile', + original_file_path, + '-overwrite_original', + '-all:all', + '-unsafe', + '-largetags', + upscaled_file_path + ] + + result = subprocess_run(exiftool_cmd, check=True, + shell=False, capture_output=True, text=True) + print(f"[ExifTool] Successfully copied metadata") + + except CalledProcessError as e: + print( + f"[ExifTool] ExifTool failed: {e.stderr if e.stderr else str(e)}") + except Exception as e: + print(f"[ExifTool] Could not copy metadata: {str(e)}") def prepare_output_image_filename( @@ -1547,45 +1589,88 @@ def extract_video_frames( selected_image_extension: str ) -> list[str]: # FluidFrames-compatible implementation - create_dir(target_directory) + try: + create_dir(target_directory) - frames_number_to_save = cpu_number * ECTRACTION_FRAMES_FOR_CPU - video_capture = opencv_VideoCapture(video_path) - frame_count = int(video_capture.get(CAP_PROP_FRAME_COUNT)) + # Check if video file exists + if not os_path_exists(video_path): + raise FileNotFoundError(f"Video file not found: {video_path}") - extracted_frames = [] - extracted_frames_paths = [] - video_frames_list = [] - frame_index = 0 + frames_number_to_save = cpu_number * ECTRACTION_FRAMES_FOR_CPU + video_capture = opencv_VideoCapture(video_path) - for frame_number in range(frame_count): - success, frame = video_capture.read() - if not success: - break - frame_path = f"{target_directory}{os_separator}frame_{frame_number:03d}{selected_image_extension}" - frame = AI_instance.resize_with_input_factor(frame) - extracted_frames.append(frame) - extracted_frames_paths.append(frame_path) - video_frames_list.append(frame_path) + # Check if video was opened successfully + if not video_capture.isOpened(): + raise ValueError(f"Could not open video file: {video_path}") - if len(extracted_frames) == frames_number_to_save: - percentage_extraction = (frame_number / frame_count) * 100 - write_process_status( - process_status_q, f"{file_number}. Extracting video frames ({round(percentage_extraction, 2)}%)") - save_extracted_frames(extracted_frames_paths, - extracted_frames, cpu_number) - extracted_frames = [] - extracted_frames_paths = [] + frame_count = int(video_capture.get(CAP_PROP_FRAME_COUNT)) - frame_index += 1 + # Check if frame count is valid + if frame_count <= 0: + raise ValueError( + f"Invalid frame count ({frame_count}) for video: {video_path}") - video_capture.release() + extracted_frames = [] + extracted_frames_paths = [] + video_frames_list = [] + frame_index = 0 - if len(extracted_frames) > 0: - save_extracted_frames(extracted_frames_paths, - extracted_frames, cpu_number) + for frame_number in range(frame_count): + success, frame = video_capture.read() + if not success: + if frame_number == 0: + raise ValueError( + f"Could not read any frames from video: {video_path}") + print( + f"Warning: Could not read frame {frame_number}, stopping extraction") + break - return video_frames_list + try: + frame_path = f"{target_directory}{os_separator}frame_{frame_number:03d}{selected_image_extension}" + frame = AI_instance.resize_with_input_factor(frame) + extracted_frames.append(frame) + extracted_frames_paths.append(frame_path) + video_frames_list.append(frame_path) + except Exception as e: + print( + f"Warning: Error processing frame {frame_number}: {str(e)}") + continue + + if len(extracted_frames) == frames_number_to_save: + percentage_extraction = (frame_number / frame_count) * 100 + write_process_status( + process_status_q, f"{file_number}. Extracting video frames ({round(percentage_extraction, 2)}%)") + try: + save_extracted_frames(extracted_frames_paths, + extracted_frames, cpu_number) + except Exception as e: + print(f"Warning: Error saving frames batch: {str(e)}") + extracted_frames = [] + extracted_frames_paths = [] + + frame_index += 1 + + video_capture.release() + + if len(extracted_frames) > 0: + try: + save_extracted_frames(extracted_frames_paths, + extracted_frames, cpu_number) + except Exception as e: + print(f"Warning: Error saving final frames batch: {str(e)}") + + if len(video_frames_list) == 0: + raise ValueError( + f"No frames were successfully extracted from video: {video_path}") + + return video_frames_list + + except Exception as e: + if 'video_capture' in locals(): + video_capture.release() + write_process_status( + process_status_q, f"{ERROR_STATUS}Error extracting frames from {os_path_basename(video_path)}: {str(e)}") + raise def video_encoding( @@ -1595,77 +1680,155 @@ def video_encoding( upscaled_frame_paths: list[str], selected_video_codec: str, ) -> None: - - if "x264" in selected_video_codec: - codec = "libx264" - elif "x265" in selected_video_codec: - codec = "libx265" - else: - codec = selected_video_codec - - txt_path = f"{os_path_splitext(video_output_path)[0]}.txt" - no_audio_path = f"{os_path_splitext(video_output_path)[0]}_no_audio{os_path_splitext(video_output_path)[1]}" - video_fps = str(get_video_fps(video_path)) - - # Cleaning files from previous encoding - if os_path_exists(no_audio_path): - os_remove(no_audio_path) - if os_path_exists(txt_path): - os_remove(txt_path) - - # Create a file .txt with all upscaled video frames paths || this file is essential - with os_fdopen(os_open(txt_path, O_WRONLY | O_CREAT, 0o777), 'w', encoding="utf-8") as txt: - for frame_path in upscaled_frame_paths: - txt.write(f"file '{frame_path}' \n") - - # Create the upscaled video without audio - print(f"[FFMPEG] ENCODING ({codec})") try: - encoding_command = [ - FFMPEG_EXE_PATH, - "-y", - "-loglevel", "error", - "-f", "concat", - "-safe", "0", - "-r", video_fps, - "-i", txt_path, - "-c:v", codec, - "-vf", "scale=in_range=full:out_range=limited,format=yuv420p", - "-color_range", "tv", - "-b:v", "12000k", - no_audio_path - ] - subprocess_run(encoding_command, check=True, shell="False") + # Validate inputs + if not upscaled_frame_paths: + raise ValueError("No frame paths provided for video encoding") + + # Check if all frame files exist + missing_frames = [ + path for path in upscaled_frame_paths if not os_path_exists(path)] + if missing_frames: + raise FileNotFoundError( + f"Missing {len(missing_frames)} frame files. First missing: {missing_frames[0]}") + + if "x264" in selected_video_codec: + codec = "libx264" + elif "x265" in selected_video_codec: + codec = "libx265" + else: + codec = selected_video_codec + + txt_path = f"{os_path_splitext(video_output_path)[0]}.txt" + no_audio_path = f"{os_path_splitext(video_output_path)[0]}_no_audio{os_path_splitext(video_output_path)[1]}" + + try: + video_fps = str(get_video_fps(video_path)) + if float(video_fps) <= 0: + raise ValueError(f"Invalid frame rate: {video_fps}") + except Exception as e: + print( + f"Warning: Could not get video FPS, using default 30.0: {str(e)}") + video_fps = "30.0" + + # Cleaning files from previous encoding + if os_path_exists(no_audio_path): + os_remove(no_audio_path) if os_path_exists(txt_path): os_remove(txt_path) - except: + # Create a file .txt with all upscaled video frames paths || this file is essential + try: + with os_fdopen(os_open(txt_path, O_WRONLY | O_CREAT, 0o777), 'w', encoding="utf-8") as txt: + for frame_path in upscaled_frame_paths: + # Ensure the path exists before writing to file + if os_path_exists(frame_path): + txt.write(f"file '{frame_path}' \n") + else: + print(f"Warning: Frame file not found: {frame_path}") + except Exception as e: + raise RuntimeError(f"Failed to create frame list file: {str(e)}") + + # Create the upscaled video without audio + print(f"[FFMPEG] ENCODING ({codec})") + try: + # Check if ffmpeg exists + if not os_path_exists(FFMPEG_EXE_PATH): + raise FileNotFoundError("FFmpeg executable not found") + + encoding_command = [ + FFMPEG_EXE_PATH, + "-y", + "-loglevel", "error", + "-f", "concat", + "-safe", "0", + "-r", video_fps, + "-i", txt_path, + "-c:v", codec, + "-vf", "scale=in_range=full:out_range=limited,format=yuv420p", + "-color_range", "tv", + "-movflags", "+faststart", + "-b:v", "12000k", + no_audio_path + ] + + result = subprocess_run( + encoding_command, check=True, shell=False, capture_output=True, text=True) + + # Check if output file was created successfully + if not os_path_exists(no_audio_path): + raise RuntimeError( + "Video encoding completed but output file was not created") + + if os_path_exists(txt_path): + os_remove(txt_path) + + print(f"[FFMPEG] Video encoding completed successfully") + + except subprocess.CalledProcessError as e: + error_msg = f"FFmpeg encoding failed: {e.stderr if e.stderr else str(e)}" + write_process_status( + process_status_q, + f"{ERROR_STATUS}{error_msg}\nHave you selected a codec compatible with your GPU? If the issue persists, try selecting 'x264'." + ) + return + except Exception as e: + write_process_status( + process_status_q, + f"{ERROR_STATUS}An error occurred during video encoding: {str(e)} \nHave you selected a codec compatible with your GPU? If the issue persists, try selecting 'x264'." + ) + return + + # Copy the audio from original video + print("[FFMPEG] AUDIO PASSTHROUGH") + audio_passthrough_command = [ + FFMPEG_EXE_PATH, + "-y", + "-loglevel", "error", + "-i", video_path, + "-i", no_audio_path, + "-c:v", "copy", + "-map", "1:v:0", + "-map", "0:a?", + "-c:a", "copy", + video_output_path + ] + try: + result = subprocess_run( + audio_passthrough_command, check=True, shell=False, capture_output=True, text=True) + if os_path_exists(no_audio_path): + os_remove(no_audio_path) + print(f"[FFMPEG] Audio passthrough completed successfully") + except subprocess.CalledProcessError as e: + print( + f"[FFMPEG] Audio passthrough error: {e.stderr if e.stderr else str(e)}") + # If audio passthrough fails, just copy the no-audio version + if os_path_exists(no_audio_path): + try: + shutil_move(no_audio_path, video_output_path) + print( + f"[FFMPEG] Using video without audio due to passthrough failure") + except Exception as move_error: + print( + f"[FFMPEG] Failed to move no-audio file: {str(move_error)}") + except Exception as e: + print(f"[FFMPEG] Audio passthrough error: {str(e)}") + # If audio passthrough fails, just copy the no-audio version + if os_path_exists(no_audio_path): + try: + shutil_move(no_audio_path, video_output_path) + print( + f"[FFMPEG] Using video without audio due to passthrough failure") + except Exception as move_error: + print( + f"[FFMPEG] Failed to move no-audio file: {str(move_error)}") + + except Exception as e: write_process_status( process_status_q, - f"{ERROR_STATUS}An error occurred during video encoding. \n Have you selected a codec compatible with your GPU? If the issue persists, try selecting 'x264'." + f"{ERROR_STATUS}Video encoding failed: {str(e)}" ) - # Copy the audio from original video - print("[FFMPEG] AUDIO PASSTHROUGH") - audio_passthrough_command = [ - FFMPEG_EXE_PATH, - "-y", - "-loglevel", "error", - "-i", video_path, - "-i", no_audio_path, - "-c:v", "copy", - "-map", "1:v:0", - "-map", "0:a?", - "-c:a", "copy", - video_output_path - ] - try: - subprocess_run(audio_passthrough_command, check=True, shell="False") - if os_path_exists(no_audio_path): - os_remove(no_audio_path) - except: - pass - def check_video_upscaling_resume( target_directory: str, @@ -1776,41 +1939,53 @@ def blend_images_and_save( starting_image, starting_image_importance, upscaled_image, upscaled_image_importance, 0) image_write(target_path, interpolated_image, file_extension) - except: + except Exception as e: + print( + f"[BLEND] Blending failed, saving original upscaled image: {str(e)}") image_write(target_path, upscaled_image, file_extension) # Core functions ------------------------ def check_upscale_steps() -> None: + """Monitorea el estado del proceso de escalado en un hilo separado.""" + global stop_thread_flag sleep(1) - try: - while True: + while not stop_thread_flag.is_set(): + try: actual_step = read_process_status() if actual_step == COMPLETED_STATUS: info_message.set(f"All files completed!") stop_upscale_process() - stop_thread() + stop_thread_flag.set() # Señaliza la finalización del hilo + break # Sal del bucle elif actual_step == STOP_STATUS: info_message.set(f"Magic stopped") stop_upscale_process() - stop_thread() + stop_thread_flag.set() # Señaliza la finalización del hilo + break # Sal del bucle elif ERROR_STATUS in actual_step: info_message.set(f"Error while upscaling :(") error_to_show = actual_step.replace(ERROR_STATUS, "") show_error_message(error_to_show.strip()) - stop_thread() - + stop_thread_flag.set() # Señaliza la finalización del hilo + break # Sal del bucle else: info_message.set(actual_step) sleep(1) - except: - place_upscale_button() + except Exception as e: + # Si hay un error al leer la cola, el proceso principal probablemente murió. + print(f"[MONITOR] Error reading process status: {str(e)}") + # Sal del bucle para terminar el hilo. + break + + # Se asegura de que el botón de re-inicio aparezca al final + place_upscale_button() def read_process_status() -> str: @@ -1829,7 +2004,7 @@ def stop_upscale_process() -> None: global process_upscale_orchestrator try: process_upscale_orchestrator - except: + except NameError: pass else: process_upscale_orchestrator.kill() @@ -1925,19 +2100,26 @@ def fluidframes_interpolation_pipeline( current_file_number = file_number + 1 # Branch between video and image: only video gets interpolation if check_if_file_is_video(file_path): - fluidframes_video_interpolate( - process_status_q, file_path, current_file_number, selected_output_path, AI_instance, - selected_AI_model, frame_gen_factor, slowmotion, selected_image_extension, selected_video_extension, - selected_video_codec, input_resize_factor, output_resize_factor, cpu_number, selected_keep_frames - ) + try: + fluidframes_video_interpolate( + process_status_q, file_path, current_file_number, selected_output_path, AI_instance, + selected_AI_model, frame_gen_factor, slowmotion, selected_image_extension, selected_video_extension, + selected_video_codec, input_resize_factor, output_resize_factor, cpu_number, selected_keep_frames + ) + except Exception as file_error: + write_process_status( + process_status_q, f"{ERROR_STATUS}Error processing {os_path_basename(file_path)}: {str(file_error)}") + continue # Continue with next file else: # If an image, just no-op/fail, or could add image interpolation, but that's not FluidFrames write_process_status( - process_status_q, f"{current_file_number}. File is not a video; skipping.") + process_status_q, f"{current_file_number}. File is not a video; skipping interpolation for image files.") write_process_status(process_status_q, f"{COMPLETED_STATUS}") except Exception as exception: + error_msg = str(exception) + print(f"Error in FluidFrames interpolation pipeline: {error_msg}") write_process_status( - process_status_q, f"{ERROR_STATUS} {str(exception)}") + process_status_q, f"{ERROR_STATUS}Interpolation error: {error_msg}") # Helper for generation options string -> factor/slowmotion # (straight copy from FluidFrames.py, rename as needed) @@ -2046,72 +2228,25 @@ def fluidframes_video_interpolate( end_timer = timer() processing_time = end_timer - start_timer global_processing_times_list.append(processing_time) - # Step 5. Save/copy/cleanup - if not selected_keep_frames: - if os_path_exists(target_directory): - remove_directory(target_directory) + # Step 5. Save/copy/cleanup - cleanup handled at end of process # Step 6. Video encoding write_process_status( process_status_q, f"{file_number}. Encoding frame-generated video") video_encoding( - process_status_q, video_path, video_output_path, total_frames_paths, frame_gen_factor, slowmotion, selected_video_codec) + process_status_q, video_path, video_output_path, total_frames_paths, selected_video_codec) copy_file_metadata(video_path, video_output_path) - # Removed invalid global declarations (because they are parameters) - - if user_input_checks(): - info_message.set("Loading") - - cpu_number = int(os_cpu_count()/2) - - print("=" * 50) - print("> Starting upscale:") - print(f" Files to upscale: {len(selected_file_list)}") - print(f" Output path: {(selected_output_path.get())}") - print(f" Selected AI model: {selected_AI_model}") - print(f" Selected GPU: {selected_gpu}") - print(f" AI multithreading: {selected_AI_multithreading}") - print(f" Blending factor: {selected_blending_factor}") - print(f" Selected image output extension: {selected_image_extension}") - print(f" Selected video output extension: {selected_video_extension}") - print(f" Selected video output codec: {selected_video_codec}") - print( - f" Tiles resolution for selected GPU VRAM: {tiles_resolution}x{tiles_resolution}px") - print(f" Input resize factor: {int(input_resize_factor * 100)}%") - print(f" Output resize factor: {int(output_resize_factor * 100)}%") - print(f" Cpu number: {cpu_number}") - print(f" Save frames: {selected_keep_frames}") - print("=" * 50) - - place_stop_button() - - process_upscale_orchestrator = Process( - target=upscale_orchestrator, - args=( - process_status_q, - selected_file_list, - selected_output_path.get(), - selected_AI_model, - selected_AI_multithreading, - input_resize_factor, - output_resize_factor, - selected_gpu, - tiles_resolution, - selected_blending_factor, - selected_keep_frames, - selected_image_extension, - selected_video_extension, - selected_video_codec, - cpu_number, - ) - ) - process_upscale_orchestrator.start() - - thread_wait = Thread(target=check_upscale_steps) - thread_wait.start() + # Step 7. Cleanup after video interpolation processing + if not selected_keep_frames and os_path_exists(target_directory): + try: + remove_directory(target_directory) + except Exception as e: + print( + f"Warning: Could not remove directory {target_directory}: {str(e)}") # ORCHESTRATOR + def upscale_orchestrator( process_status_q: multiprocessing_Queue, selected_file_list: list, @@ -2267,7 +2402,7 @@ def upscale_video( try: average_processing_time = numpy_mean(global_processing_times_list) - except: + except Exception: average_processing_time = 0.0 remaining_frames = frames_to_upscale_counter @@ -2455,9 +2590,9 @@ def upscale_video( # 1.Preparation target_directory = prepare_output_video_directory_name( - video_path, selected_output_path, selected_AI_model, input_resize_factor, output_resize_factor, selected_blending_factor) + video_path, selected_output_path, selected_AI_model, 1, False, input_resize_factor, output_resize_factor) video_output_path = prepare_output_video_filename(video_path, selected_output_path, selected_AI_model, - input_resize_factor, output_resize_factor, selected_video_extension, selected_blending_factor) + 1, False, input_resize_factor, output_resize_factor, selected_video_extension) # 2. Resume upscaling OR Extract video frames video_upscale_continue = check_video_upscaling_resume( @@ -2471,7 +2606,7 @@ def upscale_video( write_process_status( process_status_q, f"{file_number}. Extracting video frames") extracted_frames_paths = extract_video_frames( - process_status_q, file_number, target_directory, video_path, cpu_number, half_frames=False) + process_status_q, file_number, target_directory, AI_upscale_instance_list[0], video_path, cpu_number, ".jpg") upscaled_frame_paths = [prepare_output_video_frame_filename( frame_path, selected_AI_model, input_resize_factor, output_resize_factor, selected_blending_factor) for frame_path in extracted_frames_paths] @@ -2503,7 +2638,11 @@ def upscale_video( # 7. Delete frames folder if selected_keep_frames == False: if os_path_exists(target_directory): - remove_directory(target_directory) + try: + remove_directory(target_directory) + except Exception as e: + print( + f"Warning: Could not remove directory {target_directory}: {str(e)}") # GUI utils function --------------------------- @@ -2523,7 +2662,7 @@ def user_input_checks() -> bool: # Selected files try: selected_file_list = file_widget.get_selected_file_list() - except: + except Exception: info_message.set("Please select a file") return False @@ -2540,7 +2679,7 @@ def user_input_checks() -> bool: try: input_resize_factor = int( float(str(selected_input_resize_factor.get()))) - except: + except (ValueError, TypeError): info_message.set("Input resolution % must be a number") return False @@ -2554,7 +2693,7 @@ def user_input_checks() -> bool: try: output_resize_factor = int( float(str(selected_output_resize_factor.get()))) - except: + except (ValueError, TypeError): info_message.set("Output resolution % must be a number") return False @@ -2564,22 +2703,25 @@ def user_input_checks() -> bool: info_message.set("Output resolution % must be a value > 0") return False - # VRAM limiter +# VRAM limiter try: - tiles_resolution = 100 * int(float(str(selected_VRAM_limiter.get()))) - except: - info_message.set("GPU VRAM value must be a number") - return False + vram_gb = int(float(str(selected_VRAM_limiter.get()))) + if vram_gb <= 0: + info_message.set("GPU VRAM value must be a value > 0") + return False - if tiles_resolution > 0: vram_multiplier = VRAM_model_usage.get(selected_AI_model) if vram_multiplier is None: vram_multiplier = 1 # Default for interpolation models or unknowns - selected_vram = (vram_multiplier * - int(float(str(selected_VRAM_limiter.get())))) - tiles_resolution = int(selected_vram * 100) - else: - info_message.set("GPU VRAM value must be a value > 0") + + # El cálculo original parece confuso. Esta es una interpretación más clara: + # Se asume que el VRAM Limiter es la VRAM en GB y se multiplica por un factor y 100. + # Si el modelo 'RealESR_Gx4' (factor 2.2) y VRAM es 4GB, tiles_resolution sería ~880. + selected_vram_factor = vram_multiplier * vram_gb + tiles_resolution = int(selected_vram_factor * 100) + + except (ValueError, TypeError): + info_message.set("GPU VRAM value must be a number") return False return True @@ -2694,7 +2836,7 @@ def clear_dynamic_menus() -> None: widget_info = widget.place_info() if widget_info and float(widget_info.get('rely', 0)) == row2: widget.place_forget() - except: + except Exception: pass @@ -3507,7 +3649,7 @@ class SplashScreen(CTkToplevel): ) has_banner = True except Exception as e: - print(f"Could not load splash banner: {e}") + print(f"[SPLASH] Could not load splash banner: {e}") has_banner = False window_height = 200 # Smaller height if no banner @@ -3651,6 +3793,21 @@ if __name__ == "__main__": selected_frame_generation_option = "OFF" # Initialize frame generation option + # Initialize global variables that are used in video processing + global stop_thread_flag + global global_processing_times_list + global global_upscaled_frames_paths + global global_can_i_update_status + global output_resize_factor + global tiles_resolution + + stop_thread_flag = Event() + global_processing_times_list = [] + global_upscaled_frames_paths = [] + global_can_i_update_status = False + output_resize_factor = 1.0 + tiles_resolution = 800 # Default value + selected_input_resize_factor.set(default_input_resize_factor) selected_output_resize_factor.set(default_output_resize_factor) selected_VRAM_limiter.set(default_VRAM_limiter) diff --git a/Warlock-Studio.spec b/Warlock-Studio.spec index 50155d3..35d0793 100644 --- a/Warlock-Studio.spec +++ b/Warlock-Studio.spec @@ -1,16 +1,16 @@ -# -*- mode: python ; coding: utf-8 -*- +# -*- mode: python ; coding: utf-8 -*- a = Analysis( ['Warlock-Studio.py'], pathex=[], binaries=[], - datas=[('AI-onnx', 'AI-onnx'),('rsc', 'rsc'), ('Assets', 'Assets')], + datas=[('AI-onnx', 'AI-onnx'), ('Assets', 'Assets'), ('rsc', 'rsc')], hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=[], + excludes=['nltk', 'scipy', 'scipy.stats', 'scipy.stats.distributions', 'scipy.stats._distn_infrastructure'], noarchive=False, optimize=0, )