2018-12-11 20:52:48 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
__ __ _ _ ___ __ __
\ \ / / ( _ ) | | | __ \ \ \ / /
\ \ / / _ __ | | ___ ___ ) | \ V /
\ \/ / | | / _ ` | / _ \ / _ \ / / > <
\ / | | | ( _ | | | __ / | ( _ ) | / / _ / . \
\/ | _ | \__ , _ | \___ | \___ / | ____ | / _ / \_ \
2018-12-21 23:42:03 +00:00
Name : Video2X Controller
2018-12-11 20:52:48 +00:00
Author : K4YT3X
Date Created : Feb 24 , 2018
2019-03-30 18:03:52 +00:00
Last Modified : March 30 , 2019
2018-12-11 20:52:48 +00:00
Licensed under the GNU General Public License Version 3 ( GNU GPL v3 ) ,
available at : https : / / www . gnu . org / licenses / gpl - 3.0 . txt
2019-01-09 16:42:29 +00:00
( C ) 2018 - 2019 K4YT3X
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
Video2X is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Video2X is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < https : / / www . gnu . org / licenses / > .
Description : Video2X is an automation software based on waifu2x image
enlarging engine . It extracts frames from a video , enlarge it by a
number of times without losing any details or quality , keeping lines
smooth and edges sharp .
2018-12-11 20:52:48 +00:00
"""
from avalon_framework import Avalon
2019-03-13 16:31:07 +00:00
from upscaler import Upscaler
2019-03-05 00:35:50 +00:00
import argparse
2019-03-13 16:31:07 +00:00
import GPUtil
2018-12-11 20:52:48 +00:00
import json
import os
2018-12-21 23:42:03 +00:00
import psutil
2019-03-05 00:35:50 +00:00
import shutil
import tempfile
2018-12-11 20:52:48 +00:00
import time
import traceback
2019-03-30 18:03:52 +00:00
VERSION = ' 2.7.0 '
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
# each thread might take up to 2.5 GB during initialization.
2018-12-21 23:42:03 +00:00
# (system memory, not to be confused with GPU memory)
2019-02-26 23:04:51 +00:00
SYS_MEM_PER_THREAD = 2.5
GPU_MEM_PER_THREAD = 3.5
2018-12-11 20:52:48 +00:00
def process_arguments ( ) :
""" Processes CLI arguments
This function parses all arguments
This allows users to customize options
for the output video .
"""
2019-03-09 17:50:54 +00:00
parser = argparse . ArgumentParser ( formatter_class = argparse . ArgumentDefaultsHelpFormatter )
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
# video options
2019-03-19 17:25:57 +00:00
file_options = parser . add_argument_group ( ' File Options ' )
file_options . add_argument ( ' -i ' , ' --input ' , help = ' Source video file/directory ' , action = ' store ' , required = True )
file_options . add_argument ( ' -o ' , ' --output ' , help = ' Output video file/directory ' , action = ' store ' , required = True )
# upscaler options
upscaler_options = parser . add_argument_group ( ' Upscaler Options ' )
upscaler_options . add_argument ( ' -m ' , ' --method ' , help = ' Upscaling method ' , action = ' store ' , default = ' gpu ' , choices = [ ' cpu ' , ' gpu ' , ' cudnn ' ] , required = True )
upscaler_options . add_argument ( ' -d ' , ' --driver ' , help = ' Waifu2x driver ' , action = ' store ' , default = ' waifu2x_caffe ' , choices = [ ' waifu2x_caffe ' , ' waifu2x_converter ' ] )
2019-03-26 21:58:29 +00:00
upscaler_options . add_argument ( ' -y ' , ' --model_dir ' , help = ' Folder containing model JSON files ' , action = ' store ' )
2019-03-19 17:25:57 +00:00
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 ( __file__ ) ) ) )
2019-03-26 21:58:29 +00:00
upscaler_options . add_argument ( ' -b ' , ' --batch ' , help = ' Enable batch mode (select all default values to questions) ' , action = ' store_true ' )
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
# scaling options
scaling_options = parser . add_argument_group ( ' Scaling Options ' )
2019-03-26 21:58:29 +00:00
scaling_options . add_argument ( ' --width ' , help = ' Output video width ' , action = ' store ' , type = int )
scaling_options . add_argument ( ' --height ' , help = ' Output video height ' , action = ' store ' , type = int )
scaling_options . add_argument ( ' -r ' , ' --ratio ' , help = ' Scaling ratio ' , action = ' store ' , type = float )
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
# parse arguments
2018-12-11 20:52:48 +00:00
return parser . parse_args ( )
def print_logo ( ) :
print ( ' __ __ _ _ ___ __ __ ' )
print ( ' \\ \\ / / (_) | | |__ \\ \\ \\ / / ' )
print ( ' \\ \\ / / _ __| | ___ ___ ) | \\ V / ' )
print ( ' \\ \\ / / | | / _` | / _ \\ / _ \\ / / > < ' )
print ( ' \\ / | | | (_| | | __/ | (_) | / /_ / . \\ ' )
print ( ' \\ / |_| \\ __,_| \\ ___| \\ ___/ |____| /_/ \\ _ \\ ' )
print ( ' \n Video2X Video Enlarger ' )
2018-12-21 23:42:03 +00:00
spaces = ( ( 44 - len ( " Version {} " . format ( VERSION ) ) ) / / 2 ) * " "
2018-12-11 20:52:48 +00:00
print ( ' {} \n {} Version {} \n {} ' . format ( Avalon . FM . BD , spaces , VERSION , Avalon . FM . RST ) )
2019-02-26 23:04:51 +00:00
def check_memory ( ) :
2018-12-21 23:42:03 +00:00
""" Check usable system memory
Warn the user if insufficient memory is available for
the number of threads that the user have chosen .
"""
2019-02-26 23:04:51 +00:00
memory_status = [ ]
2019-03-19 17:12:12 +00:00
# get system available memory
2019-02-26 23:04:51 +00:00
system_memory_available = psutil . virtual_memory ( ) . available / ( 1024 * * 3 )
memory_status . append ( ( ' system ' , system_memory_available ) )
2019-03-19 17:12:12 +00:00
# check if Nvidia-smi is available
2019-02-26 23:04:51 +00:00
# GPUtil requires nvidia-smi.exe to interact with GPU
if args . method == ' gpu ' or args . method == ' cudnn ' :
if not os . path . isfile ( ' C: \\ Program Files \\ NVIDIA Corporation \\ NVSMI \\ nvidia-smi.exe ' ) :
# Nvidia System Management Interface not available
Avalon . warning ( ' Nvidia-smi not available, skipping available memory check ' )
2019-03-25 03:31:20 +00:00
Avalon . warning ( ' If you experience error \" cudaSuccess out of memory \" , try reducing number of threads you \' re using ' )
2019-02-26 23:04:51 +00:00
else :
2019-03-13 15:59:42 +00:00
try :
# "0" is GPU ID. Both waifu2x drivers use the first GPU available, therefore only 0 makes sense
gpu_memory_available = ( GPUtil . getGPUs ( ) [ 0 ] . memoryTotal - GPUtil . getGPUs ( ) [ 0 ] . memoryUsed ) / 1024
memory_status . append ( ( ' GPU ' , gpu_memory_available ) )
except ValueError :
pass
2019-02-26 23:04:51 +00:00
2019-03-19 17:12:12 +00:00
# go though each checkable memory type and check availability
2019-02-26 23:04:51 +00:00
for memory_type , memory_available in memory_status :
if memory_type == ' system ' :
mem_per_thread = SYS_MEM_PER_THREAD
2018-12-21 23:42:03 +00:00
else :
2019-02-26 23:04:51 +00:00
mem_per_thread = GPU_MEM_PER_THREAD
2019-03-19 17:12:12 +00:00
# if user doesn't even have enough memory to run even one thread
2019-02-26 23:04:51 +00:00
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 ) )
2018-12-21 23:42:03 +00:00
Avalon . warning ( ' Proceed with caution ' )
2019-02-26 23:04:51 +00:00
if args . threads > 1 :
2019-03-12 16:03:51 +00:00
if Avalon . ask ( ' Reduce number of threads to avoid crashing? ' , default = True , batch = args . batch ) :
2019-02-26 23:04:51 +00:00
args . threads = 1
2019-03-19 17:12:12 +00:00
# if memory available is less than needed, warn the user
2019-02-26 23:04:51 +00:00
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 ) ) )
2019-03-19 17:12:12 +00:00
# ask the user if he / she wants to change to the recommended
2019-02-26 23:04:51 +00:00
# number of threads
2019-03-12 16:03:51 +00:00
if Avalon . ask ( ' Change to the recommended value? ' , default = True , batch = args . batch ) :
2019-02-26 23:04:51 +00:00
args . threads = int ( memory_available / / mem_per_thread )
else :
Avalon . warning ( ' Proceed with caution ' )
2018-12-21 23:42:03 +00:00
2018-12-11 20:52:48 +00:00
def read_config ( config_file ) :
""" Reads configuration file
2018-12-21 23:42:03 +00:00
Returns a dictionary read by JSON .
2018-12-11 20:52:48 +00:00
"""
with open ( config_file , ' r ' ) as raw_config :
config = json . load ( raw_config )
return config
# /////////////////// Execution /////////////////// #
2019-03-19 17:12:12 +00:00
# this is not a library
2018-12-11 20:52:48 +00:00
if __name__ != ' __main__ ' :
Avalon . error ( ' This file cannot be imported ' )
2018-12-21 23:42:03 +00:00
raise ImportError ( ' {} cannot be imported ' . format ( __file__ ) )
2018-12-11 20:52:48 +00:00
print_logo ( )
2019-03-19 17:12:12 +00:00
# process CLI arguments
2018-12-11 20:52:48 +00:00
args = process_arguments ( )
2019-03-19 17:12:12 +00:00
# arguments sanity check
2019-02-08 21:49:23 +00:00
if args . driver == ' waifu2x_converter ' and args . width and args . height :
Avalon . error ( ' Waifu2x Converter CPP accepts only scaling ratio ' )
exit ( 1 )
if ( args . width or args . height ) and args . ratio :
Avalon . error ( ' You can only specify either scaling ratio or output width and height ' )
exit ( 1 )
if ( args . width and not args . height ) or ( not args . width and args . height ) :
Avalon . error ( ' You must specify both width and height ' )
exit ( 1 )
2019-03-19 17:12:12 +00:00
# check available memory
2019-02-26 23:04:51 +00:00
check_memory ( )
2018-12-21 23:42:03 +00:00
2019-03-19 17:12:12 +00:00
# read configurations from JSON
2018-12-21 23:42:03 +00:00
config = read_config ( args . config )
2019-03-09 17:50:54 +00:00
# load waifu2x configuration
if args . driver == ' waifu2x_caffe ' :
waifu2x_settings = config [ ' waifu2x_caffe ' ]
2019-03-25 03:31:20 +00:00
if not os . path . isfile ( waifu2x_settings [ ' waifu2x_caffe_path ' ] ) :
Avalon . error ( ' Specified waifu2x-caffe directory doesn \' t exist ' )
Avalon . error ( ' Please check the configuration file settings ' )
raise FileNotFoundError ( waifu2x_settings [ ' waifu2x_caffe_path ' ] )
2019-03-09 17:50:54 +00:00
elif args . driver == ' waifu2x_converter ' :
waifu2x_settings = config [ ' waifu2x_converter ' ]
2019-03-25 03:31:20 +00:00
if not os . path . isdir ( waifu2x_settings [ ' waifu2x_converter_path ' ] ) :
Avalon . error ( ' Specified waifu2x-conver-cpp directory doesn \' t exist ' )
Avalon . error ( ' Please check the configuration file settings ' )
raise FileNotFoundError ( waifu2x_settings [ ' waifu2x_converter_path ' ] )
# check if waifu2x path is valid
2019-03-09 17:50:54 +00:00
# read FFMPEG configuration
ffmpeg_settings = config [ ' ffmpeg ' ]
# load video2x settings
video2x_cache_folder = config [ ' video2x ' ] [ ' video2x_cache_folder ' ]
2019-03-30 18:03:52 +00:00
image_format = config [ ' video2x ' ] [ ' image_format ' ] . lower ( )
2019-03-09 17:50:54 +00:00
preserve_frames = config [ ' video2x ' ] [ ' preserve_frames ' ]
2019-02-08 19:05:38 +00:00
2019-03-19 17:12:12 +00:00
# create temp directories if they don't exist
2019-03-05 00:35:50 +00:00
if not video2x_cache_folder :
video2x_cache_folder = ' {} \\ video2x ' . format ( tempfile . gettempdir ( ) )
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 ) )
2019-03-12 16:03:51 +00:00
if Avalon . ask ( ' Create folder/directory? ' , default = True , batch = args . batch ) :
2019-03-05 00:35:50 +00:00
if os . mkdir ( video2x_cache_folder ) is None :
Avalon . info ( ' {} created ' . format ( video2x_cache_folder ) )
else :
Avalon . error ( ' Unable to create {} ' . format ( video2x_cache_folder ) )
Avalon . error ( ' Aborting... ' )
exit ( 1 )
else :
Avalon . error ( ' Specified cache folder/directory is a file/link ' )
Avalon . error ( ' Unable to continue, exiting... ' )
exit ( 1 )
2019-02-26 23:04:51 +00:00
2018-12-11 20:52:48 +00:00
2019-03-19 17:12:12 +00:00
# start execution
2018-12-11 20:52:48 +00:00
try :
2019-03-19 17:12:12 +00:00
# start timer
2018-12-11 20:52:48 +00:00
begin_time = time . time ( )
2018-12-21 23:42:03 +00:00
if os . path . isfile ( args . input ) :
""" Upscale single video file """
Avalon . info ( ' Upscaling single video file: {} ' . format ( args . input ) )
2019-03-30 18:03:52 +00:00
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_folder = video2x_cache_folder
upscaler . image_format = image_format
upscaler . preserve_frames = preserve_frames
# run upscaler-
2018-12-21 23:42:03 +00:00
upscaler . run ( )
2019-03-05 00:35:50 +00:00
upscaler . cleanup ( )
2018-12-21 23:42:03 +00:00
elif os . path . isdir ( args . input ) :
""" Upscale videos in a folder/directory """
2019-02-26 23:04:51 +00:00
Avalon . info ( ' Upscaling videos in folder/directory: {} ' . format ( args . input ) )
2018-12-21 23:42:03 +00:00
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 )
2019-03-30 18:03:52 +00:00
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_folder = video2x_cache_folder
upscaler . image_format = image_format
upscaler . preserve_frames = preserve_frames
# run upscaler
2018-12-21 23:42:03 +00:00
upscaler . run ( )
2019-03-05 00:35:50 +00:00
upscaler . cleanup ( )
2018-12-21 23:42:03 +00:00
else :
Avalon . error ( ' Input path is neither a file nor a folder/directory ' )
raise FileNotFoundError ( ' {} is neither file nor folder/directory ' . format ( args . input ) )
2018-12-11 20:52:48 +00:00
Avalon . info ( ' Program completed, taking {} seconds ' . format ( round ( ( time . time ( ) - begin_time ) , 5 ) ) )
except Exception :
2019-03-05 00:35:50 +00:00
Avalon . error ( ' An exception has occurred ' )
2018-12-11 20:52:48 +00:00
traceback . print_exc ( )
2019-03-25 03:31:20 +00:00
Avalon . warning ( ' If you experience error \" cudaSuccess out of memory \" , try reducing number of threads you \' re using ' )
2019-03-05 00:35:50 +00:00
finally :
2019-03-19 17:12:12 +00:00
# remove Video2X Cache folder
2019-03-05 00:35:50 +00:00
try :
if not preserve_frames :
shutil . rmtree ( video2x_cache_folder )
except FileNotFoundError :
pass