Add files via upload
This commit is contained in:
committed by
GitHub
parent
9412399a5d
commit
717e84afaf
+49
-4
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
### 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:
|
||||||

|
|
||||||
|
- 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
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- RIFE Options UI
|
### 🔹 RIFE (Frame Interpolation) Options
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 🔹 Icon App
|
||||||
|
|
||||||
|
## 
|
||||||
|
|
||||||
|
## 🛠️ 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.
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user