Merge remote-tracking branch 'upstream/master'

This commit is contained in:
BrianPetkovsek 2019-06-12 18:06:08 -04:00
commit 585164a508
11 changed files with 601 additions and 314 deletions

31
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: Bug report
about: Use this template if you believe you've encountered a bug.
title: ''
labels: bug
assignees: K4YT3X
---
## Environment Information
|Module|Version|
|-|-|
|`video2x`||
|`ffmpeg`||
|`waifu2x-caffe`||
|`waifu2x-converter-cpp`||
## Symptom
Briefly describe what's going on.
## Screenshots or Error Messages
Please paste screenshots or error messages here.
Include as much details as you can, as this greatly helps debugging the problem.
Please paste your error message in the code block down below:
```
```

View File

@ -0,0 +1,16 @@
---
name: Feature request
about: Request a new feature to be added
title: ''
labels: enhancement
assignees: K4YT3X
---
## Description
Briefly describe the feature you want to be added into `video2x`.
## Sources
Paste links to descriptions of related documentations, websites and etc. here.

119
README.md
View File

@ -2,41 +2,40 @@
### Official Discussion Group (Telegram): https://t.me/video2x ### Official Discussion Group (Telegram): https://t.me/video2x
## Download Builds (beta)
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` and `waifu2x-converter-cpp`. 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 `FFmpeg`, `waifu2x-caffe` or `waifu2x-converter-cpp` automatically, simply launch `video2x_setup.exe`.
## Prerequisites ## Prerequisites
Component names that are **bolded** are mandatory. Component names that are **bolded** can be automatically downloaded and configured with the `video2x_setup.py` script.
Component names that are *italicized* can be automatically downloaded and configured with the `video2x_setup.py` script.
1. Operating System: Windows 1. Operating System: Windows
1. AMD GPU / Nvidia GPU 2. AMD GPU / Nvidia GPU
1. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN 3. AMD GPU driver / Nvidia GPU driver / Nvidia CUDNN
1. [***FFMPEG***](https://ffmpeg.zeranoe.com/builds/) 4. [**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) 5. [**waifu2x-caffe**](https://github.com/lltcggie/waifu2x-caffe/releases) / [**waifu2x-converter-cpp**](https://github.com/DeadSix27/waifu2x-converter-cpp/releases)
## Recent Changes ## Recent Changes
### 2.6.3 (March 24, 2019) ### 2.7.1 (April 18, 2019)
- Added image cleaner by @BrianPetkovsek which removes upscaled frames. - Fixed video2x custom temp folder bug found by @cr08 .
- 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.
### 2.6.2 (March 19, 2019) ### 2.7.0 (March 30, 2019)
- Removed `--model_dir` verification due to the rapidly evolving number of models added. - Added support for different extracted image formats.
- Fixed model specifying bug. Users should now specify model using `--model_dir [path to folder containing model JSON files]`. - Redesigned FFmpeg wrapper, FFmpeg settings are now customizable in the `video2x.json` config file.
- Enhanced command execution method. - Other minor enhancements and adjustments (e.g. argument -> method variable)
### 2.6.1 (March 12, 2019) ### Setup Script 1.2.0 (March 26, 2019)
- Added `-b, --batch` option which selects applies all default values for questions automatically. - `video2x_setup.py` script can now automatically download and configure `waifu2x-converter-cpp`.
- **This new version will now require `avalon_framework>=1.6.3`**. Please run `pip install -U avalon_framework` to update the existing framework. - replaced old progress indicator with progress bar.
### 2.6.0 (March 9, 2019)
- Complete redesign of configuration file format. The configuration file is now much more flexible and easy to look at.
- Various modifications done to the rest of the program to adapt to the changes made in the configuration file. This eliminated some problems existed in the previous version.
## Description ## Description
@ -60,17 +59,21 @@ Clip is from trailer of animated movie "千と千尋の神隠し". Copyright bel
## Documentations ## 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. This tutorial currently only includes instructions for Nvidia GPUs, since AMD GPUs (OpenCL) requires installation of `waifu2x-converter-cpp` which cannot be installed automatically with Python at the moment due to its 7z compression format. 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.
--- ---
@ -80,7 +83,7 @@ Go to the [Waifu2X Drivers](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drive
- **Python 3** - **Python 3**
Download: https://www.python.org/downloads/windows/ Download: https://www.python.org/downloads/windows/
- **FFMPEG Windows Build** - **FFmpeg Windows Build**
Download: https://ffmpeg.org/download.html Download: https://ffmpeg.org/download.html
- **waifu2x-caffe** (for Nvidia CUDA/CUDNN) - **waifu2x-caffe** (for Nvidia CUDA/CUDNN)
Download: https://github.com/lltcggie/waifu2x-caffe/releases Download: https://github.com/lltcggie/waifu2x-caffe/releases
@ -91,27 +94,25 @@ Download: https://github.com/DeadSix27/waifu2x-converter-cpp/releases
First, clone the video2x repository. First, clone the video2x repository.
```bash ```shell
$ git clone https://github.com/K4YT3X/video2x.git git clone https://github.com/k4yt3x/video2x.git
$ cd video2x/bin cd video2x/bin
``` ```
Then you may run the `video2x_setup.py` script to install and configure the depencies automatically. This script is designed and tested on Windows 10. 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 `ffmpeg`, `waifu2x-caffe` to `%LOCALAPPDATA%\\video2x` and all python libraries. 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.
**`waifu2x-converter-cpp` cannot be installed automatically with this script.** Please follow the [`waifu2x-converter-cpp` installation instructions](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drivers#waifu2x-converter-cpp) to install it. ```shell
python video2x_setup.py
```bash
$ python video2x_setup.py
``` ```
Alternatively, you can also install the dependencies manually. Please refer to the prerequisites section to see what's needed. Alternatively, you can also install the dependencies manually. Please refer to the prerequisites section to see what's needed.
Then you'll need to install python dependencies before start using video2x. Install simply by executing the following command. Then you'll need to install python dependencies before start using video2x. Install simply by executing the following command.
```bash ```shell
$ pip install -r requirements.txt pip install -r requirements.txt
``` ```
**Note that all command line arguments/options overwrite configuration file settings.** **Note that all command line arguments/options overwrite configuration file settings.**
@ -131,34 +132,32 @@ Clip is from anime "さくら荘のペットな彼女". Copyright belongs to "
Enlarge the video to 1920x1080 using CUDA. You may also use the `-r/--ratio` option. Enlarge the video to 1920x1080 using CUDA. You may also use the `-r/--ratio` option.
```bash ```shell
$ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu --width=1920 --height=1080 python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu --width=1920 --height=1080
``` ```
### Nvidia CNDNN ### Nvidia CNDNN
Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option. Enlarge the video to 1920x1080 using CUDNN. You may also use the `-r/--ratio` option.
```bash ```shell
$ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cudnn --width=1920 --height=1080 python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cudnn --width=1920 --height=1080
``` ```
### AMD or Nvidia (waifu2x-converter-cpp OpenCL) ### AMD or Nvidia (waifu2x-converter-cpp OpenCL)
Enlarge the video by 2 times using OpenCL. Note that `waifu2x-converter-cpp` doesn't support width and height. You'll also have to explicitly specify that the driver to be used is `waifu2x_converter`. Enlarge the video by 2 times using OpenCL. Note that `waifu2x-converter-cpp` doesn't support width and height. You'll also have to explicitly specify that the driver to be used is `waifu2x_converter`.
```bash ```shell
$ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_converter python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m gpu -r 2 -d waifu2x_converter
``` ```
Corresponding sample configuration is shown below. Note that the `waifu2x_path` is different from the configuration for `waifu2x-caffe`. Instead of the binary path, folder containing extracted `waifu2x-converter-cpp.exe` should be specified.
### CPU ### CPU
Enlarge the video to 1920x1080 using the CPU. You may also use the `-r/--ratio` option. This is potentially much slower than using a GPU. The configuration file for this method is similar to the previous methods. Enlarge the video to 1920x1080 using the CPU. You may also use the `-r/--ratio` option. This is potentially much slower than using a GPU. The configuration file for this method is similar to the previous methods.
```bash ```shell
$ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080 python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920 --height=1080
``` ```
--- ---
@ -187,7 +186,7 @@ $ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920
Waifu2x driver (default: waifu2x_caffe) Waifu2x driver (default: waifu2x_caffe)
### -y MODEL_DIR, --model_dir MODEL_DIR ### -y MODEL_DIR, --model_dir MODEL_DIR
Folder containing model JSON files (default: None) Folder containing model JSON files
### -t THREADS, --threads THREADS ### -t THREADS, --threads THREADS
Number of threads to use for upscaling (default: 5) Number of threads to use for upscaling (default: 5)
@ -196,18 +195,18 @@ $ python video2x.py -i sample_input.mp4 -o sample_output.mp4 -m cpu --width=1920
Video2X config file location (default: video2x\bin\video2x.json) Video2X config file location (default: video2x\bin\video2x.json)
### -b, --batch ### -b, --batch
Enable batch mode (select all default values to questions) (default: False) Enable batch mode (select all default values to questions)
## Scaling Options ## Scaling Options
### --width WIDTH ### --width WIDTH
Output video width (default: False) Output video width
### --height HEIGHT ### --height HEIGHT
Output video height (default: False) Output video height
### -r RATIO, --ratio RATIO ### -r RATIO, --ratio RATIO
Scaling ratio (default: False) Scaling ratio
--- ---
@ -224,10 +223,16 @@ https://www.gnu.org/licenses/gpl-3.0.txt
This project relies on the following software and projects. 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-caffe](https://github.com/lltcggie/waifu2x-caffe)
- [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp) - [waifu2x-converter-cpp](https://github.com/DeadSix27/waifu2x-converter-cpp)
## Special Thanks
Appreciations given to the following contributors:
- @BrianPetkovsek
## Related Resources ## 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.

View File

@ -4,7 +4,7 @@
Name: FFMPEG Class Name: FFMPEG Class
Author: K4YT3X Author: K4YT3X
Date Created: Feb 24, 2018 Date Created: Feb 24, 2018
Last Modified: March 19, 2019 Last Modified: June 5, 2019
Description: This class handles all FFMPEG related Description: This class handles all FFMPEG related
operations. operations.
@ -22,22 +22,18 @@ class Ffmpeg:
and inserting audio tracks to videos. and inserting audio tracks to videos.
""" """
def __init__(self, ffmpeg_settings): def __init__(self, ffmpeg_settings, image_format):
self.ffmpeg_settings = ffmpeg_settings self.ffmpeg_settings = ffmpeg_settings
self._parse_settings()
def _parse_settings(self):
""" Parse ffmpeg settings
"""
self.ffmpeg_path = self.ffmpeg_settings['ffmpeg_path'] self.ffmpeg_path = self.ffmpeg_settings['ffmpeg_path']
# add a forward slash to directory if not present # add a forward slash to directory if not present
# otherwise there will be a format error # otherwise there will be a format error
if self.ffmpeg_path[-1] != '/' and self.ffmpeg_path[-1] != '\\': if self.ffmpeg_path[-1] != '/' and self.ffmpeg_path[-1] != '\\':
self.ffmpeg_path = '{}\\'.format(self.ffmpeg_path) self.ffmpeg_path = f'{self.ffmpeg_path}\\'
self.ffmpeg_binary = '{}ffmpeg.exe'.format(self.ffmpeg_path) self.ffmpeg_binary = f'{self.ffmpeg_path}ffmpeg.exe'
self.ffmpeg_hwaccel = self.ffmpeg_settings['ffmpeg_hwaccel'] self.ffmpeg_probe_binary = f'{self.ffmpeg_path}ffprobe.exe'
self.extra_arguments = self.ffmpeg_settings['extra_arguments'] self.image_format = image_format
def get_video_info(self, input_video): def get_video_info(self, input_video):
""" Gets input video information """ Gets input video information
@ -51,8 +47,11 @@ class Ffmpeg:
Returns: Returns:
dictionary -- JSON text of input video information dictionary -- JSON text of input video information
""" """
# this execution command needs to be hard-coded
# since video2x only strictly recignizes this one format
execute = [ execute = [
'{}ffprobe.exe'.format(self.ffmpeg_path), self.ffmpeg_probe_binary,
'-v', '-v',
'quiet', 'quiet',
'-print_format', '-print_format',
@ -60,10 +59,10 @@ class Ffmpeg:
'-show_format', '-show_format',
'-show_streams', '-show_streams',
'-i', '-i',
'{}'.format(input_video) input_video
] ]
Avalon.debug_info('Executing: {}'.format(' '.join(execute))) Avalon.debug_info(f'Executing: {" ".join(execute)}')
json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout
return json.loads(json_str.decode('utf-8')) return json.loads(json_str.decode('utf-8'))
@ -75,19 +74,26 @@ class Ffmpeg:
Arguments: Arguments:
input_video {string} -- input video path input_video {string} -- input video path
extracted_frames {string} -- video output folder extracted_frames {string} -- video output directory
""" """
execute = [ execute = [
self.ffmpeg_binary, self.ffmpeg_binary
'-i',
'{}'.format(input_video),
'{}\\extracted_%0d.png'.format(extracted_frames),
'-y'
] ]
execute += self.extra_arguments
Avalon.debug_info('Executing: {}'.format(' '.join(execute))) execute.extend([
subprocess.run(execute, shell=True, check=True) '-i',
input_video
])
execute.extend(self._read_configuration(phase='video_to_frames', section='output_options'))
execute.extend([
f'{extracted_frames}\\extracted_%0d.{self.image_format}'
])
execute.extend(self._read_configuration(phase='video_to_frames'))
self._execute(execute)
def convert_video(self, framerate, resolution, upscaled_frames): def convert_video(self, framerate, resolution, upscaled_frames):
"""Converts images into videos """Converts images into videos
@ -98,31 +104,37 @@ class Ffmpeg:
Arguments: Arguments:
framerate {float} -- target video framerate framerate {float} -- target video framerate
resolution {string} -- target video resolution resolution {string} -- target video resolution
upscaled_frames {string} -- source images folder upscaled_frames {string} -- source images directory
""" """
execute = [ execute = [
self.ffmpeg_binary, self.ffmpeg_binary,
'-r', '-r',
str(framerate), str(framerate),
'-f',
'image2',
'-s', '-s',
resolution, resolution
'-i',
'{}\\extracted_%d.png'.format(upscaled_frames),
'-vcodec',
'libx264',
'-crf',
'25',
'-pix_fmt',
'yuv420p',
'{}\\no_audio.mp4'.format(upscaled_frames),
'-y'
] ]
execute += self.extra_arguments
Avalon.debug_info('Executing: {}'.format(' '.join(execute))) # read FFmpeg input options
subprocess.run(execute, shell=True, check=True) execute.extend(self._read_configuration(phase='frames_to_video', section='input_options'))
# append input frames path into command
execute.extend([
'-i',
f'{upscaled_frames}\\extracted_%d.{self.image_format}'
])
# read FFmpeg output options
execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
# read other options
execute.extend(self._read_configuration(phase='frames_to_video'))
# specify output file location
execute.extend([
f'{upscaled_frames}\\no_audio.mp4'
])
self._execute(execute)
def migrate_audio_tracks_subtitles(self, input_video, output_video, upscaled_frames): def migrate_audio_tracks_subtitles(self, input_video, output_video, upscaled_frames):
""" Migrates audio tracks and subtitles from input video to output video """ Migrates audio tracks and subtitles from input video to output video
@ -135,21 +147,71 @@ class Ffmpeg:
execute = [ execute = [
self.ffmpeg_binary, self.ffmpeg_binary,
'-i', '-i',
'{}\\no_audio.mp4'.format(upscaled_frames), f'{upscaled_frames}\\no_audio.mp4',
'-i', '-i',
'{}'.format(input_video), input_video
'-map',
'0:v:0?',
'-map',
'1?',
'-c',
'copy',
'-map',
'-1:v?',
'{}'.format(output_video),
'-y'
] ]
execute += self.extra_arguments
Avalon.debug_info('Executing: {}'.format(' '.join(execute))) execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
subprocess.run(execute, shell=True, check=True)
execute.extend([
output_video
])
execute.extend(self._read_configuration(phase='migrating_tracks'))
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()
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 or isinstance(value, list) or isinstance(value, dict):
continue
else:
configuration.append(key)
# true means key is an option
if value is True:
continue
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
"""
Avalon.debug_info(f'Executing: {execute}')
return subprocess.run(execute, shell=True, check=True).returncode

View File

@ -5,7 +5,7 @@ Name: Video2X Image Cleaner
Author: BrianPetkovsek Author: BrianPetkovsek
Author: K4YT3X Author: K4YT3X
Date Created: March 24, 2019 Date Created: March 24, 2019
Last Modified: March 24, 2019 Last Modified: April 28, 2019
Description: This class is to remove the extracted frames Description: This class is to remove the extracted frames
that have already been upscaled. that have already been upscaled.
@ -27,11 +27,11 @@ class ImageCleaner(threading.Thread):
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) threading.Thread.__init__(self)
self.input_folder = input_folder self.input_directory = input_directory
self.output_folder = output_folder self.output_directory = output_directory
self.num_threads = num_threads self.threads = threads
self.running = False self.running = False
def run(self): def run(self):
@ -53,23 +53,23 @@ class ImageCleaner(threading.Thread):
""" remove frames that have already been upscaled """ remove frames that have already been upscaled
This method compares the files in the extracted frames 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. the frames that has already been upscaled.
""" """
# list all images in the extracted frames # 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 os.listdir(self.output_directory) if os.path.isfile(os.path.join(self.output_directory, f))]
# compare and remove frames downscaled images that finished being upscaled # compare and remove frames downscaled images that finished being upscaled
# within each thread's extracted frames folder # within each thread's extracted frames directory
for i in range(self.num_threads): for i in range(self.threads):
dir_path = os.path.join(self.input_folder, str(i)) dir_path = os.path.join(self.input_directory, str(i))
# for each file within all the folders # for each file within all the directories
for f in os.listdir(dir_path): for f in os.listdir(dir_path):
file_path = os.path.join(dir_path, f) file_path = os.path.join(dir_path, f)
# 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 # has already been processed, thus not needed anymore
if os.path.isfile(file_path) and f in output_frames: if os.path.isfile(file_path) and f in output_frames:
os.remove(file_path) os.remove(file_path)

View File

@ -4,7 +4,7 @@
Name: Video2X Upscaler Name: Video2X Upscaler
Author: K4YT3X Author: K4YT3X
Date Created: December 10, 2018 Date Created: December 10, 2018
Last Modified: March 24, 2019 Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3), Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt available at: https://www.gnu.org/licenses/gpl-3.0.txt
@ -13,10 +13,10 @@ Licensed under the GNU General Public License Version 3 (GNU GPL v3),
""" """
from avalon_framework import Avalon from avalon_framework import Avalon
from image_cleaner import ImageCleaner
from exceptions import * from exceptions import *
from ffmpeg import Ffmpeg from ffmpeg import Ffmpeg
from fractions import Fraction from fractions import Fraction
from image_cleaner import ImageCleaner
from tqdm import tqdm from tqdm import tqdm
from waifu2x_caffe import Waifu2xCaffe from waifu2x_caffe import Waifu2xCaffe
from waifu2x_converter import Waifu2xConverter from waifu2x_converter import Waifu2xConverter
@ -30,52 +30,49 @@ import time
class Upscaler: class Upscaler:
""" An instance of this class is a upscaler that will """ 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: Raises:
Exception -- all exceptions Exception -- all exceptions
ArgumentError -- if argument is not valid ArgumentError -- if argument is not valid
""" """
def __init__(self, input_video, output_video, method, waifu2x_settings, ffmpeg_settings, waifu2x_driver='waifu2x_caffe', scale_width=False, scale_height=False, scale_ratio=False, model_dir=None, threads=5, video2x_cache_folder='{}\\video2x'.format(tempfile.gettempdir()), preserve_frames=False): def __init__(self, input_video, output_video, method, waifu2x_settings, ffmpeg_settings):
# mandatory arguments # mandatory arguments
self.input_video = input_video self.input_video = input_video
self.output_video = output_video self.output_video = output_video
self.method = method self.method = method
self.waifu2x_settings = waifu2x_settings self.waifu2x_settings = waifu2x_settings
self.ffmpeg_settings = ffmpeg_settings self.ffmpeg_settings = ffmpeg_settings
self.waifu2x_driver = waifu2x_driver
# check sanity of waifu2x_driver option
if waifu2x_driver != 'waifu2x_caffe' and waifu2x_driver != 'waifu2x_converter':
raise Exception('Unrecognized waifu2x driver: {}'.format(waifu2x_driver))
# optional arguments # optional arguments
self.scale_width = scale_width self.waifu2x_driver = 'waifu2x_caffe'
self.scale_height = scale_height self.scale_width = None
self.scale_ratio = scale_ratio self.scale_height = None
self.model_dir = model_dir self.scale_ratio = None
self.threads = threads self.model_dir = None
self.threads = 5
self.video2x_cache_directory = f'{tempfile.gettempdir()}\\video2x'
self.image_format = 'png'
self.preserve_frames = False
# create temporary folder/directories def create_temp_directories(self):
self.video2x_cache_folder = video2x_cache_folder """create temporary directory
self.extracted_frames = tempfile.mkdtemp(dir=self.video2x_cache_folder) """
Avalon.debug_info('Extracted frames are being saved to: {}'.format(self.extracted_frames)) self.extracted_frames = tempfile.mkdtemp(dir=self.video2x_cache_directory)
Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}')
self.upscaled_frames = tempfile.mkdtemp(dir=self.video2x_cache_directory)
Avalon.debug_info(f'Upscaled frames are being saved to: {self.upscaled_frames}')
self.upscaled_frames = tempfile.mkdtemp(dir=self.video2x_cache_folder) def cleanup_temp_directories(self):
Avalon.debug_info('Upscaled frames are being saved to: {}'.format(self.upscaled_frames)) """delete temp directories when done
"""
self.preserve_frames = preserve_frames
def cleanup(self):
# delete temp directories when done
# avalon framework cannot be used if python is shutting down
# therefore, plain print is used
if not self.preserve_frames: if not self.preserve_frames:
for directory in [self.extracted_frames, self.upscaled_frames]: for directory in [self.extracted_frames, self.upscaled_frames]:
try: try:
print('Cleaning up cache directory: {}'.format()) # 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) shutil.rmtree(directory)
except (OSError, FileNotFoundError): except (OSError, FileNotFoundError):
pass pass
@ -92,18 +89,18 @@ class Upscaler:
elif not self.method: elif not self.method:
raise ArgumentError('You need to specify the enlarging processing unit') 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
This method prints a progress bar by keeping track This method prints a progress bar by keeping track
of the amount of frames in the input directory/folder of the amount of frames in the input directory
and the output directory/folder. This is originally and the output directory. This is originally
suggested by @ArmandBernard. suggested by @ArmandBernard.
""" """
# get number of extracted frames # get number of extracted frames
total_frames = 0 total_frames = 0
for folder in extracted_frames_folders: for directory in extracted_frames_directories:
total_frames += len([f for f in os.listdir(folder) if f[-4:] == '.png']) total_frames += len([f for f in os.listdir(directory) if f[-4:] == f'.{self.image_format}'])
with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar: with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
@ -114,7 +111,7 @@ class Upscaler:
while not self.progress_bar_exit_signal: while not self.progress_bar_exit_signal:
try: try:
total_frames_upscaled = len([f for f in os.listdir(self.upscaled_frames) if f[-4:] == '.png']) total_frames_upscaled = len([f for f in os.listdir(self.upscaled_frames) if f[-4:] == f'.{self.image_format}'])
delta = total_frames_upscaled - previous_cycle_frames delta = total_frames_upscaled - previous_cycle_frames
previous_cycle_frames = total_frames_upscaled previous_cycle_frames = total_frames_upscaled
@ -142,6 +139,10 @@ class Upscaler:
# progress bar thread exit signal # progress bar thread exit signal
self.progress_bar_exit_signal = False self.progress_bar_exit_signal = False
# create a container for exceptions in threads
# if this thread is not empty, then an exception has occured
self.upscaler_exceptions = []
# it's easier to do multi-threading with waifu2x_converter # it's easier to do multi-threading with waifu2x_converter
# the number of threads can be passed directly to waifu2x_converter # the number of threads can be passed directly to waifu2x_converter
if self.waifu2x_driver == 'waifu2x_converter': if self.waifu2x_driver == 'waifu2x_converter':
@ -149,10 +150,10 @@ class Upscaler:
progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],)) progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],))
progress_bar.start() progress_bar.start()
w2.upscale(self.extracted_frames, self.upscaled_frames, self.scale_ratio, self.threads) 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))]: 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+)?)\]\.png', '.png', image) renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', image)
shutil.move('{}\\{}'.format(self.upscaled_frames, image), '{}\\{}'.format(self.upscaled_frames, renamed)) shutil.move(f'{self.upscaled_frames}\\{image}', f'{self.upscaled_frames}\\{renamed}')
self.progress_bar_exit_signal = True self.progress_bar_exit_signal = True
progress_bar.join() progress_bar.join()
@ -161,10 +162,6 @@ class Upscaler:
# create a container for all upscaler threads # create a container for all upscaler threads
upscaler_threads = [] upscaler_threads = []
# create a container for exceptions in threads
# if this thread is not empty, then an exception has occured
self.threads_exceptions = []
# list all images in the extracted frames # 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))] 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))]
@ -173,25 +170,25 @@ class Upscaler:
if len(frames) < self.threads: if len(frames) < self.threads:
self.threads = len(frames) self.threads = len(frames)
# create a folder for each thread and append folder # create a directory for each thread and append directory
# name into a list # name into a list
thread_pool = [] thread_pool = []
thread_folders = [] thread_directories = []
for thread_id in range(self.threads): for thread_id in range(self.threads):
thread_folder = '{}\\{}'.format(self.extracted_frames, str(thread_id)) thread_directory = f'{self.extracted_frames}\\{str(thread_id)}'
thread_folders.append(thread_folder) thread_directories.append(thread_directory)
# delete old folders and create new folders # delete old directories and create new directories
if os.path.isdir(thread_folder): if os.path.isdir(thread_directory):
shutil.rmtree(thread_folder) shutil.rmtree(thread_directory)
os.mkdir(thread_folder) os.mkdir(thread_directory)
# append folder path into list # append directory path into list
thread_pool.append((thread_folder, thread_id)) thread_pool.append((thread_directory, thread_id))
# evenly distribute images into each folder # evenly distribute images into each directory
# until there is none left in the folder # until there is none left in the directory
for image in frames: for image in frames:
# move image # move image
shutil.move(image, thread_pool[0][0]) shutil.move(image, thread_pool[0][0])
@ -202,16 +199,16 @@ class Upscaler:
for thread_info in thread_pool: for thread_info in thread_pool:
# create thread # create thread
if self.scale_ratio: if self.scale_ratio:
thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, self.scale_ratio, False, False, self.threads_exceptions)) 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: else:
thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, False, self.scale_width, self.scale_height, self.threads_exceptions)) 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] thread.name = thread_info[1]
# add threads into the pool # add threads into the pool
upscaler_threads.append(thread) upscaler_threads.append(thread)
# start progress bar in a different thread # start progress bar in a different thread
progress_bar = threading.Thread(target=self._progress_bar, args=(thread_folders,)) progress_bar = threading.Thread(target=self._progress_bar, args=(thread_directories,))
progress_bar.start() progress_bar.start()
# create the clearer and start it # create the clearer and start it
@ -233,8 +230,8 @@ class Upscaler:
self.progress_bar_exit_signal = True self.progress_bar_exit_signal = True
if len(self.threads_exceptions) != 0: if len(self.upscaler_exceptions) != 0:
raise(self.threads_exceptions[0]) raise(self.upscaler_exceptions[0])
def run(self): def run(self):
"""Main controller for Video2X """Main controller for Video2X
@ -252,7 +249,7 @@ class Upscaler:
self.output_video = os.path.abspath(self.output_video) self.output_video = os.path.abspath(self.output_video)
# initialize objects for ffmpeg and waifu2x-caffe # initialize objects for ffmpeg and waifu2x-caffe
fm = Ffmpeg(self.ffmpeg_settings) fm = Ffmpeg(self.ffmpeg_settings, self.image_format)
# initialize waifu2x driver # initialize waifu2x driver
if self.waifu2x_driver == 'waifu2x_caffe': if self.waifu2x_driver == 'waifu2x_caffe':
@ -260,7 +257,7 @@ class Upscaler:
elif self.waifu2x_driver == 'waifu2x_converter': elif self.waifu2x_driver == 'waifu2x_converter':
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir) w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
else: else:
raise Exception('Unrecognized waifu2x driver: {}'.format(self.waifu2x_driver)) raise Exception(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
# extract frames from video # extract frames from video
fm.extract_frames(self.input_video, self.extracted_frames) fm.extract_frames(self.input_video, self.extracted_frames)
@ -284,14 +281,14 @@ class Upscaler:
# get average frame rate of video stream # get average frame rate of video stream
framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate'])) framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate']))
Avalon.info('Framerate: {}'.format(framerate)) Avalon.info(f'Framerate: {framerate}')
# width/height will be coded width/height x upscale factor # width/height will be coded width/height x upscale factor
if self.scale_ratio: if self.scale_ratio:
coded_width = video_info['streams'][video_stream_index]['coded_width'] original_width = video_info['streams'][video_stream_index]['width']
coded_height = video_info['streams'][video_stream_index]['coded_height'] original_height = video_info['streams'][video_stream_index]['height']
self.scale_width = self.scale_ratio * coded_width self.scale_width = int(self.scale_ratio * original_width)
self.scale_height = self.scale_ratio * coded_height self.scale_height = int(self.scale_ratio * original_height)
# upscale images one by one using waifu2x # upscale images one by one using waifu2x
Avalon.info('Starting to upscale extracted images') Avalon.info('Starting to upscale extracted images')
@ -302,7 +299,7 @@ class Upscaler:
Avalon.info('Converting extracted frames into video') Avalon.info('Converting extracted frames into video')
# use user defined output size # 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') Avalon.info('Conversion completed')
# migrate audio tracks and subtitles # migrate audio tracks and subtitles

View File

@ -1,6 +1,8 @@
{ {
"waifu2x_caffe": { "waifu2x_caffe": {
"waifu2x_caffe_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe", "waifu2x_caffe_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe",
"input_extention_list": null,
"output_extention": null,
"mode": "noise_scale", "mode": "noise_scale",
"scale_ratio": null, "scale_ratio": null,
"scale_width": null, "scale_width": null,
@ -19,31 +21,61 @@
"crop_w": null, "crop_w": null,
"crop_h": null "crop_h": null
}, },
"waifu2x_converter": { "waifu2x_converter": {
"waifu2x_converter_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-converter-cpp", "waifu2x_converter_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\waifu2x-converter-cpp",
"block_size": null, "output-format": null,
"png-compression": null,
"image-quality": null,
"block-size": null,
"disable-gpu": null, "disable-gpu": null,
"force-OpenCL": null, "force-OpenCL": null,
"processor": null, "processor": null,
"jobs": null, "jobs": null,
"model_dir": null, "model-dir": null,
"scale_ratio": null, "scale-ratio": null,
"noise_level": 3, "noise-level": 3,
"mode": "noise_scale", "mode": "noise-scale",
"quiet": true, "silent": true,
"output": null, "output": null,
"input": null "input": null
}, },
"ffmpeg": {
"ffmpeg":{ "ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-latest-win64-static\\bin",
"ffmpeg_path": "C:\\Users\\K4YT3X\\AppData\\Local\\video2x\\ffmpeg-4.1-win64-static\\bin", "video_to_frames": {
"ffmpeg_hwaccel": "auto", "output_options":{
"extra_arguments": [] "-qscale:v": null
},
"-hwaccel": "auto",
"-y": true
},
"frames_to_video": {
"input_options":{
"-qscale:v": null,
"-qscale:a": null,
"-f": "image2"
},
"output_options":{
"-vcodec": "libx264",
"-crf": 17,
"-b:v": null,
"-pix_fmt": "yuv420p"
},
"-y": true
},
"migrating_tracks": {
"output_options":{
"-map": "0:v:0?",
"-map": "1?",
"-c": "copy",
"-map": "-1:v?",
"-pix_fmt": "yuv420p"
},
"-y": true
}
}, },
"video2x": { "video2x": {
"video2x_cache_folder": false, "video2x_cache_directory": null,
"image_format": "png",
"preserve_frames": false "preserve_frames": false
} }
} }

View File

@ -13,7 +13,7 @@ __ __ _ _ ___ __ __
Name: Video2X Controller Name: Video2X Controller
Author: K4YT3X Author: K4YT3X
Date Created: Feb 24, 2018 Date Created: Feb 24, 2018
Last Modified: March 24, 2019 Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3), Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt available at: https://www.gnu.org/licenses/gpl-3.0.txt
@ -45,12 +45,14 @@ import GPUtil
import json import json
import os import os
import psutil import psutil
import re
import shutil import shutil
import sys
import tempfile import tempfile
import time import time
import traceback import traceback
VERSION = '2.6.3' VERSION = '2.7.1'
# each thread might take up to 2.5 GB during initialization. # each thread might take up to 2.5 GB during initialization.
# (system memory, not to be confused with GPU memory) # (system memory, not to be confused with GPU memory)
@ -76,16 +78,16 @@ def process_arguments():
upscaler_options = parser.add_argument_group('Upscaler Options') upscaler_options = parser.add_argument_group('Upscaler Options')
upscaler_options.add_argument('-m', '--method', help='Upscaling method', action='store', default='gpu', choices=['cpu', 'gpu', 'cudnn'], required=True) 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('-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', default=None) upscaler_options.add_argument('-y', '--model_dir', 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=5) 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', help='Video2X config file location', action='store', default='{}\\video2x.json'.format(os.path.dirname(os.path.abspath(__file__)))) upscaler_options.add_argument('-c', '--config', help='Video2X config file location', action='store', default=f'{os.path.dirname(os.path.abspath(sys.argv[0]))}\\video2x.json')
upscaler_options.add_argument('-b', '--batch', help='Enable batch mode (select all default values to questions)', action='store_true', default=False) upscaler_options.add_argument('-b', '--batch', help='Enable batch mode (select all default values to questions)', action='store_true')
# scaling options # scaling options
scaling_options = parser.add_argument_group('Scaling Options') scaling_options = parser.add_argument_group('Scaling Options')
scaling_options.add_argument('--width', help='Output video width', action='store', type=int, default=False) 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, default=False) 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=int, default=False) scaling_options.add_argument('-r', '--ratio', help='Scaling ratio', action='store', type=float)
# parse arguments # parse arguments
return parser.parse_args() return parser.parse_args()
@ -99,8 +101,8 @@ def print_logo():
print(' \\ / | | | (_| | | __/ | (_) | / /_ / . \\') print(' \\ / | | | (_| | | __/ | (_) | / /_ / . \\')
print(' \\/ |_| \\__,_| \\___| \\___/ |____| /_/ \\_\\') print(' \\/ |_| \\__,_| \\___| \\___/ |____| /_/ \\_\\')
print('\n Video2X Video Enlarger') print('\n Video2X Video Enlarger')
spaces = ((44 - len("Version {}".format(VERSION))) // 2) * " " spaces = ((44 - len(f'Version {VERSION}')) // 2) * ' '
print('{}\n{} Version {}\n{}'.format(Avalon.FM.BD, spaces, VERSION, Avalon.FM.RST)) print(f'{Avalon.FM.BD}\n{spaces} Version {VERSION}\n{Avalon.FM.RST}')
def check_memory(): def check_memory():
@ -139,17 +141,17 @@ def check_memory():
# if user doesn't even have enough memory to run even one thread # if user doesn't even have enough memory to run even one thread
if memory_available < mem_per_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') Avalon.warning('Proceed with caution')
if args.threads > 1: if args.threads > 1:
if Avalon.ask('Reduce number of threads to avoid crashing?', default=True, batch=args.batch): if Avalon.ask('Reduce number of threads to avoid crashing?', default=True, batch=args.batch):
args.threads = 1 args.threads = 1
# if memory available is less than needed, warn the user # if memory available is less than needed, warn the user
elif memory_available < (mem_per_thread * args.threads): 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(f'Each waifu2x-caffe thread will require up to {SYS_MEM_PER_THREAD} 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(f'You demanded {args.threads} threads to be created, but you only have {round(memory_available, 4)} GB {memory_type} memory available')
Avalon.warning('{} GB of {} memory is recommended for {} threads'.format(mem_per_thread * args.threads, memory_type, args.threads)) Avalon.warning(f'{mem_per_thread * args.threads} GB of {memory_type} memory is recommended for {args.threads} 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'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 # ask the user if he / she wants to change to the recommended
# number of threads # number of threads
@ -169,12 +171,46 @@ def read_config(config_file):
return 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 = os.path.dirname(os.path.abspath(sys.argv[0]))
# check waifu2x-caffe path
if not re.match('^[a-z]:', config['waifu2x_caffe']['waifu2x_caffe_path'], re.IGNORECASE):
config['waifu2x_caffe']['waifu2x_caffe_path'] = f'{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'] = f'{current_directory}\\{config["waifu2x_converter"]["waifu2x_converter_path"]}'
# check ffmpeg path
if not re.match('^[a-z]:', config['ffmpeg']['ffmpeg_path'], re.IGNORECASE):
config['ffmpeg']['ffmpeg_path'] = f'{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'] = f'{current_directory}\\{config["video2x"]["video2x_cache_directory"]}'
return config
# /////////////////// Execution /////////////////// # # /////////////////// Execution /////////////////// #
# this is not a library # this is not a library
if __name__ != '__main__': if __name__ != '__main__':
Avalon.error('This file cannot be imported') Avalon.error('This file cannot be imported')
raise ImportError('{} cannot be imported'.format(__file__)) raise ImportError(f'{__file__} cannot be imported')
print_logo() print_logo()
@ -197,6 +233,7 @@ check_memory()
# read configurations from JSON # read configurations from JSON
config = read_config(args.config) config = read_config(args.config)
config = absolutify_paths(config)
# load waifu2x configuration # load waifu2x configuration
if args.driver == 'waifu2x_caffe': if args.driver == 'waifu2x_caffe':
@ -219,25 +256,26 @@ elif args.driver == 'waifu2x_converter':
ffmpeg_settings = config['ffmpeg'] ffmpeg_settings = config['ffmpeg']
# load video2x settings # load video2x settings
video2x_cache_folder = config['video2x']['video2x_cache_folder'] video2x_cache_directory = config['video2x']['video2x_cache_directory']
image_format = config['video2x']['image_format'].lower()
preserve_frames = config['video2x']['preserve_frames'] preserve_frames = config['video2x']['preserve_frames']
# create temp directories if they don't exist # create temp directories if they don't exist
if not video2x_cache_folder: if not video2x_cache_directory:
video2x_cache_folder = '{}\\video2x'.format(tempfile.gettempdir()) video2x_cache_directory = f'{tempfile.gettempdir()}\\video2x'
if video2x_cache_folder and not os.path.isdir(video2x_cache_folder): if video2x_cache_directory and not os.path.isdir(video2x_cache_directory):
if not os.path.isfile(video2x_cache_folder) and not os.path.islink(video2x_cache_folder): if not os.path.isfile(video2x_cache_directory) and not os.path.islink(video2x_cache_directory):
Avalon.warning('Specified cache folder/directory {} does not exist'.format(video2x_cache_folder)) Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
if Avalon.ask('Create folder/directory?', default=True, batch=args.batch): if Avalon.ask('Create directory?', default=True, batch=args.batch):
if os.mkdir(video2x_cache_folder) is None: if os.mkdir(video2x_cache_directory) is None:
Avalon.info('{} created'.format(video2x_cache_folder)) Avalon.info(f'{video2x_cache_directory} created')
else: else:
Avalon.error('Unable to create {}'.format(video2x_cache_folder)) Avalon.error(f'Unable to create {video2x_cache_directory}')
Avalon.error('Aborting...') Avalon.error('Aborting...')
exit(1) exit(1)
else: else:
Avalon.error('Specified cache folder/directory is a file/link') Avalon.error('Specified cache directory is a file/link')
Avalon.error('Unable to continue, exiting...') Avalon.error('Unable to continue, exiting...')
exit(1) exit(1)
@ -247,33 +285,74 @@ try:
# start timer # start timer
begin_time = time.time() begin_time = time.time()
# if input specified is a single file
if os.path.isfile(args.input): if os.path.isfile(args.input):
""" Upscale single video file """ """ Upscale single video file """
Avalon.info('Upscaling single video file: {}'.format(args.input)) Avalon.info(f'Upscaling single video file: {args.input}')
upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings, waifu2x_driver=args.driver, scale_width=args.width, scale_height=args.height, scale_ratio=args.ratio, model_dir=args.model_dir, threads=args.threads, video2x_cache_folder=video2x_cache_folder)
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, waifu2x_driver=args.driver, scale_width=args.width, scale_height=args.height, scale_ratio=args.ratio, model_dir=args.model_dir, threads=args.threads, video2x_cache_folder=video2x_cache_folder)
upscaler.run()
upscaler.cleanup()
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.info('Program completed, taking {} seconds'.format(round((time.time() - begin_time), 5))) # check for input output format mismatch
if os.path.isdir(args.output):
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('.*\..*$', args.output):
Avalon.error('No suffix found in output file path')
Avalon.error('Suffix must be specified for FFmpeg')
raise Exception('No suffix specified')
upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings)
# set optional options
upscaler.waifu2x_driver = args.driver
upscaler.scale_width = args.width
upscaler.scale_height = args.height
upscaler.scale_ratio = args.ratio
upscaler.model_dir = args.model_dir
upscaler.threads = args.threads
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.create_temp_directories()
upscaler.run()
upscaler.cleanup_temp_directories()
# if input specified is a directory
elif os.path.isdir(args.input):
""" Upscale videos in a directory """
Avalon.info(f'Upscaling videos in directory: {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 = f'{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)
# set optional options
upscaler.waifu2x_driver = args.driver
upscaler.scale_width = args.width
upscaler.scale_height = args.height
upscaler.scale_ratio = args.ratio
upscaler.model_dir = args.model_dir
upscaler.threads = args.threads
upscaler.video2x_cache_directory = video2x_cache_directory
upscaler.image_format = image_format
upscaler.preserve_frames = preserve_frames
# run upscaler
upscaler.create_temp_directories()
upscaler.run()
upscaler.cleanup_temp_directories()
else:
Avalon.error('Input path is neither a file nor a directory')
raise FileNotFoundError(f'{args.input} is neither file nor directory')
Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds')
except Exception: except Exception:
Avalon.error('An exception has occurred') Avalon.error('An exception has occurred')
traceback.print_exc() traceback.print_exc()
Avalon.warning('If you experience error \"cudaSuccess out of memory\", try reducing number of threads you\'re using')
finally: finally:
# remove Video2X Cache folder # remove Video2X Cache directory
try: try:
if not preserve_frames: if not preserve_frames:
shutil.rmtree(video2x_cache_folder) shutil.rmtree(video2x_cache_directory)
except FileNotFoundError: except FileNotFoundError:
pass pass

View File

@ -4,7 +4,7 @@
Name: Video2X Setup Script Name: Video2X Setup Script
Author: K4YT3X Author: K4YT3X
Date Created: November 28, 2018 Date Created: November 28, 2018
Last Modified: March 9, 2019 Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3), Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt available at: https://www.gnu.org/licenses/gpl-3.0.txt
@ -19,9 +19,11 @@ Installation Details:
- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe - waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe
""" """
import argparse
import json import json
import os import os
import subprocess import subprocess
import sys
import tempfile import tempfile
import traceback import traceback
import zipfile import zipfile
@ -31,7 +33,20 @@ import zipfile
# later in the script. # later in the script.
# import requests # import requests
VERSION = '1.1.0' VERSION = '1.2.1'
def process_arguments():
"""Processes CLI arguments
"""
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# 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')
# parse arguments
return parser.parse_args()
class Video2xSetup: class Video2xSetup:
@ -41,19 +56,26 @@ class Video2xSetup:
script. All files will be installed under %LOCALAPPDATA%\\video2x. script. All files will be installed under %LOCALAPPDATA%\\video2x.
""" """
def __init__(self): def __init__(self, driver, download_python_modules):
self.driver = driver
self.download_python_modules = download_python_modules
self.trash = [] self.trash = []
def run(self): def run(self):
if self.download_python_modules:
print('\nInstalling Python libraries') print('\nInstalling Python libraries')
self._install_python_requirements() self._install_python_requirements()
print('\nInstalling FFMPEG') print('\nInstalling FFMPEG')
self._install_ffmpeg() self._install_ffmpeg()
print('\nInstalling waifu2x-caffe') if self.driver == 'all':
self._install_waifu2x_caffe() self._install_waifu2x_caffe()
self._install_waifu2x_converter_cpp()
elif self.driver == 'waifu2x_caffe':
self._install_waifu2x_caffe()
elif self.driver == 'waifu2x_converter':
self._install_waifu2x_converter_cpp()
print('\nGenerating Video2X configuration file') print('\nGenerating Video2X configuration file')
self._generate_config() self._generate_config()
@ -66,7 +88,7 @@ class Video2xSetup:
""" """
with open('requirements.txt', 'r') as req: with open('requirements.txt', 'r') as req:
for line in req: for line in req:
package = line.split(' ')[0] package = line.split('==')[0]
pip_install(package) pip_install(package)
def _cleanup(self): def _cleanup(self):
@ -74,7 +96,7 @@ class Video2xSetup:
""" """
for file in self.trash: for file in self.trash:
try: try:
print('Deleting: {}'.format(file)) print(f'Deleting: {file}')
os.remove(file) os.remove(file)
except FileNotFoundError: except FileNotFoundError:
pass pass
@ -82,17 +104,18 @@ class Video2xSetup:
def _install_ffmpeg(self): def _install_ffmpeg(self):
""" Install FFMPEG """ Install FFMPEG
""" """
latest_release = 'https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.1-win64-static.zip' latest_release = 'https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip'
ffmpeg_zip = download(latest_release, tempfile.gettempdir()) ffmpeg_zip = download(latest_release, tempfile.gettempdir())
self.trash.append(ffmpeg_zip) self.trash.append(ffmpeg_zip)
with zipfile.ZipFile(ffmpeg_zip) as zipf: with zipfile.ZipFile(ffmpeg_zip) as zipf:
zipf.extractall('{}\\video2x'.format(os.getenv('localappdata'))) zipf.extractall(f'{os.getenv("localappdata")}\\video2x')
def _install_waifu2x_caffe(self): def _install_waifu2x_caffe(self):
""" Install waifu2x_caffe """ Install waifu2x_caffe
""" """
print('\nInstalling waifu2x-caffe')
import requests import requests
# Get latest release of waifu2x-caffe via GitHub API # Get latest release of waifu2x-caffe via GitHub API
@ -104,7 +127,25 @@ class Video2xSetup:
self.trash.append(waifu2x_caffe_zip) self.trash.append(waifu2x_caffe_zip)
with zipfile.ZipFile(waifu2x_caffe_zip) as zipf: with zipfile.ZipFile(waifu2x_caffe_zip) as zipf:
zipf.extractall('{}\\video2x'.format(os.getenv('localappdata'))) zipf.extractall(f'{os.getenv("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)
for a in latest_release['assets']:
if re.search(r'waifu2x-DeadSix27-win64_v[0-9]*\.zip', a['browser_download_url']):
waifu2x_converter_cpp_zip = download(a['browser_download_url'], tempfile.gettempdir())
self.trash.append(waifu2x_converter_cpp_zip)
with zipfile.ZipFile(waifu2x_converter_cpp_zip) as zipf:
zipf.extractall(f'{os.getenv("localappdata")}\\video2x\\waifu2x-converter-cpp')
def _generate_config(self): def _generate_config(self):
""" Generate video2x config """ Generate video2x config
@ -114,38 +155,48 @@ class Video2xSetup:
template_dict = json.load(template) template_dict = json.load(template)
template.close() template.close()
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = '{}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'.format(os.getenv('localappdata')) local_app_data = os.getenv('localappdata')
template_dict['ffmpeg']['ffmpeg_path'] = '{}\\video2x\\ffmpeg-4.1-win64-static\\bin'.format(os.getenv('localappdata'))
template_dict['ffmpeg']['ffmpeg_hwaccel'] = 'auto' # configure only the specified drivers
template_dict['ffmpeg']['extra_arguments'] = [] if self.driver == 'all':
template_dict['video2x']['video2x_cache_folder'] = False template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = f'{local_app_data}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'
template_dict['waifu2x_converter']['waifu2x_converter_path'] = f'{local_app_data}\\video2x\\waifu2x-converter-cpp'
elif self.driver == 'waifu2x_caffe':
template_dict['waifu2x_caffe']['waifu2x_caffe_path'] = f'{local_app_data}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'
elif self.driver == 'waifu2x_converter':
template_dict['waifu2x_converter']['waifu2x_converter_path'] = f'{local_app_data}\\video2x\\waifu2x-converter-cpp'
template_dict['ffmpeg']['ffmpeg_path'] = f'{local_app_data}\\video2x\\ffmpeg-latest-win64-static\\bin'
template_dict['video2x']['video2x_cache_directory'] = None
template_dict['video2x']['preserve_frames'] = False template_dict['video2x']['preserve_frames'] = False
# Write configuration into file # Write configuration into file
with open('video2x.json', 'w') as config: with open('video2x.json', 'w') as config:
json.dump(template_dict, config, indent=2) json.dump(template_dict, config, indent=4)
config.close() config.close()
def download(url, save_path, chunk_size=4096): def download(url, save_path, chunk_size=4096):
""" Download file to local with requests library """ Download file to local with requests library
""" """
from tqdm import tqdm
import requests import requests
output_file = '{}\\{}'.format(save_path, url.split('/')[-1]) output_file = f'{save_path}\\{url.split("/")[-1]}'
print('Downloading: {}'.format(url)) print(f'Downloading: {url}')
print('Chunk size: {}'.format(chunk_size)) print(f'Chunk size: {chunk_size}')
print('Saving to: {}'.format(output_file)) print(f'Saving to: {output_file}')
stream = requests.get(url, stream=True) stream = requests.get(url, stream=True)
total_size = int(stream.headers['content-length'])
# Write content into file # Write content into file
with open(output_file, 'wb') as output: with open(output_file, 'wb') as output:
for chunk in stream.iter_content(chunk_size=chunk_size): with tqdm(total=total_size, ascii=True) as progress_bar:
if chunk: for chunk in stream.iter_content(chunk_size=chunk_size):
print('!', end='') if chunk:
output.write(chunk) output.write(chunk)
print() progress_bar.update(len(chunk))
return output_file return output_file
@ -161,11 +212,20 @@ def pip_install(package):
if __name__ == "__main__": if __name__ == "__main__":
try: try:
print('Video2x Setup Script') args = process_arguments()
print('Version: {}'.format(VERSION)) print('Video2X Setup Script')
setup = Video2xSetup() 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() setup.run()
print('\n Script finished successfully') print('\nScript finished successfully')
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
print('An error has occurred') print('An error has occurred')

View File

@ -4,7 +4,7 @@
Name: Waifu2x Caffe Driver Name: Waifu2x Caffe Driver
Author: K4YT3X Author: K4YT3X
Date Created: Feb 24, 2018 Date Created: Feb 24, 2018
Last Modified: March 24, 2019 Last Modified: April 28, 2019
Description: This class is a high-level wrapper Description: This class is a high-level wrapper
for waifu2x-caffe. for waifu2x-caffe.
@ -33,20 +33,20 @@ class Waifu2xCaffe:
self.model_dir = model_dir self.model_dir = model_dir
self.print_lock = threading.Lock() self.print_lock = threading.Lock()
def upscale(self, input_folder, output_folder, scale_ratio, scale_width, scale_height, threads_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 """This is the core function for WAIFU2X class
Arguments: Arguments:
input_folder {string} -- source folder path input_directory {string} -- source directory path
output_folder {string} -- output folder path output_directory {string} -- output directory path
width {int} -- output video width width {int} -- output video width
height {int} -- output video height height {int} -- output video height
""" """
try: try:
# overwrite config file settings # overwrite config file settings
self.waifu2x_settings['input_path'] = input_folder self.waifu2x_settings['input_path'] = input_directory
self.waifu2x_settings['output_path'] = output_folder self.waifu2x_settings['output_path'] = output_directory
if scale_ratio: if scale_ratio:
self.waifu2x_settings['scale_ratio'] = scale_ratio self.waifu2x_settings['scale_ratio'] = scale_ratio
@ -54,9 +54,11 @@ class Waifu2xCaffe:
self.waifu2x_settings['scale_width'] = scale_width self.waifu2x_settings['scale_width'] = scale_width
self.waifu2x_settings['scale_height'] = scale_height self.waifu2x_settings['scale_height'] = scale_height
self.waifu2x_settings['output_extention'] = image_format
# print thread start message # print thread start message
self.print_lock.acquire() 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() self.print_lock.release()
# list to be executed # list to be executed
@ -72,20 +74,20 @@ class Waifu2xCaffe:
continue continue
else: else:
if len(key) == 1: if len(key) == 1:
execute.append('-{}'.format(key)) execute.append(f'-{key}')
else: else:
execute.append('--{}'.format(key)) execute.append(f'--{key}')
execute.append(str(value)) execute.append(str(value))
Avalon.debug_info('Executing: {}'.format(execute)) Avalon.debug_info(f'Executing: {execute}')
completed_command = subprocess.run(execute, check=True) completed_command = subprocess.run(execute, check=True)
# print thread exiting message # print thread exiting message
self.print_lock.acquire() 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() self.print_lock.release()
# return command execution return code # return command execution return code
return completed_command.returncode return completed_command.returncode
except Exception as e: except Exception as e:
threads_exceptions.append(e) upscaler_exceptions.append(e)

View File

@ -4,7 +4,7 @@
Name: Waifu2x Converter CPP Driver Name: Waifu2x Converter CPP Driver
Author: K4YT3X Author: K4YT3X
Date Created: February 8, 2019 Date Created: February 8, 2019
Last Modified: March 24, 2019 Last Modified: April 28, 2019
Description: This class is a high-level wrapper Description: This class is a high-level wrapper
for waifu2x-converter-cpp. for waifu2x-converter-cpp.
@ -28,39 +28,42 @@ class Waifu2xConverter:
self.waifu2x_settings['model_dir'] = model_dir self.waifu2x_settings['model_dir'] = model_dir
self.print_lock = threading.Lock() self.print_lock = threading.Lock()
def upscale(self, input_folder, output_folder, scale_ratio, jobs, threads_exceptions): def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions):
""" Waifu2x Converter Driver Upscaler """ Waifu2x Converter Driver Upscaler
This method executes the upscaling of extracted frames. This method executes the upscaling of extracted frames.
Arguments: Arguments:
input_folder {string} -- source folder path input_directory {string} -- source directory path
output_folder {string} -- output folder path output_directory {string} -- output directory path
scale_ratio {int} -- frames' scale ratio scale_ratio {int} -- frames' scale ratio
threads {int} -- number of threads threads {int} -- number of threads
""" """
try: try:
# overwrite config file settings # overwrite config file settings
self.waifu2x_settings['input'] = input_folder self.waifu2x_settings['input'] = input_directory
self.waifu2x_settings['output'] = output_folder self.waifu2x_settings['output'] = output_directory
# temporary fix for https://github.com/DeadSix27/waifu2x-converter-cpp/issues/109 # 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['i'] = input_directory
self.waifu2x_settings['o'] = output_directory
self.waifu2x_settings['input'] = None self.waifu2x_settings['input'] = None
self.waifu2x_settings['output'] = None self.waifu2x_settings['output'] = None
"""
self.waifu2x_settings['scale_ratio'] = scale_ratio self.waifu2x_settings['scale-ratio'] = scale_ratio
self.waifu2x_settings['jobs'] = jobs self.waifu2x_settings['jobs'] = jobs
self.waifu2x_settings['output-format'] = image_format
# models_rgb must be specified manually for waifu2x-converter-cpp # models_rgb must be specified manually for waifu2x-converter-cpp
# if it's not specified in the arguments, create automatically # if it's not specified in the arguments, create automatically
if self.waifu2x_settings['model_dir'] is None: if self.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'] = f'{self.waifu2x_settings["waifu2x_converter_path"]}\\models_rgb'
# print thread start message # print thread start message
self.print_lock.acquire() 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() self.print_lock.release()
# list to be executed # list to be executed
@ -72,16 +75,16 @@ class Waifu2xConverter:
# the key doesn't need to be passed in this case # the key doesn't need to be passed in this case
if key == 'waifu2x_converter_path': if key == 'waifu2x_converter_path':
execute.append('{}\\waifu2x-converter-cpp.exe'.format(str(value))) execute.append(f'{str(value)}\\waifu2x-converter-cpp.exe')
# null or None means that leave this option out (keep default) # null or None means that leave this option out (keep default)
elif value is None or value is False: elif value is None or value is False:
continue continue
else: else:
if len(key) == 1: if len(key) == 1:
execute.append('-{}'.format(key)) execute.append(f'-{key}')
else: else:
execute.append('--{}'.format(key)) execute.append(f'--{key}')
# true means key is an option # true means key is an option
if value is True: if value is True:
@ -89,8 +92,8 @@ class Waifu2xConverter:
execute.append(str(value)) execute.append(str(value))
Avalon.debug_info('Executing: {}'.format(execute)) Avalon.debug_info(f'Executing: {execute}')
return subprocess.run(execute, check=True).returncode return subprocess.run(execute, check=True).returncode
except Exception as e: except Exception as e:
threads_exceptions.append(e) upscaler_exceptions.append(e)