mirror of
https://github.com/k4yt3x/video2x.git
synced 2026-02-09 06:14:45 +08:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
685d894bef | ||
|
|
b0167e9f08 | ||
|
|
74767743bd | ||
|
|
ece345e531 | ||
|
|
47e787c80c | ||
|
|
4639555626 | ||
|
|
d423fa1b6e | ||
|
|
734cf7bb0e | ||
|
|
77ad212b01 | ||
|
|
a27bdb4b63 | ||
|
|
89dfd21f97 | ||
|
|
5151be4122 | ||
|
|
f8d3fa5c25 | ||
|
|
61abcc6a3a | ||
|
|
40a4d3a602 | ||
|
|
9429b04528 | ||
|
|
55c2f7abfb | ||
|
|
c7c83d35ed | ||
|
|
ca90c5be02 | ||
|
|
5a2b387b9d | ||
|
|
eda5d70773 | ||
|
|
8b845e35b3 | ||
|
|
e3b7110ff5 | ||
|
|
cefa2c4f30 | ||
|
|
4d9c0866ce | ||
|
|
25fca78eb4 | ||
|
|
4a0ffae6c8 | ||
|
|
f94f99eb1c | ||
|
|
a9586bf508 | ||
|
|
5f69a44bf7 | ||
|
|
1ee76c0224 | ||
|
|
0b99ac33a6 | ||
|
|
4e3565b3ac | ||
|
|
b40aee0cd7 | ||
|
|
5b27cf26e1 | ||
|
|
8fe6fd4009 | ||
|
|
d4d485e132 | ||
|
|
53f5192896 | ||
|
|
e6d865a80d | ||
|
|
d25857a178 | ||
|
|
81af7b1c1b | ||
|
|
81b8e45b6f | ||
|
|
e003cbc568 | ||
|
|
f96b95563e | ||
|
|
76144b589a | ||
|
|
15d2dbb07a | ||
|
|
9d5ad2f70c | ||
|
|
b5da9ebbf7 | ||
|
|
a1e1d98929 | ||
|
|
fafd5fadd3 | ||
|
|
1280a791ec | ||
|
|
8e85d6a6d3 | ||
|
|
8b0f82e95e | ||
|
|
a6666c3c3b | ||
|
|
585164a508 | ||
|
|
6b3e1b9768 | ||
|
|
0f4daa12d2 | ||
|
|
4cf83dc565 | ||
|
|
146044505b | ||
|
|
a7d41cafdf | ||
|
|
875572c75a | ||
|
|
c7353c4bf2 | ||
|
|
0af68bb39c | ||
|
|
19cb823591 | ||
|
|
6c1a714a1e | ||
|
|
837aca371e | ||
|
|
6538abd6e5 | ||
|
|
a57465e866 | ||
|
|
405c4b6636 | ||
|
|
3d46e1a822 | ||
|
|
7b501f2c67 | ||
|
|
98e2cbdb04 | ||
|
|
25156077f2 | ||
|
|
b984977227 |
5
.github/ISSUE_TEMPLATE/bug-report.md
vendored
5
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -11,10 +11,11 @@ assignees: K4YT3X
|
||||
|
||||
|Module|Version|
|
||||
|-|-|
|
||||
|`video2x`||
|
||||
|`ffmpeg`||
|
||||
|`Video2X`||
|
||||
|`FFmpeg`||
|
||||
|`waifu2x-caffe`||
|
||||
|`waifu2x-converter-cpp`||
|
||||
|`waifu2x-ncnn-vulkan`||
|
||||
|
||||
## Symptom
|
||||
|
||||
|
||||
111
README.md
111
README.md
@@ -2,36 +2,52 @@
|
||||
|
||||
### Official Discussion Group (Telegram): https://t.me/video2x
|
||||
|
||||
## Download Builds
|
||||
|
||||
You can go to the [releases page](https://github.com/k4yt3x/video2x/releases) to download the latest builds of `Video2X`. The exe files will require no Python or Python module installation.
|
||||
|
||||
The **`full`** package provides all packages that will possibly be needed by `Video2X`, including `FFmpeg`, `waifu2x-caffe`, `waifu2x-converter-cpp`, `waifu2x-ncnn-vulkan`, and `Anime4K`. The config file (`video2x.json`) is also already configured for the environment. All you need to do is just to launch `video2x.exe`.
|
||||
|
||||
The **`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`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Component names that are **bolded** are mandatory.
|
||||
|
||||
Component names that are *italicized* 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. AMD GPU / Nvidia GPU
|
||||
1. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
||||
1. [***FFMPEG***](https://ffmpeg.zeranoe.com/builds/)
|
||||
1. [***waifu2x-caffe***](https://github.com/lltcggie/waifu2x-caffe/releases) / [***waifu2x-converter-cpp***](https://github.com/DeadSix27/waifu2x-converter-cpp/releases)
|
||||
2. AMD GPU / Nvidia GPU
|
||||
3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
|
||||
4. [**FFmpeg**](https://ffmpeg.zeranoe.com/builds/)
|
||||
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
|
||||
|
||||
### 2.7.0 (March 30, 2019)
|
||||
### 2.10.0 (August 16, 2019)
|
||||
|
||||
- Added support for different extracted image formats.
|
||||
- Redesigned FFMPEG wrapper, FFMPEG settings are now customizable in the `video2x.json` config file.
|
||||
- Other minor enhancements and adjustments (e.g. argument -> method variable)
|
||||
- **Added support for [Anime4K](https://github.com/bloc97/Anime4K)**
|
||||
|
||||
### Setup Script 1.2.0 (March 26, 2019)
|
||||
### 2.9.0 (July 27, 2019)
|
||||
|
||||
- `video2x_setup.py` script can now automatically download and configure `waifu2x-converter-cpp`.
|
||||
- replaced old progress indicator with progress bar.
|
||||
- Changed file handling method from `os` to `pathlib`
|
||||
- Removed f_string dependency and support for legacy versions of Python
|
||||
- Organized file import statements
|
||||
|
||||
### 2.6.3 (March 24, 2019)
|
||||
### 2.8.1 (July 9, 2019)
|
||||
|
||||
- Added image cleaner by @BrianPetkovsek which removes upscaled frames.
|
||||
- Fixed some PEP8 issues.
|
||||
- Exceptions in waifu2x are now caught, and script will now stop on waifu2x error instead of keep going on to FFMPEG.
|
||||
- Added automatic pixel format detection
|
||||
- Added automatic color bit depth detection
|
||||
|
||||
### 2.8.0 (June 25, 2019)
|
||||
|
||||
- **Added support for [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)**
|
||||
|
||||
### Setup Script 1.5.0 (August 16, 2019)
|
||||
|
||||
- Added automatic installation support for `Anime4K`
|
||||
|
||||
## Description
|
||||
|
||||
@@ -49,23 +65,29 @@ Clip is from trailer of animated movie "千と千尋の神隠し". Copyright bel
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Documentations
|
||||
|
||||
### [Video2X Wiki](https://github.com/K4YT3X/video2x/wiki)
|
||||
### [Video2X Wiki](https://github.com/k4yt3x/video2x/wiki)
|
||||
|
||||
You can find all detailed user-facing and developer-facing documentations in the [Video2X Wiki](https://github.com/K4YT3X/video2x/wiki). It covers everything from step-by-step instructions for beginners, to the code structure of this program for advanced users and developers. If this README page doesn't answer all your questions, the wiki page is where you should head to.
|
||||
You can find all detailed user-facing and developer-facing documentations in the [Video2X Wiki](https://github.com/k4yt3x/video2x/wiki). It covers everything from step-by-step instructions for beginners, to the code structure of this program for advanced users and developers. If this README page doesn't answer all your questions, the wiki page is where you should head to.
|
||||
|
||||
### [Step-By-Step Tutorial](https://github.com/K4YT3X/video2x/wiki/Step-By-Step-Tutorial) (Nvidia GPUs)
|
||||
### [Step-By-Step Tutorial](https://github.com/k4yt3x/video2x/wiki/Step-By-Step-Tutorial)
|
||||
|
||||
For those who want a detailed walk-through of how to use `Video2X`, you can head to the [Step-By-Step Tutorial](https://github.com/K4YT3X/video2x/wiki/Step-By-Step-Tutorial) wiki page. It includes almost every step you need to perform in order to enlarge your first video.
|
||||
For those who want a detailed walk-through of how to use `Video2X`, you can head to the [Step-By-Step Tutorial](https://github.com/k4yt3x/video2x/wiki/Step-By-Step-Tutorial) wiki page. It includes almost every step you need to perform in order to enlarge your first video.
|
||||
|
||||
### [Waifu2X Drivers](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drivers)
|
||||
### [Waifu2X Drivers](https://github.com/k4yt3x/video2x/wiki/Waifu2X-Drivers)
|
||||
|
||||
Go to the [Waifu2X Drivers](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drivers) wiki page if you want to see a detailed description on the different types of `waifu2x` drivers implemented by `Video2X`. This wiki page contains detailed difference between different drivers, and how to download and set each of them up for `Video2X`.
|
||||
Go to the [Waifu2X Drivers](https://github.com/k4yt3x/video2x/wiki/Waifu2X-Drivers) wiki page if you want to see a detailed description on the different types of `waifu2x` drivers implemented by `Video2X`. This wiki page contains detailed difference between different drivers, and how to download and set each of them up for `Video2X`.
|
||||
|
||||
### [Q&A](https://github.com/k4yt3x/video2x/wiki/Q&A)
|
||||
|
||||
If you have any questions, first try visiting our [Q&A](https://github.com/k4yt3x/video2x/wiki/Q&A) page to see if your question is answered there. If not, open an issue and we will respond to your questions ASAP.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,27 +95,31 @@ Go to the [Waifu2X Drivers](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drive
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Python 3**
|
||||
- **Python 3**
|
||||
Download: https://www.python.org/downloads/windows/
|
||||
- **FFMPEG Windows Build**
|
||||
Download: https://ffmpeg.org/download.html
|
||||
- **waifu2x-caffe** (for Nvidia CUDA/CUDNN)
|
||||
- **FFmpeg Windows Build**
|
||||
Download: https://ffmpeg.org/download.html
|
||||
- **waifu2x-caffe** (for Nvidia CUDA/CUDNN)
|
||||
Download: https://github.com/lltcggie/waifu2x-caffe/releases
|
||||
- **waifu2x-converter-cpp** (required for AMD, OpenCL and OpenGL processing)
|
||||
- **waifu2x-converter-cpp** (required for AMD, OpenCL and OpenGL processing)
|
||||
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
|
||||
|
||||
First, clone the video2x repository.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/K4YT3X/video2x.git
|
||||
git clone https://github.com/k4yt3x/video2x.git
|
||||
cd video2x/bin
|
||||
```
|
||||
|
||||
Then you may run the `video2x_setup.py` script to install and configure the dependencies automatically. This script is designed and tested on Windows 10.
|
||||
|
||||
This script will install the newest version of `ffmpeg`, either or both of `waifu2x-caffe` and `waifu2x-converter-cpp` to `%LOCALAPPDATA%\\video2x` and all required python libraries.
|
||||
This script will install the newest version of `ffmpeg`, any one or all `waifu2x-caffe`, `waifu2x-converter-cpp`, and `waifu2x-ncnn-vulkan` to `%LOCALAPPDATA%\\video2x` and all required python libraries.
|
||||
|
||||
```shell
|
||||
python video2x_setup.py
|
||||
@@ -128,7 +154,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
|
||||
```
|
||||
|
||||
### Nvidia CNDNN
|
||||
### Nvidia CUDNN
|
||||
|
||||
Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option.
|
||||
|
||||
@@ -144,9 +170,15 @@ Enlarge the video by 2 times using OpenCL. Note that `waifu2x-converter-cpp` doe
|
||||
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_converter
|
||||
```
|
||||
|
||||
### AMD or Nvidia (waifu2x-ncnn-vulkan Vulkan)
|
||||
|
||||
```shell
|
||||
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_ncnn_vulkan
|
||||
```
|
||||
|
||||
### CPU
|
||||
|
||||
Enlarge the video to 1920x1080 using the CPU. You may also use the `-r/--ratio` option. 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
|
||||
python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080
|
||||
@@ -215,10 +247,19 @@ https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
This project relies on the following software and projects.
|
||||
|
||||
- [FFMPEG]('https://www.ffmpeg.org/')
|
||||
- [FFmpeg]('https://www.ffmpeg.org/')
|
||||
- [waifu2x-caffe](https://github.com/lltcggie/waifu2x-caffe)
|
||||
- [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp)
|
||||
- [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)
|
||||
- [Anime4K](https://github.com/bloc97/Anime4K)
|
||||
|
||||
## Special Thanks
|
||||
|
||||
Appreciations given to the following code contributors:
|
||||
|
||||
- @BrianPetkovsek
|
||||
- @SAT3LL
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [Dandere2x](https://github.com/CardinalPanda/dandere2x): `Dandere2x` is a lossy video upscaler also built around `waifu2x`, but with video compression techniques to shorten the time needed to process a video.
|
||||
- [Dandere2x](https://github.com/CardinalPanda/dandere2x): `Dandere2x` is a lossy video upscaler also built around `waifu2x`, but with video compression techniques to shorten the time needed to process a video.
|
||||
|
||||
92
bin/anime4k.py
Normal file
92
bin/anime4k.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Name: Anime4K Driver
|
||||
Author: K4YT3X
|
||||
Date Created: August 15, 2019
|
||||
Last Modified: August 15, 2019
|
||||
|
||||
Description: This class is a high-level wrapper
|
||||
for Anime4k.
|
||||
"""
|
||||
|
||||
# built-in imports
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
|
||||
|
||||
class Anime4k:
|
||||
"""This class communicates with Anime4K engine
|
||||
|
||||
An object will be created for this class, containing information
|
||||
about the binary address and the processing method. When being called
|
||||
by the main program, other detailed information will be passed to
|
||||
the upscale function.
|
||||
"""
|
||||
|
||||
def __init__(self, waifu2x_settings):
|
||||
self.waifu2x_settings = waifu2x_settings
|
||||
self.print_lock = threading.Lock()
|
||||
|
||||
def upscale(self, input_directory, output_directory, scale_ratio, upscaler_exceptions, push_strength=None, push_grad_strength=None):
|
||||
""" Anime4K wrapper
|
||||
|
||||
Arguments:
|
||||
file_in {string} -- input file path
|
||||
file_out {string} -- output file path
|
||||
|
||||
Keyword Arguments:
|
||||
scale {int} -- scale ratio (default: {None})
|
||||
push_strength {int} -- residual push strength (default: {None})
|
||||
push_grad_strength {int} -- residual gradient push strength (default: {None})
|
||||
|
||||
Returns:
|
||||
subprocess.Popen.returncode -- command line return value of execution
|
||||
"""
|
||||
try:
|
||||
# return value is the sum of all execution return codes
|
||||
return_value = 0
|
||||
|
||||
# get a list lof all image files in input_directory
|
||||
extracted_frame_files = [f for f in input_directory.iterdir() if str(f).lower().endswith('.png') or str(f).lower().endswith('.jpg')]
|
||||
|
||||
# upscale each image in input_directory
|
||||
for image in extracted_frame_files:
|
||||
|
||||
execute = [
|
||||
self.waifu2x_settings['java_path'],
|
||||
'-jar',
|
||||
self.waifu2x_settings['anime4k_path'],
|
||||
str(image.absolute()),
|
||||
str(output_directory / image.name),
|
||||
str(scale_ratio)
|
||||
]
|
||||
|
||||
# optional arguments
|
||||
kwargs = [
|
||||
'push_strength',
|
||||
'push_grad_strength'
|
||||
]
|
||||
|
||||
# if optional argument specified, append value to execution list
|
||||
for arg in kwargs:
|
||||
if locals()[arg] is not None:
|
||||
execute.extend([locals([arg])])
|
||||
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info(f'Executing: {execute}', )
|
||||
self.print_lock.release()
|
||||
return_value += subprocess.run(execute, check=True).returncode
|
||||
|
||||
# print thread exiting message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
|
||||
self.print_lock.release()
|
||||
|
||||
# return command execution return code
|
||||
return return_value
|
||||
except Exception as e:
|
||||
upscaler_exceptions.append(e)
|
||||
@@ -1,13 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- code:utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Name: Video2X Exceptions
|
||||
Dev: K4YT3X
|
||||
Date Created: December 13, 2018
|
||||
Last Modified: March 19, 2019
|
||||
Last Modified: July 27, 2019
|
||||
"""
|
||||
|
||||
|
||||
class ArgumentError(Exception):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class StreamNotFoundError(Exception):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class UnrecognizedDriverError(Exception):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class UnsupportedPixelError(Exception):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
|
||||
236
bin/ffmpeg.py
236
bin/ffmpeg.py
@@ -1,23 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Name: FFMPEG Class
|
||||
Name: Video2X FFmpeg Controller
|
||||
Author: K4YT3X
|
||||
Date Created: Feb 24, 2018
|
||||
Last Modified: March 30, 2019
|
||||
Last Modified: August 15, 2019
|
||||
|
||||
Description: This class handles all FFMPEG related
|
||||
operations.
|
||||
Description: This class handles all FFmpeg related operations.
|
||||
"""
|
||||
from avalon_framework import Avalon
|
||||
|
||||
# built-in imports
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
|
||||
|
||||
class Ffmpeg:
|
||||
"""This class communicates with ffmpeg
|
||||
"""This class communicates with FFmpeg
|
||||
|
||||
This class deals with ffmpeg. It handles extracitng
|
||||
This class deals with FFmpeg. It handles extracting
|
||||
frames, stripping audio, converting images into videos
|
||||
and inserting audio tracks to videos.
|
||||
"""
|
||||
@@ -25,21 +29,53 @@ class Ffmpeg:
|
||||
def __init__(self, ffmpeg_settings, image_format):
|
||||
self.ffmpeg_settings = ffmpeg_settings
|
||||
|
||||
self.ffmpeg_path = self.ffmpeg_settings['ffmpeg_path']
|
||||
# add a forward slash to directory if not present
|
||||
# otherwise there will be a format error
|
||||
if self.ffmpeg_path[-1] != '/' and self.ffmpeg_path[-1] != '\\':
|
||||
self.ffmpeg_path = '{}\\'.format(self.ffmpeg_path)
|
||||
|
||||
self.ffmpeg_binary = '{}ffmpeg.exe'.format(self.ffmpeg_path)
|
||||
self.ffmpeg_probe_binary = '{}ffprobe.exe'.format(self.ffmpeg_path)
|
||||
self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path'])
|
||||
self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg.exe'
|
||||
self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe.exe'
|
||||
self.image_format = image_format
|
||||
self.pixel_format = None
|
||||
|
||||
def get_pixel_formats(self):
|
||||
""" Get a dictionary of supported pixel formats
|
||||
|
||||
List all supported pixel formats and their
|
||||
corresponding bit depth.
|
||||
|
||||
Returns:
|
||||
dictionary -- JSON dict of all pixel formats to bit depth
|
||||
"""
|
||||
execute = [
|
||||
self.ffmpeg_probe_binary,
|
||||
'-v',
|
||||
'quiet',
|
||||
'-pix_fmts'
|
||||
]
|
||||
|
||||
# turn elements into str
|
||||
execute = [str(e) for e in execute]
|
||||
|
||||
Avalon.debug_info(f'Executing: {" ".join(execute)}')
|
||||
|
||||
# initialize dictionary to store pixel formats
|
||||
pixel_formats = {}
|
||||
|
||||
# record all pixel formats into dictionary
|
||||
for line in subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout.decode().split('\n'):
|
||||
try:
|
||||
pixel_formats[' '.join(line.split()).split()[1]] = int(' '.join(line.split()).split()[3])
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
|
||||
# print pixel formats for debugging
|
||||
Avalon.debug_info(pixel_formats)
|
||||
|
||||
return pixel_formats
|
||||
|
||||
def get_video_info(self, input_video):
|
||||
""" Gets input video information
|
||||
|
||||
This method reads input video information
|
||||
using ffprobe in dictionary.
|
||||
using ffprobe in dictionary
|
||||
|
||||
Arguments:
|
||||
input_video {string} -- input video file path
|
||||
@@ -59,53 +95,92 @@ class Ffmpeg:
|
||||
'-show_format',
|
||||
'-show_streams',
|
||||
'-i',
|
||||
'{}'.format(input_video)
|
||||
input_video
|
||||
]
|
||||
|
||||
Avalon.debug_info('Executing: {}'.format(' '.join(execute)))
|
||||
# turn elements into str
|
||||
execute = [str(e) for e in execute]
|
||||
|
||||
Avalon.debug_info(f'Executing: {" ".join(execute)}')
|
||||
json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout
|
||||
return json.loads(json_str.decode('utf-8'))
|
||||
|
||||
def extract_frames(self, input_video, extracted_frames):
|
||||
"""Extract every frame from original videos
|
||||
|
||||
This method extracts every frame from videoin
|
||||
using ffmpeg
|
||||
This method extracts every frame from input video using FFmpeg
|
||||
|
||||
Arguments:
|
||||
input_video {string} -- input video path
|
||||
extracted_frames {string} -- video output folder
|
||||
extracted_frames {string} -- video output directory
|
||||
"""
|
||||
execute = [
|
||||
self.ffmpeg_binary,
|
||||
'-i',
|
||||
input_video,
|
||||
'{}\\extracted_%0d.{}'.format(extracted_frames, self.image_format)
|
||||
self.ffmpeg_binary
|
||||
]
|
||||
self._execute(execute=execute, phase='video_to_frames')
|
||||
|
||||
execute.extend(self._read_configuration(phase='video_to_frames'))
|
||||
|
||||
execute.extend([
|
||||
'-i',
|
||||
input_video
|
||||
])
|
||||
|
||||
execute.extend(self._read_configuration(phase='video_to_frames', section='output_options'))
|
||||
|
||||
execute.extend([
|
||||
extracted_frames / f'extracted_%0d.{self.image_format}'
|
||||
])
|
||||
|
||||
self._execute(execute)
|
||||
|
||||
def convert_video(self, framerate, resolution, upscaled_frames):
|
||||
"""Converts images into videos
|
||||
|
||||
This method converts a set of images into a
|
||||
video.
|
||||
This method converts a set of images into a video
|
||||
|
||||
Arguments:
|
||||
framerate {float} -- target video framerate
|
||||
resolution {string} -- target video resolution
|
||||
upscaled_frames {string} -- source images folder
|
||||
upscaled_frames {string} -- source images directory
|
||||
"""
|
||||
execute = [
|
||||
self.ffmpeg_binary,
|
||||
'-r',
|
||||
str(framerate),
|
||||
'-s',
|
||||
resolution,
|
||||
'-i',
|
||||
'{}\\extracted_%d.{}'.format(upscaled_frames, self.image_format),
|
||||
'{}\\no_audio.mp4'.format(upscaled_frames)
|
||||
resolution
|
||||
]
|
||||
self._execute(execute=execute, phase='frames_to_video')
|
||||
|
||||
# read other options
|
||||
execute.extend(self._read_configuration(phase='frames_to_video'))
|
||||
|
||||
# read FFmpeg input options
|
||||
execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
|
||||
|
||||
# WORKAROUND FOR WAIFU2X-NCNN-VULKAN
|
||||
# Dev: SAT3LL
|
||||
# rename all .png.png suffixes to .png
|
||||
import re
|
||||
regex = re.compile(r'\.png\.png$', re.IGNORECASE)
|
||||
for frame_name in upscaled_frames.iterdir():
|
||||
(upscaled_frames / frame_name).rename(upscaled_frames / regex.sub('.png', str(frame_name)))
|
||||
# END WORKAROUND
|
||||
|
||||
# append input frames path into command
|
||||
execute.extend([
|
||||
'-i',
|
||||
upscaled_frames / f'extracted_%d.{self.image_format}'
|
||||
])
|
||||
|
||||
# read FFmpeg output options
|
||||
execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
|
||||
|
||||
# specify output file location
|
||||
execute.extend([
|
||||
upscaled_frames / 'no_audio.mp4'
|
||||
])
|
||||
|
||||
self._execute(execute)
|
||||
|
||||
def migrate_audio_tracks_subtitles(self, input_video, output_video, upscaled_frames):
|
||||
""" Migrates audio tracks and subtitles from input video to output video
|
||||
@@ -116,32 +191,97 @@ class Ffmpeg:
|
||||
upscaled_frames {string} -- directory containing upscaled frames
|
||||
"""
|
||||
execute = [
|
||||
self.ffmpeg_binary,
|
||||
'-i',
|
||||
'{}\\no_audio.mp4'.format(upscaled_frames),
|
||||
'-i',
|
||||
input_video,
|
||||
output_video
|
||||
self.ffmpeg_binary
|
||||
]
|
||||
self._execute(execute=execute, phase='migrating_tracks')
|
||||
|
||||
def _execute(self, execute, phase):
|
||||
execute.extend(self._read_configuration(phase='migrating_tracks'))
|
||||
|
||||
for key in self.ffmpeg_settings[phase].keys():
|
||||
execute.extend([
|
||||
'-i',
|
||||
upscaled_frames / 'no_audio.mp4',
|
||||
'-i',
|
||||
input_video
|
||||
])
|
||||
|
||||
value = self.ffmpeg_settings[phase][key]
|
||||
execute.extend(self._read_configuration(phase='migrating_tracks', section='output_options'))
|
||||
|
||||
execute.extend([
|
||||
output_video
|
||||
])
|
||||
|
||||
self._execute(execute)
|
||||
|
||||
def _read_configuration(self, phase, section=None):
|
||||
""" read configuration from JSON
|
||||
|
||||
Read the configurations (arguments) from the JSON
|
||||
configuration file and append them to the end of the
|
||||
FFmpeg command.
|
||||
|
||||
Arguments:
|
||||
execute {list} -- list of arguments to be executed
|
||||
phase {str} -- phase of operation
|
||||
"""
|
||||
|
||||
configuration = []
|
||||
|
||||
# if section is specified, read configurations or keys
|
||||
# from only that section
|
||||
if section:
|
||||
source = self.ffmpeg_settings[phase][section].keys()
|
||||
|
||||
# if pixel format is not specified, use the source pixel format
|
||||
try:
|
||||
if self.ffmpeg_settings[phase][section].get('-pix_fmt') is None:
|
||||
self.ffmpeg_settings[phase][section]['-pix_fmt'] = self.pixel_format
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
source = self.ffmpeg_settings[phase].keys()
|
||||
|
||||
for key in source:
|
||||
|
||||
if section:
|
||||
value = self.ffmpeg_settings[phase][section][key]
|
||||
else:
|
||||
value = self.ffmpeg_settings[phase][key]
|
||||
|
||||
# null or None means that leave this option out (keep default)
|
||||
if value is None or value is False:
|
||||
if value is None or value is False or isinstance(value, dict):
|
||||
continue
|
||||
|
||||
# if the value is a list, append the same argument and all values
|
||||
elif isinstance(value, list):
|
||||
|
||||
for subvalue in value:
|
||||
configuration.append(key)
|
||||
if value is not True:
|
||||
configuration.append(str(subvalue))
|
||||
|
||||
# otherwise the value is typical
|
||||
else:
|
||||
execute.append(key)
|
||||
configuration.append(key)
|
||||
|
||||
# true means key is an option
|
||||
if value is True:
|
||||
continue
|
||||
|
||||
execute.append(str(value))
|
||||
configuration.append(str(value))
|
||||
|
||||
return configuration
|
||||
|
||||
def _execute(self, execute):
|
||||
""" execute command
|
||||
|
||||
Arguments:
|
||||
execute {list} -- list of arguments to be executed
|
||||
|
||||
Returns:
|
||||
int -- execution return code
|
||||
"""
|
||||
# turn all list elements into string to avoid errors
|
||||
execute = [str(e) for e in execute]
|
||||
|
||||
Avalon.debug_info(f'Executing: {execute}')
|
||||
|
||||
Avalon.debug_info('Executing: {}'.format(execute))
|
||||
return subprocess.run(execute, shell=True, check=True).returncode
|
||||
|
||||
@@ -5,13 +5,13 @@ Name: Video2X Image Cleaner
|
||||
Author: BrianPetkovsek
|
||||
Author: K4YT3X
|
||||
Date Created: March 24, 2019
|
||||
Last Modified: March 24, 2019
|
||||
Last Modified: July 27, 2019
|
||||
|
||||
Description: This class is to remove the extracted frames
|
||||
that have already been upscaled.
|
||||
"""
|
||||
|
||||
import os
|
||||
# built-in imports
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -27,11 +27,11 @@ class ImageCleaner(threading.Thread):
|
||||
threading.Thread
|
||||
"""
|
||||
|
||||
def __init__(self, input_folder, output_folder, num_threads):
|
||||
def __init__(self, input_directory, output_directory, threads):
|
||||
threading.Thread.__init__(self)
|
||||
self.input_folder = input_folder
|
||||
self.output_folder = output_folder
|
||||
self.num_threads = num_threads
|
||||
self.input_directory = input_directory
|
||||
self.output_directory = output_directory
|
||||
self.threads = threads
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
@@ -53,24 +53,24 @@ class ImageCleaner(threading.Thread):
|
||||
""" remove frames that have already been upscaled
|
||||
|
||||
This method compares the files in the extracted frames
|
||||
folder with the upscaled frames folder, and removes
|
||||
directory with the upscaled frames directory, and removes
|
||||
the frames that has already been upscaled.
|
||||
"""
|
||||
|
||||
# list all images in the extracted frames
|
||||
output_frames = [f for f in os.listdir(self.output_folder) if os.path.isfile(os.path.join(self.output_folder, f))]
|
||||
output_frames = [f for f in self.output_directory.iterdir() if f.is_file()]
|
||||
|
||||
# compare and remove frames downscaled images that finished being upscaled
|
||||
# within each thread's extracted frames folder
|
||||
for i in range(self.num_threads):
|
||||
dir_path = os.path.join(self.input_folder, str(i))
|
||||
# within each thread's extracted frames directory
|
||||
for thread_id in range(self.threads):
|
||||
dir_path = self.input_directory / str(thread_id)
|
||||
|
||||
# for each file within all the folders
|
||||
for f in os.listdir(dir_path):
|
||||
file_path = os.path.join(dir_path, f)
|
||||
# for each file within all the directories
|
||||
for file in dir_path.iterdir():
|
||||
file_path = dir_path / file
|
||||
|
||||
# if file also exists in the output folder, then the file
|
||||
# if file also exists in the output directory, then the file
|
||||
# has already been processed, thus not needed anymore
|
||||
if os.path.isfile(file_path) and f in output_frames:
|
||||
os.remove(file_path)
|
||||
output_frames.remove(f)
|
||||
if file_path.is_file() and file in output_frames:
|
||||
file_path.unlink(file)
|
||||
output_frames.remove(file)
|
||||
|
||||
292
bin/upscaler.py
292
bin/upscaler.py
@@ -4,7 +4,9 @@
|
||||
Name: Video2X Upscaler
|
||||
Author: K4YT3X
|
||||
Date Created: December 10, 2018
|
||||
Last Modified: March 30, 2019
|
||||
Last Modified: August 21, 2019
|
||||
|
||||
Dev: SAT3LL
|
||||
|
||||
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
||||
available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -12,25 +14,37 @@ Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
||||
(C) 2018-2019 K4YT3X
|
||||
"""
|
||||
|
||||
from avalon_framework import Avalon
|
||||
# local imports
|
||||
from anime4k import Anime4k
|
||||
from exceptions import *
|
||||
from ffmpeg import Ffmpeg
|
||||
from fractions import Fraction
|
||||
from image_cleaner import ImageCleaner
|
||||
from tqdm import tqdm
|
||||
from waifu2x_caffe import Waifu2xCaffe
|
||||
from waifu2x_converter import Waifu2xConverter
|
||||
import os
|
||||
from waifu2x_ncnn_vulkan import Waifu2xNcnnVulkan
|
||||
|
||||
# built-in imports
|
||||
from fractions import Fraction
|
||||
import contextlib
|
||||
import copy
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
from tqdm import tqdm
|
||||
|
||||
AVAILABLE_DRIVERS = ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
|
||||
|
||||
|
||||
class Upscaler:
|
||||
""" An instance of this class is a upscaler that will
|
||||
upscale all images in the given folder.
|
||||
upscale all images in the given directory.
|
||||
|
||||
Raises:
|
||||
Exception -- all exceptions
|
||||
@@ -52,28 +66,31 @@ class Upscaler:
|
||||
self.scale_ratio = None
|
||||
self.model_dir = None
|
||||
self.threads = 5
|
||||
self.video2x_cache_folder = '{}\\video2x'.format(tempfile.gettempdir())
|
||||
self.video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
|
||||
self.image_format = 'png'
|
||||
self.preserve_frames = False
|
||||
|
||||
# create temporary folder/directories
|
||||
self.extracted_frames = tempfile.mkdtemp(dir=self.video2x_cache_folder)
|
||||
Avalon.debug_info('Extracted frames are being saved to: {}'.format(self.extracted_frames))
|
||||
self.upscaled_frames = tempfile.mkdtemp(dir=self.video2x_cache_folder)
|
||||
Avalon.debug_info('Upscaled frames are being saved to: {}'.format(self.upscaled_frames))
|
||||
def create_temp_directories(self):
|
||||
"""create temporary directory
|
||||
"""
|
||||
self.extracted_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
|
||||
Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}')
|
||||
self.upscaled_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory))
|
||||
Avalon.debug_info(f'Upscaled frames are being saved to: {self.upscaled_frames}')
|
||||
|
||||
def cleanup(self):
|
||||
# delete temp directories when done
|
||||
# avalon framework cannot be used if python is shutting down
|
||||
# therefore, plain print is used
|
||||
def cleanup_temp_directories(self):
|
||||
"""delete temp directories when done
|
||||
"""
|
||||
if not self.preserve_frames:
|
||||
|
||||
for directory in [self.extracted_frames, self.upscaled_frames]:
|
||||
try:
|
||||
print('Cleaning up cache directory: {}'.format(directory))
|
||||
# avalon framework cannot be used if python is shutting down
|
||||
# therefore, plain print is used
|
||||
print(f'Cleaning up cache directory: {directory}')
|
||||
shutil.rmtree(directory)
|
||||
except (OSError, FileNotFoundError):
|
||||
pass
|
||||
print(f'Unable to delete: {directory}')
|
||||
traceback.print_exc()
|
||||
|
||||
def _check_arguments(self):
|
||||
# check if arguments are valid / all necessary argument
|
||||
@@ -87,20 +104,21 @@ class Upscaler:
|
||||
elif not self.method:
|
||||
raise ArgumentError('You need to specify the enlarging processing unit')
|
||||
|
||||
def _progress_bar(self, extracted_frames_folders):
|
||||
def _progress_bar(self, extracted_frames_directories):
|
||||
""" This method prints a progress bar
|
||||
|
||||
This method prints a progress bar by keeping track
|
||||
of the amount of frames in the input directory/folder
|
||||
and the output directory/folder. This is originally
|
||||
of the amount of frames in the input directory
|
||||
and the output directory. This is originally
|
||||
suggested by @ArmandBernard.
|
||||
"""
|
||||
# get number of extracted frames
|
||||
total_frames = 0
|
||||
for folder in extracted_frames_folders:
|
||||
total_frames += len([f for f in os.listdir(folder) if f[-4:] == '.png'])
|
||||
|
||||
with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
|
||||
# get number of extracted frames
|
||||
self.total_frames = 0
|
||||
for directory in extracted_frames_directories:
|
||||
self.total_frames += len([f for f in directory.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
|
||||
|
||||
with tqdm(total=self.total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
|
||||
|
||||
# tqdm update method adds the value to the progress
|
||||
# bar instead of setting the value. Therefore, a delta
|
||||
@@ -108,23 +126,21 @@ class Upscaler:
|
||||
previous_cycle_frames = 0
|
||||
while not self.progress_bar_exit_signal:
|
||||
|
||||
try:
|
||||
total_frames_upscaled = len([f for f in os.listdir(self.upscaled_frames) if f[-4:] == '.png'])
|
||||
delta = total_frames_upscaled - previous_cycle_frames
|
||||
previous_cycle_frames = total_frames_upscaled
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
self.total_frames_upscaled = len([f for f in self.upscaled_frames.iterdir() if str(f)[-4:] == f'.{self.image_format}'])
|
||||
delta = self.total_frames_upscaled - previous_cycle_frames
|
||||
previous_cycle_frames = self.total_frames_upscaled
|
||||
|
||||
# if upscaling is finished
|
||||
if total_frames_upscaled >= total_frames:
|
||||
if self.total_frames_upscaled >= self.total_frames:
|
||||
return
|
||||
|
||||
# adds the detla into the progress bar
|
||||
# adds the delta into the progress bar
|
||||
progress_bar.update(delta)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def _upscale_frames(self, w2):
|
||||
def _upscale_frames(self):
|
||||
""" Upscale video frames with waifu2x-caffe
|
||||
|
||||
This function upscales all the frames extracted
|
||||
@@ -141,95 +157,140 @@ class Upscaler:
|
||||
# if this thread is not empty, then an exception has occured
|
||||
self.upscaler_exceptions = []
|
||||
|
||||
# initialize waifu2x driver
|
||||
drivers = AVAILABLE_DRIVERS
|
||||
if self.waifu2x_driver not in drivers:
|
||||
raise UnrecognizedDriverError(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
|
||||
|
||||
# it's easier to do multi-threading with waifu2x_converter
|
||||
# the number of threads can be passed directly to waifu2x_converter
|
||||
if self.waifu2x_driver == 'waifu2x_converter':
|
||||
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
|
||||
|
||||
progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],))
|
||||
progress_bar.start()
|
||||
|
||||
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads, self.image_format, self.upscaler_exceptions)
|
||||
for image in [f for f in os.listdir(self.upscaled_frames) if os.path.isfile(os.path.join(self.upscaled_frames, f))]:
|
||||
renamed = re.sub('_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{}'.format(self.image_format), '.{}'.format(self.image_format), image)
|
||||
shutil.move('{}\\{}'.format(self.upscaled_frames, image), '{}\\{}'.format(self.upscaled_frames, renamed))
|
||||
for image in [f for f in self.upscaled_frames.iterdir() if f.is_file()]:
|
||||
renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', str(image))
|
||||
(self.upscaled_frames / image).rename(self.upscaled_frames / renamed)
|
||||
|
||||
self.progress_bar_exit_signal = True
|
||||
progress_bar.join()
|
||||
return
|
||||
|
||||
# create a container for all upscaler threads
|
||||
upscaler_threads = []
|
||||
# drivers that are to be multi-threaded by video2x
|
||||
else:
|
||||
# create a container for all upscaler threads
|
||||
upscaler_threads = []
|
||||
|
||||
# list all images in the extracted frames
|
||||
frames = [os.path.join(self.extracted_frames, f) for f in os.listdir(self.extracted_frames) if os.path.isfile(os.path.join(self.extracted_frames, f))]
|
||||
# list all images in the extracted frames
|
||||
frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file]
|
||||
|
||||
# if we have less images than threads,
|
||||
# create only the threads necessary
|
||||
if len(frames) < self.threads:
|
||||
self.threads = len(frames)
|
||||
# if we have less images than threads,
|
||||
# create only the threads necessary
|
||||
if len(frames) < self.threads:
|
||||
self.threads = len(frames)
|
||||
|
||||
# create a folder for each thread and append folder
|
||||
# name into a list
|
||||
# create a directory for each thread and append directory
|
||||
# name into a list
|
||||
|
||||
thread_pool = []
|
||||
thread_folders = []
|
||||
for thread_id in range(self.threads):
|
||||
thread_folder = '{}\\{}'.format(self.extracted_frames, str(thread_id))
|
||||
thread_folders.append(thread_folder)
|
||||
thread_pool = []
|
||||
thread_directories = []
|
||||
for thread_id in range(self.threads):
|
||||
thread_directory = self.extracted_frames / str(thread_id)
|
||||
thread_directories.append(thread_directory)
|
||||
|
||||
# delete old folders and create new folders
|
||||
if os.path.isdir(thread_folder):
|
||||
shutil.rmtree(thread_folder)
|
||||
os.mkdir(thread_folder)
|
||||
# delete old directories and create new directories
|
||||
if thread_directory.is_dir():
|
||||
shutil.rmtree(thread_directory)
|
||||
thread_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# append folder path into list
|
||||
thread_pool.append((thread_folder, thread_id))
|
||||
# append directory path into list
|
||||
thread_pool.append((thread_directory, thread_id))
|
||||
|
||||
# evenly distribute images into each folder
|
||||
# until there is none left in the folder
|
||||
for image in frames:
|
||||
# move image
|
||||
shutil.move(image, thread_pool[0][0])
|
||||
# rotate list
|
||||
thread_pool = thread_pool[-1:] + thread_pool[:-1]
|
||||
# evenly distribute images into each directory
|
||||
# until there is none left in the directory
|
||||
for image in frames:
|
||||
# move image
|
||||
image.rename(thread_pool[0][0] / image.name)
|
||||
# rotate list
|
||||
thread_pool = thread_pool[-1:] + thread_pool[:-1]
|
||||
|
||||
# create threads and start them
|
||||
for thread_info in thread_pool:
|
||||
# create thread
|
||||
if self.scale_ratio:
|
||||
thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, self.scale_ratio, False, False, self.image_format, self.upscaler_exceptions))
|
||||
else:
|
||||
thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, False, self.scale_width, self.scale_height, self.image_format, self.upscaler_exceptions))
|
||||
thread.name = thread_info[1]
|
||||
# create threads and start them
|
||||
for thread_info in thread_pool:
|
||||
|
||||
# add threads into the pool
|
||||
upscaler_threads.append(thread)
|
||||
# create a separate w2 instance for each thread
|
||||
if self.waifu2x_driver == 'waifu2x_caffe':
|
||||
w2 = Waifu2xCaffe(copy.deepcopy(self.waifu2x_settings), self.method, self.model_dir, self.bit_depth)
|
||||
if self.scale_ratio:
|
||||
thread = threading.Thread(target=w2.upscale,
|
||||
args=(thread_info[0],
|
||||
self.upscaled_frames,
|
||||
self.scale_ratio,
|
||||
False,
|
||||
False,
|
||||
self.image_format,
|
||||
self.upscaler_exceptions))
|
||||
else:
|
||||
thread = threading.Thread(target=w2.upscale,
|
||||
args=(thread_info[0],
|
||||
self.upscaled_frames,
|
||||
False,
|
||||
self.scale_width,
|
||||
self.scale_height,
|
||||
self.image_format,
|
||||
self.upscaler_exceptions))
|
||||
|
||||
# start progress bar in a different thread
|
||||
progress_bar = threading.Thread(target=self._progress_bar, args=(thread_folders,))
|
||||
progress_bar.start()
|
||||
# if the driver being used is waifu2x_ncnn_vulkan
|
||||
elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan':
|
||||
w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings))
|
||||
thread = threading.Thread(target=w2.upscale,
|
||||
args=(thread_info[0],
|
||||
self.upscaled_frames,
|
||||
self.scale_ratio,
|
||||
self.upscaler_exceptions))
|
||||
|
||||
# create the clearer and start it
|
||||
Avalon.debug_info('Starting upscaled image cleaner')
|
||||
image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_threads))
|
||||
image_cleaner.start()
|
||||
# if the driver being used is anime4k
|
||||
elif self.waifu2x_driver == 'anime4k':
|
||||
w2 = Anime4k(copy.deepcopy(self.waifu2x_settings))
|
||||
thread = threading.Thread(target=w2.upscale,
|
||||
args=(thread_info[0],
|
||||
self.upscaled_frames,
|
||||
self.scale_ratio,
|
||||
self.upscaler_exceptions))
|
||||
|
||||
# start all threads
|
||||
for thread in upscaler_threads:
|
||||
thread.start()
|
||||
# create thread
|
||||
thread.name = thread_info[1]
|
||||
|
||||
# wait for threads to finish
|
||||
for thread in upscaler_threads:
|
||||
thread.join()
|
||||
# add threads into the pool
|
||||
upscaler_threads.append(thread)
|
||||
|
||||
# upscaling done, kill the clearer
|
||||
Avalon.debug_info('Killing upscaled image cleaner')
|
||||
image_cleaner.stop()
|
||||
# start progress bar in a different thread
|
||||
progress_bar = threading.Thread(target=self._progress_bar, args=(thread_directories,))
|
||||
progress_bar.start()
|
||||
|
||||
self.progress_bar_exit_signal = True
|
||||
# create the clearer and start it
|
||||
Avalon.debug_info('Starting upscaled image cleaner')
|
||||
image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_threads))
|
||||
image_cleaner.start()
|
||||
|
||||
if len(self.upscaler_exceptions) != 0:
|
||||
raise(self.upscaler_exceptions[0])
|
||||
# start all threads
|
||||
for thread in upscaler_threads:
|
||||
thread.start()
|
||||
|
||||
# wait for threads to finish
|
||||
for thread in upscaler_threads:
|
||||
thread.join()
|
||||
|
||||
# upscaling done, kill the clearer
|
||||
Avalon.debug_info('Killing upscaled image cleaner')
|
||||
image_cleaner.stop()
|
||||
|
||||
self.progress_bar_exit_signal = True
|
||||
|
||||
if len(self.upscaler_exceptions) != 0:
|
||||
raise(self.upscaler_exceptions[0])
|
||||
|
||||
def run(self):
|
||||
"""Main controller for Video2X
|
||||
@@ -243,20 +304,12 @@ class Upscaler:
|
||||
self._check_arguments()
|
||||
|
||||
# convert paths to absolute paths
|
||||
self.input_video = os.path.abspath(self.input_video)
|
||||
self.output_video = os.path.abspath(self.output_video)
|
||||
self.input_video = self.input_video.absolute()
|
||||
self.output_video = self.output_video.absolute()
|
||||
|
||||
# initialize objects for ffmpeg and waifu2x-caffe
|
||||
fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
|
||||
|
||||
# initialize waifu2x driver
|
||||
if self.waifu2x_driver == 'waifu2x_caffe':
|
||||
w2 = Waifu2xCaffe(self.waifu2x_settings, self.method, self.model_dir)
|
||||
elif self.waifu2x_driver == 'waifu2x_converter':
|
||||
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
|
||||
else:
|
||||
raise Exception('Unrecognized waifu2x driver: {}'.format(self.waifu2x_driver))
|
||||
|
||||
# extract frames from video
|
||||
fm.extract_frames(self.input_video, self.extracted_frames)
|
||||
|
||||
@@ -275,29 +328,40 @@ class Upscaler:
|
||||
# exit if no video stream found
|
||||
if video_stream_index is None:
|
||||
Avalon.error('Aborting: No video stream found')
|
||||
exit(1)
|
||||
raise StreamNotFoundError('no video stream found')
|
||||
|
||||
# get average frame rate of video stream
|
||||
framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate']))
|
||||
Avalon.info('Framerate: {}'.format(framerate))
|
||||
fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt']
|
||||
|
||||
# get a dict of all pixel formats and corresponding bit depth
|
||||
pixel_formats = fm.get_pixel_formats()
|
||||
|
||||
try:
|
||||
self.bit_depth = pixel_formats[fm.pixel_format]
|
||||
except KeyError:
|
||||
Avalon.error(f'Unsupported pixel format: {fm.pixel_format}')
|
||||
raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}')
|
||||
|
||||
Avalon.info(f'Framerate: {framerate}')
|
||||
|
||||
# width/height will be coded width/height x upscale factor
|
||||
if self.scale_ratio:
|
||||
coded_width = video_info['streams'][video_stream_index]['coded_width']
|
||||
coded_height = video_info['streams'][video_stream_index]['coded_height']
|
||||
self.scale_width = int(self.scale_ratio * coded_width)
|
||||
self.scale_height = int(self.scale_ratio * coded_height)
|
||||
original_width = video_info['streams'][video_stream_index]['width']
|
||||
original_height = video_info['streams'][video_stream_index]['height']
|
||||
self.scale_width = int(self.scale_ratio * original_width)
|
||||
self.scale_height = int(self.scale_ratio * original_height)
|
||||
|
||||
# upscale images one by one using waifu2x
|
||||
Avalon.info('Starting to upscale extracted images')
|
||||
self._upscale_frames(w2)
|
||||
self._upscale_frames()
|
||||
Avalon.info('Upscaling completed')
|
||||
|
||||
# frames to Video
|
||||
Avalon.info('Converting extracted frames into video')
|
||||
|
||||
# use user defined output size
|
||||
fm.convert_video(framerate, '{}x{}'.format(self.scale_width, self.scale_height), self.upscaled_frames)
|
||||
fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames)
|
||||
Avalon.info('Conversion completed')
|
||||
|
||||
# migrate audio tracks and subtitles
|
||||
|
||||
@@ -39,34 +39,63 @@
|
||||
"output": null,
|
||||
"input": null
|
||||
},
|
||||
"waifu2x_ncnn_vulkan": {
|
||||
"waifu2x_ncnn_vulkan_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-ncnn-vulkan\\waifu2x-ncnn-vulkan.exe",
|
||||
"v": null,
|
||||
"i": null,
|
||||
"o": null,
|
||||
"n": 2,
|
||||
"s": 2,
|
||||
"t": 400,
|
||||
"m": "models-cunet",
|
||||
"g": 0,
|
||||
"j": "1:2:2"
|
||||
},
|
||||
"anime4k": {
|
||||
"anime4k_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\anime4k\\Anime4K.jar",
|
||||
"java_path": "C:\\Program Files\\Java\\jdk-12.0.2\\bin\\java.exe"
|
||||
},
|
||||
"ffmpeg": {
|
||||
"ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-latest-win64-static\\bin",
|
||||
"video_to_frames": {
|
||||
"output_options": {
|
||||
"-qscale:v": null,
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
"-pix_fmt": "rgba64be"
|
||||
},
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
},
|
||||
"frames_to_video": {
|
||||
"input_options": {
|
||||
"-qscale:v": null,
|
||||
"-qscale:a": null,
|
||||
"-f": "image2",
|
||||
"-f": "image2"
|
||||
},
|
||||
"output_options": {
|
||||
"-vcodec": "libx264",
|
||||
"-crf": 25,
|
||||
"-pix_fmt": "yuv420p",
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
"-crf": 17,
|
||||
"-b:v": null,
|
||||
"-pix_fmt": null
|
||||
},
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
},
|
||||
"migrating_tracks": {
|
||||
"-map": "0:v:0?",
|
||||
"-map": "1?",
|
||||
"output_options": {
|
||||
"-map": [
|
||||
"0:v:0?",
|
||||
"1?",
|
||||
"-1:v?"
|
||||
],
|
||||
"-c": "copy",
|
||||
"-map": "-1:v?",
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
"-pix_fmt": null
|
||||
},
|
||||
"-hwaccel": "auto",
|
||||
"-y": true
|
||||
}
|
||||
},
|
||||
"video2x": {
|
||||
"video2x_cache_folder": null,
|
||||
"video2x_cache_directory": null,
|
||||
"image_format": "png",
|
||||
"preserve_frames": false
|
||||
}
|
||||
|
||||
320
bin/video2x.py
320
bin/video2x.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
r"""
|
||||
|
||||
__ __ _ _ ___ __ __
|
||||
\ \ / / (_) | | |__ \ \ \ / /
|
||||
@@ -13,7 +13,10 @@ __ __ _ _ ___ __ __
|
||||
Name: Video2X Controller
|
||||
Author: K4YT3X
|
||||
Date Created: Feb 24, 2018
|
||||
Last Modified: March 30, 2019
|
||||
Last Modified: August 29, 2019
|
||||
|
||||
Dev: BrianPetkovsek
|
||||
Dev: SAT3LL
|
||||
|
||||
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
||||
available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -38,20 +41,45 @@ enlarging engine. It extracts frames from a video, enlarge it by a
|
||||
number of times without losing any details or quality, keeping lines
|
||||
smooth and edges sharp.
|
||||
"""
|
||||
from avalon_framework import Avalon
|
||||
|
||||
# local imports
|
||||
from exceptions import *
|
||||
from upscaler import AVAILABLE_DRIVERS
|
||||
from upscaler import Upscaler
|
||||
|
||||
# built-in imports
|
||||
import argparse
|
||||
import GPUtil
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import psutil
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
VERSION = '2.7.0'
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
import GPUtil
|
||||
import psutil
|
||||
|
||||
VERSION = '2.10.0'
|
||||
|
||||
LEGAL_INFO = f'''Video2X Version: {VERSION}
|
||||
Author: K4YT3X
|
||||
License: GNU GPL v3
|
||||
Github Page: https://github.com/k4yt3x/video2x
|
||||
Contact: k4yt3x@k4yt3x.com'''
|
||||
|
||||
LOGO = r'''
|
||||
__ __ _ _ ___ __ __
|
||||
\ \ / / (_) | | |__ \ \ \ / /
|
||||
\ \ / / _ __| | ___ ___ ) | \ V /
|
||||
\ \/ / | | / _` | / _ \ / _ \ / / > <
|
||||
\ / | | | (_| | | __/ | (_) | / /_ / . \
|
||||
\/ |_| \__,_| \___| \___/ |____| /_/ \_\
|
||||
'''
|
||||
|
||||
# each thread might take up to 2.5 GB during initialization.
|
||||
# (system memory, not to be confused with GPU memory)
|
||||
@@ -70,38 +98,37 @@ def process_arguments():
|
||||
|
||||
# video options
|
||||
file_options = parser.add_argument_group('File Options')
|
||||
file_options.add_argument('-i', '--input', help='Source video file/directory', action='store', required=True)
|
||||
file_options.add_argument('-o', '--output', help='Output video file/directory', action='store', required=True)
|
||||
file_options.add_argument('-i', '--input', type=pathlib.Path, help='source video file/directory', action='store')
|
||||
file_options.add_argument('-o', '--output', type=pathlib.Path, help='output video file/directory', action='store')
|
||||
|
||||
# upscaler options
|
||||
upscaler_options = parser.add_argument_group('Upscaler Options')
|
||||
upscaler_options.add_argument('-m', '--method', help='Upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'], required=True)
|
||||
upscaler_options.add_argument('-d', '--driver', help='Waifu2x driver', action='store', default='waifu2x_caffe', choices=['waifu2x_caffe', 'waifu2x_converter'])
|
||||
upscaler_options.add_argument('-y', '--model_dir', help='Folder 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=5)
|
||||
upscaler_options.add_argument('-c', '--config', help='Video2X config file location', action='store', default='{}\\video2x.json'.format(os.path.dirname(os.path.abspath(sys.argv[0]))))
|
||||
upscaler_options.add_argument('-b', '--batch', help='Enable batch mode (select all default values to questions)', action='store_true')
|
||||
upscaler_options.add_argument('-m', '--method', help='upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'])
|
||||
upscaler_options.add_argument('-d', '--driver', help='upscaling driver', action='store', default='waifu2x_caffe', choices=AVAILABLE_DRIVERS)
|
||||
upscaler_options.add_argument('-y', '--model_dir', type=pathlib.Path, help='directory containing model JSON files', action='store')
|
||||
upscaler_options.add_argument('-t', '--threads', help='number of threads to use for upscaling', action='store', type=int, default=1)
|
||||
upscaler_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.json')
|
||||
upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true')
|
||||
|
||||
# scaling options
|
||||
scaling_options = parser.add_argument_group('Scaling Options')
|
||||
scaling_options.add_argument('--width', help='Output video width', action='store', type=int)
|
||||
scaling_options.add_argument('--height', help='Output video height', action='store', type=int)
|
||||
scaling_options.add_argument('-r', '--ratio', help='Scaling ratio', action='store', type=float)
|
||||
scaling_options.add_argument('--width', help='output video width', action='store', type=int)
|
||||
scaling_options.add_argument('--height', help='output video height', action='store', type=int)
|
||||
scaling_options.add_argument('-r', '--ratio', help='scaling ratio', action='store', type=float)
|
||||
|
||||
# extra options
|
||||
extra_options = parser.add_argument_group('Extra Options')
|
||||
extra_options.add_argument('-v', '--version', help='display version, lawful information and exit', action='store_true')
|
||||
|
||||
# parse arguments
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def print_logo():
|
||||
print('__ __ _ _ ___ __ __')
|
||||
print('\\ \\ / / (_) | | |__ \\ \\ \\ / /')
|
||||
print(' \\ \\ / / _ __| | ___ ___ ) | \\ V /')
|
||||
print(' \\ \\/ / | | / _` | / _ \\ / _ \\ / / > <')
|
||||
print(' \\ / | | | (_| | | __/ | (_) | / /_ / . \\')
|
||||
print(' \\/ |_| \\__,_| \\___| \\___/ |____| /_/ \\_\\')
|
||||
print('\n Video2X Video Enlarger')
|
||||
spaces = ((44 - len("Version {}".format(VERSION))) // 2) * " "
|
||||
print('{}\n{} Version {}\n{}'.format(Avalon.FM.BD, spaces, VERSION, Avalon.FM.RST))
|
||||
"""print video2x logo"""
|
||||
print(LOGO)
|
||||
print(f'\n{"Video2X Video Enlarger".rjust(40, " ")}')
|
||||
print(f'\n{Avalon.FM.BD}{f"Version {VERSION}".rjust(36, " ")}{Avalon.FM.RST}\n')
|
||||
|
||||
|
||||
def check_memory():
|
||||
@@ -118,17 +145,16 @@ def check_memory():
|
||||
# check if Nvidia-smi is available
|
||||
# GPUtil requires nvidia-smi.exe to interact with GPU
|
||||
if args.method == 'gpu' or args.method == 'cudnn':
|
||||
if not os.path.isfile('C:\\Program Files\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe'):
|
||||
if not (shutil.which('nvidia-smi') or
|
||||
pathlib.Path(r'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe').is_file()):
|
||||
# Nvidia System Management Interface not available
|
||||
Avalon.warning('Nvidia-smi not available, skipping available memory check')
|
||||
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
|
||||
else:
|
||||
try:
|
||||
with contextlib.suppress(ValueError):
|
||||
# "0" is GPU ID. Both waifu2x drivers use the first GPU available, therefore only 0 makes sense
|
||||
gpu_memory_available = (GPUtil.getGPUs()[0].memoryTotal - GPUtil.getGPUs()[0].memoryUsed) / 1024
|
||||
memory_status.append(('GPU', gpu_memory_available))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# go though each checkable memory type and check availability
|
||||
for memory_type, memory_available in memory_status:
|
||||
@@ -140,17 +166,17 @@ def check_memory():
|
||||
|
||||
# if user doesn't even have enough memory to run even one thread
|
||||
if memory_available < mem_per_thread:
|
||||
Avalon.warning('You might have insufficient amount of {} memory available to run this program ({} GB)'.format(memory_type, memory_available))
|
||||
Avalon.warning(f'You might have insufficient amount of {memory_type} memory available to run this program ({memory_available} GB)')
|
||||
Avalon.warning('Proceed with caution')
|
||||
if args.threads > 1:
|
||||
if Avalon.ask('Reduce number of threads to avoid crashing?', default=True, batch=args.batch):
|
||||
args.threads = 1
|
||||
# if memory available is less than needed, warn the user
|
||||
elif memory_available < (mem_per_thread * args.threads):
|
||||
Avalon.warning('Each waifu2x-caffe thread will require up to 2.5 GB of system memory')
|
||||
Avalon.warning('You demanded {} threads to be created, but you only have {} GB {} memory available'.format(args.threads, round(memory_available, 4), memory_type))
|
||||
Avalon.warning('{} GB of {} memory is recommended for {} threads'.format(mem_per_thread * args.threads, memory_type, args.threads))
|
||||
Avalon.warning('With your current amount of {} memory available, {} threads is recommended'.format(memory_type, int(memory_available // mem_per_thread)))
|
||||
Avalon.warning(f'Each waifu2x-caffe thread will require up to {SYS_MEM_PER_THREAD} GB of system memory')
|
||||
Avalon.warning(f'You demanded {args.threads} threads to be created, but you only have {round(memory_available, 4)} GB {memory_type} memory available')
|
||||
Avalon.warning(f'{mem_per_thread * args.threads} GB of {memory_type} memory is recommended for {args.threads} threads')
|
||||
Avalon.warning(f'With your current amount of {memory_type} memory available, {int(memory_available // mem_per_thread)} threads is recommended')
|
||||
|
||||
# ask the user if he / she wants to change to the recommended
|
||||
# number of threads
|
||||
@@ -170,78 +196,172 @@ def read_config(config_file):
|
||||
return config
|
||||
|
||||
|
||||
def absolutify_paths(config):
|
||||
""" Check to see if paths to binaries are absolute
|
||||
|
||||
This function checks if paths to binary files are absolute.
|
||||
If not, then absolutify the path.
|
||||
|
||||
Arguments:
|
||||
config {dict} -- configuration file dictionary
|
||||
|
||||
Returns:
|
||||
dict -- configuration file dictionary
|
||||
"""
|
||||
current_directory = pathlib.Path(sys.argv[0]).parent.absolute()
|
||||
|
||||
# check waifu2x-caffe path
|
||||
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
|
||||
config['waifu2x_caffe']['waifu2x_caffe_path'] = current_directory / config['waifu2x_caffe']['waifu2x_caffe_path']
|
||||
|
||||
# check waifu2x-converter-cpp path
|
||||
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
|
||||
config['waifu2x_converter']['waifu2x_converter_path'] = current_directory / config['waifu2x_converter']['waifu2x_converter_path']
|
||||
|
||||
# check waifu2x_ncnn_vulkan path
|
||||
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
|
||||
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = current_directory / config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path']
|
||||
|
||||
# check anime4k path
|
||||
if not re.match('^[a-z]:', config['anime4k']['anime4k_path'], re.IGNORECASE):
|
||||
config['anime4k']['anime4k_path'] = current_directory / config['anime4k']['anime4k_path']
|
||||
|
||||
# check ffmpeg path
|
||||
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
||||
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
|
||||
|
||||
# check video2x cache path
|
||||
if config['video2x']['video2x_cache_directory']:
|
||||
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
|
||||
config['video2x']['video2x_cache_directory'] = current_directory / config['video2x']['video2x_cache_directory']
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# /////////////////// Execution /////////////////// #
|
||||
|
||||
# this is not a library
|
||||
if __name__ != '__main__':
|
||||
Avalon.error('This file cannot be imported')
|
||||
raise ImportError('{} cannot be imported'.format(__file__))
|
||||
raise ImportError(f'{__file__} cannot be imported')
|
||||
|
||||
# print video2x logo
|
||||
print_logo()
|
||||
|
||||
# process CLI arguments
|
||||
args = process_arguments()
|
||||
|
||||
# display version and lawful informaition
|
||||
if args.version:
|
||||
print(LEGAL_INFO)
|
||||
exit(0)
|
||||
|
||||
# arguments sanity check
|
||||
if args.driver == 'waifu2x_converter' and args.width and args.height:
|
||||
Avalon.error('Waifu2x Converter CPP accepts only scaling ratio')
|
||||
exit(1)
|
||||
if not args.input:
|
||||
Avalon.error('You must specify input video file/directory path')
|
||||
raise ArgumentError('input video path not specified')
|
||||
if not args.output:
|
||||
Avalon.error('You must specify output video file/directory path')
|
||||
raise ArgumentError('output video path not specified')
|
||||
if (args.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and args.width and args.height:
|
||||
Avalon.error('Selected driver accepts only scaling ratio')
|
||||
raise ArgumentError('selected driver supports only scaling ratio')
|
||||
if args.driver == 'waifu2x_ncnn_vulkan' and (args.ratio > 2 or not args.ratio.is_integer()):
|
||||
Avalon.error('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
|
||||
raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
|
||||
if (args.width or args.height) and args.ratio:
|
||||
Avalon.error('You can only specify either scaling ratio or output width and height')
|
||||
exit(1)
|
||||
raise ArgumentError('both scaling ration and width/height specified')
|
||||
if (args.width and not args.height) or (not args.width and args.height):
|
||||
Avalon.error('You must specify both width and height')
|
||||
exit(1)
|
||||
raise ArgumentError('only one of width or height is specified')
|
||||
|
||||
# check available memory
|
||||
check_memory()
|
||||
# check available memory if driver is waifu2x-based
|
||||
if args.driver in ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan']:
|
||||
check_memory()
|
||||
|
||||
# anime4k runs significantly faster with more threads
|
||||
if args.driver == 'anime4k' and args.threads <= 1:
|
||||
Avalon.warning('Anime4K runs significantly faster with more threads')
|
||||
if Avalon.ask('Use more threads of Anime4K?', True):
|
||||
while True:
|
||||
try:
|
||||
threads = Avalon.gets('Amount of threads to use [5]: ')
|
||||
args.threads = int(threads)
|
||||
break
|
||||
except ValueError:
|
||||
if threads == '':
|
||||
args.threads = 5
|
||||
break
|
||||
else:
|
||||
Avalon.error(f'{threads} is not a valid integer')
|
||||
|
||||
# read configurations from JSON
|
||||
config = read_config(args.config)
|
||||
config = absolutify_paths(config)
|
||||
|
||||
# load waifu2x configuration
|
||||
if args.driver == 'waifu2x_caffe':
|
||||
waifu2x_settings = config['waifu2x_caffe']
|
||||
if not os.path.isfile(waifu2x_settings['waifu2x_caffe_path']):
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
|
||||
Avalon.error('Specified waifu2x-caffe directory doesn\'t exist')
|
||||
Avalon.error('Please check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
|
||||
elif args.driver == 'waifu2x_converter':
|
||||
waifu2x_settings = config['waifu2x_converter']
|
||||
if not os.path.isdir(waifu2x_settings['waifu2x_converter_path']):
|
||||
Avalon.error('Specified waifu2x-conver-cpp directory doesn\'t exist')
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
|
||||
Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist')
|
||||
Avalon.error('Please check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
|
||||
elif args.driver == 'waifu2x_ncnn_vulkan':
|
||||
waifu2x_settings = config['waifu2x_ncnn_vulkan']
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file():
|
||||
Avalon.error('Specified waifu2x_ncnn_vulkan directory doesn\'t exist')
|
||||
Avalon.error('Please check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
|
||||
elif args.driver == 'anime4k':
|
||||
waifu2x_settings = config['anime4k']
|
||||
if not pathlib.Path(waifu2x_settings['anime4k_path']).is_file():
|
||||
Avalon.error('Specified anime4k directory doesn\'t exist')
|
||||
Avalon.error('Please check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['anime4k_path'])
|
||||
|
||||
# check if waifu2x path is valid
|
||||
|
||||
|
||||
# read FFMPEG configuration
|
||||
# read FFmpeg configuration
|
||||
ffmpeg_settings = config['ffmpeg']
|
||||
|
||||
# load video2x settings
|
||||
video2x_cache_folder = config['video2x']['video2x_cache_folder']
|
||||
image_format = config['video2x']['image_format'].lower()
|
||||
preserve_frames = config['video2x']['preserve_frames']
|
||||
|
||||
# create temp directories if they don't exist
|
||||
if not video2x_cache_folder:
|
||||
video2x_cache_folder = '{}\\video2x'.format(tempfile.gettempdir())
|
||||
# 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_folder and not os.path.isdir(video2x_cache_folder):
|
||||
if not os.path.isfile(video2x_cache_folder) and not os.path.islink(video2x_cache_folder):
|
||||
Avalon.warning('Specified cache folder/directory {} does not exist'.format(video2x_cache_folder))
|
||||
if Avalon.ask('Create folder/directory?', default=True, batch=args.batch):
|
||||
if os.mkdir(video2x_cache_folder) is None:
|
||||
Avalon.info('{} created'.format(video2x_cache_folder))
|
||||
else:
|
||||
Avalon.error('Unable to create {}'.format(video2x_cache_folder))
|
||||
Avalon.error('Aborting...')
|
||||
exit(1)
|
||||
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
|
||||
Avalon.error('Specified cache directory is a file/link')
|
||||
raise FileExistsError('Specified cache directory is a file/link')
|
||||
|
||||
elif not video2x_cache_directory.exists():
|
||||
# if destination file is a file or a symbolic link
|
||||
Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
|
||||
|
||||
# try creating the cache directory
|
||||
if Avalon.ask('Create directory?', default=True, batch=args.batch):
|
||||
try:
|
||||
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
|
||||
Avalon.info(f'{video2x_cache_directory} created')
|
||||
|
||||
# there can be a number of exceptions here
|
||||
# PermissionError, FileExistsError, etc.
|
||||
# therefore, we put a catch-them-all here
|
||||
except Exception as e:
|
||||
Avalon.error(f'Unable to create {video2x_cache_directory}')
|
||||
Avalon.error('Aborting...')
|
||||
raise e
|
||||
else:
|
||||
Avalon.error('Specified cache folder/directory is a file/link')
|
||||
Avalon.error('Unable to continue, exiting...')
|
||||
exit(1)
|
||||
raise FileNotFoundError('Could not create cache directory')
|
||||
|
||||
|
||||
# start execution
|
||||
@@ -249,9 +369,22 @@ try:
|
||||
# start timer
|
||||
begin_time = time.time()
|
||||
|
||||
if os.path.isfile(args.input):
|
||||
""" Upscale single video file """
|
||||
Avalon.info('Upscaling single video file: {}'.format(args.input))
|
||||
# if input specified is a single file
|
||||
if args.input.is_file():
|
||||
|
||||
# upscale single video file
|
||||
Avalon.info(f'Upscaling single video file: {args.input}')
|
||||
|
||||
# check for input output format mismatch
|
||||
if args.output.is_dir():
|
||||
Avalon.error('Input and output path type mismatch')
|
||||
Avalon.error('Input is single file but output is directory')
|
||||
raise Exception('input output path type mismatch')
|
||||
if not re.search(r'.*\..*$', str(args.output)):
|
||||
Avalon.error('No suffix found in output file path')
|
||||
Avalon.error('Suffix must be specified for FFmpeg')
|
||||
raise Exception('No suffix specified')
|
||||
|
||||
upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
||||
|
||||
# set optional options
|
||||
@@ -261,19 +394,26 @@ try:
|
||||
upscaler.scale_ratio = args.ratio
|
||||
upscaler.model_dir = args.model_dir
|
||||
upscaler.threads = args.threads
|
||||
upscaler.video2x_cache_folder = video2x_cache_folder
|
||||
upscaler.video2x_cache_directory = video2x_cache_directory
|
||||
upscaler.image_format = image_format
|
||||
upscaler.preserve_frames = preserve_frames
|
||||
|
||||
# run upscaler-
|
||||
# run upscaler
|
||||
upscaler.create_temp_directories()
|
||||
upscaler.run()
|
||||
upscaler.cleanup()
|
||||
elif os.path.isdir(args.input):
|
||||
""" Upscale videos in a folder/directory """
|
||||
Avalon.info('Upscaling videos in folder/directory: {}'.format(args.input))
|
||||
for input_video in [f for f in os.listdir(args.input) if os.path.isfile(os.path.join(args.input, f))]:
|
||||
output_video = '{}\\{}'.format(args.output, input_video)
|
||||
upscaler = Upscaler(input_video=os.path.join(args.input, input_video), output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
||||
upscaler.cleanup_temp_directories()
|
||||
|
||||
# if input specified is a directory
|
||||
elif args.input.is_dir():
|
||||
# upscale videos in a directory
|
||||
Avalon.info(f'Upscaling videos in directory: {args.input}')
|
||||
|
||||
# make output directory if it doesn't exist
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for input_video in [f for f in args.input.iterdir() if f.is_file()]:
|
||||
output_video = args.output / input_video.name
|
||||
upscaler = Upscaler(input_video=input_video, output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
||||
|
||||
# set optional options
|
||||
upscaler.waifu2x_driver = args.driver
|
||||
@@ -282,26 +422,24 @@ try:
|
||||
upscaler.scale_ratio = args.ratio
|
||||
upscaler.model_dir = args.model_dir
|
||||
upscaler.threads = args.threads
|
||||
upscaler.video2x_cache_folder = video2x_cache_folder
|
||||
upscaler.video2x_cache_directory = video2x_cache_directory
|
||||
upscaler.image_format = image_format
|
||||
upscaler.preserve_frames = preserve_frames
|
||||
|
||||
# run upscaler
|
||||
upscaler.create_temp_directories()
|
||||
upscaler.run()
|
||||
upscaler.cleanup()
|
||||
upscaler.cleanup_temp_directories()
|
||||
else:
|
||||
Avalon.error('Input path is neither a file nor a folder/directory')
|
||||
raise FileNotFoundError('{} is neither file nor folder/directory'.format(args.input))
|
||||
Avalon.error('Input path is neither a file nor a directory')
|
||||
raise FileNotFoundError(f'{args.input} is neither file nor directory')
|
||||
|
||||
Avalon.info('Program completed, taking {} seconds'.format(round((time.time() - begin_time), 5)))
|
||||
Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds')
|
||||
except Exception:
|
||||
Avalon.error('An exception has occurred')
|
||||
traceback.print_exc()
|
||||
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
|
||||
finally:
|
||||
# remove Video2X Cache folder
|
||||
try:
|
||||
# remove Video2X cache directory
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
if not preserve_frames:
|
||||
shutil.rmtree(video2x_cache_folder)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
shutil.rmtree(video2x_cache_directory)
|
||||
|
||||
416
bin/video2x_gui.py
Normal file
416
bin/video2x_gui.py
Normal file
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Name: Video2x GUI
|
||||
Author: K4YT3X
|
||||
Date Created: July 27, 2019
|
||||
Last Modified: August 17, 2019
|
||||
|
||||
Description: GUI for Video2X
|
||||
"""
|
||||
|
||||
# local imports
|
||||
from exceptions import *
|
||||
from upscaler import Upscaler
|
||||
|
||||
# built-in imports
|
||||
from tkinter import *
|
||||
from tkinter import messagebox
|
||||
from tkinter import ttk
|
||||
from tkinter.filedialog import *
|
||||
import json
|
||||
import pathlib
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
VERSION = '1.1.1'
|
||||
|
||||
LEGAL_INFO = f'''Video2X GUI Version: {VERSION}
|
||||
Author: K4YT3X
|
||||
License: GNU GPL v3
|
||||
Github Page: https://github.com/k4yt3x/video2x
|
||||
Contact: k4yt3x@k4yt3x.com'''
|
||||
|
||||
# global static variables
|
||||
AVAILABLE_METHODS = {
|
||||
'GPU': 'gpu',
|
||||
'CUDNN': 'cudnn',
|
||||
'CPU': 'cpu'
|
||||
}
|
||||
|
||||
AVAILABLE_DRIVERS = {
|
||||
'Waifu2X Caffe': 'waifu2x_caffe',
|
||||
'Waifu2X Converter CPP': 'waifu2x_converter',
|
||||
'Waifu2x NCNN Vulkan': 'waifu2x_ncnn_vulkan',
|
||||
'Anime4K': 'anime4k'
|
||||
}
|
||||
|
||||
IMAGE_FORMATS = {'PNG', 'JPG'}
|
||||
|
||||
|
||||
class Video2xGui():
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.running = False
|
||||
|
||||
# create main window
|
||||
self.main_window = Tk()
|
||||
self.main_window.title(f'Video2X GUI {VERSION}')
|
||||
self.main_frame = Frame()
|
||||
self.main_frame.pack(fill=BOTH, expand=True)
|
||||
|
||||
# add menu bar
|
||||
self.menu_bar = Menu(self.main_frame)
|
||||
|
||||
# file menu
|
||||
self.file_menu = Menu(self.menu_bar, tearoff=0)
|
||||
self.file_menu.add_command(label='Exit', command=self.main_frame.quit)
|
||||
self.menu_bar.add_cascade(label='File', menu=self.file_menu)
|
||||
|
||||
# help menu
|
||||
self.help_menu = Menu(self.menu_bar, tearoff=0)
|
||||
self.help_menu.add_command(label='About', command=self._display_help)
|
||||
self.menu_bar.add_cascade(label='Help', menu=self.help_menu)
|
||||
|
||||
self.main_window.config(menu=self.menu_bar)
|
||||
|
||||
# file frame
|
||||
self.file_frame = Frame(self.main_frame)
|
||||
self.file_frame.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
|
||||
# input file
|
||||
self.input_file = StringVar()
|
||||
label_text = StringVar()
|
||||
label_text.set('Input File')
|
||||
Label(self.file_frame, textvariable=label_text, relief=RIDGE, width=10).grid(row=0, column=0, padx=5, pady=5, sticky=W)
|
||||
Entry(self.file_frame, textvariable=self.input_file, width=60).grid(row=0, column=1, padx=5, pady=5, sticky=W)
|
||||
Button(self.file_frame, text='Select', command=self._select_input).grid(row=0, column=2, padx=5, pady=5, sticky=W)
|
||||
|
||||
# output file
|
||||
self.output_file = StringVar()
|
||||
label_text = StringVar()
|
||||
label_text.set('Output File')
|
||||
Label(self.file_frame, textvariable=label_text, relief=RIDGE, width=10).grid(row=1, column=0, padx=5, pady=5, sticky=W)
|
||||
Entry(self.file_frame, textvariable=self.output_file, width=60).grid(row=1, column=1, padx=5, pady=5, sticky=W)
|
||||
Button(self.file_frame, text='Select', command=self._select_output).grid(row=1, column=2, padx=5, pady=5, sticky=W)
|
||||
|
||||
# options
|
||||
self.options_frame = Frame()
|
||||
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
self.options_frame.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
|
||||
self.options_left = Frame(self.options_frame)
|
||||
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
self.options_left.grid(row=0, column=0, padx=5, pady=5, sticky=N)
|
||||
|
||||
# width
|
||||
self.width = IntVar()
|
||||
# self.width.set(1920)
|
||||
Label(self.options_left, text='Width', relief=RIDGE, width=15).grid(row=0, column=0, padx=2, pady=3)
|
||||
width_field = Entry(self.options_left, textvariable=self.width)
|
||||
width_field.grid(row=0, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# height
|
||||
self.height = IntVar()
|
||||
# self.height.set(1080)
|
||||
Label(self.options_left, text='Height', relief=RIDGE, width=15).grid(row=1, column=0, padx=2, pady=3)
|
||||
height_field = Entry(self.options_left, textvariable=self.height)
|
||||
height_field.grid(row=1, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# scale ratio
|
||||
self.scale_ratio = DoubleVar()
|
||||
# self.scale_ratio.set(2.0)
|
||||
Label(self.options_left, text='Scale Ratio', relief=RIDGE, width=15).grid(row=2, column=0, padx=2, pady=3)
|
||||
scale_ratio_field = Entry(self.options_left, textvariable=self.scale_ratio)
|
||||
scale_ratio_field.grid(row=2, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# image format
|
||||
self.image_format = StringVar(self.options_left)
|
||||
self.image_format.set('PNG')
|
||||
Label(self.options_left, text='Image Format', relief=RIDGE, width=15).grid(row=3, column=0, padx=2, pady=3)
|
||||
image_format_menu = OptionMenu(self.options_left, self.image_format, *IMAGE_FORMATS)
|
||||
image_format_menu.grid(row=3, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# options
|
||||
self.options_right = Frame(self.options_frame)
|
||||
# self.options_left.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
self.options_right.grid(row=0, column=1, padx=5, pady=5, sticky=N)
|
||||
|
||||
# threads
|
||||
self.threads = IntVar()
|
||||
self.threads.set(1)
|
||||
Label(self.options_right, text='Threads', relief=RIDGE, width=15).grid(row=0, column=0, padx=2, pady=3)
|
||||
threads_field = Entry(self.options_right, textvariable=self.threads)
|
||||
threads_field.grid(row=0, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# method
|
||||
self.method = StringVar(self.options_left)
|
||||
self.method.set('GPU')
|
||||
Label(self.options_right, text='Method', relief=RIDGE, width=15).grid(row=1, column=0, padx=2, pady=3)
|
||||
method_menu = OptionMenu(self.options_right, self.method, *AVAILABLE_METHODS)
|
||||
method_menu.grid(row=1, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# driver
|
||||
self.driver = StringVar(self.options_left)
|
||||
self.driver.set('Waifu2X Caffe')
|
||||
Label(self.options_right, text='Driver', relief=RIDGE, width=15).grid(row=2, column=0, padx=2, pady=3)
|
||||
driver_menu = OptionMenu(self.options_right, self.driver, *AVAILABLE_DRIVERS)
|
||||
driver_menu.grid(row=2, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# preserve frames
|
||||
self.preserve_frames = BooleanVar(self.options_left)
|
||||
self.preserve_frames.set(True)
|
||||
Label(self.options_right, text='Preserve Frames', relief=RIDGE, width=15).grid(row=3, column=0, padx=2, pady=3)
|
||||
preserve_frames_menu = OptionMenu(self.options_right, self.preserve_frames, *{True, False})
|
||||
preserve_frames_menu.grid(row=3, column=1, padx=2, pady=3, sticky=W)
|
||||
|
||||
# progress bar
|
||||
self.progress_bar_frame = Frame()
|
||||
self.progress_bar_frame.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
|
||||
self.progress_bar = ttk.Progressbar(self.progress_bar_frame, orient='horizontal', length=100, mode='determinate')
|
||||
self.progress_bar.pack(fill=X)
|
||||
|
||||
# start button frame
|
||||
self.start_frame = Frame()
|
||||
self.start_frame.pack(fill=X, padx=5, pady=5, expand=True)
|
||||
|
||||
# start button
|
||||
self.start_button_text = StringVar()
|
||||
self.start_button_text.set('Start')
|
||||
Button(self.start_frame, textvariable=self.start_button_text, command=self._launch_upscaling, width=20).pack(side=RIGHT)
|
||||
|
||||
self.main_frame.mainloop()
|
||||
|
||||
def _display_help(self):
|
||||
messagebox.showinfo('About', LEGAL_INFO)
|
||||
|
||||
def _launch_upscaling(self):
|
||||
|
||||
# prevent launching multiple instances
|
||||
if self.running:
|
||||
messagebox.showerror('Error', 'Video2X is already running')
|
||||
return
|
||||
|
||||
# arguments sanity check
|
||||
if self.input_file.get() == '':
|
||||
messagebox.showerror('Error', 'You must specify input video file/directory path')
|
||||
return
|
||||
if self.output_file.get() == '':
|
||||
messagebox.showerror('Error', 'You must specify output video file/directory path')
|
||||
return
|
||||
if (self.driver.get() in ['Waifu2X Converter CPP', 'Waifu2x NCNN Vulkan', 'Anime4K']) and self.width.get() and self.height.get():
|
||||
messagebox.showerror('Error', f'Selected driver \"{self.driver.get()}\" accepts only scaling ratio')
|
||||
return
|
||||
if self.driver.get() == 'waifu2x_ncnn_vulkan' and (self.scale_ratio.get() > 2 or not self.scale_ratio.get().is_integer()):
|
||||
messagebox.showerror('Error', 'Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')
|
||||
return
|
||||
if (self.width.get() or self.height.get()) and self.scale_ratio.get():
|
||||
messagebox.showerror('Error', 'You can only specify either scaling ratio or output width and height')
|
||||
return
|
||||
if (self.width.get() and not self.height.get()) or (not self.width.get() and self.height.get()):
|
||||
messagebox.showerror('Error', 'You must specify both width and height')
|
||||
return
|
||||
if (not self.width.get() or not self.height.get()) and not self.scale_ratio.get():
|
||||
messagebox.showerror('Error', 'You must specify either output dimensions or scaling ratio')
|
||||
return
|
||||
|
||||
upscale = threading.Thread(target=self._upscale)
|
||||
upscale.start()
|
||||
self.running = True
|
||||
self.start_button_text.set('Running')
|
||||
|
||||
def _upscale(self):
|
||||
|
||||
# start timer
|
||||
begin_time = time.time()
|
||||
|
||||
# read configuration file
|
||||
config = read_config('video2x.json')
|
||||
config = absolutify_paths(config)
|
||||
|
||||
input_file = pathlib.Path(self.input_file.get())
|
||||
output_file = pathlib.Path(self.output_file.get())
|
||||
driver = AVAILABLE_DRIVERS[self.driver.get()]
|
||||
|
||||
if driver == 'waifu2x_caffe':
|
||||
waifu2x_settings = config['waifu2x_caffe']
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file():
|
||||
messagebox.showerror('Error', 'Specified waifu2x-caffe directory doesn\'t exist\nPlease check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path'])
|
||||
elif driver == 'waifu2x_converter':
|
||||
waifu2x_settings = config['waifu2x_converter']
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir():
|
||||
messagebox.showerror('Error', 'Specified waifu2x-converter-cpp directory doesn\'t exist\nPlease check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path'])
|
||||
elif driver == 'waifu2x_ncnn_vulkan':
|
||||
waifu2x_settings = config['waifu2x_ncnn_vulkan']
|
||||
if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file():
|
||||
messagebox.showerror('Error', 'Specified waifu2x_ncnn_vulkan directory doesn\'t exist\nPlease check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path'])
|
||||
elif driver == 'anime4k':
|
||||
waifu2x_settings = config['anime4k']
|
||||
if not pathlib.Path(waifu2x_settings['anime4k_path']).is_file():
|
||||
messagebox.showerror('Error', 'Specified Anime4K directory doesn\'t exist\nPlease check the configuration file settings')
|
||||
raise FileNotFoundError(waifu2x_settings['anime4k_path'])
|
||||
|
||||
# read FFmpeg configuration
|
||||
ffmpeg_settings = config['ffmpeg']
|
||||
|
||||
# load video2x settings
|
||||
image_format = config['video2x']['image_format'].lower()
|
||||
preserve_frames = config['video2x']['preserve_frames']
|
||||
|
||||
# load cache directory
|
||||
if isinstance(config['video2x']['video2x_cache_directory'], str):
|
||||
video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory'])
|
||||
else:
|
||||
video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x'
|
||||
|
||||
if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir():
|
||||
messagebox.showerror('Error', 'Specified cache directory is a file/link')
|
||||
raise FileExistsError('Specified cache directory is a file/link')
|
||||
|
||||
elif not video2x_cache_directory.exists():
|
||||
# try creating the cache directory
|
||||
if messagebox.askyesno('Question', f'Specified cache directory {video2x_cache_directory} does not exist\nCreate directory?'):
|
||||
try:
|
||||
video2x_cache_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# there can be a number of exceptions here
|
||||
# PermissionError, FileExistsError, etc.
|
||||
# therefore, we put a catch-them-all here
|
||||
except Exception as e:
|
||||
messagebox.showerror('Error', f'Unable to create {video2x_cache_directory}\nAborting...')
|
||||
raise e
|
||||
else:
|
||||
raise FileNotFoundError('Could not create cache directory')
|
||||
|
||||
# load more settings from gui
|
||||
width = self.width.get()
|
||||
height = self.height.get()
|
||||
scale_ratio = self.scale_ratio.get()
|
||||
image_format = self.image_format.get()
|
||||
threads = self.threads.get()
|
||||
method = AVAILABLE_METHODS[self.method.get()]
|
||||
preserve_frames = self.preserve_frames.get()
|
||||
|
||||
self.upscaler = Upscaler(input_video=input_file, output_video=output_file, method=method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
|
||||
|
||||
# set optional options
|
||||
self.upscaler.waifu2x_driver = driver
|
||||
self.upscaler.scale_width = width
|
||||
self.upscaler.scale_height = height
|
||||
self.upscaler.scale_ratio = scale_ratio
|
||||
self.upscaler.model_dir = None
|
||||
self.upscaler.threads = threads
|
||||
self.upscaler.video2x_cache_directory = video2x_cache_directory
|
||||
self.upscaler.image_format = image_format
|
||||
self.upscaler.preserve_frames = preserve_frames
|
||||
|
||||
# run upscaler
|
||||
self.upscaler.create_temp_directories()
|
||||
|
||||
# start progress bar
|
||||
progress_bar = threading.Thread(target=self._progress_bar)
|
||||
progress_bar.start()
|
||||
|
||||
# start upscaling
|
||||
self.upscaler.run()
|
||||
self.upscaler.cleanup_temp_directories()
|
||||
|
||||
# show message when upscaling completes
|
||||
messagebox.showinfo('Info', f'Upscaling Completed\nTime Taken: {round((time.time() - begin_time), 5)} seconds')
|
||||
self.progress_bar['value'] = 100
|
||||
self.running = False
|
||||
self.start_button_text.set('Start')
|
||||
|
||||
def _progress_bar(self):
|
||||
""" This method prints a progress bar
|
||||
|
||||
This method prints a progress bar by keeping track
|
||||
of the amount of frames in the input directory
|
||||
and the output directory. This is originally
|
||||
suggested by @ArmandBernard.
|
||||
"""
|
||||
# initialize variables early
|
||||
self.upscaler.progress_bar_exit_signal = False
|
||||
self.upscaler.total_frames_upscaled = 0
|
||||
self.upscaler.total_frames = 1
|
||||
|
||||
# initialize progress bar values
|
||||
self.progress_bar['value'] = 0
|
||||
|
||||
while not self.upscaler.progress_bar_exit_signal:
|
||||
self.progress_bar['value'] = int(100 * self.upscaler.total_frames_upscaled / self.upscaler.total_frames)
|
||||
time.sleep(1)
|
||||
|
||||
def _select_input(self):
|
||||
self.input_file.set(askopenfilename(title='Select Input File'))
|
||||
|
||||
# try to set an output file name automatically
|
||||
output_file = pathlib.Path(f'{self.input_file.get()}_output.mp4')
|
||||
|
||||
output_file_id = 0
|
||||
while output_file.is_file() and output_file_id <= 10:
|
||||
output_file = pathlib.Path(f'{self.input_file.get()}_output_{output_file_id}.mp4')
|
||||
output_file_id += 1
|
||||
|
||||
if not output_file.exists():
|
||||
self.output_file.set(str(output_file))
|
||||
|
||||
def _select_output(self):
|
||||
self.output_file.set(asksaveasfilename(title='Select Output File'))
|
||||
|
||||
|
||||
def read_config(config_file):
|
||||
""" Reads configuration file
|
||||
|
||||
Returns a dictionary read by JSON.
|
||||
"""
|
||||
with open(config_file, 'r') as raw_config:
|
||||
config = json.load(raw_config)
|
||||
return config
|
||||
|
||||
|
||||
def absolutify_paths(config):
|
||||
""" Check to see if paths to binaries are absolute
|
||||
|
||||
This function checks if paths to binary files are absolute.
|
||||
If not, then absolutify the path.
|
||||
|
||||
Arguments:
|
||||
config {dict} -- configuration file dictionary
|
||||
|
||||
Returns:
|
||||
dict -- configuration file dictionary
|
||||
"""
|
||||
current_directory = pathlib.Path(sys.argv[0]).parent.absolute()
|
||||
|
||||
# check waifu2x-caffe path
|
||||
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
|
||||
config['waifu2x_caffe']['waifu2x_caffe_path'] = current_directory / config['waifu2x_caffe']['waifu2x_caffe_path']
|
||||
|
||||
# check waifu2x-converter-cpp path
|
||||
if not re.match('^[a-z]:', config['waifu2x_converter']['waifu2x_converter_path'], re.IGNORECASE):
|
||||
config['waifu2x_converter']['waifu2x_converter_path'] = current_directory / config['waifu2x_converter']['waifu2x_converter_path']
|
||||
|
||||
# check waifu2x_ncnn_vulkan path
|
||||
if not re.match('^[a-z]:', config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'], re.IGNORECASE):
|
||||
config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = current_directory / config['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path']
|
||||
|
||||
# check ffmpeg path
|
||||
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
|
||||
config['ffmpeg']['ffmpeg_path'] = current_directory / config['ffmpeg']['ffmpeg_path']
|
||||
|
||||
# check video2x cache path
|
||||
if config['video2x']['video2x_cache_directory']:
|
||||
if not re.match('^[a-z]:', config['video2x']['video2x_cache_directory'], re.IGNORECASE):
|
||||
config['video2x']['video2x_cache_directory'] = current_directory / config['video2x']['video2x_cache_directory']
|
||||
|
||||
return config
|
||||
|
||||
|
||||
video2x_gui = Video2xGui()
|
||||
@@ -3,8 +3,11 @@
|
||||
"""
|
||||
Name: Video2X Setup Script
|
||||
Author: K4YT3X
|
||||
Author: BrianPetkovsek
|
||||
Date Created: November 28, 2018
|
||||
Last Modified: March 26, 2019
|
||||
Last Modified: August 20, 2019
|
||||
|
||||
Dev: SAT3LL
|
||||
|
||||
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
|
||||
available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
@@ -17,14 +20,24 @@ and generates a configuration for it.
|
||||
Installation Details:
|
||||
- ffmpeg: %LOCALAPPDATA%\\video2x\\ffmpeg
|
||||
- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe
|
||||
|
||||
- waifu2x-cpp-converter: %LOCALAPPDATA%\\video2x\\waifu2x-converter-cpp
|
||||
- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan
|
||||
- anime4k: %LOCALAPPDATA%\\video2x\\anime4k
|
||||
"""
|
||||
|
||||
# built-in imports
|
||||
import argparse
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
import urllib
|
||||
import zipfile
|
||||
|
||||
# Requests doesn't come with windows, therefore
|
||||
@@ -32,7 +45,11 @@ import zipfile
|
||||
# later in the script.
|
||||
# import requests
|
||||
|
||||
VERSION = '1.2.0'
|
||||
VERSION = '1.5.0'
|
||||
|
||||
# global static variables
|
||||
LOCALAPPDATA = pathlib.Path(os.getenv('localappdata'))
|
||||
DRIVER_OPTIONS = ['all', 'waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']
|
||||
|
||||
|
||||
def process_arguments():
|
||||
@@ -42,7 +59,7 @@ def process_arguments():
|
||||
|
||||
# video 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'], default='all')
|
||||
general_options.add_argument('-d', '--driver', help='driver to download and configure', action='store', choices=DRIVER_OPTIONS, default='all')
|
||||
|
||||
# parse arguments
|
||||
return parser.parse_args()
|
||||
@@ -55,25 +72,32 @@ class Video2xSetup:
|
||||
script. All files will be installed under %LOCALAPPDATA%\\video2x.
|
||||
"""
|
||||
|
||||
def __init__(self, driver):
|
||||
def __init__(self, driver, download_python_modules):
|
||||
self.driver = driver
|
||||
self.download_python_modules = download_python_modules
|
||||
self.trash = []
|
||||
|
||||
def run(self):
|
||||
if self.download_python_modules:
|
||||
print('\nInstalling Python libraries')
|
||||
self._install_python_requirements()
|
||||
|
||||
print('\nInstalling Python libraries')
|
||||
self._install_python_requirements()
|
||||
|
||||
print('\nInstalling FFMPEG')
|
||||
print('\nInstalling FFmpeg')
|
||||
self._install_ffmpeg()
|
||||
|
||||
if self.driver == 'all':
|
||||
self._install_waifu2x_caffe()
|
||||
self._install_waifu2x_converter_cpp()
|
||||
self._install_waifu2x_ncnn_vulkan()
|
||||
self._install_anime4k()
|
||||
elif self.driver == 'waifu2x_caffe':
|
||||
self._install_waifu2x_caffe()
|
||||
elif self.driver == 'waifu2x_converter':
|
||||
self._install_waifu2x_converter_cpp()
|
||||
elif self.driver == 'waifu2x_ncnn_vulkan':
|
||||
self._install_waifu2x_ncnn_vulkan()
|
||||
elif self.driver == 'anime4k':
|
||||
self._install_anime4k()
|
||||
|
||||
print('\nGenerating Video2X configuration file')
|
||||
self._generate_config()
|
||||
@@ -84,20 +108,22 @@ class Video2xSetup:
|
||||
def _install_python_requirements(self):
|
||||
""" Read requirements.txt and return its content
|
||||
"""
|
||||
with open('requirements.txt', 'r') as req:
|
||||
for line in req:
|
||||
package = line.split('==')[0]
|
||||
pip_install(package)
|
||||
pip_install('requirements.txt')
|
||||
|
||||
def _cleanup(self):
|
||||
""" Cleanup all the temp files downloaded
|
||||
"""
|
||||
for file in self.trash:
|
||||
try:
|
||||
print('Deleting: {}'.format(file))
|
||||
os.remove(file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if file.is_dir():
|
||||
print(f'Deleting directory: {file}')
|
||||
shutil.rmtree(file)
|
||||
else:
|
||||
print(f'Deleting file: {file}')
|
||||
file.unlink()
|
||||
except Exception:
|
||||
print(f'Error deleting: {file}')
|
||||
traceback.print_exc()
|
||||
|
||||
def _install_ffmpeg(self):
|
||||
""" Install FFMPEG
|
||||
@@ -108,7 +134,7 @@ class Video2xSetup:
|
||||
self.trash.append(ffmpeg_zip)
|
||||
|
||||
with zipfile.ZipFile(ffmpeg_zip) as zipf:
|
||||
zipf.extractall('{}\\video2x'.format(os.getenv('localappdata')))
|
||||
zipf.extractall(LOCALAPPDATA / 'video2x')
|
||||
|
||||
def _install_waifu2x_caffe(self):
|
||||
""" Install waifu2x_caffe
|
||||
@@ -117,7 +143,7 @@ class Video2xSetup:
|
||||
import requests
|
||||
|
||||
# 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)
|
||||
latest_release = requests.get('https://api.github.com/repos/lltcggie/waifu2x-caffe/releases/latest').json()
|
||||
|
||||
for a in latest_release['assets']:
|
||||
if 'waifu2x-caffe.zip' in a['browser_download_url']:
|
||||
@@ -125,17 +151,16 @@ class Video2xSetup:
|
||||
self.trash.append(waifu2x_caffe_zip)
|
||||
|
||||
with zipfile.ZipFile(waifu2x_caffe_zip) as zipf:
|
||||
zipf.extractall('{}\\video2x'.format(os.getenv('localappdata')))
|
||||
zipf.extractall(LOCALAPPDATA / 'video2x')
|
||||
|
||||
def _install_waifu2x_converter_cpp(self):
|
||||
""" Install waifu2x_caffe
|
||||
"""
|
||||
print('\nInstalling waifu2x-converter-cpp')
|
||||
import re
|
||||
import requests
|
||||
|
||||
# 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)
|
||||
latest_release = requests.get('https://api.github.com/repos/DeadSix27/waifu2x-converter-cpp/releases/latest').json()
|
||||
|
||||
for a in latest_release['assets']:
|
||||
if re.search(r'waifu2x-DeadSix27-win64_v[0-9]*\.zip', a['browser_download_url']):
|
||||
@@ -143,7 +168,61 @@ class Video2xSetup:
|
||||
self.trash.append(waifu2x_converter_cpp_zip)
|
||||
|
||||
with zipfile.ZipFile(waifu2x_converter_cpp_zip) as zipf:
|
||||
zipf.extractall('{}\\video2x\\waifu2x-converter-cpp'.format(os.getenv('localappdata')))
|
||||
zipf.extractall(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
||||
|
||||
def _install_waifu2x_ncnn_vulkan(self):
|
||||
""" Install waifu2x-ncnn-vulkan
|
||||
"""
|
||||
print('\nInstalling waifu2x-ncnn-vulkan')
|
||||
import requests
|
||||
|
||||
# Get latest release of waifu2x-ncnn-vulkan via Github API
|
||||
latest_release = requests.get('https://api.github.com/repos/nihui/waifu2x-ncnn-vulkan/releases/latest').json()
|
||||
|
||||
for a in latest_release['assets']:
|
||||
if re.search(r'waifu2x-ncnn-vulkan-\d*\.zip', a['browser_download_url']):
|
||||
waifu2x_ncnn_vulkan_zip = download(a['browser_download_url'], tempfile.gettempdir())
|
||||
self.trash.append(waifu2x_ncnn_vulkan_zip)
|
||||
|
||||
# extract and rename
|
||||
waifu2x_ncnn_vulkan_directory = LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan'
|
||||
with zipfile.ZipFile(waifu2x_ncnn_vulkan_zip) as zipf:
|
||||
zipf.extractall(LOCALAPPDATA / 'video2x')
|
||||
|
||||
# if directory already exists, remove it
|
||||
if waifu2x_ncnn_vulkan_directory.exists():
|
||||
shutil.rmtree(waifu2x_ncnn_vulkan_directory)
|
||||
|
||||
# rename the newly extracted 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):
|
||||
""" Generate video2x config
|
||||
@@ -155,17 +234,21 @@ class Video2xSetup:
|
||||
|
||||
# configure only the specified drivers
|
||||
if self.driver == 'all':
|
||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = '{}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'.format(os.getenv('localappdata'))
|
||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = '{}\\video2x\\waifu2x-converter-cpp'.format(os.getenv('localappdata'))
|
||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
|
||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
||||
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
|
||||
template_dict['anime4k']['anime4k_path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
|
||||
elif self.driver == 'waifu2x_caffe':
|
||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = '{}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'.format(os.getenv('localappdata'))
|
||||
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui.exe')
|
||||
elif self.driver == 'waifu2x_converter':
|
||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = '{}\\video2x\\waifu2x-converter-cpp'.format(os.getenv('localappdata'))
|
||||
template_dict['waifu2x_converter']['waifu2x_converter_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp')
|
||||
elif self.driver == 'waifu2x_ncnn_vulkan':
|
||||
template_dict['waifu2x_ncnn_vulkan']['waifu2x_ncnn_vulkan_path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan.exe')
|
||||
elif self.driver == 'anime4k':
|
||||
template_dict['anime4k']['anime4k_path'] = str(LOCALAPPDATA / 'video2x' / 'anime4k' / 'Anime4K.jar')
|
||||
|
||||
template_dict['ffmpeg']['ffmpeg_path'] = '{}\\video2x\\ffmpeg-latest-win64-static\\bin'.format(os.getenv('localappdata'))
|
||||
template_dict['ffmpeg']['ffmpeg_hwaccel'] = 'auto'
|
||||
template_dict['ffmpeg']['extra_arguments'] = []
|
||||
template_dict['video2x']['video2x_cache_folder'] = False
|
||||
template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin')
|
||||
template_dict['video2x']['video2x_cache_directory'] = None
|
||||
template_dict['video2x']['preserve_frames'] = False
|
||||
|
||||
# Write configuration into file
|
||||
@@ -180,13 +263,40 @@ def download(url, save_path, chunk_size=4096):
|
||||
from tqdm import tqdm
|
||||
import requests
|
||||
|
||||
output_file = '{}\\{}'.format(save_path, url.split('/')[-1])
|
||||
print('Downloading: {}'.format(url))
|
||||
print('Chunk size: {}'.format(chunk_size))
|
||||
print('Saving to: {}'.format(output_file))
|
||||
save_path = pathlib.Path(save_path)
|
||||
|
||||
stream = requests.get(url, stream=True)
|
||||
total_size = int(stream.headers['content-length'])
|
||||
# create target folder if it doesn't exist
|
||||
save_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# create requests stream for steaming file
|
||||
stream = requests.get(url, stream=True, allow_redirects=True)
|
||||
|
||||
# get file name
|
||||
file_name = None
|
||||
if 'content-disposition' in stream.headers:
|
||||
disposition = stream.headers['content-disposition']
|
||||
with contextlib.suppress(IndexError):
|
||||
file_name = re.findall("filename=(.+)", disposition)[0].strip('"')
|
||||
|
||||
if file_name is None:
|
||||
# output_file = f'{save_path}\\{stream.url.split("/")[-1]}'
|
||||
output_file = save_path / stream.url.split('/')[-1]
|
||||
else:
|
||||
output_file = save_path / file_name
|
||||
|
||||
# decode url encoding
|
||||
output_file = pathlib.Path(urllib.parse.unquote(str(output_file)))
|
||||
|
||||
# get total size for progress bar if provided in headers
|
||||
total_size = 0
|
||||
if 'content-length' in stream.headers:
|
||||
total_size = int(stream.headers['content-length'])
|
||||
|
||||
# print download information summary
|
||||
print(f'Downloading: {url}')
|
||||
print(f'Total size: {total_size}')
|
||||
print(f'Chunk size: {chunk_size}')
|
||||
print(f'Saving to: {output_file}')
|
||||
|
||||
# Write content into file
|
||||
with open(output_file, 'wb') as output:
|
||||
@@ -196,28 +306,46 @@ def download(url, save_path, chunk_size=4096):
|
||||
output.write(chunk)
|
||||
progress_bar.update(len(chunk))
|
||||
|
||||
# return the full path of saved file
|
||||
return output_file
|
||||
|
||||
|
||||
def pip_install(package):
|
||||
def pip_install(file):
|
||||
""" Install python package via python pip module
|
||||
|
||||
pip.main() is not available after pip 9.0.1, thus
|
||||
pip module is not used in this case.
|
||||
"""
|
||||
return subprocess.run(['python', '-m', 'pip', 'install', '-U', package]).returncode
|
||||
return subprocess.run([sys.executable, '-m', 'pip', 'install', '-U', '-r', file]).returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
args = process_arguments()
|
||||
print('Video2x Setup Script')
|
||||
print('Version: {}'.format(VERSION))
|
||||
setup = Video2xSetup(args.driver)
|
||||
print('Video2X Setup Script')
|
||||
print(f'Version: {VERSION}')
|
||||
|
||||
# do not install pip modules if script
|
||||
# is packaged in exe format
|
||||
download_python_modules = True
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
print('\nScript is packaged as exe, skipping pip module download')
|
||||
download_python_modules = False
|
||||
|
||||
setup = Video2xSetup(args.driver, download_python_modules)
|
||||
setup.run()
|
||||
print('\n Script finished successfully')
|
||||
print('\nScript finished successfully')
|
||||
except Exception:
|
||||
|
||||
traceback.print_exc()
|
||||
print('An error has occurred')
|
||||
print('Video2X Automatic Setup has failed')
|
||||
|
||||
# in case of a failure, try cleaning up temp files
|
||||
try:
|
||||
setup._cleanup()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print('An error occurred while trying to cleanup files')
|
||||
|
||||
exit(1)
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
Name: Waifu2x Caffe Driver
|
||||
Author: K4YT3X
|
||||
Date Created: Feb 24, 2018
|
||||
Last Modified: March 30, 2019
|
||||
Last Modified: August 3, 2019
|
||||
|
||||
Description: This class is a high-level wrapper
|
||||
for waifu2x-caffe.
|
||||
"""
|
||||
from avalon_framework import Avalon
|
||||
|
||||
# built-in imports
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
|
||||
|
||||
class Waifu2xCaffe:
|
||||
"""This class communicates with waifu2x cui engine
|
||||
@@ -23,30 +27,31 @@ class Waifu2xCaffe:
|
||||
the upscale function.
|
||||
"""
|
||||
|
||||
def __init__(self, waifu2x_settings, process, model_dir):
|
||||
def __init__(self, waifu2x_settings, process, model_dir, bit_depth):
|
||||
self.waifu2x_settings = waifu2x_settings
|
||||
self.waifu2x_settings['process'] = process
|
||||
self.waifu2x_settings['model_dir'] = model_dir
|
||||
self.waifu2x_settings['output_depth'] = bit_depth
|
||||
|
||||
# arguments passed through command line overwrites config file values
|
||||
self.process = process
|
||||
self.model_dir = model_dir
|
||||
self.print_lock = threading.Lock()
|
||||
|
||||
def upscale(self, input_folder, output_folder, scale_ratio, scale_width, scale_height, image_format, upscaler_exceptions):
|
||||
def upscale(self, input_directory, output_directory, scale_ratio, scale_width, scale_height, image_format, upscaler_exceptions):
|
||||
"""This is the core function for WAIFU2X class
|
||||
|
||||
Arguments:
|
||||
input_folder {string} -- source folder path
|
||||
output_folder {string} -- output folder path
|
||||
input_directory {string} -- source directory path
|
||||
output_directory {string} -- output directory path
|
||||
width {int} -- output video width
|
||||
height {int} -- output video height
|
||||
"""
|
||||
|
||||
try:
|
||||
# overwrite config file settings
|
||||
self.waifu2x_settings['input_path'] = input_folder
|
||||
self.waifu2x_settings['output_path'] = output_folder
|
||||
self.waifu2x_settings['input_path'] = input_directory
|
||||
self.waifu2x_settings['output_path'] = output_directory
|
||||
|
||||
if scale_ratio:
|
||||
self.waifu2x_settings['scale_ratio'] = scale_ratio
|
||||
@@ -58,13 +63,13 @@ class Waifu2xCaffe:
|
||||
|
||||
# print thread start message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info('[upscaler] Thread {} started'.format(threading.current_thread().name))
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
|
||||
self.print_lock.release()
|
||||
|
||||
# list to be executed
|
||||
execute = []
|
||||
# initialize the list with waifu2x binary path as the first element
|
||||
execute = [str(self.waifu2x_settings['waifu2x_caffe_path'])]
|
||||
|
||||
execute.append(self.waifu2x_settings['waifu2x_caffe_path'])
|
||||
for key in self.waifu2x_settings.keys():
|
||||
|
||||
value = self.waifu2x_settings[key]
|
||||
@@ -74,17 +79,17 @@ class Waifu2xCaffe:
|
||||
continue
|
||||
else:
|
||||
if len(key) == 1:
|
||||
execute.append('-{}'.format(key))
|
||||
execute.append(f'-{key}')
|
||||
else:
|
||||
execute.append('--{}'.format(key))
|
||||
execute.append(f'--{key}')
|
||||
execute.append(str(value))
|
||||
|
||||
Avalon.debug_info('Executing: {}'.format(execute))
|
||||
Avalon.debug_info(f'Executing: {execute}')
|
||||
completed_command = subprocess.run(execute, check=True)
|
||||
|
||||
# print thread exiting message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info('[upscaler] Thread {} exiting'.format(threading.current_thread().name))
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
|
||||
self.print_lock.release()
|
||||
|
||||
# return command execution return code
|
||||
|
||||
@@ -4,15 +4,20 @@
|
||||
Name: Waifu2x Converter CPP Driver
|
||||
Author: K4YT3X
|
||||
Date Created: February 8, 2019
|
||||
Last Modified: March 30, 2019
|
||||
Last Modified: August 3, 2019
|
||||
|
||||
Description: This class is a high-level wrapper
|
||||
for waifu2x-converter-cpp.
|
||||
"""
|
||||
from avalon_framework import Avalon
|
||||
|
||||
# built-in imports
|
||||
import pathlib
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
|
||||
|
||||
class Waifu2xConverter:
|
||||
"""This class communicates with waifu2x cui engine
|
||||
@@ -28,30 +33,21 @@ class Waifu2xConverter:
|
||||
self.waifu2x_settings['model_dir'] = model_dir
|
||||
self.print_lock = threading.Lock()
|
||||
|
||||
def upscale(self, input_folder, output_folder, scale_ratio, jobs, image_format, upscaler_exceptions):
|
||||
def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions):
|
||||
""" Waifu2x Converter Driver Upscaler
|
||||
This method executes the upscaling of extracted frames.
|
||||
|
||||
Arguments:
|
||||
input_folder {string} -- source folder path
|
||||
output_folder {string} -- output folder path
|
||||
input_directory {string} -- source directory path
|
||||
output_directory {string} -- output directory path
|
||||
scale_ratio {int} -- frames' scale ratio
|
||||
threads {int} -- number of threads
|
||||
"""
|
||||
|
||||
try:
|
||||
# overwrite config file settings
|
||||
self.waifu2x_settings['input'] = input_folder
|
||||
self.waifu2x_settings['output'] = output_folder
|
||||
|
||||
# temporary fix for https://github.com/DeadSix27/waifu2x-converter-cpp/issues/109
|
||||
"""
|
||||
self.waifu2x_settings['i'] = input_folder
|
||||
self.waifu2x_settings['o'] = output_folder
|
||||
self.waifu2x_settings['input'] = None
|
||||
self.waifu2x_settings['output'] = None
|
||||
"""
|
||||
|
||||
self.waifu2x_settings['input'] = input_directory
|
||||
self.waifu2x_settings['output'] = output_directory
|
||||
self.waifu2x_settings['scale-ratio'] = scale_ratio
|
||||
self.waifu2x_settings['jobs'] = jobs
|
||||
self.waifu2x_settings['output-format'] = image_format
|
||||
@@ -59,15 +55,16 @@ class Waifu2xConverter:
|
||||
# models_rgb must be specified manually for waifu2x-converter-cpp
|
||||
# if it's not specified in the arguments, create automatically
|
||||
if self.waifu2x_settings['model-dir'] is None:
|
||||
self.waifu2x_settings['model-dir'] = '{}\\models_rgb'.format(self.waifu2x_settings['waifu2x_converter_path'])
|
||||
self.waifu2x_settings['model-dir'] = pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'models_rgb'
|
||||
|
||||
# print thread start message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info('[upscaler] Thread {} started'.format(threading.current_thread().name))
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
|
||||
self.print_lock.release()
|
||||
|
||||
# list to be executed
|
||||
execute = []
|
||||
# initialize the list with waifu2x binary path as the first element
|
||||
execute = [str(pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'waifu2x-converter-cpp.exe')]
|
||||
|
||||
for key in self.waifu2x_settings.keys():
|
||||
|
||||
@@ -75,16 +72,16 @@ class Waifu2xConverter:
|
||||
|
||||
# the key doesn't need to be passed in this case
|
||||
if key == 'waifu2x_converter_path':
|
||||
execute.append('{}\\waifu2x-converter-cpp.exe'.format(str(value)))
|
||||
continue
|
||||
|
||||
# null or None means that leave this option out (keep default)
|
||||
elif value is None or value is False:
|
||||
continue
|
||||
else:
|
||||
if len(key) == 1:
|
||||
execute.append('-{}'.format(key))
|
||||
execute.append(f'-{key}')
|
||||
else:
|
||||
execute.append('--{}'.format(key))
|
||||
execute.append(f'--{key}')
|
||||
|
||||
# true means key is an option
|
||||
if value is True:
|
||||
@@ -92,7 +89,7 @@ class Waifu2xConverter:
|
||||
|
||||
execute.append(str(value))
|
||||
|
||||
Avalon.debug_info('Executing: {}'.format(execute))
|
||||
Avalon.debug_info(f'Executing: {execute}')
|
||||
return subprocess.run(execute, check=True).returncode
|
||||
|
||||
except Exception as e:
|
||||
|
||||
93
bin/waifu2x_ncnn_vulkan.py
Normal file
93
bin/waifu2x_ncnn_vulkan.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Name: Waifu2x NCNN Vulkan Driver
|
||||
Author: SAT3LL
|
||||
Date Created: June 26, 2019
|
||||
Last Modified: August 3, 2019
|
||||
|
||||
Dev: K4YT3X
|
||||
|
||||
Description: This class is a high-level wrapper
|
||||
for waifu2x_ncnn_vulkan.
|
||||
"""
|
||||
|
||||
# built-in imports
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
# third-party imports
|
||||
from avalon_framework import Avalon
|
||||
|
||||
|
||||
class Waifu2xNcnnVulkan:
|
||||
"""This class communicates with waifu2x ncnn vulkan engine
|
||||
|
||||
An object will be created for this class, containing information
|
||||
about the binary address and the processing method. When being called
|
||||
by the main program, other detailed information will be passed to
|
||||
the upscale function.
|
||||
"""
|
||||
|
||||
def __init__(self, waifu2x_settings):
|
||||
self.waifu2x_settings = waifu2x_settings
|
||||
|
||||
# arguments passed through command line overwrites config file values
|
||||
|
||||
# waifu2x_ncnn_vulkan can't find its own model directory if its not in the current dir
|
||||
# so change to it
|
||||
os.chdir(os.path.join(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'], '..'))
|
||||
|
||||
self.print_lock = threading.Lock()
|
||||
|
||||
def upscale(self, input_directory, output_directory, scale_ratio, upscaler_exceptions):
|
||||
"""This is the core function for WAIFU2X class
|
||||
|
||||
Arguments:
|
||||
input_directory {string} -- source directory path
|
||||
output_directory {string} -- output directory path
|
||||
ratio {int} -- output video ratio
|
||||
"""
|
||||
|
||||
try:
|
||||
# overwrite config file settings
|
||||
self.waifu2x_settings['i'] = input_directory
|
||||
self.waifu2x_settings['o'] = output_directory
|
||||
self.waifu2x_settings['s'] = scale_ratio
|
||||
|
||||
# print thread start message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} started')
|
||||
self.print_lock.release()
|
||||
|
||||
# list to be executed
|
||||
# initialize the list with waifu2x binary path as the first element
|
||||
execute = [str(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'])]
|
||||
|
||||
for key in self.waifu2x_settings.keys():
|
||||
|
||||
value = self.waifu2x_settings[key]
|
||||
|
||||
# is executable key or null or None means that leave this option out (keep default)
|
||||
if key == 'waifu2x_ncnn_vulkan_path' or value is None or value is False:
|
||||
continue
|
||||
else:
|
||||
if len(key) == 1:
|
||||
execute.append(f'-{key}')
|
||||
else:
|
||||
execute.append(f'--{key}')
|
||||
execute.append(str(value))
|
||||
|
||||
Avalon.debug_info(f'Executing: {execute}')
|
||||
completed_command = subprocess.run(execute, check=True)
|
||||
|
||||
# print thread exiting message
|
||||
self.print_lock.acquire()
|
||||
Avalon.debug_info(f'[upscaler] Thread {threading.current_thread().name} exiting')
|
||||
self.print_lock.release()
|
||||
|
||||
# return command execution return code
|
||||
return completed_command.returncode
|
||||
except Exception as e:
|
||||
upscaler_exceptions.append(e)
|
||||
Reference in New Issue
Block a user