Add files via upload

This commit is contained in:
Iván Eduardo Chavez Ayub
2025-06-23 12:09:20 -06:00
committed by GitHub
parent 9412399a5d
commit 717e84afaf
5 changed files with 628 additions and 323 deletions
+49 -4
View File
@@ -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** # 📝 **CHANGELOG — Warlock-Studio v2.0**
**Release Date:** June 6, 2025 **Release Date:** June 6, 2025
@@ -13,7 +62,6 @@
- Enables temporal upscaling of video via AI. - Enables temporal upscaling of video via AI.
- 🎥 **RIFE Models Integration:** - 🎥 **RIFE Models Integration:**
- Added `RIFE` and `RIFE_Lite` to supported models. - Added `RIFE` and `RIFE_Lite` to supported models.
- Interpolation model list introduced: `RIFE_models_list`. - Interpolation model list introduced: `RIFE_models_list`.
- Extended `AI_models_list` to include all model types: SRVGGNetCompact, BSRGAN, IRCNN, and RIFE. - Extended `AI_models_list` to include all model types: SRVGGNetCompact, BSRGAN, IRCNN, and RIFE.
@@ -45,7 +93,6 @@
- Ensures broader compatibility with input formats. - Ensures broader compatibility with input formats.
- 🚀 **Improved GPU Execution Support:** - 🚀 **Improved GPU Execution Support:**
- Enhanced logic for selecting GPU via `DirectML`. - Enhanced logic for selecting GPU via `DirectML`.
- Supports up to 4 GPUs (`Auto`, `GPU 1` to `GPU 4`) via `provider_options`. - 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). - Support for dynamic multi-frame generation with tree-based logic (e.g. A-B-C from D).
- 📊 **Improved Numeric Precision and Postprocessing:** - 📊 **Improved Numeric Precision and Postprocessing:**
- Improved handling of floating-point range and normalization. - Improved handling of floating-point range and normalization.
- Enhanced logic for RGB/RGBA conversion and alpha blending. - 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)`). - MessageBox window can now be resized by the user (`resizable(True, True)`).
- 👌 **Improved Dialog Formatting:** - 👌 **Improved Dialog Formatting:**
- Better spacing and ordering of message elements. - Better spacing and ordering of message elements.
- Cleaner font use and default value display. - Cleaner font use and default value display.
+150 -47
View File
@@ -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:
<table>
<tr>
<td align="center">
<a href="https://sourceforge.net/projects/warlock-studio/files/latest/download">
<img src="https://a.fsdn.com/con/app/sf-download-button" alt="Download from SourceForge" />
</a>
</td>
<td align="center">
<a href="https://ivanayub97.itch.io/warlock-studio">
<img src="rsc/badge-color.png" alt="Download from Itch.io" />
</a>
</td>
<td align="center">
<a href="https://drive.google.com/file/d/1ZSLyaU6zWQErPphXUcdFyxNAfATnb0J7/view?usp=sharing">
<img src="rsc/google_drive-logo.png" alt="Download from Google Drive" />
</a>
</td>
</tr>
</table>
![Warlock-Studio logo](rsc/banner.png) ![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 Version 2.1 introduces major improvements, including:
![icon](Assets/logo.ico)
- 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) ![Screenshot of Warlock-Studio](rsc/Capture.png)
- RIFE Options UI ### 🔹 RIFE (Frame Interpolation) Options
![Screenshot of Warlock-Studio](rsc/CaptureRIFE.png) ![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 ## 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. 1. **Run the installer** and follow the setup instructions.
2. **Launch the app:** open `Warlock-Studio.exe` on Windows. 2. **Launch the application** by opening `Warlock-Studio.exe`.
3. **Start enhancing** your images and videos with a few clicks! 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 ## Key Features
- **State-of-the-Art AI Models:** - **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. 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:** - **AI Frame Interpolation & Slow Motion**
Generate intermediate frames between existing video frames using RIFE. Create smooth **x2/x4/x8** transitions or cinematic slow motion effects. Generate new in-between frames using RIFE to create smooth **2x/4x/8x** motion or dramatic slow-motion effects.
- **Batch Processing:** - **Batch Processing**
Upscale, interpolate, and enhance multiple images or videos in one go—ideal for large collections. Simultaneously process multiple images or videos—ideal for large-scale media projects.
- **Customizable Workflows:** - **Customizable Workflows**
Pick your AI model, output resolution, file format (PNG, JPEG, MP4, etc.), and quality settings to suit any project. Choose your preferred AI model, output resolution, format (PNG, JPEG, MP4, etc.), and quality settings for full creative control.
- **Intuitive UI:** - **Intuitive Interface**
A clean, user-friendly interface designed for both novices and pros—everything you need is a click away. Designed for both beginners and professionals—simple, clean, and efficient.
- **Open-Source & Extensible:** - **Open-Source & Extensible**
Licensed under the MIT License. Additional conditions are described in the [NOTICE](NOTICE) file. Licensed under the MIT License. Additional usage terms can be found in the [NOTICE](NOTICE) file.
--- ---
## How to Use ## How to Use
1. **Run as Administrator** (optional but recommended for best performance). 1. **Run as Administrator** (optional but recommended for optimal performance).
2. **Load Media:** drag & drop images, videos, or folders into the app. 2. **Load your media**: drag and drop images, videos, or folders directly into the app.
3. **Configure Settings:** 3. **Configure settings**:
- **Choose AI Model** (Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, **RIFE**, etc.) - Select an **AI Model** (e.g., Real-ESRGAN, SRGAN, BSRGAN, IRCNN, Waifu2x, Anime4K, RIFE)
- **Set Output Resolution**, **Format**, and optionally enable **interpolation** or **slow motion** - 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. 4. **Start Processing**: click **Start** to begin enhancement.
5. **Retrieve Results:** the enhanced files will appear in your selected output folder. 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 ## System Requirements
- **OS:** Windows 10 or later - **Operating System:** Windows 10 or later
- **RAM:** 4 GB minimum (8 GB+ recommended) - **Memory (RAM):** Minimum 4 GB (8 GB or more recommended)
- **GPU:** NVIDIA or DirectML-compatible GPU highly recommended for speed and compatibility - **Graphics Card:** NVIDIA or DirectML-compatible GPU highly recommended for performance
- **Storage:** Ample space for your media files and outputs - **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) | | 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) | | 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) | | 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) | | 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) | | 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) | | 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) | | 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/) | | 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) | | 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) | | 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) | | 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 ## 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. 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 ## License
© 2025 Iván Eduardo Chavez Ayub © 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.
+4 -4
View File
@@ -1,7 +1,7 @@
[Setup] [Setup]
; Basic installation configuration ; Basic installation configuration
AppName=Warlock-Studio AppName=Warlock-Studio 2.1
AppVersion=2.0 AppVersion=2.1
DefaultDirName={pf}\Warlock-Studio DefaultDirName={pf}\Warlock-Studio
DefaultGroupName=Warlock-Studio DefaultGroupName=Warlock-Studio
OutputDir=.\Output OutputDir=.\Output
@@ -49,11 +49,11 @@ begin
MsgBox('© 2025 Iván Eduardo Chavez Ayub'#13#10 + 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 + '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 + '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 + '*** 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 + '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 + '*** INTEGRATED TECHNOLOGIES & LICENSES ***'#13#10 +
+422 -265
View File
@@ -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 expanduser as os_path_expanduser
from os.path import join as os_path_join from os.path import join as os_path_join
from os.path import splitext as os_path_splitext from os.path import splitext as os_path_splitext
from shutil import move as shutil_move
from shutil import rmtree as remove_directory from shutil import rmtree as remove_directory
from subprocess import CalledProcessError
from subprocess import run as subprocess_run from subprocess import run as subprocess_run
from threading import Thread from threading import Event, Thread
from time import sleep from time import sleep
from timeit import default_timer as timer from timeit import default_timer as timer
# GUI imports # GUI imports
@@ -71,6 +73,13 @@ from onnxruntime import InferenceSession
from PIL.Image import fromarray as pillow_image_fromarray from PIL.Image import fromarray as pillow_image_fromarray
from PIL.Image import open as pillow_image_open 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: if sys.stdout is None:
sys.stdout = open(os_devnull, "w") sys.stdout = open(os_devnull, "w")
if sys.stderr is None: if sys.stderr is None:
@@ -84,12 +93,12 @@ def find_by_relative_path(relative_path: str) -> str:
app_name = "Warlock-Studio" app_name = "Warlock-Studio"
version = "2.0" version = "2.1"
background_color = "#121212" # Negro grisáceo profundo background_color = "#121212" # Negro grisáceo profundo
app_name_color = "#FF0E0E" # Blanco puro para el nombre de la app app_name_color = "#ECD125" # Blanco puro para el nombre de la app
widget_background_color = "#454242" # Rojo oscuro (Dark Red) widget_background_color = "#960707" # Rojo oscuro (Dark Red)
text_color = "#FFFFFF" # Blanco opaco para texto legible text_color = "#F0EEEE" # Blanco opaco para texto legible
VRAM_model_usage = { VRAM_model_usage = {
'RealESR_Gx4': 2.2, 'RealESR_Gx4': 2.2,
@@ -219,21 +228,7 @@ little_textbox_width = 74
little_menu_width = 98 little_menu_width = 98
supported_file_extensions = [ # Remove duplicate definitions - using the ones defined earlier
'.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"
]
# AI ------------------- # AI -------------------
@@ -273,23 +268,35 @@ class AI_upscale:
return 4 return 4
def _load_inferenceSession(self) -> None: 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: match self.directml_gpu:
case 'Auto': provider_options = [{"performance_preference": "high_performance"}] case 'Auto': provider_options = [{"performance_preference": "high_performance"}]
case 'GPU 1': provider_options = [{"device_id": "0"}] case 'GPU 1': provider_options = [{"device_id": "0"}]
case 'GPU 2': provider_options = [{"device_id": "1"}] case 'GPU 2': provider_options = [{"device_id": "1"}]
case 'GPU 3': provider_options = [{"device_id": "2"}] case 'GPU 3': provider_options = [{"device_id": "2"}]
case 'GPU 4': provider_options = [{"device_id": "3"}] case 'GPU 4': provider_options = [{"device_id": "3"}]
inference_session = InferenceSession( inference_session = InferenceSession(
path_or_bytes=self.AI_model_path, path_or_bytes=self.AI_model_path,
providers=providers, providers=providers,
provider_options=provider_options, 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 # INTERNAL CLASS FUNCTIONS
@@ -593,34 +600,48 @@ class AI_interpolation:
self.inferenceSession = self._load_inferenceSession() self.inferenceSession = self._load_inferenceSession()
def _load_inferenceSession(self) -> 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: match self.directml_gpu:
case 'Auto': provider_options = [{"performance_preference": "high_performance"}] case 'Auto': provider_options = [{"performance_preference": "high_performance"}]
case 'GPU 1': provider_options = [{"device_id": "0"}] case 'GPU 1': provider_options = [{"device_id": "0"}]
case 'GPU 2': provider_options = [{"device_id": "1"}] case 'GPU 2': provider_options = [{"device_id": "1"}]
case 'GPU 3': provider_options = [{"device_id": "2"}] case 'GPU 3': provider_options = [{"device_id": "2"}]
case 'GPU 4': provider_options = [{"device_id": "3"}] case 'GPU 4': provider_options = [{"device_id": "3"}]
inference_session = InferenceSession( inference_session = InferenceSession(
path_or_bytes=self.AI_model_path, path_or_bytes=self.AI_model_path,
providers=providers, providers=providers,
provider_options=provider_options 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 # INTERNAL CLASS FUNCTIONS
def get_image_mode(self, image: numpy_ndarray) -> str: def get_image_mode(self, image: numpy_ndarray) -> str:
match image.shape: shape = image.shape
case (rows, cols): if len(shape) == 2: # Grayscale: 2D array (rows, cols)
return "Grayscale" return "Grayscale"
case (rows, cols, channels) if channels == 3: # RGB: 3D array with 3 channels
return "RGB" elif len(shape) == 3 and shape[2] == 3:
case (rows, cols, channels) if channels == 4: return "RGB"
return "RGBA" # 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: def get_image_resolution(self, image: numpy_ndarray) -> tuple:
height = image.shape[0] height = image.shape[0]
@@ -1137,14 +1158,14 @@ def get_values_for_file_widget() -> tuple:
try: try:
input_resize_factor = int( input_resize_factor = int(
float(str(selected_input_resize_factor.get()))) float(str(selected_input_resize_factor.get())))
except: except (ValueError, TypeError):
input_resize_factor = 0 input_resize_factor = 0
# Output resolution % # Output resolution %
try: try:
output_resize_factor = int( output_resize_factor = int(
float(str(selected_output_resize_factor.get()))) float(str(selected_output_resize_factor.get())))
except: except (ValueError, TypeError):
output_resize_factor = 0 output_resize_factor = 0
return upscale_factor, input_resize_factor, output_resize_factor 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: def update_file_widget(a, b, c) -> None:
try: try:
global file_widget selected_file_list = file_widget.get_selected_file_list()
file_widget except Exception:
except:
return return
upscale_factor, input_resize_factor, output_resize_factor = get_values_for_file_widget() upscale_factor, input_resize_factor, output_resize_factor = get_values_for_file_widget()
@@ -1303,7 +1323,7 @@ def create_active_button(
icon: CTkImage = None, icon: CTkImage = None,
width: int = 140, width: int = 140,
height: int = 30, height: int = 30,
border_color: str = "#0096FF" border_color: str = "#C11919"
) -> CTkButton: ) -> CTkButton:
return CTkButton( return CTkButton(
@@ -1331,7 +1351,10 @@ def create_dir(name_dir: str) -> None:
os_makedirs(name_dir, mode=0o777) 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: 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: 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: try:
subprocess_run(exiftool_cmd, check=True, shell="False") # Check if exiftool exists
except: if not os_path_exists(EXIFTOOL_EXE_PATH):
pass 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( def prepare_output_image_filename(
@@ -1547,45 +1589,88 @@ def extract_video_frames(
selected_image_extension: str selected_image_extension: str
) -> list[str]: ) -> list[str]:
# FluidFrames-compatible implementation # FluidFrames-compatible implementation
create_dir(target_directory) try:
create_dir(target_directory)
frames_number_to_save = cpu_number * ECTRACTION_FRAMES_FOR_CPU # Check if video file exists
video_capture = opencv_VideoCapture(video_path) if not os_path_exists(video_path):
frame_count = int(video_capture.get(CAP_PROP_FRAME_COUNT)) raise FileNotFoundError(f"Video file not found: {video_path}")
extracted_frames = [] frames_number_to_save = cpu_number * ECTRACTION_FRAMES_FOR_CPU
extracted_frames_paths = [] video_capture = opencv_VideoCapture(video_path)
video_frames_list = []
frame_index = 0
for frame_number in range(frame_count): # Check if video was opened successfully
success, frame = video_capture.read() if not video_capture.isOpened():
if not success: raise ValueError(f"Could not open video file: {video_path}")
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)
if len(extracted_frames) == frames_number_to_save: frame_count = int(video_capture.get(CAP_PROP_FRAME_COUNT))
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_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: for frame_number in range(frame_count):
save_extracted_frames(extracted_frames_paths, success, frame = video_capture.read()
extracted_frames, cpu_number) 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( def video_encoding(
@@ -1595,77 +1680,155 @@ def video_encoding(
upscaled_frame_paths: list[str], upscaled_frame_paths: list[str],
selected_video_codec: str, selected_video_codec: str,
) -> None: ) -> 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: try:
encoding_command = [ # Validate inputs
FFMPEG_EXE_PATH, if not upscaled_frame_paths:
"-y", raise ValueError("No frame paths provided for video encoding")
"-loglevel", "error",
"-f", "concat", # Check if all frame files exist
"-safe", "0", missing_frames = [
"-r", video_fps, path for path in upscaled_frame_paths if not os_path_exists(path)]
"-i", txt_path, if missing_frames:
"-c:v", codec, raise FileNotFoundError(
"-vf", "scale=in_range=full:out_range=limited,format=yuv420p", f"Missing {len(missing_frames)} frame files. First missing: {missing_frames[0]}")
"-color_range", "tv",
"-b:v", "12000k", if "x264" in selected_video_codec:
no_audio_path codec = "libx264"
] elif "x265" in selected_video_codec:
subprocess_run(encoding_command, check=True, shell="False") 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): if os_path_exists(txt_path):
os_remove(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( write_process_status(
process_status_q, 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( def check_video_upscaling_resume(
target_directory: str, target_directory: str,
@@ -1776,41 +1939,53 @@ def blend_images_and_save(
starting_image, starting_image_importance, upscaled_image, upscaled_image_importance, 0) starting_image, starting_image_importance, upscaled_image, upscaled_image_importance, 0)
image_write(target_path, interpolated_image, file_extension) 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) image_write(target_path, upscaled_image, file_extension)
# Core functions ------------------------ # Core functions ------------------------
def check_upscale_steps() -> None: def check_upscale_steps() -> None:
"""Monitorea el estado del proceso de escalado en un hilo separado."""
global stop_thread_flag
sleep(1) sleep(1)
try: while not stop_thread_flag.is_set():
while True: try:
actual_step = read_process_status() actual_step = read_process_status()
if actual_step == COMPLETED_STATUS: if actual_step == COMPLETED_STATUS:
info_message.set(f"All files completed!") info_message.set(f"All files completed!")
stop_upscale_process() 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: elif actual_step == STOP_STATUS:
info_message.set(f"Magic stopped") info_message.set(f"Magic stopped")
stop_upscale_process() 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: elif ERROR_STATUS in actual_step:
info_message.set(f"Error while upscaling :(") info_message.set(f"Error while upscaling :(")
error_to_show = actual_step.replace(ERROR_STATUS, "") error_to_show = actual_step.replace(ERROR_STATUS, "")
show_error_message(error_to_show.strip()) 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: else:
info_message.set(actual_step) info_message.set(actual_step)
sleep(1) sleep(1)
except: except Exception as e:
place_upscale_button() # 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: def read_process_status() -> str:
@@ -1829,7 +2004,7 @@ def stop_upscale_process() -> None:
global process_upscale_orchestrator global process_upscale_orchestrator
try: try:
process_upscale_orchestrator process_upscale_orchestrator
except: except NameError:
pass pass
else: else:
process_upscale_orchestrator.kill() process_upscale_orchestrator.kill()
@@ -1925,19 +2100,26 @@ def fluidframes_interpolation_pipeline(
current_file_number = file_number + 1 current_file_number = file_number + 1
# Branch between video and image: only video gets interpolation # Branch between video and image: only video gets interpolation
if check_if_file_is_video(file_path): if check_if_file_is_video(file_path):
fluidframes_video_interpolate( try:
process_status_q, file_path, current_file_number, selected_output_path, AI_instance, fluidframes_video_interpolate(
selected_AI_model, frame_gen_factor, slowmotion, selected_image_extension, selected_video_extension, process_status_q, file_path, current_file_number, selected_output_path, AI_instance,
selected_video_codec, input_resize_factor, output_resize_factor, cpu_number, selected_keep_frames 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: else:
# If an image, just no-op/fail, or could add image interpolation, but that's not FluidFrames # If an image, just no-op/fail, or could add image interpolation, but that's not FluidFrames
write_process_status( 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}") write_process_status(process_status_q, f"{COMPLETED_STATUS}")
except Exception as exception: except Exception as exception:
error_msg = str(exception)
print(f"Error in FluidFrames interpolation pipeline: {error_msg}")
write_process_status( 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 # Helper for generation options string -> factor/slowmotion
# (straight copy from FluidFrames.py, rename as needed) # (straight copy from FluidFrames.py, rename as needed)
@@ -2046,72 +2228,25 @@ def fluidframes_video_interpolate(
end_timer = timer() end_timer = timer()
processing_time = end_timer - start_timer processing_time = end_timer - start_timer
global_processing_times_list.append(processing_time) global_processing_times_list.append(processing_time)
# Step 5. Save/copy/cleanup # Step 5. Save/copy/cleanup - cleanup handled at end of process
if not selected_keep_frames:
if os_path_exists(target_directory):
remove_directory(target_directory)
# Step 6. Video encoding # Step 6. Video encoding
write_process_status( write_process_status(
process_status_q, f"{file_number}. Encoding frame-generated video") process_status_q, f"{file_number}. Encoding frame-generated video")
video_encoding( 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) 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 # ORCHESTRATOR
def upscale_orchestrator( def upscale_orchestrator(
process_status_q: multiprocessing_Queue, process_status_q: multiprocessing_Queue,
selected_file_list: list, selected_file_list: list,
@@ -2267,7 +2402,7 @@ def upscale_video(
try: try:
average_processing_time = numpy_mean(global_processing_times_list) average_processing_time = numpy_mean(global_processing_times_list)
except: except Exception:
average_processing_time = 0.0 average_processing_time = 0.0
remaining_frames = frames_to_upscale_counter remaining_frames = frames_to_upscale_counter
@@ -2455,9 +2590,9 @@ def upscale_video(
# 1.Preparation # 1.Preparation
target_directory = prepare_output_video_directory_name( 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, 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 # 2. Resume upscaling OR Extract video frames
video_upscale_continue = check_video_upscaling_resume( video_upscale_continue = check_video_upscaling_resume(
@@ -2471,7 +2606,7 @@ def upscale_video(
write_process_status( write_process_status(
process_status_q, f"{file_number}. Extracting video frames") process_status_q, f"{file_number}. Extracting video frames")
extracted_frames_paths = extract_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( 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] 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 # 7. Delete frames folder
if selected_keep_frames == False: if selected_keep_frames == False:
if os_path_exists(target_directory): 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 --------------------------- # GUI utils function ---------------------------
@@ -2523,7 +2662,7 @@ def user_input_checks() -> bool:
# Selected files # Selected files
try: try:
selected_file_list = file_widget.get_selected_file_list() selected_file_list = file_widget.get_selected_file_list()
except: except Exception:
info_message.set("Please select a file") info_message.set("Please select a file")
return False return False
@@ -2540,7 +2679,7 @@ def user_input_checks() -> bool:
try: try:
input_resize_factor = int( input_resize_factor = int(
float(str(selected_input_resize_factor.get()))) float(str(selected_input_resize_factor.get())))
except: except (ValueError, TypeError):
info_message.set("Input resolution % must be a number") info_message.set("Input resolution % must be a number")
return False return False
@@ -2554,7 +2693,7 @@ def user_input_checks() -> bool:
try: try:
output_resize_factor = int( output_resize_factor = int(
float(str(selected_output_resize_factor.get()))) float(str(selected_output_resize_factor.get())))
except: except (ValueError, TypeError):
info_message.set("Output resolution % must be a number") info_message.set("Output resolution % must be a number")
return False return False
@@ -2564,22 +2703,25 @@ def user_input_checks() -> bool:
info_message.set("Output resolution % must be a value > 0") info_message.set("Output resolution % must be a value > 0")
return False return False
# VRAM limiter # VRAM limiter
try: try:
tiles_resolution = 100 * int(float(str(selected_VRAM_limiter.get()))) vram_gb = int(float(str(selected_VRAM_limiter.get())))
except: if vram_gb <= 0:
info_message.set("GPU VRAM value must be a number") info_message.set("GPU VRAM value must be a value > 0")
return False return False
if tiles_resolution > 0:
vram_multiplier = VRAM_model_usage.get(selected_AI_model) vram_multiplier = VRAM_model_usage.get(selected_AI_model)
if vram_multiplier is None: if vram_multiplier is None:
vram_multiplier = 1 # Default for interpolation models or unknowns vram_multiplier = 1 # Default for interpolation models or unknowns
selected_vram = (vram_multiplier *
int(float(str(selected_VRAM_limiter.get())))) # El cálculo original parece confuso. Esta es una interpretación más clara:
tiles_resolution = int(selected_vram * 100) # Se asume que el VRAM Limiter es la VRAM en GB y se multiplica por un factor y 100.
else: # Si el modelo 'RealESR_Gx4' (factor 2.2) y VRAM es 4GB, tiles_resolution sería ~880.
info_message.set("GPU VRAM value must be a value > 0") 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 False
return True return True
@@ -2694,7 +2836,7 @@ def clear_dynamic_menus() -> None:
widget_info = widget.place_info() widget_info = widget.place_info()
if widget_info and float(widget_info.get('rely', 0)) == row2: if widget_info and float(widget_info.get('rely', 0)) == row2:
widget.place_forget() widget.place_forget()
except: except Exception:
pass pass
@@ -3507,7 +3649,7 @@ class SplashScreen(CTkToplevel):
) )
has_banner = True has_banner = True
except Exception as e: except Exception as e:
print(f"Could not load splash banner: {e}") print(f"[SPLASH] Could not load splash banner: {e}")
has_banner = False has_banner = False
window_height = 200 # Smaller height if no banner window_height = 200 # Smaller height if no banner
@@ -3651,6 +3793,21 @@ if __name__ == "__main__":
selected_frame_generation_option = "OFF" # Initialize frame generation option 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_input_resize_factor.set(default_input_resize_factor)
selected_output_resize_factor.set(default_output_resize_factor) selected_output_resize_factor.set(default_output_resize_factor)
selected_VRAM_limiter.set(default_VRAM_limiter) selected_VRAM_limiter.set(default_VRAM_limiter)
+3 -3
View File
@@ -1,16 +1,16 @@
# -*- mode: python ; coding: utf-8 -*- # -*- mode: python ; coding: utf-8 -*-
a = Analysis( a = Analysis(
['Warlock-Studio.py'], ['Warlock-Studio.py'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[('AI-onnx', 'AI-onnx'),('rsc', 'rsc'), ('Assets', 'Assets')], datas=[('AI-onnx', 'AI-onnx'), ('Assets', 'Assets'), ('rsc', 'rsc')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
excludes=[], excludes=['nltk', 'scipy', 'scipy.stats', 'scipy.stats.distributions', 'scipy.stats._distn_infrastructure'],
noarchive=False, noarchive=False,
optimize=0, optimize=0,
) )