diff --git a/AI-onnx/Instructions_for_downloading_AI_models.txt b/AI-onnx/Instructions_for_downloading_AI_models.txt index b1f41d5..b3a62af 100644 --- a/AI-onnx/Instructions_for_downloading_AI_models.txt +++ b/AI-onnx/Instructions_for_downloading_AI_models.txt @@ -1,2 +1,2 @@ Download the AI-onnx.zip file from the releases section, prioritizing the latest available version. -https://github.com/Ivan-Ayub97/Warlock-Studio/releases/download/v4.2.1/AI-onnx.zip +https://github.com/Ivan-Ayub97/Warlock-Studio/releases/download/v4.3/AI-onnx.zip diff --git a/Assets/Instructions_for_downloading_bin_files.txt b/Assets/Instructions_for_downloading_bin_files.txt index cd50f92..452e8f4 100644 --- a/Assets/Instructions_for_downloading_bin_files.txt +++ b/Assets/Instructions_for_downloading_bin_files.txt @@ -1,2 +1,2 @@ Download the Assets_bin.zip file from the releases section, prioritizing the latest available version, and extract the ffmpeg and exiftool bins to this directory. -https://github.com/Ivan-Ayub97/Warlock-Studio/releases/download/v4.2.1/Assets.zip +https://github.com/Ivan-Ayub97/Warlock-Studio/releases/download/v4.3/Assets.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index ef19661..830df77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,72 @@ +## Version 4.3 + +**Release date:** November 15, 2025 + +### **1. Critical Stability & Process Synchronization** + +#### **1.1. Forced Writer Thread Synchronization (Critical Stability Fix)** + +- Implemented explicit and mandatory synchronization using the `t.join()` function for **all frame writing threads** (`writer_threads`) within the main video orchestrator (`upscale_orchestrator`). +- This change is **critical for workflow integrity**, ensuring that all AI-upscaled frames are completely written and closed on disk before initiating the FFmpeg video encoding process. +- **Technical Impact:** Resolves intermittent encoding failures (`File Not Found` or `Input/Output error`) that could occur when FFmpeg attempted to read files still being written by concurrent threads. + +#### **1.2. Graceful Termination for Process Monitoring Thread** + +- The status monitoring thread (`upscale_monitor_thread`) has been enhanced to handle unexpected failures or abrupt terminations of the main upscaling process. +- An explicit `break` statement was added to the `except Exception` block that manages errors reading from the status queue (`process_status_q`). +- **Technical Impact:** If the main upscaling process (`Process`) fails or hangs, the monitoring thread now **terminates its execution gracefully and immediately** instead of becoming a "zombie thread," allowing the user interface to correctly reset global status controls. + +#### **1.3. Reinforced Frame Sequence Integrity Validation** + +- A robust verification function was introduced to validate the integrity of the frame sequence on disk before starting the AI upscaling. +- This measure is vital for `Resume` operations and now explicitly validates: + - The **physical existence** of every frame path (`os_path_exists`). + - The **readability** of the image file (`image_read`), preventing segmentation faults or memory errors if the file is corrupted or incomplete. + +### **2. Performance Improvements and Technical Refinements** + +#### **2.1. Aggressive Post-Process Memory Optimization** + +- A memory optimization step (`optimize_memory_usage`) with calls to `gc.collect()` was implemented after the last upscaled frames are saved to disk and before video encoding starts. +- **Purpose:** To reduce peak RAM usage by aggressively freeing any residual NumPy arrays and image references held in memory, ensuring the system has maximum memory available for the FFmpeg encoding subprocess. + +#### **2.2. Clarification of Tiling and VRAM Logic** + +- Detailed internal documentation (`# comments`) was added to the validation function (`check_upscale_settings`) to **explain the calculation logic for tile size** based on the user's VRAM limiter and the AI model's usage factor. +- **Clarified Formula:** `tiles_resolution = (ModelUsageFactor * VRAM_GB) * 100`. This is a technical refinement ensuring transparency and justification for the internal calculation. + +#### **2.3. Robustness and Clarification in Video Encoding (FFmpeg Command Line) 🎬** + +- **Explicit Stream Mapping:** Within `create_video_with_ffmpeg`, the FFmpeg command line now uses **more explicit and safer stream mapping** (`-map 0:v:0 -map 0:a:0?`). This ensures that the first video stream is selected obligatorily, and the first audio stream is selected optionally, resolving ambiguity issues in inputs containing multiple video or audio streams. +- **Data Stream Exclusion:** The **`-dn`** (Disable Data stream) argument was introduced into the base FFmpeg command line. This instructs the encoder to ignore unnecessary _data streams_ (such as subtitles or complex metadata) that are not relevant to the video or audio, **simplifying the muxing process and improving compatibility** across various players. + +### **3. Aesthetic and UI/UX Refinements** + +#### **3.1. Complete Aesthetic Overhaul: Crimson Gold Dark Theme 🌑** + +- The application's entire color scheme was replaced (migrating from a v4.2.1 red/yellow palette) with a high-contrast **"Crimson Gold Dark Theme"**: + - **Background:** Deep Black (`#121212`). + - **Widgets/Panels:** Very dark Wine Red (`#4B0000`). + - **Primary Accent:** Pure Gold (`#FFD700`). +- Dropdown menus (`CTkOptionMenu`) were styled with explicit **`corner_radius=0`** for a more modern, defined, and flat aesthetic. + +#### **3.2. Detailed Visualization of File Information in Queue** + +- Clarity of information in the queue widget (`FileWidget.add_file_information`) was improved by breaking down the complete resolution transformation sequence into three lines, eliminating ambiguity about intermediate dimensions: + - **AI Input:** Shows the final input resolution. + - **AI Output:** Shows the native resolution after upscaling by the AI model factor. + - **Video Output:** Shows the final resolution of the encoded video. + +#### **3.3. Visual Consistency and Updated Menu Separators** + +- The visual separator in all dropdown menus has been updated from the string `"<------------------>"` (v4.2.1) to a cleaner, more discreet sequence of dots: **`• • • • • • • • • • • •`**. + +#### **3.4. Refined Dynamic Menu Logic** + +- The conditional visibility logic for menus in `select_AI_from_menu` was refined for a smoother user experience: + - The **Frame Interpolation** control is only visible for RIFE models. + - The **Blending** control is only visible for Upscaling/Facial Restoration models (with automatic disabling logic for GFPGAN). + ## Version 4.2.1 **Release date:** 27 October 2025 diff --git a/Manual/Warlock-Studio_Manual.pdf b/Manual/Warlock-Studio_Manual.pdf new file mode 100644 index 0000000..d9ace1c Binary files /dev/null and b/Manual/Warlock-Studio_Manual.pdf differ diff --git a/Manual/Warlock-Studio_Manual.tex b/Manual/Warlock-Studio_Manual.tex new file mode 100644 index 0000000..06a6eb6 --- /dev/null +++ b/Manual/Warlock-Studio_Manual.tex @@ -0,0 +1,616 @@ +% ======================================================================================= +% 1. DOCUMENT SETUP & PACKAGES +% ======================================================================================= +\documentclass[11pt, a4paper]{article} + +% --- ESSENTIAL PACKAGES --- +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage[english]{babel} + +% --- PAGE LAYOUT & STYLE --- +\usepackage[a4paper, margin=2.2cm, headheight=15pt, footskip=30pt]{geometry} +\usepackage{graphicx} +\usepackage{float} +\usepackage{caption} +\usepackage{subcaption} +\usepackage{fancyhdr} +\usepackage{xcolor} +\usepackage{titletoc} + +% --- TYPOGRAPHY & TEXT --- +\usepackage{lato} +\renewcommand*\familydefault{\sfdefault} +\usepackage{textcomp} +\usepackage{fontawesome5} +\usepackage{microtype} % Improves line breaking and spacing + +% Slightly more readable line spacing and allow more flexible breaks +\usepackage{setspace} +\setstretch{1.05} +\sloppy +\emergencystretch=2em + +% --- TABLES, LISTS, & BOXES --- +\usepackage{booktabs} +\usepackage{longtable} +\usepackage{tabularx} +\usepackage{array} +\usepackage{enumitem} +\usepackage[skins, breakable, theorems]{tcolorbox} +\tcbuselibrary{skins, breakable, shadows} % Load advanced libraries + +% --- TITLES & HYPERLINKS --- +\usepackage{titlesec} +\usepackage{hyperref} % Always load this last + +% --- TIKZ FOR DIAGRAMS (STYLIZED WITH CORPORATE PALETTE) --- +\usepackage{tikz} +\usetikzlibrary{shapes, arrows.meta, positioning, shadows, calc, decorations.pathreplacing, backgrounds} +\usepackage{pgfkeys} + +% ======================================================================================= +% 2. COLOR & STYLE DEFINITIONS +% ======================================================================================= + +% --- CORPORATE COLOR PALETTE --- +\definecolor{WarlockRed}{HTML}{C11919} +\definecolor{WarlockGold}{HTML}{ECD125} +\definecolor{WarlockDark}{HTML}{1A1C1E} +\definecolor{WarlockGray}{HTML}{333333} +\definecolor{WarlockLightGray}{HTML}{F0F0F0} +\definecolor{WarlockWhite}{HTML}{FFFFFF} + +% --- SECTION COLOR PALETTE --- +\definecolor{IntroColor}{HTML}{005A9B} % Blue +\definecolor{QuickStartColor}{HTML}{00796B} % Green +\definecolor{InstallColor}{HTML}{8E44AD} % Purple +\definecolor{ModelsColor}{HTML}{D35400} % Orange +\definecolor{OptimizeColor}{HTML}{27AE60} % Emerald Green +\definecolor{TroubleColor}{HTML}{C0392B} % Red +\definecolor{ArchColor}{HTML}{2C3E50} % Dark Blue +\definecolor{GlossaryColor}{HTML}{7F8C8D} % Gray +\definecolor{SupportColor}{HTML}{34495E} % Dark Gray + +% --- TEXTBOX COLOR PALETTE --- +\definecolor{InfoFill}{HTML}{E7F3FE} +\definecolor{InfoBorder}{HTML}{005A9B} +\definecolor{WarnFill}{HTML}{FFFBE6} +\definecolor{WarnBorder}{HTML}{FFBE0B} +\definecolor{QuickStartFill}{HTML}{E6FFFA} +\definecolor{QuickStartBorder}{HTML}{00796B} + +% --- DEFAULT TEXT COLOR APPLICATION --- +\color{WarlockGray} + +% ======================================================================================= +% 3. DOCUMENT ELEMENT CONFIGURATION +% ======================================================================================= + +% --- HYPERLINKS --- +\hypersetup{ + colorlinks=true, + linkcolor=WarlockRed, + filecolor=WarlockRed, + urlcolor=WarlockRed, + pdftitle={Warlock-Studio | Technical Documentation and User Manual}, + pdfauthor={Iván Eduardo Chavez Ayub} +} + +% --- SECTION & SUBSECTION TITLES (CORRECTED) --- +\newcommand{\SectionColor}{WarlockGray} % Default color +\newcommand{\setsectioncolor}[1]{\renewcommand{\SectionColor}{#1}} + +\titleformat{\section} + {\normalfont\Large\bfseries} + {} + {0em} + {% + \begin{tcolorbox}[ + enhanced, + colback=\SectionColor!90!black, + colframe=\SectionColor!90!black, + boxrule=0pt, + sharp corners, + halign=center, + valign=center, + boxsep=6pt, + left=0pt, right=0pt, top=0pt, bottom=0pt + ] + \color{white}\faBookmark\hspace{0.5em}\thesection.\hspace{1em}#1 + \end{tcolorbox} + } +\titleformat{\subsection} + {\normalfont\large\bfseries\color{\SectionColor!80!black}} + {\faCaretRight\ \thesubsection} + {1em} + {} +\titlespacing*{\section}{0pt}{4ex plus 1ex minus .2ex}{3ex plus .2ex} +\titlespacing*{\subsection}{0pt}{2.5ex plus 1ex minus .2ex}{1.3ex plus .2ex} + +% --- ENHANCED TEXTBOX DEFINITIONS --- +\newtcolorbox{infobox}[2][]{ + enhanced, breakable, + colback=InfoFill, colframe=InfoBorder, + fonttitle=\bfseries, coltitle=InfoBorder, + title=\faInfoCircle\hspace{0.5em}#2, + attach boxed title to top left={yshift=-2mm, xshift=3mm}, + boxed title style={colback=InfoBorder, sharp corners}, + coltext=WarlockDark, + shadow={2mm}{-1mm}{0mm}{black!20!white}, + #1 +} +\newtcolorbox{warnbox}[2][]{ + enhanced, breakable, + colback=WarnFill, colframe=WarnBorder, + fonttitle=\bfseries, coltitle=WarnBorder!80!black, + title=\faExclamationTriangle\hspace{0.5em}#2, + attach boxed title to top left={yshift=-2mm, xshift=3mm}, + boxed title style={colback=WarnBorder, sharp corners}, + coltext=WarlockDark, + shadow={2mm}{-1mm}{0mm}{black!20!white}, + #1 +} +\newtcolorbox{quickstartbox}[2][]{ + enhanced, breakable, + colback=QuickStartFill, colframe=QuickStartBorder, + fonttitle=\bfseries, coltitle=QuickStartBorder!80!black, + title=\faRocket\hspace{0.5em}#2, + attach boxed title to top left={yshift=-2mm, xshift=3mm}, + boxed title style={colback=QuickStartBorder, sharp corners}, + coltext=WarlockDark, + shadow={2mm}{-1mm}{0mm}{black!20!white}, + #1 +} + +% --- INLINE CODE COMMAND --- +% Use detokenize to preserve special characters and avoid problematic hyphenation +\newcommand{\inlinecode}[1]{\colorbox{WarlockLightGray}{\small\texttt{\detokenize{#1}}}} + +% --- HEADER & FOOTER --- +\pagestyle{fancy} +\fancyhf{} +\fancyhead[L]{\textit{Warlock-Studio v4.3}} +\fancyhead[R]{\leftmark} +\fancyfoot[L]{\includegraphics[height=0.8cm]{logo.png}} +\fancyfoot[C]{\thepage} +\fancyfoot[R]{\textcopyright~2025 Warlock-Studio} +\renewcommand{\headrulewidth}{0.4pt} +\renewcommand{\footrulewidth}{0.4pt} +\renewcommand{\sectionmark}[1]{\markboth{\thesection. #1}{}} + +% ======================================================================================= +% BEGIN DOCUMENT +% ======================================================================================= +\begin{document} + +% --- REDESIGNED TITLE PAGE (KEEPING ORIGINAL STYLE FROM YOUR BASE CODE) --- +\begin{titlepage} + \begin{tcolorbox}[ + enhanced, sharp corners, + colback=WarlockDark, colframe=WarlockGold, + boxrule=2pt, + height=\textheight, + halign=center, valign=center + ] + \centering + \includegraphics[width=0.4\textwidth]{logo.png}\par + \vfill + \color{WarlockWhite} + {\Huge\bfseries\scshape Warlock-Studio\par} + \vspace{1.5cm} + \color{WarlockGold} + \rule{0.6\textwidth}{1pt}\par + \vspace{0.4cm} + \color{WarlockWhite} + {\Large\bfseries Technical Documentation and User Guide\par} + \vspace{0.2cm} + {\large Software Version: 4.3\par} + \vspace{0.4cm} + \color{WarlockGold} + \rule{0.6\textwidth}{1pt}\par + \vfill + {\large Iván Eduardo Chavez Ayub\par} + \href{https://github.com/Ivan-Ayub97}{\texttt{\color{WarlockGold}\faGithub\ @Ivan-Ayub97}}\par + \vspace{1.5cm} + {\large \today\par} + \end{tcolorbox} + \thispagestyle{empty} +\end{titlepage} + +\newpage +\tableofcontents +\newpage + +% ======================================================================================= +% SECTION 1: INTRODUCTION +% ======================================================================================= +\setsectioncolor{IntroColor} +\section{Introduction to Warlock-Studio} +Welcome to Warlock-Studio — an AI-powered suite for digital media enhancement. +It provides advanced tools for super-resolution, artifact removal, and frame generation through an intuitive interface that delivers professional-quality results with minimal effort. + +% ======================================================================================= +% SECTION 2: QUICK START GUIDE +% ======================================================================================= +\setsectioncolor{QuickStartColor} +\section{Quick Start Guide} +\begin{quickstartbox}{Accelerated Media Enhancement Procedure} +Follow these 5 steps to process your first media file in less than a minute. +\begin{enumerate} + \item \textbf{Load Files:} Click the \textbf{"SELECT FILES"} button and choose one or more image or video files for processing. + \item \textbf{AI Model Selection:} In the \textbf{"AI model"} dropdown menu, select an inference model. + \begin{itemize} + \item For photorealistic images, \inlinecode{BSRGANx4} is recommended for its ability to reconstruct fine textures. + \item For animation or illustrated content, \inlinecode{RealESR_Animex4} is the optimal choice for preserving sharp edges. + \item For video sequences, \inlinecode{RealESR_Gx4} offers an excellent balance between computational performance and perceptual quality. + \end{itemize} + \item \textbf{Adjust Input Resolution:} For a quick first pass, set \textbf{"Input resolution"} to \texttt{75}\%. This significantly reduces the computational load with an often imperceptible loss in final quality. + \item \textbf{Verify VRAM Limit:} Ensure the value in \textbf{"GPU VRAM (GB)"} is equal to or less than your graphics card's dedicated video memory. An initial value of \texttt{4} GB is a safe setting for most modern hardware. + \item \textbf{Start Processing:} Click \textbf{"Make Magic"} to initiate the processing pipeline. The output files will be generated in the specified directory or, by default, in the same location as the source files. +\end{enumerate} +\end{quickstartbox} + +% ======================================================================================= +% SECTION 3: INSTALLATION & ARCHITECTURE +% ======================================================================================= +\setsectioncolor{InstallColor} +\section{Installation and System Architecture} +\subsection{\faDownload\ Installation Process} +Warlock-Studio uses a self-contained offline installer, simplifying deployment. +\begin{enumerate}[leftmargin=*] + \item \textbf{Obtaining the Executable:} Download the `Warlock-Studio-Setup.exe` file from the official repositories on GitHub or SourceForge. + \item \textbf{Run with Elevated Privileges:} Right-click the installer and select "Run as administrator." This step is crucial to ensure the application has the necessary permissions to interact with low-level GPU drivers. + \item \textbf{Installation Wizard:} Follow the on-screen instructions. The inclusion of all AI models and dependencies eliminates the need for additional downloads during this process. + \item \textbf{Launch the Application:} Once the installation is complete, Warlock-Studio can be launched from the Start Menu or the desktop shortcut. +\end{enumerate} + +\subsection{\faMicrochip\ System Requirements} +\begin{table}[H] + \centering + \begin{tabularx}{\textwidth}{lX} + \toprule + \textbf{Component} & \textbf{Technical Specification} \\ + \midrule + Operating System & Windows 11 or Windows 10 (64-bit architecture required). \\ + RAM & 8 GB (minimum), 16 GB (recommended for high-resolution video processing). \\ + Graphics Card (GPU) & \textbf{Mandatory Requirement:} GPU with support for the \textbf{DirectX 12} API. \\ + & \textbf{NVIDIA (for CUDA):} Maxwell architecture (GTX 900 Series) or newer. Studio drivers are recommended for stability. \\ + & \textbf{AMD/Intel (for DirectML):} Any modern GPU with updated drivers that support DirectX 12 Feature Level 12.0+. \\ + & \textbf{4+ GB of VRAM} is recommended to avoid memory bottlenecks. \\ + Storage & 2 GB of free disk space. Using a Solid State Drive (SSD) drastically improves I/O performance during video sequence processing. \\ + \bottomrule + \end{tabularx} + \caption{Hardware and software specifications for optimal performance of Warlock-Studio.} +\end{table} + +\subsection{\faFolderOpen\ File Structure and Dependencies} +\begin{infobox}{Self-Contained Components} +Warlock-Studio operates as a fully self-contained environment. +All critical assets, dependencies, and user data are automatically managed by the application, requiring no manual configuration from the user. +\end{infobox} + +\begin{itemize}[leftmargin=*] + \item \textbf{Core Assets:} + The executables \inlinecode{ffmpeg.exe} (for video encoding/decoding) and \inlinecode{exiftool.exe} (for metadata preservation) are stored in the \texttt{Assets} directory. + Their paths are dynamically resolved through the \inlinecode{find_by_relative_path} function, which accurately locates resources in both development and bundled environments (using the \inlinecode{_MEIPASS} directory created by PyInstaller). + + \item \textbf{AI Models:} + All AI inference models are provided in interoperable \texttt{.onnx} format and stored within the \texttt{AI-onnx} directory. + This ensures cross-platform compatibility and seamless hardware acceleration through ONNX Runtime. + + \item \textbf{User Preferences:} + The configuration file \inlinecode{USER_PREFERENCE_PATH} is automatically generated in the user’s \textbf{Documents} folder (\inlinecode{os_path_expanduser('~')}). + It stores GUI layout, last-used settings, and model selections between sessions. + + \item \textbf{Diagnostic Logs:} + Log files (\inlinecode{MAIN_LOG_FILENAME} and \inlinecode{ERROR_LOG_FILENAME}) are created in a dedicated subfolder inside \textbf{Documents}. + These serve as vital diagnostic resources, recording operational events, system validation reports, and runtime exceptions for troubleshooting. +\end{itemize} + + +% ======================================================================================= +% SECTION 4: DETAILED AI MODEL GUIDE +% ======================================================================================= +\setsectioncolor{ModelsColor} +\section{Detailed Analysis of Inference Models} + +\begin{infobox}{Model Selection Overview} +Selecting the appropriate AI inference model is the most critical decision +in Warlock-Studio’s enhancement pipeline. It directly determines the balance +between visual quality, computational efficiency, and hardware resource usage. +Each model implements distinct neural architectures and loss functions, optimized +for different types of input data and output fidelity targets. + +The following section provides a comprehensive technical breakdown and comparative +analysis to support informed model selection. +\end{infobox} + + +\subsection{\faTable\ Model Comparison Matrix} +\begin{longtable}{p{2.8cm} p{1.8cm} p{1.2cm} p{1.5cm} p{7.2cm}} +\toprule +\textbf{Model} & \textbf{Primary Function} & \textbf{Scale} & \textbf{VRAM (GB)} & \textbf{Use Case and Technical Considerations} \\ +\midrule +\endhead +\multicolumn{5}{c}{\textit{\textbf{\faEraser\ Denoising Models}}} \\ +\midrule +\texttt{IRCNN\_Mx1} & Denoise & x1 & 4.0 & Moderate-level noise reduction. Ideal for suppressing JPEG compression artifacts and low-ISO sensor noise in old photographs. \\ +\texttt{IRCNN\_Lx1} & Denoise & x1 & 4.0 & Intensive noise reduction algorithm. Optimal for severely degraded images with pronounced luminance and chrominance noise. \\ +\midrule +\multicolumn{5}{c}{\textit{\textbf{\faTachometerAlt\ High-Fidelity Upscaling Models (Computationally Intensive)}}} \\ +\midrule +\texttt{BSRGANx4} & Upscale & x4 & 0.6 & Generative Adversarial Network optimized for realistic texture synthesis. It is the model of choice for portraits, nature photography, and where the preservation of fine details is paramount. \\ +\texttt{BSRGANx2} & Upscale & x2 & 0.7 & A variant with a reduced scaling factor. It offers a perceptual quality similar to the x4 version but with lower computational cost, ideal for moderate upscaling needs. \\ +\texttt{RealESRGANx4} & Upscale & x4 & 0.6 & A robust general-purpose model. It excels in reconstructing a wide variety of content, including textures, landscapes, and architectural elements. \\ +\texttt{RealESRNetx4} & Upscale & x4 & 2.2 & An alternative to RealESRGAN, often producing results with fewer "hallucinatory" artifacts. It can offer a better balance between speed and fidelity on certain GPU architectures. \\ +\midrule +\multicolumn{5}{c}{\textit{\textbf{\faBolt\ High-Speed Upscaling Models (Lightweight)}}} \\ +\midrule +\texttt{RealESR\_Gx4} & Upscale & x4 & 2.2 & A lightweight and fast model, optimized for real-time or near-real-time video processing. It offers an excellent compromise between performance and visual quality. \\ +\texttt{RealESR\_Animex4} & Upscale & x4 & 2.2 & Specialized for non-photorealistic content. It preserves sharp lines and flat colors, avoiding the smoothing artifacts common in models trained on natural data. \\ +\midrule +\multicolumn{5}{c}{\textit{\textbf{\faUserCircle\ Facial Restoration Models}}} \\ +\midrule +\texttt{GFPGAN} & Restore & x1 & 1.8 & Generative Adversarial Network with a facial prior. It not only upscales but also reconstructs and enhances damaged or low-resolution facial features in photographs. \\ +\midrule +\multicolumn{5}{c}{\textit{\textbf{\faFilm\ Frame Interpolation Models (Video Only)}}} \\ +\midrule +\texttt{RIFE} & Interpolate & N/A & \textasciitilde{}1.5 & High-quality algorithm for motion interpolation. It generates intermediate frames to increase the fluidity of a video (e.g., from 30 to 60 FPS). \\ +\texttt{RIFE\_Lite} & Interpolate & N/A & \textasciitilde{}1.2 & An optimized variant of RIFE, designed for GPUs with limited VRAM. It offers faster processing at the cost of a slight reduction in interpolation accuracy. \\ +\midrule +\bottomrule +\caption{Technical guide for the selection of AI models. VRAM values are base estimates derived from the \inlinecode{VRAM_model_usage} dictionary in the source code and may vary.} +\label{tab:modelos} +\end{longtable} + +% ======================================================================================= +% SECTION 5: BEST PRACTICES & OPTIMIZATION +% ======================================================================================= +\setsectioncolor{OptimizeColor} +\section{Performance Optimization and Best Practices} + +\subsection{\faSlidersH\ Critical Performance Parameters} +\begin{itemize}[leftmargin=*, itemsep=2pt] + \item \textbf{Input Resolution \%:} This is the most influential parameter on processing speed. A value between \textbf{50\% and 75\%} implements an initial downsampling before upscaling, drastically reducing the computational load with minimal impact on the final perceptual quality. + +\item \textbf{GPU VRAM Limiter (GB):} Defines the video memory budget. +This value (from \inlinecode{selected_VRAM_limiter.get()}) is used in a precise formula +to calculate the internal \inlinecode{tiles_resolution} parameter: + +\begin{center} +\begin{tcolorbox}[ + enhanced, + colback=WarlockLightGray!30, + colframe=OptimizeColor!80!black, + boxrule=0.6pt, + arc=2pt, + left=4pt,right=4pt,top=2pt,bottom=2pt, + width=0.9\linewidth +] +\small +\texttt{vram\_factor = VRAM\_model\_usage[model] * VRAM\_GB\_input}\\ +\texttt{tiles\_resolution = int(vram\_factor * 100)} +\end{tcolorbox} +\end{center} + +This \inlinecode{tiles_resolution} (e.g., \texttt{880px} for a 4~GB card using +\inlinecode{RealESR_Gx4}) is then used as the maximum tile dimension for image processing, +ensuring full stability and preventing \textbf{Out Of Memory (OOM)} errors during inference. + + + \item \textbf{AI Multithreading:} (Video only) Allows for the parallel processing of multiple frames via a \inlinecode{ThreadPool}. It significantly increases performance on systems with multi-core CPUs, but at the cost of higher VRAM and CPU consumption. + + \item \textbf{AI Blending:} Mitigates visual artifacts. This value (from \inlinecode{selected_blending_factor}) is passed to the \inlinecode{blend_images_and_save} function, which uses OpenCV's \inlinecode{addWeighted} to perform an alpha blend between the original upscaled image and the AI-processed image. + + \item \textbf{System Validation:} Before starting, the \inlinecode{validate_system_requirements} function automatically checks for the presence of \inlinecode{ffmpeg.exe}, available disk space, and available system RAM (using the \inlinecode{psutil} library). +\end{itemize} +\subsection{\faTrophy\ Tips for Maximum Quality Results} + +\begin{tcolorbox}[ + enhanced, breakable, + colback=QuickStartFill, + colframe=QuickStartBorder, + coltitle=QuickStartBorder!80!black, + title=\faStar\hspace{0.5em}\textbf{Expert Recommendations for Optimal Output}, + fonttitle=\bfseries, + sharp corners, + boxrule=0.6pt, + shadow={1mm}{-1mm}{0mm}{black!10!white} +] + +\begin{description}[leftmargin=*, style=nextline, itemsep=1em] + + \item[\textbf{Maximizing Visual Fidelity}] + To achieve the highest possible perceptual quality, use the \inlinecode{BSRGANx4} or \inlinecode{RealESRGANx4} models with an \textbf{Input Resolution} of \textbf{100\%}. + Although computationally demanding, this configuration ensures maximum reconstruction of micro-textures and reduces information loss, especially on portrait or landscape imagery. + + \item[\textbf{Workflow for Video Restoration}] + A robust two-stage restoration pipeline for archival or degraded footage involves: + \begin{enumerate}[nosep, leftmargin=*] + \item \textbf{Stage 1 — Denoising:} Apply a denoising model such as \inlinecode{IRCNN\_Mx1} to clean sensor noise and compression artifacts. + \item \textbf{Stage 2 — Upscaling:} Use an upscaling model such as \inlinecode{RealESR\_Gx4} on the cleaned output to reconstruct fine detail while maintaining temporal coherence. + \end{enumerate} + + \item[\textbf{Impact of SSD Storage}] + A Solid State Drive (SSD) significantly improves throughput during video restoration. + The extraction (\inlinecode{extract\_video\_frames}), writing (\inlinecode{save\_frames\_on\_disk}), and reassembly (\inlinecode{video\_encoding}) of thousands of frames represent the main I/O bottlenecks; SSDs mitigate these delays and improve system responsiveness. + + \item[\textbf{Frame Persistence for Experimentation}] + For testing different encoding settings (e.g., codecs or bitrates), enable the \textbf{"Keep frames"} option (\inlinecode{selected\_keep\_frames = True}). + This preserves the processed frames on disk, allowing video re-encoding without repeating the AI inference phase — saving time and extending the experimental workflow. + +\end{description} +\end{tcolorbox} + + +% ======================================================================================= +% SECTION 6: TROUBLESHOOTING +% ======================================================================================= +\setsectioncolor{TroubleColor} +\section{Diagnostics and Troubleshooting} +\begin{warnbox}{Primary Cause of Errors} +The \textbf{\#1 cause} of processing failures is \textbf{non-standard characters} in file paths and filenames. Avoid using: \texttt{', ", @, \#, \$, \%, \&, *, [, ], ?, etc.}. +\end{warnbox} + +\begin{description}[leftmargin=*, style=nextline, itemsep=0.8em] + \item[\faBan\ Error: "FFmpeg encoding failed..."] + \textbf{Diagnosis:} The \inlinecode{video_encoding} function, which uses \inlinecode{subprocess_run}, has failed. The code specifically checks for several causes: + \begin{itemize}[nosep, leftmargin=*] + \item \textbf{"Invalid argument"}: Most commonly caused by special characters in file paths. + \item \textbf{"Unknown encoder"}: The selected video codec (e.g., \inlinecode{hevc_nvenc}) is not supported by your FFmpeg build or hardware. + \item \textbf{"Device or resource busy"}: Your GPU's hardware encoder is being used by another application (e.g., OBS, ShadowPlay). + \end{itemize} + \textbf{Solution:} Rename files to remove special characters. For encoder errors, select a different codec (e.g., \inlinecode{x264} or \inlinecode{x265}). + + \item[\faRocket\ Error: "Failed to load model" or Execution Provider Failure] + \textbf{Diagnosis:} Failure in the initialization of the selected hardware backend, caught within the \inlinecode{create_onnx_session} function. + \textbf{Solution:} The system is designed to automatically fall back to a functional provider in the hierarchy (CUDA -> DirectML -> CPU). To resolve the root cause: + \begin{enumerate}[nosep, leftmargin=*] + \item \textbf{For CUDA errors:} Verify the installation of the latest NVIDIA drivers (Game Ready or Studio). + \item \textbf{For DirectML errors:} Ensure that the Windows operating system is fully updated and that you have the latest drivers for your GPU (NVIDIA, AMD, or Intel). + \end{enumerate} + + \item[\faMemory\ Error: "out of memory" (OOM) or "allocation" failure] + \textbf{Diagnosis:} The GPU's VRAM has been exhausted. This error is caught within the \inlinecode{upscale_video_frames_async} function. + \textbf{Solution:} + \begin{enumerate}[nosep, leftmargin=*] + \item Lower the \textbf{VRAM Limiter} to a value equal to or less than your GPU's physical VRAM. + \item Decrease the \textbf{Input Resolution \%} to 75\% or less. + \item \textbf{Automatic Recovery:} The code will attempt an automatic recovery. It progressively reduces the processing tile size (by lowering the \inlinecode{AI_instance.max_resolution} variable) and retries the failed frame. + \end{enumerate} + + \item[\faTachometerAlt\ Error: "cannot convert float NaN to integer"] + \textbf{Diagnosis:} This specific error is caught in the main \inlinecode{upscale_orchestrator}. It indicates a GPU driver timeout (TDR - Timeout Detection and Recovery), often caused by hardware overload or overheating, which returns a "Not a Number" (NaN) value instead of pixel data. + \textbf{Solution:} Restart the process \textbf{without deleting the generated frames folder}. The application will detect the existing frames and resume the task from the point of failure. +\end{description} + +% ======================================================================================= +% SECTION 7: ADVANCED TECHNICAL ARCHITECTURE +% ======================================================================================= +\setsectioncolor{ArchColor} +\section{Software Architecture Analysis} + +\subsection{\faCogs\ Inference Engine and Hardware Abstraction} +Warlock-Studio is built upon a multi-layered inference engine powered by \textbf{ONNX Runtime}. The \inlinecode{create_onnx_session} function abstracts the underlying hardware and prioritizes Execution Providers to maximize performance: +\begin{enumerate}[leftmargin=*] + \item \textbf{CUDA (\texttt{CUDAExecutionProvider}):} The highest performance option, leveraging the parallel computing architecture of NVIDIA GPUs. + \item \textbf{DirectML (\texttt{DmlExecutionProvider}):} If CUDA is not available, the system falls back to DirectML. This Microsoft API translates neural network operations into native \textbf{DirectX 12} calls, ensuring broad hardware compatibility (NVIDIA, AMD, Intel). + \item \textbf{CPU (\texttt{CPUExecutionProvider}):} This is the last resort provider. If no GPU acceleration is viable, the application runs the model on the CPU, guaranteeing universal functionality at the cost of significantly lower performance. +\end{enumerate} + +\subsection{\faThLarge\ Dynamic Tiling and Memory Management} +To process high-resolution media, the \inlinecode{AI_upscale_with_tilling} function is invoked. It programmatically subdivides a large frame into smaller "tiles" based on the \inlinecode{max_resolution} variable. This \inlinecode{max_resolution} (confusingly named \inlinecode{tiles_resolution} in the GUI logic) is the pixel dimension calculated directly from the user's \textbf{VRAM Limiter} input, ensuring that no single tile exceeds the GPU's memory budget. + +\subsection{\faSyncAlt\ Resume and Checkpointing Functionality} +If a video process is interrupted, the frames already processed and written to disk are preserved. Upon restarting the same task, the \inlinecode{check_video_upscaling_resume} function detects these partial files by checking for their existence. It then returns a list of only the *original* frames that still need processing, allowing the \inlinecode{upscale_video} orchestrator to resume the task from the point of failure. + +\subsection{\faBolt\ Asynchronous Frame Writing} +During video upscaling, the main processing loop in \inlinecode{upscale_video_frames_async} does not write to disk directly. Instead, it batches processed frames in memory. Once a batch is ready (defaulting to \inlinecode{MULTIPLE_FRAMES_TO_SAVE = 8}), it calls \inlinecode{save_frames_on_disk}, which spawns a new \inlinecode{Thread} from the `threading` module to run \inlinecode{save_multiple_upscaled_frame_async}. This decouples the GPU-bound inference from the I/O-bound disk writing, maximizing GPU utilization. + +% ======================================================================================= +% SECTION 8: PROCESSING PIPELINE DIAGRAM (FIXED TO PAGE WIDTH) +% ======================================================================================= +\section{Processing Pipeline (Diagram)} +\noindent +The following diagram illustrates the high-level processing pipeline. + +\begin{figure}[H] + \centering + % Resize to text width to avoid overflow + \resizebox{\textwidth}{!}{% + \begin{tikzpicture}[node distance=12mm, every node/.style={font=\small}, >=Stealth] + \tikzset{ + box/.style={rectangle, rounded corners=4pt, draw=WarlockDark!80!black, fill=WarlockDark!6, minimum width=48mm, minimum height=9mm, align=center, drop shadow}, + proc/.style={rectangle, rounded corners=3pt, draw=WarlockGold!70!black, fill=WarlockGold!12, minimum width=48mm, minimum height=9mm, align=center}, + disk/.style={cylinder, shape border rotate=90, draw=WarlockGray!70!black, fill=WarlockLightGray, minimum width=10mm, minimum height=10mm, align=center}, + arrow/.style={->, thick, draw=WarlockDark!80!black} + } + \node[box] (ingest) {\textbf{Input Ingest}\\(images \& video)}; + \node[proc, right=28mm of ingest] (analysis) {\textbf{Preprocessing}\\format, resize, color space}; + \node[proc, right=28mm of analysis] (model) {\textbf{AI Inference}\\selected model(s)}; + \node[box, right=28mm of model] (post) {\textbf{Postprocess}\\blend, alpha, denoise}; + \node[disk, below=8mm of model] (cache) {\textbf{Frame Cache}}; + \node[proc, right=28mm of post] (encode) {\textbf{Encoding}\\ffmpeg reassembly}; + + \draw[arrow] (ingest) -- (analysis); + \draw[arrow] (analysis) -- (model); + \draw[arrow] (model) -- (post); + \draw[arrow] (post) -- (encode); + \draw[arrow] (model) -- (cache); + \draw[arrow] (cache) -- (post); + + % Decorative bracket and label under pipeline + \draw[decorate, decoration={brace, amplitude=6pt}, thick, WarlockGold] ($(analysis.south west)+(0,-6mm)$) -- ($(post.south east)+(0,-6mm)$) node[midway, below=9mm]{\small \textbf{GPU-Accelerated AI Pipeline}}; + \end{tikzpicture} + } + \caption{High-level processing pipeline (ingest $\rightarrow$ inference $\rightarrow$ encode).} +\end{figure} + +% ======================================================================================= +% SECTION 10: ARCHITECTURE DIAGRAM (FIXED) +% ======================================================================================= +\section{Software Architecture (Diagram)} +\noindent +Component-level architecture showing logical components and their relationship to hardware. + +\begin{figure}[H] + \centering + \resizebox{\textwidth}{!}{% + \begin{tikzpicture}[node distance=12mm, every node/.style={font=\small}] + \tikzset{ + comp/.style={rectangle, rounded corners=3pt, draw=WarlockDark!70!black, fill=WarlockDark!6, minimum width=42mm, minimum height=9mm, align=center, drop shadow}, + hw/.style={rectangle, rounded corners=3pt, draw=WarlockGold!70!black, fill=WarlockGold!10, minimum width=42mm, minimum height=9mm, align=center}, + line/.style={-Latex, thick, color=WarlockGray!80!black} + } + \node[comp] (gui) {GUI / Preferences}; + \node[comp, right=28mm of gui] (orchestrator) {Orchestrator / Job Queue}; + \node[comp, right=28mm of orchestrator] (ai) {AI Core / ONNX Wrapper}; + \node[comp, right=28mm of ai] (io) {I/O \& Encoding (FFmpeg)}; + \node[hw, below=12mm of ai] (gpu) {GPU (CUDA / DirectML)}; + \node[hw, below=12mm of io] (disk) {Disk / SSD}; + + \draw[line] (gui) -- (orchestrator); + \draw[line] (orchestrator) -- (ai); + \draw[line] (ai) -- (io); + \draw[line, dashed] (ai) -- (gpu) node[midway, right]{\small Inference}; + \draw[line] (io) -- (disk); + \draw[line, dashed] (orchestrator) -- (disk) node[midway, right]{\small Checkpoints / Frames}; + \end{tikzpicture} + } + \caption{Component-level architecture (logical components and hardware).} +\end{figure} + + +% ======================================================================================= +% SECTION 9: TECHNICAL GLOSSARY +% ======================================================================================= +\setsectioncolor{GlossaryColor} +\section{Glossary} +\begin{description}[leftmargin=*, style=nextline, itemsep=0.8em] + \item[ONNX Runtime] (Open Neural Network Exchange) A high-performance, cross-platform inference engine for AI models. It allows Warlock-Studio to run models in a hardware-agnostic manner. + \item[CUDA] (Compute Unified Device Architecture) A parallel computing platform and programming model API created by NVIDIA. It enables general-purpose acceleration on NVIDIA GPUs. + \item[DirectML] (Direct Machine Learning) A low-level Microsoft API that uses DirectX 12 to provide GPU-accelerated AI on a wide range of DX12-compatible hardware. + \item[VRAM] (Video RAM) High-speed random-access memory dedicated to a graphics card, used to store textures, framebuffers, and other data critical for rendering and computation on the GPU. + \item[Tiling] A technique of dividing a large image into smaller "tiles" to be processed individually. It is an essential mechanism for handling resolutions that exceed available VRAM. + \item[Codec] (Coder-Decoder) A software or hardware algorithm/device that compresses and decompresses digital video data. Examples of software codecs are x264 (H.264) and x265 (HEVC). Hardware codecs (NVENC, AMF, QSV) use dedicated encoders on the GPU to accelerate this process. +\end{description} + +% ======================================================================================= +% SECTION 11: SUPPORT & CONTRIBUTIONS +% ======================================================================================= +\setsectioncolor{SupportColor} +\section{Support and Community} +\begin{itemize}[leftmargin=*] + \item \textbf{\faBug\ Reporting Issues:} If you encounter a reproducible bug, please open an "Issue" on the GitHub repository. It is essential to attach the \inlinecode{error_log.txt} file (located in your \textbf{Documents} folder) for effective diagnosis. + \item \textbf{\faCodeBranch\ Code Contributions:} Contributions to the source code are welcome. It is recommended to follow the standard workflow: fork the repository, create a new branch for the feature or fix, and submit a "Pull Request" for review. + \item \textbf{\faEnvelope\ Direct Contact:} For general inquiries or technical support that does not constitute a software bug, you can contact the author at \href{mailto:negroayub97@gmail.com}{\texttt{negroayub97@gmail.com}}. +\end{itemize} +\vspace{1cm} +\centering +\textbf{Thank you for using Warlock-Studio.} + +% ======================================================================================= +% END OF DOCUMENT +% ======================================================================================= +\end{document} diff --git a/Manual/logo.png b/Manual/logo.png new file mode 100644 index 0000000..99f0acc Binary files /dev/null and b/Manual/logo.png differ diff --git a/README.md b/README.md index 17efc89..214baaf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
[![Build Status](https://img.shields.io/badge/Build-Stable_Release-0A192F?style=for-the-badge&logo=github&logoColor=FFD700)](https://github.com/Ivan-Ayub97/Warlock-Studio/releases) -[![Version](https://img.shields.io/badge/Version-4.2.1-FF4500?style=for-the-badge&logo=git&logoColor=white)](https://github.com/Ivan-Ayub97/Warlock-Studio/releases/tag/4.2.1) +[![Version](https://img.shields.io/badge/Version-4.3-FF4500?style=for-the-badge&logo=git&logoColor=white)](https://github.com/Ivan-Ayub97/Warlock-Studio/releases/tag/4.3) [![License](https://img.shields.io/badge/License-MIT-6A0DAD?style=for-the-badge&logo=open-source-initiative&logoColor=white)](LICENSE) [![Downloads](https://img.shields.io/github/downloads/Ivan-Ayub97/Warlock-Studio/total?style=for-the-badge&color=FFD700&logo=download&logoColor=black)](https://github.com/Ivan-Ayub97/Warlock-Studio/releases) @@ -16,33 +16,21 @@ Inspired by [Djdefrag](https://github.com/Djdefrag) tools such as **QualityScale --- -## 📥 Download Installer: +## 📥 Download Installer - - -
+ Warlock-Studio on SourceForge + width="175" style="display:block; margin:auto; margin-bottom:1px;" /> - + Download from GitHub - -
- -### 📚 User Manuals & Documentation - - - @@ -51,13 +39,13 @@ Inspired by [Djdefrag](https://github.com/Djdefrag) tools such as **QualityScale --- ## 🖼️ Interface Previews -![Main interface](rsc/Capture.png) -![Console](rsc/CaptureCONSOLE.png) +![Main interface](rsc/Capture.png) --- ## 🖼️ Quality Comparison + [WsvideovsGit.webm](https://github.com/user-attachments/assets/c72f389d-827e-49b9-91b7-fd13e5b59f22) [WsvideovsGit2.webm](https://github.com/user-attachments/assets/6695cce2-f42f-4955-8b43-56ec6d7b0bd2) @@ -157,7 +145,6 @@ Warlock-Studio/ └── SECURITY.md # Security reporting policies ``` -
--- @@ -166,8 +153,8 @@ Warlock-Studio/ We welcome contributions from the community: -**Fork** the repository. -**Create a branch** for your feature or bug fix. +**Fork** the repository. +**Create a branch** for your feature or bug fix. **Submit a Pull Request** with a detailed description and testing notes. 📧 Contact: **[negroayub97@gmail.com](mailto:negroayub97@gmail.com)** @@ -176,7 +163,7 @@ We welcome contributions from the community: ## 📜 License -© 2025 Iván Eduardo Chavez Ayub +© 2025 Iván Eduardo Chavez Ayub Licensed under **MIT**. Additional terms and attributions are provided in `NOTICE.md`. ### 📊 Integrated Technologies & Licenses @@ -198,4 +185,3 @@ Licensed under **MIT**. Additional terms and attributions are provided in `NOTIC | Inno Setup | Custom | Jordan Russell | [Official Site](http://www.jrsoftware.org/isinfo.php) |
- diff --git a/SECURITY.md b/SECURITY.md index 5c7bc32..8d2c1b4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,6 +6,7 @@ We aim to support the most recent stable release of Warlock-Studio. Security upd | Version | Supported | | ------- | --------- | +| 4.3.x | ✅ | | 4.2.1 | ✅ | | 4.2.x | ✅ | | 4.1.x | ✅ | diff --git a/Setup.iss b/Setup.iss index 5c2d713..99d6eeb 100644 --- a/Setup.iss +++ b/Setup.iss @@ -1,9 +1,9 @@ ; =================================================================== -; Warlock-Studio 4.2.1 - Inno Setup Script (Full Offline Installer) +; Warlock-Studio 4.3 - Inno Setup Script (Full Offline Installer) ; =================================================================== #define AppName "Warlock-Studio" -#define AppVersion "4.2.1" +#define AppVersion "4.3" #define AppPublisher "Iván Eduardo Chavez Ayub" #define AppURL "https://github.com/Ivan-Ayub97/Warlock-Studio" #define AppExeName "Warlock-Studio.exe" diff --git a/Warlock-Studio.py b/Warlock-Studio.py index b18e219..7800443 100644 --- a/Warlock-Studio.py +++ b/Warlock-Studio.py @@ -110,20 +110,32 @@ def find_by_relative_path(relative_path: str) -> str: app_name = "Warlock-Studio" -version = "4.2.1" +version = "4.3" + +# 🌑 Crimson Gold Dark Theme + +background_color = "#121212" # Negro profundo con leve calidez +app_name_color = "#FFFFFF" # Blanco puro, nítido sobre fondo oscuro +# Rojo vino muy oscuro (para paneles y marcos) +widget_background_color = "#4B0000" +text_color = "#FFFFFF" # Blanco principal para texto +# Gris claro suave, para subtítulos o texto menos importante +secondary_text_color = "#C8C8C8" +# Dorado puro (detalle de lujo y contraste) +accent_color = "#FFD700" +button_hover_color = "#FF4444" # Rojo claro brillante, resalta sin saturar +border_color = "#2A2A2A" # Gris oscuro para contornos discretos +# Rojo sangre sobrio (botones secundarios) +info_button_color = "#8B0000" +warning_color = "#E6C200" # Dorado cálido para alertas y avisos +# Verde neón tenue (no rompe la estética) +success_color = "#3FE55B" +error_color = "#B00020" # Rojo carmesí oscuro para errores +# Dorado-anaranjado suave (resalta elementos activos) +highlight_color = "#FFB84C" +# Rojo oscuro translúcido para barras y scrolls +scrollbar_color = "#660000" -background_color = "#480B0B" -app_name_color = "#FFFFFF" -widget_background_color = "#252525" -text_color = "#FFE32C" -secondary_text_color = "#D0D0D0" -accent_color = "#FFFFFF" -button_hover_color = "#FF6666" -border_color = "#404040" -info_button_color = "#A80000" -warning_color = "#E02CDA" -success_color = "#32CD32" -error_color = "#070087" VRAM_model_usage = { 'RealESR_Gx4': 2.2, @@ -137,7 +149,7 @@ VRAM_model_usage = { 'GFPGAN': 1.8, } -MENU_LIST_SEPARATOR = ["<------------------>"] +MENU_LIST_SEPARATOR = ["• • • • • • • • • • • •"] SRVGGNetCompact_models_list = ["RealESR_Gx4", "RealESR_Animex4"] BSRGAN_models_list = ["BSRGANx4", "BSRGANx2", "RealESRGANx4", "RealESRNetx4"] IRCNN_models_list = ["IRCNN_Mx1", "IRCNN_Lx1"] @@ -912,72 +924,103 @@ class AI_interpolation: class AI_face_restoration: """ - Face restoration AI class for model like GFPGAN - These model are specialized for face enhancement and restoration tasks. + Clase para restauración facial (GFPGAN u otros modelos ONNX de face-restoration). + Reemplaza/actualiza la implementación anterior con: + - Preprocesado seguro (float32 por defecto) + - Conversión a float16 solo si la sesión ONNX realmente lo requiere + - Manejo de alpha channel y reescalados + - Logs diagnósticos y manejo robusto de errores """ def __init__( - self, - AI_model_name: str, - directml_gpu: str, - input_resize_factor: float, - output_resize_factor: float, - max_resolution: int + self, + AI_model_name: str, + directml_gpu: str, + input_resize_factor: float, + output_resize_factor: float, + max_resolution: int ): - # Passed variables + # Parámetros pasados self.AI_model_name = AI_model_name self.directml_gpu = directml_gpu self.input_resize_factor = input_resize_factor self.output_resize_factor = output_resize_factor self.max_resolution = max_resolution - # Model-specific configurations + # Configuración por modelo (ajustable) + # GFPGAN suele usar 512x512; ajustar según tu modelo real self.model_configs = { "GFPGAN": { "input_size": (512, 512), "scale_factor": 1, "description": "GFPGAN v1.4 for face restoration", - "fp16": True + "fp16": True # indica que hay una variante fp16, pero no forzamos su uso } } - # Determine model path based on model name + # Rutas y estado self.AI_model_path = self._get_model_path() self.model_config = self.model_configs.get( AI_model_name, self.model_configs["GFPGAN"]) self.inferenceSession = None + # ------------------- + # CARGA Y SESIÓN ONNX + # ------------------- def _get_model_path(self) -> str: """ - Get the appropriate model path based on the model name + Construye la ruta al archivo ONNX del modelo. """ - if self.AI_model_name == "GFPGAN": - return find_by_relative_path(f"AI-onnx{os_separator}GFPGANv1.4.fp16.onnx") - else: - # Default fallback to GFPGAN - return find_by_relative_path(f"AI-onnx{os_separator}GFPGANv1.4.fp16.onnx") + # Prioriza la versión fp16 si nombre lo sugiere, si no existe cae en fp32 + candidate_fp16 = find_by_relative_path( + f"AI-onnx{os_separator}{self.AI_model_name}_fp16.onnx") + candidate_fp32 = find_by_relative_path( + f"AI-onnx{os_separator}{self.AI_model_name}_fp32.onnx") + candidate_default = find_by_relative_path( + f"AI-onnx{os_separator}{self.AI_model_name}.onnx") + + if os_path_exists(candidate_fp16): + return candidate_fp16 + if os_path_exists(candidate_fp32): + return candidate_fp32 + if os_path_exists(candidate_default): + return candidate_default + + # Si no existe, retornamos la ruta esperada (la carga fallará y se informará) + return candidate_default -# REEMPLAZA ESTE MÉTODO EN LA CLASE AI_face_restoration def _load_inferenceSession(self) -> None: - """Carga la sesión de inferencia utilizando la función centralizada.""" + """ + Carga la sesión ONNX usando la función centralizada create_onnx_session. + Levanta RuntimeError si falla. + """ try: + if not os_path_exists(self.AI_model_path): + raise FileNotFoundError( + f"AI model not found: {self.AI_model_path}") self.inferenceSession = create_onnx_session( self.AI_model_path, self.directml_gpu) + print( + f"[GFPGAN] Modelo cargado: {os_path_basename(self.AI_model_path)}") except Exception as e: error_msg = f"Failed to load face restoration model {os_path_basename(self.AI_model_path)}: {str(e)}" print(f"[AI ERROR] {error_msg}") raise RuntimeError(error_msg) + # ------------------- + # UTILIDADES INTERNAS + # ------------------- def get_image_mode(self, image: numpy_ndarray) -> str: + """ + Devuelve 'Grayscale', 'RGB' o 'RGBA' según la forma del array. + """ if image is None: raise ValueError("Image is None") shape = image.shape - if len(shape) == 2: # Grayscale: 2D array (rows, cols) + if len(shape) == 2: return "Grayscale" - # RGB: 3D array with 3 channels elif len(shape) == 3 and shape[2] == 3: return "RGB" - # RGBA: 3D array with 4 channels elif len(shape) == 3 and shape[2] == 4: return "RGBA" else: @@ -989,164 +1032,244 @@ class AI_face_restoration: return height, width def resize_with_input_factor(self, image: numpy_ndarray) -> numpy_ndarray: - old_height, old_width = self.get_image_resolution(image) - new_width = int(old_width * self.input_resize_factor) - new_height = int(old_height * self.input_resize_factor) - - new_width = new_width if new_width % 2 == 0 else new_width + 1 - new_height = new_height if new_height % 2 == 0 else new_height + 1 + """ + Redimensiona la imagen según input_resize_factor y garantiza dimensiones pares. + """ + old_h, old_w = self.get_image_resolution(image) + new_w = int(old_w * self.input_resize_factor) + new_h = int(old_h * self.input_resize_factor) + new_w = new_w if new_w % 2 == 0 else new_w + 1 + new_h = new_h if new_h % 2 == 0 else new_h + 1 if self.input_resize_factor > 1: - return opencv_resize(image, (new_width, new_height), interpolation=INTER_CUBIC) + return opencv_resize(image, (new_w, new_h), interpolation=INTER_CUBIC) elif self.input_resize_factor < 1: - return opencv_resize(image, (new_width, new_height), interpolation=INTER_AREA) + return opencv_resize(image, (new_w, new_h), interpolation=INTER_AREA) else: return image def resize_with_output_factor(self, image: numpy_ndarray) -> numpy_ndarray: - old_height, old_width = self.get_image_resolution(image) - new_width = int(old_width * self.output_resize_factor) - new_height = int(old_height * self.output_resize_factor) - - new_width = new_width if new_width % 2 == 0 else new_width + 1 - new_height = new_height if new_height % 2 == 0 else new_height + 1 + """ + Redimensiona la imagen según output_resize_factor y garantiza dimensiones pares. + """ + old_h, old_w = self.get_image_resolution(image) + new_w = int(old_w * self.output_resize_factor) + new_h = int(old_h * self.output_resize_factor) + new_w = new_w if new_w % 2 == 0 else new_w + 1 + new_h = new_h if new_h % 2 == 0 else new_h + 1 if self.output_resize_factor > 1: - return opencv_resize(image, (new_width, new_height), interpolation=INTER_CUBIC) + return opencv_resize(image, (new_w, new_h), interpolation=INTER_CUBIC) elif self.output_resize_factor < 1: - return opencv_resize(image, (new_width, new_height), interpolation=INTER_AREA) + return opencv_resize(image, (new_w, new_h), interpolation=INTER_AREA) else: return image + def add_alpha_channel(self, image: numpy_ndarray) -> numpy_ndarray: + """ + Asegura que la imagen tenga canal alpha (lo añade opaco si no). + """ + if len(image.shape) == 3 and image.shape[2] == 3: + alpha = numpy_full( + (image.shape[0], image.shape[1], 1), 255, dtype=uint8) + image = numpy_concatenate((image, alpha), axis=2) + return image + + # ------------------- + # PRE / POST PROCESS + # ------------------- def preprocess_face_image(self, image: numpy_ndarray) -> tuple[numpy_ndarray, bool]: """ - Preprocess image for face restoration models - Face restoration models typically expect normalized input in range [0, 1] - Returns: (preprocessed_image, has_alpha) + Prepara la imagen para la inferencia de restauración facial. + Devuelve (preprocessed_image_float32, had_alpha_bool). + - Siempre devuelve float32 por defecto. + - El caller decidirá convertir a float16 justo antes de la inferencia si la sesión lo requiere. """ - # Optimización: Asegurar memoria contigua al inicio + # Asegurar memoria contigua image = numpy_ascontiguousarray(image) - # Check if image has alpha channel - has_alpha = False + # Detectar alpha + had_alpha = False if len(image.shape) == 3 and image.shape[2] == 4: - has_alpha = True - # Convert BGRA to BGR for model processing - image = opencv_cvtColor(image, COLOR_BGRA2BGR) - elif len(image.shape) == 3 and image.shape[2] != 3: - # Handle unexpected channel counts - if image.shape[2] > 4: - # Take only first 3 channels + had_alpha = True + # Guardamos alpha pero procesaremos solo BGR + # Convertir BGRA -> BGR para el modelo + try: + image = opencv_cvtColor(image, COLOR_BGRA2BGR) + except Exception: + # Fallback: eliminar canal alpha si cvtColor falla image = image[:, :, :3] - elif image.shape[2] == 1: - # Convert grayscale to BGR - image = opencv_cvtColor(image, COLOR_GRAY2BGR) - # Resize to model's expected input size - target_size = self.model_config["input_size"] - image = opencv_resize(image, target_size, interpolation=INTER_AREA) + # Asegurar que la imagen tenga 3 canales + if len(image.shape) == 2: + image = opencv_cvtColor(image, COLOR_GRAY2RGB) + elif len(image.shape) == 3 and image.shape[2] != 3: + # si hay más canales, recortar a 3 + image = image[:, :, :3] - # Determinar el tipo de dato correcto (float16 o float32) - if self.model_config.get("fp16", False): - dtype = float16 - else: - dtype = float32 + # Redimensionar a tamaño del modelo (input_size) + target_w, target_h = self.model_config["input_size"][1], self.model_config["input_size"][0] + try: + image_resized = opencv_resize( + image, (target_w, target_h), interpolation=INTER_AREA) + except Exception as e: + print( + f"[GFPGAN] Warning: resize failed: {e}. Using original size.") + image_resized = image - # Optimización: Normalizar usando memoria contigua - image = numpy_ascontiguousarray(image, dtype=dtype) / 255.0 + # Normalizar a float32 en rango [0,1] + preprocessed = numpy_ascontiguousarray( + image_resized, dtype=float32) / 255.0 - # Transpose to CHW format (channels, height, width) - image = numpy_transpose(image, (2, 0, 1)) + # Transpose a NCHW + preprocessed = numpy_transpose(preprocessed, (2, 0, 1)) + preprocessed = numpy_expand_dims(preprocessed, axis=0) # batch dim - # Add batch dimension - image = numpy_expand_dims(image, axis=0) - - return image, has_alpha + return preprocessed, had_alpha def postprocess_face_image(self, output: numpy_ndarray, original_size: tuple) -> numpy_ndarray: """ - Postprocess face restoration model output + Postprocesa la salida del modelo: + - squeeze batch + - clamp [0,1] + - transpose a HWC + - convertir a uint8 y redimensionar a tamaño original """ - # Remove batch dimension + # Squeeze batch output = numpy_squeeze(output, axis=0) - # Clamp values to [0, 1] - output = numpy_clip(output, 0, 1) + # Clamp y asegurar tipo float32 + output = numpy_clip(output, 0.0, 1.0) - # Transpose back to HWC format + # Transpose a HWC output = numpy_transpose(output, (1, 2, 0)) - # Convert back to uint8 - output = (output * 255).astype(uint8) + # Convertir a uint8 + output_uint8 = (output * 255.0).round().astype(uint8) - # Resize back to original size - if original_size != self.model_config["input_size"]: - output = opencv_resize( - output, (original_size[1], original_size[0]), interpolation=INTER_CUBIC) + # Redimensionar a tamaño original (original_size es (h, w)) + try: + if (original_size[0], original_size[1]) != (self.model_config["input_size"][0], self.model_config["input_size"][1]): + # opencv resize espera (width, height) + output_uint8 = opencv_resize( + output_uint8, (original_size[1], original_size[0]), interpolation=INTER_CUBIC) + except Exception as e: + print(f"[GFPGAN] Warning: postprocess resize failed: {e}") - return output + return output_uint8 + # ------------------- + # LÓGICA PRINCIPAL + # ------------------- def face_restoration(self, image: numpy_ndarray) -> numpy_ndarray: """ - Perform face restoration on the input image + Orquestador principal: aplica restauración facial usando el modelo ONNX. + Retorna la imagen restaurada (preservando alpha cuando sea necesario). """ if self.inferenceSession is None: self._load_inferenceSession() - # Store original size and check for alpha channel - original_size = (image.shape[0], image.shape[1]) + # Guardar tamaño original y alpha si existe + original_h, original_w = self.get_image_resolution(image) original_alpha = None - - # Extract alpha channel if present if len(image.shape) == 3 and image.shape[2] == 4: - original_alpha = image[:, :, 3] # Store original alpha + original_alpha = image[:, :, 3] - # Apply input resizing - image = self.resize_with_input_factor(image) + # Aplicar factor de input (si corresponde) + try: + resized_input = self.resize_with_input_factor(image) + except Exception as e: + print(f"[GFPGAN] Warning: resize_with_input_factor failed: {e}") + resized_input = image - # Preprocess for face restoration - preprocessed, had_alpha = self.preprocess_face_image(image) + # Preprocess -> float32 + preprocessed, had_alpha = self.preprocess_face_image(resized_input) - # Run inference - input_name = self.inferenceSession.get_inputs()[0].name - output_name = self.inferenceSession.get_outputs()[0].name + # Detectar el dtype esperado por la sesión ONNX (si es posible) + session_input = None + input_type_str = None + try: + session_input = self.inferenceSession.get_inputs()[0] + # Algunos objetos tienen .type o .dtype, algunos no; usamos str() como fallback + if hasattr(session_input, 'type') and session_input.type: + input_type_str = str(session_input.type) + elif hasattr(session_input, 'dtype') and session_input.dtype: + input_type_str = str(session_input.dtype) + else: + # Intentar inspeccionar la información de la firma + try: + input_type_str = str(session_input) # puede contener info + except Exception: + input_type_str = None + except Exception: + input_type_str = None - result = self.inferenceSession.run( - [output_name], {input_name: preprocessed})[0] + print( + f"[GFPGAN] Pre-infer dtype(preprocessed)={preprocessed.dtype}, session_input_type={input_type_str}") - # Postprocess the result + # Convertir a float16 SOLO si la sesión lo requiere explícitamente + run_input = preprocessed + try: + requires_fp16 = False + if input_type_str: + if 'float16' in input_type_str.lower() or 'fp16' in input_type_str.lower(): + requires_fp16 = True + if requires_fp16: + # Convertimos sólo aquí, antes de pasar al modelo + run_input = preprocessed.astype(float16) + print( + "[GFPGAN] Convirtiendo input a float16 para la inferencia (según sesión).") + except Exception as e: + print( + f"[GFPGAN] Warning: no se pudo convertir a float16: {e}. Manteniendo float32.") + + # Ejecutar la inferencia + try: + input_name = self.inferenceSession.get_inputs()[0].name + output_name = self.inferenceSession.get_outputs()[0].name + # Ejecutar la sesión (pasamos run_input) + output = self.inferenceSession.run( + [output_name], {input_name: run_input})[0] + except Exception as e: + raise RuntimeError(f"GFPGAN inference failed: {e}") + + # Postprocess restored_face = self.postprocess_face_image( - result, (image.shape[0], image.shape[1])) + output, (resized_input.shape[0], resized_input.shape[1])) - # Restore alpha channel if original image had one + # Restaurar canal alpha si era necesario if had_alpha and original_alpha is not None: - # Resize alpha to match restored face size - alpha_resized = opencv_resize(original_alpha, - (restored_face.shape[1], - restored_face.shape[0]), - interpolation=INTER_CUBIC) + try: + alpha_resized = opencv_resize( + original_alpha, (restored_face.shape[1], restored_face.shape[0]), interpolation=INTER_CUBIC) + if len(alpha_resized.shape) == 2: + alpha_resized = numpy_expand_dims(alpha_resized, axis=-1) + restored_face = numpy_concatenate( + (restored_face, alpha_resized), axis=2) + except Exception as e: + print( + f"[GFPGAN] Warning: failed to restore alpha channel: {e}") - # Convert to RGBA - if len(alpha_resized.shape) == 2: # Ensure alpha has correct shape - alpha_resized = numpy_expand_dims(alpha_resized, axis=-1) - - restored_face = numpy_concatenate( - (restored_face, alpha_resized), axis=2) - - # Apply output resizing - restored_face = self.resize_with_output_factor(restored_face) + # Aplicar factor de output (si corresponde) + try: + restored_face = self.resize_with_output_factor(restored_face) + except Exception as e: + print(f"[GFPGAN] Warning: resize_with_output_factor failed: {e}") return restored_face + # ------------------- + # Orquestador público + # ------------------- def AI_orchestration(self, image: numpy_ndarray) -> numpy_ndarray: """ - Main orchestration function for face restoration + Método público que otros módulos llaman para aplicar restauración facial. """ try: return self.face_restoration(image) except Exception as e: print(f"[FACE RESTORATION ERROR] {str(e)}") - # Return original image if restoration fails + # En caso de fallo devolvemos la imagen original (no alterada) return image @@ -2998,152 +3121,162 @@ def create_frame_list_file(frame_paths: list[str], txt_path: str) -> bool: def video_encoding( - process_status_q: multiprocessing_Queue, - video_path: str, - video_output_path: str, - upscaled_frame_paths: list[str], - selected_video_codec: str, + process_status_q: multiprocessing_Queue, + video_path: str, + video_output_path: str, + upscaled_frame_paths: list[str], + selected_video_codec: str, ) -> None: - """Enhanced video encoding with robust error handling and codec support.""" + """ + Video encoding function for Warlock-Studio. + - Toma los frames mejorados por IA y los recompone en un video final. + - Detecta y conserva (o re-codifica) el audio original del video. + - Maneja errores de FFmpeg de forma robusta y limpia temporales. + """ try: - # Validate inputs - if not upscaled_frame_paths: - raise ValueError("No frame paths provided for video encoding") - - if not validate_ffmpeg_executable(): - raise RuntimeError("FFmpeg validation failed") - - # Get video information - video_info = get_video_info(video_path) - if not video_info: - raise ValueError("Could not get video information") - - # Get optimized codec settings - codec_settings = get_video_codec_settings( - selected_video_codec, video_info) - - # Test codec compatibility - if not test_codec_compatibility(codec_settings['codec']): - print( - f"[WARNING] Codec {codec_settings['codec']} not available, falling back to libx264") - codec_settings = get_video_codec_settings('x264', video_info) - - # Prepare file paths + # --- Preparación de rutas temporales --- base_name = os_path_splitext(video_output_path)[0] txt_path = f"{base_name}_frames.txt" no_audio_path = f"{base_name}_no_audio{os_path_splitext(video_output_path)[1]}" - # Clean up any existing temporary files + # Eliminar residuos previos for temp_file in [txt_path, no_audio_path]: if os_path_exists(temp_file): try: os_remove(temp_file) except Exception as e: print( - f"[WARNING] Could not remove temporary file {temp_file}: {e}") + f"[WARNING] No se pudo eliminar temporal {temp_file}: {e}") - # Get video FPS with fallback + # --- Obtener FPS del video original --- try: video_fps = get_video_fps(video_path) - if video_fps <= 0 or video_fps > 1000: # Sanity check - raise ValueError(f"Invalid frame rate: {video_fps}") - video_fps_str = f"{video_fps:.6f}" # High precision for FFmpeg + if video_fps <= 0 or video_fps > 1000: + raise ValueError(f"FPS inválido: {video_fps}") + video_fps_str = f"{video_fps:.6f}" except Exception as e: - print(f"[WARNING] Could not get video FPS: {e}, using 30.0") + print( + f"[WARNING] No se pudieron obtener los FPS: {e}, usando 30.0 por defecto") video_fps_str = "30.000000" - # Create frame list file + # --- Crear lista de frames para FFmpeg --- if not create_frame_list_file(upscaled_frame_paths, txt_path): - raise RuntimeError("Failed to create frame list file") + raise RuntimeError("Error al crear el archivo de lista de frames") - # Build encoding command + # --- Configurar codificación principal --- + codec_settings = get_video_codec_settings( + selected_video_codec, {'fps': video_fps_str}) encoding_command = build_encoding_command( - video_path, txt_path, no_audio_path, codec_settings, video_fps_str - ) + video_path, txt_path, no_audio_path, codec_settings, video_fps_str) - # Execute video encoding - print(f"[FFMPEG] Starting encoding with {codec_settings['codec']}") + print(f"[FFMPEG] Iniciando codificación con {codec_settings['codec']}") print( - f"[FFMPEG] Processing {len(upscaled_frame_paths)} frames at {video_fps_str} FPS") + f"[FFMPEG] Procesando {len(upscaled_frame_paths)} frames a {video_fps_str} FPS") + # --- Ejecutar FFmpeg para generar video sin audio --- try: result = subprocess_run( encoding_command, check=True, capture_output=True, text=True, - timeout=3600 # 1 hour timeout + timeout=3600 ) - - # Verify output file was created and has reasonable size if not os_path_exists(no_audio_path): raise RuntimeError( - "Video encoding completed but output file was not created") - + "FFmpeg terminó pero el archivo de salida no existe") output_size = os_path_getsize(no_audio_path) - if output_size < 1024: # Less than 1KB indicates failure + if output_size < 1024: raise RuntimeError( - f"Video encoding produced suspiciously small file: {output_size} bytes") - + f"Archivo de salida sospechosamente pequeño: {output_size} bytes") print( - f"[FFMPEG] Video encoding completed: {output_size / (1024*1024):.1f} MB") - + f"[FFMPEG] Codificación de video completada ({output_size / (1024*1024):.1f} MB)") except subprocess.TimeoutExpired: - error_msg = "Video encoding timeout (exceeded 1 hour)" + error_msg = "FFmpeg excedió el tiempo límite (1 hora)" log_and_report_error(error_msg) write_process_status( process_status_q, f"{ERROR_STATUS}{error_msg}") return except CalledProcessError as e: error_details = e.stderr if e.stderr else str(e) - error_msg = f"FFmpeg encoding failed: {error_details}" - - # Try to provide helpful error messages + error_msg = f"FFmpeg falló durante codificación: {error_details}" if "Unknown encoder" in error_details: - error_msg += "\nThe selected codec is not supported. Try x264 instead." + error_msg += "\nEl códec seleccionado no está soportado. Prueba con x264." elif "Device or resource busy" in error_details: - error_msg += "\nGPU encoder is busy. Try software encoding (x264/x265)." + error_msg += "\nEl codificador GPU está ocupado. Prueba codificación por software." elif "Invalid data" in error_details: - error_msg += "\nFrame data may be corrupted. Check input images." - + error_msg += "\nLos datos de los frames podrían estar dañados." log_and_report_error(error_msg) write_process_status( process_status_q, f"{ERROR_STATUS}{error_msg}") return - # Audio passthrough with multiple fallback strategies - print("[FFMPEG] Processing audio track") + # --- Detección de audio --- + print("[FFMPEG] Verificando pista de audio del video original...") - # Check if original video has audio - audio_info_command = [ - FFMPEG_EXE_PATH, - "-i", video_path, - "-hide_banner", - "-loglevel", "error", - "-select_streams", "a:0", - "-show_entries", "stream=codec_name", - "-of", "csv=p=0" - ] + ffprobe_path = None + try: + ffprobe_guess = FFMPEG_EXE_PATH.replace( + "ffmpeg.exe", "ffprobe.exe") + if os_path_exists(ffprobe_guess): + ffprobe_path = ffprobe_guess + else: + ffprobe_guess2 = FFMPEG_EXE_PATH.replace( + "ffmpeg.exe", "ffprobe") + if os_path_exists(ffprobe_guess2): + ffprobe_path = ffprobe_guess2 + except Exception: + ffprobe_path = None has_audio = False - try: - audio_result = subprocess_run( - audio_info_command, - capture_output=True, - text=True, - timeout=30 - ) - has_audio = audio_result.returncode == 0 and audio_result.stdout.strip() - except Exception: - print("[WARNING] Could not detect audio stream, assuming no audio") + audio_codec = "" + if ffprobe_path: + try: + probe_cmd = [ + ffprobe_path, + "-v", "error", + "-select_streams", "a", + "-show_entries", "stream=codec_name", + "-of", "default=noprint_wrappers=1:nokey=1", + video_path + ] + probe = subprocess_run( + probe_cmd, capture_output=True, text=True, timeout=30) + audio_codec = probe.stdout.strip() + print(f"[FFPROBE] stdout: {probe.stdout.strip()}") + print(f"[FFPROBE] stderr: {probe.stderr.strip()}") + has_audio = bool(audio_codec) + except Exception as e: + print(f"[WARNING] No se pudo detectar audio con ffprobe: {e}") + has_audio = False + + if not ffprobe_path or not has_audio: + try: + probe_cmd = [FFMPEG_EXE_PATH, "-i", video_path] + probe = subprocess_run( + probe_cmd, capture_output=True, text=True, timeout=20) + stderr_output = probe.stderr or probe.stdout or "" + print(f"[FFMPEG PROBE] Primeras líneas del stderr:") + print("\n".join(stderr_output.splitlines()[:10])) + for line in stderr_output.splitlines(): + if "Audio:" in line: + has_audio = True + audio_codec = line.strip() + break + except Exception as e: + print(f"[WARNING] Fallback de detección con FFmpeg falló: {e}") + has_audio = False + + print(f"[FFMPEG] has_audio={has_audio}, audio_codec={audio_codec!r}") + + # --- Estrategias de audio --- if has_audio: - # Strategy 1: Copy audio as-is - audio_command = [ + # Estrategia 1: copiar pista original + audio_copy_cmd = [ FFMPEG_EXE_PATH, - "-y", - "-loglevel", "error", + "-y", "-loglevel", "error", "-i", video_path, "-i", no_audio_path, "-c:v", "copy", @@ -3153,101 +3286,76 @@ def video_encoding( "-shortest", video_output_path ] - try: - result = subprocess_run( - audio_command, - check=True, - capture_output=True, - text=True, - timeout=600 - ) - + print("[FFMPEG] Intentando copiar pista de audio...") + res = subprocess_run( + audio_copy_cmd, check=True, capture_output=True, text=True, timeout=600) + print(f"[FFMPEG] stdout:\n{res.stdout}") + print(f"[FFMPEG] stderr:\n{res.stderr}") if os_path_exists(no_audio_path): os_remove(no_audio_path) - print("[FFMPEG] Audio passthrough completed successfully") + print("[FFMPEG] Copia de audio completada exitosamente.") + return + except Exception as e: + print(f"[WARNING] Copia de audio falló: {e}") + print("[FFMPEG] Intentando re-codificar audio a AAC...") - except (CalledProcessError, subprocess.TimeoutExpired) as e: - print(f"[WARNING] Audio copy failed: {e}") + # Estrategia 2: re-codificar audio + audio_reencode_cmd = [ + FFMPEG_EXE_PATH, + "-y", "-loglevel", "error", + "-i", video_path, + "-i", no_audio_path, + "-c:v", "copy", + "-c:a", "aac", + "-b:a", "128k", + "-map", "1:v:0", + "-map", "0:a:0", + "-shortest", + video_output_path + ] + try: + res = subprocess_run( + audio_reencode_cmd, check=True, capture_output=True, text=True, timeout=600) + print(f"[FFMPEG] Re-codificación de audio completada.") + if os_path_exists(no_audio_path): + os_remove(no_audio_path) + return + except Exception as audio_error: + print( + f"[WARNING] Re-codificación de audio falló: {audio_error}") - # Strategy 2: Re-encode audio - print("[FFMPEG] Trying audio re-encoding...") - audio_reencode_command = [ - FFMPEG_EXE_PATH, - "-y", - "-loglevel", "error", - "-i", video_path, - "-i", no_audio_path, - "-c:v", "copy", - "-c:a", "aac", - "-b:a", "128k", - "-map", "1:v:0", - "-map", "0:a:0", - "-shortest", - video_output_path - ] + # Estrategia 3: sin audio + try: + if os_path_exists(no_audio_path): + shutil_move(no_audio_path, video_output_path) + print("[FFMPEG] Video final sin pista de audio (fallback).") + return + except Exception as move_error: + raise RuntimeError( + f"No se pudo mover archivo final sin audio: {move_error}") - try: - result = subprocess_run( - audio_reencode_command, - check=True, - capture_output=True, - text=True, - timeout=600 - ) - - if os_path_exists(no_audio_path): - os_remove(no_audio_path) - print("[FFMPEG] Audio re-encoding completed successfully") - - except Exception as audio_error: - print( - f"[WARNING] Audio re-encoding also failed: {audio_error}") - # Strategy 3: Use video without audio - try: - if os_path_exists(no_audio_path): - shutil_move(no_audio_path, video_output_path) - print("[FFMPEG] Using video without audio") - except Exception as move_error: - raise RuntimeError( - f"Failed to save final video: {move_error}") else: - # No audio in original, just rename the video file + # Video original sin pista de audio try: shutil_move(no_audio_path, video_output_path) - print("[FFMPEG] Video saved successfully (no audio track)") + print( + "[FFMPEG] Video guardado sin pista de audio (originalmente mudo).") + return except Exception as move_error: - raise RuntimeError(f"Failed to save final video: {move_error}") - - # Clean up temporary files - for temp_file in [txt_path]: - if os_path_exists(temp_file): - try: - os_remove(temp_file) - except Exception: - pass - - # Final validation - if not os_path_exists(video_output_path): - raise RuntimeError( - "Video encoding completed but final output file is missing") - - final_size = os_path_getsize(video_output_path) - print( - f"[FFMPEG] Final video created: {final_size / (1024*1024):.1f} MB") + raise RuntimeError( + f"No se pudo guardar video sin audio: {move_error}") except Exception as e: - error_msg = f"Video encoding failed: {str(e)}" + error_msg = f"Error general en video_encoding: {str(e)}" log_and_report_error(error_msg) write_process_status(process_status_q, f"{ERROR_STATUS}{error_msg}") - - # Clean up on failure - for temp_file in [txt_path, no_audio_path] if 'txt_path' in locals() and 'no_audio_path' in locals() else []: - if os_path_exists(temp_file): - try: - os_remove(temp_file) - except Exception: - pass + # Limpieza de temporales + if 'txt_path' in locals() and os_path_exists(txt_path): + try: + os_remove(txt_path) + except Exception: + pass def check_video_upscaling_resume( @@ -3686,7 +3794,6 @@ def fluidframes_video_interpolate( process_status_q, f"{file_number}. Encoding frame-generated video") video_encoding( process_status_q, video_path, video_output_path, total_frames_paths, selected_video_codec) - copy_file_metadata(video_path, video_output_path) # Step 7. Cleanup after video interpolation processing if not selected_keep_frames and os_path_exists(target_directory): @@ -4184,7 +4291,6 @@ def upscale_video( process_status_q, f"{file_number}. Encoding upscaled video") video_encoding(process_status_q, video_path, video_output_path, upscaled_frame_paths, selected_video_codec) - copy_file_metadata(video_path, video_output_path) # 7. Delete frames folder if not selected_keep_frames: diff --git a/rsc/Capture.png b/rsc/Capture.png index 736a678..3f6c00d 100644 Binary files a/rsc/Capture.png and b/rsc/Capture.png differ diff --git a/rsc/CaptureCONSOLE.png b/rsc/CaptureCONSOLE.png deleted file mode 100644 index 62ce32a..0000000 Binary files a/rsc/CaptureCONSOLE.png and /dev/null differ
- - Download Manual + width="300" style="display:block; margin:auto; margin-bottom:10px;" />