diff --git a/README.md b/README.md index 90ed948..242b914 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,13 @@ Component names that are **bolded** can be automatically downloaded and configur ## Recent Changes +### 3.0.0 (November 26, 2019) + +- Linux compatibility +- Configuration file changed to YAML format + - You may still use a JSON-formatted config file. To do so, please specify `-c video2x.json`. +- Other code clean-up and optimization + ### 2.10.0 (August 16, 2019) - **Added support for [Anime4K](https://github.com/bloc97/Anime4K)** @@ -36,18 +43,10 @@ Component names that are **bolded** can be automatically downloaded and configur - Removed f_string dependency and support for legacy versions of Python - Organized file import statements -### 2.8.1 (July 9, 2019) +### Setup Script 1.6.0 (November 26, 2019) -- Added automatic pixel format detection -- Added automatic color bit depth detection - -### 2.8.0 (June 25, 2019) - -- **Added support for [waifu2x-ncnn-vulkan](https://github.com/nihui/waifu2x-ncnn-vulkan)** - -### Setup Script 1.5.0 (August 16, 2019) - -- Added automatic installation support for `Anime4K` +- Added compatibility for new YAML configuration file +- Added better exception handling ## Description diff --git a/bin/anime4k.py b/src/anime4k.py old mode 100644 new mode 100755 similarity index 92% rename from bin/anime4k.py rename to src/anime4k.py index 5f992eb..61bc262 --- a/bin/anime4k.py +++ b/src/anime4k.py @@ -4,7 +4,7 @@ Name: Anime4K Driver Author: K4YT3X Date Created: August 15, 2019 -Last Modified: August 15, 2019 +Last Modified: November 15, 2019 Description: This class is a high-level wrapper for Anime4k. @@ -27,8 +27,8 @@ class Anime4k: the upscale function. """ - def __init__(self, waifu2x_settings): - self.waifu2x_settings = waifu2x_settings + def __init__(self, driver_settings): + self.driver_settings = driver_settings self.print_lock = threading.Lock() def upscale(self, input_directory, output_directory, scale_ratio, upscaler_exceptions, push_strength=None, push_grad_strength=None): @@ -57,9 +57,9 @@ class Anime4k: for image in extracted_frame_files: execute = [ - self.waifu2x_settings['java_path'], + self.driver_settings['java_path'], '-jar', - self.waifu2x_settings['anime4k_path'], + self.driver_settings['path'], str(image.absolute()), str(output_directory / image.name), str(scale_ratio) diff --git a/bin/exceptions.py b/src/exceptions.py old mode 100644 new mode 100755 similarity index 100% rename from bin/exceptions.py rename to src/exceptions.py diff --git a/bin/ffmpeg.py b/src/ffmpeg.py old mode 100644 new mode 100755 similarity index 96% rename from bin/ffmpeg.py rename to src/ffmpeg.py index 17bbc61..5387bc7 --- a/bin/ffmpeg.py +++ b/src/ffmpeg.py @@ -4,7 +4,7 @@ Name: Video2X FFmpeg Controller Author: K4YT3X Date Created: Feb 24, 2018 -Last Modified: August 15, 2019 +Last Modified: November 15, 2019 Description: This class handles all FFmpeg related operations. """ @@ -30,8 +30,8 @@ class Ffmpeg: self.ffmpeg_settings = ffmpeg_settings self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings['ffmpeg_path']) - self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg.exe' - self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe.exe' + self.ffmpeg_binary = self.ffmpeg_path / 'ffmpeg' + self.ffmpeg_probe_binary = self.ffmpeg_path / 'ffprobe' self.image_format = image_format self.pixel_format = None @@ -67,7 +67,7 @@ class Ffmpeg: pass # print pixel formats for debugging - Avalon.debug_info(pixel_formats) + Avalon.debug_info(str(pixel_formats)) return pixel_formats @@ -284,4 +284,4 @@ class Ffmpeg: Avalon.debug_info(f'Executing: {execute}') - return subprocess.run(execute, shell=True, check=True).returncode + return subprocess.run(execute, check=True).returncode diff --git a/bin/image_cleaner.py b/src/image_cleaner.py old mode 100644 new mode 100755 similarity index 100% rename from bin/image_cleaner.py rename to src/image_cleaner.py diff --git a/bin/requirements.txt b/src/requirements.txt similarity index 88% rename from bin/requirements.txt rename to src/requirements.txt index 472e89c..ab9322b 100644 --- a/bin/requirements.txt +++ b/src/requirements.txt @@ -2,5 +2,6 @@ avalon_framework colorama GPUtil psutil +pyyaml requests tqdm diff --git a/bin/upscaler.py b/src/upscaler.py old mode 100644 new mode 100755 similarity index 96% rename from bin/upscaler.py rename to src/upscaler.py index 6b7de79..f251406 --- a/bin/upscaler.py +++ b/src/upscaler.py @@ -4,7 +4,7 @@ Name: Video2X Upscaler Author: K4YT3X Date Created: December 10, 2018 -Last Modified: August 21, 2019 +Last Modified: October 6, 2019 Dev: SAT3LL @@ -51,12 +51,12 @@ class Upscaler: ArgumentError -- if argument is not valid """ - def __init__(self, input_video, output_video, method, waifu2x_settings, ffmpeg_settings): + def __init__(self, input_video, output_video, method, driver_settings, ffmpeg_settings): # mandatory arguments self.input_video = input_video self.output_video = output_video self.method = method - self.waifu2x_settings = waifu2x_settings + self.driver_settings = driver_settings self.ffmpeg_settings = ffmpeg_settings # optional arguments @@ -165,7 +165,7 @@ class Upscaler: # it's easier to do multi-threading with waifu2x_converter # the number of threads can be passed directly to waifu2x_converter if self.waifu2x_driver == 'waifu2x_converter': - w2 = Waifu2xConverter(self.waifu2x_settings, self.model_dir) + w2 = Waifu2xConverter(self.driver_settings, self.model_dir) progress_bar = threading.Thread(target=self._progress_bar, args=([self.extracted_frames],)) progress_bar.start() @@ -222,7 +222,7 @@ class Upscaler: # create a separate w2 instance for each thread if self.waifu2x_driver == 'waifu2x_caffe': - w2 = Waifu2xCaffe(copy.deepcopy(self.waifu2x_settings), self.method, self.model_dir, self.bit_depth) + w2 = Waifu2xCaffe(copy.deepcopy(self.driver_settings), self.method, self.model_dir, self.bit_depth) if self.scale_ratio: thread = threading.Thread(target=w2.upscale, args=(thread_info[0], @@ -244,7 +244,7 @@ class Upscaler: # if the driver being used is waifu2x_ncnn_vulkan elif self.waifu2x_driver == 'waifu2x_ncnn_vulkan': - w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.waifu2x_settings)) + w2 = Waifu2xNcnnVulkan(copy.deepcopy(self.driver_settings)) thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, @@ -253,7 +253,7 @@ class Upscaler: # if the driver being used is anime4k elif self.waifu2x_driver == 'anime4k': - w2 = Anime4k(copy.deepcopy(self.waifu2x_settings)) + w2 = Anime4k(copy.deepcopy(self.driver_settings)) thread = threading.Thread(target=w2.upscale, args=(thread_info[0], self.upscaled_frames, diff --git a/bin/video2x.json b/src/video2x.json similarity index 100% rename from bin/video2x.json rename to src/video2x.json diff --git a/bin/video2x.py b/src/video2x.py old mode 100644 new mode 100755 similarity index 87% rename from bin/video2x.py rename to src/video2x.py index 1b48673..a05cb1c --- a/bin/video2x.py +++ b/src/video2x.py @@ -11,12 +11,12 @@ __ __ _ _ ___ __ __ Name: Video2X Controller -Author: K4YT3X +Creator: K4YT3X Date Created: Feb 24, 2018 -Last Modified: August 29, 2019 +Last Modified: November 15, 2019 -Dev: BrianPetkovsek -Dev: SAT3LL +Editor: BrianPetkovsek +Editor: SAT3LL Licensed under the GNU General Public License Version 3 (GNU GPL v3), available at: https://www.gnu.org/licenses/gpl-3.0.txt @@ -50,7 +50,6 @@ from upscaler import Upscaler # built-in imports import argparse import contextlib -import json import pathlib import re import shutil @@ -58,13 +57,18 @@ import sys import tempfile import time import traceback +import yaml # third-party imports from avalon_framework import Avalon -import GPUtil import psutil -VERSION = '2.10.0' +# platform-specific imports +if sys.platform == 'win32': + import GPUtil + + +VERSION = '3.0.0' LEGAL_INFO = f'''Video2X Version: {VERSION} Author: K4YT3X @@ -87,7 +91,7 @@ SYS_MEM_PER_THREAD = 2.5 GPU_MEM_PER_THREAD = 3.5 -def process_arguments(): +def parse_arguments(): """Processes CLI arguments This function parses all arguments @@ -107,7 +111,7 @@ def process_arguments(): upscaler_options.add_argument('-d', '--driver', help='upscaling driver', action='store', default='waifu2x_caffe', choices=AVAILABLE_DRIVERS) upscaler_options.add_argument('-y', '--model_dir', type=pathlib.Path, help='directory containing model JSON files', action='store') upscaler_options.add_argument('-t', '--threads', help='number of threads to use for upscaling', action='store', type=int, default=1) - upscaler_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.json') + upscaler_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', default=pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml') upscaler_options.add_argument('-b', '--batch', help='enable batch mode (select all default values to questions)', action='store_true') # scaling options @@ -186,14 +190,18 @@ def check_memory(): Avalon.warning('Proceed with caution') -def read_config(config_file): - """ Reads configuration file +def read_config(config_file: pathlib.Path) -> dict: + """ read video2x configurations from config file - Returns a dictionary read by JSON. + Arguments: + config_file {pathlib.Path} -- video2x configuration file pathlib.Path + + Returns: + dict -- dictionary of video2x configuration """ - with open(config_file, 'r') as raw_config: - config = json.load(raw_config) - return config + + with open(config_file, 'r') as config: + return yaml.load(config, Loader=yaml.FullLoader) def absolutify_paths(config): @@ -248,13 +256,13 @@ if __name__ != '__main__': # print video2x logo print_logo() -# process CLI arguments -args = process_arguments() +# parse command line arguments +args = parse_arguments() # display version and lawful informaition if args.version: print(LEGAL_INFO) - exit(0) + sys.exit(0) # arguments sanity check if not args.input: @@ -277,54 +285,44 @@ if (args.width and not args.height) or (not args.width and args.height): raise ArgumentError('only one of width or height is specified') # check available memory if driver is waifu2x-based -if args.driver in ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan']: +if args.driver in ['waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan'] and sys.platform == 'win32': check_memory() # anime4k runs significantly faster with more threads if args.driver == 'anime4k' and args.threads <= 1: Avalon.warning('Anime4K runs significantly faster with more threads') - if Avalon.ask('Use more threads of Anime4K?', True): + if Avalon.ask('Use more threads of Anime4K?', default=True, batch=args.batch): while True: try: - threads = Avalon.gets('Amount of threads to use [5]: ') + threads = Avalon.gets('Amount of threads to use [5]: ', default=5, batch=args.batch) args.threads = int(threads) break except ValueError: if threads == '': args.threads = 5 break - else: - Avalon.error(f'{threads} is not a valid integer') + Avalon.error(f'{threads} is not a valid integer') -# read configurations from JSON +# read configurations from configuration file config = read_config(args.config) -config = absolutify_paths(config) + +# config = absolutify_paths(config) # load waifu2x configuration -if args.driver == 'waifu2x_caffe': - waifu2x_settings = config['waifu2x_caffe'] - if not pathlib.Path(waifu2x_settings['waifu2x_caffe_path']).is_file(): - Avalon.error('Specified waifu2x-caffe directory doesn\'t exist') +driver_settings = config[args.driver] + +# check if driver path exists +if not pathlib.Path(driver_settings['path']).is_file(): + if not pathlib.Path(f'{driver_settings["path"]}.exe').is_file(): + Avalon.error('Specified driver executable directory doesn\'t exist') Avalon.error('Please check the configuration file settings') - raise FileNotFoundError(waifu2x_settings['waifu2x_caffe_path']) -elif args.driver == 'waifu2x_converter': - waifu2x_settings = config['waifu2x_converter'] - if not pathlib.Path(waifu2x_settings['waifu2x_converter_path']).is_dir(): - Avalon.error('Specified waifu2x-converter-cpp directory doesn\'t exist') - Avalon.error('Please check the configuration file settings') - raise FileNotFoundError(waifu2x_settings['waifu2x_converter_path']) -elif args.driver == 'waifu2x_ncnn_vulkan': - waifu2x_settings = config['waifu2x_ncnn_vulkan'] - if not pathlib.Path(waifu2x_settings['waifu2x_ncnn_vulkan_path']).is_file(): - Avalon.error('Specified waifu2x_ncnn_vulkan directory doesn\'t exist') - Avalon.error('Please check the configuration file settings') - raise FileNotFoundError(waifu2x_settings['waifu2x_ncnn_vulkan_path']) -elif args.driver == 'anime4k': - waifu2x_settings = config['anime4k'] - if not pathlib.Path(waifu2x_settings['anime4k_path']).is_file(): - Avalon.error('Specified anime4k directory doesn\'t exist') - Avalon.error('Please check the configuration file settings') - raise FileNotFoundError(waifu2x_settings['anime4k_path']) + raise FileNotFoundError(driver_settings['path']) + +# if the driver is Anime4K, check if JDK 12 is installed +if args.driver == 'anime4k': + if not pathlib.Path('C:/Program Files/Java/jdk-12.0.2/bin/java.exe').is_file(): + Avalon.warning('Cannot find JDK 12 at its default installation location') + Avalon.warning('Please ensure you have JDK 12 installed and configured') # read FFmpeg configuration ffmpeg_settings = config['ffmpeg'] @@ -385,7 +383,7 @@ try: 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) + upscaler = Upscaler(input_video=args.input, output_video=args.output, method=args.method, driver_settings=driver_settings, ffmpeg_settings=ffmpeg_settings) # set optional options upscaler.waifu2x_driver = args.driver @@ -413,7 +411,7 @@ try: for input_video in [f for f in args.input.iterdir() if f.is_file()]: output_video = args.output / input_video.name - upscaler = Upscaler(input_video=input_video, output_video=output_video, method=args.method, waifu2x_settings=waifu2x_settings, ffmpeg_settings=ffmpeg_settings) + upscaler = Upscaler(input_video=input_video, output_video=output_video, method=args.method, driver_settings=driver_settings, ffmpeg_settings=ffmpeg_settings) # set optional options upscaler.waifu2x_driver = args.driver @@ -435,9 +433,11 @@ try: 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: Avalon.error('An exception has occurred') traceback.print_exc() + finally: # remove Video2X cache directory with contextlib.suppress(FileNotFoundError): diff --git a/src/video2x.yaml b/src/video2x.yaml new file mode 100644 index 0000000..f9bbf4b --- /dev/null +++ b/src/video2x.yaml @@ -0,0 +1,89 @@ +--- +waifu2x_caffe: + path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-caffe\waifu2x-caffe-cui + input_extention_list: + output_extention: + mode: noise_scale + scale_ratio: + scale_width: + scale_height: + noise_level: 3 + process: gpu + crop_size: 128 + output_quality: -1 + output_depth: 8 + batch_size: 1 + gpu: 0 + tta: 0 + input_path: + output_path: + model_dir: + crop_w: + crop_h: +waifu2x_converter: + path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-converter-cpp + output-format: + png-compression: + image-quality: + block-size: + disable-gpu: + force-OpenCL: + processor: + jobs: + model-dir: + scale-ratio: + noise-level: 3 + mode: noise-scale + silent: true + output: + input: +waifu2x_ncnn_vulkan: + path: C:\Users\K4YT3X\AppData\Local\video2x\waifu2x-ncnn-vulkan\waifu2x-ncnn-vulkan + v: + i: + o: + n: 2 + s: 2 + t: 400 + m: models-cunet + g: 0 + j: "1:2:2" +anime4k: + path: C:\Users\K4YT3X\AppData\Local\video2x\anime4k\Anime4K.jar + java_path: C:\Program Files\Java\jdk-12.0.2\bin\java.exe +ffmpeg: + ffmpeg_path: C:\Users\K4YT3X\AppData\Local\video2x\ffmpeg-latest-win64-static\bin + video_to_frames: + output_options: + "-qscale:v": + "-pix_fmt": rgba64be + "-hwaccel": auto + "-y": true + frames_to_video: + input_options: + "-qscale:v": + "-qscale:a": + "-f": image2 + output_options: + "-vcodec": libx264 + "-crf": 17 + "-b:v": + "-pix_fmt": + "-hwaccel": auto + "-y": true + migrating_tracks: + output_options: + "-map": + - 0:v? + - 1:a? + - 1:s? + - 1:d? + - 1:t? + "-c": copy + "-pix_fmt": + "-hwaccel": auto + "-y": true +video2x: + video2x_cache_directory: + image_format: png + preserve_frames: false diff --git a/bin/video2x_gui.py b/src/video2x_gui.py old mode 100644 new mode 100755 similarity index 79% rename from bin/video2x_gui.py rename to src/video2x_gui.py index 6648b9c..824d84a --- a/bin/video2x_gui.py +++ b/src/video2x_gui.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Name: Video2x GUI +Creator: Video2X GUI Author: K4YT3X Date Created: July 27, 2019 -Last Modified: August 17, 2019 +Last Modified: November 16, 2019 -Description: GUI for Video2X +Description: A simple GUI for Video2X made with tkinter. """ # local imports @@ -18,13 +18,16 @@ from tkinter import * from tkinter import messagebox from tkinter import ttk from tkinter.filedialog import * -import json import pathlib +import sys import tempfile import threading import time +import yaml -VERSION = '1.1.1' +VERSION = '1.1.2' + +VIDEO2X_CONFIG = pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml' LEGAL_INFO = f'''Video2X GUI Version: {VERSION} Author: K4YT3X @@ -48,6 +51,46 @@ AVAILABLE_DRIVERS = { IMAGE_FORMATS = {'PNG', 'JPG'} +DEMUXER_EXTENSIONS = {'3dostr', '4xm', 'aa', 'aac', 'ac3', 'acm', 'act', + 'adf', 'adp', 'ads', 'adx', 'aea', 'afc', 'aiff', 'aix', 'alaw', + 'alias_pix', 'alsa', 'amr', 'amrnb', 'amrwb', 'anm', 'apc', 'ape', + 'apng', 'aptx', 'aptx_hd', 'aqtitle', 'asf', 'asf_o', 'ass', 'ast', + 'au', 'avi', 'avisynth', 'avr', 'avs', 'avs2', 'bethsoftvid', 'bfi', + 'bfstm', 'bin', 'bink', 'bit', 'bmp_pipe', 'bmv', 'boa', 'brender_pix', + 'brstm', 'c93', 'caf', 'cavsvideo', 'cdg', 'cdxl', 'cine', 'codec2', + 'codec2raw', 'concat', 'dash', 'data', 'daud', 'dcstr', 'dds_pipe', + 'dfa', 'dirac', 'dnxhd', 'dpx_pipe', 'dsf', 'dsicin', 'dss', 'dts', + 'dtshd', 'dv', 'dvbsub', 'dvbtxt', 'dxa', 'ea', 'ea_cdata', 'eac3', + 'epaf', 'exr_pipe', 'f32be', 'f32le', 'f64be', 'f64le', 'fbdev', + 'ffmetadata', 'film_cpk', 'filmstrip', 'fits', 'flac', 'flic', 'flv', + 'frm', 'fsb', 'g722', 'g723_1', 'g726', 'g726le', 'g729', 'gdv', 'genh', + 'gif', 'gsm', 'gxf', 'h261', 'h263', 'h264', 'hevc', 'hls', 'applehttp', + 'hnm', 'ico', 'idcin', 'idf', 'iec61883', 'iff', 'ilbc', 'image2', + 'image2pipe', 'ingenient', 'ipmovie', 'ircam', 'iss', 'iv8', 'ivf', + 'ivr', 'j2k_pipe', 'jack', 'jacosub', 'jpeg_pipe', 'jpegls_pipe', + 'jv', 'kmsgrab', 'lavfi', 'libcdio', 'libdc1394', 'libgme', 'libopenmpt', + 'live_flv', 'lmlm4', 'loas', 'lrc', 'lvf', 'lxf', 'm4v', 'matroska', 'webm', + 'mgsts', 'microdvd', 'mjpeg', 'mjpeg_2000', 'mlp', 'mlv', 'mm', 'mmf', + 'mov', 'mp4', 'm4a', '3gp', '3g2', 'mj2', 'mp3', 'mpc', 'mpc8', 'mpeg', + 'mpegts', 'mpegtsraw', 'mpegvideo', 'mpjpeg', 'mpl2', 'mpsub', 'msf', + 'msnwctcp', 'mtaf', 'mtv', 'mulaw', 'musx', 'mv', 'mvi', 'mxf', 'mxg', + 'nc', 'nistsphere', 'nsp', 'nsv', 'nut', 'nuv', 'ogg', 'oma', 'openal', + 'oss', 'paf', 'pam_pipe', 'pbm_pipe', 'pcx_pipe', 'pgm_pipe', 'pgmyuv_pipe', + 'pictor_pipe', 'pjs', 'pmp', 'png_pipe', 'ppm_pipe', 'psd_pipe', 'psxstr', + 'pulse', 'pva', 'pvf', 'qcp', 'qdraw_pipe', 'r3d', 'rawvideo', 'realtext', + 'redspark', 'rl2', 'rm', 'roq', 'rpl', 'rsd', 'rso', 'rtp', 'rtsp', + 's16be', 's16le', 's24be', 's24le', 's32be', 's32le', 's337m', 's8', + 'sami', 'sap', 'sbc', 'sbg', 'scc', 'sdp', 'sdr2', 'sds', 'sdx', 'ser', + 'sgi_pipe', 'shn', 'siff', 'sln', 'smjpeg', 'smk', 'smush', 'sndio', + 'sol', 'sox', 'spdif', 'srt', 'stl', 'subviewer', 'subviewer1', 'sunrast_pipe', + 'sup', 'svag', 'svg_pipe', 'swf', 'tak', 'tedcaptions', 'thp', 'tiertexseq', + 'tiff_pipe', 'tmv', 'truehd', 'tta', 'tty', 'txd', 'ty', 'u16be', 'u16le', + 'u24be', 'u24le', 'u32be', 'u32le', 'u8', 'v210', 'v210x', 'vag', 'vc1', + 'vc1test', 'vidc', 'video4linux2', 'v4l2', 'vivo', 'vmd', 'vobsub', 'voc', + 'vpk', 'vplayer', 'vqf', 'w64', 'wav', 'wc3movie', 'webm_dash_manifest', + 'webp_pipe', 'webvtt', 'wsaud', 'wsd', 'wsvqa', 'wtv', 'wv', 'wve', 'x11grab', + 'xa', 'xbin', 'xmv', 'xpm_pipe', 'xvag', 'xwd_pipe', 'xwma', 'yop', 'yuv4mpegpipe'} + class Video2xGui(): @@ -228,7 +271,7 @@ class Video2xGui(): begin_time = time.time() # read configuration file - config = read_config('video2x.json') + config = read_config(VIDEO2X_CONFIG) config = absolutify_paths(config) input_file = pathlib.Path(self.input_file.get()) @@ -350,12 +393,18 @@ class Video2xGui(): def _select_input(self): self.input_file.set(askopenfilename(title='Select Input File')) + # remove input file extension + input_filename = str(self.input_file.get()) + for extension in DEMUXER_EXTENSIONS: + if input_filename.endswith(f'.{extension}'): + input_filename = input_filename[:-1 - len(extension)] + # try to set an output file name automatically - output_file = pathlib.Path(f'{self.input_file.get()}_output.mp4') + output_file = pathlib.Path(f'{input_filename}_output.mp4') output_file_id = 0 while output_file.is_file() and output_file_id <= 10: - output_file = pathlib.Path(f'{self.input_file.get()}_output_{output_file_id}.mp4') + output_file = pathlib.Path(f'{input_filename}_output_{output_file_id}.mp4') output_file_id += 1 if not output_file.exists(): @@ -368,10 +417,10 @@ class Video2xGui(): def read_config(config_file): """ Reads configuration file - Returns a dictionary read by JSON. + Returns a dictionary read by parsing Video2X config. """ with open(config_file, 'r') as raw_config: - config = json.load(raw_config) + config = yaml.load(raw_config, Loader=yaml.FullLoader) return config diff --git a/bin/video2x_setup.py b/src/video2x_setup.py old mode 100644 new mode 100755 similarity index 88% rename from bin/video2x_setup.py rename to src/video2x_setup.py index 893e2f3..dc36457 --- a/bin/video2x_setup.py +++ b/src/video2x_setup.py @@ -2,17 +2,12 @@ # -*- coding: utf-8 -*- """ Name: Video2X Setup Script -Author: K4YT3X -Author: BrianPetkovsek +Creator: K4YT3X Date Created: November 28, 2018 -Last Modified: August 20, 2019 +Last Modified: November 26, 2019 -Dev: SAT3LL - -Licensed under the GNU General Public License Version 3 (GNU GPL v3), - available at: https://www.gnu.org/licenses/gpl-3.0.txt - -(C) 2018-2019 K4YT3X +Editor: BrianPetkovsek +Editor: SAT3LL Description: This script helps installing all dependencies of video2x and generates a configuration for it. @@ -26,9 +21,9 @@ Installation Details: """ # built-in imports +from datetime import timedelta import argparse import contextlib -import json import os import pathlib import re @@ -36,6 +31,7 @@ import shutil import subprocess import sys import tempfile +import time import traceback import urllib import zipfile @@ -45,14 +41,15 @@ import zipfile # later in the script. # import requests -VERSION = '1.5.0' +VERSION = '1.6.0' # global static variables LOCALAPPDATA = pathlib.Path(os.getenv('localappdata')) +VIDEO2X_CONFIG = pathlib.Path(sys.argv[0]).parent.absolute() / 'video2x.yaml' DRIVER_OPTIONS = ['all', 'waifu2x_caffe', 'waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k'] -def process_arguments(): +def parse_arguments(): """Processes CLI arguments """ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -227,9 +224,11 @@ class Video2xSetup: def _generate_config(self): """ Generate video2x config """ - # Open current video2x.json file as template - with open('video2x.json', 'r') as template: - template_dict = json.load(template) + import yaml + + # open current video2x configuration file as template + with open(VIDEO2X_CONFIG, 'r') as template: + template_dict = yaml.load(template, Loader=yaml.FullLoader) template.close() # configure only the specified drivers @@ -251,10 +250,9 @@ class Video2xSetup: 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) - config.close() + # write configuration into file + with open(VIDEO2X_CONFIG, 'w') as config: + yaml.dump(template_dict, config) def download(url, save_path, chunk_size=4096): @@ -321,7 +319,20 @@ def pip_install(file): if __name__ == '__main__': try: - args = process_arguments() + # set default exit code + EXIT_CODE = 0 + + # get start time + start_time = time.time() + + # check platform + if sys.platform == 'win32': + print('This script is currently only compatible with Windows') + EXIT_CODE = 1 + sys.exit(1) + + # parse command line arguments + args = parse_arguments() print('Video2X Setup Script') print(f'Version: {VERSION}') @@ -335,8 +346,20 @@ if __name__ == '__main__': setup = Video2xSetup(args.driver, download_python_modules) setup.run() print('\nScript finished successfully') - except Exception: + except SystemExit: + pass + + # if PermissionError is raised + # user needs to run this with higher privilege + except PermissionError: + traceback.print_exc() + print('You might have insufficient privilege for this script to run') + print('Try running this script with Administrator privileges') + EXIT_CODE = 1 + + # for any exception in the script + except Exception: traceback.print_exc() print('An error has occurred') print('Video2X Automatic Setup has failed') @@ -348,4 +371,12 @@ if __name__ == '__main__': traceback.print_exc() print('An error occurred while trying to cleanup files') - exit(1) + EXIT_CODE = 1 + + # regardless if script finishes successfully or not + # print script execution summary + finally: + print('Script finished') + print(f'Time taken: {timedelta(seconds=round(time.time() - start_time))}') + input('Press [ENTER] to exit script') + sys.exit(EXIT_CODE) diff --git a/bin/waifu2x_caffe.py b/src/waifu2x_caffe.py old mode 100644 new mode 100755 similarity index 72% rename from bin/waifu2x_caffe.py rename to src/waifu2x_caffe.py index 8d613c8..179d7d5 --- a/bin/waifu2x_caffe.py +++ b/src/waifu2x_caffe.py @@ -4,7 +4,7 @@ Name: Waifu2x Caffe Driver Author: K4YT3X Date Created: Feb 24, 2018 -Last Modified: August 3, 2019 +Last Modified: October 6, 2019 Description: This class is a high-level wrapper for waifu2x-caffe. @@ -27,11 +27,11 @@ class Waifu2xCaffe: the upscale function. """ - def __init__(self, waifu2x_settings, process, model_dir, bit_depth): - self.waifu2x_settings = waifu2x_settings - self.waifu2x_settings['process'] = process - self.waifu2x_settings['model_dir'] = model_dir - self.waifu2x_settings['output_depth'] = bit_depth + def __init__(self, driver_settings, process, model_dir, bit_depth): + self.driver_settings = driver_settings + self.driver_settings['process'] = process + self.driver_settings['model_dir'] = model_dir + self.driver_settings['output_depth'] = bit_depth # arguments passed through command line overwrites config file values self.process = process @@ -50,16 +50,16 @@ class Waifu2xCaffe: try: # overwrite config file settings - self.waifu2x_settings['input_path'] = input_directory - self.waifu2x_settings['output_path'] = output_directory + self.driver_settings['input_path'] = input_directory + self.driver_settings['output_path'] = output_directory if scale_ratio: - self.waifu2x_settings['scale_ratio'] = scale_ratio + self.driver_settings['scale_ratio'] = scale_ratio elif scale_width and scale_height: - self.waifu2x_settings['scale_width'] = scale_width - self.waifu2x_settings['scale_height'] = scale_height + self.driver_settings['scale_width'] = scale_width + self.driver_settings['scale_height'] = scale_height - self.waifu2x_settings['output_extention'] = image_format + self.driver_settings['output_extention'] = image_format # print thread start message self.print_lock.acquire() @@ -68,14 +68,14 @@ class Waifu2xCaffe: # list to be executed # initialize the list with waifu2x binary path as the first element - execute = [str(self.waifu2x_settings['waifu2x_caffe_path'])] + execute = [str(self.driver_settings['path'])] - for key in self.waifu2x_settings.keys(): + for key in self.driver_settings.keys(): - value = self.waifu2x_settings[key] + value = self.driver_settings[key] # is executable key or null or None means that leave this option out (keep default) - if key == 'waifu2x_caffe_path' or value is None or value is False: + if key == 'path' or value is None or value is False: continue else: if len(key) == 1: diff --git a/bin/waifu2x_converter.py b/src/waifu2x_converter.py old mode 100644 new mode 100755 similarity index 72% rename from bin/waifu2x_converter.py rename to src/waifu2x_converter.py index 6f9bf9a..388b69d --- a/bin/waifu2x_converter.py +++ b/src/waifu2x_converter.py @@ -4,7 +4,7 @@ Name: Waifu2x Converter CPP Driver Author: K4YT3X Date Created: February 8, 2019 -Last Modified: August 3, 2019 +Last Modified: October 6, 2019 Description: This class is a high-level wrapper for waifu2x-converter-cpp. @@ -28,9 +28,9 @@ class Waifu2xConverter: the upscale function. """ - def __init__(self, waifu2x_settings, model_dir): - self.waifu2x_settings = waifu2x_settings - self.waifu2x_settings['model_dir'] = model_dir + def __init__(self, driver_settings, model_dir): + self.driver_settings = driver_settings + self.driver_settings['model_dir'] = model_dir self.print_lock = threading.Lock() def upscale(self, input_directory, output_directory, scale_ratio, jobs, image_format, upscaler_exceptions): @@ -46,16 +46,16 @@ class Waifu2xConverter: try: # overwrite config file settings - self.waifu2x_settings['input'] = input_directory - self.waifu2x_settings['output'] = output_directory - self.waifu2x_settings['scale-ratio'] = scale_ratio - self.waifu2x_settings['jobs'] = jobs - self.waifu2x_settings['output-format'] = image_format + self.driver_settings['input'] = input_directory + self.driver_settings['output'] = output_directory + self.driver_settings['scale-ratio'] = scale_ratio + self.driver_settings['jobs'] = jobs + self.driver_settings['output-format'] = image_format # 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'] = pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'models_rgb' + if self.driver_settings['model-dir'] is None: + self.driver_settings['model-dir'] = pathlib.Path(self.driver_settings['waifu2x_converter_path']) / 'models_rgb' # print thread start message self.print_lock.acquire() @@ -64,14 +64,14 @@ class Waifu2xConverter: # list to be executed # initialize the list with waifu2x binary path as the first element - execute = [str(pathlib.Path(self.waifu2x_settings['waifu2x_converter_path']) / 'waifu2x-converter-cpp.exe')] + execute = [str(pathlib.Path(self.driver_settings['path']) / 'waifu2x-converter-cpp.exe')] - for key in self.waifu2x_settings.keys(): + for key in self.driver_settings.keys(): - value = self.waifu2x_settings[key] + value = self.driver_settings[key] # the key doesn't need to be passed in this case - if key == 'waifu2x_converter_path': + if key == 'path': continue # null or None means that leave this option out (keep default) diff --git a/bin/waifu2x_ncnn_vulkan.py b/src/waifu2x_ncnn_vulkan.py old mode 100644 new mode 100755 similarity index 79% rename from bin/waifu2x_ncnn_vulkan.py rename to src/waifu2x_ncnn_vulkan.py index d74c128..a1dffe7 --- a/bin/waifu2x_ncnn_vulkan.py +++ b/src/waifu2x_ncnn_vulkan.py @@ -4,7 +4,7 @@ Name: Waifu2x NCNN Vulkan Driver Author: SAT3LL Date Created: June 26, 2019 -Last Modified: October 24, 2019 +Last Modified: November 15, 2019 Dev: K4YT3X @@ -30,14 +30,14 @@ class Waifu2xNcnnVulkan: the upscale function. """ - def __init__(self, waifu2x_settings): - self.waifu2x_settings = waifu2x_settings + def __init__(self, driver_settings): + self.driver_settings = driver_settings # arguments passed through command line overwrites config file values # waifu2x_ncnn_vulkan can't find its own model directory if its not in the current dir # so change to it - os.chdir(os.path.join(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'], '..')) + os.chdir(os.path.join(self.driver_settings['waifu2x_ncnn_vulkan_path'], '..')) self.print_lock = threading.Lock() @@ -52,9 +52,9 @@ class Waifu2xNcnnVulkan: try: # overwrite config file settings - self.waifu2x_settings['i'] = input_directory - self.waifu2x_settings['o'] = output_directory - self.waifu2x_settings['s'] = int(scale_ratio) + self.driver_settings['i'] = input_directory + self.driver_settings['o'] = output_directory + self.driver_settings['s'] = int(scale_ratio) # print thread start message self.print_lock.acquire() @@ -63,14 +63,14 @@ class Waifu2xNcnnVulkan: # list to be executed # initialize the list with waifu2x binary path as the first element - execute = [str(self.waifu2x_settings['waifu2x_ncnn_vulkan_path'])] + execute = [str(self.driver_settings['path'])] - for key in self.waifu2x_settings.keys(): + for key in self.driver_settings.keys(): - value = self.waifu2x_settings[key] + value = self.driver_settings[key] # is executable key or null or None means that leave this option out (keep default) - if key == 'waifu2x_ncnn_vulkan_path' or value is None or value is False: + if key == 'path' or value is None or value is False: continue else: if len(key) == 1: