mirror of
https://github.com/k4yt3x/video2x.git
synced 2026-02-25 08:54:44 +08:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5db2cfd2a3 | ||
|
|
652b9f34bd | ||
|
|
f3c50ea728 | ||
|
|
880087241d | ||
|
|
067e17290a | ||
|
|
fc675e7899 | ||
|
|
2d81270e44 | ||
|
|
3ace2447c2 | ||
|
|
30b0e2c7bf | ||
|
|
d54fea0310 | ||
|
|
332055a4e5 | ||
|
|
19e17b1a8f | ||
|
|
9e745fb747 | ||
|
|
fd5edead7e | ||
|
|
aa7c0b3f12 | ||
|
|
65cc4c6afb | ||
|
|
bd2da021bd | ||
|
|
7e888db7b2 | ||
|
|
7726a86e35 | ||
|
|
a0595136ab | ||
|
|
5c93a5a73b | ||
|
|
7b9b87fa9b | ||
|
|
cb255da65c | ||
|
|
d503325a62 | ||
|
|
954233c238 | ||
|
|
95416f68a8 | ||
|
|
dce778b3bf | ||
|
|
c537dd726c | ||
|
|
b1918a4a8a | ||
|
|
7ae9618785 | ||
|
|
eb3d29103c | ||
|
|
a188f6ebda | ||
|
|
fd67dfca11 | ||
|
|
b1f29f1098 | ||
|
|
fe7c0c840d | ||
|
|
dc2410d4da | ||
|
|
91ac512d57 | ||
|
|
732288f075 | ||
|
|
e812c228c3 | ||
|
|
9841fa9577 | ||
|
|
5391f59847 | ||
|
|
6180ccd9b2 | ||
|
|
e10315f89e | ||
|
|
6521e071ce | ||
|
|
a685e91b14 | ||
|
|
9d72bad314 | ||
|
|
2b63b5f0a6 | ||
|
|
9c84228c17 | ||
|
|
f9bb191336 | ||
|
|
7a3af366f8 | ||
|
|
d4223f93ec | ||
|
|
c5c764a91f | ||
|
|
ba6472c9fc | ||
|
|
dc1244580f | ||
|
|
0347d05127 | ||
|
|
45f5bf91e6 | ||
|
|
5e78beabcf | ||
|
|
6c5b7ea17f | ||
|
|
d9610abae0 | ||
|
|
47471352ba | ||
|
|
969cfe5c0d | ||
|
|
e12db41213 | ||
|
|
7a69d58e28 | ||
|
|
ad6308d385 | ||
|
|
8c3a85ac42 | ||
|
|
db603062e9 | ||
|
|
807276fcc3 | ||
|
|
9be46f7d62 | ||
|
|
f42a8ab71f | ||
|
|
e3b2624977 |
5
.github/ISSUE_TEMPLATE/bug-report.md
vendored
5
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -11,10 +11,11 @@ assignees: K4YT3X
|
|||||||
|
|
||||||
|Module|Version|
|
|Module|Version|
|
||||||
|-|-|
|
|-|-|
|
||||||
|`video2x`||
|
|`Video2X`||
|
||||||
|`ffmpeg`||
|
|`FFmpeg`||
|
||||||
|`waifu2x-caffe`||
|
|`waifu2x-caffe`||
|
||||||
|`waifu2x-converter-cpp`||
|
|`waifu2x-converter-cpp`||
|
||||||
|
|`waifu2x-ncnn-vulkan`||
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
|
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
### Official Discussion Group (Telegram): https://t.me/video2x
|
### Official Discussion Group (Telegram): https://t.me/video2x
|
||||||
|
|
||||||
## Download Builds (beta)
|
## 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.
|
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`, and `waifu2x-ncnn-vulkan`. 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`, 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 **`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.json` are included. To setup dependencies (e.g. `FFmpeg` and `Waifu2X`) automatically, simply launch `video2x_setup.exe`.
|
||||||
|
|
||||||
@@ -14,36 +14,39 @@ The **`light`** package provides only the most basic functions of `Video2X`. Onl
|
|||||||
|
|
||||||
Component names that are **bolded** can be automatically downloaded and configured with the `video2x_setup.py` script.
|
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
|
2. AMD GPU / Nvidia GPU
|
||||||
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
||||||
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
|
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
|
||||||
5. [**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)
|
5. One of the following drivers
|
||||||
|
- [**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)
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
|
### 3.0.0 (November 26, 2019)
|
||||||
|
|
||||||
|
- Linux compatibility
|
||||||
|
- Configuration file changed to YAML format
|
||||||
|
- You may still use a JSON-formatted config file. To do so, please specify `-c video2x.json`.
|
||||||
|
- Other code clean-up and optimization
|
||||||
|
|
||||||
|
### 2.10.0 (August 16, 2019)
|
||||||
|
|
||||||
|
- **Added support for [Anime4K](https://github.com/bloc97/Anime4K)**
|
||||||
|
|
||||||
### 2.9.0 (July 27, 2019)
|
### 2.9.0 (July 27, 2019)
|
||||||
|
|
||||||
- Changed file handling method from `os` to `pathlib`
|
- Changed file handling method from `os` to `pathlib`
|
||||||
- Removed f_string dependency and support for legacy versions of Python
|
- Removed f_string dependency and support for legacy versions of Python
|
||||||
- Organized file import statements
|
- Organized file import statements
|
||||||
|
|
||||||
### 2.8.1 (July 9, 2019)
|
### Setup Script 1.6.0 (November 26, 2019)
|
||||||
|
|
||||||
- Added automatic pixel format detection
|
- Added compatibility for new YAML configuration file
|
||||||
- Added automatic color bit depth detection
|
- Added better exception handling
|
||||||
|
|
||||||
### 2.8.0 (June 25, 2019)
|
|
||||||
|
|
||||||
- Added support for [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)
|
|
||||||
|
|
||||||
### 2.7.1 (April 18, 2019)
|
|
||||||
|
|
||||||
- Fixed video2x custom temp folder bug found by @cr08 .
|
|
||||||
|
|
||||||
### Setup Script 1.3.0 (June 25, 2019)
|
|
||||||
|
|
||||||
- Added automatic installation support for `waifu2x-ncnn-vulkan`
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
@@ -61,6 +64,8 @@ Clip is from trailer of animated movie "千と千尋の神隠し". Copyright bel
|
|||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -97,6 +102,10 @@ Download: https://ffmpeg.org/download.html
|
|||||||
Download: https://github.com/lltcggie/waifu2x-caffe/releases
|
Download: https://github.com/lltcggie/waifu2x-caffe/releases
|
||||||
- **waifu2x-converter-cpp** (required for AMD, OpenCL and OpenGL processing)
|
- **waifu2x-converter-cpp** (required for AMD, OpenCL and OpenGL processing)
|
||||||
Download: https://github.com/DeadSix27/waifu2x-converter-cpp/releases
|
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
|
||||||
|
|
||||||
### Installing Dependencies
|
### Installing Dependencies
|
||||||
|
|
||||||
@@ -144,7 +153,7 @@ Enlarge the video to 1920x1080 using CUDA. You may also use the `-r/--ratio` opt
|
|||||||
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 -m gpu --width=1920 --height=1080
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nvidia CNDNN
|
### Nvidia CUDNN
|
||||||
|
|
||||||
Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option.
|
Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option.
|
||||||
|
|
||||||
@@ -166,9 +175,17 @@ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2
|
|||||||
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_ncnn_vulkan
|
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_ncnn_vulkan
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Anime4K
|
||||||
|
|
||||||
|
Enlarge the video by 2 times using Anime4K. **Remember to install and configure [JRE 12](https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase12-5440181.html) path in the configuration file.**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -r 2 -d anime4k
|
||||||
|
```
|
||||||
|
|
||||||
### CPU
|
### CPU
|
||||||
|
|
||||||
Enlarge the video to 1920x1080 using the CPU. You may also use the `-r/--ratio` option. This is potentially much slower than using a GPU. The configuration file for this method is similar to the previous methods.
|
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
|
```shell
|
||||||
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080
|
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080
|
||||||
@@ -231,7 +248,7 @@ https://www.gnu.org/licenses/gpl-3.0.txt
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
(C) 2018-2019 K4YT3X
|
(C) 2018-2020 K4YT3X
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
@@ -241,6 +258,7 @@ This project relies on the following software and projects.
|
|||||||
- [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe)
|
- [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe)
|
||||||
- [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp)
|
- [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp)
|
||||||
- [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)
|
- [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)
|
||||||
|
- [Anime4K](https://github.com/bloc97/Anime4K)
|
||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|
||||||
|
|||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-slate
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Name: Video2x GUI
|
|
||||||
Author: K4YT3X
|
|
||||||
Date Created: July 27, 2019
|
|
||||||
Last Modified: July 27, 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
|
|
||||||
import tkinter as tk
|
|
||||||
|
|
||||||
VERSION = '1.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
}
|
|
||||||
|
|
||||||
IMAGE_FORMATS = {'PNG', 'JPG'}
|
|
||||||
|
|
||||||
|
|
||||||
class Video2xGui():
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
# create main window
|
|
||||||
self.main_window = Tk()
|
|
||||||
self.main_window.title('Video2X GUI')
|
|
||||||
self.main_frame = Frame()
|
|
||||||
self.main_frame.pack(fill=BOTH, expand=True)
|
|
||||||
|
|
||||||
# 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 _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() == 'waifu2x_converter' or self.driver.get() == 'waifu2x_ncnn_vulkan') and self.width.get() and self.height.get():
|
|
||||||
messagebox.showerror('Error', 'Waifu2x Converter CPP/NCNN 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):
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
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'])
|
|
||||||
|
|
||||||
# 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', 'Upscaling Completed')
|
|
||||||
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'))
|
|
||||||
self.output_file.set(f'{self.input_file.get()}_output.mp4')
|
|
||||||
|
|
||||||
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()
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Name: Waifu2x NCNN Vulkan Driver
|
|
||||||
Author: SAT3LL
|
|
||||||
Date Created: June 26, 2019
|
|
||||||
Last Modified: July 27, 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['input'] = input_directory
|
|
||||||
self.waifu2x_settings['output'] = output_directory
|
|
||||||
|
|
||||||
# print thread start message
|
|
||||||
self.print_lock.acquire()
|
|
||||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
|
|
||||||
self.print_lock.release()
|
|
||||||
|
|
||||||
# waifu2x_ncnn_vulkan does not have long-opts, we'll have a dictionary that maps "our" config long-opt
|
|
||||||
# names to their short opts
|
|
||||||
waifu2x_ncnn_vulkan_opt_flag = {
|
|
||||||
'input': '-i',
|
|
||||||
'output': '-o',
|
|
||||||
'noise-level': '-n',
|
|
||||||
'scale-ratio': '-s',
|
|
||||||
'tile-size': '-t',
|
|
||||||
'model-path': '-m',
|
|
||||||
'gpu': '-g',
|
|
||||||
'load-proc-save_threads': '-j',
|
|
||||||
'verbose': '-v'
|
|
||||||
}
|
|
||||||
|
|
||||||
execute = [self.waifu2x_settings['waifu2x_ncnn_vulkan_path']]
|
|
||||||
for key in self.waifu2x_settings.keys():
|
|
||||||
value = self.waifu2x_settings[key]
|
|
||||||
if key == 'waifu2x_ncnn_vulkan_path':
|
|
||||||
continue
|
|
||||||
elif key == 'input':
|
|
||||||
execute.append(waifu2x_ncnn_vulkan_opt_flag[key])
|
|
||||||
execute.append(input_directory)
|
|
||||||
elif key == 'output':
|
|
||||||
execute.append(waifu2x_ncnn_vulkan_opt_flag[key])
|
|
||||||
execute.append(output_directory)
|
|
||||||
elif key == 'scale-ratio':
|
|
||||||
execute.append(waifu2x_ncnn_vulkan_opt_flag[key])
|
|
||||||
# waifu2x_ncnn_vulkan does not accept an arbitrary scale ratio, max is 2
|
|
||||||
if scale_ratio == 1:
|
|
||||||
execute.append('1')
|
|
||||||
else:
|
|
||||||
execute.append('2')
|
|
||||||
# allow upper if cases to take precedence
|
|
||||||
elif value is None or value is False:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
execute.append(waifu2x_ncnn_vulkan_opt_flag[key])
|
|
||||||
execute.append(str(value))
|
|
||||||
|
|
||||||
Avalon.debug_info(f'Executing: {execute}')
|
|
||||||
subprocess.run(execute, check=True, stderr=subprocess.DEVNULL)
|
|
||||||
|
|
||||||
# print thread exiting message
|
|
||||||
self.print_lock.acquire()
|
|
||||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
|
|
||||||
self.print_lock.release()
|
|
||||||
|
|
||||||
return 0
|
|
||||||
except Exception as e:
|
|
||||||
upscaler_exceptions.append(e)
|
|
||||||
92
src/anime4k.py
Executable file
92
src/anime4k.py
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Name: Anime4K Driver
|
||||||
|
Author: K4YT3X
|
||||||
|
Date Created: August 15, 2019
|
||||||
|
Last Modified: November 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, driver_settings):
|
||||||
|
self.driver_settings = driver_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.driver_settings['java_path'],
|
||||||
|
'-jar',
|
||||||
|
self.driver_settings['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)
|
||||||
0
bin/exceptions.py → src/exceptions.py
Normal file → Executable file
0
bin/exceptions.py → src/exceptions.py
Normal file → Executable file
39
bin/ffmpeg.py → src/ffmpeg.py
Normal file → Executable file
39
bin/ffmpeg.py → src/ffmpeg.py
Normal file → Executable file
@@ -4,14 +4,13 @@
|
|||||||
Name: Video2X FFmpeg Controller
|
Name: Video2X FFmpeg Controller
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 27, 2019
|
Last Modified: November 15, 2019
|
||||||
|
|
||||||
Description: This class handles all FFmpeg related operations.
|
Description: This class handles all FFmpeg related operations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -31,8 +30,8 @@ class Ffmpeg:
|
|||||||
self.ffmpeg_settings = ffmpeg_settings
|
self.ffmpeg_settings = ffmpeg_settings
|
||||||
|
|
||||||
self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path'])
|
self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path'])
|
||||||
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg.exe'
|
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg'
|
||||||
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe.exe'
|
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe'
|
||||||
self.image_format = image_format
|
self.image_format = image_format
|
||||||
self.pixel_format = None
|
self.pixel_format = None
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ class Ffmpeg:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# print pixel formats for debugging
|
# print pixel formats for debugging
|
||||||
Avalon.debug_info(pixel_formats)
|
Avalon.debug_info(str(pixel_formats))
|
||||||
|
|
||||||
return pixel_formats
|
return pixel_formats
|
||||||
|
|
||||||
@@ -119,6 +118,8 @@ class Ffmpeg:
|
|||||||
self.ffmpeg_binary
|
self.ffmpeg_binary
|
||||||
]
|
]
|
||||||
|
|
||||||
|
execute.extend(self._read_configuration(phase='video_to_frames'))
|
||||||
|
|
||||||
execute.extend([
|
execute.extend([
|
||||||
'-i',
|
'-i',
|
||||||
input_video
|
input_video
|
||||||
@@ -130,8 +131,6 @@ class Ffmpeg:
|
|||||||
extracted_frames / f'extracted_%0d.{self.image_format}'
|
extracted_frames / f'extracted_%0d.{self.image_format}'
|
||||||
])
|
])
|
||||||
|
|
||||||
execute.extend(self._read_configuration(phase='video_to_frames'))
|
|
||||||
|
|
||||||
self._execute(execute)
|
self._execute(execute)
|
||||||
|
|
||||||
def convert_video(self, framerate, resolution, upscaled_frames):
|
def convert_video(self, framerate, resolution, upscaled_frames):
|
||||||
@@ -152,6 +151,9 @@ class Ffmpeg:
|
|||||||
resolution
|
resolution
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# read other options
|
||||||
|
execute.extend(self._read_configuration(phase='frames_to_video'))
|
||||||
|
|
||||||
# read FFmpeg input options
|
# read FFmpeg input options
|
||||||
execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
|
execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
|
||||||
|
|
||||||
@@ -159,8 +161,7 @@ class Ffmpeg:
|
|||||||
# Dev: SAT3LL
|
# Dev: SAT3LL
|
||||||
# rename all .png.png suffixes to .png
|
# rename all .png.png suffixes to .png
|
||||||
import re
|
import re
|
||||||
import shutil
|
regex = re.compile(r'\.png\.png$', re.IGNORECASE)
|
||||||
regex = re.compile(r'\.png\.png$')
|
|
||||||
for frame_name in upscaled_frames.iterdir():
|
for frame_name in upscaled_frames.iterdir():
|
||||||
(upscaled_frames / frame_name).rename(upscaled_frames / regex.sub('.png', str(frame_name)))
|
(upscaled_frames / frame_name).rename(upscaled_frames / regex.sub('.png', str(frame_name)))
|
||||||
# END WORKAROUND
|
# END WORKAROUND
|
||||||
@@ -174,9 +175,6 @@ class Ffmpeg:
|
|||||||
# read FFmpeg output options
|
# read FFmpeg output options
|
||||||
execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
|
execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
|
||||||
|
|
||||||
# read other options
|
|
||||||
execute.extend(self._read_configuration(phase='frames_to_video'))
|
|
||||||
|
|
||||||
# specify output file location
|
# specify output file location
|
||||||
execute.extend([
|
execute.extend([
|
||||||
upscaled_frames / 'no_audio.mp4'
|
upscaled_frames / 'no_audio.mp4'
|
||||||
@@ -193,12 +191,17 @@ class Ffmpeg:
|
|||||||
upscaled_frames {string} -- directory containing upscaled frames
|
upscaled_frames {string} -- directory containing upscaled frames
|
||||||
"""
|
"""
|
||||||
execute = [
|
execute = [
|
||||||
self.ffmpeg_binary,
|
self.ffmpeg_binary
|
||||||
|
]
|
||||||
|
|
||||||
|
execute.extend(self._read_configuration(phase='migrating_tracks'))
|
||||||
|
|
||||||
|
execute.extend([
|
||||||
'-i',
|
'-i',
|
||||||
upscaled_frames / 'no_audio.mp4',
|
upscaled_frames / 'no_audio.mp4',
|
||||||
'-i',
|
'-i',
|
||||||
input_video
|
input_video
|
||||||
]
|
])
|
||||||
|
|
||||||
execute.extend(self._read_configuration(phase='migrating_tracks', section='output_options'))
|
execute.extend(self._read_configuration(phase='migrating_tracks', section='output_options'))
|
||||||
|
|
||||||
@@ -206,8 +209,6 @@ class Ffmpeg:
|
|||||||
output_video
|
output_video
|
||||||
])
|
])
|
||||||
|
|
||||||
execute.extend(self._read_configuration(phase='migrating_tracks'))
|
|
||||||
|
|
||||||
self._execute(execute)
|
self._execute(execute)
|
||||||
|
|
||||||
def _read_configuration(self, phase, section=None):
|
def _read_configuration(self, phase, section=None):
|
||||||
@@ -278,9 +279,9 @@ class Ffmpeg:
|
|||||||
Returns:
|
Returns:
|
||||||
int -- execution return code
|
int -- execution return code
|
||||||
"""
|
"""
|
||||||
Avalon.debug_info(f'Executing: {execute}')
|
|
||||||
|
|
||||||
# turn all list elements into string to avoid errors
|
# turn all list elements into string to avoid errors
|
||||||
execute = [str(e) for e in execute]
|
execute = [str(e) for e in execute]
|
||||||
|
|
||||||
return subprocess.run(execute, shell=True, check=True).returncode
|
Avalon.debug_info(f'Executing: {execute}')
|
||||||
|
|
||||||
|
return subprocess.run(execute, check=True).returncode
|
||||||
1
bin/image_cleaner.py → src/image_cleaner.py
Normal file → Executable file
1
bin/image_cleaner.py → src/image_cleaner.py
Normal file → Executable file
@@ -12,7 +12,6 @@ that have already been upscaled.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
import os
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -2,5 +2,6 @@ avalon_framework
|
|||||||
colorama
|
colorama
|
||||||
GPUtil
|
GPUtil
|
||||||
psutil
|
psutil
|
||||||
|
pyyaml
|
||||||
requests
|
requests
|
||||||
tqdm
|
tqdm
|
||||||
39
bin/upscaler.py → src/upscaler.py
Normal file → Executable file
39
bin/upscaler.py → src/upscaler.py
Normal file → Executable file
@@ -4,7 +4,7 @@
|
|||||||
Name: Video2X Upscaler
|
Name: Video2X Upscaler
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: December 10, 2018
|
Date Created: December 10, 2018
|
||||||
Last Modified: July 27, 2019
|
Last Modified: December 11, 2019
|
||||||
|
|
||||||
Dev: SAT3LL
|
Dev: SAT3LL
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
|
from anime4k import Anime4k
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
from ffmpeg import Ffmpeg
|
from ffmpeg import Ffmpeg
|
||||||
from image_cleaner import ImageCleaner
|
from image_cleaner import ImageCleaner
|
||||||
@@ -24,6 +25,7 @@ from waifu2x_ncnn_vulkan import Waifu2xNcnnVulkan
|
|||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
import contextlib
|
||||||
import copy
|
import copy
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
@@ -37,6 +39,8 @@ import traceback
|
|||||||
from avalon_framework import Avalon
|
from avalon_framework import Avalon
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
AVAILABLE_DRIVERS = ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
|
||||||
|
|
||||||
|
|
||||||
class Upscaler:
|
class Upscaler:
|
||||||
""" An instance of this class is a upscaler that will
|
""" An instance of this class is a upscaler that will
|
||||||
@@ -47,12 +51,12 @@ class Upscaler:
|
|||||||
ArgumentError -- if argument is not valid
|
ArgumentError -- if argument is not valid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, input_video, output_video, method, waifu2x_settings, ffmpeg_settings):
|
def __init__(self, input_video, output_video, method, driver_settings, ffmpeg_settings):
|
||||||
# mandatory arguments
|
# mandatory arguments
|
||||||
self.input_video = input_video
|
self.input_video = input_video
|
||||||
self.output_video = output_video
|
self.output_video = output_video
|
||||||
self.method = method
|
self.method = method
|
||||||
self.waifu2x_settings = waifu2x_settings
|
self.driver_settings = driver_settings
|
||||||
self.ffmpeg_settings = ffmpeg_settings
|
self.ffmpeg_settings = ffmpeg_settings
|
||||||
|
|
||||||
# optional arguments
|
# optional arguments
|
||||||
@@ -78,7 +82,7 @@ class Upscaler:
|
|||||||
"""delete temp directories when done
|
"""delete temp directories when done
|
||||||
"""
|
"""
|
||||||
if not self.preserve_frames:
|
if not self.preserve_frames:
|
||||||
for directory in [self.extracted_frames, self.upscaled_frames]:
|
for directory in [self.extracted_frames, self.upscaled_frames, self.video2x_cache_directory]:
|
||||||
try:
|
try:
|
||||||
# avalon framework cannot be used if python is shutting down
|
# avalon framework cannot be used if python is shutting down
|
||||||
# therefore, plain print is used
|
# therefore, plain print is used
|
||||||
@@ -122,7 +126,7 @@ class Upscaler:
|
|||||||
previous_cycle_frames = 0
|
previous_cycle_frames = 0
|
||||||
while not self.progress_bar_exit_signal:
|
while not self.progress_bar_exit_signal:
|
||||||
|
|
||||||
try:
|
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}'])
|
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
|
delta = self.total_frames_upscaled - previous_cycle_frames
|
||||||
previous_cycle_frames = self.total_frames_upscaled
|
previous_cycle_frames = self.total_frames_upscaled
|
||||||
@@ -131,10 +135,8 @@ class Upscaler:
|
|||||||
if self.total_frames_upscaled >= self.total_frames:
|
if self.total_frames_upscaled >= self.total_frames:
|
||||||
return
|
return
|
||||||
|
|
||||||
# adds the detla into the progress bar
|
# adds the delta into the progress bar
|
||||||
progress_bar.update(delta)
|
progress_bar.update(delta)
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
@@ -156,26 +158,28 @@ class Upscaler:
|
|||||||
self.upscaler_exceptions = []
|
self.upscaler_exceptions = []
|
||||||
|
|
||||||
# initialize waifu2x driver
|
# initialize waifu2x driver
|
||||||
drivers = ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan']
|
drivers = AVAILABLE_DRIVERS
|
||||||
if self.waifu2x_driver not in drivers:
|
if self.waifu2x_driver not in drivers:
|
||||||
raise UnrecognizedDriverError(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
|
raise UnrecognizedDriverError(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
|
||||||
|
|
||||||
# it's easier to do multi-threading with waifu2x_converter
|
# it's easier to do multi-threading with waifu2x_converter
|
||||||
# the number of threads can be passed directly to waifu2x_converter
|
# the number of threads can be passed directly to waifu2x_converter
|
||||||
if self.waifu2x_driver == 'waifu2x_converter':
|
if self.waifu2x_driver == 'waifu2x_converter':
|
||||||
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
|
w2 = Waifu2xConverter(self.driver_settings, self.model_dir)
|
||||||
|
|
||||||
progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],))
|
progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],))
|
||||||
progress_bar.start()
|
progress_bar.start()
|
||||||
|
|
||||||
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads, self.image_format, self.upscaler_exceptions)
|
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()]:
|
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}', image)
|
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.upscaled_frames / image).rename(self.upscaled_frames / renamed)
|
||||||
|
|
||||||
self.progress_bar_exit_signal = True
|
self.progress_bar_exit_signal = True
|
||||||
progress_bar.join()
|
progress_bar.join()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# drivers that are to be multi-threaded by video2x
|
||||||
else:
|
else:
|
||||||
# create a container for all upscaler threads
|
# create a container for all upscaler threads
|
||||||
upscaler_threads = []
|
upscaler_threads = []
|
||||||
@@ -218,7 +222,7 @@ class Upscaler:
|
|||||||
|
|
||||||
# create a separate w2 instance for each thread
|
# create a separate w2 instance for each thread
|
||||||
if self.waifu2x_driver == 'waifu2x_caffe':
|
if self.waifu2x_driver == 'waifu2x_caffe':
|
||||||
w2 = Waifu2xCaffe(copy.deepcopy(self.waifu2x_settings), self.method, self.model_dir, self.bit_depth)
|
w2 = Waifu2xCaffe(copy.deepcopy(self.driver_settings), self.method, self.model_dir, self.bit_depth)
|
||||||
if self.scale_ratio:
|
if self.scale_ratio:
|
||||||
thread = threading.Thread(target=w2.upscale,
|
thread = threading.Thread(target=w2.upscale,
|
||||||
args=(thread_info[0],
|
args=(thread_info[0],
|
||||||
@@ -240,7 +244,16 @@ class Upscaler:
|
|||||||
|
|
||||||
# if the driver being used is waifu2x_ncnn_vulkan
|
# if the driver being used is waifu2x_ncnn_vulkan
|
||||||
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
|
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
|
||||||
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings))
|
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.driver_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.driver_settings))
|
||||||
thread = threading.Thread(target=w2.upscale,
|
thread = threading.Thread(target=w2.upscale,
|
||||||
args=(thread_info[0],
|
args=(thread_info[0],
|
||||||
self.upscaled_frames,
|
self.upscaled_frames,
|
||||||
@@ -41,15 +41,19 @@
|
|||||||
},
|
},
|
||||||
"waifu2x_ncnn_vulkan": {
|
"waifu2x_ncnn_vulkan": {
|
||||||
"waifu2x_ncnn_vulkan_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-ncnn-vulkan\\waifu2x-ncnn-vulkan.exe",
|
"waifu2x_ncnn_vulkan_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-ncnn-vulkan\\waifu2x-ncnn-vulkan.exe",
|
||||||
"input": null,
|
"v": null,
|
||||||
"output": null,
|
"i": null,
|
||||||
"noise-level": 2,
|
"o": null,
|
||||||
"scale-ratio": null,
|
"n": 2,
|
||||||
"tile-size": 200,
|
"s": 2,
|
||||||
"model-path": null,
|
"t": 400,
|
||||||
"gpu": 0,
|
"m": "models-cunet",
|
||||||
"load-proc-save_threads": null,
|
"g": 0,
|
||||||
"verbose": null
|
"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": {
|
||||||
"ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-latest-win64-static\\bin",
|
"ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-latest-win64-static\\bin",
|
||||||
@@ -73,6 +77,7 @@
|
|||||||
"-b:v": null,
|
"-b:v": null,
|
||||||
"-pix_fmt": null
|
"-pix_fmt": null
|
||||||
},
|
},
|
||||||
|
"-hwaccel": "auto",
|
||||||
"-y": true
|
"-y": true
|
||||||
},
|
},
|
||||||
"migrating_tracks": {
|
"migrating_tracks": {
|
||||||
@@ -85,6 +90,7 @@
|
|||||||
"-c": "copy",
|
"-c": "copy",
|
||||||
"-pix_fmt": null
|
"-pix_fmt": null
|
||||||
},
|
},
|
||||||
|
"-hwaccel": "auto",
|
||||||
"-y": true
|
"-y": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
178
bin/video2x.py → src/video2x.py
Normal file → Executable file
178
bin/video2x.py → src/video2x.py
Normal file → Executable file
@@ -11,17 +11,17 @@ __ __ _ _ ___ __ __
|
|||||||
|
|
||||||
|
|
||||||
Name: Video2X Controller
|
Name: Video2X Controller
|
||||||
Author: K4YT3X
|
Creator: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 27, 2019
|
Last Modified: January 4, 2020
|
||||||
|
|
||||||
Dev: BrianPetkovsek
|
Editor: BrianPetkovsek
|
||||||
Dev: SAT3LL
|
Editor: SAT3LL
|
||||||
|
|
||||||
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
||||||
available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
|
||||||
(C) 2018-2019 K4YT3X
|
(C) 2018 - 2020 K4YT3X
|
||||||
|
|
||||||
Video2X is free software: you can redistribute it and/or modify
|
Video2X is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -44,13 +44,12 @@ smooth and edges sharp.
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
|
from upscaler import AVAILABLE_DRIVERS
|
||||||
from upscaler import Upscaler
|
from upscaler import Upscaler
|
||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
import argparse
|
import argparse
|
||||||
import glob
|
import contextlib
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -58,13 +57,30 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import yaml
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from avalon_framework import Avalon
|
from avalon_framework import Avalon
|
||||||
import GPUtil
|
import GPUtil
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
VERSION = '2.9.0'
|
|
||||||
|
VERSION = '3.0.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.
|
# each thread might take up to 2.5 GB during initialization.
|
||||||
# (system memory, not to be confused with GPU memory)
|
# (system memory, not to be confused with GPU memory)
|
||||||
@@ -72,7 +88,7 @@ SYS_MEM_PER_THREAD = 2.5
|
|||||||
GPU_MEM_PER_THREAD = 3.5
|
GPU_MEM_PER_THREAD = 3.5
|
||||||
|
|
||||||
|
|
||||||
def process_arguments():
|
def parse_arguments():
|
||||||
"""Processes CLI arguments
|
"""Processes CLI arguments
|
||||||
|
|
||||||
This function parses all arguments
|
This function parses all arguments
|
||||||
@@ -89,10 +105,10 @@ def process_arguments():
|
|||||||
# upscaler options
|
# upscaler options
|
||||||
upscaler_options = parser.add_argument_group('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('-m', '--method', help='upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'])
|
||||||
upscaler_options.add_argument('-d', '--driver', help='waifu2x driver', action='store', default='waifu2x_caffe', choices=['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan'])
|
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('-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('-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('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml')
|
||||||
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
|
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
|
||||||
|
|
||||||
# scaling options
|
# scaling options
|
||||||
@@ -111,18 +127,9 @@ def process_arguments():
|
|||||||
|
|
||||||
def print_logo():
|
def print_logo():
|
||||||
"""print video2x logo"""
|
"""print video2x logo"""
|
||||||
logo = r'''
|
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')
|
||||||
\ \ / / _ __| | ___ ___ ) | \ V /
|
|
||||||
\ \/ / | | / _` | / _ \ / _ \ / / > <
|
|
||||||
\ / | | | (_| | | __/ | (_) | / /_ / . \
|
|
||||||
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
|
|
||||||
'''
|
|
||||||
print(logo)
|
|
||||||
print('\n Video2X Video Enlarger')
|
|
||||||
spaces = ((44 - len(f'Version {VERSION}')) // 2) * ' '
|
|
||||||
print(f'{Avalon.FM.BD}\n{spaces} Version {VERSION}\n{Avalon.FM.RST}')
|
|
||||||
|
|
||||||
|
|
||||||
def check_memory():
|
def check_memory():
|
||||||
@@ -138,19 +145,17 @@ def check_memory():
|
|||||||
|
|
||||||
# check if Nvidia-smi is available
|
# check if Nvidia-smi is available
|
||||||
# GPUtil requires nvidia-smi.exe to interact with GPU
|
# GPUtil requires nvidia-smi.exe to interact with GPU
|
||||||
if args.method == 'gpu' or args.method == 'cudnn':
|
if args.method in ['gpu', 'cudnn']:
|
||||||
if not (shutil.which('nvidia-smi') or
|
if not (shutil.which('nvidia-smi') or
|
||||||
pathlib.Path(r'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe').is_file()):
|
pathlib.Path(r'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe').is_file()):
|
||||||
# Nvidia System Management Interface not available
|
# Nvidia System Management Interface not available
|
||||||
Avalon.warning('Nvidia-smi not available, skipping available memory check')
|
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')
|
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
|
||||||
else:
|
else:
|
||||||
try:
|
with contextlib.suppress(ValueError):
|
||||||
# "0" is GPU ID. Both waifu2x drivers use the first GPU available, therefore only 0 makes sense
|
# "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
|
gpu_memory_available = (GPUtil.getGPUs()[0].memoryTotal - GPUtil.getGPUs()[0].memoryUsed) / 1024
|
||||||
memory_status.append(('GPU', gpu_memory_available))
|
memory_status.append(('GPU', gpu_memory_available))
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# go though each checkable memory type and check availability
|
# go though each checkable memory type and check availability
|
||||||
for memory_type, memory_available in memory_status:
|
for memory_type, memory_available in memory_status:
|
||||||
@@ -182,14 +187,18 @@ def check_memory():
|
|||||||
Avalon.warning('Proceed with caution')
|
Avalon.warning('Proceed with caution')
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_file):
|
def read_config(config_file: pathlib.Path) -> dict:
|
||||||
""" Reads configuration file
|
""" read video2x configurations from config file
|
||||||
|
|
||||||
Returns a dictionary read by JSON.
|
Arguments:
|
||||||
|
config_file {pathlib.Path} -- video2x configuration file pathlib.Path
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict -- dictionary of video2x configuration
|
||||||
"""
|
"""
|
||||||
with open(config_file, 'r') as raw_config:
|
|
||||||
config = json.load(raw_config)
|
with open(config_file, 'r') as config:
|
||||||
return config
|
return yaml.load(config, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
def absolutify_paths(config):
|
def absolutify_paths(config):
|
||||||
@@ -218,6 +227,10 @@ def absolutify_paths(config):
|
|||||||
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
|
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']
|
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
|
# check ffmpeg path
|
||||||
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
||||||
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
|
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
|
||||||
@@ -240,17 +253,13 @@ if __name__ != '__main__':
|
|||||||
# print video2x logo
|
# print video2x logo
|
||||||
print_logo()
|
print_logo()
|
||||||
|
|
||||||
# process CLI arguments
|
# parse command line arguments
|
||||||
args = process_arguments()
|
args = parse_arguments()
|
||||||
|
|
||||||
# display version and lawful informaition
|
# display version and lawful informaition
|
||||||
if args.version:
|
if args.version:
|
||||||
print(f'Video2X Version: {VERSION}')
|
print(LEGAL_INFO)
|
||||||
print('Author: K4YT3X')
|
sys.exit(0)
|
||||||
print('License: GNU GPL v3')
|
|
||||||
print('Github Page: https://github.com/k4yt3x/video2x')
|
|
||||||
print('Contact: k4yt3x@k4yt3x.com')
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
# arguments sanity check
|
# arguments sanity check
|
||||||
if not args.input:
|
if not args.input:
|
||||||
@@ -259,10 +268,10 @@ if not args.input:
|
|||||||
if not args.output:
|
if not args.output:
|
||||||
Avalon.error('You must specify output video file/directory path')
|
Avalon.error('You must specify output video file/directory path')
|
||||||
raise ArgumentError('output video path not specified')
|
raise ArgumentError('output video path not specified')
|
||||||
if (args.driver == 'waifu2x_converter' or args.driver == 'waifu2x_ncnn_vulkan') and args.width and args.height:
|
if (args.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and args.width and args.height:
|
||||||
Avalon.error('Waifu2x Converter CPP/NCNN accepts only scaling ratio')
|
Avalon.error('Selected driver accepts only scaling ratio')
|
||||||
raise ArgumentError('waifu2x-converter supports 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()):
|
if args.driver == 'waifu2x_ncnn_vulkan' and args.ratio is not None and (args.ratio > 2 or not args.ratio.is_integer()):
|
||||||
Avalon.error('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
|
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')
|
raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
|
||||||
if (args.width or args.height) and args.ratio:
|
if (args.width or args.height) and args.ratio:
|
||||||
@@ -272,32 +281,45 @@ if (args.width and not args.height) or (not args.width and args.height):
|
|||||||
Avalon.error('You must specify both width and height')
|
Avalon.error('You must specify both width and height')
|
||||||
raise ArgumentError('only one of width or height is specified')
|
raise ArgumentError('only one of width or height is specified')
|
||||||
|
|
||||||
# check available memory
|
# check available memory if driver is waifu2x-based
|
||||||
check_memory()
|
if args.driver in ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan']:
|
||||||
|
check_memory()
|
||||||
|
|
||||||
# read configurations from JSON
|
# 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?', default=True, batch=args.batch):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
threads = Avalon.gets('Amount of threads to use [5]: ', default=5, batch=args.batch)
|
||||||
|
args.threads = int(threads)
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
if threads == '':
|
||||||
|
args.threads = 5
|
||||||
|
break
|
||||||
|
Avalon.error(f'{threads} is not a valid integer')
|
||||||
|
|
||||||
|
# read configurations from configuration file
|
||||||
config = read_config(args.config)
|
config = read_config(args.config)
|
||||||
config = absolutify_paths(config)
|
|
||||||
|
# config = absolutify_paths(config)
|
||||||
|
|
||||||
# load waifu2x configuration
|
# load waifu2x configuration
|
||||||
if args.driver == 'waifu2x_caffe':
|
driver_settings = config[args.driver]
|
||||||
waifu2x_settings = config['waifu2x_caffe']
|
|
||||||
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
|
# check if driver path exists
|
||||||
Avalon.error('Specified waifu2x-caffe directory doesn\'t exist')
|
if not pathlib.Path(driver_settings['path']).is_file():
|
||||||
|
if not pathlib.Path(f'{driver_settings["path"]}.exe').is_file():
|
||||||
|
Avalon.error('Specified driver executable directory doesn\'t exist')
|
||||||
Avalon.error('Please check the configuration file settings')
|
Avalon.error('Please check the configuration file settings')
|
||||||
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
|
raise FileNotFoundError(driver_settings['path'])
|
||||||
elif args.driver == 'waifu2x_converter':
|
|
||||||
waifu2x_settings = config['waifu2x_converter']
|
# if the driver is Anime4K, check if JDK 12 is installed
|
||||||
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
|
if args.driver == 'anime4k':
|
||||||
Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist')
|
if not pathlib.Path('C:/Program Files/Java/jdk-12.0.2/bin/java.exe').is_file():
|
||||||
Avalon.error('Please check the configuration file settings')
|
Avalon.warning('Cannot find JDK 12 at its default installation location')
|
||||||
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
|
Avalon.warning('Please ensure you have JDK 12 installed and configured')
|
||||||
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'])
|
|
||||||
|
|
||||||
# read FFmpeg configuration
|
# read FFmpeg configuration
|
||||||
ffmpeg_settings = config['ffmpeg']
|
ffmpeg_settings = config['ffmpeg']
|
||||||
@@ -353,12 +375,12 @@ try:
|
|||||||
Avalon.error('Input and output path type mismatch')
|
Avalon.error('Input and output path type mismatch')
|
||||||
Avalon.error('Input is single file but output is directory')
|
Avalon.error('Input is single file but output is directory')
|
||||||
raise Exception('input output path type mismatch')
|
raise Exception('input output path type mismatch')
|
||||||
if not re.search('.*\..*$', str(args.output)):
|
if not re.search(r'.*\..*$', str(args.output)):
|
||||||
Avalon.error('No suffix found in output file path')
|
Avalon.error('No suffix found in output file path')
|
||||||
Avalon.error('Suffix must be specified for FFmpeg')
|
Avalon.error('Suffix must be specified for FFmpeg')
|
||||||
raise Exception('No suffix specified')
|
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)
|
upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, driver_settings=driver_settings, ffmpeg_settings=ffmpeg_settings)
|
||||||
|
|
||||||
# set optional options
|
# set optional options
|
||||||
upscaler.waifu2x_driver = args.driver
|
upscaler.waifu2x_driver = args.driver
|
||||||
@@ -380,9 +402,13 @@ try:
|
|||||||
elif args.input.is_dir():
|
elif args.input.is_dir():
|
||||||
# upscale videos in a directory
|
# upscale videos in a directory
|
||||||
Avalon.info(f'Upscaling videos in directory: {args.input}')
|
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()]:
|
for input_video in [f for f in args.input.iterdir() if f.is_file()]:
|
||||||
output_video = args.output / input_video
|
output_video = args.output / input_video.name
|
||||||
upscaler = Upscaler(input_video=args.input / input_video, output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
upscaler = Upscaler(input_video=input_video, output_video=output_video, method=args.method, driver_settings=driver_settings, ffmpeg_settings=ffmpeg_settings)
|
||||||
|
|
||||||
# set optional options
|
# set optional options
|
||||||
upscaler.waifu2x_driver = args.driver
|
upscaler.waifu2x_driver = args.driver
|
||||||
@@ -404,13 +430,17 @@ try:
|
|||||||
raise FileNotFoundError(f'{args.input} is neither file nor directory')
|
raise FileNotFoundError(f'{args.input} is neither file nor directory')
|
||||||
|
|
||||||
Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds')
|
Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds')
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
Avalon.error('An exception has occurred')
|
Avalon.error('An exception has occurred')
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# try cleaning up temp directories
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
upscaler.cleanup_temp_directories()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# remove Video2X cache directory
|
# remove Video2X cache directory
|
||||||
try:
|
with contextlib.suppress(FileNotFoundError):
|
||||||
if not preserve_frames:
|
if not preserve_frames:
|
||||||
shutil.rmtree(video2x_cache_directory)
|
shutil.rmtree(video2x_cache_directory)
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
89
src/video2x.yaml
Normal file
89
src/video2x.yaml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
waifu2x_caffe:
|
||||||
|
path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-caffe\waifu2x-caffe-cui
|
||||||
|
input_extention_list:
|
||||||
|
output_extention:
|
||||||
|
mode: noise_scale
|
||||||
|
scale_ratio:
|
||||||
|
scale_width:
|
||||||
|
scale_height:
|
||||||
|
noise_level: 3
|
||||||
|
process: gpu
|
||||||
|
crop_size: 128
|
||||||
|
output_quality: -1
|
||||||
|
output_depth: 8
|
||||||
|
batch_size: 1
|
||||||
|
gpu: 0
|
||||||
|
tta: 0
|
||||||
|
input_path:
|
||||||
|
output_path:
|
||||||
|
model_dir:
|
||||||
|
crop_w:
|
||||||
|
crop_h:
|
||||||
|
waifu2x_converter:
|
||||||
|
path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-converter-cpp
|
||||||
|
output-format:
|
||||||
|
png-compression:
|
||||||
|
image-quality:
|
||||||
|
block-size:
|
||||||
|
disable-gpu:
|
||||||
|
force-OpenCL:
|
||||||
|
processor:
|
||||||
|
jobs:
|
||||||
|
model-dir:
|
||||||
|
scale-ratio:
|
||||||
|
noise-level: 3
|
||||||
|
mode: noise-scale
|
||||||
|
silent: true
|
||||||
|
output:
|
||||||
|
input:
|
||||||
|
waifu2x_ncnn_vulkan:
|
||||||
|
path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-ncnn-vulkan\waifu2x-ncnn-vulkan
|
||||||
|
v:
|
||||||
|
i:
|
||||||
|
o:
|
||||||
|
n: 2
|
||||||
|
s: 2
|
||||||
|
t: 400
|
||||||
|
m: models-cunet
|
||||||
|
g: 0
|
||||||
|
j: "1:2:2"
|
||||||
|
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":
|
||||||
|
"-pix_fmt": rgba64be
|
||||||
|
"-hwaccel": auto
|
||||||
|
"-y": true
|
||||||
|
frames_to_video:
|
||||||
|
input_options:
|
||||||
|
"-qscale:v":
|
||||||
|
"-qscale:a":
|
||||||
|
"-f": image2
|
||||||
|
output_options:
|
||||||
|
"-vcodec": libx264
|
||||||
|
"-crf": 17
|
||||||
|
"-b:v":
|
||||||
|
"-pix_fmt":
|
||||||
|
"-hwaccel": auto
|
||||||
|
"-y": true
|
||||||
|
migrating_tracks:
|
||||||
|
output_options:
|
||||||
|
"-map":
|
||||||
|
- 0:v?
|
||||||
|
- 1:a?
|
||||||
|
- 1:s?
|
||||||
|
- 1:d?
|
||||||
|
- 1:t?
|
||||||
|
"-c": copy
|
||||||
|
"-pix_fmt":
|
||||||
|
"-hwaccel": auto
|
||||||
|
"-y": true
|
||||||
|
video2x:
|
||||||
|
video2x_cache_directory:
|
||||||
|
image_format: png
|
||||||
|
preserve_frames: false
|
||||||
462
src/video2x_gui.py
Executable file
462
src/video2x_gui.py
Executable file
@@ -0,0 +1,462 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Creator: Video2X GUI
|
||||||
|
Author: K4YT3X
|
||||||
|
Date Created: July 27, 2019
|
||||||
|
Last Modified: December 11, 2019
|
||||||
|
|
||||||
|
Description: A simple GUI for Video2X made with tkinter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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 contextlib
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
VERSION = '1.1.3'
|
||||||
|
|
||||||
|
VIDEO2X_CONFIG = pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml'
|
||||||
|
|
||||||
|
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'}
|
||||||
|
|
||||||
|
DEMUXER_EXTENSIONS = {'3dostr', '4xm', 'aa', 'aac', 'ac3', 'acm', 'act',
|
||||||
|
'adf', 'adp', 'ads', 'adx', 'aea', 'afc', 'aiff', 'aix', 'alaw',
|
||||||
|
'alias_pix', 'alsa', 'amr', 'amrnb', 'amrwb', 'anm', 'apc', 'ape',
|
||||||
|
'apng', 'aptx', 'aptx_hd', 'aqtitle', 'asf', 'asf_o', 'ass', 'ast',
|
||||||
|
'au', 'avi', 'avisynth', 'avr', 'avs', 'avs2', 'bethsoftvid', 'bfi',
|
||||||
|
'bfstm', 'bin', 'bink', 'bit', 'bmp_pipe', 'bmv', 'boa', 'brender_pix',
|
||||||
|
'brstm', 'c93', 'caf', 'cavsvideo', 'cdg', 'cdxl', 'cine', 'codec2',
|
||||||
|
'codec2raw', 'concat', 'dash', 'data', 'daud', 'dcstr', 'dds_pipe',
|
||||||
|
'dfa', 'dirac', 'dnxhd', 'dpx_pipe', 'dsf', 'dsicin', 'dss', 'dts',
|
||||||
|
'dtshd', 'dv', 'dvbsub', 'dvbtxt', 'dxa', 'ea', 'ea_cdata', 'eac3',
|
||||||
|
'epaf', 'exr_pipe', 'f32be', 'f32le', 'f64be', 'f64le', 'fbdev',
|
||||||
|
'ffmetadata', 'film_cpk', 'filmstrip', 'fits', 'flac', 'flic', 'flv',
|
||||||
|
'frm', 'fsb', 'g722', 'g723_1', 'g726', 'g726le', 'g729', 'gdv', 'genh',
|
||||||
|
'gif', 'gsm', 'gxf', 'h261', 'h263', 'h264', 'hevc', 'hls', 'applehttp',
|
||||||
|
'hnm', 'ico', 'idcin', 'idf', 'iec61883', 'iff', 'ilbc', 'image2',
|
||||||
|
'image2pipe', 'ingenient', 'ipmovie', 'ircam', 'iss', 'iv8', 'ivf',
|
||||||
|
'ivr', 'j2k_pipe', 'jack', 'jacosub', 'jpeg_pipe', 'jpegls_pipe',
|
||||||
|
'jv', 'kmsgrab', 'lavfi', 'libcdio', 'libdc1394', 'libgme', 'libopenmpt',
|
||||||
|
'live_flv', 'lmlm4', 'loas', 'lrc', 'lvf', 'lxf', 'm4v', 'matroska', 'webm',
|
||||||
|
'mgsts', 'microdvd', 'mjpeg', 'mjpeg_2000', 'mlp', 'mlv', 'mm', 'mmf',
|
||||||
|
'mov', 'mp4', 'm4a', '3gp', '3g2', 'mj2', 'mp3', 'mpc', 'mpc8', 'mpeg',
|
||||||
|
'mpegts', 'mpegtsraw', 'mpegvideo', 'mpjpeg', 'mpl2', 'mpsub', 'msf',
|
||||||
|
'msnwctcp', 'mtaf', 'mtv', 'mulaw', 'musx', 'mv', 'mvi', 'mxf', 'mxg',
|
||||||
|
'nc', 'nistsphere', 'nsp', 'nsv', 'nut', 'nuv', 'ogg', 'oma', 'openal',
|
||||||
|
'oss', 'paf', 'pam_pipe', 'pbm_pipe', 'pcx_pipe', 'pgm_pipe', 'pgmyuv_pipe',
|
||||||
|
'pictor_pipe', 'pjs', 'pmp', 'png_pipe', 'ppm_pipe', 'psd_pipe', 'psxstr',
|
||||||
|
'pulse', 'pva', 'pvf', 'qcp', 'qdraw_pipe', 'r3d', 'rawvideo', 'realtext',
|
||||||
|
'redspark', 'rl2', 'rm', 'roq', 'rpl', 'rsd', 'rso', 'rtp', 'rtsp',
|
||||||
|
's16be', 's16le', 's24be', 's24le', 's32be', 's32le', 's337m', 's8',
|
||||||
|
'sami', 'sap', 'sbc', 'sbg', 'scc', 'sdp', 'sdr2', 'sds', 'sdx', 'ser',
|
||||||
|
'sgi_pipe', 'shn', 'siff', 'sln', 'smjpeg', 'smk', 'smush', 'sndio',
|
||||||
|
'sol', 'sox', 'spdif', 'srt', 'stl', 'subviewer', 'subviewer1', 'sunrast_pipe',
|
||||||
|
'sup', 'svag', 'svg_pipe', 'swf', 'tak', 'tedcaptions', 'thp', 'tiertexseq',
|
||||||
|
'tiff_pipe', 'tmv', 'truehd', 'tta', 'tty', 'txd', 'ty', 'u16be', 'u16le',
|
||||||
|
'u24be', 'u24le', 'u32be', 'u32le', 'u8', 'v210', 'v210x', 'vag', 'vc1',
|
||||||
|
'vc1test', 'vidc', 'video4linux2', 'v4l2', 'vivo', 'vmd', 'vobsub', 'voc',
|
||||||
|
'vpk', 'vplayer', 'vqf', 'w64', 'wav', 'wc3movie', 'webm_dash_manifest',
|
||||||
|
'webp_pipe', 'webvtt', 'wsaud', 'wsd', 'wsvqa', 'wtv', 'wv', 'wve', 'x11grab',
|
||||||
|
'xa', 'xbin', 'xmv', 'xpm_pipe', 'xvag', 'xwd_pipe', 'xwma', 'yop', 'yuv4mpegpipe'}
|
||||||
|
|
||||||
|
|
||||||
|
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(False)
|
||||||
|
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):
|
||||||
|
|
||||||
|
try:
|
||||||
|
# start timer
|
||||||
|
begin_time = time.time()
|
||||||
|
|
||||||
|
# read configuration file
|
||||||
|
config = read_config(VIDEO2X_CONFIG)
|
||||||
|
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()]
|
||||||
|
|
||||||
|
# load specified driver's config into driver_settings
|
||||||
|
driver_settings = config[driver]
|
||||||
|
|
||||||
|
# if executable doesn't exist, show warning
|
||||||
|
if not pathlib.Path(driver_settings['path']).is_file() and not pathlib.Path(f'{driver_settings["path"]}.exe').is_file():
|
||||||
|
messagebox.showerror('Error', 'Specified driver directory doesn\'t exist\nPlease 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 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, driver_settings=driver_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')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror('Error', f'Upscaler ran into an error:\n{e}')
|
||||||
|
|
||||||
|
# try cleaning up temp directories
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
self.upscaler.cleanup_temp_directories()
|
||||||
|
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
# remove input file extension
|
||||||
|
input_filename = str(self.input_file.get())
|
||||||
|
for extension in DEMUXER_EXTENSIONS:
|
||||||
|
if input_filename.endswith(f'.{extension}'):
|
||||||
|
input_filename = input_filename[:-1 - len(extension)]
|
||||||
|
|
||||||
|
# try to set an output file name automatically
|
||||||
|
output_file = pathlib.Path(f'{input_filename}_output.mp4')
|
||||||
|
|
||||||
|
output_file_id = 0
|
||||||
|
while output_file.is_file() and output_file_id <= 10:
|
||||||
|
output_file = pathlib.Path(f'{input_filename}_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 parsing Video2X config.
|
||||||
|
"""
|
||||||
|
with open(config_file, 'r') as raw_config:
|
||||||
|
config = yaml.load(raw_config, Loader=yaml.FullLoader)
|
||||||
|
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']['path'], re.IGNORECASE):
|
||||||
|
config['waifu2x_caffe']['path'] = current_directory / config['waifu2x_caffe']['path']
|
||||||
|
|
||||||
|
# check waifu2x-converter-cpp path
|
||||||
|
if not re.match('^[a-z]:', config['waifu2x_converter']['path'], re.IGNORECASE):
|
||||||
|
config['waifu2x_converter']['path'] = current_directory / config['waifu2x_converter']['path']
|
||||||
|
|
||||||
|
# check waifu2x_ncnn_vulkan path
|
||||||
|
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['path'], re.IGNORECASE):
|
||||||
|
config['waifu2x_ncnn_vulkan']['path'] = current_directory / config['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()
|
||||||
138
bin/video2x_setup.py → src/video2x_setup.py
Normal file → Executable file
138
bin/video2x_setup.py → src/video2x_setup.py
Normal file → Executable file
@@ -2,17 +2,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Name: Video2X Setup Script
|
Name: Video2X Setup Script
|
||||||
Author: K4YT3X
|
Creator: K4YT3X
|
||||||
Author: BrianPetkovsek
|
|
||||||
Date Created: November 28, 2018
|
Date Created: November 28, 2018
|
||||||
Last Modified: July 30, 2019
|
Last Modified: January 4, 2020
|
||||||
|
|
||||||
Dev: SAT3LL
|
Editor: BrianPetkovsek
|
||||||
|
Editor: 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
|
|
||||||
|
|
||||||
Description: This script helps installing all dependencies of video2x
|
Description: This script helps installing all dependencies of video2x
|
||||||
and generates a configuration for it.
|
and generates a configuration for it.
|
||||||
@@ -22,11 +17,13 @@ Installation Details:
|
|||||||
- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe
|
- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe
|
||||||
- waifu2x-cpp-converter: %LOCALAPPDATA%\\video2x\\waifu2x-converter-cpp
|
- waifu2x-cpp-converter: %LOCALAPPDATA%\\video2x\\waifu2x-converter-cpp
|
||||||
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
|
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
|
||||||
|
- anime4k: %LOCALAPPDATA%\\video2x\\anime4k
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# built-in imports
|
# built-in imports
|
||||||
|
from datetime import timedelta
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
@@ -34,6 +31,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib
|
import urllib
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -43,20 +41,22 @@ import zipfile
|
|||||||
# later in the script.
|
# later in the script.
|
||||||
# import requests
|
# import requests
|
||||||
|
|
||||||
VERSION = '1.4.0'
|
VERSION = '1.6.1'
|
||||||
|
|
||||||
# global static variables
|
# global static variables
|
||||||
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
||||||
|
VIDEO2X_CONFIG = pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml'
|
||||||
|
DRIVER_OPTIONS = ['all', 'waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
|
||||||
|
|
||||||
|
|
||||||
def process_arguments():
|
def parse_arguments():
|
||||||
"""Processes CLI arguments
|
"""Processes CLI arguments
|
||||||
"""
|
"""
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
# video options
|
# video options
|
||||||
general_options = parser.add_argument_group('General Options')
|
general_options = parser.add_argument_group('General Options')
|
||||||
general_options.add_argument('-d', '--driver', help='driver to download and configure', action='store', choices=['all', 'waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan'], default='all')
|
general_options.add_argument('-d', '--driver', help='driver to download and configure', action='store', choices=DRIVER_OPTIONS, default='all')
|
||||||
|
|
||||||
# parse arguments
|
# parse arguments
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
@@ -79,19 +79,22 @@ class Video2xSetup:
|
|||||||
print('\nInstalling Python libraries')
|
print('\nInstalling Python libraries')
|
||||||
self._install_python_requirements()
|
self._install_python_requirements()
|
||||||
|
|
||||||
print('\nInstalling FFMPEG')
|
print('\nInstalling FFmpeg')
|
||||||
self._install_ffmpeg()
|
self._install_ffmpeg()
|
||||||
|
|
||||||
if self.driver == 'all':
|
if self.driver == 'all':
|
||||||
self._install_waifu2x_caffe()
|
self._install_waifu2x_caffe()
|
||||||
self._install_waifu2x_converter_cpp()
|
self._install_waifu2x_converter_cpp()
|
||||||
self._install_waifu2x_ncnn_vulkan()
|
self._install_waifu2x_ncnn_vulkan()
|
||||||
|
self._install_anime4k()
|
||||||
elif self.driver == 'waifu2x_caffe':
|
elif self.driver == 'waifu2x_caffe':
|
||||||
self._install_waifu2x_caffe()
|
self._install_waifu2x_caffe()
|
||||||
elif self.driver == 'waifu2x_converter':
|
elif self.driver == 'waifu2x_converter':
|
||||||
self._install_waifu2x_converter_cpp()
|
self._install_waifu2x_converter_cpp()
|
||||||
elif self.driver == 'waifu2x_ncnn_vulkan':
|
elif self.driver == 'waifu2x_ncnn_vulkan':
|
||||||
self._install_waifu2x_ncnn_vulkan()
|
self._install_waifu2x_ncnn_vulkan()
|
||||||
|
elif self.driver == 'anime4k':
|
||||||
|
self._install_anime4k()
|
||||||
|
|
||||||
print('\nGenerating Video2X configuration file')
|
print('\nGenerating Video2X configuration file')
|
||||||
self._generate_config()
|
self._generate_config()
|
||||||
@@ -137,7 +140,7 @@ class Video2xSetup:
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
# Get latest release of waifu2x-caffe via GitHub API
|
# Get latest release of waifu2x-caffe via GitHub API
|
||||||
latest_release = json.loads(requests.get('https://api.github.com/repos/lltcggie/waifu2x-caffe/releases/latest').content.decode('utf-8'))
|
latest_release = requests.get('https://api.github.com/repos/lltcggie/waifu2x-caffe/releases/latest').json()
|
||||||
|
|
||||||
for a in latest_release['assets']:
|
for a in latest_release['assets']:
|
||||||
if 'waifu2x-caffe.zip' in a['browser_download_url']:
|
if 'waifu2x-caffe.zip' in a['browser_download_url']:
|
||||||
@@ -154,7 +157,7 @@ class Video2xSetup:
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
# Get latest release of waifu2x-caffe via GitHub API
|
# Get latest release of waifu2x-caffe via GitHub API
|
||||||
latest_release = json.loads(requests.get('https://api.github.com/repos/DeadSix27/waifu2x-converter-cpp/releases/latest').content.decode('utf-8'))
|
latest_release = requests.get('https://api.github.com/repos/DeadSix27/waifu2x-converter-cpp/releases/latest').json()
|
||||||
|
|
||||||
for a in latest_release['assets']:
|
for a in latest_release['assets']:
|
||||||
if re.search(r'waifu2x-DeadSix27-win64_v[0-9]*\.zip', a['browser_download_url']):
|
if re.search(r'waifu2x-DeadSix27-win64_v[0-9]*\.zip', a['browser_download_url']):
|
||||||
@@ -171,7 +174,7 @@ class Video2xSetup:
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
# Get latest release of waifu2x-ncnn-vulkan via Github API
|
# Get latest release of waifu2x-ncnn-vulkan via Github API
|
||||||
latest_release = json.loads(requests.get('https://api.github.com/repos/nihui/waifu2x-ncnn-vulkan/releases/latest').content.decode('utf-8'))
|
latest_release = requests.get('https://api.github.com/repos/nihui/waifu2x-ncnn-vulkan/releases/latest').json()
|
||||||
|
|
||||||
for a in latest_release['assets']:
|
for a in latest_release['assets']:
|
||||||
if re.search(r'waifu2x-ncnn-vulkan-\d*\.zip', a['browser_download_url']):
|
if re.search(r'waifu2x-ncnn-vulkan-\d*\.zip', a['browser_download_url']):
|
||||||
@@ -190,34 +193,66 @@ class Video2xSetup:
|
|||||||
# rename the newly extracted directory
|
# rename the newly extracted directory
|
||||||
(LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(waifu2x_ncnn_vulkan_directory)
|
(LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(waifu2x_ncnn_vulkan_directory)
|
||||||
|
|
||||||
|
def _install_anime4k(self):
|
||||||
|
""" Install Anime4K
|
||||||
|
"""
|
||||||
|
print('\nInstalling Anime4K')
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
# therefore releases/latest won't work
|
||||||
|
latest_release = requests.get('https://api.github.com/repos/bloc97/Anime4K/releases').json()[0]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# extract and rename
|
||||||
|
with zipfile.ZipFile(anime4k_zip) as zipf:
|
||||||
|
zipf.extractall(LOCALAPPDATA / 'video2x' / 'anime4k')
|
||||||
|
|
||||||
def _generate_config(self):
|
def _generate_config(self):
|
||||||
""" Generate video2x config
|
""" Generate video2x config
|
||||||
"""
|
"""
|
||||||
# Open current video2x.json file as template
|
import yaml
|
||||||
with open('video2x.json', 'r') as template:
|
|
||||||
template_dict = json.load(template)
|
# open current video2x configuration file as template
|
||||||
|
with open(VIDEO2X_CONFIG, 'r') as template:
|
||||||
|
template_dict = yaml.load(template, Loader=yaml.FullLoader)
|
||||||
template.close()
|
template.close()
|
||||||
|
|
||||||
# configure only the specified drivers
|
# configure only the specified drivers
|
||||||
if self.driver == 'all':
|
if self.driver == 'all':
|
||||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
|
template_dict['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_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['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
|
||||||
|
template_dict['anime4k']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
|
||||||
elif self.driver == 'waifu2x_caffe':
|
elif self.driver == 'waifu2x_caffe':
|
||||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
|
template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
|
||||||
elif self.driver == 'waifu2x_converter':
|
elif self.driver == 'waifu2x_converter':
|
||||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
template_dict['waifu2x_converter']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
||||||
elif self.driver == 'waifu2x_ncnn_vulkan':
|
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')
|
template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
|
||||||
|
elif self.driver == 'anime4k':
|
||||||
|
template_dict['anime4k']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
|
||||||
|
|
||||||
template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin')
|
template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin')
|
||||||
template_dict['video2x']['video2x_cache_directory'] = None
|
template_dict['video2x']['video2x_cache_directory'] = None
|
||||||
template_dict['video2x']['preserve_frames'] = False
|
template_dict['video2x']['preserve_frames'] = False
|
||||||
|
|
||||||
# Write configuration into file
|
# write configuration into file
|
||||||
with open('video2x.json', 'w') as config:
|
with open(VIDEO2X_CONFIG, 'w') as config:
|
||||||
json.dump(template_dict, config, indent=4)
|
yaml.dump(template_dict, config)
|
||||||
config.close()
|
|
||||||
|
|
||||||
|
|
||||||
def download(url, save_path, chunk_size=4096):
|
def download(url, save_path, chunk_size=4096):
|
||||||
@@ -238,10 +273,8 @@ def download(url, save_path, chunk_size=4096):
|
|||||||
file_name = None
|
file_name = None
|
||||||
if 'content-disposition' in stream.headers:
|
if 'content-disposition' in stream.headers:
|
||||||
disposition = stream.headers['content-disposition']
|
disposition = stream.headers['content-disposition']
|
||||||
try:
|
with contextlib.suppress(IndexError):
|
||||||
file_name = re.findall("filename=(.+)", disposition)[0].strip('"')
|
file_name = re.findall("filename=(.+)", disposition)[0].strip('"')
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
# output_file = f'{save_path}\\{stream.url.split("/")[-1]}'
|
# output_file = f'{save_path}\\{stream.url.split("/")[-1]}'
|
||||||
@@ -286,7 +319,20 @@ def pip_install(file):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
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('Video2X Setup Script')
|
||||||
print(f'Version: {VERSION}')
|
print(f'Version: {VERSION}')
|
||||||
|
|
||||||
@@ -300,8 +346,20 @@ if __name__ == '__main__':
|
|||||||
setup = Video2xSetup(args.driver, download_python_modules)
|
setup = Video2xSetup(args.driver, download_python_modules)
|
||||||
setup.run()
|
setup.run()
|
||||||
print('\nScript finished successfully')
|
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()
|
traceback.print_exc()
|
||||||
print('An error has occurred')
|
print('An error has occurred')
|
||||||
print('Video2X Automatic Setup has failed')
|
print('Video2X Automatic Setup has failed')
|
||||||
@@ -313,4 +371,12 @@ if __name__ == '__main__':
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print('An error occurred while trying to cleanup files')
|
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)
|
||||||
34
bin/waifu2x_caffe.py → src/waifu2x_caffe.py
Normal file → Executable file
34
bin/waifu2x_caffe.py → src/waifu2x_caffe.py
Normal file → Executable file
@@ -4,7 +4,7 @@
|
|||||||
Name: Waifu2x Caffe Driver
|
Name: Waifu2x Caffe Driver
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: Feb 24, 2018
|
Date Created: Feb 24, 2018
|
||||||
Last Modified: July 27, 2019
|
Last Modified: October 6, 2019
|
||||||
|
|
||||||
Description: This class is a high-level wrapper
|
Description: This class is a high-level wrapper
|
||||||
for waifu2x-caffe.
|
for waifu2x-caffe.
|
||||||
@@ -27,11 +27,11 @@ class Waifu2xCaffe:
|
|||||||
the upscale function.
|
the upscale function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, waifu2x_settings, process, model_dir, bit_depth):
|
def __init__(self, driver_settings, process, model_dir, bit_depth):
|
||||||
self.waifu2x_settings = waifu2x_settings
|
self.driver_settings = driver_settings
|
||||||
self.waifu2x_settings['process'] = process
|
self.driver_settings['process'] = process
|
||||||
self.waifu2x_settings['model_dir'] = model_dir
|
self.driver_settings['model_dir'] = model_dir
|
||||||
self.waifu2x_settings['output_depth'] = bit_depth
|
self.driver_settings['output_depth'] = bit_depth
|
||||||
|
|
||||||
# arguments passed through command line overwrites config file values
|
# arguments passed through command line overwrites config file values
|
||||||
self.process = process
|
self.process = process
|
||||||
@@ -50,16 +50,16 @@ class Waifu2xCaffe:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# overwrite config file settings
|
# overwrite config file settings
|
||||||
self.waifu2x_settings['input_path'] = input_directory
|
self.driver_settings['input_path'] = input_directory
|
||||||
self.waifu2x_settings['output_path'] = output_directory
|
self.driver_settings['output_path'] = output_directory
|
||||||
|
|
||||||
if scale_ratio:
|
if scale_ratio:
|
||||||
self.waifu2x_settings['scale_ratio'] = scale_ratio
|
self.driver_settings['scale_ratio'] = scale_ratio
|
||||||
elif scale_width and scale_height:
|
elif scale_width and scale_height:
|
||||||
self.waifu2x_settings['scale_width'] = scale_width
|
self.driver_settings['scale_width'] = scale_width
|
||||||
self.waifu2x_settings['scale_height'] = scale_height
|
self.driver_settings['scale_height'] = scale_height
|
||||||
|
|
||||||
self.waifu2x_settings['output_extention'] = image_format
|
self.driver_settings['output_extention'] = image_format
|
||||||
|
|
||||||
# print thread start message
|
# print thread start message
|
||||||
self.print_lock.acquire()
|
self.print_lock.acquire()
|
||||||
@@ -67,15 +67,15 @@ class Waifu2xCaffe:
|
|||||||
self.print_lock.release()
|
self.print_lock.release()
|
||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
execute = []
|
# initialize the list with waifu2x binary path as the first element
|
||||||
|
execute = [str(self.driver_settings['path'])]
|
||||||
|
|
||||||
execute.append(self.waifu2x_settings['waifu2x_caffe_path'])
|
for key in self.driver_settings.keys():
|
||||||
for key in self.waifu2x_settings.keys():
|
|
||||||
|
|
||||||
value = self.waifu2x_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# is executable key or null or None means that leave this option out (keep default)
|
# 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:
|
if key == 'path' or value is None or value is False:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if len(key) == 1:
|
if len(key) == 1:
|
||||||
42
bin/waifu2x_converter.py → src/waifu2x_converter.py
Normal file → Executable file
42
bin/waifu2x_converter.py → src/waifu2x_converter.py
Normal file → Executable file
@@ -4,7 +4,7 @@
|
|||||||
Name: Waifu2x Converter CPP Driver
|
Name: Waifu2x Converter CPP Driver
|
||||||
Author: K4YT3X
|
Author: K4YT3X
|
||||||
Date Created: February 8, 2019
|
Date Created: February 8, 2019
|
||||||
Last Modified: July 27, 2019
|
Last Modified: October 6, 2019
|
||||||
|
|
||||||
Description: This class is a high-level wrapper
|
Description: This class is a high-level wrapper
|
||||||
for waifu2x-converter-cpp.
|
for waifu2x-converter-cpp.
|
||||||
@@ -28,9 +28,9 @@ class Waifu2xConverter:
|
|||||||
the upscale function.
|
the upscale function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, waifu2x_settings, model_dir):
|
def __init__(self, driver_settings, model_dir):
|
||||||
self.waifu2x_settings = waifu2x_settings
|
self.driver_settings = driver_settings
|
||||||
self.waifu2x_settings['model_dir'] = model_dir
|
self.driver_settings['model_dir'] = model_dir
|
||||||
self.print_lock = threading.Lock()
|
self.print_lock = threading.Lock()
|
||||||
|
|
||||||
def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions):
|
def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions):
|
||||||
@@ -46,25 +46,16 @@ class Waifu2xConverter:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# overwrite config file settings
|
# overwrite config file settings
|
||||||
self.waifu2x_settings['input'] = input_directory
|
self.driver_settings['input'] = input_directory
|
||||||
self.waifu2x_settings['output'] = output_directory
|
self.driver_settings['output'] = output_directory
|
||||||
|
self.driver_settings['scale-ratio'] = scale_ratio
|
||||||
# temporary fix for https://github.com/DeadSix27/waifu2x-converter-cpp/issues/109
|
self.driver_settings['jobs'] = jobs
|
||||||
"""
|
self.driver_settings['output-format'] = image_format
|
||||||
self.waifu2x_settings['i'] = input_directory
|
|
||||||
self.waifu2x_settings['o'] = output_directory
|
|
||||||
self.waifu2x_settings['input'] = None
|
|
||||||
self.waifu2x_settings['output'] = None
|
|
||||||
"""
|
|
||||||
|
|
||||||
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
|
# models_rgb must be specified manually for waifu2x-converter-cpp
|
||||||
# if it's not specified in the arguments, create automatically
|
# if it's not specified in the arguments, create automatically
|
||||||
if self.waifu2x_settings['model-dir'] is None:
|
if self.driver_settings['model-dir'] is None:
|
||||||
self.waifu2x_settings['model-dir'] = pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'models_rgb'
|
self.driver_settings['model-dir'] = pathlib.Path(self.driver_settings['waifu2x_converter_path']) / 'models_rgb'
|
||||||
|
|
||||||
# print thread start message
|
# print thread start message
|
||||||
self.print_lock.acquire()
|
self.print_lock.acquire()
|
||||||
@@ -72,15 +63,16 @@ class Waifu2xConverter:
|
|||||||
self.print_lock.release()
|
self.print_lock.release()
|
||||||
|
|
||||||
# list to be executed
|
# list to be executed
|
||||||
execute = []
|
# initialize the list with waifu2x binary path as the first element
|
||||||
|
execute = [str(pathlib.Path(self.driver_settings['path']) / 'waifu2x-converter-cpp.exe')]
|
||||||
|
|
||||||
for key in self.waifu2x_settings.keys():
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
value = self.waifu2x_settings[key]
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
# the key doesn't need to be passed in this case
|
# the key doesn't need to be passed in this case
|
||||||
if key == 'waifu2x_converter_path':
|
if key == 'path':
|
||||||
execute.append(pathlib.Path(str(value)) / 'waifu2x-converter-cpp.exe')
|
continue
|
||||||
|
|
||||||
# null or None means that leave this option out (keep default)
|
# null or None means that leave this option out (keep default)
|
||||||
elif value is None or value is False:
|
elif value is None or value is False:
|
||||||
94
src/waifu2x_ncnn_vulkan.py
Executable file
94
src/waifu2x_ncnn_vulkan.py
Executable file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Name: Waifu2x NCNN Vulkan Driver
|
||||||
|
Creator: SAT3LL
|
||||||
|
Date Created: June 26, 2019
|
||||||
|
Last Modified: November 15, 2019
|
||||||
|
|
||||||
|
Editor: K4YT3X
|
||||||
|
Last Modified: January 4, 2020
|
||||||
|
|
||||||
|
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, driver_settings):
|
||||||
|
self.driver_settings = driver_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.driver_settings['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.driver_settings['i'] = input_directory
|
||||||
|
self.driver_settings['o'] = output_directory
|
||||||
|
self.driver_settings['s'] = int(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.driver_settings['path'])]
|
||||||
|
|
||||||
|
for key in self.driver_settings.keys():
|
||||||
|
|
||||||
|
value = self.driver_settings[key]
|
||||||
|
|
||||||
|
# is executable key or null or None means that leave this option out (keep default)
|
||||||
|
if key == '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)
|
||||||
Reference in New Issue
Block a user