96 Commits

Author SHA1 Message Date
k4yt3x
31728ff625 added PowerShell automatic build script 2020-05-06 20:20:51 -04:00
k4yt3x
3168737fd4 added time elapsed, time remaining and speed rate 2020-05-06 20:20:38 -04:00
k4yt3x
5fbc195740 unified config path case 2020-05-06 20:19:49 -04:00
K4YT3X
40d0d79141 added a patreon badge 2020-05-06 20:03:49 +00:00
k4yt3x
37a0966bad added binary path loading and error dialog system 2020-05-06 15:36:55 -04:00
k4yt3x
baf4c5815f added pyqt5 into requirements 2020-05-06 14:30:59 -04:00
k4yt3x
d25a478e64 added automatic constraint update upon driver selection 2020-05-06 14:27:21 -04:00
K4YT3X
ba723c6216 enable sponsorships 2020-05-06 17:46:35 +00:00
k4yt3x
11ea8a7a21 added config descriptions for some drivers 2020-05-06 13:24:32 -04:00
k4yt3x
3bce37416b GUI 2.0 design and basic functionalities finished 2020-05-06 13:24:10 -04:00
k4yt3x
e3cfe13b5c fixed some bugs in GUI 2020-05-06 11:01:14 -04:00
k4yt3x
81d8ce78cb updated default values 2020-05-06 10:40:04 -04:00
k4yt3x
89fa91c8c5 updated GUI UI file for new settings 2020-05-06 10:39:47 -04:00
k4yt3x
c8dbb90f5b added waifu2x-caffe and waifu2x-converter-cpp settings to GUI 2020-05-06 10:39:34 -04:00
k4yt3x
685120011b added Waifu2x-Extension-GUI into the related projects section 2020-05-06 04:57:43 -04:00
k4yt3x
82da40327b updated Video2X GUI screenshot 2020-05-06 04:48:04 -04:00
k4yt3x
9b4fb3258a video2x GUI 2.0.0: completely redesigned and rewritten with PyQt5 2020-05-06 04:45:48 -04:00
k4yt3x
f00a10aa91 added image files and project files 2020-05-06 04:44:36 -04:00
k4yt3x
93e8f36ffb fixed a typo 2020-05-06 00:12:49 -04:00
k4yt3x
f10e59099c deprecated JSON config file 2020-05-06 00:12:31 -04:00
k4yt3x
82715ade18 addressed srmd-ncnn-vulkan and waifu2x-ncnn-vulkan model path issues 2020-05-05 02:49:10 -04:00
K4YT3X
63de63e594 added master branch version badge 2020-05-05 06:29:03 +00:00
K4YT3X
8daeb862a4 updated README terminal screenshot 2020-05-05 06:26:39 +00:00
k4yt3x
be0d1f1d08 added some badges 2020-05-05 02:22:37 -04:00
k4yt3x
ec1327b5d8 updated README for version 4.0.0 2020-05-05 01:51:24 -04:00
k4yt3x
5b3e5604cc fixed srmd-ncnn-vulkan binary path issue 2020-05-05 01:12:12 -04:00
k4yt3x
a2fc71d6c5 fixed Anime4KCPP upscaling bug 2020-05-05 01:11:50 -04:00
k4yt3x
ab684bcd48 updated default Anime4KCPP path 2020-05-05 01:11:37 -04:00
k4yt3x
d26b406813 setup script 1.8.0: added Anime4KCPP setup support 2020-05-05 01:11:26 -04:00
k4yt3x
133b837b15 added language zh_CN 2020-05-04 20:25:43 -04:00
k4yt3x
9aebc77883 removing default values for model directories for multi-platform support 2020-05-04 20:25:32 -04:00
k4yt3x
b8472f155a added internationalization support 2020-05-04 20:25:12 -04:00
k4yt3x
19e6d241d5 fixing driver argument TypeError 2020-05-04 17:19:48 -04:00
k4yt3x
89860f22cb 4.0.0 new argument parsing mechanism and project structure 2020-05-04 17:12:41 -04:00
k4yt3x
88299d404a removed anime4k support and added Anime4KCPP support; cleaned up the program 2020-05-03 19:20:23 -04:00
k4yt3x
0f1639ed62 adding Anime4KCPP support 2020-05-03 19:19:19 -04:00
k4yt3x
3b6462d1da removing support for Anime4K (Java) 2020-05-03 19:19:00 -04:00
k4yt3x
43a2078330 moving wrappers into a sub-directory 2020-05-03 19:18:39 -04:00
k4yt3x
b1e844dcad updated README for setup script 1.7.0 2020-04-26 20:12:42 -04:00
k4yt3x
becce32d3e setup script 1.7.0: added support for srmd-ncnn-vulkan 2020-04-26 20:12:06 -04:00
k4yt3x
d2ddf32527 updated README for 3.2.0 2020-04-26 20:04:37 -04:00
k4yt3x
7224c53997 3.2.0 added SRMD NCNN Vulkan support 2020-04-26 20:01:37 -04:00
k4yt3x
2ed9bd4da6 updated configuration files for SRMD NCNN Vulkan 2020-04-26 20:00:01 -04:00
k4yt3x
38c52cdfd2 updated JSON config file to match YAML 2020-04-26 19:40:21 -04:00
k4yt3x
80579d3fa9 updated absolutify paths function 2020-04-26 13:40:22 -04:00
k4yt3x
47f94dfd6c updated Anime4K ZIP file download URL 2020-04-24 00:22:39 -04:00
k4yt3x
c470dde5da fixing issue #220 2020-04-04 06:24:40 -04:00
k4yt3x
c532ac3c88 updated file modification dates and editor information 2020-03-23 09:05:24 -04:00
K4YT3X
f50ceecd62 Merge pull request #214 from 28598519a/master
Fix k4yt3x#211 bug
2020-03-23 08:55:16 -04:00
劃破黑夜
2ba0e951f9 Fix k4yt3x#211 bug 2020-03-17 22:01:56 +08:00
k4yt3x
5753ea8f52 updated readme for version 3.1.0 2020-02-26 22:31:51 -05:00
k4yt3x
09db8bedd0 3.1.0 switched to multi-processing and added KeyboardInterrupt support 2020-02-26 05:28:48 -05:00
k4yt3x
6a100b1526 removed multi-threading in favor of muti-processing 2020-02-26 05:27:57 -05:00
k4yt3x
a2150a8dbc fixing progress bar error due to string case mismatch 2020-02-22 02:31:39 -05:00
k4yt3x
78ded08ed5 changed default number of threads to 1 2020-02-21 23:16:22 -05:00
k4yt3x
5db2cfd2a3 added anime4k instructions 2020-01-12 03:32:25 -05:00
k4yt3x
652b9f34bd fixing #180 bug2 attempt 2 2020-01-04 17:03:16 -05:00
k4yt3x
f3c50ea728 fixing #180 bug2 2020-01-04 16:59:51 -05:00
k4yt3x
880087241d updated waifu2x-ncnn-vulkan path key for new config structure 2020-01-04 16:56:08 -05:00
k4yt3x
067e17290a 1.6.1 changed driver paths to 3.0.0 config style 2020-01-04 16:49:13 -05:00
k4yt3x
fc675e7899 updated copyright date 2020-01-04 16:48:37 -05:00
K4YT3X
2d81270e44 Set theme jekyll-theme-slate 2019-12-22 06:00:02 +00:00
K4YT3X
3ace2447c2 Create CNAME 2019-12-22 05:48:39 +00:00
K4YT3X
30b0e2c7bf Delete CNAME 2019-12-22 05:48:25 +00:00
k4yt3x
d54fea0310 enables available memory warning for Linux platform 2019-12-19 23:03:58 -05:00
k4yt3x
332055a4e5 fixed temp directory cleaning problem on exit 2019-12-11 22:20:01 -05:00
k4yt3x
19e17b1a8f updated platform information 2019-12-11 21:59:38 -05:00
k4yt3x
9e745fb747 video2x_gui 1.1.3: fixed driver path key name 2019-12-11 21:59:20 -05:00
K4YT3X
fd5edead7e Merge pull request #171 from YOUSIKI/master
Check platform correctly
2019-11-30 19:56:08 +00:00
YOUSIKI
aa7c0b3f12 Check platform correctly
Continue if running on win32.
Exit if running on other platforms.
2019-11-30 22:56:34 +08:00
k4yt3x
65cc4c6afb Merge branch '3.0.0' 2019-11-26 04:14:13 -05:00
k4yt3x
bd2da021bd updated README for 3.0.0 2019-11-26 04:12:04 -05:00
k4yt3x
7e888db7b2 formatted YAML file 2019-11-26 04:10:01 -05:00
k4yt3x
7726a86e35 added platform check, made PermissionError more obvious 2019-11-26 04:03:03 -05:00
K4YT3X
a0595136ab Create CNAME 2019-11-18 06:43:00 +00:00
K4YT3X
5c93a5a73b Delete CNAME 2019-11-18 06:42:54 +00:00
K4YT3X
7b9b87fa9b Create CNAME 2019-11-17 05:41:13 +00:00
k4yt3x
cb255da65c added missing sys library import 2019-11-16 02:54:25 -05:00
k4yt3x
d503325a62 changed YAML load to FullLoader to make it WIndows-compatible 2019-11-16 02:52:33 -05:00
k4yt3x
954233c238 changed default config path from JSON to YAML 2019-11-16 01:51:01 -05:00
k4yt3x
95416f68a8 make the GUI script YAML-compatible 2019-11-16 01:50:41 -05:00
k4yt3x
dce778b3bf 1.1.2 fixed repeated output file extensions 2019-11-15 22:40:48 -05:00
k4yt3x
c537dd726c changed YAML loader to Windows compatible FullLoader 2019-11-15 22:40:19 -05:00
k4yt3x
b1918a4a8a fixed pixel_formats print bug and subprocess execution bug 2019-11-15 02:39:27 -05:00
k4yt3x
7ae9618785 updated file modification date 2019-11-15 02:38:39 -05:00
k4yt3x
eb3d29103c fixed anime4k path key 2019-11-15 02:38:11 -05:00
k4yt3x
a188f6ebda 1.6.0 added support for YAML, better exception handling 2019-11-15 02:04:12 -05:00
k4yt3x
fd67dfca11 docstring modifications, added JDK warning 2019-11-15 02:03:44 -05:00
k4yt3x
b1f29f1098 fixed NCNN Vulkan driver argument error 2019-11-15 01:09:55 -05:00
k4yt3x
fe7c0c840d fixed type error for waifu2x-ncnn-vulkan argument 2019-10-24 21:33:05 -04:00
k4yt3x
dc2410d4da renaming variables, using YAML to replace JSON 2019-10-19 21:53:12 -04:00
k4yt3x
91ac512d57 remove .exe extension for Linux compatibility 2019-10-19 21:52:31 -04:00
k4yt3x
732288f075 renamed waifu2x_settings to driver_settings 2019-10-19 21:52:11 -04:00
k4yt3x
e812c228c3 updated file permissions 2019-10-19 21:51:18 -04:00
k4yt3x
9841fa9577 converted JSON config into YAML 2019-10-19 21:45:44 -04:00
k4yt3x
5391f59847 renamed bin to src since python isn't binary 2019-10-19 21:40:00 -04:00
35 changed files with 3914 additions and 1875 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: k4yt3x
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

1
CNAME Normal file
View File

@@ -0,0 +1 @@
video2x.org

177
README.md
View File

@@ -1,20 +1,30 @@
![Master Branch Version](https://img.shields.io/badge/master-v4.0.0-9cf?style=flat-square)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/k4yt3x/video2x?style=flat-square)
![GitHub All Releases](https://img.shields.io/github/downloads/k4yt3x/video2x/total?style=flat-square)
![GitHub](https://img.shields.io/github/license/k4yt3x/video2x?style=flat-square)
![Platforms](https://img.shields.io/badge/Platforms-Windows%20%7C%20Linux%20%7C%20macOS-blue?style=flat-square)
<img alt="Become a Patron!"
src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png"
href="https://www.patreon.com/bePatron?u=34970782"
height=20 />
# Video2X Lossless Video Enlarger
### Official Discussion Group (Telegram): https://t.me/video2x
## Download Builds
## Download Builds (Windows)
You can go to the [releases page](https://github.com/k4yt3x/video2x/releases) to download the latest builds of `Video2X`. The exe files will require no Python or Python module installation.
The **`full`** package provides all packages that will possibly be needed by `Video2X`, including `FFmpeg`, `waifu2x-caffe`, `waifu2x-converter-cpp`, `waifu2x-ncnn-vulkan`, and `Anime4K`. The config file (`video2x.json`) is also already configured for the environment. All you need to do is just to launch `video2x.exe`.
The **`full`** package provides all packages that will possibly be needed by `Video2X`, including `FFmpeg`, `waifu2x-caffe`, `waifu2x-converter-cpp`, `waifu2x-ncnn-vulkan`, `srmd-ncnn-vulkan` and `Anime4KCPP`. The config file (`video2x.yaml`) is also already configured for the environment. All you need to do is just to launch `video2x.exe`.
The **`light`** package provides only the most basic functions of `Video2X`. Only `video2x.exe`, `video2x_setup.exe` and `video2x.json` are included. To setup dependencies (e.g. `FFmpeg` and `Waifu2X`) automatically, simply launch `video2x_setup.exe`.
The **`light`** package provides only the most basic functions of `Video2X`. Only `video2x.exe`, `video2x_setup.exe` and `video2x.yaml` are included. To setup dependencies (e.g. `FFmpeg` and `Waifu2X`) automatically, simply launch `video2x_setup.exe`.
## Prerequisites
Component names that are **bolded** can be automatically downloaded and configured with the `video2x_setup.py` script.
1. Operating System: Windows
1. Operating System: Windows / Linux
2. AMD GPU / Nvidia GPU
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
@@ -22,38 +32,41 @@ Component names that are **bolded** can be automatically downloaded and configur
- [**waifu2x-caffe**](https://github.com/lltcggie/waifu2x-caffe/releases)
- [**waifu2x-converter-cpp**](https://github.com/DeadSix27/waifu2x-converter-cpp/releases)
- [**waifu2x-ncnn-vulkan**](https://github.com/nihui/waifu2x-ncnn-vulkan)
- [**Anime4K**](https://github.com/bloc97/Anime4K)
- [**srmd-ncnn-vulkan**](https://github.com/nihui/srmd-ncnn-vulkan)
- [**Anime4KCPP**](https://github.com/TianZerL/Anime4KCPP)
## Recent Changes
### 2.10.0 (August 16, 2019)
### 4.0.0 (May 5, 2020)
- **Added support for [Anime4K](https://github.com/bloc97/Anime4K)**
- Added internationalization support
- Added language zh_CN (简体中文)
- Language will change automatically according to system locale settings
- Added support for [Anime4KCPP](https://github.com/TianZerL/Anime4KCPP) in replacement for Anime4K (Java)
- Driver-specific settings can now be specified in the command line by specifying them after a `--`
- All driver-specific settings are parsed by the corresponding driver
- Modularized driver wrappers in Video2X
- Cleaned up some clutters in the code
### 2.9.0 (July 27, 2019)
### 3.2.0 (April 26, 2020)
- Changed file handling method from `os` to `pathlib`
- Removed f_string dependency and support for legacy versions of Python
- Organized file import statements
- Added support for [SRMD-NCNN-Vulkan](https://github.com/nihui/srmd-ncnn-vulkan)
### 2.8.1 (July 9, 2019)
### 3.1.0 (February 26, 2020)
- Added automatic pixel format detection
- Added automatic color bit depth detection
- Removed the redundant layer of multi-threading since multi-process has to be implemented for launching Windows PE files in sub-processes
- Added support for graceful exit upon `KeyboardInterrupt` or termination signals
- Other minor improvements such as replacing `' '.join(execute)` with `shlex.join(execute)`
### 2.8.0 (June 25, 2019)
### Setup Script 1.8.0 (May 5, 2020)
- **Added support for [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)**
### Setup Script 1.5.0 (August 16, 2019)
- Added automatic installation support for `Anime4K`
- Added support for Anime4KCPP
## Description
Video2X is an automation software based on waifu2x image enlarging engine. It extracts frames from a video, enlarge it by a number of times without losing any details or quality, keeping lines smooth and edges sharp.
For short: **Video2X enlarges your video without losing details**
For short: **Video2X enlarges your video without losing details**.
Watch for the sharper edges in this screenshot around the shadows:
@@ -63,11 +76,15 @@ Watch for the sharper edges in this screenshot around the shadows:
Clip is from trailer of animated movie "千と千尋の神隠し". Copyright belongs to "株式会社スタジオジブリ (STUDIO GHIBLI INC.)". Will delete immediately if use of clip is in violation of copyright.
## Screenshot
## Screenshots
![video2x_gui](https://user-images.githubusercontent.com/21986859/63219691-ee478400-c166-11e9-8c15-dcf59228bd96.png)
### Video2X GUI
![screenshot](https://user-images.githubusercontent.com/21986859/40265170-39c0caae-5b01-11e8-8371-8b6c24769639.png)
![Video2X GUI Screenshot](https://user-images.githubusercontent.com/21986859/81157039-19346b00-8f76-11ea-965f-4d7edf6a818e.png)
### Video2X CLI
![Video2X CLI Screenshot](https://user-images.githubusercontent.com/21986859/81039711-4fe88380-8e99-11ea-9846-175f72100a76.png)
---
@@ -81,9 +98,9 @@ You can find all detailed user-facing and developer-facing documentations in the
For those who want a detailed walk-through of how to use `Video2X`, you can head to the [Step-By-Step Tutorial](https://github.com/k4yt3x/video2x/wiki/Step-By-Step-Tutorial) wiki page. It includes almost every step you need to perform in order to enlarge your first video.
### [Waifu2X Drivers](https://github.com/k4yt3x/video2x/wiki/Waifu2X-Drivers)
### [Drivers](https://github.com/k4yt3x/video2x/wiki/Drivers)
Go to the [Waifu2X Drivers](https://github.com/k4yt3x/video2x/wiki/Waifu2X-Drivers) wiki page if you want to see a detailed description on the different types of `waifu2x` drivers implemented by `Video2X`. This wiki page contains detailed difference between different drivers, and how to download and set each of them up for `Video2X`.
Go to the [Drivers](https://github.com/k4yt3x/video2x/wiki/Drivers) wiki page if you want to see a detailed description on the different types of drivers implemented by `Video2X`. This wiki page contains detailed difference between different drivers, and how to download and set each of them up for `Video2X`.
### [Q&A](https://github.com/k4yt3x/video2x/wiki/Q&A)
@@ -95,18 +112,20 @@ If you have any questions, first try visiting our [Q&A](https://github.com/k4yt3
### Prerequisites
- **Python 3**
- **Python 3.8**
Download: https://www.python.org/downloads/windows/
- **FFmpeg Windows Build**
Download: https://ffmpeg.org/download.html
- **waifu2x-caffe** (for Nvidia CUDA/CUDNN)
- **waifu2x-caffe** (designed for Nvidia CUDA/cuDNN)
Download: https://github.com/lltcggie/waifu2x-caffe/releases
- **waifu2x-converter-cpp** (required for AMD, OpenCL and OpenGL processing)
- **waifu2x-converter-cpp**
Download: https://github.com/DeadSix27/waifu2x-converter-cpp/releases
- **waifu2x-ncnn-vulkan**
Download: https://github.com/nihui/waifu2x-ncnn-vulkan/releases
- **Anime4K**
Download: https://github.com/bloc97/Anime4K/releases
- **Anime4KCPP**
Download: https://github.com/TianZerL/Anime4KCPP/releases
- **srmd-ncnn-vulkan**
Download: https://github.com/nihui/srmd-ncnn-vulkan/releases
### Installing Dependencies
@@ -114,12 +133,12 @@ First, clone the video2x repository.
```shell
git clone https://github.com/k4yt3x/video2x.git
cd video2x/bin
cd video2x/src
```
Then you may run the `video2x_setup.py` script to install and configure the dependencies automatically. This script is designed and tested on Windows 10.
This script will install the newest version of `ffmpeg`, any one or all `waifu2x-caffe`, `waifu2x-converter-cpp`, and `waifu2x-ncnn-vulkan` to `%LOCALAPPDATA%\\video2x` and all required python libraries.
This script will install the newest version of `ffmpeg`, and all upscaling drivers to `%LOCALAPPDATA%\\video2x` and all required python libraries.
```shell
python video2x_setup.py
@@ -133,104 +152,78 @@ Then you'll need to install python dependencies before start using video2x. Inst
pip install -r requirements.txt
```
**Note that all command line arguments/options overwrite configuration file settings.**
### Sample Videos
If you can't find a video clip to begin with, or if you want to see a before-after comparison, we have prepared some sample clips for you. The quick start guide down below will also be based on the name of the sample clips.
![sample_video](https://user-images.githubusercontent.com/21986859/52905766-d5512b00-3236-11e9-9aea-077636539679.png)
- [Sample Video Original (240P) 1.7MB](https://files.flexio.org/Resources/Videos/sample_input.mp4)
- [Sample Video Upscaled (1080P) 4.8MB](https://files.flexio.org/Resources/Videos/sample_output.mp4)
- [Sample Video Original (240P) 1.7MB](https://files.k4yt3x.com/Resources/Videos/sample_input.mp4)
- [Sample Video Upscaled (1080P) 4.8MB](https://files.k4yt3x.com/Resources/Videos/sample_output.mp4)
Clip is from anime "さくら荘のペットな彼女". Copyright belongs to "株式会社アニプレックス (Aniplex Inc.)". Will delete immediately if use of clip is in violation of copyright.
### Nvidia CUDA (waifu2x-caffe)
### Basic Upscale Example
Enlarge the video to 1920x1080 using CUDA. You may also use the `-r/--ratio` option.
This example command below uses `waifu2x-caffe` to enlarge the video `sample-input.mp4` two double its original size.
```shell
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu --width=1920 --height=1080
python video2x.py -i sample-input.mp4 -o sample-output.mp4 -r 2 -d waifu2x_caffe
```
### Nvidia CUDNN
### Advanced Upscale Example
Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option.
If you would like to tweak engine-specific settings, either specify the corresponding argument after `--`, or edit the corresponding field in the configuration file `video2x.yaml`. **Command line arguments will overwrite default values in the config file.**
This example below adds enables TTA for `waifu2x-caffe`.
```shell
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cudnn --width=1920 --height=1080
python video2x.py -i sample-input.mp4 -o sample-output.mp4 -r 2 -d waifu2x_caffe -- --tta 1
```
### AMD or Nvidia (waifu2x-converter-cpp OpenCL)
Enlarge the video by 2 times using OpenCL. Note that `waifu2x-converter-cpp` doesn't support width and height. You'll also have to explicitly specify that the driver to be used is `waifu2x_converter`.
To see a help page for driver-specific settings, use `-d` to select the driver and append `-- --help` as demonstrated below. This will print all driver-specific settings and descriptions.
```shell
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_converter
```
### AMD or Nvidia (waifu2x-ncnn-vulkan Vulkan)
```shell
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_ncnn_vulkan
```
### CPU
Enlarge the video to 1920x1080 using the CPU. You may also use the `-r/--ratio` option. **waifu2x-based upscalers potentially run much slower than using a GPU, but Anime4K is more CPU-dependant**. The configuration file for this method is similar to the previous methods.
```shell
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080
python video2x.py -d waifu2x_caffe -- --help
```
---
# Full Usage
## General Options
## General Options:
### -h, --help
show this help message and exit
## File Options
### -i INPUT, --input INPUT
Source video file/directory (default: None)
source video file/directory
### -o OUTPUT, --output OUTPUT
Output video file/directory (default: None)
## Upscaling Options
### -m {cpu,gpu,cudnn}, --method {cpu,gpu,cudnn}
Upscaling method (default: gpu)
### -d {waifu2x_caffe,waifu2x_converter}, --driver {waifu2x_caffe,waifu2x_converter}
Waifu2x driver (default: waifu2x_caffe)
### -y MODEL_DIR, --model_dir MODEL_DIR
Folder containing model JSON files
### -t THREADS, --threads THREADS
Number of threads to use for upscaling (default: 5)
output video file/directory
### -c CONFIG, --config CONFIG
Video2X config file location (default: video2x\bin\video2x.json)
video2x config file path
### -b, --batch
Enable batch mode (select all default values to questions)
### -d {waifu2x_caffe,waifu2x_converter_cpp,waifu2x_ncnn_vulkan,srmd_ncnn_vulkan,anime4kcpp}, --driver {waifu2x_caffe,waifu2x_converter_cpp,waifu2x_ncnn_vulkan,srmd_ncnn_vulkan,anime4kcpp}
upscaling driver (default: waifu2x_caffe)
### -p PROCESSES, --processes PROCESSES
number of processes to use for upscaling (default: 1)
### -v, --version
display version, lawful information and exit
## Scaling Options
### --width WIDTH
Output video width
output video width
### --height HEIGHT
Output video height
output video height
### -r RATIO, --ratio RATIO
Scaling ratio
scaling ratio
---
@@ -241,17 +234,18 @@ https://www.gnu.org/licenses/gpl-3.0.txt
![GPLv3 Icon](https://www.gnu.org/graphics/gplv3-127x51.png)
(C) 2018-2019 K4YT3X
(C) 2018-2020 K4YT3X
## Credits
This project relies on the following software and projects.
- [FFmpeg]('https://www.ffmpeg.org/')
- [FFmpeg](https://www.ffmpeg.org/)
- [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe)
- [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp)
- [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)
- [Anime4K](https://github.com/bloc97/Anime4K)
- [srmd-ncnn-vulkan](https://github.com/nihui/srmd-ncnn-vulkan)
- [Anime4KCPP](https://github.com/TianZerL/Anime4KCPP)
## Special Thanks
@@ -260,6 +254,7 @@ Appreciations given to the following code contributors:
- @BrianPetkovsek
- @SAT3LL
## Related Resources
## Related Projects
- [Dandere2x](https://github.com/CardinalPanda/dandere2x): `Dandere2x` is a lossy video upscaler also built around `waifu2x`, but with video compression techniques to shorten the time needed to process a video.
- [Dandere2x](https://github.com/CardinalPanda/dandere2x): A lossy video upscaler also built around `waifu2x`, but with video compression techniques to shorten the time needed to process a video.
- [Waifu2x-Extension-GUI](https://github.com/AaronFeng753/Waifu2x-Extension-GUI): A similar project that focuses more and only on building a better graphical user interface. It is built using C++ and Qt5, and currently only supports the Windows platform.

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-slate

View File

@@ -1,92 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Anime4K Driver
Author: K4YT3X
Date Created: August 15, 2019
Last Modified: August 15, 2019
Description: This class is a high-level wrapper
for Anime4k.
"""
# built-in imports
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class Anime4k:
"""This class communicates with Anime4K engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, waifu2x_settings):
self.waifu2x_settings = waifu2x_settings
self.print_lock = threading.Lock()
def upscale(self, input_directory, output_directory, scale_ratio, upscaler_exceptions, push_strength=None, push_grad_strength=None):
""" Anime4K wrapper
Arguments:
file_in {string} -- input file path
file_out {string} -- output file path
Keyword Arguments:
scale {int} -- scale ratio (default: {None})
push_strength {int} -- residual push strength (default: {None})
push_grad_strength {int} -- residual gradient push strength (default: {None})
Returns:
subprocess.Popen.returncode -- command line return value of execution
"""
try:
# return value is the sum of all execution return codes
return_value = 0
# get a list lof all image files in input_directory
extracted_frame_files = [f for f in input_directory.iterdir() if str(f).lower().endswith('.png') or str(f).lower().endswith('.jpg')]
# upscale each image in input_directory
for image in extracted_frame_files:
execute = [
self.waifu2x_settings['java_path'],
'-jar',
self.waifu2x_settings['anime4k_path'],
str(image.absolute()),
str(output_directory / image.name),
str(scale_ratio)
]
# optional arguments
kwargs = [
'push_strength',
'push_grad_strength'
]
# if optional argument specified, append value to execution list
for arg in kwargs:
if locals()[arg] is not None:
execute.extend([locals([arg])])
self.print_lock.acquire()
Avalon.debug_info(f'Executing: {execute}', )
self.print_lock.release()
return_value += subprocess.run(execute, check=True).returncode
# print thread exiting message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
self.print_lock.release()
# return command execution return code
return return_value
except Exception as e:
upscaler_exceptions.append(e)

View File

@@ -1,369 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Video2X Upscaler
Author: K4YT3X
Date Created: December 10, 2018
Last Modified: August 21, 2019
Dev: SAT3LL
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
(C) 2018-2019 K4YT3X
"""
# local imports
from anime4k import Anime4k
from exceptions import *
from ffmpeg import Ffmpeg
from image_cleaner import ImageCleaner
from waifu2x_caffe import Waifu2xCaffe
from waifu2x_converter import Waifu2xConverter
from waifu2x_ncnn_vulkan import Waifu2xNcnnVulkan
# built-in imports
from fractions import Fraction
import contextlib
import copy
import pathlib
import re
import shutil
import tempfile
import threading
import time
import traceback
# third-party imports
from avalon_framework import Avalon
from tqdm import tqdm
AVAILABLE_DRIVERS = ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
class Upscaler:
""" An instance of this class is a upscaler that will
upscale all images in the given directory.
Raises:
Exception -- all exceptions
ArgumentError -- if argument is not valid
"""
def __init__(self, input_video, output_video, method, waifu2x_settings, ffmpeg_settings):
# mandatory arguments
self.input_video = input_video
self.output_video = output_video
self.method = method
self.waifu2x_settings = waifu2x_settings
self.ffmpeg_settings = ffmpeg_settings
# optional arguments
self.waifu2x_driver = 'waifu2x_caffe'
self.scale_width = None
self.scale_height = None
self.scale_ratio = None
self.model_dir = None
self.threads = 5
self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
self.image_format = 'png'
self.preserve_frames = False
def create_temp_directories(self):
"""create temporary directory
"""
self.extracted_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}')
self.upscaled_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
Avalon.debug_info(f'Upscaled frames are being saved to: {self.upscaled_frames}')
def cleanup_temp_directories(self):
"""delete temp directories when done
"""
if not self.preserve_frames:
for directory in [self.extracted_frames, self.upscaled_frames]:
try:
# avalon framework cannot be used if python is shutting down
# therefore, plain print is used
print(f'Cleaning up cache directory: {directory}')
shutil.rmtree(directory)
except (OSError, FileNotFoundError):
print(f'Unable to delete: {directory}')
traceback.print_exc()
def _check_arguments(self):
# check if arguments are valid / all necessary argument
# values are specified
if not self.input_video:
raise ArgumentError('You need to specify the video to process')
elif (not self.scale_width or not self.scale_height) and not self.scale_ratio:
raise ArgumentError('You must specify output video width and height or upscale factor')
elif not self.output_video:
raise ArgumentError('You need to specify the output video name')
elif not self.method:
raise ArgumentError('You need to specify the enlarging processing unit')
def _progress_bar(self, extracted_frames_directories):
""" This method prints a progress bar
This method prints a progress bar by keeping track
of the amount of frames in the input directory
and the output directory. This is originally
suggested by @ArmandBernard.
"""
# get number of extracted frames
self.total_frames = 0
for directory in extracted_frames_directories:
self.total_frames += len([f for f in directory.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
with tqdm(total=self.total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
# tqdm update method adds the value to the progress
# bar instead of setting the value. Therefore, a delta
# needs to be calculated.
previous_cycle_frames = 0
while not self.progress_bar_exit_signal:
with contextlib.suppress(FileNotFoundError):
self.total_frames_upscaled = len([f for f in self.upscaled_frames.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
delta = self.total_frames_upscaled - previous_cycle_frames
previous_cycle_frames = self.total_frames_upscaled
# if upscaling is finished
if self.total_frames_upscaled >= self.total_frames:
return
# adds the delta into the progress bar
progress_bar.update(delta)
time.sleep(1)
def _upscale_frames(self):
""" Upscale video frames with waifu2x-caffe
This function upscales all the frames extracted
by ffmpeg using the waifu2x-caffe binary.
Arguments:
w2 {Waifu2x Object} -- initialized waifu2x object
"""
# progress bar thread exit signal
self.progress_bar_exit_signal = False
# create a container for exceptions in threads
# if this thread is not empty, then an exception has occured
self.upscaler_exceptions = []
# initialize waifu2x driver
drivers = AVAILABLE_DRIVERS
if self.waifu2x_driver not in drivers:
raise UnrecognizedDriverError(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
# it's easier to do multi-threading with waifu2x_converter
# the number of threads can be passed directly to waifu2x_converter
if self.waifu2x_driver == 'waifu2x_converter':
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],))
progress_bar.start()
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads, self.image_format, self.upscaler_exceptions)
for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', str(image))
(self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
self.progress_bar_exit_signal = True
progress_bar.join()
return
# drivers that are to be multi-threaded by video2x
else:
# create a container for all upscaler threads
upscaler_threads = []
# list all images in the extracted frames
frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file]
# if we have less images than threads,
# create only the threads necessary
if len(frames) < self.threads:
self.threads = len(frames)
# create a directory for each thread and append directory
# name into a list
thread_pool = []
thread_directories = []
for thread_id in range(self.threads):
thread_directory = self.extracted_frames / str(thread_id)
thread_directories.append(thread_directory)
# delete old directories and create new directories
if thread_directory.is_dir():
shutil.rmtree(thread_directory)
thread_directory.mkdir(parents=True, exist_ok=True)
# append directory path into list
thread_pool.append((thread_directory, thread_id))
# evenly distribute images into each directory
# until there is none left in the directory
for image in frames:
# move image
image.rename(thread_pool[0][0] / image.name)
# rotate list
thread_pool = thread_pool[-1:] + thread_pool[:-1]
# create threads and start them
for thread_info in thread_pool:
# create a separate w2 instance for each thread
if self.waifu2x_driver == 'waifu2x_caffe':
w2 = Waifu2xCaffe(copy.deepcopy(self.waifu2x_settings), self.method, self.model_dir, self.bit_depth)
if self.scale_ratio:
thread = threading.Thread(target=w2.upscale,
args=(thread_info[0],
self.upscaled_frames,
self.scale_ratio,
False,
False,
self.image_format,
self.upscaler_exceptions))
else:
thread = threading.Thread(target=w2.upscale,
args=(thread_info[0],
self.upscaled_frames,
False,
self.scale_width,
self.scale_height,
self.image_format,
self.upscaler_exceptions))
# if the driver being used is waifu2x_ncnn_vulkan
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings))
thread = threading.Thread(target=w2.upscale,
args=(thread_info[0],
self.upscaled_frames,
self.scale_ratio,
self.upscaler_exceptions))
# if the driver being used is anime4k
elif self.waifu2x_driver == 'anime4k':
w2 = Anime4k(copy.deepcopy(self.waifu2x_settings))
thread = threading.Thread(target=w2.upscale,
args=(thread_info[0],
self.upscaled_frames,
self.scale_ratio,
self.upscaler_exceptions))
# create thread
thread.name = thread_info[1]
# add threads into the pool
upscaler_threads.append(thread)
# start progress bar in a different thread
progress_bar = threading.Thread(target=self._progress_bar, args=(thread_directories,))
progress_bar.start()
# create the clearer and start it
Avalon.debug_info('Starting upscaled image cleaner')
image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_threads))
image_cleaner.start()
# start all threads
for thread in upscaler_threads:
thread.start()
# wait for threads to finish
for thread in upscaler_threads:
thread.join()
# upscaling done, kill the clearer
Avalon.debug_info('Killing upscaled image cleaner')
image_cleaner.stop()
self.progress_bar_exit_signal = True
if len(self.upscaler_exceptions) != 0:
raise(self.upscaler_exceptions[0])
def run(self):
"""Main controller for Video2X
This function controls the flow of video conversion
and handles all necessary functions.
"""
# parse arguments for waifu2x
# check argument sanity
self._check_arguments()
# convert paths to absolute paths
self.input_video = self.input_video.absolute()
self.output_video = self.output_video.absolute()
# initialize objects for ffmpeg and waifu2x-caffe
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
# extract frames from video
fm.extract_frames(self.input_video, self.extracted_frames)
Avalon.info('Reading video information')
video_info = fm.get_video_info(self.input_video)
# analyze original video with ffprobe and retrieve framerate
# width, height = info['streams'][0]['width'], info['streams'][0]['height']
# find index of video stream
video_stream_index = None
for stream in video_info['streams']:
if stream['codec_type'] == 'video':
video_stream_index = stream['index']
break
# exit if no video stream found
if video_stream_index is None:
Avalon.error('Aborting: No video stream found')
raise StreamNotFoundError('no video stream found')
# get average frame rate of video stream
framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate']))
fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt']
# get a dict of all pixel formats and corresponding bit depth
pixel_formats = fm.get_pixel_formats()
try:
self.bit_depth = pixel_formats[fm.pixel_format]
except KeyError:
Avalon.error(f'Unsupported pixel format: {fm.pixel_format}')
raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}')
Avalon.info(f'Framerate: {framerate}')
# width/height will be coded width/height x upscale factor
if self.scale_ratio:
original_width = video_info['streams'][video_stream_index]['width']
original_height = video_info['streams'][video_stream_index]['height']
self.scale_width = int(self.scale_ratio * original_width)
self.scale_height = int(self.scale_ratio * original_height)
# upscale images one by one using waifu2x
Avalon.info('Starting to upscale extracted images')
self._upscale_frames()
Avalon.info('Upscaling completed')
# frames to Video
Avalon.info('Converting extracted frames into video')
# use user defined output size
fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames)
Avalon.info('Conversion completed')
# migrate audio tracks and subtitles
Avalon.info('Migrating audio tracks and subtitles to upscaled video')
fm.migrate_audio_tracks_subtitles(self.input_video, self.output_video, self.upscaled_frames)

View File

@@ -1,102 +0,0 @@
{
"waifu2x_caffe": {
"waifu2x_caffe_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe",
"input_extention_list": null,
"output_extention": null,
"mode": "noise_scale",
"scale_ratio": null,
"scale_width": null,
"scale_height": null,
"noise_level": 3,
"process": "gpu",
"crop_size": 128,
"output_quality": -1,
"output_depth": 8,
"batch_size": 1,
"gpu": 0,
"tta": 0,
"input_path": null,
"output_path": null,
"model_dir": null,
"crop_w": null,
"crop_h": null
},
"waifu2x_converter": {
"waifu2x_converter_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-converter-cpp",
"output-format": null,
"png-compression": null,
"image-quality": null,
"block-size": null,
"disable-gpu": null,
"force-OpenCL": null,
"processor": null,
"jobs": null,
"model-dir": null,
"scale-ratio": null,
"noise-level": 3,
"mode": "noise-scale",
"silent": true,
"output": null,
"input": null
},
"waifu2x_ncnn_vulkan": {
"waifu2x_ncnn_vulkan_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-ncnn-vulkan\\waifu2x-ncnn-vulkan.exe",
"v": null,
"i": null,
"o": null,
"n": 2,
"s": 2,
"t": 400,
"m": "models-cunet",
"g": 0,
"j": "1:2:2"
},
"anime4k": {
"anime4k_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\anime4k\\Anime4K.jar",
"java_path": "C:\\Program Files\\Java\\jdk-12.0.2\\bin\\java.exe"
},
"ffmpeg": {
"ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-latest-win64-static\\bin",
"video_to_frames": {
"output_options": {
"-qscale:v": null,
"-pix_fmt": "rgba64be"
},
"-hwaccel": "auto",
"-y": true
},
"frames_to_video": {
"input_options": {
"-qscale:v": null,
"-qscale:a": null,
"-f": "image2"
},
"output_options": {
"-vcodec": "libx264",
"-crf": 17,
"-b:v": null,
"-pix_fmt": null
},
"-hwaccel": "auto",
"-y": true
},
"migrating_tracks": {
"output_options": {
"-map": [
"0:v:0?",
"1?",
"-1:v?"
],
"-c": "copy",
"-pix_fmt": null
},
"-hwaccel": "auto",
"-y": true
}
},
"video2x": {
"video2x_cache_directory": null,
"image_format": "png",
"preserve_frames": false
}
}

View File

@@ -1,445 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
__ __ _ _ ___ __ __
\ \ / / (_) | | |__ \ \ \ / /
\ \ / / _ __| | ___ ___ ) | \ V /
\ \/ / | | / _` | / _ \ / _ \ / / > <
\ / | | | (_| | | __/ | (_) | / /_ / . \
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
Name: Video2X Controller
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: August 29, 2019
Dev: BrianPetkovsek
Dev: SAT3LL
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
(C) 2018-2019 K4YT3X
Video2X is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Video2X is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Description: Video2X is an automation software based on waifu2x image
enlarging engine. It extracts frames from a video, enlarge it by a
number of times without losing any details or quality, keeping lines
smooth and edges sharp.
"""
# local imports
from exceptions import *
from upscaler import AVAILABLE_DRIVERS
from upscaler import Upscaler
# built-in imports
import argparse
import contextlib
import json
import pathlib
import re
import shutil
import sys
import tempfile
import time
import traceback
# third-party imports
from avalon_framework import Avalon
import GPUtil
import psutil
VERSION = '2.10.0'
LEGAL_INFO = f'''Video2X Version: {VERSION}
Author: K4YT3X
License: GNU GPL v3
Github Page: https://github.com/k4yt3x/video2x
Contact: k4yt3x@k4yt3x.com'''
LOGO = r'''
__ __ _ _ ___ __ __
\ \ / / (_) | | |__ \ \ \ / /
\ \ / / _ __| | ___ ___ ) | \ V /
\ \/ / | | / _` | / _ \ / _ \ / / > <
\ / | | | (_| | | __/ | (_) | / /_ / . \
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
'''
# each thread might take up to 2.5 GB during initialization.
# (system memory, not to be confused with GPU memory)
SYS_MEM_PER_THREAD = 2.5
GPU_MEM_PER_THREAD = 3.5
def process_arguments():
"""Processes CLI arguments
This function parses all arguments
This allows users to customize options
for the output video.
"""
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# video options
file_options = parser.add_argument_group('File Options')
file_options.add_argument('-i', '--input', type=pathlib.Path, help='source video file/directory', action='store')
file_options.add_argument('-o', '--output', type=pathlib.Path, help='output video file/directory', action='store')
# upscaler options
upscaler_options = parser.add_argument_group('Upscaler Options')
upscaler_options.add_argument('-m', '--method', help='upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'])
upscaler_options.add_argument('-d', '--driver', help='upscaling driver', action='store', default='waifu2x_caffe', choices=AVAILABLE_DRIVERS)
upscaler_options.add_argument('-y', '--model_dir', type=pathlib.Path, help='directory containing model JSON files', action='store')
upscaler_options.add_argument('-t', '--threads', help='number of threads to use for upscaling', action='store', type=int, default=1)
upscaler_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.json')
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
# scaling options
scaling_options = parser.add_argument_group('Scaling Options')
scaling_options.add_argument('--width', help='output video width', action='store', type=int)
scaling_options.add_argument('--height', help='output video height', action='store', type=int)
scaling_options.add_argument('-r', '--ratio', help='scaling ratio', action='store', type=float)
# extra options
extra_options = parser.add_argument_group('Extra Options')
extra_options.add_argument('-v', '--version', help='display version, lawful information and exit', action='store_true')
# parse arguments
return parser.parse_args()
def print_logo():
"""print video2x logo"""
print(LOGO)
print(f'\n{"Video2X Video Enlarger".rjust(40, " ")}')
print(f'\n{Avalon.FM.BD}{f"Version {VERSION}".rjust(36, " ")}{Avalon.FM.RST}\n')
def check_memory():
""" Check usable system memory
Warn the user if insufficient memory is available for
the number of threads that the user have chosen.
"""
memory_status = []
# get system available memory
system_memory_available = psutil.virtual_memory().available / (1024 ** 3)
memory_status.append(('system', system_memory_available))
# check if Nvidia-smi is available
# GPUtil requires nvidia-smi.exe to interact with GPU
if args.method == 'gpu' or args.method == 'cudnn':
if not (shutil.which('nvidia-smi') or
pathlib.Path(r'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe').is_file()):
# Nvidia System Management Interface not available
Avalon.warning('Nvidia-smi not available, skipping available memory check')
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
else:
with contextlib.suppress(ValueError):
# "0" is GPU ID. Both waifu2x drivers use the first GPU available, therefore only 0 makes sense
gpu_memory_available = (GPUtil.getGPUs()[0].memoryTotal - GPUtil.getGPUs()[0].memoryUsed) / 1024
memory_status.append(('GPU', gpu_memory_available))
# go though each checkable memory type and check availability
for memory_type, memory_available in memory_status:
if memory_type == 'system':
mem_per_thread = SYS_MEM_PER_THREAD
else:
mem_per_thread = GPU_MEM_PER_THREAD
# if user doesn't even have enough memory to run even one thread
if memory_available < mem_per_thread:
Avalon.warning(f'You might have insufficient amount of {memory_type} memory available to run this program ({memory_available} GB)')
Avalon.warning('Proceed with caution')
if args.threads > 1:
if Avalon.ask('Reduce number of threads to avoid crashing?', default=True, batch=args.batch):
args.threads = 1
# if memory available is less than needed, warn the user
elif memory_available < (mem_per_thread * args.threads):
Avalon.warning(f'Each waifu2x-caffe thread will require up to {SYS_MEM_PER_THREAD} GB of system memory')
Avalon.warning(f'You demanded {args.threads} threads to be created, but you only have {round(memory_available, 4)} GB {memory_type} memory available')
Avalon.warning(f'{mem_per_thread * args.threads} GB of {memory_type} memory is recommended for {args.threads} threads')
Avalon.warning(f'With your current amount of {memory_type} memory available, {int(memory_available // mem_per_thread)} threads is recommended')
# ask the user if he / she wants to change to the recommended
# number of threads
if Avalon.ask('Change to the recommended value?', default=True, batch=args.batch):
args.threads = int(memory_available // mem_per_thread)
else:
Avalon.warning('Proceed with caution')
def read_config(config_file):
""" Reads configuration file
Returns a dictionary read by JSON.
"""
with open(config_file, 'r') as raw_config:
config = json.load(raw_config)
return config
def absolutify_paths(config):
""" Check to see if paths to binaries are absolute
This function checks if paths to binary files are absolute.
If not, then absolutify the path.
Arguments:
config {dict} -- configuration file dictionary
Returns:
dict -- configuration file dictionary
"""
current_directory = pathlib.Path(sys.argv[0]).parent.absolute()
# check waifu2x-caffe path
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
config['waifu2x_caffe']['waifu2x_caffe_path'] = current_directory / config['waifu2x_caffe']['waifu2x_caffe_path']
# check waifu2x-converter-cpp path
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
config['waifu2x_converter']['waifu2x_converter_path'] = current_directory / config['waifu2x_converter']['waifu2x_converter_path']
# check waifu2x_ncnn_vulkan path
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = current_directory / config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path']
# check anime4k path
if not re.match('^[a-z]:', config['anime4k']['anime4k_path'], re.IGNORECASE):
config['anime4k']['anime4k_path'] = current_directory / config['anime4k']['anime4k_path']
# check ffmpeg path
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
# check video2x cache path
if config['video2x']['video2x_cache_directory']:
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
config['video2x']['video2x_cache_directory'] = current_directory / config['video2x']['video2x_cache_directory']
return config
# /////////////////// Execution /////////////////// #
# this is not a library
if __name__ != '__main__':
Avalon.error('This file cannot be imported')
raise ImportError(f'{__file__} cannot be imported')
# print video2x logo
print_logo()
# process CLI arguments
args = process_arguments()
# display version and lawful informaition
if args.version:
print(LEGAL_INFO)
exit(0)
# arguments sanity check
if not args.input:
Avalon.error('You must specify input video file/directory path')
raise ArgumentError('input video path not specified')
if not args.output:
Avalon.error('You must specify output video file/directory path')
raise ArgumentError('output video path not specified')
if (args.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and args.width and args.height:
Avalon.error('Selected driver accepts only scaling ratio')
raise ArgumentError('selected driver supports only scaling ratio')
if args.driver == 'waifu2x_ncnn_vulkan' and (args.ratio > 2 or not args.ratio.is_integer()):
Avalon.error('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
if (args.width or args.height) and args.ratio:
Avalon.error('You can only specify either scaling ratio or output width and height')
raise ArgumentError('both scaling ration and width/height specified')
if (args.width and not args.height) or (not args.width and args.height):
Avalon.error('You must specify both width and height')
raise ArgumentError('only one of width or height is specified')
# check available memory if driver is waifu2x-based
if args.driver in ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan']:
check_memory()
# anime4k runs significantly faster with more threads
if args.driver == 'anime4k' and args.threads <= 1:
Avalon.warning('Anime4K runs significantly faster with more threads')
if Avalon.ask('Use more threads of Anime4K?', True):
while True:
try:
threads = Avalon.gets('Amount of threads to use [5]: ')
args.threads = int(threads)
break
except ValueError:
if threads == '':
args.threads = 5
break
else:
Avalon.error(f'{threads} is not a valid integer')
# read configurations from JSON
config = read_config(args.config)
config = absolutify_paths(config)
# load waifu2x configuration
if args.driver == 'waifu2x_caffe':
waifu2x_settings = config['waifu2x_caffe']
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
Avalon.error('Specified waifu2x-caffe directory doesn\'t exist')
Avalon.error('Please check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
elif args.driver == 'waifu2x_converter':
waifu2x_settings = config['waifu2x_converter']
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist')
Avalon.error('Please check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
elif args.driver == 'waifu2x_ncnn_vulkan':
waifu2x_settings = config['waifu2x_ncnn_vulkan']
if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file():
Avalon.error('Specified waifu2x_ncnn_vulkan directory doesn\'t exist')
Avalon.error('Please check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
elif args.driver == 'anime4k':
waifu2x_settings = config['anime4k']
if not pathlib.Path(waifu2x_settings['anime4k_path']).is_file():
Avalon.error('Specified anime4k directory doesn\'t exist')
Avalon.error('Please check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['anime4k_path'])
# read FFmpeg configuration
ffmpeg_settings = config['ffmpeg']
# load video2x settings
image_format = config['video2x']['image_format'].lower()
preserve_frames = config['video2x']['preserve_frames']
# load cache directory
if isinstance(config['video2x']['video2x_cache_directory'], str):
video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory'])
else:
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
Avalon.error('Specified cache directory is a file/link')
raise FileExistsError('Specified cache directory is a file/link')
elif not video2x_cache_directory.exists():
# if destination file is a file or a symbolic link
Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
# try creating the cache directory
if Avalon.ask('Create directory?', default=True, batch=args.batch):
try:
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
Avalon.info(f'{video2x_cache_directory} created')
# there can be a number of exceptions here
# PermissionError, FileExistsError, etc.
# therefore, we put a catch-them-all here
except Exception as e:
Avalon.error(f'Unable to create {video2x_cache_directory}')
Avalon.error('Aborting...')
raise e
else:
raise FileNotFoundError('Could not create cache directory')
# start execution
try:
# start timer
begin_time = time.time()
# if input specified is a single file
if args.input.is_file():
# upscale single video file
Avalon.info(f'Upscaling single video file: {args.input}')
# check for input output format mismatch
if args.output.is_dir():
Avalon.error('Input and output path type mismatch')
Avalon.error('Input is single file but output is directory')
raise Exception('input output path type mismatch')
if not re.search(r'.*\..*$', str(args.output)):
Avalon.error('No suffix found in output file path')
Avalon.error('Suffix must be specified for FFmpeg')
raise Exception('No suffix specified')
upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
# set optional options
upscaler.waifu2x_driver = args.driver
upscaler.scale_width = args.width
upscaler.scale_height = args.height
upscaler.scale_ratio = args.ratio
upscaler.model_dir = args.model_dir
upscaler.threads = args.threads
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.create_temp_directories()
upscaler.run()
upscaler.cleanup_temp_directories()
# if input specified is a directory
elif args.input.is_dir():
# upscale videos in a directory
Avalon.info(f'Upscaling videos in directory: {args.input}')
# make output directory if it doesn't exist
args.output.mkdir(parents=True, exist_ok=True)
for input_video in [f for f in args.input.iterdir() if f.is_file()]:
output_video = args.output / input_video.name
upscaler = Upscaler(input_video=input_video, output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
# set optional options
upscaler.waifu2x_driver = args.driver
upscaler.scale_width = args.width
upscaler.scale_height = args.height
upscaler.scale_ratio = args.ratio
upscaler.model_dir = args.model_dir
upscaler.threads = args.threads
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.create_temp_directories()
upscaler.run()
upscaler.cleanup_temp_directories()
else:
Avalon.error('Input path is neither a file nor a directory')
raise FileNotFoundError(f'{args.input} is neither file nor directory')
Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds')
except Exception:
Avalon.error('An exception has occurred')
traceback.print_exc()
finally:
# remove Video2X cache directory
with contextlib.suppress(FileNotFoundError):
if not preserve_frames:
shutil.rmtree(video2x_cache_directory)

View File

@@ -1,416 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Video2x GUI
Author: K4YT3X
Date Created: July 27, 2019
Last Modified: August 17, 2019
Description: GUI for Video2X
"""
# local imports
from exceptions import *
from upscaler import Upscaler
# built-in imports
from tkinter import *
from tkinter import messagebox
from tkinter import ttk
from tkinter.filedialog import *
import json
import pathlib
import tempfile
import threading
import time
VERSION = '1.1.1'
LEGAL_INFO = f'''Video2X GUI Version: {VERSION}
Author: K4YT3X
License: GNU GPL v3
Github Page: https://github.com/k4yt3x/video2x
Contact: k4yt3x@k4yt3x.com'''
# global static variables
AVAILABLE_METHODS = {
'GPU': 'gpu',
'CUDNN': 'cudnn',
'CPU': 'cpu'
}
AVAILABLE_DRIVERS = {
'Waifu2X Caffe': 'waifu2x_caffe',
'Waifu2X Converter CPP': 'waifu2x_converter',
'Waifu2x NCNN Vulkan': 'waifu2x_ncnn_vulkan',
'Anime4K': 'anime4k'
}
IMAGE_FORMATS = {'PNG', 'JPG'}
class Video2xGui():
def __init__(self):
self.running = False
# create main window
self.main_window = Tk()
self.main_window.title(f'Video2X GUI {VERSION}')
self.main_frame = Frame()
self.main_frame.pack(fill=BOTH, expand=True)
# add menu bar
self.menu_bar = Menu(self.main_frame)
# file menu
self.file_menu = Menu(self.menu_bar, tearoff=0)
self.file_menu.add_command(label='Exit', command=self.main_frame.quit)
self.menu_bar.add_cascade(label='File', menu=self.file_menu)
# help menu
self.help_menu = Menu(self.menu_bar, tearoff=0)
self.help_menu.add_command(label='About', command=self._display_help)
self.menu_bar.add_cascade(label='Help', menu=self.help_menu)
self.main_window.config(menu=self.menu_bar)
# file frame
self.file_frame = Frame(self.main_frame)
self.file_frame.pack(fill=X, padx=5, pady=5, expand=True)
# input file
self.input_file = StringVar()
label_text = StringVar()
label_text.set('Input File')
Label(self.file_frame, textvariable=label_text, relief=RIDGE, width=10).grid(row=0, column=0, padx=5, pady=5, sticky=W)
Entry(self.file_frame, textvariable=self.input_file, width=60).grid(row=0, column=1, padx=5, pady=5, sticky=W)
Button(self.file_frame, text='Select', command=self._select_input).grid(row=0, column=2, padx=5, pady=5, sticky=W)
# output file
self.output_file = StringVar()
label_text = StringVar()
label_text.set('Output File')
Label(self.file_frame, textvariable=label_text, relief=RIDGE, width=10).grid(row=1, column=0, padx=5, pady=5, sticky=W)
Entry(self.file_frame, textvariable=self.output_file, width=60).grid(row=1, column=1, padx=5, pady=5, sticky=W)
Button(self.file_frame, text='Select', command=self._select_output).grid(row=1, column=2, padx=5, pady=5, sticky=W)
# options
self.options_frame = Frame()
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
self.options_frame.pack(fill=X, padx=5, pady=5, expand=True)
self.options_left = Frame(self.options_frame)
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
self.options_left.grid(row=0, column=0, padx=5, pady=5, sticky=N)
# width
self.width = IntVar()
# self.width.set(1920)
Label(self.options_left, text='Width', relief=RIDGE, width=15).grid(row=0, column=0, padx=2, pady=3)
width_field = Entry(self.options_left, textvariable=self.width)
width_field.grid(row=0, column=1, padx=2, pady=3, sticky=W)
# height
self.height = IntVar()
# self.height.set(1080)
Label(self.options_left, text='Height', relief=RIDGE, width=15).grid(row=1, column=0, padx=2, pady=3)
height_field = Entry(self.options_left, textvariable=self.height)
height_field.grid(row=1, column=1, padx=2, pady=3, sticky=W)
# scale ratio
self.scale_ratio = DoubleVar()
# self.scale_ratio.set(2.0)
Label(self.options_left, text='Scale Ratio', relief=RIDGE, width=15).grid(row=2, column=0, padx=2, pady=3)
scale_ratio_field = Entry(self.options_left, textvariable=self.scale_ratio)
scale_ratio_field.grid(row=2, column=1, padx=2, pady=3, sticky=W)
# image format
self.image_format = StringVar(self.options_left)
self.image_format.set('PNG')
Label(self.options_left, text='Image Format', relief=RIDGE, width=15).grid(row=3, column=0, padx=2, pady=3)
image_format_menu = OptionMenu(self.options_left, self.image_format, *IMAGE_FORMATS)
image_format_menu.grid(row=3, column=1, padx=2, pady=3, sticky=W)
# options
self.options_right = Frame(self.options_frame)
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
self.options_right.grid(row=0, column=1, padx=5, pady=5, sticky=N)
# threads
self.threads = IntVar()
self.threads.set(1)
Label(self.options_right, text='Threads', relief=RIDGE, width=15).grid(row=0, column=0, padx=2, pady=3)
threads_field = Entry(self.options_right, textvariable=self.threads)
threads_field.grid(row=0, column=1, padx=2, pady=3, sticky=W)
# method
self.method = StringVar(self.options_left)
self.method.set('GPU')
Label(self.options_right, text='Method', relief=RIDGE, width=15).grid(row=1, column=0, padx=2, pady=3)
method_menu = OptionMenu(self.options_right, self.method, *AVAILABLE_METHODS)
method_menu.grid(row=1, column=1, padx=2, pady=3, sticky=W)
# driver
self.driver = StringVar(self.options_left)
self.driver.set('Waifu2X Caffe')
Label(self.options_right, text='Driver', relief=RIDGE, width=15).grid(row=2, column=0, padx=2, pady=3)
driver_menu = OptionMenu(self.options_right, self.driver, *AVAILABLE_DRIVERS)
driver_menu.grid(row=2, column=1, padx=2, pady=3, sticky=W)
# preserve frames
self.preserve_frames = BooleanVar(self.options_left)
self.preserve_frames.set(True)
Label(self.options_right, text='Preserve Frames', relief=RIDGE, width=15).grid(row=3, column=0, padx=2, pady=3)
preserve_frames_menu = OptionMenu(self.options_right, self.preserve_frames, *{True, False})
preserve_frames_menu.grid(row=3, column=1, padx=2, pady=3, sticky=W)
# progress bar
self.progress_bar_frame = Frame()
self.progress_bar_frame.pack(fill=X, padx=5, pady=5, expand=True)
self.progress_bar = ttk.Progressbar(self.progress_bar_frame, orient='horizontal', length=100, mode='determinate')
self.progress_bar.pack(fill=X)
# start button frame
self.start_frame = Frame()
self.start_frame.pack(fill=X, padx=5, pady=5, expand=True)
# start button
self.start_button_text = StringVar()
self.start_button_text.set('Start')
Button(self.start_frame, textvariable=self.start_button_text, command=self._launch_upscaling, width=20).pack(side=RIGHT)
self.main_frame.mainloop()
def _display_help(self):
messagebox.showinfo('About', LEGAL_INFO)
def _launch_upscaling(self):
# prevent launching multiple instances
if self.running:
messagebox.showerror('Error', 'Video2X is already running')
return
# arguments sanity check
if self.input_file.get() == '':
messagebox.showerror('Error', 'You must specify input video file/directory path')
return
if self.output_file.get() == '':
messagebox.showerror('Error', 'You must specify output video file/directory path')
return
if (self.driver.get() in ['Waifu2X Converter CPP', 'Waifu2x NCNN Vulkan', 'Anime4K']) and self.width.get() and self.height.get():
messagebox.showerror('Error', f'Selected driver \"{self.driver.get()}\" accepts only scaling ratio')
return
if self.driver.get() == 'waifu2x_ncnn_vulkan' and (self.scale_ratio.get() > 2 or not self.scale_ratio.get().is_integer()):
messagebox.showerror('Error', 'Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
return
if (self.width.get() or self.height.get()) and self.scale_ratio.get():
messagebox.showerror('Error', 'You can only specify either scaling ratio or output width and height')
return
if (self.width.get() and not self.height.get()) or (not self.width.get() and self.height.get()):
messagebox.showerror('Error', 'You must specify both width and height')
return
if (not self.width.get() or not self.height.get()) and not self.scale_ratio.get():
messagebox.showerror('Error', 'You must specify either output dimensions or scaling ratio')
return
upscale = threading.Thread(target=self._upscale)
upscale.start()
self.running = True
self.start_button_text.set('Running')
def _upscale(self):
# start timer
begin_time = time.time()
# read configuration file
config = read_config('video2x.json')
config = absolutify_paths(config)
input_file = pathlib.Path(self.input_file.get())
output_file = pathlib.Path(self.output_file.get())
driver = AVAILABLE_DRIVERS[self.driver.get()]
if driver == 'waifu2x_caffe':
waifu2x_settings = config['waifu2x_caffe']
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
messagebox.showerror('Error', 'Specified waifu2x-caffe directory doesn\'t exist\nPlease check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
elif driver == 'waifu2x_converter':
waifu2x_settings = config['waifu2x_converter']
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
messagebox.showerror('Error', 'Specified waifu2x-converter-cpp directory doesn\'t exist\nPlease check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
elif driver == 'waifu2x_ncnn_vulkan':
waifu2x_settings = config['waifu2x_ncnn_vulkan']
if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file():
messagebox.showerror('Error', 'Specified waifu2x_ncnn_vulkan directory doesn\'t exist\nPlease check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
elif driver == 'anime4k':
waifu2x_settings = config['anime4k']
if not pathlib.Path(waifu2x_settings['anime4k_path']).is_file():
messagebox.showerror('Error', 'Specified Anime4K directory doesn\'t exist\nPlease check the configuration file settings')
raise FileNotFoundError(waifu2x_settings['anime4k_path'])
# read FFmpeg configuration
ffmpeg_settings = config['ffmpeg']
# load video2x settings
image_format = config['video2x']['image_format'].lower()
preserve_frames = config['video2x']['preserve_frames']
# load cache directory
if isinstance(config['video2x']['video2x_cache_directory'], str):
video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory'])
else:
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
messagebox.showerror('Error', 'Specified cache directory is a file/link')
raise FileExistsError('Specified cache directory is a file/link')
elif not video2x_cache_directory.exists():
# try creating the cache directory
if messagebox.askyesno('Question', f'Specified cache directory {video2x_cache_directory} does not exist\nCreate directory?'):
try:
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
# there can be a number of exceptions here
# PermissionError, FileExistsError, etc.
# therefore, we put a catch-them-all here
except Exception as e:
messagebox.showerror('Error', f'Unable to create {video2x_cache_directory}\nAborting...')
raise e
else:
raise FileNotFoundError('Could not create cache directory')
# load more settings from gui
width = self.width.get()
height = self.height.get()
scale_ratio = self.scale_ratio.get()
image_format = self.image_format.get()
threads = self.threads.get()
method = AVAILABLE_METHODS[self.method.get()]
preserve_frames = self.preserve_frames.get()
self.upscaler = Upscaler(input_video=input_file, output_video=output_file, method=method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
# set optional options
self.upscaler.waifu2x_driver = driver
self.upscaler.scale_width = width
self.upscaler.scale_height = height
self.upscaler.scale_ratio = scale_ratio
self.upscaler.model_dir = None
self.upscaler.threads = threads
self.upscaler.video2x_cache_directory = video2x_cache_directory
self.upscaler.image_format = image_format
self.upscaler.preserve_frames = preserve_frames
# run upscaler
self.upscaler.create_temp_directories()
# start progress bar
progress_bar = threading.Thread(target=self._progress_bar)
progress_bar.start()
# start upscaling
self.upscaler.run()
self.upscaler.cleanup_temp_directories()
# show message when upscaling completes
messagebox.showinfo('Info', f'Upscaling Completed\nTime Taken: {round((time.time() - begin_time), 5)} seconds')
self.progress_bar['value'] = 100
self.running = False
self.start_button_text.set('Start')
def _progress_bar(self):
""" This method prints a progress bar
This method prints a progress bar by keeping track
of the amount of frames in the input directory
and the output directory. This is originally
suggested by @ArmandBernard.
"""
# initialize variables early
self.upscaler.progress_bar_exit_signal = False
self.upscaler.total_frames_upscaled = 0
self.upscaler.total_frames = 1
# initialize progress bar values
self.progress_bar['value'] = 0
while not self.upscaler.progress_bar_exit_signal:
self.progress_bar['value'] = int(100 * self.upscaler.total_frames_upscaled / self.upscaler.total_frames)
time.sleep(1)
def _select_input(self):
self.input_file.set(askopenfilename(title='Select Input File'))
# try to set an output file name automatically
output_file = pathlib.Path(f'{self.input_file.get()}_output.mp4')
output_file_id = 0
while output_file.is_file() and output_file_id <= 10:
output_file = pathlib.Path(f'{self.input_file.get()}_output_{output_file_id}.mp4')
output_file_id += 1
if not output_file.exists():
self.output_file.set(str(output_file))
def _select_output(self):
self.output_file.set(asksaveasfilename(title='Select Output File'))
def read_config(config_file):
""" Reads configuration file
Returns a dictionary read by JSON.
"""
with open(config_file, 'r') as raw_config:
config = json.load(raw_config)
return config
def absolutify_paths(config):
""" Check to see if paths to binaries are absolute
This function checks if paths to binary files are absolute.
If not, then absolutify the path.
Arguments:
config {dict} -- configuration file dictionary
Returns:
dict -- configuration file dictionary
"""
current_directory = pathlib.Path(sys.argv[0]).parent.absolute()
# check waifu2x-caffe path
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
config['waifu2x_caffe']['waifu2x_caffe_path'] = current_directory / config['waifu2x_caffe']['waifu2x_caffe_path']
# check waifu2x-converter-cpp path
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
config['waifu2x_converter']['waifu2x_converter_path'] = current_directory / config['waifu2x_converter']['waifu2x_converter_path']
# check waifu2x_ncnn_vulkan path
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = current_directory / config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path']
# check ffmpeg path
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
# check video2x cache path
if config['video2x']['video2x_cache_directory']:
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
config['video2x']['video2x_cache_directory'] = current_directory / config['video2x']['video2x_cache_directory']
return config
video2x_gui = Video2xGui()

View File

@@ -1,98 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x Caffe Driver
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: August 3, 2019
Description: This class is a high-level wrapper
for waifu2x-caffe.
"""
# built-in imports
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class Waifu2xCaffe:
"""This class communicates with waifu2x cui engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, waifu2x_settings, process, model_dir, bit_depth):
self.waifu2x_settings = waifu2x_settings
self.waifu2x_settings['process'] = process
self.waifu2x_settings['model_dir'] = model_dir
self.waifu2x_settings['output_depth'] = bit_depth
# arguments passed through command line overwrites config file values
self.process = process
self.model_dir = model_dir
self.print_lock = threading.Lock()
def upscale(self, input_directory, output_directory, scale_ratio, scale_width, scale_height, image_format, upscaler_exceptions):
"""This is the core function for WAIFU2X class
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
width {int} -- output video width
height {int} -- output video height
"""
try:
# overwrite config file settings
self.waifu2x_settings['input_path'] = input_directory
self.waifu2x_settings['output_path'] = output_directory
if scale_ratio:
self.waifu2x_settings['scale_ratio'] = scale_ratio
elif scale_width and scale_height:
self.waifu2x_settings['scale_width'] = scale_width
self.waifu2x_settings['scale_height'] = scale_height
self.waifu2x_settings['output_extention'] = image_format
# print thread start message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
self.print_lock.release()
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [str(self.waifu2x_settings['waifu2x_caffe_path'])]
for key in self.waifu2x_settings.keys():
value = self.waifu2x_settings[key]
# is executable key or null or None means that leave this option out (keep default)
if key == 'waifu2x_caffe_path' or value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
execute.append(str(value))
Avalon.debug_info(f'Executing: {execute}')
completed_command = subprocess.run(execute, check=True)
# print thread exiting message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
self.print_lock.release()
# return command execution return code
return completed_command.returncode
except Exception as e:
upscaler_exceptions.append(e)

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x Converter CPP Driver
Author: K4YT3X
Date Created: February 8, 2019
Last Modified: August 3, 2019
Description: This class is a high-level wrapper
for waifu2x-converter-cpp.
"""
# built-in imports
import pathlib
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class Waifu2xConverter:
"""This class communicates with waifu2x cui engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, waifu2x_settings, model_dir):
self.waifu2x_settings = waifu2x_settings
self.waifu2x_settings['model_dir'] = model_dir
self.print_lock = threading.Lock()
def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions):
""" Waifu2x Converter Driver Upscaler
This method executes the upscaling of extracted frames.
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
scale_ratio {int} -- frames' scale ratio
threads {int} -- number of threads
"""
try:
# overwrite config file settings
self.waifu2x_settings['input'] = input_directory
self.waifu2x_settings['output'] = output_directory
self.waifu2x_settings['scale-ratio'] = scale_ratio
self.waifu2x_settings['jobs'] = jobs
self.waifu2x_settings['output-format'] = image_format
# models_rgb must be specified manually for waifu2x-converter-cpp
# if it's not specified in the arguments, create automatically
if self.waifu2x_settings['model-dir'] is None:
self.waifu2x_settings['model-dir'] = pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'models_rgb'
# print thread start message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
self.print_lock.release()
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [str(pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'waifu2x-converter-cpp.exe')]
for key in self.waifu2x_settings.keys():
value = self.waifu2x_settings[key]
# the key doesn't need to be passed in this case
if key == 'waifu2x_converter_path':
continue
# null or None means that leave this option out (keep default)
elif value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is True:
continue
execute.append(str(value))
Avalon.debug_info(f'Executing: {execute}')
return subprocess.run(execute, check=True).returncode
except Exception as e:
upscaler_exceptions.append(e)

View File

@@ -1,93 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x NCNN Vulkan Driver
Author: SAT3LL
Date Created: June 26, 2019
Last Modified: August 3, 2019
Dev: K4YT3X
Description: This class is a high-level wrapper
for waifu2x_ncnn_vulkan.
"""
# built-in imports
import os
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class Waifu2xNcnnVulkan:
"""This class communicates with waifu2x ncnn vulkan engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, waifu2x_settings):
self.waifu2x_settings = waifu2x_settings
# arguments passed through command line overwrites config file values
# waifu2x_ncnn_vulkan can't find its own model directory if its not in the current dir
# so change to it
os.chdir(os.path.join(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'], '..'))
self.print_lock = threading.Lock()
def upscale(self, input_directory, output_directory, scale_ratio, upscaler_exceptions):
"""This is the core function for WAIFU2X class
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
ratio {int} -- output video ratio
"""
try:
# overwrite config file settings
self.waifu2x_settings['i'] = input_directory
self.waifu2x_settings['o'] = output_directory
self.waifu2x_settings['s'] = scale_ratio
# print thread start message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
self.print_lock.release()
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [str(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'])]
for key in self.waifu2x_settings.keys():
value = self.waifu2x_settings[key]
# is executable key or null or None means that leave this option out (keep default)
if key == 'waifu2x_ncnn_vulkan_path' or value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
execute.append(str(value))
Avalon.debug_info(f'Executing: {execute}')
completed_command = subprocess.run(execute, check=True)
# print thread exiting message
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
self.print_lock.release()
# return command execution return code
return completed_command.returncode
except Exception as e:
upscaler_exceptions.append(e)

86
src/build.ps1 Normal file
View File

@@ -0,0 +1,86 @@
<#
Name: Video2X Build Script
Creator: K4YT3X
Date Created: May 6, 2020
Last Modified: May 6, 2020
Description: A PowerShell script that will build Video2X
executable (PE) releases automatically using PyInstaller.
This script is currently only tuned for K4YT3X's environment.
To start a PowerShell session with execution policy bypass
powershell ExecutionPolicy Bypass
#>
# version number
$SCRIPT_VERSION = "1.0.0"
$VIDEO2X_VERSION = "4.0.0"
Write-Host -ForegroundColor White "Video2X Building Script Version $($SCRIPT_VERSION)
Starting to build Video2X release packages"
# build Video2X CLI
Write-Host -ForegroundColor White "`nBuilding Video2X CLI"
pyinstaller --noconfirm --log-level=WARN `
--onefile `
--add-data="wrappers;wrappers" `
--icon="images\video2x.ico" `
video2x.py
# build Video2X GUI
Write-Host -ForegroundColor White "`nBuilding Video2X GUI"
pyinstaller --noconfirm --log-level=WARN `
--onefile `
--add-data="images;images" `
--add-data="locale;locale" `
--add-data="video2x_gui.ui;." `
--add-data="wrappers;wrappers" `
--icon="images\video2x.ico" `
video2x_gui.py
# build setup script
Write-Host -ForegroundColor White "`nBuilding Video2X setup script"
pyinstaller --noconfirm --log-level=WARN `
--onefile `
--icon="images\video2x.ico" `
video2x_setup.py
# remove old builds if found
if (Test-Path "video2x-builds" -PathType any) {
Remove-Item -path "video2x-builds" -recurse
}
# create build directory
New-Item "video2x-builds" -ItemType Directory
# copy files into corresponding builds
# full edition
Write-Host -ForegroundColor White "`nCreating full package"
New-Item "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-full" -ItemType Directory
Copy-Item "dist\video2x.exe" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-full\"
Copy-Item "dist\video2x_gui.exe" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-full\"
Copy-Item -Path "$env:LOCALAPPDATA\video2x" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-full\dependencies" -Recurse
# overwrite paths to relative paths
(Get-Content "video2x.yaml").replace("C:\Users\K4YT3X\AppData\Local\video2x\", "dependencies\") | Set-Content "video2x.yaml.relative"
Move-Item "video2x.yaml.relative" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-full\video2x.yaml"
# light edition
Write-Host -ForegroundColor White "`nCreating light package"
New-Item "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light" -ItemType Directory
Copy-Item "dist\video2x.exe" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light\"
Copy-Item "dist\video2x_gui.exe" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light\"
Copy-Item "dist\video2x_setup.exe" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light\"
Copy-Item "video2x.yaml" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light\"
Copy-Item "requirements.txt" -Destination "video2x-builds\video2x-$($VIDEO2X_VERSION)-win32-light\"
# clean up temporary files
Write-Host -ForegroundColor White "`nDeleting temporary files"
$pathsToRemove = "__pycache__", "build", "dist", "*.spec"
foreach ($path in $pathsToRemove){
Write-Host "Removing path: $($path)"
Remove-Item -path $path -recurse
}
Write-Host -ForegroundColor White "`nBuild script finished"

0
bin/exceptions.py → src/exceptions.py Normal file → Executable file
View File

17
bin/image_cleaner.py → src/image_cleaner.py Normal file → Executable file
View File

@@ -3,10 +3,15 @@
"""
Name: Video2X Image Cleaner
Author: BrianPetkovsek
Author: K4YT3X
Date Created: March 24, 2019
Last Modified: July 27, 2019
Editor: K4YT3X
Last Modified: March 23, 2020
Editor: 28598519a
Last Modified: March 23, 2020
Description: This class is to remove the extracted frames
that have already been upscaled.
"""
@@ -58,7 +63,7 @@ class ImageCleaner(threading.Thread):
"""
# list all images in the extracted frames
output_frames = [f for f in self.output_directory.iterdir() if f.is_file()]
output_frames = [f.name for f in self.output_directory.iterdir() if f.is_file()]
# compare and remove frames downscaled images that finished being upscaled
# within each thread's extracted frames directory
@@ -67,10 +72,8 @@ class ImageCleaner(threading.Thread):
# for each file within all the directories
for file in dir_path.iterdir():
file_path = dir_path / file
# if file also exists in the output directory, then the file
# has already been processed, thus not needed anymore
if file_path.is_file() and file in output_frames:
file_path.unlink(file)
output_frames.remove(file)
if file.is_file() and file.name in output_frames:
file.unlink()
output_frames.remove(file.name)

Binary file not shown.

BIN
src/images/Video2X Icon.psd Normal file

Binary file not shown.

BIN
src/images/v2x.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
src/images/video2x.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
src/images/video2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,244 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2020-05-04 19:14-0400\n"
"PO-Revision-Date: 2020-05-04 19:16-0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: upscaler.py:85
msgid "Extracted frames are being saved to: {}"
msgstr "提取的帧将被保存到:{}"
#: upscaler.py:87
msgid "Upscaled frames are being saved to: {}"
msgstr "已放大的帧将被保存到:{}"
#: upscaler.py:97
msgid "Cleaning up cache directory: {}"
msgstr "清理缓存目录:{}"
#: upscaler.py:100
msgid "Unable to delete: {}"
msgstr "无法删除:{}"
#: upscaler.py:107
msgid "You must specify input video file/directory path"
msgstr "您必须指定输入视频文件/目录路径"
#: upscaler.py:110
msgid "You must specify output video file/directory path"
msgstr "您必须指定输出视频文件/目录路径"
#: upscaler.py:113
msgid "Selected driver accepts only scaling ratio"
msgstr "所选驱动程序仅接受缩放比率"
#: upscaler.py:116
msgid "Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan"
msgstr "waifu2x_ncnn_vulkan 的缩放比必须为 1 或 2"
#: upscaler.py:119
msgid "Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan"
msgstr "srmd_ncnn_vulkan 的缩放比必须为 2、3 或 4"
#: upscaler.py:122
msgid "You can only specify either scaling ratio or output width and height"
msgstr "您只能指定缩放比或输出宽度和高度两者之一"
#: upscaler.py:125
msgid "You must specify both width and height"
msgstr "您必须同时指定宽度和高度"
#: upscaler.py:142
msgid "Upscaling Progress"
msgstr "放大进度"
#: upscaler.py:179
msgid "Unrecognized driver: {}"
msgstr "无法识别的驱动名称:{}"
#: upscaler.py:258
msgid "Starting upscaled image cleaner"
msgstr "启动已放大图像清理程序"
#: upscaler.py:264
msgid "Main process waiting for subprocesses to exit"
msgstr "主进程开始等待子进程结束"
#: upscaler.py:266
msgid "Subprocess {} exited with code {}"
msgstr "子进程 {} 结束,返回码 {}"
#: upscaler.py:274 upscaler.py:287
msgid "Killing upscaled image cleaner"
msgstr "终结已放大图像清理程序"
#: upscaler.py:313 upscaler.py:368
msgid "Starting to upscale extracted images"
msgstr "开始对提取的帧进行放大"
#: upscaler.py:316 upscaler.py:370
msgid "Upscaling completed"
msgstr "放大完成"
#: upscaler.py:324
msgid "Reading video information"
msgstr "读取视频信息"
#: upscaler.py:338
msgid "Aborting: No video stream found"
msgstr "程序中止:文件中未找到视频流"
#: upscaler.py:355
msgid "Unsupported pixel format: {}"
msgstr "不支持的像素格式:{}"
#: upscaler.py:358
msgid "Framerate: {}"
msgstr "帧率:{}"
#: upscaler.py:373
msgid "Converting extracted frames into video"
msgstr "将提取的帧转换为视频"
#: upscaler.py:377
msgid "Conversion completed"
msgstr "转换已完成"
#: upscaler.py:380
msgid "Migrating audio tracks and subtitles to upscaled video"
msgstr "将音轨和字幕迁移到放大后的视频"
#: video2x.py:87
msgid ""
"Video2X Version: {}\n"
"Author: K4YT3X\n"
"License: GNU GPL v3\n"
"Github Page: https://github.com/k4yt3x/video2x\n"
"Contact: k4yt3x@k4yt3x.com"
msgstr ""
"Video2X 版本: {}\n"
"作者: K4YT3X\n"
"开源许可: GNU GPL v3\n"
"GitHub 主页https://github.com/k4yt3x/video2x\n"
"联系方式k4yt3x@k4yt3x.com"
#: video2x.py:109
msgid "General Options"
msgstr "通用选项"
#: video2x.py:110
msgid "show this help message and exit"
msgstr "显示此帮助消息并退出"
#: video2x.py:111
msgid "source video file/directory"
msgstr "源视频文件/目录"
#: video2x.py:112
msgid "output video file/directory"
msgstr "输出视频文件/目录"
#: video2x.py:113
msgid "video2x config file path"
msgstr "video2x 配置文件路径"
#: video2x.py:115
msgid "upscaling driver"
msgstr "视频放大驱动"
#: video2x.py:116
msgid "number of processes to use for upscaling"
msgstr "并发进程数"
#: video2x.py:117
msgid "display version, lawful information and exit"
msgstr "显示版本和法律信息并退出"
#: video2x.py:120
msgid "Scaling Options"
msgstr "缩放选项"
#: video2x.py:121
msgid "output video width"
msgstr "输出视频宽度"
#: video2x.py:122
msgid "output video height"
msgstr "输出视频高度"
#: video2x.py:123
msgid "scaling ratio"
msgstr "缩放比"
#: video2x.py:163
msgid "This file cannot be imported"
msgstr "此文件无法被当作模块导入"
#: video2x.py:193
msgid "Specified driver executable directory doesn't exist"
msgstr "指定驱动的可执行文件不存在"
#: video2x.py:194
msgid "Please check the configuration file settings"
msgstr "请检查配置文件设置"
#: video2x.py:211
msgid "Specified cache directory is a file/link"
msgstr "指定的缓存目录是文件/链接"
#: video2x.py:218
msgid "Creating cache directory {}"
msgstr "创建缓存目录 {}"
#: video2x.py:224
msgid "Unable to create {}"
msgstr "无法创建 {}"
#: video2x.py:237
msgid "Upscaling single video file: {}"
msgstr "放大单个视频文件:{}"
#: video2x.py:241
msgid "Input and output path type mismatch"
msgstr "输入和输出路径类型不匹配"
#: video2x.py:242
msgid "Input is single file but output is directory"
msgstr "所选的输入路径是单个文件,但输出路径是目录"
#: video2x.py:245
msgid "No suffix found in output file path"
msgstr "在输出文件路径中未找到后缀"
#: video2x.py:246
msgid "Suffix must be specified for FFmpeg"
msgstr "必须为 FFmpeg 指定后缀"
#: video2x.py:270
msgid "Upscaling videos in directory: {}"
msgstr "放大该文件夹中的所有视频:{}"
#: video2x.py:295
msgid "Input path is neither a file nor a directory"
msgstr "输入路径既不是文件也不是目录"
#: video2x.py:298
msgid "Program completed, taking {} seconds"
msgstr "程序执行完毕,总计花费 {} 秒"
#: video2x.py:301
msgid "An exception has occurred"
msgstr "发生了异常"

View File

@@ -1,6 +1,9 @@
avalon_framework
colorama
GPUtil
patool
psutil
pyqt5
pyunpack
pyyaml
requests
tqdm
tqdm

401
src/upscaler.py Executable file
View File

@@ -0,0 +1,401 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Video2X Upscaler
Author: K4YT3X
Date Created: December 10, 2018
Last Modified: May 6, 2020
Description: This file contains the Upscaler class. Each
instance of the Upscaler class is an upscaler on an image or
a folder.
"""
# local imports
from exceptions import *
from image_cleaner import ImageCleaner
from wrappers.ffmpeg import Ffmpeg
# built-in imports
from fractions import Fraction
import contextlib
import copy
import gettext
import importlib
import locale
import os
import pathlib
import re
import shutil
import sys
import tempfile
import threading
import time
import traceback
# third-party imports
from avalon_framework import Avalon
from tqdm import tqdm
# internationalization constants
DOMAIN = 'video2x'
LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / 'locale'
# getting default locale settings
default_locale, encoding = locale.getdefaultlocale()
language = gettext.translation(DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True)
language.install()
_ = language.gettext
# these names are consistent for
# - driver selection in command line
# - driver wrapper file names
# - config file keys
AVAILABLE_DRIVERS = ['waifu2x_caffe',
'waifu2x_converter_cpp',
'waifu2x_ncnn_vulkan',
'srmd_ncnn_vulkan',
'anime4kcpp']
class Upscaler:
""" An instance of this class is a upscaler that will
upscale all images in the given directory.
Raises:
Exception -- all exceptions
ArgumentError -- if argument is not valid
"""
def __init__(self, input_video, output_video, driver_settings, ffmpeg_settings):
# mandatory arguments
self.input_video = input_video
self.output_video = output_video
self.driver_settings = driver_settings
self.ffmpeg_settings = ffmpeg_settings
# optional arguments
self.driver = 'waifu2x_caffe'
self.scale_width = None
self.scale_height = None
self.scale_ratio = None
self.processes = 1
self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
self.image_format = 'png'
self.preserve_frames = False
def create_temp_directories(self):
"""create temporary directory
"""
# create a new temp directory if the current one is not found
if not self.video2x_cache_directory.exists():
self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
# create temp directories for extracted frames and upscaled frames
self.extracted_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
Avalon.debug_info(_('Extracted frames are being saved to: {}').format(self.extracted_frames))
self.upscaled_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
Avalon.debug_info(_('Upscaled frames are being saved to: {}').format(self.upscaled_frames))
def cleanup_temp_directories(self):
"""delete temp directories when done
"""
if not self.preserve_frames:
for directory in [self.extracted_frames, self.upscaled_frames, self.video2x_cache_directory]:
try:
# avalon framework cannot be used if python is shutting down
# therefore, plain print is used
print(_('Cleaning up cache directory: {}').format(directory))
shutil.rmtree(directory)
except (OSError, FileNotFoundError):
print(_('Unable to delete: {}').format(directory))
traceback.print_exc()
def _check_arguments(self):
# check if arguments are valid / all necessary argument
# values are specified
if not self.input_video:
Avalon.error(_('You must specify input video file/directory path'))
raise ArgumentError('input video path not specified')
if not self.output_video:
Avalon.error(_('You must specify output video file/directory path'))
raise ArgumentError('output video path not specified')
if (self.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and self.scale_width and self.scale_height:
Avalon.error(_('Selected driver accepts only scaling ratio'))
raise ArgumentError('selected driver supports only scaling ratio')
if self.driver == 'waifu2x_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio > 2 or not self.scale_ratio.is_integer()):
Avalon.error(_('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan'))
raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
if self.driver == 'srmd_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio not in [2, 3, 4]):
Avalon.error(_('Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan'))
raise ArgumentError('scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan')
if (self.scale_width or self.scale_height) and self.scale_ratio:
Avalon.error(_('You can only specify either scaling ratio or output width and height'))
raise ArgumentError('both scaling ration and width/height specified')
if (self.scale_width and not self.scale_height) or (not self.scale_width and self.scale_height):
Avalon.error(_('You must specify both width and height'))
raise ArgumentError('only one of width or height is specified')
def _progress_bar(self, extracted_frames_directories):
""" This method prints a progress bar
This method prints a progress bar by keeping track
of the amount of frames in the input directory
and the output directory. This is originally
suggested by @ArmandBernard.
"""
# get number of extracted frames
self.total_frames = 0
for directory in extracted_frames_directories:
self.total_frames += len([f for f in directory.iterdir() if str(f).lower().endswith(self.image_format.lower())])
with tqdm(total=self.total_frames, ascii=True, desc=_('Upscaling Progress')) as progress_bar:
# tqdm update method adds the value to the progress
# bar instead of setting the value. Therefore, a delta
# needs to be calculated.
previous_cycle_frames = 0
while not self.progress_bar_exit_signal:
with contextlib.suppress(FileNotFoundError):
self.total_frames_upscaled = len([f for f in self.upscaled_frames.iterdir() if str(f).lower().endswith(self.image_format.lower())])
delta = self.total_frames_upscaled - previous_cycle_frames
previous_cycle_frames = self.total_frames_upscaled
# if upscaling is finished
if self.total_frames_upscaled >= self.total_frames:
return
# adds the delta into the progress bar
progress_bar.update(delta)
time.sleep(1)
def _upscale_frames(self):
""" Upscale video frames with waifu2x-caffe
This function upscales all the frames extracted
by ffmpeg using the waifu2x-caffe binary.
Arguments:
w2 {Waifu2x Object} -- initialized waifu2x object
"""
# progress bar process exit signal
self.progress_bar_exit_signal = False
# initialize waifu2x driver
if self.driver not in AVAILABLE_DRIVERS:
raise UnrecognizedDriverError(_('Unrecognized driver: {}').format(self.driver))
# create a container for all upscaler processes
upscaler_processes = []
# list all images in the extracted frames
frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file]
# if we have less images than processes,
# create only the processes necessary
if len(frames) < self.processes:
self.processes = len(frames)
# create a directory for each process and append directory
# name into a list
process_directories = []
for process_id in range(self.processes):
process_directory = self.extracted_frames / str(process_id)
process_directories.append(process_directory)
# delete old directories and create new directories
if process_directory.is_dir():
shutil.rmtree(process_directory)
process_directory.mkdir(parents=True, exist_ok=True)
# waifu2x-converter-cpp will perform multi-threading within its own process
if self.driver == 'waifu2x_converter_cpp':
process_directories = [self.extracted_frames]
else:
# evenly distribute images into each directory
# until there is none left in the directory
for image in frames:
# move image
image.rename(process_directories[0] / image.name)
# rotate list
process_directories = process_directories[-1:] + process_directories[:-1]
# create threads and start them
for process_directory in process_directories:
DriverWrapperMain = getattr(importlib.import_module(f'wrappers.{self.driver}'), 'WrapperMain')
driver = DriverWrapperMain(copy.deepcopy(self.driver_settings))
# if the driver being used is waifu2x-caffe
if self.driver == 'waifu2x_caffe':
upscaler_processes.append(driver.upscale(process_directory,
self.upscaled_frames,
self.scale_ratio,
self.scale_width,
self.scale_height,
self.image_format,
self.bit_depth))
# if the driver being used is waifu2x-converter-cpp
elif self.driver == 'waifu2x_converter_cpp':
upscaler_processes.append(driver.upscale(process_directory,
self.upscaled_frames,
self.scale_ratio,
self.processes,
self.image_format))
# if the driver being used is waifu2x-ncnn-vulkan
elif self.driver == 'waifu2x_ncnn_vulkan':
upscaler_processes.append(driver.upscale(process_directory,
self.upscaled_frames,
self.scale_ratio))
# if the driver being used is srmd_ncnn_vulkan
elif self.driver == 'srmd_ncnn_vulkan':
upscaler_processes.append(driver.upscale(process_directory,
self.upscaled_frames,
self.scale_ratio))
# start progress bar in a different thread
progress_bar = threading.Thread(target=self._progress_bar, args=(process_directories,))
progress_bar.start()
# create the clearer and start it
Avalon.debug_info(_('Starting upscaled image cleaner'))
image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_processes))
image_cleaner.start()
# wait for all process to exit
try:
Avalon.debug_info(_('Main process waiting for subprocesses to exit'))
for process in upscaler_processes:
Avalon.debug_info(_('Subprocess {} exited with code {}').format(process.pid, process.wait()))
except (KeyboardInterrupt, SystemExit):
Avalon.warning('Exit signal received')
Avalon.warning('Killing processes')
for process in upscaler_processes:
process.terminate()
# cleanup and exit with exit code 1
Avalon.debug_info(_('Killing upscaled image cleaner'))
image_cleaner.stop()
self.progress_bar_exit_signal = True
sys.exit(1)
# if the driver is waifu2x-converter-cpp
# images need to be renamed to be recognizable for FFmpeg
if self.driver == 'waifu2x_converter_cpp':
for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.image_format}', f'.{self.image_format}', str(image.name))
(self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
# upscaling done, kill the clearer
Avalon.debug_info(_('Killing upscaled image cleaner'))
image_cleaner.stop()
# pass exit signal to progress bar thread
self.progress_bar_exit_signal = True
def run(self):
""" Main controller for Video2X
This function controls the flow of video conversion
and handles all necessary functions.
"""
# parse arguments for waifu2x
# check argument sanity
self._check_arguments()
# convert paths to absolute paths
self.input_video = self.input_video.absolute()
self.output_video = self.output_video.absolute()
# drivers that have native support for video processing
if self.driver == 'anime4kcpp':
# append FFmpeg path to the end of PATH
# Anime4KCPP will then use FFmpeg to migrate audio tracks
os.environ['PATH'] += f';{self.ffmpeg_settings["ffmpeg_path"]}'
Avalon.info(_('Starting to upscale extracted images'))
# import and initialize Anime4KCPP wrapper
DriverWrapperMain = getattr(importlib.import_module('wrappers.anime4kcpp'), 'WrapperMain')
driver = DriverWrapperMain(copy.deepcopy(self.driver_settings))
# run Anime4KCPP
driver.upscale(self.input_video, self.output_video, self.scale_ratio, self.processes).wait()
Avalon.info(_('Upscaling completed'))
else:
self.create_temp_directories()
# initialize objects for ffmpeg and waifu2x-caffe
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
Avalon.info(_('Reading video information'))
video_info = fm.get_video_info(self.input_video)
# analyze original video with ffprobe and retrieve framerate
# width, height = info['streams'][0]['width'], info['streams'][0]['height']
# find index of video stream
video_stream_index = None
for stream in video_info['streams']:
if stream['codec_type'] == 'video':
video_stream_index = stream['index']
break
# exit if no video stream found
if video_stream_index is None:
Avalon.error(_('Aborting: No video stream found'))
raise StreamNotFoundError('no video stream found')
# extract frames from video
fm.extract_frames(self.input_video, self.extracted_frames)
# get average frame rate of video stream
framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate']))
fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt']
# get a dict of all pixel formats and corresponding bit depth
pixel_formats = fm.get_pixel_formats()
# try getting pixel format's corresponding bti depth
try:
self.bit_depth = pixel_formats[fm.pixel_format]
except KeyError:
Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format))
raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}')
Avalon.info(_('Framerate: {}').format(framerate))
# width/height will be coded width/height x upscale factor
if self.scale_ratio:
original_width = video_info['streams'][video_stream_index]['width']
original_height = video_info['streams'][video_stream_index]['height']
self.scale_width = int(self.scale_ratio * original_width)
self.scale_height = int(self.scale_ratio * original_height)
# upscale images one by one using waifu2x
Avalon.info(_('Starting to upscale extracted images'))
self._upscale_frames()
Avalon.info(_('Upscaling completed'))
# frames to Video
Avalon.info(_('Converting extracted frames into video'))
# use user defined output size
fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames)
Avalon.info(_('Conversion completed'))
# migrate audio tracks and subtitles
Avalon.info(_('Migrating audio tracks and subtitles to upscaled video'))
fm.migrate_audio_tracks_subtitles(self.input_video, self.output_video, self.upscaled_frames)
# destroy temp directories
self.cleanup_temp_directories()

311
src/video2x.py Executable file
View File

@@ -0,0 +1,311 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
__ __ _ _ ___ __ __
\ \ / / (_) | | |__ \ \ \ / /
\ \ / / _ __| | ___ ___ ) | \ V /
\ \/ / | | / _` | / _ \ / _ \ / / > <
\ / | | | (_| | | __/ | (_) | / /_ / . \
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
Name: Video2X Controller
Creator: K4YT3X
Date Created: Feb 24, 2018
Last Modified: May 4, 2020
Editor: BrianPetkovsek
Last Modified: June 17, 2019
Editor: SAT3LL
Last Modified: June 25, 2019
Editor: 28598519a
Last Modified: March 23, 2020
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
(C) 2018 - 2020 K4YT3X
Video2X is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Video2X is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Description: Video2X is an automation software based on waifu2x image
enlarging engine. It extracts frames from a video, enlarge it by a
number of times without losing any details or quality, keeping lines
smooth and edges sharp.
"""
# local imports
from upscaler import AVAILABLE_DRIVERS
from upscaler import Upscaler
# built-in imports
import argparse
import contextlib
import gettext
import importlib
import locale
import pathlib
import re
import shutil
import sys
import tempfile
import time
import traceback
import yaml
# third-party imports
from avalon_framework import Avalon
# internationalization constants
DOMAIN = 'video2x'
LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / 'locale'
# getting default locale settings
default_locale, encoding = locale.getdefaultlocale()
language = gettext.translation(DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True)
language.install()
_ = language.gettext
VERSION = '4.0.0'
LEGAL_INFO = _('''Video2X Version: {}
Author: K4YT3X
License: GNU GPL v3
Github Page: https://github.com/k4yt3x/video2x
Contact: k4yt3x@k4yt3x.com''').format(VERSION)
LOGO = r'''
__ __ _ _ ___ __ __
\ \ / / (_) | | |__ \ \ \ / /
\ \ / / _ __| | ___ ___ ) | \ V /
\ \/ / | | / _` | / _ \ / _ \ / / > <
\ / | | | (_| | | __/ | (_) | / /_ / . \
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
'''
def parse_arguments():
""" parse CLI arguments
"""
parser = argparse.ArgumentParser(prog='video2x', formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
# video options
general_options = parser.add_argument_group(_('General Options'))
general_options.add_argument('-h', '--help', action='help', help=_('show this help message and exit'))
general_options.add_argument('-i', '--input', type=pathlib.Path, help=_('source video file/directory'))
general_options.add_argument('-o', '--output', type=pathlib.Path, help=_('output video file/directory'))
general_options.add_argument('-c', '--config', type=pathlib.Path, help=_('video2x config file path'), action='store',
default=pathlib.Path(__file__).parent.absolute() / 'video2x.yaml')
general_options.add_argument('-d', '--driver', help=_('upscaling driver'), choices=AVAILABLE_DRIVERS, default='waifu2x_caffe')
general_options.add_argument('-p', '--processes', help=_('number of processes to use for upscaling'), action='store', type=int, default=1)
general_options.add_argument('-v', '--version', help=_('display version, lawful information and exit'), action='store_true')
# scaling options
scaling_options = parser.add_argument_group(_('Scaling Options'))
scaling_options.add_argument('--width', help=_('output video width'), action='store', type=int)
scaling_options.add_argument('--height', help=_('output video height'), action='store', type=int)
scaling_options.add_argument('-r', '--ratio', help=_('scaling ratio'), action='store', type=float)
# if no driver arguments are specified
if '--' not in sys.argv:
video2x_args = parser.parse_args()
return video2x_args, None
# if driver arguments are specified
else:
video2x_args = parser.parse_args(sys.argv[1:sys.argv.index('--')])
wrapper = getattr(importlib.import_module(f'wrappers.{video2x_args.driver}'), 'WrapperMain')
driver_args = wrapper.parse_arguments(sys.argv[sys.argv.index('--') + 1:])
return video2x_args, driver_args
def print_logo():
"""print video2x logo"""
print(LOGO)
print(f'\n{"Video2X Video Enlarger".rjust(40, " ")}')
print(f'\n{Avalon.FM.BD}{f"Version {VERSION}".rjust(36, " ")}{Avalon.FM.RST}\n')
def read_config(config_file: pathlib.Path) -> dict:
""" read video2x configurations from config file
Arguments:
config_file {pathlib.Path} -- video2x configuration file pathlib.Path
Returns:
dict -- dictionary of video2x configuration
"""
with open(config_file, 'r') as config:
return yaml.load(config, Loader=yaml.FullLoader)
# /////////////////// Execution /////////////////// #
# this is not a library
if __name__ != '__main__':
Avalon.error(_('This file cannot be imported'))
raise ImportError(f'{__file__} cannot be imported')
# print video2x logo
print_logo()
# parse command line arguments
video2x_args, driver_args = parse_arguments()
# display version and lawful informaition
if video2x_args.version:
print(LEGAL_INFO)
sys.exit(0)
# read configurations from configuration file
config = read_config(video2x_args.config)
# load waifu2x configuration
driver_settings = config[video2x_args.driver]
# overwrite driver_settings with driver_args
if driver_args is not None:
driver_args_dict = vars(driver_args)
for key in driver_args_dict:
if driver_args_dict[key] is not None:
driver_settings[key] = driver_args_dict[key]
# check if driver path exists
if not pathlib.Path(driver_settings['path']).exists():
if not pathlib.Path(f'{driver_settings["path"]}.exe').exists():
Avalon.error(_('Specified driver executable directory doesn\'t exist'))
Avalon.error(_('Please check the configuration file settings'))
raise FileNotFoundError(driver_settings['path'])
# read FFmpeg configuration
ffmpeg_settings = config['ffmpeg']
# load video2x settings
image_format = config['video2x']['image_format'].lower()
preserve_frames = config['video2x']['preserve_frames']
# load cache directory
if config['video2x']['video2x_cache_directory'] is not None:
video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory'])
else:
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
Avalon.error(_('Specified cache directory is a file/link'))
raise FileExistsError('Specified cache directory is a file/link')
# if cache directory doesn't exist
# ask the user if it should be created
elif not video2x_cache_directory.exists():
try:
Avalon.debug_info(_('Creating cache directory {}').format(video2x_cache_directory))
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
# there can be a number of exceptions here
# PermissionError, FileExistsError, etc.
# therefore, we put a catch-them-all here
except Exception as exception:
Avalon.error(_('Unable to create {}').format(video2x_cache_directory))
raise exception
# start execution
try:
# start timer
begin_time = time.time()
# if input specified is a single file
if video2x_args.input.is_file():
# upscale single video file
Avalon.info(_('Upscaling single video file: {}').format(video2x_args.input))
# check for input output format mismatch
if video2x_args.output.is_dir():
Avalon.error(_('Input and output path type mismatch'))
Avalon.error(_('Input is single file but output is directory'))
raise Exception('input output path type mismatch')
if not re.search(r'.*\..*$', str(video2x_args.output)):
Avalon.error(_('No suffix found in output file path'))
Avalon.error(_('Suffix must be specified for FFmpeg'))
raise Exception('No suffix specified')
upscaler = Upscaler(input_video=video2x_args.input,
output_video=video2x_args.output,
driver_settings=driver_settings,
ffmpeg_settings=ffmpeg_settings)
# set optional options
upscaler.driver = video2x_args.driver
upscaler.scale_width = video2x_args.width
upscaler.scale_height = video2x_args.height
upscaler.scale_ratio = video2x_args.ratio
upscaler.processes = video2x_args.processes
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.run()
# if input specified is a directory
elif video2x_args.input.is_dir():
# upscale videos in a directory
Avalon.info(_('Upscaling videos in directory: {}').format(video2x_args.input))
# make output directory if it doesn't exist
video2x_args.output.mkdir(parents=True, exist_ok=True)
for input_video in [f for f in video2x_args.input.iterdir() if f.is_file()]:
output_video = video2x_args.output / input_video.name
upscaler = Upscaler(input_video=input_video,
output_video=output_video,
driver_settings=driver_settings,
ffmpeg_settings=ffmpeg_settings)
# set optional options
upscaler.driver = video2x_args.driver
upscaler.scale_width = video2x_args.width
upscaler.scale_height = video2x_args.height
upscaler.scale_ratio = video2x_args.ratio
upscaler.processes = video2x_args.processes
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.run()
else:
Avalon.error(_('Input path is neither a file nor a directory'))
raise FileNotFoundError(f'{video2x_args.input} is neither file nor directory')
Avalon.info(_('Program completed, taking {} seconds').format(round((time.time() - begin_time), 5)))
except Exception:
Avalon.error(_('An exception has occurred'))
traceback.print_exc()
# try cleaning up temp directories
with contextlib.suppress(Exception):
upscaler.cleanup_temp_directories()
finally:
# remove Video2X cache directory
with contextlib.suppress(FileNotFoundError):
if not preserve_frames:
shutil.rmtree(video2x_cache_directory)

126
src/video2x.yaml Normal file
View File

@@ -0,0 +1,126 @@
waifu2x_caffe:
path: 'C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-caffe\waifu2x-caffe-cui'
input_extention_list: null
output_extention: null
mode: noise_scale
scale_ratio: null
scale_width: null
scale_height: null
noise_level: 3
process: gpu
crop_size: 128
output_quality: -1
output_depth: 8
batch_size: 1
gpu: 0
tta: 0
input_path: null
output_path: null
model_dir: null
crop_w: null
crop_h: null
waifu2x_converter_cpp:
path: 'C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-converter-cpp\waifu2x-converter-cpp'
# list-supported-formats: null
# list-opencv-formats: null
# list-processor
output-format: progress_bar_exit_signal
png-compression: 5
image-quality: -1
block-size: 0
disable-gpu: false
force-OpenCL: false
processor: -1
jobs: 0
model-dir: null # models_rgb
scale-ratio: 2.0
noise-level: 1
mode: noise-scale
log-level: 1
silent: null
tta: 0
# generate-subdir: 0
# recursive-directory: 0
output: null
input: null
waifu2x_ncnn_vulkan:
path: 'C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-ncnn-vulkan\waifu2x-ncnn-vulkan'
v: null # verbose output
i: null # input-path: input image path (jpg/png) or directory
o: null # output-path: output image path (png) or directory
'n': 2 # noise-level: denoise level (-1/0/1/2/3, default=0)
s: 2 # scale: upscale ratio (1/2, default=2)
t: 400 # tile-size: tile size (>=32, default=400)
m: null # model-path: waifu2x model path (default=models-cunet)
g: 0 # gpu-id: gpu device to use (default=0)
j: '1:2:2' # thread count for load/proc/save (default=1:2:2)
x: false # enable tta mode
srmd_ncnn_vulkan:
path: 'C:\Users\K4YT3X\AppData\Local\video2x\srmd-ncnn-vulkan\srmd-ncnn-vulkan'
v: null # verbose output
i: null # input-path: input image path (jpg/png) or directory
o: null # output-path: output image path (png) or directory
'n': 3 # noise-level: denoise level (-1/0/1/2/3/4/5/6/7/8/9/10, default=3)
s: 2 # upscale ratio (2/3/4, default=2)
t: 400 # tile-size: tile size (>=32, default=400)
m: null # srmd model path (default=models-srmd)
g: 0 # gpu device to use (default=0)
j: '1:2:2' # thread count for load/proc/save (default=1:2:2)
x: false # enable tta mode
anime4kcpp:
path: 'C:\Users\K4YT3X\AppData\Local\video2x\anime4kcpp\CLI\Anime4KCPP_CLI\Anime4KCPP_CLI'
input: null # File for loading (string [=./pic/p1.png])
output: null # File for outputting (string [=output.png])
passes: 2 # Passes for processing (int [=2])
pushColorCount: 2 # Limit the number of color pushes (int [=2])
strengthColor: 0.3 # Strength for pushing color,range 0 to 1,higher for thinner (double [=0.3])
strengthGradient: 1.0 # Strength for pushing gradient,range 0 to 1,higher for sharper (double [=1])
zoomFactor: 2.0 # zoom factor for resizing (double [=2])
threads: 16 # Threads count for video processing (unsigned int [=16])
fastMode: false # Faster but maybe low quality
videoMode: true # Video process
preview: null # Preview image
preProcessing: False # Enable pre processing
postProcessing: False # Enable post processing
preFilters: 4 # Enhancement filter, only working when preProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D) (unsigned int [=4])
postFilters: 40 # Enhancement filter, only working when postProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D), so you can put 40 to enable Gaussian blur weak and Bilateral filter, which also is what I recommend for image that < 1080P, 48 for image that >= 1080P, and for performance I recommend to use 72 for video that < 1080P, 80 for video that >=1080P (unsigned int [=40])
GPUMode: False # Enable GPU acceleration
listGPUs: null # list GPUs
platformID: 0 # Specify the platform ID (unsigned int [=0])
deviceID: 0 # Specify the device ID (unsigned int [=0])
ffmpeg:
ffmpeg_path: 'C:\Users\K4YT3X\AppData\Local\video2x\ffmpeg-latest-win64-static\bin'
video_to_frames:
output_options:
'-qscale:v': null
'-pix_fmt': rgba64be
'-hwaccel': auto
'-y': true
frames_to_video:
input_options:
'-qscale:v': null
'-qscale:a': null
'-f': image2
output_options:
'-vcodec': libx264
'-crf': 17
'-b:v': null
'-pix_fmt': null
'-hwaccel': auto
'-y': true
migrating_tracks:
output_options:
'-map':
- '0:v?'
- '1:a?'
- '1:s?'
- '1:d?'
- '1:t?'
'-c': copy
'-pix_fmt': null
'-hwaccel': auto
'-y': true
video2x:
video2x_cache_directory: null # default: %TEMP%\video2x
image_format: png
preserve_frames: false

687
src/video2x_gui.py Executable file
View File

@@ -0,0 +1,687 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Creator: Video2X QT
Author: K4YT3X
Date Created: May 5, 2020
Last Modified: May 6, 2020
"""
# local imports
from upscaler import Upscaler
# built-in imports
import contextlib
import pathlib
import re
import shutil
import sys
import tempfile
import time
import traceback
import yaml
# third-party imports
from PyQt5 import QtWidgets, QtGui
from PyQt5 import uic
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool
VERSION = '2.0.0'
LEGAL_INFO = f'''Video2X GUI Version: {VERSION}
Author: K4YT3X
License: GNU GPL v3
Github Page: https://github.com/k4yt3x/video2x
Contact: k4yt3x@k4yt3x.com'''
AVAILABLE_DRIVERS = {
'Waifu2X Caffe': 'waifu2x_caffe',
'Waifu2X Converter CPP': 'waifu2x_converter_cpp',
'Waifu2X NCNN Vulkan': 'waifu2x_ncnn_vulkan',
'SRMD NCNN Vulkan': 'srmd_ncnn_vulkan',
'Anime4KCPP': 'anime4kcpp'
}
def resource_path(relative_path: str) -> pathlib.Path:
try:
base_path = pathlib.Path(sys._MEIPASS)
except Exception:
base_path = pathlib.Path(__file__).parent
return base_path / relative_path
class WorkerSignals(QObject):
progress = pyqtSignal(tuple)
error = pyqtSignal(str)
finished = pyqtSignal()
class ProgressBarWorker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(ProgressBarWorker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['progress_callback'] = self.signals.progress
@pyqtSlot()
def run(self):
try:
self.fn(*self.args, **self.kwargs)
except Exception:
pass
class UpscalerWorker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(UpscalerWorker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
@pyqtSlot()
def run(self):
# Retrieve args/kwargs here; and fire processing using them
try:
self.fn(*self.args, **self.kwargs)
except Exception:
error_message = traceback.format_exc()
print(error_message, file=sys.stderr)
self.signals.error.emit(error_message)
finally:
self.signals.finished.emit()
class Video2XMainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
uic.loadUi(str(resource_path('video2x_gui.ui')), self)
self.video2x_icon_path = str(resource_path('images/video2x.png'))
self.setWindowTitle(f'Video2X GUI {VERSION}')
self.setWindowIcon(QtGui.QIcon(self.video2x_icon_path))
# menu bar
self.action_exit = self.findChild(QtWidgets.QAction, 'actionExit')
self.action_exit.triggered.connect(sys.exit)
self.action_about = self.findChild(QtWidgets.QAction, 'actionAbout')
self.action_about.triggered.connect(lambda: self.show_message(LEGAL_INFO, custom_icon=QtGui.QPixmap(self.video2x_icon_path)))
# main tab
# select input file/folder
self.input_line_edit = self.findChild(QtWidgets.QLineEdit, 'inputLineEdit')
self.input_select_file_button = self.findChild(QtWidgets.QPushButton, 'inputSelectFileButton')
self.input_select_file_button.clicked.connect(self.select_input_file)
self.input_select_folder_button = self.findChild(QtWidgets.QPushButton, 'inputSelectFolderButton')
self.input_select_folder_button.clicked.connect(self.select_input_folder)
# select output file/folder
self.output_line_edit = self.findChild(QtWidgets.QLineEdit, 'outputLineEdit')
self.output_select_file_button = self.findChild(QtWidgets.QPushButton, 'outputSelectFileButton')
self.output_select_file_button.clicked.connect(self.select_output_file)
self.output_select_folder_button = self.findChild(QtWidgets.QPushButton, 'outputSelectFolderButton')
self.output_select_folder_button.clicked.connect(self.select_output_folder)
# config file
self.config_line_edit = self.findChild(QtWidgets.QLineEdit, 'configLineEdit')
self.config_line_edit.setText(str((pathlib.Path(__file__).parent / 'video2x.yaml').absolute()))
self.config_select_file_button = self.findChild(QtWidgets.QPushButton, 'configSelectButton')
self.config_select_file_button.clicked.connect(self.select_config_file)
# cache directory
self.cache_line_edit = self.findChild(QtWidgets.QLineEdit, 'cacheLineEdit')
self.cache_select_folder_button = self.findChild(QtWidgets.QPushButton, 'cacheSelectFolderButton')
self.cache_select_folder_button.clicked.connect(self.select_cache_folder)
# express settings
self.driver_combo_box = self.findChild(QtWidgets.QComboBox, 'driverComboBox')
self.driver_combo_box.currentTextChanged.connect(self.update_driver_constraints)
self.processes_spin_box = self.findChild(QtWidgets.QSpinBox, 'processesSpinBox')
self.scale_ratio_double_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'scaleRatioDoubleSpinBox')
self.preserve_frames_check_box = self.findChild(QtWidgets.QCheckBox, 'preserveFramesCheckBox')
# progress bar and start/stop controls
self.progress_bar = self.findChild(QtWidgets.QProgressBar, 'progressBar')
self.time_elapsed_label = self.findChild(QtWidgets.QLabel, 'timeElapsedLabel')
self.time_remaining_label = self.findChild(QtWidgets.QLabel, 'timeRemainingLabel')
self.rate_label = self.findChild(QtWidgets.QLabel, 'rateLabel')
self.start_button = self.findChild(QtWidgets.QPushButton, 'startButton')
self.start_button.clicked.connect(self.start)
self.stop_button = self.findChild(QtWidgets.QPushButton, 'stopButton')
self.stop_button.clicked.connect(self.stop)
# driver settings
# waifu2x-caffe
self.waifu2x_caffe_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xCaffePathLineEdit')
self.waifu2x_caffe_path_select_button = self.findChild(QtWidgets.QPushButton, 'waifu2xCaffePathSelectButton')
self.waifu2x_caffe_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_caffe_path_line_edit))
self.waifu2x_caffe_mode_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeModeComboBox')
self.waifu2x_caffe_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeNoiseLevelSpinBox')
self.waifu2x_caffe_process_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeProcessComboBox')
self.waifu2x_caffe_model_combobox = self.findChild(QtWidgets.QComboBox, 'waifu2xCaffeModelComboBox')
self.waifu2x_caffe_crop_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeCropSizeSpinBox')
self.waifu2x_caffe_output_quality_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeOutputQualitySpinBox')
self.waifu2x_caffe_output_depth_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeOutputDepthSpinBox')
self.waifu2x_caffe_batch_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeBatchSizeSpinBox')
self.waifu2x_caffe_gpu_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xCaffeGpuSpinBox')
self.waifu2x_caffe_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'waifu2xCaffeTtaCheckBox')
# waifu2x-converter-cpp
self.waifu2x_converter_cpp_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xConverterCppPathLineEdit')
self.waifu2x_converter_cpp_path_edit_button = self.findChild(QtWidgets.QPushButton, 'waifu2xConverterCppPathSelectButton')
self.waifu2x_converter_cpp_path_edit_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_converter_cpp_path_line_edit))
self.waifu2x_converter_cpp_png_compression_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xConverterCppPngCompressionSpinBox')
self.waifu2x_converter_cpp_processor_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xConverterCppProcessorSpinBox')
self.waifu2x_converter_cpp_model_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xConverterCppModelComboBox')
self.waifu2x_converter_cpp_mode_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xConverterCppModeComboBox')
self.waifu2x_converter_cpp_disable_gpu_check_box = self.findChild(QtWidgets.QCheckBox, 'disableGpuCheckBox')
self.waifu2x_converter_cpp_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'ttaCheckBox')
# waifu2x-ncnn-vulkan
self.waifu2x_ncnn_vulkan_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xNcnnVulkanPathLineEdit')
self.waifu2x_ncnn_vulkan_path_select_button = self.findChild(QtWidgets.QPushButton, 'waifu2xNcnnVulkanPathSelectButton')
self.waifu2x_ncnn_vulkan_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.waifu2x_ncnn_vulkan_path_line_edit))
self.waifu2x_ncnn_vulkan_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanNoiseLevelSpinBox')
self.waifu2x_ncnn_vulkan_tile_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanTileSizeSpinBox')
self.waifu2x_ncnn_vulkan_model_combo_box = self.findChild(QtWidgets.QComboBox, 'waifu2xNcnnVulkanModelComboBox')
self.waifu2x_ncnn_vulkan_gpu_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'waifu2xNcnnVulkanGpuIdSpinBox')
self.waifu2x_ncnn_vulkan_jobs_line_edit = self.findChild(QtWidgets.QLineEdit, 'waifu2xNcnnVulkanJobsLineEdit')
self.waifu2x_ncnn_vulkan_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'waifu2xNcnnVulkanTtaCheckBox')
# srmd-ncnn-vulkan
self.srmd_ncnn_vulkan_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'srmdNcnnVulkanPathLineEdit')
self.srmd_ncnn_vulkan_path_select_button = self.findChild(QtWidgets.QPushButton, 'srmdNcnnVulkanPathSelectButton')
self.srmd_ncnn_vulkan_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.srmd_ncnn_vulkan_path_line_edit))
self.srmd_ncnn_vulkan_noise_level_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanNoiseLevelSpinBox')
self.srmd_ncnn_vulkan_tile_size_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanTileSizeSpinBox')
self.srmd_ncnn_vulkan_model_combo_box = self.findChild(QtWidgets.QComboBox, 'srmdNcnnVulkanModelComboBox')
self.srmd_ncnn_vulkan_gpu_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'srmdNcnnVulkanGpuIdSpinBox')
self.srmd_ncnn_vulkan_jobs_line_edit = self.findChild(QtWidgets.QLineEdit, 'srmdNcnnVulkanJobsLineEdit')
self.srmd_ncnn_vulkan_tta_check_box = self.findChild(QtWidgets.QCheckBox, 'srmdNcnnVulkanTtaCheckBox')
# anime4k
self.anime4kcpp_path_line_edit = self.findChild(QtWidgets.QLineEdit, 'anime4kCppPathLineEdit')
self.anime4kcpp_path_select_button = self.findChild(QtWidgets.QPushButton, 'anime4kCppPathSelectButton')
self.anime4kcpp_path_select_button.clicked.connect(lambda: self.select_driver_binary_path(self.anime4kcpp_path_line_edit))
self.anime4kcpp_passes_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPassesSpinBox')
self.anime4kcpp_push_color_count_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPushColorCountSpinBox')
self.anime4kcpp_strength_color_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'anime4kCppStrengthColorSpinBox')
self.anime4kcpp_strength_gradient_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'anime4kCppStrengthGradientSpinBox')
self.anime4kcpp_threads_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppThreadsSpinBox')
self.anime4kcpp_pre_filters_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPreFiltersSpinBox')
self.anime4kcpp_post_filters_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPostFiltersSpinBox')
self.anime4kcpp_platform_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppPlatformIdSpinBox')
self.anime4kcpp_device_id_spin_box = self.findChild(QtWidgets.QSpinBox, 'anime4kCppDeviceIdSpinBox')
self.anime4kcpp_fast_mode_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppFastModeCheckBox')
self.anime4kcpp_pre_processing_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppPreProcessingCheckBox')
self.anime4kcpp_post_processing_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppPostProcessingCheckBox')
self.anime4kcpp_gpu_mode_check_box = self.findChild(QtWidgets.QCheckBox, 'anime4kCppGpuModeCheckBox')
# load configurations
self.load_configurations()
@staticmethod
def read_config(config_file: pathlib.Path) -> dict:
""" read video2x configurations from config file
Arguments:
config_file {pathlib.Path} -- video2x configuration file pathlib.Path
Returns:
dict -- dictionary of video2x configuration
"""
with open(config_file, 'r') as config:
return yaml.load(config, Loader=yaml.FullLoader)
def load_configurations(self):
# get config file path from line edit
config_file_path = pathlib.Path(self.config_line_edit.text())
# if file doesn't exist, return
if not config_file_path.is_file():
QtWidgets.QErrorMessage(self).showMessage('Video2X configuration file not found, please specify manually.')
return
# read configuration dict from config file
self.config = self.read_config(config_file_path)
# load FFmpeg settings
self.ffmpeg_settings = self.config['ffmpeg']
# load cache directory, create it if necessary
if self.config['video2x']['video2x_cache_directory'] is not None:
video2x_cache_directory = pathlib.Path(self.config['video2x']['video2x_cache_directory'])
else:
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
self.show_error('Specified cache directory is a file/link')
raise FileExistsError('Specified cache directory is a file/link')
# if cache directory doesn't exist
# ask the user if it should be created
elif not video2x_cache_directory.exists():
try:
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
except Exception as exception:
self.show_error(f'Unable to create cache directory: {video2x_cache_directory}')
raise exception
self.cache_line_edit.setText(str(video2x_cache_directory.absolute()))
# load preserve frames settings
self.preserve_frames_check_box.setChecked(self.config['video2x']['preserve_frames'])
self.start_button.setEnabled(True)
# waifu2x-caffe
settings = self.config['waifu2x_caffe']
self.waifu2x_caffe_path_line_edit.setText(str(pathlib.Path(settings['path']).absolute()))
self.waifu2x_caffe_mode_combo_box.setCurrentText(settings['mode'])
self.waifu2x_caffe_noise_level_spin_box.setValue(settings['noise_level'])
self.waifu2x_caffe_process_combo_box.setCurrentText(settings['process'])
self.waifu2x_caffe_crop_size_spin_box.setValue(settings['crop_size'])
self.waifu2x_caffe_output_quality_spin_box.setValue(settings['output_quality'])
self.waifu2x_caffe_output_depth_spin_box.setValue(settings['output_depth'])
self.waifu2x_caffe_batch_size_spin_box.setValue(settings['batch_size'])
self.waifu2x_caffe_gpu_spin_box.setValue(settings['gpu'])
self.waifu2x_caffe_tta_check_box.setChecked(bool(settings['tta']))
# waifu2x-converter-cpp
settings = self.config['waifu2x_converter_cpp']
self.waifu2x_converter_cpp_path_line_edit.setText(str(pathlib.Path(settings['path']).absolute()))
self.waifu2x_converter_cpp_png_compression_spin_box.setValue(settings['png-compression'])
self.waifu2x_converter_cpp_processor_spin_box.setValue(settings['processor'])
self.waifu2x_converter_cpp_mode_combo_box.setCurrentText(settings['mode'])
self.waifu2x_converter_cpp_disable_gpu_check_box.setChecked(settings['disable-gpu'])
self.waifu2x_converter_cpp_tta_check_box.setChecked(bool(settings['tta']))
# waifu2x-ncnn-vulkan
settings = self.config['waifu2x_ncnn_vulkan']
self.waifu2x_ncnn_vulkan_path_line_edit.setText(str(pathlib.Path(settings['path']).absolute()))
self.waifu2x_ncnn_vulkan_noise_level_spin_box.setValue(settings['n'])
self.waifu2x_ncnn_vulkan_tile_size_spin_box.setValue(settings['t'])
self.waifu2x_ncnn_vulkan_gpu_id_spin_box.setValue(settings['g'])
self.waifu2x_ncnn_vulkan_jobs_line_edit.setText(settings['j'])
self.waifu2x_ncnn_vulkan_tta_check_box.setChecked(settings['x'])
# srmd-ncnn-vulkan
settings = self.config['srmd_ncnn_vulkan']
self.srmd_ncnn_vulkan_path_line_edit.setText(str(pathlib.Path(settings['path']).absolute()))
self.srmd_ncnn_vulkan_noise_level_spin_box.setValue(settings['n'])
self.srmd_ncnn_vulkan_tile_size_spin_box.setValue(settings['t'])
self.srmd_ncnn_vulkan_gpu_id_spin_box.setValue(settings['g'])
self.srmd_ncnn_vulkan_jobs_line_edit.setText(settings['j'])
self.srmd_ncnn_vulkan_tta_check_box.setChecked(settings['x'])
# anime4k
settings = self.config['anime4kcpp']
self.anime4kcpp_path_line_edit.setText(str(pathlib.Path(settings['path']).absolute()))
self.anime4kcpp_passes_spin_box.setValue(settings['passes'])
self.anime4kcpp_push_color_count_spin_box.setValue(settings['pushColorCount'])
self.anime4kcpp_strength_color_spin_box.setValue(settings['strengthColor'])
self.anime4kcpp_strength_gradient_spin_box.setValue(settings['strengthGradient'])
self.anime4kcpp_threads_spin_box.setValue(settings['threads'])
self.anime4kcpp_pre_filters_spin_box.setValue(settings['preFilters'])
self.anime4kcpp_post_filters_spin_box.setValue(settings['postFilters'])
self.anime4kcpp_platform_id_spin_box.setValue(settings['platformID'])
self.anime4kcpp_device_id_spin_box.setValue(settings['deviceID'])
self.anime4kcpp_fast_mode_check_box.setChecked(settings['fastMode'])
self.anime4kcpp_pre_processing_check_box.setChecked(settings['preProcessing'])
self.anime4kcpp_post_processing_check_box.setChecked(settings['postProcessing'])
self.anime4kcpp_gpu_mode_check_box.setChecked(settings['GPUMode'])
def resolve_driver_settings(self):
# waifu2x-caffe
self.config['waifu2x_caffe']['path'] = self.waifu2x_caffe_path_line_edit.text()
self.config['waifu2x_caffe']['mode'] = self.waifu2x_caffe_mode_combo_box.currentText()
self.config['waifu2x_caffe']['noise_level'] = self.waifu2x_caffe_noise_level_spin_box.value()
self.config['waifu2x_caffe']['process'] = self.waifu2x_caffe_process_combo_box.currentText()
self.config['waifu2x_caffe']['model_dir'] = str((pathlib.Path(self.config['waifu2x_caffe']['path']).parent / 'models' / self.waifu2x_caffe_model_combobox.currentText()).absolute())
self.config['waifu2x_caffe']['crop_size'] = self.waifu2x_caffe_crop_size_spin_box.value()
self.config['waifu2x_caffe']['output_quality'] = self.waifu2x_caffe_output_depth_spin_box.value()
self.config['waifu2x_caffe']['output_depth'] = self.waifu2x_caffe_output_depth_spin_box.value()
self.config['waifu2x_caffe']['batch_size'] = self.waifu2x_caffe_batch_size_spin_box.value()
self.config['waifu2x_caffe']['gpu'] = self.waifu2x_caffe_gpu_spin_box.value()
self.config['waifu2x_caffe']['tta'] = int(self.waifu2x_caffe_tta_check_box.checkState())
# waifu2x-converter-cpp
self.config['waifu2x_converter_cpp']['path'] = self.waifu2x_converter_cpp_path_line_edit.text()
self.config['waifu2x_converter_cpp']['png-compression'] = self.waifu2x_converter_cpp_png_compression_spin_box.value()
self.config['waifu2x_converter_cpp']['processor'] = self.waifu2x_converter_cpp_processor_spin_box.value()
self.config['waifu2x_converter_cpp']['model-dir'] = str((pathlib.Path(self.config['waifu2x_converter_cpp']['path']).parent / self.waifu2x_converter_cpp_model_combo_box.currentText()).absolute())
self.config['waifu2x_converter_cpp']['mode'] = self.waifu2x_converter_cpp_mode_combo_box.currentText()
self.config['waifu2x_converter_cpp']['disable-gpu'] = bool(self.waifu2x_converter_cpp_disable_gpu_check_box.checkState())
self.config['waifu2x_converter_cpp']['tta'] = int(self.waifu2x_converter_cpp_tta_check_box.checkState())
# waifu2x-ncnn-vulkan
self.config['waifu2x_ncnn_vulkan']['path'] = self.waifu2x_ncnn_vulkan_path_line_edit.text()
self.config['waifu2x_ncnn_vulkan']['n'] = self.waifu2x_ncnn_vulkan_noise_level_spin_box.value()
self.config['waifu2x_ncnn_vulkan']['t'] = self.waifu2x_ncnn_vulkan_tile_size_spin_box.value()
self.config['waifu2x_ncnn_vulkan']['m'] = str((pathlib.Path(self.config['waifu2x_ncnn_vulkan']['path']).parent / self.waifu2x_ncnn_vulkan_model_combo_box.currentText()).absolute())
self.config['waifu2x_ncnn_vulkan']['g'] = self.waifu2x_ncnn_vulkan_gpu_id_spin_box.value()
self.config['waifu2x_ncnn_vulkan']['j'] = self.waifu2x_ncnn_vulkan_jobs_line_edit.text()
self.config['waifu2x_ncnn_vulkan']['x'] = self.waifu2x_ncnn_vulkan_tta_check_box.checkState()
# srmd-ncnn-vulkan
self.config['srmd_ncnn_vulkan']['path'] = self.srmd_ncnn_vulkan_path_line_edit.text()
self.config['srmd_ncnn_vulkan']['n'] = self.srmd_ncnn_vulkan_noise_level_spin_box.value()
self.config['srmd_ncnn_vulkan']['t'] = self.srmd_ncnn_vulkan_tile_size_spin_box.value()
self.config['srmd_ncnn_vulkan']['m'] = str((pathlib.Path(self.config['srmd_ncnn_vulkan']['path']).parent / self.srmd_ncnn_vulkan_model_combo_box.currentText()).absolute())
self.config['srmd_ncnn_vulkan']['g'] = self.srmd_ncnn_vulkan_gpu_id_spin_box.value()
self.config['srmd_ncnn_vulkan']['j'] = self.srmd_ncnn_vulkan_jobs_line_edit.text()
self.config['srmd_ncnn_vulkan']['x'] = self.srmd_ncnn_vulkan_tta_check_box.checkState()
# anime4k
self.config['anime4kcpp']['path'] = self.anime4kcpp_path_line_edit.text()
self.config['anime4kcpp']['passes'] = self.anime4kcpp_passes_spin_box.value()
self.config['anime4kcpp']['pushColorCount'] = self.anime4kcpp_push_color_count_spin_box.value()
self.config['anime4kcpp']['strengthColor'] = self.anime4kcpp_strength_color_spin_box.value()
self.config['anime4kcpp']['strengthGradient'] = self.anime4kcpp_strength_gradient_spin_box.value()
self.config['anime4kcpp']['threads'] = self.anime4kcpp_threads_spin_box.value()
self.config['anime4kcpp']['preFilters'] = self.anime4kcpp_pre_filters_spin_box.value()
self.config['anime4kcpp']['postFilters'] = self.anime4kcpp_post_filters_spin_box.value()
self.config['anime4kcpp']['platformID'] = self.anime4kcpp_platform_id_spin_box.value()
self.config['anime4kcpp']['deviceID'] = self.anime4kcpp_device_id_spin_box.value()
self.config['anime4kcpp']['fastMode'] = bool(self.anime4kcpp_fast_mode_check_box.checkState())
self.config['anime4kcpp']['preProcessing'] = bool(self.anime4kcpp_pre_processing_check_box.checkState())
self.config['anime4kcpp']['postProcessing'] = bool(self.anime4kcpp_post_processing_check_box.checkState())
self.config['anime4kcpp']['GPUMode'] = bool(self.anime4kcpp_gpu_mode_check_box.checkState())
def update_driver_constraints(self):
current_driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
if current_driver == 'waifu2x_caffe':
self.scale_ratio_double_spin_box.setMinimum(0.0)
self.scale_ratio_double_spin_box.setMaximum(999.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'waifu2x_converter_cpp':
self.scale_ratio_double_spin_box.setMinimum(0.0)
self.scale_ratio_double_spin_box.setMaximum(999.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'waifu2x_ncnn_vulkan':
self.scale_ratio_double_spin_box.setMinimum(1.0)
self.scale_ratio_double_spin_box.setMaximum(2.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'srmd_ncnn_vulkan':
self.scale_ratio_double_spin_box.setMinimum(2.0)
self.scale_ratio_double_spin_box.setMaximum(4.0)
self.scale_ratio_double_spin_box.setValue(2.0)
elif current_driver == 'anime4kcpp':
self.scale_ratio_double_spin_box.setMinimum(0)
self.scale_ratio_double_spin_box.setMaximum(999.0)
self.scale_ratio_double_spin_box.setValue(2.0)
def select_input_file(self):
input_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Input File')
if not isinstance(input_file, tuple) or input_file[0] == '':
return
input_file = pathlib.Path(input_file[0])
self.input_line_edit.setText(str(input_file.absolute()))
# try to set an output file name automatically
output_file = input_file.parent / f'{input_file.stem}_output.mp4'
output_file_id = 0
while output_file.is_file() and output_file_id <= 10:
output_file = input_file.parent / pathlib.Path(f'{input_file.stem}_output_{output_file_id}.mp4')
output_file_id += 1
if not output_file.exists():
self.output_line_edit.setText(str(output_file.absolute()))
def select_input_folder(self):
input_folder = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Input Folder')
if input_folder == '':
return
input_folder = pathlib.Path(input_folder)
self.input_line_edit.setText(str(input_folder.absolute()))
# try to set an output file name automatically
output_folder = input_folder.parent / f'{input_folder.stem}_output'
output_file_id = 0
while output_folder.is_dir() and output_file_id <= 10:
output_folder = input_folder.parent / pathlib.Path(f'{input_folder.stem}_output_{output_file_id}')
output_file_id += 1
if not output_folder.exists():
self.output_line_edit.setText(str(output_folder.absolute()))
def select_output_file(self):
output_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Output File')
if not isinstance(output_file, tuple):
return
self.output_line_edit.setText(str(pathlib.Path(output_file[0]).absolute()))
def select_output_folder(self):
output_folder = QtWidgets.QFileDialog.getSaveFileName(self, 'Select Output Folder')
if output_folder == '':
return
self.output_line_edit.setText(str(pathlib.Path(output_folder).absolute()))
def select_cache_folder(self):
cache_folder = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Cache Folder')
if cache_folder == '':
return
self.cache_line_edit.setText(str(pathlib.Path(cache_folder).absolute()))
def select_config_file(self):
config_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Config File', filter='(YAML files (*.yaml))')
if not isinstance(config_file, tuple):
return
self.config_line_edit.setText(str(pathlib.Path(config_file[0]).absolute()))
self.load_configurations()
def select_driver_binary_path(self, driver_line_edit):
driver_binary_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Driver Binary File')
if not isinstance(driver_binary_path, tuple) or driver_binary_path[0] == '':
return
driver_line_edit.setText(str(pathlib.Path(driver_binary_path[0]).absolute()))
def show_error(self, message: str):
QtWidgets.QErrorMessage(self).showMessage(message.replace('\n', '<br>'))
def show_message(self, message: str, custom_icon=None):
message_box = QtWidgets.QMessageBox()
message_box.setWindowTitle('Message')
if custom_icon:
message_box.setIconPixmap(custom_icon.scaled(64, 64))
else:
message_box.setIcon(QtWidgets.QMessageBox.Information)
message_box.setText(message)
message_box.exec_()
def start_progress_bar(self, progress_callback):
# initialize variables early
self.upscaler.progress_bar_exit_signal = False
self.upscaler.total_frames_upscaled = 0
self.upscaler.total_frames = 1
# initialize progress bar values
upscale_begin_time = time.time()
progress_callback.emit((0, 0, 0, upscale_begin_time))
# keep querying upscaling process and feed information to callback signal
while not self.upscaler.progress_bar_exit_signal:
progress_callback.emit((int(100 * self.upscaler.total_frames_upscaled / self.upscaler.total_frames),
self.upscaler.total_frames_upscaled,
self.upscaler.total_frames,
upscale_begin_time))
time.sleep(1)
# upscale process will stop at 99%
# so it's set to 100 manually when all is done
progress_callback.emit((100, 0, 0, upscale_begin_time))
def set_progress(self, progress_information: tuple):
progress_percentage = progress_information[0]
total_frames_upscaled = progress_information[1]
total_frames = progress_information[2]
upscale_begin_time = progress_information[3]
# calculate fields based on frames and time elapsed
time_elapsed = time.time() - upscale_begin_time
try:
rate = total_frames_upscaled / (time.time() - upscale_begin_time)
time_remaining = (total_frames - total_frames_upscaled) / rate
except Exception:
rate = 0.0
time_remaining = 0.0
# set calculated values in GUI
self.progress_bar.setValue(progress_percentage)
self.time_elapsed_label.setText('Time Elapsed: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_elapsed))))
self.time_remaining_label.setText('Time Remaining: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_remaining))))
self.rate_label.setText('Rate (FPS): {}'.format(round(rate, 2)))
def start(self):
# start execution
try:
# start timer
self.begin_time = time.time()
# resolve input and output directories from GUI
if self.input_line_edit.text().strip() == '':
self.show_error('Input path not specified')
return
if self.output_line_edit.text().strip() == '':
self.show_error('Output path not specified')
return
input_directory = pathlib.Path(self.input_line_edit.text())
output_directory = pathlib.Path(self.output_line_edit.text())
# create thread pool for upscaler workers
self.threadpool = QThreadPool()
# load driver settings from GUI
self.resolve_driver_settings()
# load driver settings for the current driver
self.driver_settings = self.config[AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]]
# if input specified is a single file
if input_directory.is_file():
# check for input output format mismatch
if output_directory.is_dir():
self.show_error('Input and output path type mismatch\n\
Input is single file but output is directory')
raise Exception('input output path type mismatch')
if not re.search(r'.*\..*$', str(output_directory)):
self.show_error('No suffix found in output file path\n\
Suffix must be specified for FFmpeg')
raise Exception('No suffix specified')
self.upscaler = Upscaler(input_video=input_directory,
output_video=output_directory,
driver_settings=self.driver_settings,
ffmpeg_settings=self.ffmpeg_settings)
# set optional options
self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value()
self.upscaler.processes = self.processes_spin_box.value()
self.upscaler.video2x_cache_directory = pathlib.Path(self.cache_line_edit.text())
self.upscaler.image_format = self.config['video2x']['image_format'].lower()
self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.checkState())
# start progress bar
if AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] != 'anime4kcpp':
progress_bar_worker = ProgressBarWorker(self.start_progress_bar)
progress_bar_worker.signals.progress.connect(self.set_progress)
self.threadpool.start(progress_bar_worker)
# run upscaler
worker = UpscalerWorker(self.upscaler.run)
worker.signals.error.connect(self.upscale_errored)
worker.signals.finished.connect(self.upscale_completed)
self.threadpool.start(worker)
self.start_button.setEnabled(False)
# self.stop_button.setEnabled(True)
# if input specified is a directory
elif input_directory.is_dir():
# upscale videos in a directory
# make output directory if it doesn't exist
output_directory.mkdir(parents=True, exist_ok=True)
for input_video in [f for f in input_directory.iterdir() if f.is_file()]:
output_video = output_directory / input_video.name
self.upscaler = Upscaler(input_video=input_video,
output_video=output_video,
driver_settings=self.driver_settings,
ffmpeg_settings=self.ffmpeg_settings)
# set optional options
self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]
self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value()
self.upscaler.processes = self.processes_spin_box.value()
self.upscaler.video2x_cache_directory = pathlib.Path(self.cache_line_edit.text())
self.upscaler.image_format = self.config['video2x']['image_format'].lower()
self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.checkState())
# start progress bar
if AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] != 'anime4kcpp':
progress_bar_worker = ProgressBarWorker(self.start_progress_bar)
self.threadpool.start(progress_bar_worker)
# run upscaler
worker = UpscalerWorker(self.upscaler.run)
worker.signals.error.connect(self.upscale_errored)
worker.signals.finished.connect(self.upscale_completed)
self.threadpool.start(worker)
self.start_button.setEnabled(False)
# self.stop_button.setEnabled(True)
else:
self.show_error('Input path is neither a file nor a directory')
raise FileNotFoundError(f'{input_directory} is neither file nor directory')
except Exception:
self.upscale_errored(traceback.format_exc())
self.upscale_completed()
def upscale_errored(self, error_message):
self.show_error(f'Upscaler ran into an error:\n{error_message}')
# try cleaning up temp directories
with contextlib.suppress(Exception):
self.upscaler.progress_bar_exit_signal = True
self.upscaler.cleanup_temp_directories()
def upscale_completed(self):
# if all threads have finished
if self.threadpool.activeThreadCount() == 0:
self.show_message('Program completed, taking {} seconds'.format(round((time.time() - self.begin_time), 5)))
# remove Video2X cache directory
with contextlib.suppress(FileNotFoundError):
if not bool(self.preserve_frames_check_box.checkState()):
shutil.rmtree(pathlib.Path(self.cache_line_edit.text()))
self.start_button.setEnabled(True)
def stop(self):
# TODO unimplemented yet
pass
app = QtWidgets.QApplication(sys.argv)
window = Video2XMainWindow()
window.show()
app.exec_()

View File

@@ -0,0 +1,3 @@
{
"files": ["video2x_gui.ui","video2x_gui.py"]
}

1294
src/video2x_gui.ui Normal file

File diff suppressed because it is too large Load Diff

176
bin/video2x_setup.py → src/video2x_setup.py Normal file → Executable file
View File

@@ -2,17 +2,12 @@
# -*- coding: utf-8 -*-
"""
Name: Video2X Setup Script
Author: K4YT3X
Author: BrianPetkovsek
Creator: K4YT3X
Date Created: November 28, 2018
Last Modified: August 20, 2019
Last Modified: May 5, 2020
Dev: SAT3LL
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
(C) 2018-2019 K4YT3X
Editor: BrianPetkovsek
Editor: SAT3LL
Description: This script helps installing all dependencies of video2x
and generates a configuration for it.
@@ -22,13 +17,14 @@ Installation Details:
- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe
- waifu2x-cpp-converter: %LOCALAPPDATA%\\video2x\\waifu2x-converter-cpp
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
- anime4k: %LOCALAPPDATA%\\video2x\\anime4k
- anime4kcpp: %LOCALAPPDATA%\\video2x\\anime4kcpp
- srmd_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\srmd-ncnn-vulkan
"""
# built-in imports
from datetime import timedelta
import argparse
import contextlib
import json
import os
import pathlib
import re
@@ -36,6 +32,7 @@ import shutil
import subprocess
import sys
import tempfile
import time
import traceback
import urllib
import zipfile
@@ -45,14 +42,15 @@ import zipfile
# later in the script.
# import requests
VERSION = '1.5.0'
VERSION = '1.8.0'
# global static variables
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
DRIVER_OPTIONS = ['all', 'waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
VIDEO2X_CONFIG = pathlib.Path(__file__).parent.absolute() / 'video2x.yaml'
DRIVER_OPTIONS = ['all', 'ffmpeg', 'waifu2x_caffe', 'waifu2x_converter_cpp', 'waifu2x_ncnn_vulkan', 'anime4kcpp', 'srmd_ncnn_vulkan']
def process_arguments():
def parse_arguments():
"""Processes CLI arguments
"""
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@@ -82,22 +80,25 @@ class Video2xSetup:
print('\nInstalling Python libraries')
self._install_python_requirements()
print('\nInstalling FFmpeg')
self._install_ffmpeg()
if self.driver == 'all':
self._install_ffmpeg()
self._install_waifu2x_caffe()
self._install_waifu2x_converter_cpp()
self._install_waifu2x_ncnn_vulkan()
self._install_anime4k()
self._install_anime4kcpp()
self._install_srmd_ncnn_vulkan()
elif self.driver == 'ffmpeg':
self._install_ffmpeg()
elif self.driver == 'waifu2x_caffe':
self._install_waifu2x_caffe()
elif self.driver == 'waifu2x_converter':
elif self.driver == 'waifu2x_converter_cpp':
self._install_waifu2x_converter_cpp()
elif self.driver == 'waifu2x_ncnn_vulkan':
self._install_waifu2x_ncnn_vulkan()
elif self.driver == 'anime4k':
self._install_anime4k()
elif self.driver == 'anime4kcpp':
self._install_anime4kcpp()
elif self.driver == 'srmd_ncnn_vulkan':
self._install_srmd_ncnn_vulkan()
print('\nGenerating Video2X configuration file')
self._generate_config()
@@ -128,6 +129,8 @@ class Video2xSetup:
def _install_ffmpeg(self):
""" Install FFMPEG
"""
print('\nInstalling FFmpeg')
latest_release = 'https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip'
ffmpeg_zip = download(latest_release, tempfile.gettempdir())
@@ -196,65 +199,87 @@ class Video2xSetup:
# rename the newly extracted directory
(LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(waifu2x_ncnn_vulkan_directory)
def _install_anime4k(self):
""" Install Anime4K
def _install_anime4kcpp(self):
""" Install Anime4KCPP
"""
print('\nInstalling Anime4K')
print('\nInstalling Anime4KCPP')
"""
import pyunpack
import requests
# get latest release of Anime4K via Github API
# at the time of writing this portion, Anime4K doesn't yet have a stable release
# get latest release of Anime4KCPP via Github API
# at the time of writing this portion, Anime4KCPP doesn't yet have a stable release
# therefore releases/latest won't work
latest_release = requests.get('https://api.github.com/repos/bloc97/Anime4K/releases').json()[0]
latest_release = requests.get('https://api.github.com/repos/TianZerL/Anime4KCPP/releases/latest').json()
for a in latest_release['assets']:
if 'Anime4K_Java.zip' in a['browser_download_url']:
anime4k_zip = download(a['browser_download_url'], tempfile.gettempdir())
self.trash.append(anime4k_zip)
"""
# since Java pre-compiled release has been removed from download
# page, we use this cached version as a temporary solution
anime4k_zip = download('https://files.flexio.org/Resources/anime4k.zip', tempfile.gettempdir())
self.trash.append(anime4k_zip)
if re.search(r'Anime4KCPP_CLI-.*-Win64-msvc\.7z', a['browser_download_url']):
anime4kcpp_zip = download(a['browser_download_url'], tempfile.gettempdir())
self.trash.append(anime4kcpp_zip)
# extract and rename
with zipfile.ZipFile(anime4k_zip) as zipf:
zipf.extractall(LOCALAPPDATA / 'video2x' / 'anime4k')
# with py7zr.SevenZipFile(anime4kcpp_zip, mode='r') as archive:
(LOCALAPPDATA / 'video2x' / 'anime4kcpp').mkdir(parents=True, exist_ok=True)
pyunpack.Archive(anime4kcpp_zip).extractall(LOCALAPPDATA / 'video2x' / 'anime4kcpp')
def _install_srmd_ncnn_vulkan(self):
""" Install srmd-ncnn-vulkan
"""
print('\nInstalling srmd-ncnn-vulkan')
import requests
# Get latest release of srmd-ncnn-vulkan via Github API
latest_release = requests.get('https://api.github.com/repos/nihui/srmd-ncnn-vulkan/releases/latest').json()
for a in latest_release['assets']:
if re.search(r'srmd-ncnn-vulkan-\d*\.zip', a['browser_download_url']):
srmd_ncnn_vulkan_zip = download(a['browser_download_url'], tempfile.gettempdir())
self.trash.append(srmd_ncnn_vulkan_zip)
# extract and rename
srmd_ncnn_vulkan_directory = LOCALAPPDATA / 'video2x' / 'srmd-ncnn-vulkan'
with zipfile.ZipFile(srmd_ncnn_vulkan_zip) as zipf:
zipf.extractall(LOCALAPPDATA / 'video2x')
# if directory already exists, remove it
if srmd_ncnn_vulkan_directory.exists():
shutil.rmtree(srmd_ncnn_vulkan_directory)
# rename the newly extracted directory
(LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(srmd_ncnn_vulkan_directory)
def _generate_config(self):
""" Generate video2x config
"""
# Open current video2x.json file as template
with open('video2x.json', 'r') as template:
template_dict = json.load(template)
import yaml
# open current video2x configuration file as template
with open(VIDEO2X_CONFIG, 'r') as template:
template_dict = yaml.load(template, Loader=yaml.FullLoader)
template.close()
# configure only the specified drivers
if self.driver == 'all':
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
template_dict['waifu2x_converter']['waifu2x_converter_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
template_dict['anime4k']['anime4k_path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI')
elif self.driver == 'waifu2x_caffe':
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
elif self.driver == 'waifu2x_converter':
template_dict['waifu2x_converter']['waifu2x_converter_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
elif self.driver == 'waifu2x_converter_cpp':
template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp' / 'waifu2x-converter-cpp')
elif self.driver == 'waifu2x_ncnn_vulkan':
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
elif self.driver == 'anime4k':
template_dict['anime4k']['anime4k_path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
elif self.driver == 'anime4kcpp':
template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI')
template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin')
template_dict['video2x']['video2x_cache_directory'] = None
template_dict['video2x']['preserve_frames'] = False
# Write configuration into file
with open('video2x.json', 'w') as config:
json.dump(template_dict, config, indent=2)
config.close()
# write configuration into file
with open(VIDEO2X_CONFIG, 'w') as config:
yaml.dump(template_dict, config)
def download(url, save_path, chunk_size=4096):
@@ -321,7 +346,20 @@ def pip_install(file):
if __name__ == '__main__':
try:
args = process_arguments()
# set default exit code
EXIT_CODE = 0
# get start time
start_time = time.time()
# check platform
if sys.platform != 'win32':
print('This script is currently only compatible with Windows')
EXIT_CODE = 1
sys.exit(1)
# parse command line arguments
args = parse_arguments()
print('Video2X Setup Script')
print(f'Version: {VERSION}')
@@ -335,8 +373,20 @@ if __name__ == '__main__':
setup = Video2xSetup(args.driver, download_python_modules)
setup.run()
print('\nScript finished successfully')
except Exception:
except SystemExit:
pass
# if PermissionError is raised
# user needs to run this with higher privilege
except PermissionError:
traceback.print_exc()
print('You might have insufficient privilege for this script to run')
print('Try running this script with Administrator privileges')
EXIT_CODE = 1
# for any exception in the script
except Exception:
traceback.print_exc()
print('An error has occurred')
print('Video2X Automatic Setup has failed')
@@ -348,4 +398,12 @@ if __name__ == '__main__':
traceback.print_exc()
print('An error occurred while trying to cleanup files')
exit(1)
EXIT_CODE = 1
# regardless if script finishes successfully or not
# print script execution summary
finally:
print('Script finished')
print(f'Time taken: {timedelta(seconds=round(time.time() - start_time))}')
input('Press [ENTER] to exit script')
sys.exit(EXIT_CODE)

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x Caffe Driver
Author: K4YT3X
Date Created: May 3, 2020
Last Modified: May 4, 2020
Description: This class is a high-level wrapper
for waifu2x-caffe.
"""
# built-in imports
import argparse
import os
import shlex
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class WrapperMain:
""" Anime4K CPP wrapper
"""
def __init__(self, driver_settings):
self.driver_settings = driver_settings
self.print_lock = threading.Lock()
@staticmethod
def parse_arguments(arguments):
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
parser.add_argument('--help', action='help', help='show this help message and exit')
# parser.add_argument('-i', '--input', type=pathlib.Path, help='File for loading')
# parser.add_argument('-o', '--output', type=pathlib.Path, help='File for outputting')
parser.add_argument('-p', '--passes', type=int, help='Passes for processing')
parser.add_argument('-n', '--pushColorCount', type=int, help='Limit the number of color pushes')
parser.add_argument('-c', '--strengthColor', type=float, help='Strength for pushing color,range 0 to 1,higher for thinner')
parser.add_argument('-g', '--strengthGradient', type=float, help='Strength for pushing gradient,range 0 to 1,higher for sharper')
parser.add_argument('-z', '--zoomFactor', type=float, help='zoom factor for resizing')
parser.add_argument('-t', '--threads', type=int, help='Threads count for video processing')
parser.add_argument('-f', '--fastMode', action='store_true', help='Faster but maybe low quality')
# parser.add_argument('-v', '--videoMode', action='store_true', help='Video process')
parser.add_argument('-s', '--preview', action='store_true', help='Preview image')
parser.add_argument('-b', '--preProcessing', action='store_true', help='Enable pre processing')
parser.add_argument('-a', '--postProcessing', action='store_true', help='Enable post processing')
parser.add_argument('-r', '--preFilters', type=int, help='Enhancement filter, only working when preProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D)')
parser.add_argument('-e', '--postFilters', type=int, help='Enhancement filter, only working when postProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D), so you can put 40 to enable Gaussian blur weak and Bilateral filter, which also is what I recommend for image that < 1080P, 48 for image that >= 1080P, and for performance I recommend to use 72 for video that < 1080P, 80 for video that >=1080P')
parser.add_argument('-q', '--GPUMode', action='store_true', help='Enable GPU acceleration')
parser.add_argument('-l', '--listGPUs', action='store_true', help='list GPUs')
parser.add_argument('-h', '--platformID', type=int, help='Specify the platform ID')
parser.add_argument('-d', '--deviceID', type=int, help='Specify the device ID')
return parser.parse_args(arguments)
def upscale(self, input_file, output_file, zoom_factor, threads):
"""This is the core function for WAIFU2X class
Arguments:
input_file {string} -- source directory path
output_file {string} -- output directory path
width {int} -- output video width
height {int} -- output video height
"""
# overwrite config file settings
self.driver_settings['input'] = input_file
self.driver_settings['output'] = output_file
self.driver_settings['zoomFactor'] = zoom_factor
self.driver_settings['threads'] = threads
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [self.driver_settings.pop('path')]
for key in self.driver_settings.keys():
value = self.driver_settings[key]
# null or None means that leave this option out (keep default)
if value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is not True:
execute.append(str(value))
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -4,7 +4,7 @@
Name: Video2X FFmpeg Controller
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: August 15, 2019
Last Modified: November 15, 2019
Description: This class handles all FFmpeg related operations.
"""
@@ -30,8 +30,8 @@ class Ffmpeg:
self.ffmpeg_settings = ffmpeg_settings
self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path'])
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg.exe'
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe.exe'
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg'
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe'
self.image_format = image_format
self.pixel_format = None
@@ -67,7 +67,7 @@ class Ffmpeg:
pass
# print pixel formats for debugging
Avalon.debug_info(pixel_formats)
Avalon.debug_info(str(pixel_formats))
return pixel_formats
@@ -284,4 +284,4 @@ class Ffmpeg:
Avalon.debug_info(f'Executing: {execute}')
return subprocess.run(execute, shell=True, check=True).returncode
return subprocess.run(execute, check=True).returncode

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: SRMD NCNN Vulkan Driver
Creator: K4YT3X
Date Created: April 26, 2020
Last Modified: May 5, 2020
Description: This class is a high-level wrapper
for srmd_ncnn_vulkan.
"""
# built-in imports
import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class WrapperMain:
"""This class communicates with SRMD NCNN Vulkan engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, driver_settings):
self.driver_settings = driver_settings
self.print_lock = threading.Lock()
@staticmethod
def parse_arguments(arguments):
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
parser.add_argument('--help', action='help', help='show this help message and exit')
parser.add_argument('-v', action='store_true', help='verbose output')
# parser.add_argument('-i', type=pathlib.Path, help='input image path (jpg/png) or directory')
# parser.add_argument('-o', type=pathlib.Path, help='output image path (png) or directory')
parser.add_argument('-n', type=int, choices=range(-1, 11), help='denoise level')
parser.add_argument('-s', type=int, choices=range(2, 5), help='upscale ratio')
parser.add_argument('-t', type=int, help='tile size (>=32)')
parser.add_argument('-m', type=str, help='srmd model path')
parser.add_argument('-g', type=int, help='gpu device to use')
parser.add_argument('-j', type=str, help='thread count for load/proc/save')
parser.add_argument('-x', action='store_true', help='enable tta mode')
return parser.parse_args(arguments)
def upscale(self, input_directory, output_directory, scale_ratio):
"""This is the core function for SRMD ncnn Vulkan class
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
ratio {int} -- output video ratio
"""
# overwrite config file settings
self.driver_settings['i'] = input_directory
self.driver_settings['o'] = output_directory
self.driver_settings['s'] = scale_ratio
# by default, srmd-ncnn-vulkan will look for the models under the current working directory
# change the working directory to its containing folder if model directory not specified
if self.driver_settings['m'] is None and platform.system() == 'Windows':
os.chdir(pathlib.Path(self.driver_settings['path']).parent)
# list to be executed
# initialize the list with the binary path as the first element
execute = [self.driver_settings.pop('path')]
for key in self.driver_settings.keys():
value = self.driver_settings[key]
# null or None means that leave this option out (keep default)
if value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is not True:
execute.append(str(value))
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x Caffe Driver
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: May 4, 2020
Description: This class is a high-level wrapper
for waifu2x-caffe.
"""
# built-in imports
import argparse
import os
import shlex
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class WrapperMain:
"""This class communicates with waifu2x cui engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, driver_settings):
self.driver_settings = driver_settings
self.print_lock = threading.Lock()
@staticmethod
def parse_arguments(arguments):
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
parser.add_argument('--help', action='help', help='show this help message and exit')
parser.add_argument('-t', '--tta', type=int, choices=range(2), help='8x slower and slightly high quality')
parser.add_argument('--gpu', type=int, help='gpu device no')
parser.add_argument('-b', '--batch_size', type=int, help='input batch size')
parser.add_argument('--crop_h', type=int, help='input image split size(height)')
parser.add_argument('--crop_w', type=int, help='input image split size(width)')
parser.add_argument('-c', '--crop_size', type=int, help='input image split size')
parser.add_argument('-d', '--output_depth', type=int, help='output image chaneel depth bit')
parser.add_argument('-q', '--output_quality', type=int, help='output image quality')
parser.add_argument('-p', '--process', choices=['cpu', 'gpu', 'cudnn'], help='process mode')
parser.add_argument('--model_dir', type=str, help='path to custom model directory (don\'t append last / )')
parser.add_argument('-h', '--scale_height', type=int, help='custom scale height')
parser.add_argument('-w', '--scale_width', type=int, help='custom scale width')
parser.add_argument('-s', '--scale_ratio', type=float, help='custom scale ratio')
parser.add_argument('-n', '--noise_level', type=int, choices=range(4), help='noise reduction level')
parser.add_argument('-m', '--mode', choices=['noise', 'scale', 'noise_scale'], help='image processing mode')
parser.add_argument('-e', '--output_extension', type=str, help='extention to output image file when output_path is (auto) or input_path is folder')
parser.add_argument('-l', '--input_extention_list', type=str, help='extention to input image file when input_path is folder')
# parser.add_argument('-o', '--output', type=pathlib.Path, help='path to output image file (when input_path is folder, output_path must be folder)')
# parser.add_argument('-i', '--input_file', type=pathlib.Path, help='(required) path to input image file')
return parser.parse_args(arguments)
def upscale(self, input_directory, output_directory, scale_ratio, scale_width, scale_height, image_format, bit_depth):
"""This is the core function for WAIFU2X class
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
width {int} -- output video width
height {int} -- output video height
"""
# overwrite config file settings
self.driver_settings['input_path'] = input_directory
self.driver_settings['output_path'] = output_directory
if scale_ratio:
self.driver_settings['scale_ratio'] = scale_ratio
elif scale_width and scale_height:
self.driver_settings['scale_width'] = scale_width
self.driver_settings['scale_height'] = scale_height
self.driver_settings['output_extention'] = image_format
self.driver_settings['output_depth'] = bit_depth
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [self.driver_settings.pop('path')]
for key in self.driver_settings.keys():
value = self.driver_settings[key]
# null or None means that leave this option out (keep default)
if value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is not True:
execute.append(str(value))
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -0,0 +1,116 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x Converter CPP Driver
Author: K4YT3X
Date Created: February 8, 2019
Last Modified: May 4, 2020
Description: This class is a high-level wrapper
for waifu2x-converter-cpp.
"""
# built-in imports
import argparse
import os
import pathlib
import shlex
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class WrapperMain:
"""This class communicates with waifu2x cui engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, driver_settings):
self.driver_settings = driver_settings
self.print_lock = threading.Lock()
@staticmethod
def parse_arguments(arguments):
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
parser.add_argument('--help', action='help', help='show this help message and exit')
parser.add_argument('--list-supported-formats', action='store_true', help='dump currently supported format list')
parser.add_argument('--list-opencv-formats', action='store_true', help='(deprecated. Use --list-supported-formats) dump opencv supported format list')
parser.add_argument('-l', '--list-processor', action='store_true', help='dump processor list')
parser.add_argument('-f', '--output-format', choices=['png', 'jpg'], help='The format used when running in recursive/folder mode\nSee --list-supported-formats for a list of supported formats/extensions.')
parser.add_argument('-c', '--png-compression', type=int, choices=range(10), help='Set PNG compression level (0-9), 9 = Max compression (slowest & smallest)')
parser.add_argument('-q', '--image-quality', type=int, choices=range(100), help='JPEG & WebP Compression quality (0-101, 0 being smallest size and lowest quality), use 101 for lossless WebP')
parser.add_argument('--block-size', type=int, help='block size')
parser.add_argument('--disable-gpu', action='store_true', help='disable GPU')
parser.add_argument('--force-OpenCL', action='store_true', help='force to use OpenCL on Intel Platform')
parser.add_argument('-p', '--processor', type=int, help='set target processor')
parser.add_argument('-j', '--jobs', type=int, help='number of threads launching at the same time')
parser.add_argument('--model-dir', type=str, help='path to custom model directory (don\'t append last / )')
parser.add_argument('--scale-ratio', type=float, help='custom scale ratio')
parser.add_argument('--noise-level', type=int, choices=range(4), help='noise reduction level')
parser.add_argument('-m', '--mode', choices=['noise', 'scale', 'noise-scale'], help='image processing mode')
parser.add_argument('-v', '--log-level', type=int, choices=range(5), help='Set log level')
parser.add_argument('-s', '--silent', action='store_true', help='Enable silent mode. (same as --log-level 1)')
parser.add_argument('-t', '--tta', type=int, choices=range(2), help='Enable Test-Time Augmentation mode.')
parser.add_argument('-g', '--generate-subdir', type=int, choices=range(2), help='Generate sub folder when recursive directory is enabled.')
parser.add_argument('-a', '--auto-naming', type=int, choices=range(2), help='Add postfix to output name when output path is not specified.\nSet 0 to disable this.')
parser.add_argument('-r', '--recursive-directory', type=int, choices=range(2), help='Search recursively through directories to find more images to process.')
# parser.add_argument('-o', '--output', type=pathlib.Pathh, help='path to output image file or directory (you should use the full path)')
# parser.add_argument('-i', '--input', type=pathlib.Path, help='(required) path to input image file or directory (you should use the full path)')
parser.add_argument('--version', action='store_true', help='Displays version information and exits.')
return parser.parse_args(arguments)
def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format):
""" Waifu2x Converter Driver Upscaler
This method executes the upscaling of extracted frames.
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
scale_ratio {int} -- frames' scale ratio
threads {int} -- number of threads
"""
# overwrite config file settings
self.driver_settings['input'] = input_directory
self.driver_settings['output'] = output_directory
self.driver_settings['scale-ratio'] = scale_ratio
self.driver_settings['jobs'] = jobs
self.driver_settings['output-format'] = image_format
# models_rgb must be specified manually for waifu2x-converter-cpp
# if it's not specified in the arguments, create automatically
if self.driver_settings['model-dir'] is None:
self.driver_settings['model-dir'] = pathlib.Path(self.driver_settings['path']).parent / 'models_rgb'
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [self.driver_settings.pop('path')]
for key in self.driver_settings.keys():
value = self.driver_settings[key]
# null or None means that leave this option out (keep default)
if value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is not True:
execute.append(str(value))
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Name: Waifu2x NCNN Vulkan Driver
Creator: SAT3LL
Date Created: June 26, 2019
Last Modified: May 5, 2020
Editor: K4YT3X
Last Modified: February 22, 2020
Description: This class is a high-level wrapper
for waifu2x_ncnn_vulkan.
"""
# built-in imports
import argparse
import os
import pathlib
import platform
import shlex
import subprocess
import threading
# third-party imports
from avalon_framework import Avalon
class WrapperMain:
"""This class communicates with waifu2x ncnn vulkan engine
An object will be created for this class, containing information
about the binary address and the processing method. When being called
by the main program, other detailed information will be passed to
the upscale function.
"""
def __init__(self, driver_settings):
self.driver_settings = driver_settings
self.print_lock = threading.Lock()
@staticmethod
def parse_arguments(arguments):
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False)
parser.add_argument('--help', action='help', help='show this help message and exit')
parser.add_argument('-v', action='store_true', help='verbose output')
# parser.add_argument('-i', type=pathlib.Path, help='input image path (jpg/png) or directory')
# parser.add_argument('-o', type=pathlib.Path, help='output image path (png) or directory')
parser.add_argument('-n', type=int, choices=range(-1, 4), help='denoise level')
parser.add_argument('-s', type=int, choices=range(1, 3), help='upscale ratio')
parser.add_argument('-t', type=int, help='tile size (>=32)')
parser.add_argument('-m', type=str, help='waifu2x model path')
parser.add_argument('-g', type=int, help='gpu device to use')
parser.add_argument('-j', type=str, help='thread count for load/proc/save')
parser.add_argument('-x', action='store_true', help='enable tta mode')
return parser.parse_args(arguments)
def upscale(self, input_directory, output_directory, scale_ratio):
"""This is the core function for WAIFU2X class
Arguments:
input_directory {string} -- source directory path
output_directory {string} -- output directory path
ratio {int} -- output video ratio
"""
# overwrite config file settings
self.driver_settings['i'] = input_directory
self.driver_settings['o'] = output_directory
self.driver_settings['s'] = int(scale_ratio)
# by default, waifu2x-ncnn-vulkan will look for the models under the current working directory
# change the working directory to its containing folder if model directory not specified
if self.driver_settings['m'] is None and platform.system() == 'Windows':
os.chdir(pathlib.Path(self.driver_settings['path']).parent)
# list to be executed
# initialize the list with waifu2x binary path as the first element
execute = [self.driver_settings.pop('path')]
for key in self.driver_settings.keys():
value = self.driver_settings[key]
# null or None means that leave this option out (keep default)
if value is None or value is False:
continue
else:
if len(key) == 1:
execute.append(f'-{key}')
else:
execute.append(f'--{key}')
# true means key is an option
if value is not True:
execute.append(str(value))
# return the Popen object of the new process created
self.print_lock.acquire()
Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {shlex.join(execute)}')
self.print_lock.release()
return subprocess.Popen(execute)