15 Commits
2.7.0 ... 2.7.1

Author SHA1 Message Date
k4yt3x
146044505b fixed issue #77 incorrect output video format 2019-05-06 16:25:10 -04:00
k4yt3x
a7d41cafdf changed default number of threads to 1 2019-04-30 03:53:40 -04:00
k4yt3x
875572c75a corrected repo links and step by step instruction description 2019-04-29 00:11:30 -04:00
k4yt3x
c7353c4bf2 unified all "folder/directory" into "directory" to end this mess 2019-04-29 00:06:54 -04:00
k4yt3x
0af68bb39c added description for Q&A page 2019-04-29 00:05:56 -04:00
k4yt3x
19cb823591 all string formatting switched to f-string 2019-04-22 01:26:36 -04:00
k4yt3x
6c1a714a1e updated README for 2.7.1 2019-04-18 14:57:30 -04:00
k4yt3x
837aca371e fixed temp folders bug 2019-04-18 14:56:30 -04:00
k4yt3x
6538abd6e5 corrected a typo 2019-04-11 14:56:51 -04:00
k4yt3x
a57465e866 added input and output file type mismatch check 2019-04-11 14:53:46 -04:00
k4yt3x
405c4b6636 fixed error reading video resolution 2019-04-02 16:23:59 -04:00
k4yt3x
3d46e1a822 changed default crf value to visually lossless, added video bitrate limit option 2019-04-01 16:02:26 -04:00
k4yt3x
7b501f2c67 added video2x builds information 2019-03-31 03:58:09 -04:00
k4yt3x
98e2cbdb04 1.2.1 added support for exe packaging 2019-03-31 03:46:15 -04:00
k4yt3x
25156077f2 added function to absolutify paths set in config file for better exe support 2019-03-31 03:23:08 -04:00
9 changed files with 356 additions and 208 deletions

View File

@@ -2,24 +2,34 @@
### 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
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. [**waifu2x-caffe**](https://github.com/lltcggie/waifu2x-caffe/releases) / [**waifu2x-converter-cpp**](https://github.com/DeadSix27/waifu2x-converter-cpp/releases)
## Recent Changes
### 2.7.1 (April 18, 2019)
- Fixed video2x custom temp folder bug found by @cr08 .
### 2.7.0 (March 30, 2019)
- Added support for different extracted image formats.
- Redesigned FFMPEG wrapper, FFMPEG settings are now customizable in the `video2x.json` config file.
- Redesigned FFmpeg wrapper, FFmpeg settings are now customizable in the `video2x.json` config file.
- Other minor enhancements and adjustments (e.g. argument -> method variable)
### Setup Script 1.2.0 (March 26, 2019)
@@ -27,12 +37,6 @@ Component names that are *italicized* can be automatically downloaded and config
- `video2x_setup.py` script can now automatically download and configure `waifu2x-converter-cpp`.
- replaced old progress indicator with progress bar.
### 2.6.3 (March 24, 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.
## Description
Video2X is an automation software based on waifu2x image enlarging engine. It extracts frames from a video, enlarge it by a number of times without losing any details or quality, keeping lines smooth and edges sharp.
@@ -55,17 +59,21 @@ Clip is from trailer of animated movie "千と千尋の神隠し". Copyright bel
## 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.
---
@@ -75,7 +83,7 @@ Go to the [Waifu2X Drivers](https://github.com/K4YT3X/video2x/wiki/Waifu2X-Drive
- **Python 3**
Download: https://www.python.org/downloads/windows/
- **FFMPEG Windows Build**
- **FFmpeg Windows Build**
Download: https://ffmpeg.org/download.html
- **waifu2x-caffe** (for Nvidia CUDA/CUDNN)
Download: https://github.com/lltcggie/waifu2x-caffe/releases
@@ -87,7 +95,7 @@ Download: https://github.com/DeadSix27/waifu2x-converter-cpp/releases
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
```
@@ -215,10 +223,16 @@ 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)
## Special Thanks
Appreciations given to the following contributors:
- @BrianPetkovsek
## 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.

View File

@@ -4,7 +4,7 @@
Name: FFMPEG Class
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: March 30, 2019
Last Modified: May 4, 2019
Description: This class handles all FFMPEG related
operations.
@@ -29,10 +29,10 @@ class Ffmpeg:
# 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_path = f'{self.ffmpeg_path}\\'
self.ffmpeg_binary = '{}ffmpeg.exe'.format(self.ffmpeg_path)
self.ffmpeg_probe_binary = '{}ffprobe.exe'.format(self.ffmpeg_path)
self.ffmpeg_binary = f'{self.ffmpeg_path}ffmpeg.exe'
self.ffmpeg_probe_binary = f'{self.ffmpeg_path}ffprobe.exe'
self.image_format = image_format
def get_video_info(self, input_video):
@@ -59,10 +59,10 @@ class Ffmpeg:
'-show_format',
'-show_streams',
'-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
return json.loads(json_str.decode('utf-8'))
@@ -74,15 +74,21 @@ class Ffmpeg:
Arguments:
input_video {string} -- input video path
extracted_frames {string} -- video output folder
extracted_frames {string} -- video output directory
"""
execute = [
self.ffmpeg_binary,
self.ffmpeg_binary
]
execute.extend(self._read_configuration(phase='video_to_frames'))
execute.extend([
'-i',
input_video,
'{}\\extracted_%0d.{}'.format(extracted_frames, self.image_format)
]
self._execute(execute=execute, phase='video_to_frames')
f'{extracted_frames}\\extracted_%0d.{self.image_format}'
])
self._execute(execute)
def convert_video(self, framerate, resolution, upscaled_frames):
"""Converts images into videos
@@ -93,19 +99,37 @@ class Ffmpeg:
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 FFmpeg input options
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):
""" Migrates audio tracks and subtitles from input video to output video
@@ -118,30 +142,71 @@ class Ffmpeg:
execute = [
self.ffmpeg_binary,
'-i',
'{}\\no_audio.mp4'.format(upscaled_frames),
f'{upscaled_frames}\\no_audio.mp4',
'-i',
input_video,
output_video
input_video
]
self._execute(execute=execute, phase='migrating_tracks')
def _execute(self, execute, phase):
execute.extend(self._read_configuration(phase='frames_to_video', section='output_options'))
for key in self.ffmpeg_settings[phase].keys():
execute.extend([
output_video
])
value = self.ffmpeg_settings[phase][key]
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:
if value is None or value is False or isinstance(value, list) or isinstance(value, dict):
continue
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))
Avalon.debug_info('Executing: {}'.format(execute))
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: K4YT3X
Date Created: March 24, 2019
Last Modified: March 24, 2019
Last Modified: April 28, 2019
Description: This class is to remove the extracted frames
that have already been upscaled.
@@ -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,23 +53,23 @@ 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 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
# 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 i in range(self.threads):
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):
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
if os.path.isfile(file_path) and f in output_frames:
os.remove(file_path)

View File

@@ -4,7 +4,7 @@
Name: Video2X Upscaler
Author: K4YT3X
Date Created: December 10, 2018
Last Modified: March 30, 2019
Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
@@ -30,7 +30,7 @@ import time
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,25 +52,27 @@ 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 = f'{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 = 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}')
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
@@ -87,18 +89,18 @@ 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'])
for directory in extracted_frames_directories:
total_frames += len([f for f in os.listdir(directory) if f[-4:] == '.png'])
with tqdm(total=total_frames, ascii=True, desc='Upscaling Progress') as progress_bar:
@@ -150,8 +152,8 @@ class Upscaler:
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))
renamed = re.sub(f'_\[.*-.*\]\[x(\d+(\.\d+)?)\]\.{self.image_format}', f'.{self.image_format}', image)
shutil.move(f'{self.upscaled_frames}\\{image}', f'{self.upscaled_frames}\\{renamed}')
self.progress_bar_exit_signal = True
progress_bar.join()
@@ -168,25 +170,25 @@ class Upscaler:
if len(frames) < self.threads:
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
thread_pool = []
thread_folders = []
thread_directories = []
for thread_id in range(self.threads):
thread_folder = '{}\\{}'.format(self.extracted_frames, str(thread_id))
thread_folders.append(thread_folder)
thread_directory = f'{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 os.path.isdir(thread_directory):
shutil.rmtree(thread_directory)
os.mkdir(thread_directory)
# 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
# evenly distribute images into each directory
# until there is none left in the directory
for image in frames:
# move image
shutil.move(image, thread_pool[0][0])
@@ -206,7 +208,7 @@ class Upscaler:
upscaler_threads.append(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()
# create the clearer and start it
@@ -255,7 +257,7 @@ class Upscaler:
elif self.waifu2x_driver == 'waifu2x_converter':
w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir)
else:
raise Exception('Unrecognized waifu2x driver: {}'.format(self.waifu2x_driver))
raise Exception(f'Unrecognized waifu2x driver: {self.waifu2x_driver}')
# extract frames from video
fm.extract_frames(self.input_video, self.extracted_frames)
@@ -279,14 +281,14 @@ class Upscaler:
# get average frame rate of video stream
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
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')
@@ -297,7 +299,7 @@ class Upscaler:
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

View File

@@ -47,26 +47,32 @@
"-y": true
},
"frames_to_video": {
"-qscale:v": null,
"-qscale:a": null,
"-f": "image2",
"-vcodec": "libx264",
"-crf": 25,
"-pix_fmt": "yuv420p",
"-hwaccel": "auto",
"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": {
"-map": "0:v:0?",
"-map": "1?",
"-c": "copy",
"-map": "-1:v?",
"-hwaccel": "auto",
"output_options":{
"-map": "0:v:0?",
"-map": "1?",
"-c": "copy",
"-map": "-1:v?",
"-pix_fmt": "yuv420p"
},
"-y": true
}
},
"video2x": {
"video2x_cache_folder": null,
"video2x_cache_directory": null,
"image_format": "png",
"preserve_frames": false
}

View File

@@ -13,7 +13,7 @@ __ __ _ _ ___ __ __
Name: Video2X Controller
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: March 30, 2019
Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
@@ -45,13 +45,14 @@ import GPUtil
import json
import os
import psutil
import re
import shutil
import sys
import tempfile
import time
import traceback
VERSION = '2.7.0'
VERSION = '2.7.1'
# each thread might take up to 2.5 GB during initialization.
# (system memory, not to be confused with GPU memory)
@@ -77,9 +78,9 @@ def process_arguments():
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('-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=1)
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')
# scaling options
@@ -100,8 +101,8 @@ def print_logo():
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))
spaces = ((44 - len(f'Version {VERSION}')) // 2) * ' '
print(f'{Avalon.FM.BD}\n{spaces} Version {VERSION}\n{Avalon.FM.RST}')
def check_memory():
@@ -140,17 +141,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,12 +171,46 @@ 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 = 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 /////////////////// #
# 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_logo()
@@ -198,6 +233,7 @@ check_memory()
# read configurations from JSON
config = read_config(args.config)
config = absolutify_paths(config)
# load waifu2x configuration
if args.driver == 'waifu2x_caffe':
@@ -220,26 +256,26 @@ elif args.driver == 'waifu2x_converter':
ffmpeg_settings = config['ffmpeg']
# 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']
# create temp directories if they don't exist
if not video2x_cache_folder:
video2x_cache_folder = '{}\\video2x'.format(tempfile.gettempdir())
if not video2x_cache_directory:
video2x_cache_directory = f'{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))
if video2x_cache_directory and not os.path.isdir(video2x_cache_directory):
if not os.path.isfile(video2x_cache_directory) and not os.path.islink(video2x_cache_directory):
Avalon.warning(f'Specified cache directory {video2x_cache_directory} does not exist')
if Avalon.ask('Create directory?', default=True, batch=args.batch):
if os.mkdir(video2x_cache_directory) is None:
Avalon.info(f'{video2x_cache_directory} created')
else:
Avalon.error('Unable to create {}'.format(video2x_cache_folder))
Avalon.error(f'Unable to create {video2x_cache_directory}')
Avalon.error('Aborting...')
exit(1)
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...')
exit(1)
@@ -249,9 +285,21 @@ try:
# start timer
begin_time = time.time()
# if input specified is a single file
if os.path.isfile(args.input):
""" Upscale single video file """
Avalon.info('Upscaling single video file: {}'.format(args.input))
Avalon.info(f'Upscaling single video file: {args.input}')
# 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
@@ -261,18 +309,21 @@ 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()
upscaler.cleanup_temp_directories()
# if input specified is a directory
elif os.path.isdir(args.input):
""" Upscale videos in a folder/directory """
Avalon.info('Upscaling videos in folder/directory: {}'.format(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 = '{}\\{}'.format(args.output, input_video)
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
@@ -282,26 +333,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
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
# remove Video2X Cache directory
try:
if not preserve_frames:
shutil.rmtree(video2x_cache_folder)
shutil.rmtree(video2x_cache_directory)
except FileNotFoundError:
pass

View File

@@ -4,7 +4,7 @@
Name: Video2X Setup Script
Author: K4YT3X
Date Created: November 28, 2018
Last Modified: March 26, 2019
Last Modified: April 28, 2019
Licensed under the GNU General Public License Version 3 (GNU GPL v3),
available at: https://www.gnu.org/licenses/gpl-3.0.txt
@@ -23,6 +23,7 @@ import argparse
import json
import os
import subprocess
import sys
import tempfile
import traceback
import zipfile
@@ -32,7 +33,7 @@ import zipfile
# later in the script.
# import requests
VERSION = '1.2.0'
VERSION = '1.2.1'
def process_arguments():
@@ -55,14 +56,15 @@ 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):
print('\nInstalling Python libraries')
self._install_python_requirements()
if self.download_python_modules:
print('\nInstalling Python libraries')
self._install_python_requirements()
print('\nInstalling FFMPEG')
self._install_ffmpeg()
@@ -94,7 +96,7 @@ class Video2xSetup:
"""
for file in self.trash:
try:
print('Deleting: {}'.format(file))
print(f'Deleting: {file}')
os.remove(file)
except FileNotFoundError:
pass
@@ -108,7 +110,7 @@ class Video2xSetup:
self.trash.append(ffmpeg_zip)
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):
""" Install waifu2x_caffe
@@ -125,7 +127,7 @@ 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(f'{os.getenv("localappdata")}\\video2x')
def _install_waifu2x_converter_cpp(self):
""" Install waifu2x_caffe
@@ -143,7 +145,7 @@ 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(f'{os.getenv("localappdata")}\\video2x\\waifu2x-converter-cpp')
def _generate_config(self):
""" Generate video2x config
@@ -153,24 +155,24 @@ class Video2xSetup:
template_dict = json.load(template)
template.close()
local_app_data = os.getenv('localappdata')
# 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'] = 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'] = '{}\\video2x\\waifu2x-caffe\\waifu2x-caffe-cui.exe'.format(os.getenv('localappdata'))
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'] = '{}\\video2x\\waifu2x-converter-cpp'.format(os.getenv('localappdata'))
template_dict['waifu2x_converter']['waifu2x_converter_path'] = f'{local_app_data}\\video2x\\waifu2x-converter-cpp'
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'] = f'{local_app_data}\\video2x\\ffmpeg-latest-win64-static\\bin'
template_dict['video2x']['video2x_cache_directory'] = None
template_dict['video2x']['preserve_frames'] = False
# Write configuration into file
with open('video2x.json', 'w') as config:
json.dump(template_dict, config, indent=2)
json.dump(template_dict, config, indent=4)
config.close()
@@ -180,10 +182,10 @@ 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))
output_file = f'{save_path}\\{url.split("/")[-1]}'
print(f'Downloading: {url}')
print(f'Chunk size: {chunk_size}')
print(f'Saving to: {output_file}')
stream = requests.get(url, stream=True)
total_size = int(stream.headers['content-length'])
@@ -211,11 +213,19 @@ def pip_install(package):
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')

View File

@@ -4,7 +4,7 @@
Name: Waifu2x Caffe Driver
Author: K4YT3X
Date Created: Feb 24, 2018
Last Modified: March 30, 2019
Last Modified: April 28, 2019
Description: This class is a high-level wrapper
for waifu2x-caffe.
@@ -33,20 +33,20 @@ class Waifu2xCaffe:
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,7 +58,7 @@ 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
@@ -74,17 +74,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

View File

@@ -4,7 +4,7 @@
Name: Waifu2x Converter CPP Driver
Author: K4YT3X
Date Created: February 8, 2019
Last Modified: March 30, 2019
Last Modified: April 28, 2019
Description: This class is a high-level wrapper
for waifu2x-converter-cpp.
@@ -28,26 +28,26 @@ 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
self.waifu2x_settings['input'] = input_directory
self.waifu2x_settings['output'] = output_directory
# 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['output'] = None
"""
@@ -59,11 +59,11 @@ 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'] = f'{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
@@ -75,16 +75,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)))
execute.append(f'{str(value)}\\waifu2x-converter-cpp.exe')
# 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 +92,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: