diff --git a/.gitignore b/.gitignore index b10712b..c43db1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ +# compiled wrappers for testing purposes +video2x/wrappers + +# test videos +*.mp4 + +# vim +Session.vim + # QtCreator video2x_gui.pyproject.user @@ -107,4 +116,4 @@ venv.bak/ /site # mypy -.mypy_cache/ \ No newline at end of file +.mypy_cache/ diff --git a/video2x/README.md b/video2x/README.md deleted file mode 100644 index e979072..0000000 --- a/video2x/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Directory Listing - -- [**images**](images): image assets such as executable icons -- [**locale**](locale): language locale files (.po, .mo) -- [**pyinstaller**](pyinstaller): PyInstaller-specific files -- [**wrappers**](wrappers): binary Python wrappers/bindings -- [**bilogger.py**](bilogger.py): bidirectional logger -- [**build.ps1**](build.ps1): release building script -- [**exceptions.py**](exceptions.py): custom exceptions -- [**image_cleaner.py**](image_cleaner.py): upscaled images cleaner -- [**progress_monitor.py**](progress_monitor.py): upscaling progress monitor -- [**requirements.txt**](requirements.txt): Python requirements for pip -- [**upscaler.py**](upscaler.py): main upscaler class -- [**video2x.pot**](video2x.pot): project i18n POT translation file -- [**video2x.py**](video2x.py): command line interface -- [**video2x.yaml**](video2x.yaml): configuration file -- [**video2x_gui.py**](video2x_gui.py): graphical user interface -- [**video2x_gui.pyproject**](video2x_gui.pyproject): QtCreator project file -- [**video2x_gui.ui**](video2x_gui.ui): GUI UI file (defines GUI layout) -- [**video2x_setup.py**](video2x_setup.py): automatic setup script for Windows -- [**video2x_setup_ubuntu.sh**](video2x_setup_ubuntu.sh): automatic setup script for Ubuntu diff --git a/video2x/__init__.py b/video2x/__init__.py new file mode 100755 index 0000000..175d984 --- /dev/null +++ b/video2x/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Name: Video2X package init +Author: K4YT3X +Date Created: July 3, 2021 +Last Modified: July 3, 2021 +""" +from .video2x import Video2X +from .upscaler import Upscaler +from .interpolator import Interpolator diff --git a/video2x/__main__.py b/video2x/__main__.py new file mode 100755 index 0000000..56915bc --- /dev/null +++ b/video2x/__main__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Name: Video2X package main +Author: K4YT3X +Date Created: July 3, 2021 +Last Modified: July 3, 2021 +""" + +from .video2x import main + +if __name__ == "__main__": + main() diff --git a/video2x/bilogger.py b/video2x/bilogger.py deleted file mode 100755 index bbc73d1..0000000 --- a/video2x/bilogger.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Creator: Video2X Bidirectional Logger -Author: K4YT3X -Date Created: June 4, 2020 -Last Modified: December 13, 2020 -""" - -# built-in imports -import io - - -class BiLogger(io.TextIOWrapper): - """A bidirectional logger that both prints the output - and log all output to file. - - Original code from: https://stackoverflow.com/a/14906787 - """ - - def __init__(self, terminal: io.TextIOWrapper, log_file: io.BufferedRandom): - """initialize BiLogger - - Args: - terminal (_io.TextIOWrapper): original terminal IO wrapper - logfile (_io.BufferedRandom): log file wrapper - """ - self.terminal = terminal - self.log_file = log_file - self.fileno = self.log_file.fileno - - def write(self, message: str): - """write message to original terminal output and log file - - Args: - message (str): message to write - """ - self.terminal.write(message) - self.terminal.flush() - self.log_file.write(message) - self.log_file.flush() - - def flush(self): - """flush logger (for compability only)""" - pass diff --git a/video2x/build.ps1 b/video2x/build.ps1 deleted file mode 100755 index ed2a6a0..0000000 --- a/video2x/build.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -<# -Name: Video2X Build Script -Creator: K4YT3X -Date Created: May 6, 2020 -Last Modified: May 13, 2020 - -Description: A PowerShell script that will build Video2X -executable (PE) releases automatically using PyInstaller. -This script is currently only tuned for K4YT3X's environment. - -To start a PowerShell session with execution policy bypass -powershell -ExecutionPolicy Bypass -#> - -if ($args.count -ne 1) { - Write-Host -ForegroundColor White "Usage:`n .\build.ps1 VIDEO2X_VERSION" - Exit -} - -# version number -$SCRIPT_VERSION = "1.0.1" -$VIDEO2X_VERSION = $args[0] - -Write-Host -ForegroundColor White "Video2X Building Script Version $($SCRIPT_VERSION) -Starting to build Video2X release packages -Building Video2X release $($VIDEO2X_VERSION)" - -# build Video2X CLI -Write-Host -ForegroundColor White "`nBuilding Video2X CLI" -pyinstaller --noconfirm --log-level=WARN ` - --onefile ` - --add-data="locale;locale" ` - --add-data="wrappers;wrappers" ` - --icon="images\video2x.ico" ` - video2x.py - -# build Video2X GUI -Write-Host -ForegroundColor White "`nBuilding Video2X GUI" -pyinstaller --noconfirm --log-level=WARN ` - --onefile ` - --add-data="images;images" ` - --add-data="locale;locale" ` - --add-data="video2x_gui.ui;." ` - --add-data="wrappers;wrappers" ` - --icon="images\video2x.ico" ` - video2x_gui.py - -# build setup script -Write-Host -ForegroundColor White "`nBuilding Video2X setup script" -pyinstaller --noconfirm --log-level=WARN ` - --onefile ` - --additional-hooks-dir "pyinstaller\hooks" ` - --add-data="locale;locale" ` - --add-data="pyinstaller\7z1900-extra;7z" ` - --icon="images\video2x.ico" ` - video2x_setup.py - -# remove old builds if found -if (Test-Path "$($VIDEO2X_VERSION)" -PathType any) { - Remove-Item -path "$($VIDEO2X_VERSION)" -recurse -} - -# create build directory -New-Item "$($VIDEO2X_VERSION)" -ItemType Directory - -# copy files into corresponding builds -# full edition -Write-Host -ForegroundColor White "`nCreating full package" -New-Item "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-full" -ItemType Directory -Copy-Item "dist\video2x.exe" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-full\" -Copy-Item "dist\video2x_gui.exe" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-full\" -Copy-Item -Path "$env:LOCALAPPDATA\video2x" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-full\dependencies" -Recurse - -# overwrite paths to relative paths -(Get-Content "video2x.yaml").replace("%LOCALAPPDATA%\video2x", "dependencies") | Set-Content "video2x.yaml.relative" -Move-Item "video2x.yaml.relative" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-full\video2x.yaml" - -# light edition -Write-Host -ForegroundColor White "`nCreating light package" -New-Item "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light" -ItemType Directory -Copy-Item "dist\video2x.exe" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light\" -Copy-Item "dist\video2x_gui.exe" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light\" -Copy-Item "dist\video2x_setup.exe" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light\" -Copy-Item "video2x.yaml" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light\" -Copy-Item "requirements.txt" -Destination "$($VIDEO2X_VERSION)\video2x-$($VIDEO2X_VERSION)-win32-light\" - -# clean up temporary files -Write-Host -ForegroundColor White "`nDeleting temporary files" -$pathsToRemove = "__pycache__", "build", "dist", "*.spec" - -foreach ($path in $pathsToRemove) { - Write-Host "Removing path: $($path)" - Remove-Item -path $path -recurse -} - -Write-Host -ForegroundColor White "`nBuild script finished" diff --git a/video2x/decoder.py b/video2x/decoder.py new file mode 100755 index 0000000..1941548 --- /dev/null +++ b/video2x/decoder.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Name: Video Decoder +Author: K4YT3X +Date Created: June 17, 2021 +Last Modified: June 17, 2021 +""" + +# built-in imports +import os +import pathlib +import queue +import subprocess +import threading + +# third-party imports +from loguru import logger +from PIL import Image +import ffmpeg + + +# map Loguru log levels to FFmpeg log levels +LOGURU_FFMPEG_LOGLEVELS = { + "trace": "trace", + "debug": "debug", + "info": "info", + "success": "info", + "warning": "warning", + "error": "error", + "critical": "fatal", +} + + +class VideoDecoder(threading.Thread): + def __init__( + self, + input_path: pathlib.Path, + input_width: int, + input_height: int, + frame_rate: float, + processing_queue: queue.Queue, + processing_settings: tuple, + ignore_max_image_pixels=True, + ): + threading.Thread.__init__(self) + self.running = False + self.input_path = input_path + self.input_width = input_width + self.input_height = input_height + self.processing_queue = processing_queue + self.processing_settings = processing_settings + + # this disables the "possible DDoS" warning + if ignore_max_image_pixels: + Image.MAX_IMAGE_PIXELS = None + + self.exception = None + self.decoder = subprocess.Popen( + ffmpeg.compile( + ffmpeg.input(input_path, r=frame_rate)["v"] + .output("pipe:1", format="rawvideo", pix_fmt="rgb24", vsync="1") + .global_args("-hide_banner") + .global_args("-nostats") + .global_args( + "-loglevel", + LOGURU_FFMPEG_LOGLEVELS.get( + os.environ.get("LOGURU_LEVEL", "INFO").lower() + ), + ), + overwrite_output=True, + ), + stdout=subprocess.PIPE, + # stderr=subprocess.DEVNULL, + ) + + def run(self): + self.running = True + + # the index of the frame + frame_index = 0 + + # create placeholder for previous frame + # used in interpolate mode + previous_image = None + + # continue running until an exception occurs + # or all frames have been decoded + while self.running: + try: + buffer = self.decoder.stdout.read( + 3 * self.input_width * self.input_height + ) + + # source depleted (decoding finished) + # after the last frame has been decoded + # read will return nothing + if len(buffer) == 0: + logger.debug("Decoding queue depleted") + break + + # convert raw bytes into image object + image = Image.frombytes( + "RGB", (self.input_width, self.input_height), buffer + ) + + # if this is the first frame + # there wouldn't be a "previous image" + if previous_image is not None: + self.processing_queue.put( + ( + frame_index, + (previous_image, image), + self.processing_settings, + ) + ) + previous_image = image + + frame_index += 1 + + # most likely "not enough image data" + except ValueError as e: + logger.exception(e) + break + + # send exceptions into the client connection pipe + except Exception as e: + self.exception = e + logger.exception(e) + break + + # ensure the decoder has exited + self.decoder.wait() + logger.debug("Decoder thread exiting") + + self.running = False + return super().run() + + def stop(self): + self.running = False diff --git a/video2x/encoder.py b/video2x/encoder.py new file mode 100755 index 0000000..500a2d1 --- /dev/null +++ b/video2x/encoder.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Name: Video Encoder +Author: K4YT3X +Date Created: June 17, 2021 +Last Modified: June 30, 2021 +""" + +# built-in imports +import multiprocessing +import multiprocessing.managers +import multiprocessing.sharedctypes +import os +import pathlib +import signal +import subprocess +import threading +import time + +# third-party imports +from loguru import logger +import ffmpeg + + +# map Loguru log levels to FFmpeg log levels +LOGURU_FFMPEG_LOGLEVELS = { + "trace": "trace", + "debug": "debug", + "info": "info", + "success": "info", + "warning": "warning", + "error": "error", + "critical": "fatal", +} + + +class VideoEncoder(threading.Thread): + def __init__( + self, + input_path: pathlib.Path, + frame_rate: float, + output_path: pathlib.Path, + output_width: int, + output_height: int, + total_frames: int, + processed_frames: multiprocessing.managers.ListProxy, + processed: multiprocessing.sharedctypes.Synchronized, + ): + threading.Thread.__init__(self) + self.running = False + self.input_path = input_path + self.output_path = output_path + self.total_frames = total_frames + self.processed_frames = processed_frames + self.processed = processed + + self.original = ffmpeg.input(input_path) + + # define frames as input + frames = ffmpeg.input( + "pipe:0", + format="rawvideo", + pix_fmt="rgb24", + vsync="1", + s=f"{output_width}x{output_height}", + r=frame_rate, + ) + + # map additional streams from original file + """ + additional_streams = [ + # self.original["v?"], + self.original["a?"], + self.original["s?"], + self.original["d?"], + self.original["t?"], + ] + """ + + # run FFmpeg and produce final output + self.encoder = subprocess.Popen( + ffmpeg.compile( + ffmpeg.output( + frames, + str(self.output_path), + pix_fmt="yuv420p", + vcodec="libx264", + acodec="copy", + r=frame_rate, + crf=17, + vsync="1", + # map_metadata=1, + # metadata="comment=Upscaled with Video2X", + ) + .global_args("-hide_banner") + .global_args("-nostats") + .global_args( + "-loglevel", + LOGURU_FFMPEG_LOGLEVELS.get( + os.environ.get("LOGURU_LEVEL", "INFO").lower() + ), + ), + overwrite_output=True, + ), + stdin=subprocess.PIPE, + # stdout=subprocess.DEVNULL, + # stderr=subprocess.DEVNULL, + ) + + def run(self): + self.running = True + frame_index = 0 + while self.running and frame_index < self.total_frames: + try: + image = self.processed_frames[frame_index] + if image is None: + time.sleep(0.1) + continue + + self.encoder.stdin.write(image.tobytes()) + + with self.processed.get_lock(): + self.processed.value += 1 + + frame_index += 1 + + # send exceptions into the client connection pipe + except Exception as e: + logger.exception(e) + break + + # flush the remaining data in STDIN and close PIPE + logger.debug("Encoding queue depleted") + self.encoder.stdin.flush() + self.encoder.stdin.close() + + # send SIGINT (2) to FFmpeg + # this instructs it to finalize and exit + self.encoder.send_signal(signal.SIGINT) + + # wait for process to terminate + self.encoder.wait() + logger.debug("Encoder thread exiting") + + self.running = False + return super().run() + + def stop(self): + self.running = False diff --git a/video2x/exceptions.py b/video2x/exceptions.py deleted file mode 100755 index ad6346d..0000000 --- a/video2x/exceptions.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X Exceptions -Dev: K4YT3X -Date Created: December 13, 2018 -Last Modified: July 27, 2019 -""" - - -class ArgumentError(Exception): - def __init__(self, message): - super().__init__(message) - - -class StreamNotFoundError(Exception): - def __init__(self, message): - super().__init__(message) - - -class UnrecognizedDriverError(Exception): - def __init__(self, message): - super().__init__(message) - - -class UnsupportedPixelError(Exception): - def __init__(self, message): - super().__init__(message) diff --git a/video2x/generate_pot.ps1 b/video2x/generate_pot.ps1 deleted file mode 100644 index 1d62e98..0000000 --- a/video2x/generate_pot.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -<# -Name: Video2X Generate POT Script -Creator: K4YT3X -Date Created: September 12, 2020 -Last Modified: September 12, 2020 - -Description: A PowerShell script that uses Python's pygettext -script to generate the POT file for translations. - -To start a PowerShell session with execution policy bypass -powershell -ExecutionPolicy Bypass -#> - -python $env:LOCALAPPDATA\Programs\Python\Python38\Tools\i18n\pygettext.py -d video2x *.py wrappers\*.py diff --git a/video2x/image_cleaner.py b/video2x/image_cleaner.py deleted file mode 100755 index 087ce4f..0000000 --- a/video2x/image_cleaner.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X Image Cleaner -Author: BrianPetkovsek -Date Created: March 24, 2019 -Last Modified: July 27, 2019 - -Editor: K4YT3X -Last Modified: March 23, 2020 - -Editor: 28598519a -Last Modified: March 23, 2020 - -Description: This class is to remove the extracted frames -that have already been upscaled. -""" - -# built-in imports -import threading -import time - - -class ImageCleaner(threading.Thread): - """Video2X Image Cleaner - - This class creates an object that keeps track of extracted - frames that has already been upscaled and are not needed - anymore. It then deletes them to save disk space. - - Extends: - threading.Thread - """ - - def __init__(self, input_directory, output_directory, threads): - threading.Thread.__init__(self) - self.input_directory = input_directory - self.output_directory = output_directory - self.threads = threads - self.running = False - - def run(self): - """Run image cleaner""" - self.running = True - - while self.running: - self.remove_upscaled_frames() - time.sleep(1) - - def stop(self): - """Stop the image cleaner""" - self.running = False - self.join() - - def remove_upscaled_frames(self): - """remove frames that have already been upscaled - - This method compares the files in the extracted frames - 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.name for f in self.output_directory.iterdir() if f.is_file()] - - # compare and remove frames downscaled images that finished being upscaled - # within each thread's extracted frames directory - for thread_id in range(self.threads): - dir_path = self.input_directory / str(thread_id) - - # for each file within all the directories - for file in dir_path.iterdir(): - # if file also exists in the output directory, then the file - # has already been processed, thus not needed anymore - if file.is_file() and file.name in output_frames: - file.unlink() - output_frames.remove(file.name) diff --git a/video2x/images/video2x-banner.png b/video2x/images/video2x-banner.png deleted file mode 100644 index e9c785b..0000000 Binary files a/video2x/images/video2x-banner.png and /dev/null differ diff --git a/video2x/images/video2x-banner.psd b/video2x/images/video2x-banner.psd deleted file mode 100644 index a19f2cd..0000000 Binary files a/video2x/images/video2x-banner.psd and /dev/null differ diff --git a/video2x/images/video2x-github-social-preview.png b/video2x/images/video2x-github-social-preview.png deleted file mode 100644 index 1468629..0000000 Binary files a/video2x/images/video2x-github-social-preview.png and /dev/null differ diff --git a/video2x/images/video2x-github-social-preview.psd b/video2x/images/video2x-github-social-preview.psd deleted file mode 100644 index 61546ae..0000000 Binary files a/video2x/images/video2x-github-social-preview.psd and /dev/null differ diff --git a/video2x/images/video2x-icon.psd b/video2x/images/video2x-icon.psd deleted file mode 100644 index 6c6c829..0000000 Binary files a/video2x/images/video2x-icon.psd and /dev/null differ diff --git a/video2x/images/video2x-logo.psd b/video2x/images/video2x-logo.psd deleted file mode 100644 index 4c4efb0..0000000 Binary files a/video2x/images/video2x-logo.psd and /dev/null differ diff --git a/video2x/images/video2x-workflow-en_US.png b/video2x/images/video2x-workflow-en_US.png deleted file mode 100644 index aacb3ed..0000000 Binary files a/video2x/images/video2x-workflow-en_US.png and /dev/null differ diff --git a/video2x/images/video2x-workflow-en_US.psd b/video2x/images/video2x-workflow-en_US.psd deleted file mode 100644 index bcca0b0..0000000 Binary files a/video2x/images/video2x-workflow-en_US.psd and /dev/null differ diff --git a/video2x/images/video2x-workflow-zh_CN.png b/video2x/images/video2x-workflow-zh_CN.png deleted file mode 100644 index 6140655..0000000 Binary files a/video2x/images/video2x-workflow-zh_CN.png and /dev/null differ diff --git a/video2x/images/video2x-workflow-zh_CN.psd b/video2x/images/video2x-workflow-zh_CN.psd deleted file mode 100644 index 8ac0230..0000000 Binary files a/video2x/images/video2x-workflow-zh_CN.psd and /dev/null differ diff --git a/video2x/images/video2x.ico b/video2x/images/video2x.ico deleted file mode 100644 index 0a6ac53..0000000 Binary files a/video2x/images/video2x.ico and /dev/null differ diff --git a/video2x/images/video2x.png b/video2x/images/video2x.png deleted file mode 100644 index bb08875..0000000 Binary files a/video2x/images/video2x.png and /dev/null differ diff --git a/video2x/interpolator.py b/video2x/interpolator.py new file mode 100755 index 0000000..41232b3 --- /dev/null +++ b/video2x/interpolator.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Name: Interpolator +Author: K4YT3X +Date Created: May 27, 2021 +Last Modified: July 3, 2021 +""" + +# local imports +from .wrappers.rife_ncnn_vulkan_python.rife_ncnn_vulkan import RIFE + +# built-in imports +import multiprocessing +import multiprocessing.managers +import multiprocessing.sharedctypes +import queue +import signal +import time + +# third-party imports +from PIL import ImageChops, ImageStat +from loguru import logger + + +DRIVER_CLASSES = {"rife": RIFE} + + +class Interpolator(multiprocessing.Process): + def __init__( + self, + processing_queue: multiprocessing.Queue, + processed_frames: multiprocessing.managers.ListProxy, + ): + multiprocessing.Process.__init__(self) + self.running = False + self.processing_queue = processing_queue + self.processed_frames = processed_frames + + signal.signal(signal.SIGTERM, self._stop) + + def run(self): + self.running = True + logger.info(f"Interpolator process {self.name} initiating") + driver_objects = {} + while self.running: + try: + try: + # get new job from queue + ( + frame_index, + (image0, image1), + (difference_threshold, driver), + ) = self.processing_queue.get(False) + except queue.Empty: + time.sleep(0.1) + continue + + difference = ImageChops.difference(image0, image1) + difference_stat = ImageStat.Stat(difference) + difference_ratio = ( + sum(difference_stat.mean) / (len(difference_stat.mean) * 255) * 100 + ) + + # if the difference is lower than threshold + # process the interpolation + if difference_ratio < difference_threshold: + + # select a driver object with the required settings + # create a new object if none are available + driver_object = driver_objects.get(driver) + if driver_object is None: + driver_object = DRIVER_CLASSES[driver](0) + driver_objects[driver] = driver_object + interpolated_image = driver_object.process(image0, image1) + + # if the difference is greater than threshold + # there's a change in camera angle, ignore + else: + interpolated_image = image0 + + if frame_index == 1: + self.processed_frames[0] = image0 + self.processed_frames[frame_index * 2 - 1] = interpolated_image + self.processed_frames[frame_index * 2] = image1 + + # send exceptions into the client connection pipe + except (SystemExit, KeyboardInterrupt): + break + + except Exception as e: + logger.exception(e) + break + + logger.info(f"Interpolator process {self.name} terminating") + self.running = False + return super().run() + + def _stop(self, signal_number, frame): + self.running = False diff --git a/video2x/locale/zh_CN/LC_MESSAGES/video2x.mo b/video2x/locale/zh_CN/LC_MESSAGES/video2x.mo deleted file mode 100644 index 476548e..0000000 Binary files a/video2x/locale/zh_CN/LC_MESSAGES/video2x.mo and /dev/null differ diff --git a/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.mo b/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.mo deleted file mode 100644 index 476548e..0000000 Binary files a/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.mo and /dev/null differ diff --git a/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.po b/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.po deleted file mode 100644 index 4ccd99e..0000000 --- a/video2x/locale/zh_CN/LC_MESSAGES/zh_CN.po +++ /dev/null @@ -1,431 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: 2021-01-23 16:45-0500\n" -"PO-Revision-Date: 2021-01-23 16:47-0500\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: zh_CN\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" -"X-Generator: Poedit 2.4.1\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#: progress_monitor.py:40 -msgid "Processing: {} (pass {}/{})" -msgstr "正在处理:{}(进度)" - -#: upscaler.py:160 -msgid "Specified or default cache directory is a file/link" -msgstr "指定或默认的缓存目录是文件/链接" - -#: upscaler.py:167 -msgid "Creating cache directory {}" -msgstr "创建缓存目录 {}" - -#: upscaler.py:174 -msgid "Unable to create {}" -msgstr "无法创建 {}" - -#: upscaler.py:183 -msgid "Extracted frames are being saved to: {}" -msgstr "提取的帧将被保存到:{}" - -#: upscaler.py:189 -msgid "Upscaled frames are being saved to: {}" -msgstr "已放大的帧将被保存到:{}" - -#: upscaler.py:203 -msgid "Cleaning up cache directory: {}" -msgstr "清理缓存目录:{}" - -#: upscaler.py:208 -msgid "Unable to delete: {}" -msgstr "无法删除:{}" - -#: upscaler.py:214 upscaler.py:239 upscaler.py:250 -msgid "Input and output path type mismatch" -msgstr "输入和输出路径类型不匹配" - -#: upscaler.py:215 -msgid "Input is multiple files but output is not directory" -msgstr "输入是多个文件,但输出不是目录" - -#: upscaler.py:220 -msgid "Input path {} is neither a file nor a directory" -msgstr "输入路径 {} 既不是文件也不是目录" - -#: upscaler.py:230 upscaler.py:256 -msgid "Input directory and output directory cannot be the same" -msgstr "输入目录和输出目录不能相同" - -#: upscaler.py:240 -msgid "Input is single file but output is directory" -msgstr "所选的输入路径是单个文件,但输出路径是目录" - -#: upscaler.py:243 -msgid "No suffix found in output file path" -msgstr "在输出文件路径中未找到后缀" - -#: upscaler.py:244 -msgid "Suffix must be specified" -msgstr "必须指定文件后缀" - -#: upscaler.py:251 -msgid "Input is directory but output is existing single file" -msgstr "输入是目录,但输出是现有的单个文件" - -#: upscaler.py:264 -msgid "Input path is neither a file nor a directory" -msgstr "输入路径既不是文件也不是目录" - -#: upscaler.py:280 -msgid "FFmpeg or FFprobe cannot be found under the specified path" -msgstr "在指定的路径下找不到 FFmpeg 或 FFprobe" - -#: upscaler.py:282 upscaler.py:295 -msgid "Please check the configuration file settings" -msgstr "请检查配置文件设置" - -#: upscaler.py:294 -msgid "Specified driver executable directory doesn't exist" -msgstr "指定驱动的可执行文件不存在" - -#: upscaler.py:323 -msgid "Failed to parse driver argument: {}" -msgstr "解析驱动程序参数失败:{}" - -#: upscaler.py:346 -msgid "Unrecognized driver: {}" -msgstr "无法识别的驱动名称:{}" - -#: upscaler.py:397 -msgid "Starting progress monitor" -msgstr "启动进度监视器" - -#: upscaler.py:402 -msgid "Starting upscaled image cleaner" -msgstr "启动已放大图像清理程序" - -#: upscaler.py:413 upscaler.py:432 -msgid "Killing progress monitor" -msgstr "终结进度监视器" - -#: upscaler.py:416 upscaler.py:435 -msgid "Killing upscaled image cleaner" -msgstr "终结已放大图像清理程序" - -#: upscaler.py:439 -msgid "Terminating all processes" -msgstr "正在终止所有进程" - -#: upscaler.py:445 -msgid "Main process waiting for subprocesses to exit" -msgstr "主进程开始等待子进程结束" - -#: upscaler.py:465 upscaler.py:475 -msgid "Subprocess {} exited with code {}" -msgstr "子进程 {} 结束,返回码 {}" - -#: upscaler.py:484 -msgid "Stop signal received" -msgstr "收到停止信号" - -#: upscaler.py:489 -msgid "Subprocess execution ran into an error" -msgstr "子进程执行遇到错误" - -#: upscaler.py:523 -msgid "Loading files into processing queue" -msgstr "正在将文件添加到处理队列中" - -#: upscaler.py:524 -msgid "Input path(s): {}" -msgstr "输入路径:{}" - -#: upscaler.py:576 -msgid "File MIME type: {}" -msgstr "文件 MIME 类型:{}" - -#: upscaler.py:601 -msgid "File {} ({}) neither an image nor a video" -msgstr "文件 {} ({}) 既不是图片也不是视频" - -#: upscaler.py:605 -msgid "Skipping this file" -msgstr "将跳过此文件" - -#: upscaler.py:640 -msgid "Loaded files into processing queue" -msgstr "文件已添加到处理队列" - -#: upscaler.py:643 -msgid "Input file: {}" -msgstr "输入文件:{}" - -#: upscaler.py:661 -msgid "Reading file information" -msgstr "正在读取视频信息" - -#: upscaler.py:670 -msgid "Starting upscaling image" -msgstr "开始放大图像" - -#: upscaler.py:684 -msgid "Starting upscaling video/GIF" -msgstr "开始放大视频/GIF" - -#: upscaler.py:695 -msgid "Aborting: No video stream found" -msgstr "程序中止:文件中未找到视频流" - -#: upscaler.py:708 -msgid "Getting total number of frames in the file" -msgstr "正在获取文件中的总帧数" - -#: upscaler.py:723 -msgid "Calculating scaling parameters" -msgstr "正在计算缩放参数" - -#: upscaler.py:797 -msgid "Framerate: {}" -msgstr "帧率:{}" - -#: upscaler.py:798 -msgid "Width: {}" -msgstr "宽:{}" - -#: upscaler.py:799 -msgid "Height: {}" -msgstr "高:{}" - -#: upscaler.py:801 -msgid "Total number of frames: {}" -msgstr "总帧数:{}" - -#: upscaler.py:803 -msgid "Output width: {}" -msgstr "输出宽度:{}" - -#: upscaler.py:804 -msgid "Output height: {}" -msgstr "输出高度:{}" - -#: upscaler.py:805 -msgid "Required scale ratio: {}" -msgstr "需要的缩放比例:{}" - -#: upscaler.py:807 -msgid "Upscaling jobs queue: {}" -msgstr "放大工作队列:{}" - -#: upscaler.py:834 -msgid "Unsupported pixel format: {}" -msgstr "不支持的像素格式:{}" - -#: upscaler.py:843 -msgid "Starting to upscale extracted frames" -msgstr "开始对提取的帧进行放大" - -#: upscaler.py:860 -msgid "Upscaling completed" -msgstr "放大完成" - -#: upscaler.py:862 -msgid "Average processing speed: {} seconds per frame" -msgstr "平均处理速度:{} 秒每帧" - -#: upscaler.py:868 -msgid "Lanczos downscaling frames" -msgstr "正在使用 Lanczos 算法缩放图像" - -#: upscaler.py:880 -msgid "Downscaling" -msgstr "正在缩放图像" - -#: upscaler.py:905 -msgid "Exporting image" -msgstr "正在导出图像" - -#: upscaler.py:918 -msgid "Converting extracted frames into GIF image" -msgstr "正在将提取的帧转换为 GIF" - -#: upscaler.py:931 upscaler.py:944 -msgid "Conversion completed" -msgstr "转换已完成" - -#: upscaler.py:936 -msgid "Converting extracted frames into video" -msgstr "正在将提取的帧转换为视频" - -#: upscaler.py:949 -msgid "Migrating audio, subtitles and other streams to upscaled video" -msgstr "正在将音频、字幕和其他流迁移到放大后的视频" - -#: upscaler.py:966 -msgid "Failed to migrate streams" -msgstr "迁移流失败" - -#: upscaler.py:968 -msgid "Trying to output video without additional streams" -msgstr "正在尝试输出不含其他流的视频" - -#: upscaler.py:991 -msgid "Output video file exists" -msgstr "输出目标文件已存在" - -#: upscaler.py:1000 -msgid "Created temporary directory to contain file" -msgstr "为文件创建了临时目录" - -#: upscaler.py:1005 -msgid "Writing intermediate file to: {}" -msgstr "正在将中间视频文件写入至:{}" - -#: video2x.py:89 -msgid "" -"Video2X CLI Version: {}\n" -"Upscaler Version: {}\n" -"Author: K4YT3X\n" -"License: GNU GPL v3\n" -"Github Page: https://github.com/k4yt3x/video2x\n" -"Contact: k4yt3x@k4yt3x.com" -msgstr "" -"Video2X 版本: {}\n" -"放大组件版本:{}\n" -"作者: K4YT3X\n" -"开源许可: GNU GPL v3\n" -"GitHub 主页:https://github.com/k4yt3x/video2x\n" -"联系方式:k4yt3x@k4yt3x.com" - -#: video2x.py:117 -msgid "Video2X Options" -msgstr "Video2X 选项" - -#: video2x.py:120 -msgid "show this help message and exit" -msgstr "显示此帮助消息并退出" - -#: video2x.py:133 -msgid "source video file/directory" -msgstr "源视频文件/目录" - -#: video2x.py:141 -msgid "output video file/directory" -msgstr "输出视频文件/目录" - -#: video2x.py:149 -msgid "Video2X config file path" -msgstr "Video2X 配置文件路径" - -#: video2x.py:154 -msgid "log file path" -msgstr "日志文件路径" - -#: video2x.py:159 -msgid "display version, lawful information and exit" -msgstr "显示版本和法律信息并退出" - -#: video2x.py:164 -msgid "Upscaling Options" -msgstr "视频放大选项" - -#: video2x.py:167 -msgid "scaling ratio" -msgstr "缩放比" - -#: video2x.py:171 -msgid "output width" -msgstr "输出宽度" - -#: video2x.py:175 -msgid "output height" -msgstr "输出高度" - -#: video2x.py:181 -msgid "upscaling driver" -msgstr "视频放大驱动" - -#: video2x.py:189 -msgid "number of processes to use for upscaling" -msgstr "并发进程数" - -#: video2x.py:197 -msgid "preserve extracted and upscaled frames" -msgstr "保留提取的和放大的帧" - -#: video2x.py:241 -msgid "This file cannot be imported" -msgstr "此文件无法被当作模块导入" - -#: video2x.py:259 -msgid "Specify either scaling ratio or scaling resolution, not both" -msgstr "您只能指定缩放比或输出分辨率两者之一" - -#: video2x.py:265 -msgid "Either scaling ratio or scaling resolution needs to be specified" -msgstr "必须指定缩放比或输出分辨率" - -#: video2x.py:353 -msgid "Program completed, taking {} seconds" -msgstr "程序执行完毕,总计花费 {} 秒" - -#: video2x.py:360 -msgid "An exception has occurred" -msgstr "发生了异常" - -#: video2x.py:375 -msgid "The error log file can be found at: {}" -msgstr "错误日志已被保存到:{}" - -#~ msgid "disable logging" -#~ msgstr "禁用日志" - -#~ msgid "Only one of scaling width and scaling height is specified" -#~ msgstr "输出高度和宽度仅有其中一项被指定" - -#~ msgid "Redirecting console logs to {}" -#~ msgstr "将控制台日志重定向到 {}" - -#~ msgid "Upscaling Progress" -#~ msgstr "放大进度" - -#~ msgid "Loading files from multiple paths" -#~ msgstr "正在从多个路径中导入文件" - -#~ msgid "Loading single file" -#~ msgstr "正在导入单个文件" - -#~ msgid "Loading files from directory" -#~ msgstr "正在从文件夹中导入文件" - -#~ msgid "Anime4KCPP doesn't yet support GIF processing" -#~ msgstr "Anime4KCPP 尚不支持GIF处理" - -#~ msgid "Starting to upscale video with Anime4KCPP" -#~ msgstr "开始用 Anime4KCPP 放大视频" - -#~ msgid "You must specify input video file/directory path" -#~ msgstr "您必须指定输入视频文件/目录路径" - -#~ msgid "You must specify output video file/directory path" -#~ msgstr "您必须指定输出视频文件/目录路径" - -#~ msgid "Selected driver accepts only scaling ratio" -#~ msgstr "所选驱动程序仅接受缩放比率" - -#~ msgid "Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan" -#~ msgstr "waifu2x_ncnn_vulkan 的缩放比必须为 1 或 2" - -#~ msgid "Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan" -#~ msgstr "srmd_ncnn_vulkan 的缩放比必须为 2、3 或 4" - -#~ msgid "You must specify both width and height" -#~ msgstr "您必须同时指定宽度和高度" diff --git a/video2x/progress_monitor.py b/video2x/progress_monitor.py deleted file mode 100755 index 2a8f0a0..0000000 --- a/video2x/progress_monitor.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X Upscale Progress Monitor -Author: K4YT3X -Date Created: May 7, 2020 -Last Modified: September 9, 2020 -""" - -# built-in imports -import contextlib -import threading -import time - -# third-party imports -from tqdm import tqdm - - -class ProgressMonitor(threading.Thread): - """progress monitor - - This class provides progress monitoring functionalities - by keeping track of the amount of frames in the input - directory and the output directory. This is originally - suggested by @ArmandBernard. - """ - - def __init__(self, upscaler, extracted_frames_directories): - threading.Thread.__init__(self) - self.upscaler = upscaler - self.extracted_frames_directories = extracted_frames_directories - self.running = False - - def run(self): - self.running = True - - with tqdm( - total=self.upscaler.total_frames, - ascii=True, - desc=_("Processing: {} (pass {}/{})").format( - self.upscaler.current_input_file.name, - self.upscaler.current_pass, - len(self.upscaler.scaling_jobs), - ), - ) as progress_bar: - # tqdm update method adds the value to the progress - # bar instead of setting the value. Therefore, a delta - # needs to be calculated. - previous_cycle_frames = 0 - while self.running: - - with contextlib.suppress(FileNotFoundError): - upscaled_frames = [ - f - for f in self.upscaler.upscaled_frames.iterdir() - if str(f) - .lower() - .endswith(self.upscaler.extracted_frame_format.lower()) - ] - if len(upscaled_frames) >= 1: - self.upscaler.last_frame_upscaled = sorted(upscaled_frames)[-1] - self.upscaler.total_frames_upscaled = len(upscaled_frames) - - # update progress bar - delta = self.upscaler.total_frames_upscaled - previous_cycle_frames - previous_cycle_frames = self.upscaler.total_frames_upscaled - progress_bar.update(delta) - - time.sleep(1) - - def stop(self): - self.running = False - self.join() diff --git a/video2x/pyinstaller/7z1900-extra/7za.dll b/video2x/pyinstaller/7z1900-extra/7za.dll deleted file mode 100644 index 088f395..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/7za.dll and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/7za.exe b/video2x/pyinstaller/7z1900-extra/7za.exe deleted file mode 100644 index 2bdd57d..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/7za.exe and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/7zxa.dll b/video2x/pyinstaller/7z1900-extra/7zxa.dll deleted file mode 100644 index f755fb5..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/7zxa.dll and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.hlf b/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.hlf deleted file mode 100644 index f5867c8..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.hlf +++ /dev/null @@ -1,85 +0,0 @@ -.Language=English,English -.PluginContents=7-Zip Plugin - -@Contents -$^#7-Zip Plugin 19.00# -$^#Copyright (c) 1999-2019 Igor Pavlov# - This FAR module performs transparent #archive# processing. -Files in the archive are handled in the same manner as if they -were in a folder. - - ~Extracting from the archive~@Extract@ - - ~Add files to the archive~@Update@ - - ~7-Zip Plugin configuration~@Config@ - - - Web site: #www.7-zip.org# - -@Extract -$ #Extracting from the archive# - -In this dialog you may enter extracting mode. - - Path mode - - #Full pathnames# Extract files with full pathnames. - - #Current pathnames# Extract files with all relative paths. - - #No pathnames# Extract files without folder paths. - - - Overwrite mode - - #Ask before overwrite# Ask before overwriting existing files. - - #Overwrite without prompt# Overwrite existing files without prompt. - - #Skip existing files# Skip extracting of existing files. - - - Files - - #Selected files# Extract only selected files. - - #All files# Extract all files from archive. - -@Update -$ #Add files to the archive# - -This dialog allows you to specify options for process of updating archive. - - - Compression method - - #Store# Files will be copied to archive without compression. - - #Normal# Files will be compressed. - - #Maximum# Files will be compressed with method that gives - maximum compression ratio. - - - Update mode - - #Add and replace files# Add all specified files to the archive. - - #Update and add files# Update older files in the archive and add - files that are new to the archive. - - #Freshen existing files# Update specified files in the archive that - are older than the selected disk files. - - #Synchronize files# Replace specified files only if - added files are newer. Always add those - files, which are not present in the - archive. Delete from archive those files, - which are not present on the disk. - -@Config -$ #7-Zip Plugin configuration# - In this dialog you may change following parameters: - - #Plugin is used by default# Plugin is used by default. diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.lng b/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.lng deleted file mode 100644 index fbd37a4..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/7-ZipEng.lng +++ /dev/null @@ -1,211 +0,0 @@ -.Language=English,English - -"Ok" -"&Cancel" - -"Warning" -"Error" - -"Format" - -"Properties" - -"Yes" -"No" - -"Get password" -"Enter password" - -"Extract" -"&Extract to" - -"Path mode" -"&Full pathnames" -"C&urrent pathnames" -"&No pathnames" - -"Overwrite mode" -"As&k before overwrite" -"&Overwrite without prompt" -"Sk&ip existing files" -"A&uto rename" -"A&uto rename existing files" - -"Extract" -"&Selected files" -"A&ll files" - -"&Password" - -"Extr&act" -"&Cancel" - -"Can not open output file '%s'." - -"Unsupported compression method for '%s'." -"CRC failed in '%s'." -"Data error in '%s'." -"CRC failed in encrypted file '%s'. Wrong password?" -"Data error in encrypted file '%s'. Wrong password?" - -"Confirm File Replace" -"Destination folder already contains processed file." -"Would you like to replace the existing file" -"with this one" - -"bytes" -"modified on" - - -"&Yes" -"Yes to &All" -"&No" -"No to A&ll" -"A&uto rename" -"&Cancel" - - -"Update operations are not supported for this archive." - - -"Delete from archive" -"Delete \"%.40s\" from the archive" -"Delete selected files from the archive" -"Delete %d files from the archive" -"Delete" -"Cancel" - -"Add files to archive" - -"Add to %s a&rchive:" - -"Compression method" -"&Store" -"Fas&test" -"&Fast" -"&Normal" -"&Maximum" -"&Ultra" - -"Update mode" -"A&dd and replace files" -"&Update and add files" -"&Freshen existing files" -"S&ynchronize files" - -"&Add" -"Se&lect archiver" - -"Select archive format" - -"Wait" -"Reading the archive" -"Extracting from the archive" -"Deleting from the archive" -"Updating the archive" - -"Move operation is not supported" - -"7-Zip" -"7-Zip (add to archive)" - -"7-Zip" - -"Plugin is used by default" - -"0" -"1" -"2" -"Path" -"Name" -"Extension" -"Is Folder" -"Size" -"Packed Size" -"Attributes" -"Created" -"Accessed" -"Modified" -"Solid" -"Commented" -"Encrypted" -"Splited Before" -"Splited After" -"Dictionary Size" -"CRC" -"Type" -"Anti" -"Method" -"Host OS" -"File System" -"User" -"Group" -"Block" -"Comment" -"Position" -"Path Prefix" -"Folders" -"Files" -"Version" -"Volume" -"Multivolume" -"Offset" -"Links" -"Blocks" -"Volumes" -"Time Type" -"64-bit" -"Big-endian" -"CPU" -"Physical Size" -"Headers Size" -"Checksum" -"Characteristics" -"Virtual Address" -"ID" -"Short Name" -"Creator Application" -"Sector Size" -"Mode" -"Symbolic Link" -"Error" -"Total Size" -"Free Space" -"Cluster Size" -"Label" -"Local Name" -"Provider" -"NT Security" -"Alternate Stream" -"Aux" -"Deleted" -"Tree" -"SHA-1" -"SHA-256" -"Error Type" -"Errors" -"Errors" -"Warnings" -"Warning" -"Streams" -"Alternate Streams" -"Alternate Streams Size" -"Virtual Size" -"Unpack Size" -"Total Physical Size" -"Volume Index" -"SubType" -"Short Comment" -"Code Page" -"Is not archive type" -"Physical Size can't be detected" -"Zeros Tail Is Allowed" -"Tail Size" -"Embedded Stub Size" -"Link" -"Hard Link" -"iNode" -"Stream ID" -"Read-only" -"Out Name" -"Copy Link" diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar.dll b/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar.dll deleted file mode 100644 index e51058d..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar.dll and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar64.dll b/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar64.dll deleted file mode 100644 index 697af07..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/Far/7-ZipFar64.dll and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.hlf b/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.hlf deleted file mode 100644 index 9dff746..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.hlf +++ /dev/null @@ -1,84 +0,0 @@ -.Language=Russian,Russian (᪨) -.PluginContents= 7-Zip - -@Contents -$^#7-Zip Plugin 19.00# -$^#Copyright (c) 1999-2019 Igor Pavlov# - FAR ࠡ #娢#. 짮⥫ -䠩 娢 ⫨ 䠩 . - - - ~ᯠ 䠩 娢~@Extract@ - - ~ 䠩 娢~@Update@ - - ~ࠬ ࠡ 娢~@Config@ - - - Web site: #www.7-zip.org# - -@Extract -$ #ᯠ 䠩 娢# - ⮬ ᯠ 䠩 -० ᯠ. - - - - # # ᯠ 䠩 묨 ﬨ. - - #⭮⥫ # ᯠ ⭮⥫묨 ﬨ. - - # ⥩# ᯠ ⥩. - - - १ - - #訢 ⢥ত# 訢 ⢥ত - १ 饣 䠩. - - # ⢥ত# 騩 䠩 - ⢥ত. - - #ய᪠# ய᪠ 騥 䠩. - - - ᯠ - - #࠭ 䠩# ᯠ ⮫쪮 뤥 䠩 娢. - - # 䠩# ᯠ 䠩 娢. - -@Update -$ # 䠩 娢# - - ⮬ ० 㯠. - - - ⮤ ᦠ: - - # ᦠ# ᪮஢ ᦠ. - - #ଠ쭮 ᦠ⨥# ᦠ. - - #ᨬ쭮 ᦠ⨥# ᦠ ᨬ쭮 - ⥯ ᦠ. - - - : - - # # ࠭ 䠩 娢. - - # # ॢ訥 䠩 娢 - 䠩, 娢. - - ## ॢ訥 䠩 娢. - - #஭஢# ஭஢ ᮤন 娢 - ࠭묨 䠩. - - -@Config -$ #ࠬ ࠡ 7-Zip# - ⮬ ᫥騥 ࠬ: - - # ᯮ 㬮砭# ᯮ 㬮砭 diff --git a/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.lng b/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.lng deleted file mode 100644 index 7663cfa..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/7-ZipRus.lng +++ /dev/null @@ -1,211 +0,0 @@ -.Language=Russian,Russian (᪨) - -"த" -"&⬥" - -"।०" -"訡" - -"ଠ" - -"⢠" - -"" -"" - -" ஫" -" ஫" - -"ᯠ" -"&ᯠ " - -"" -"& " -"&⭮⥫ " -"& ⥩" - -"१" -"&訢 ⢥ত" -"& ⢥ত" -"ய&᪠" -"२ ⮬." -"२. ⮬. ." - -"ᯠ" -"&࠭ 䠩" -" &䠩" - -"&஫" - -"&ᯠ" -"&⬥" - -" 䠩 '%s'." - -"ন ⮤ ᦠ 䠩 '%s'." -"訡 CRC '%s'." -"訡 '%s'." -"訡 CRC ஢ 䠩 '%s'. ஫?" -"訡 ஢ 䠩 '%s'. ஫?" - -"⢥न 䠩" -" 㦥 ᮤন ࠡ뢠 䠩." -" 騩 䠩" -"᫥騬 䠩" - -"" -"" - - -"&" -" &" -"&" -" &" -"२ ⮬᪨" -"&⬥" - - -" ⮣ 娢 樨 ন." - - -" 娢" -" \"%.40s\" 娢" -" ࠭ 䠩 娢" -" %d 䠩 娢" -"" -"⬥" - -" 䠩 娢" - -" %s &娢" - -"⮤ ᦠ" -" ᦠ" -"⭮" -"" -"ଠ" -"ᨬ" -"" - -" " -" " -" " -"" -"஭஢" - -"&" -"&娢" - -"롮 娢 ଠ" - -"" -"⥭ 娢" -"ᯠ" -"" -"" - -"६饭 䠩 ন" - -"7-Zip" -"7-Zip ( 娢)" - -"7-Zip configuration" - -" ᯮ 㬮砭" - -"0" -"1" -"2" -"" -"" -"७" -"" -"" -"" -"ਡ" -"" -"" -"" -"뢭" -"਩" -"஢" -" " -" ᫥" -"" -"CRC" -"" -"" -"⮤" -"⥬" -" ⥬" -"짮⥫" -"㯯" -"" -"਩" -"" -"" -"" -"" -"" -"" -"⮬" -"饭" -"뫮" -"" -"" -"Time Type" -"64-bit" -"Big-endian" -"" -"᪨ " -" " -". 㬬" -"ࠪ⨪" -"㠫 " -"ID" -"⪮ " -"ணࠬ" -" ᥪ" -"" -"쭠 뫪" -"訡" -"" -"" -" " -"⪠" -"쭮 " -"஢" -"NT ᭮" -"ୠ⨢ ⮪" -"Aux" -"" -"ॢ" -"SHA-1" -"SHA-256" -" 訡" -"訡" -"訡" -"।०" -"।०" -"⮪" -"ୠ⨢ ⮪" -" ୠ⨢ ⮪" -"㠫 " -"ᯠ " -"騩 ᪨ " -" " -"⨯" -"⪨ ਩" -" ࠭" -"Is not archive type" -"Physical Size can't be detected" -"Zeros Tail Is Allowed" -" ⪠" -" ஥ " -"뫪" -"⪠ 뫪" -"iNode" -"ID ⮪" -"쪮 ⥭" -"Out Name" -"Copy Link" diff --git a/video2x/pyinstaller/7z1900-extra/Far/7zToFar.ini b/video2x/pyinstaller/7z1900-extra/Far/7zToFar.ini deleted file mode 100644 index 684976e..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/7zToFar.ini +++ /dev/null @@ -1,67 +0,0 @@ -; 7z supporting for MutiArc in Far -; Append the following strings to file -; ..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini - -[7z] -TypeName=7z -ID=37 7A BC AF 27 1C -IDPos= -IDOnly=1 -Extension=7z -List=7z l -- %%AQ -Start="^-----" -End="^-----" -Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" -Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A -Delete=7z d {-p%%P} -r0 -ms=off -scsDOS -i@%%LQMN -- %%A -Add=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -i@%%LQMN -- %%A -AddRecurse=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -i@%%LQMN -- %%A -AllFilesMask="*" - -[rpm] -TypeName=rpm -ID=ED AB EE DB -IDPos= -IDOnly=1 -Extension=rpm -List=7z l -- %%AQ -Start="^-----" -End="^-----" -Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" -Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A -AllFilesMask="*" - -[cpio] -TypeName=cpio -ID= -IDPos= -IDOnly=0 -Extension=cpio -List=7z l -- %%AQ -Start="^-----" -End="^-----" -Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" -Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A -AllFilesMask="*" - -[deb] -TypeName=deb -ID= -IDPos= -IDOnly=0 -Extension=deb -List=7z l -- %%AQ -Start="^-----" -End="^-----" -Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" -Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A -Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A -AllFilesMask="*" - diff --git a/video2x/pyinstaller/7z1900-extra/Far/far7z.reg b/video2x/pyinstaller/7z1900-extra/Far/far7z.reg deleted file mode 100644 index 105e8f6..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/far7z.reg +++ /dev/null @@ -1,67 +0,0 @@ -REGEDIT4 - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP] -"Extract"="7z x {-p%%P} -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e {-p%%P} -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A" -"Delete"="7z d {-p%%P} -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Add"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AddRecurse"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\TAR] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Add"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AddRecurse"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\GZIP] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Add"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AddRecurse"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\BZIP] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Add"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AddRecurse"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ARJ] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\CAB] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\LZH] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\RAR] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" - -[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\Z(Unix)] -"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" -"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" -"AllFilesMask"="*" diff --git a/video2x/pyinstaller/7z1900-extra/Far/far7z.txt b/video2x/pyinstaller/7z1900-extra/Far/far7z.txt deleted file mode 100644 index fac709e..0000000 --- a/video2x/pyinstaller/7z1900-extra/Far/far7z.txt +++ /dev/null @@ -1,67 +0,0 @@ -7-Zip Plugin for FAR Manager ----------------------------- - -FAR Manager is a file manager working in text mode. -You can download "FAR Manager" from site: -http://www.farmanager.com - -Files: - -far7z.txt - This file -far7z.reg - Regisrty file for MultiArc Plugin -7zToFar.ini - Supporting 7z for MultiArc Plugin -7-ZipFar.dll - 7-Zip Plugin for FAR Manager -7-ZipEng.hlf - Help file in English for FAR Manager -7-ZipRus.hlf - Help file in Russian for FAR Manager -7-ZipEng.lng - Plugin message strings in English for FAR Manager -7-ZipRus.lng - Plugin message strings in Russian for FAR Manager - -There are two ways to use 7-Zip with FAR Manager: - - 1) Via 7-Zip FAR Plugin (it's recommended way). - 2) Via standard MultiArc Plugin. - - -7-Zip FAR Plugin -~~~~~~~~~~~~~~~~ - -7-Zip FAR Plugin is first level plugin for FAR Manager, like MultiArc plugin. -It very fast extracts and updates files in archive, since it doesn't use -external programs. It supports all formats supported by 7-Zip: -7z, ZIP, RAR, CAB, ARJ, GZIP, BZIP2, Z, TAR, CPIO, RPM and DEB. - -To install 7-Zip FAR Plugin: - 1) Create "7-Zip" folder in ...\Program Files\Far\Plugins folder. - 2) Copy all files from "FAR" folder of this package to created folder. - 3) Install 7-Zip, or copy 7z.dll from 7-Zip to Program Files\Far\Plugins\7-Zip\ - 4) Restart FAR. - -You can open archives with one of the following ways: - * Pressing Enter. - * Pressing Ctrl-PgDown. - * Pressing F11 and selecting 7-Zip item. - - -You can create new archives with 7-Zip by pressing F11 and -selecting 7-Zip (add to archive) item. - -If you think that some operations with archives is better to do with MultiArc Plugin, -you can disable 7-Zip plugin via Options / Pligin configuration / 7-Zip. In such mode -opening archives by pressing Enter and Ctrl-PgDown will start MultiArc Plugin. And -if you want to open archive with 7-Zip, press F11 and select 7-Zip item. - - -Using command line 7-Zip via MultiArc Plugin -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you want to use 7-Zip via MultiArc Plugin, you must -register file far7z.reg. - -If you want to use 7z archives via MultiArc Plugin, you must -append contents of file Far\7zToFar.ini to file -..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini. - - -If you want to cancel using 7-Zip by MultiArc, just remove lines that contain -7-Zip (7z) program name from HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP -registry key. diff --git a/video2x/pyinstaller/7z1900-extra/License.txt b/video2x/pyinstaller/7z1900-extra/License.txt deleted file mode 100644 index 48dc6c6..0000000 --- a/video2x/pyinstaller/7z1900-extra/License.txt +++ /dev/null @@ -1,31 +0,0 @@ - 7-Zip Extra - ~~~~~~~~~~~ - License for use and distribution - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Copyright (C) 1999-2019 Igor Pavlov. - - 7-Zip Extra files are under the GNU LGPL license. - - - Notes: - You can use 7-Zip Extra on any computer, including a computer in a commercial - organization. You don't need to register or pay for 7-Zip. - - - GNU LGPL information - -------------------- - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You can receive a copy of the GNU Lesser General Public License from - http://www.gnu.org/ - diff --git a/video2x/pyinstaller/7z1900-extra/history.txt b/video2x/pyinstaller/7z1900-extra/history.txt deleted file mode 100644 index 2154c3d..0000000 --- a/video2x/pyinstaller/7z1900-extra/history.txt +++ /dev/null @@ -1,111 +0,0 @@ -7-Zip Extra history -------------------- - -This file contains only information about changes related to that package exclusively. -The full history of changes is listed in history.txt in main 7-Zip program. - - -19.00 2019-02-21 -------------------------- -- Encryption strength for 7z archives was increased: - the size of random initialization vector was increased from 64-bit to 128-bit, - and the pseudo-random number generator was improved. -- Some bugs were fixed. - - -18.06 2018-12-30 -------------------------- -- The speed for LZMA/LZMA2 compressing was increased by 3-10%, - and there are minor changes in compression ratio. -- Some bugs were fixed. - - -18.05 2018-04-30 -------------------------- -- The speed for LZMA/LZMA2 compressing was increased - by 8% for fastest/fast compression levels and - by 3% for normal/maximum compression levels. - - -18.03 beta 2018-03-04 -------------------------- -- The speed for single-thread LZMA/LZMA2 decoding - was increased by 30% in x64 version and by 3% in x86 version. -- 7-Zip now can use multi-threading for 7z/LZMA2 decoding, - if there are multiple independent data chunks in LZMA2 stream. - - -9.35 beta 2014-12-07 ------------------------------- - - SFX modules were moved to LZMA SDK package. - - -9.34 alpha 2014-06-22 ------------------------------- - - Minimum supported system now is Windows 2000 for EXE and DLL files. - - all EXE and DLL files use msvcrt.dll. - - 7zr.exe now support AES encryption. - - -9.18 2010-11-02 ------------------------------- - - New small SFX module for installers. - - -9.17 2010-10-04 ------------------------------- - - New 7-Zip plugin for FAR Manager x64. - - -9.10 2009-12-30 ------------------------------- - - 7-Zip for installers now supports LZMA2. - - -9.09 2009-12-12 ------------------------------- - - LZMA2 compression method support. - - Some bugs were fixed. - - -4.65 2009-02-03 ------------------------------- - - Some bugs were fixed. - - -4.38 beta 2006-04-13 ------------------------------- - - SFX for installers now supports new properties in config file: - Progress, Directory, ExecuteFile, ExecuteParameters. - - -4.34 beta 2006-02-27 ------------------------------- - - ISetProperties::SetProperties: - it's possible to specify desirable number of CPU threads: - PROPVARIANT: name=L"mt", vt = VT_UI4, ulVal = NumberOfThreads - If "mt" is not defined, 7za.dll will check number of processors in system to set - number of desirable threads. - Now 7za.dll can use: - 2 threads for LZMA compressing - N threads for BZip2 compressing - 4 threads for BZip2 decompressing - Other codecs use only one thread. - Note: 7za.dll can use additional "small" threads with low CPU load. - - It's possible to call ISetProperties::SetProperties to specify "mt" property for decoder. - - -4.33 beta 2006-02-05 ------------------------------- - - Compressing speed and Memory requirements were increased. - Default dictionary size was increased: Fastest: 64 KB, Fast: 1 MB, - Normal: 4 MB, Max: 16 MB, Ultra: 64 MB. - - 7z/LZMA now can use only these match finders: HC4, BT2, BT3, BT4 - - -4.27 2005-09-21 ------------------------------- - - Some GUIDs/interfaces were changed. - IStream.h: - ISequentialInStream::Read now works as old ReadPart - ISequentialOutStream::Write now works as old WritePart diff --git a/video2x/pyinstaller/7z1900-extra/readme.txt b/video2x/pyinstaller/7z1900-extra/readme.txt deleted file mode 100644 index a54a313..0000000 --- a/video2x/pyinstaller/7z1900-extra/readme.txt +++ /dev/null @@ -1,124 +0,0 @@ -7-Zip Extra 19.00 ------------------ - -7-Zip Extra is package of extra modules of 7-Zip. - -7-Zip Copyright (C) 1999-2019 Igor Pavlov. - -7-Zip is free software. Read License.txt for more information about license. - -Source code of binaries can be found at: - http://www.7-zip.org/ - -This package contains the following files: - -7za.exe - standalone console version of 7-Zip with reduced formats support. -7za.dll - library for working with 7z archives -7zxa.dll - library for extracting from 7z archives -License.txt - license information -readme.txt - this file - -Far\ - plugin for Far Manager -x64\ - binaries for x64 - - -All 32-bit binaries can work in: - Windows 2000 / 2003 / 2008 / XP / Vista / 7 / 8 / 10 - and in any Windows x64 version with WoW64 support. -All x64 binaries can work in any Windows x64 version. - -All binaries use msvcrt.dll. - -7za.exe -------- - -7za.exe - is a standalone console version of 7-Zip with reduced formats support. - - Extra: 7za.exe : support for only some formats of 7-Zip. - 7-Zip: 7z.exe with 7z.dll : support for all formats of 7-Zip. - -7za.exe and 7z.exe from 7-Zip have same command line interface. -7za.exe doesn't use external DLL files. - -You can read Help File (7-zip.chm) from 7-Zip package for description -of all commands and switches for 7za.exe and 7z.exe. - -7za.exe features: - - - High compression ratio in 7z format - - Supported formats: - - Packing / unpacking: 7z, xz, ZIP, GZIP, BZIP2 and TAR - - Unpacking only: Z, lzma, CAB. - - Highest compression ratio for ZIP and GZIP formats. - - Fast compression and decompression - - Strong AES-256 encryption in 7z and ZIP formats. - -Note: LZMA SDK contains 7zr.exe - more reduced version of 7za.exe. -But you can use 7zr.exe as "public domain" code. - - - -DLL files ---------- - -7za.dll and 7zxa.dll are reduced versions of 7z.dll from 7-Zip. -7za.dll and 7zxa.dll support only 7z format. -Note: 7z.dll is main DLL file that works with all archive types in 7-Zip. - -7za.dll and 7zxa.dll support the following decoding methods: - - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES, BZip2, Deflate. - -7za.dll also supports 7z encoding with the following encoding methods: - - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES. - -7za.dll and 7zxa.dll work via COM interfaces. -But these DLLs don't use standard COM interfaces for objects creating. - -Look also example code that calls DLL functions (in source code of 7-Zip): - - 7zip\UI\Client7z - -Another example of binary that uses these interface is 7-Zip itself. -The following binaries from 7-Zip use 7z.dll: - - 7z.exe (console version) - - 7zG.exe (GUI version) - - 7zFM.exe (7-Zip File Manager) - -Note: The source code of LZMA SDK also contains the code for similar DLLs -(DLLs without BZip2, Deflate support). And these files from LZMA SDK can be -used as "public domain" code. If you use LZMA SDK files, you don't need to -follow GNU LGPL rules, if you want to change the code. - - - - -License FAQ ------------ - -Can I use the EXE or DLL files from 7-Zip in a commercial application? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Yes, but you are required to specify in documentation for your application: - (1) that you used parts of the 7-Zip program, - (2) that 7-Zip is licensed under the GNU LGPL license and - (3) you must give a link to www.7-zip.org, where the source code can be found. - - -Can I use the source code of 7-Zip in a commercial application? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Since 7-Zip is licensed under the GNU LGPL you must follow the rules of that license. -In brief, it means that any LGPL'ed code must remain licensed under the LGPL. -For instance, you can change the code from 7-Zip or write a wrapper for some -code from 7-Zip and compile it into a DLL; but, the source code of that DLL -(including your modifications / additions / wrapper) must be licensed under -the LGPL or GPL. -Any other code in your application can be licensed as you wish. This scheme allows -users and developers to change LGPL'ed code and recompile that DLL. That is the -idea of free software. Read more here: http://www.gnu.org/. - - - -Note: You can look also LZMA SDK, which is available under a more liberal license. - - ---- -End of document diff --git a/video2x/pyinstaller/7z1900-extra/x64/7za.dll b/video2x/pyinstaller/7z1900-extra/x64/7za.dll deleted file mode 100644 index bc2b47a..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/x64/7za.dll and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/x64/7za.exe b/video2x/pyinstaller/7z1900-extra/x64/7za.exe deleted file mode 100644 index 9f27b20..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/x64/7za.exe and /dev/null differ diff --git a/video2x/pyinstaller/7z1900-extra/x64/7zxa.dll b/video2x/pyinstaller/7z1900-extra/x64/7zxa.dll deleted file mode 100644 index d51e3f0..0000000 Binary files a/video2x/pyinstaller/7z1900-extra/x64/7zxa.dll and /dev/null differ diff --git a/video2x/pyinstaller/hooks/hook-patoolib.py b/video2x/pyinstaller/hooks/hook-patoolib.py deleted file mode 100644 index d17873a..0000000 --- a/video2x/pyinstaller/hooks/hook-patoolib.py +++ /dev/null @@ -1,80 +0,0 @@ -# ----------------------------------------------------------------------------- -# Copyright (c) 2013-2017, PyInstaller Development Team. -# -# Distributed under the terms of the GNU General Public License with exception -# for distributing bootloader. -# -# The full license is in the file COPYING.txt, distributed with this software. -# ----------------------------------------------------------------------------- - -""" -Name: PyInstaller patoolib Hook -Original solution: https://github.com/pyinstaller/pyinstaller/issues/3013 - -PyInstaller cannot find libraries imported by patoolib, - since it uses importlib to import these modules. -""" - -hiddenimports = [ - "patoolib.programs", - "patoolib.programs.ar", - "patoolib.programs.arc", - "patoolib.programs.archmage", - "patoolib.programs.bsdcpio", - "patoolib.programs.bsdtar", - "patoolib.programs.bzip2", - "patoolib.programs.cabextract", - "patoolib.programs.chmlib", - "patoolib.programs.clzip", - "patoolib.programs.compress", - "patoolib.programs.cpio", - "patoolib.programs.dpkg", - "patoolib.programs.flac", - "patoolib.programs.genisoimage", - "patoolib.programs.gzip", - "patoolib.programs.isoinfo", - "patoolib.programs.lbzip2", - "patoolib.programs.lcab", - "patoolib.programs.lha", - "patoolib.programs.lhasa", - "patoolib.programs.lrzip", - "patoolib.programs.lzip", - "patoolib.programs.lzma", - "patoolib.programs.lzop", - "patoolib.programs.mac", - "patoolib.programs.nomarch", - "patoolib.programs.p7azip", - "patoolib.programs.p7rzip", - "patoolib.programs.p7zip", - "patoolib.programs.pbzip2", - "patoolib.programs.pdlzip", - "patoolib.programs.pigz", - "patoolib.programs.plzip", - "patoolib.programs.py_bz2", - "patoolib.programs.py_echo", - "patoolib.programs.py_gzip", - "patoolib.programs.py_lzma", - "patoolib.programs.py_tarfile", - "patoolib.programs.py_zipfile", - "patoolib.programs.rar", - "patoolib.programs.rpm", - "patoolib.programs.rpm2cpio", - "patoolib.programs.rzip", - "patoolib.programs.shar", - "patoolib.programs.shorten", - "patoolib.programs.star", - "patoolib.programs.tar", - "patoolib.programs.unace", - "patoolib.programs.unadf", - "patoolib.programs.unalz", - "patoolib.programs.uncompress", - "patoolib.programs.unrar", - "patoolib.programs.unshar", - "patoolib.programs.unzip", - "patoolib.programs.xdms", - "patoolib.programs.xz", - "patoolib.programs.zip", - "patoolib.programs.zoo", - "patoolib.programs.zopfli", - "patoolib.programs.zpaq", -] diff --git a/video2x/requirements.txt b/video2x/requirements.txt index 39d887e..903c478 100644 --- a/video2x/requirements.txt +++ b/video2x/requirements.txt @@ -1,10 +1,6 @@ -avalon_framework -colorama -patool +ffmpeg-python +loguru +opencv-python pillow -pyqt5 -python-magic-bin; platform_system == "Windows" -python-magic; platform_system != "Windows" -pyyaml -requests -tqdm \ No newline at end of file +rich +tqdm diff --git a/video2x/upscaler.py b/video2x/upscaler.py index c5813c8..ab78646 100755 --- a/video2x/upscaler.py +++ b/video2x/upscaler.py @@ -1,772 +1,111 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Name: Video2X Upscaler +Name: Upscaler Author: K4YT3X -Date Created: December 10, 2018 -Last Modified: January 23, 2021 - -Description: This file contains the Upscaler class. Each -instance of the Upscaler class is an upscaler on an image or -a folder. +Date Created: May 27, 2021 +Last Modified: June 28, 2021 """ # local imports -from exceptions import * -from image_cleaner import ImageCleaner -from progress_monitor import ProgressMonitor -from wrappers.ffmpeg import Ffmpeg -from wrappers.gifski import Gifski +from .wrappers.realsr_ncnn_vulkan_python.realsr_ncnn_vulkan import RealSR +from .wrappers.srmd_ncnn_vulkan_python.srmd_ncnn_vulkan import SRMD +from .wrappers.waifu2x_ncnn_vulkan_python.waifu2x_ncnn_vulkan import Waifu2x # built-in imports -from fractions import Fraction -import contextlib -import copy -import gettext -import importlib -import locale import math -import mimetypes -import pathlib +import multiprocessing +import multiprocessing.managers +import multiprocessing.sharedctypes import queue -import re -import shutil -import subprocess -import tempfile +import signal import time -import traceback # third-party imports -from PIL import Image -from avalon_framework import Avalon -from tqdm import tqdm -import magic - -# internationalization constants -DOMAIN = "video2x" -LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / "locale" - -# getting default locale settings -default_locale, encoding = locale.getdefaultlocale() -if default_locale is None: - default_locale = "en_US" -language = gettext.translation( - DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True -) -language.install() -_ = language.gettext - -# version information -UPSCALER_VERSION = "4.4.2" - -# these names are consistent for -# - driver selection in command line -# - driver wrapper file names -# - config file keys -AVAILABLE_DRIVERS = [ - "waifu2x_caffe", - "waifu2x_converter_cpp", - "waifu2x_ncnn_vulkan", - "srmd_ncnn_vulkan", - "realsr_ncnn_vulkan", - "anime4kcpp", -] +from PIL import Image, ImageChops, ImageStat +from loguru import logger # fixed scaling ratios supported by the drivers # that only support certain fixed scale ratios DRIVER_FIXED_SCALING_RATIOS = { - "waifu2x_ncnn_vulkan": [1, 2], - "srmd_ncnn_vulkan": [2, 3, 4], - "realsr_ncnn_vulkan": [4], + "waifu2x": [1, 2], + "srmd": [2, 3, 4], + "realsr": [4], } +DRIVER_CLASSES = {"waifu2x": Waifu2x, "srmd": SRMD, "realsr": RealSR} -class Upscaler: - """An instance of this class is a upscaler that will - upscale all images in the given directory. - - Raises: - Exception -- all exceptions - ArgumentError -- if argument is not valid - """ +class Upscaler(multiprocessing.Process): def __init__( self, - input_path: pathlib.Path or list, - output_path: pathlib.Path, - driver_settings: dict, - ffmpeg_settings: dict, - gifski_settings: dict, - driver: str = "waifu2x_caffe", - scale_ratio: float = None, - scale_width: int = None, - scale_height: int = None, - processes: int = 1, - video2x_cache_directory: pathlib.Path = pathlib.Path(tempfile.gettempdir()) - / "video2x", - extracted_frame_format: str = "png", - output_file_name_format_string: str = "{original_file_name}_output{extension}", - image_output_extension: str = ".png", - video_output_extension: str = ".mp4", - preserve_frames: bool = False, + processing_queue: multiprocessing.Queue, + processed_frames: multiprocessing.managers.ListProxy, ): - - # required parameters - self.input = input_path - self.output = output_path - self.driver_settings = driver_settings - self.ffmpeg_settings = ffmpeg_settings - self.gifski_settings = gifski_settings - - # optional parameters - self.driver = driver - self.scale_ratio = scale_ratio - self.scale_width = scale_width - self.scale_height = scale_height - self.processes = processes - self.video2x_cache_directory = video2x_cache_directory - self.extracted_frame_format = extracted_frame_format - self.output_file_name_format_string = output_file_name_format_string - self.image_output_extension = image_output_extension - self.video_output_extension = video_output_extension - self.preserve_frames = preserve_frames - - # other internal members and signals + multiprocessing.Process.__init__(self) self.running = False - self.current_processing_starting_time = time.time() - self.total_frames_upscaled = 0 - self.total_frames = 0 - self.total_files = 0 - self.total_processed = 0 - self.scaling_jobs = [] - self.current_pass = 0 - self.current_input_file = pathlib.Path() - self.last_frame_upscaled = pathlib.Path() + self.processing_queue = processing_queue + self.processed_frames = processed_frames - def create_temp_directories(self): - """create temporary directories""" - - # if cache directory unspecified, use %TEMP%\video2x - if self.video2x_cache_directory is None: - self.video2x_cache_directory = ( - pathlib.Path(tempfile.gettempdir()) / "video2x" - ) - - # if specified cache path exists and isn't a directory - if ( - self.video2x_cache_directory.exists() - and not self.video2x_cache_directory.is_dir() - ): - Avalon.error(_("Specified or default cache directory is a file/link")) - raise FileExistsError("Specified or default cache directory is a file/link") - - # if cache directory doesn't exist, try creating it - if not self.video2x_cache_directory.exists(): - try: - Avalon.debug_info( - _("Creating cache directory {}").format( - self.video2x_cache_directory - ) - ) - self.video2x_cache_directory.mkdir(parents=True, exist_ok=True) - except Exception as exception: - Avalon.error( - _("Unable to create {}").format(self.video2x_cache_directory) - ) - raise exception - - # create temp directories for extracted frames and upscaled frames - self.extracted_frames = pathlib.Path( - tempfile.mkdtemp(dir=self.video2x_cache_directory) - ) - Avalon.debug_info( - _("Extracted frames are being saved to: {}").format(self.extracted_frames) - ) - self.upscaled_frames = pathlib.Path( - tempfile.mkdtemp(dir=self.video2x_cache_directory) - ) - Avalon.debug_info( - _("Upscaled frames are being saved to: {}").format(self.upscaled_frames) - ) - - def cleanup_temp_directories(self): - """delete temp directories when done""" - if not self.preserve_frames: - for directory in [ - self.extracted_frames, - self.upscaled_frames, - self.video2x_cache_directory, - ]: - try: - # avalon framework cannot be used if python is shutting down - # therefore, plain print is used - print(_("Cleaning up cache directory: {}").format(directory)) - shutil.rmtree(directory) - except FileNotFoundError: - pass - except OSError: - print(_("Unable to delete: {}").format(directory)) - traceback.print_exc() - - def _check_arguments(self): - if isinstance(self.input, list): - if self.output.exists() and not self.output.is_dir(): - Avalon.error(_("Input and output path type mismatch")) - Avalon.error(_("Input is multiple files but output is not directory")) - raise ArgumentError("input output path type mismatch") - for input_path in self.input: - if not input_path.is_file() and not input_path.is_dir(): - Avalon.error( - _("Input path {} is neither a file nor a directory").format( - input_path - ) - ) - raise FileNotFoundError( - f"{input_path} is neither file nor directory" - ) - with contextlib.suppress(FileNotFoundError): - if input_path.samefile(self.output): - Avalon.error( - _("Input directory and output directory cannot be the same") - ) - raise FileExistsError( - "input directory and output directory are the same" - ) - - # if input is a file - elif self.input.is_file(): - if self.output.is_dir(): - Avalon.error(_("Input and output path type mismatch")) - Avalon.error(_("Input is single file but output is directory")) - raise ArgumentError("input output path type mismatch") - if self.output.suffix == "": - Avalon.error(_("No suffix found in output file path")) - Avalon.error(_("Suffix must be specified")) - raise ArgumentError("no output file suffix specified") - - # if input is a directory - elif self.input.is_dir(): - if self.output.is_file(): - Avalon.error(_("Input and output path type mismatch")) - Avalon.error(_("Input is directory but output is existing single file")) - raise ArgumentError("input output path type mismatch") - with contextlib.suppress(FileNotFoundError): - if self.input.samefile(self.output): - Avalon.error( - _("Input directory and output directory cannot be the same") - ) - raise FileExistsError( - "input directory and output directory are the same" - ) - - # if input is neither - else: - Avalon.error(_("Input path is neither a file nor a directory")) - raise FileNotFoundError(f"{self.input} is neither file nor directory") - - # check FFmpeg settings - ffmpeg_path = pathlib.Path(self.ffmpeg_settings["ffmpeg_path"]) - if not ( - ( - pathlib.Path(ffmpeg_path / "ffmpeg.exe").is_file() - and pathlib.Path(ffmpeg_path / "ffprobe.exe").is_file() - ) - or ( - pathlib.Path(ffmpeg_path / "ffmpeg").is_file() - and pathlib.Path(ffmpeg_path / "ffprobe").is_file() - ) - ): - Avalon.error( - _("FFmpeg or FFprobe cannot be found under the specified path") - ) - Avalon.error(_("Please check the configuration file settings")) - raise FileNotFoundError(self.ffmpeg_settings["ffmpeg_path"]) - - # check if driver settings - driver_settings = copy.deepcopy(self.driver_settings) - driver_path = driver_settings.pop("path") - - # check if driver path exists - if not ( - pathlib.Path(driver_path).is_file() - or pathlib.Path(f"{driver_path}.exe").is_file() - ): - Avalon.error(_("Specified driver executable directory doesn't exist")) - Avalon.error(_("Please check the configuration file settings")) - raise FileNotFoundError(driver_path) - - # parse driver arguments using driver's parser - # the parser will throw AttributeError if argument doesn't satisfy constraints - try: - driver_arguments = [] - for key in driver_settings.keys(): - - value = driver_settings[key] - - if value is None or value is False: - continue - - else: - if len(key) == 1: - driver_arguments.append(f"-{key}") - else: - driver_arguments.append(f"--{key}") - # true means key is an option - if value is not True: - driver_arguments.append(str(value)) - - DriverWrapperMain = getattr( - importlib.import_module(f"wrappers.{self.driver}"), "WrapperMain" - ) - DriverWrapperMain.parse_arguments(driver_arguments) - except AttributeError as e: - Avalon.error(_("Failed to parse driver argument: {}").format(e.args[0])) - raise e - - def _upscale_frames( - self, input_directory: pathlib.Path, output_directory: pathlib.Path - ): - """Upscale video frames with waifu2x-caffe - - This function upscales all the frames extracted - by ffmpeg using the waifu2x-caffe binary. - - Args: - input_directory (pathlib.Path): directory containing frames to upscale - output_directory (pathlib.Path): directory which upscaled frames should be exported to - - Raises: - UnrecognizedDriverError: raised when the given driver is not recognized - e: re-raised exception after an exception has been captured and finished processing in this scope - """ - - # initialize waifu2x driver - if self.driver not in AVAILABLE_DRIVERS: - raise UnrecognizedDriverError( - _("Unrecognized driver: {}").format(self.driver) - ) - - # list all images in the extracted frames - frames = [(input_directory / f) for f in input_directory.iterdir() if f.is_file] - - # if we have less images than processes, - # create only the processes necessary - if len(frames) < self.processes: - self.processes = len(frames) - - # create a directory for each process and append directory - # name into a list - process_directories = [] - for process_id in range(self.processes): - process_directory = input_directory / str(process_id) - process_directories.append(process_directory) - - # delete old directories and create new directories - if process_directory.is_dir(): - shutil.rmtree(process_directory) - process_directory.mkdir(parents=True, exist_ok=True) - - # waifu2x-converter-cpp will perform multi-threading within its own process - if self.driver in [ - "waifu2x_converter_cpp", - "waifu2x_ncnn_vulkan", - "srmd_ncnn_vulkan", - "realsr_ncnn_vulkan", - "anime4kcpp", - ]: - process_directories = [input_directory] - - else: - # evenly distribute images into each directory - # until there is none left in the directory - for image in frames: - # move image - image.rename(process_directories[0] / image.name) - # rotate list - process_directories = ( - process_directories[-1:] + process_directories[:-1] - ) - - # create driver processes and start them - for process_directory in process_directories: - self.process_pool.append( - self.driver_object.upscale(process_directory, output_directory) - ) - - # start progress bar in a different thread - Avalon.debug_info(_("Starting progress monitor")) - self.progress_monitor = ProgressMonitor(self, process_directories) - self.progress_monitor.start() - - # create the clearer and start it - Avalon.debug_info(_("Starting upscaled image cleaner")) - self.image_cleaner = ImageCleaner( - input_directory, output_directory, len(self.process_pool) - ) - self.image_cleaner.start() - - # wait for all process to exit - try: - self._wait() - except (Exception, KeyboardInterrupt, SystemExit) as e: - # cleanup - Avalon.debug_info(_("Killing progress monitor")) - self.progress_monitor.stop() - - Avalon.debug_info(_("Killing upscaled image cleaner")) - self.image_cleaner.stop() - raise e - - # if the driver is waifu2x-converter-cpp - # images need to be renamed to be recognizable for FFmpeg - if self.driver == "waifu2x_converter_cpp": - for image in [f for f in output_directory.iterdir() if f.is_file()]: - renamed = re.sub( - f"_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.extracted_frame_format}", - f".{self.extracted_frame_format}", - str(image.name), - ) - (output_directory / image).rename(output_directory / renamed) - - # upscaling done, kill helper threads - Avalon.debug_info(_("Killing progress monitor")) - self.progress_monitor.stop() - - Avalon.debug_info(_("Killing upscaled image cleaner")) - self.image_cleaner.stop() - - def _terminate_subprocesses(self): - Avalon.warning(_("Terminating all processes")) - for process in self.process_pool: - process.terminate() - - def _wait(self): - """wait for subprocesses in process pool to complete""" - Avalon.debug_info(_("Main process waiting for subprocesses to exit")) - - try: - # while process pool not empty - while self.process_pool: - - # if stop signal received, terminate all processes - if self.running is False: - raise SystemExit - - for process in self.process_pool: - process_status = process.poll() - - # if process finished - if process_status is None: - continue - - # if return code is not 0 - elif process_status != 0: - Avalon.error( - _("Subprocess {} exited with code {}").format( - process.pid, process_status - ) - ) - raise subprocess.CalledProcessError( - process_status, process.args - ) - - else: - Avalon.debug_info( - _("Subprocess {} exited with code {}").format( - process.pid, process_status - ) - ) - self.process_pool.remove(process) - - time.sleep(0.1) - - except (KeyboardInterrupt, SystemExit) as e: - Avalon.warning(_("Stop signal received")) - self._terminate_subprocesses() - raise e - - except (Exception, subprocess.CalledProcessError) as e: - Avalon.error(_("Subprocess execution ran into an error")) - self._terminate_subprocesses() - raise e + signal.signal(signal.SIGTERM, self._stop) def run(self): - """Main controller for Video2X - - This function controls the flow of video conversion - and handles all necessary functions. - """ - - # external stop signal when called in a thread self.running = True - - # define process pool to contain processes - self.process_pool = [] - - # load driver modules - DriverWrapperMain = getattr( - importlib.import_module(f"wrappers.{self.driver}"), "WrapperMain" - ) - self.driver_object = DriverWrapperMain(self.driver_settings) - - # load options from upscaler class into driver settings - self.driver_object.load_configurations(self) - - # initialize FFmpeg object - self.ffmpeg_object = Ffmpeg( - self.ffmpeg_settings, extracted_frame_format=self.extracted_frame_format - ) - - # define processing queue - self.processing_queue = queue.Queue() - - Avalon.info(_("Loading files into processing queue")) - Avalon.debug_info(_("Input path(s): {}").format(self.input)) - - # make output directory if the input is a list or a directory - if isinstance(self.input, list) or self.input.is_dir(): - self.output.mkdir(parents=True, exist_ok=True) - - input_files = [] - - # if input is single directory - # put it in a list for compability with the following code - if not isinstance(self.input, list): - input_paths = [self.input] - else: - input_paths = self.input - - # flatten directories into file paths - for input_path in input_paths: - - # if the input path is a single file - # add the file's path object to input_files - if input_path.is_file(): - input_files.append(input_path) - - # if the input path is a directory - # add all files under the directory into the input_files (non-recursive) - elif input_path.is_dir(): - input_files.extend([f for f in input_path.iterdir() if f.is_file()]) - - output_paths = [] - - for input_path in input_files: - - # get file type - # try python-magic if it's available + logger.info(f"Upscaler process {self.name} initiating") + driver_objects = {} + while self.running: try: - input_file_mime_type = magic.from_file( - str(input_path.absolute()), mime=True - ) - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] - except Exception: - input_file_mime_type = input_file_type = input_file_subtype = "" + try: + # get new job from queue + ( + frame_index, + (image0, image1), + ( + output_width, + output_height, + noise, + difference_threshold, + driver, + ), + ) = self.processing_queue.get(False) - # if python-magic doesn't determine the file to be an image/video file - # fall back to mimetypes to guess the file type based on the extension - if input_file_type not in ["image", "video"]: - # in case python-magic fails to detect file type - # try guessing file mime type with mimetypes - input_file_mime_type = mimetypes.guess_type(input_path.name)[0] - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] + # destructure settings + except queue.Empty: + time.sleep(0.1) + continue - Avalon.debug_info(_("File MIME type: {}").format(input_file_mime_type)) - - # set default output file suffixes - # if image type is GIF, default output suffix is also .gif - if input_file_mime_type == "image/gif": - output_path = self.output / self.output_file_name_format_string.format( - original_file_name=input_path.stem, extension=".gif" + difference = ImageChops.difference(image0, image1) + difference_stat = ImageStat.Stat(difference) + difference_ratio = ( + sum(difference_stat.mean) / (len(difference_stat.mean) * 255) * 100 ) - elif input_file_type == "image": - output_path = self.output / self.output_file_name_format_string.format( - original_file_name=input_path.stem, - extension=self.image_output_extension, - ) + # if the difference is lower than threshold + # process the interpolation + if difference_ratio < difference_threshold: - elif input_file_type == "video": - output_path = self.output / self.output_file_name_format_string.format( - original_file_name=input_path.stem, - extension=self.video_output_extension, - ) - - # if file is none of: image, image/gif, video - # skip to the next task - else: - Avalon.error( - _("File {} ({}) neither an image nor a video").format( - input_path, input_file_mime_type - ) - ) - Avalon.warning(_("Skipping this file")) - continue - - # if there is only one input file - # do not modify output file suffix - if isinstance(self.input, pathlib.Path) and self.input.is_file(): - output_path = self.output - - output_path_id = 0 - while str(output_path) in output_paths: - output_path = output_path.parent / pathlib.Path( - f"{output_path.stem}_{output_path_id}{output_path.suffix}" - ) - output_path_id += 1 - - # record output path - output_paths.append(str(output_path)) - - # push file information into processing queue - self.processing_queue.put( - ( - input_path.absolute(), - output_path.absolute(), - input_file_mime_type, - input_file_type, - input_file_subtype, - ) - ) - - # check argument sanity before running - self._check_arguments() - - # record file count for external calls - self.total_files = self.processing_queue.qsize() - - Avalon.info(_("Loaded files into processing queue")) - # print all files in queue for debugging - for job in self.processing_queue.queue: - Avalon.debug_info(_("Input file: {}").format(job[0].absolute())) - - try: - while not self.processing_queue.empty(): - - # get new job from queue - ( - self.current_input_file, - output_path, - input_file_mime_type, - input_file_type, - input_file_subtype, - ) = self.processing_queue.get() - - # get current job starting time for GUI calculations - self.current_processing_starting_time = time.time() - - # get video information JSON using FFprobe - Avalon.info(_("Reading file information")) - file_info = self.ffmpeg_object.probe_file_info(self.current_input_file) - - # create temporary directories for storing frames - self.create_temp_directories() - - # start handling input - # if input file is a static image - if input_file_type == "image" and input_file_subtype != "gif": - Avalon.info(_("Starting upscaling image")) - - # copy original file into the pre-processing directory - shutil.copy( - self.current_input_file, - self.extracted_frames / self.current_input_file.name, - ) - - width = int(file_info["streams"][0]["width"]) - height = int(file_info["streams"][0]["height"]) - framerate = self.total_frames = 1 - - # elif input_file_mime_type == 'image/gif' or input_file_type == 'video': - else: - Avalon.info(_("Starting upscaling video/GIF")) - - # find index of video stream - video_stream_index = None - for stream in file_info["streams"]: - if stream["codec_type"] == "video": - video_stream_index = stream["index"] - break - - # exit if no video stream found - if video_stream_index is None: - Avalon.error(_("Aborting: No video stream found")) - raise StreamNotFoundError("no video stream found") - - # get average frame rate of video stream - framerate = float( - Fraction( - file_info["streams"][video_stream_index]["r_frame_rate"] - ) - ) - width = int(file_info["streams"][video_stream_index]["width"]) - height = int(file_info["streams"][video_stream_index]["height"]) - - # get total number of frames - Avalon.info(_("Getting total number of frames in the file")) - - # if container stores total number of frames in nb_frames, fetch it directly - if "nb_frames" in file_info["streams"][video_stream_index]: - self.total_frames = int( - file_info["streams"][video_stream_index]["nb_frames"] - ) - - # otherwise call FFprobe to count the total number of frames - else: - self.total_frames = self.ffmpeg_object.get_number_of_frames( - self.current_input_file - ) - - # calculate scale width/height/ratio and scaling jobs if required - Avalon.info(_("Calculating scaling parameters")) - - # create a local copy of the global output settings - output_scale = self.scale_ratio - output_width = self.scale_width - output_height = self.scale_height - - # calculate output width and height if scale ratio is specified - if output_scale is not None: - output_width = int(math.ceil(width * output_scale / 2.0) * 2) - output_height = int(math.ceil(height * output_scale / 2.0) * 2) - - else: - # scale keeping aspect ratio is only one of width/height is given - if output_width == 0 or output_width is None: - output_width = output_height / height * width - - elif output_height == 0 or output_height is None: - output_height = output_width / width * height - - output_width = int(math.ceil(output_width / 2.0) * 2) - output_height = int(math.ceil(output_height / 2.0) * 2) + width, height = image1.size # calculate required minimum scale ratio output_scale = max(output_width / width, output_height / height) - # if driver is one of the drivers that doesn't support arbitrary scaling ratio - # TODO: more documentations on this block - if self.driver in DRIVER_FIXED_SCALING_RATIOS: - # select the optimal driver scaling ratio to use supported_scaling_ratios = sorted( - DRIVER_FIXED_SCALING_RATIOS[self.driver] + DRIVER_FIXED_SCALING_RATIOS[driver] ) remaining_scaling_ratio = math.ceil(output_scale) - self.scaling_jobs = [] + scaling_jobs = [] # if the scaling ratio is 1.0 # apply the smallest scaling ratio available if remaining_scaling_ratio == 1: - self.scaling_jobs.append(supported_scaling_ratios[0]) + scaling_jobs.append(supported_scaling_ratios[0]) else: while remaining_scaling_ratio > 1: for ratio in supported_scaling_ratios: if ratio >= remaining_scaling_ratio: - self.scaling_jobs.append(ratio) + scaling_jobs.append(ratio) remaining_scaling_ratio /= ratio break @@ -775,7 +114,7 @@ class Upscaler: for i in supported_scaling_ratios: for j in supported_scaling_ratios: if i * j >= remaining_scaling_ratio: - self.scaling_jobs.extend([i, j]) + scaling_jobs.extend([i, j]) remaining_scaling_ratio /= i * j found = True break @@ -783,245 +122,53 @@ class Upscaler: break if found is False: - self.scaling_jobs.append( - supported_scaling_ratios[-1] - ) + scaling_jobs.append(supported_scaling_ratios[-1]) remaining_scaling_ratio /= supported_scaling_ratios[ -1 ] - else: - self.scaling_jobs = [output_scale] + for job in scaling_jobs: - # print file information - Avalon.debug_info(_("Framerate: {}").format(framerate)) - Avalon.debug_info(_("Width: {}").format(width)) - Avalon.debug_info(_("Height: {}").format(height)) - Avalon.debug_info( - _("Total number of frames: {}").format(self.total_frames) - ) - Avalon.debug_info(_("Output width: {}").format(output_width)) - Avalon.debug_info(_("Output height: {}").format(output_height)) - Avalon.debug_info(_("Required scale ratio: {}").format(output_scale)) - Avalon.debug_info( - _("Upscaling jobs queue: {}").format(self.scaling_jobs) - ) - - # extract frames from video - if input_file_mime_type == "image/gif" or input_file_type == "video": - self.process_pool.append( - ( - self.ffmpeg_object.extract_frames( - self.current_input_file, self.extracted_frames + # select a driver object with the required settings + # create a new object if none are available + driver_object = driver_objects.get((driver, job)) + if driver_object is None: + driver_object = DRIVER_CLASSES[driver]( + scale=job, noise=noise ) - ) - ) - self._wait() + driver_objects[(driver, job)] = driver_object - # if driver is waifu2x-caffe - # pass pixel format output depth information - if self.driver == "waifu2x_caffe": - # get a dict of all pixel formats and corresponding bit depth - pixel_formats = self.ffmpeg_object.get_pixel_formats() + # process the image with the selected driver + image1 = driver_object.process(image1) - # try getting pixel format's corresponding bti depth - try: - self.driver_settings["output_depth"] = pixel_formats[ - self.ffmpeg_object.pixel_format - ] - except KeyError: - Avalon.error( - _("Unsupported pixel format: {}").format( - self.ffmpeg_object.pixel_format - ) - ) - raise UnsupportedPixelError( - f"unsupported pixel format {self.ffmpeg_object.pixel_format}" - ) + # downscale the image to the desired output size and save the image to disk + image1 = image1.resize((output_width, output_height), Image.LANCZOS) + self.processed_frames[frame_index] = image1 - # upscale images one by one using waifu2x - Avalon.info(_("Starting to upscale extracted frames")) - upscale_begin_time = time.time() - - self.current_pass = 1 - if self.driver == "waifu2x_caffe": - self.driver_object.set_scale_resolution(output_width, output_height) - else: - self.driver_object.set_scale_ratio(self.scaling_jobs[0]) - self._upscale_frames(self.extracted_frames, self.upscaled_frames) - for job in self.scaling_jobs[1:]: - self.current_pass += 1 - self.driver_object.set_scale_ratio(job) - shutil.rmtree(self.extracted_frames) - shutil.move(self.upscaled_frames, self.extracted_frames) - self.upscaled_frames.mkdir(parents=True, exist_ok=True) - self._upscale_frames(self.extracted_frames, self.upscaled_frames) - - Avalon.info(_("Upscaling completed")) - Avalon.info( - _("Average processing speed: {} seconds per frame").format( - self.total_frames / (time.time() - upscale_begin_time) - ) - ) - - # downscale frames with Lanczos - Avalon.info(_("Lanczos downscaling frames")) - shutil.rmtree(self.extracted_frames) - shutil.move(self.upscaled_frames, self.extracted_frames) - self.upscaled_frames.mkdir(parents=True, exist_ok=True) - - for image in tqdm( - [ - i - for i in self.extracted_frames.iterdir() - if i.is_file() and i.name.endswith(self.extracted_frame_format) - ], - ascii=True, - desc=_("Downscaling"), - ): - image_object = Image.open(image) - - # if the image dimensions are not equal to the output size - # resize the image using Lanczos - if (image_object.width, image_object.height) != ( - output_width, - output_height, - ): - image_object.resize( - (output_width, output_height), Image.LANCZOS - ).save(self.upscaled_frames / image.name) - image_object.close() - - # if the image's dimensions are already equal to the output size - # move image to the finished directory - else: - image_object.close() - shutil.move(image, self.upscaled_frames / image.name) - - # start handling output - # output can be either GIF or video - if input_file_type == "image" and input_file_subtype != "gif": - - Avalon.info(_("Exporting image")) - - # there should be only one image in the directory - shutil.move( - [f for f in self.upscaled_frames.iterdir() if f.is_file()][0], - output_path, - ) - - # elif input_file_mime_type == 'image/gif' or input_file_type == 'video': + # if the difference is greater than threshold + # there's a change in camera angle, ignore else: - # if the desired output is gif file - if output_path.suffix.lower() == ".gif": - Avalon.info(_("Converting extracted frames into GIF image")) - gifski_object = Gifski(self.gifski_settings) - self.process_pool.append( - gifski_object.make_gif( - self.upscaled_frames, - output_path, - framerate, - self.extracted_frame_format, - output_width, - output_height, - ) - ) - self._wait() - Avalon.info(_("Conversion completed")) + # make sure the previous frame has been processed + while self.processed_frames[frame_index - 1] is None: + time.sleep(0.1) - # if the desired output is video - else: - # frames to video - Avalon.info(_("Converting extracted frames into video")) - self.process_pool.append( - self.ffmpeg_object.assemble_video( - framerate, self.upscaled_frames - ) - ) - # f'{scale_width}x{scale_height}' - self._wait() - Avalon.info(_("Conversion completed")) + # make the current image the same as the previous result + self.processed_frames[frame_index] = self.processed_frames[ + frame_index - 1 + ] - try: - # migrate audio tracks and subtitles - Avalon.info( - _( - "Migrating audio, subtitles and other streams to upscaled video" - ) - ) - self.process_pool.append( - self.ffmpeg_object.migrate_streams( - self.current_input_file, - output_path, - self.upscaled_frames, - ) - ) - self._wait() + # send exceptions into the client connection pipe + except (SystemExit, KeyboardInterrupt): + break - # if failed to copy streams - # use file with only video stream - except subprocess.CalledProcessError: - traceback.print_exc() - Avalon.error(_("Failed to migrate streams")) - Avalon.warning( - _("Trying to output video without additional streams") - ) + except Exception as e: + logger.exception(e) + break - if input_file_mime_type == "image/gif": - # copy will overwrite destination content if exists - shutil.copy( - self.upscaled_frames - / self.ffmpeg_object.intermediate_file_name, - output_path, - ) - - else: - # construct output file path - output_file_name = f"{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}" - output_video_path = ( - output_path.parent / output_file_name - ) - - # if output file already exists - # create temporary directory in output folder - # temporary directories generated by tempfile are guaranteed to be unique - # and won't conflict with other files - if output_video_path.exists(): - Avalon.error(_("Output video file exists")) - - temporary_directory = pathlib.Path( - tempfile.mkdtemp(dir=output_path.parent) - ) - output_video_path = ( - temporary_directory / output_file_name - ) - Avalon.info( - _("Created temporary directory to contain file") - ) - - # move file to new destination - Avalon.info( - _("Writing intermediate file to: {}").format( - output_video_path.absolute() - ) - ) - shutil.move( - self.upscaled_frames - / self.ffmpeg_object.intermediate_file_name, - output_video_path, - ) - - # increment total number of files processed - self.cleanup_temp_directories() - self.processing_queue.task_done() - self.total_processed += 1 - - except (Exception, KeyboardInterrupt, SystemExit) as e: - with contextlib.suppress(ValueError, AttributeError): - self.cleanup_temp_directories() - self.running = False - raise e - - # signal upscaling completion + logger.info(f"Upscaler process {self.name} terminating") + self.running = False + return super().run() + + def _stop(self, signal_number, frame): self.running = False diff --git a/video2x/video2x.pot b/video2x/video2x.pot deleted file mode 100644 index 949ef16..0000000 --- a/video2x/video2x.pot +++ /dev/null @@ -1,379 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2021-01-23 16:45-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=cp1252\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" - - -#: progress_monitor.py:40 -msgid "Processing: {} (pass {}/{})" -msgstr "" - -#: upscaler.py:160 -msgid "Specified or default cache directory is a file/link" -msgstr "" - -#: upscaler.py:167 -msgid "Creating cache directory {}" -msgstr "" - -#: upscaler.py:174 -msgid "Unable to create {}" -msgstr "" - -#: upscaler.py:183 -msgid "Extracted frames are being saved to: {}" -msgstr "" - -#: upscaler.py:189 -msgid "Upscaled frames are being saved to: {}" -msgstr "" - -#: upscaler.py:203 -msgid "Cleaning up cache directory: {}" -msgstr "" - -#: upscaler.py:208 -msgid "Unable to delete: {}" -msgstr "" - -#: upscaler.py:214 upscaler.py:239 upscaler.py:250 -msgid "Input and output path type mismatch" -msgstr "" - -#: upscaler.py:215 -msgid "Input is multiple files but output is not directory" -msgstr "" - -#: upscaler.py:220 -msgid "Input path {} is neither a file nor a directory" -msgstr "" - -#: upscaler.py:230 upscaler.py:256 -msgid "Input directory and output directory cannot be the same" -msgstr "" - -#: upscaler.py:240 -msgid "Input is single file but output is directory" -msgstr "" - -#: upscaler.py:243 -msgid "No suffix found in output file path" -msgstr "" - -#: upscaler.py:244 -msgid "Suffix must be specified" -msgstr "" - -#: upscaler.py:251 -msgid "Input is directory but output is existing single file" -msgstr "" - -#: upscaler.py:264 -msgid "Input path is neither a file nor a directory" -msgstr "" - -#: upscaler.py:280 -msgid "FFmpeg or FFprobe cannot be found under the specified path" -msgstr "" - -#: upscaler.py:282 upscaler.py:295 -msgid "Please check the configuration file settings" -msgstr "" - -#: upscaler.py:294 -msgid "Specified driver executable directory doesn't exist" -msgstr "" - -#: upscaler.py:323 -msgid "Failed to parse driver argument: {}" -msgstr "" - -#: upscaler.py:346 -msgid "Unrecognized driver: {}" -msgstr "" - -#: upscaler.py:397 -msgid "Starting progress monitor" -msgstr "" - -#: upscaler.py:402 -msgid "Starting upscaled image cleaner" -msgstr "" - -#: upscaler.py:413 upscaler.py:432 -msgid "Killing progress monitor" -msgstr "" - -#: upscaler.py:416 upscaler.py:435 -msgid "Killing upscaled image cleaner" -msgstr "" - -#: upscaler.py:439 -msgid "Terminating all processes" -msgstr "" - -#: upscaler.py:445 -msgid "Main process waiting for subprocesses to exit" -msgstr "" - -#: upscaler.py:465 upscaler.py:475 -msgid "Subprocess {} exited with code {}" -msgstr "" - -#: upscaler.py:484 -msgid "Stop signal received" -msgstr "" - -#: upscaler.py:489 -msgid "Subprocess execution ran into an error" -msgstr "" - -#: upscaler.py:523 -msgid "Loading files into processing queue" -msgstr "" - -#: upscaler.py:524 -msgid "Input path(s): {}" -msgstr "" - -#: upscaler.py:576 -msgid "File MIME type: {}" -msgstr "" - -#: upscaler.py:601 -msgid "File {} ({}) neither an image nor a video" -msgstr "" - -#: upscaler.py:605 -msgid "Skipping this file" -msgstr "" - -#: upscaler.py:640 -msgid "Loaded files into processing queue" -msgstr "" - -#: upscaler.py:643 -msgid "Input file: {}" -msgstr "" - -#: upscaler.py:661 -msgid "Reading file information" -msgstr "" - -#: upscaler.py:670 -msgid "Starting upscaling image" -msgstr "" - -#: upscaler.py:684 -msgid "Starting upscaling video/GIF" -msgstr "" - -#: upscaler.py:695 -msgid "Aborting: No video stream found" -msgstr "" - -#: upscaler.py:708 -msgid "Getting total number of frames in the file" -msgstr "" - -#: upscaler.py:723 -msgid "Calculating scaling parameters" -msgstr "" - -#: upscaler.py:797 -msgid "Framerate: {}" -msgstr "" - -#: upscaler.py:798 -msgid "Width: {}" -msgstr "" - -#: upscaler.py:799 -msgid "Height: {}" -msgstr "" - -#: upscaler.py:801 -msgid "Total number of frames: {}" -msgstr "" - -#: upscaler.py:803 -msgid "Output width: {}" -msgstr "" - -#: upscaler.py:804 -msgid "Output height: {}" -msgstr "" - -#: upscaler.py:805 -msgid "Required scale ratio: {}" -msgstr "" - -#: upscaler.py:807 -msgid "Upscaling jobs queue: {}" -msgstr "" - -#: upscaler.py:834 -msgid "Unsupported pixel format: {}" -msgstr "" - -#: upscaler.py:843 -msgid "Starting to upscale extracted frames" -msgstr "" - -#: upscaler.py:860 -msgid "Upscaling completed" -msgstr "" - -#: upscaler.py:862 -msgid "Average processing speed: {} seconds per frame" -msgstr "" - -#: upscaler.py:868 -msgid "Lanczos downscaling frames" -msgstr "" - -#: upscaler.py:880 -msgid "Downscaling" -msgstr "" - -#: upscaler.py:905 -msgid "Exporting image" -msgstr "" - -#: upscaler.py:918 -msgid "Converting extracted frames into GIF image" -msgstr "" - -#: upscaler.py:931 upscaler.py:944 -msgid "Conversion completed" -msgstr "" - -#: upscaler.py:936 -msgid "Converting extracted frames into video" -msgstr "" - -#: upscaler.py:949 -msgid "Migrating audio, subtitles and other streams to upscaled video" -msgstr "" - -#: upscaler.py:966 -msgid "Failed to migrate streams" -msgstr "" - -#: upscaler.py:968 -msgid "Trying to output video without additional streams" -msgstr "" - -#: upscaler.py:991 -msgid "Output video file exists" -msgstr "" - -#: upscaler.py:1000 -msgid "Created temporary directory to contain file" -msgstr "" - -#: upscaler.py:1005 -msgid "Writing intermediate file to: {}" -msgstr "" - -#: video2x.py:89 -msgid "" -"Video2X CLI Version: {}\n" -"Upscaler Version: {}\n" -"Author: K4YT3X\n" -"License: GNU GPL v3\n" -"Github Page: https://github.com/k4yt3x/video2x\n" -"Contact: k4yt3x@k4yt3x.com" -msgstr "" - -#: video2x.py:117 -msgid "Video2X Options" -msgstr "" - -#: video2x.py:120 -msgid "show this help message and exit" -msgstr "" - -#: video2x.py:133 -msgid "source video file/directory" -msgstr "" - -#: video2x.py:141 -msgid "output video file/directory" -msgstr "" - -#: video2x.py:149 -msgid "Video2X config file path" -msgstr "" - -#: video2x.py:154 -msgid "log file path" -msgstr "" - -#: video2x.py:159 -msgid "display version, lawful information and exit" -msgstr "" - -#: video2x.py:164 -msgid "Upscaling Options" -msgstr "" - -#: video2x.py:167 -msgid "scaling ratio" -msgstr "" - -#: video2x.py:171 -msgid "output width" -msgstr "" - -#: video2x.py:175 -msgid "output height" -msgstr "" - -#: video2x.py:181 -msgid "upscaling driver" -msgstr "" - -#: video2x.py:189 -msgid "number of processes to use for upscaling" -msgstr "" - -#: video2x.py:197 -msgid "preserve extracted and upscaled frames" -msgstr "" - -#: video2x.py:241 -msgid "This file cannot be imported" -msgstr "" - -#: video2x.py:259 -msgid "Specify either scaling ratio or scaling resolution, not both" -msgstr "" - -#: video2x.py:265 -msgid "Either scaling ratio or scaling resolution needs to be specified" -msgstr "" - -#: video2x.py:353 -msgid "Program completed, taking {} seconds" -msgstr "" - -#: video2x.py:360 -msgid "An exception has occurred" -msgstr "" - -#: video2x.py:375 -msgid "The error log file can be found at: {}" -msgstr "" - diff --git a/video2x/video2x.py b/video2x/video2x.py index ecbfe4d..af2bfa1 100755 --- a/video2x/video2x.py +++ b/video2x/video2x.py @@ -10,10 +10,10 @@ __ __ _ _ ___ __ __ \/ |_| \__,_| \___| \___/ |____| /_/ \_\ -Name: Video2X Controller +Name: Video2X Creator: K4YT3X Date Created: Feb 24, 2018 -Last Modified: January 23, 2021 +Last Modified: July 3, 2021 Editor: BrianPetkovsek Last Modified: June 17, 2019 @@ -49,332 +49,412 @@ smooth and edges sharp. """ # local imports -from bilogger import BiLogger -from upscaler import AVAILABLE_DRIVERS -from upscaler import UPSCALER_VERSION -from upscaler import Upscaler +from .decoder import VideoDecoder +from .encoder import VideoEncoder +from .interpolator import Interpolator +from .upscaler import Upscaler # built-in imports import argparse -import gettext -import importlib -import locale +import math +import multiprocessing import os import pathlib import sys -import tempfile import time -import traceback -import yaml # third-party imports -from avalon_framework import Avalon +from loguru import logger +from rich import print +from tqdm import tqdm +import cv2 +import ffmpeg +import numpy as np -# internationalization constants -DOMAIN = "video2x" -LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / "locale" -# getting default locale settings -default_locale, encoding = locale.getdefaultlocale() -if default_locale is None: - default_locale = "en_US" -language = gettext.translation( - DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True -) -language.install() -_ = language.gettext +VERSION = "5.0.0" -CLI_VERSION = "4.3.3" - -LEGAL_INFO = _( - """Video2X CLI Version: {} -Upscaler Version: {} +LEGAL_INFO = """Video2X {} Author: K4YT3X License: GNU GPL v3 Github Page: https://github.com/k4yt3x/video2x -Contact: k4yt3x@k4yt3x.com""" -).format(CLI_VERSION, UPSCALER_VERSION) +Contact: k4yt3x@k4yt3x.com""".format( + VERSION +) -LOGO = r""" - __ __ _ _ ___ __ __ - \ \ / / (_) | | |__ \ \ \ / / - \ \ / / _ __| | ___ ___ ) | \ V / - \ \/ / | | / _` | / _ \ / _ \ / / > < - \ / | | | (_| | | __/ | (_) | / /_ / . \ - \/ |_| \__,_| \___| \___/ |____| /_/ \_\ -""" +UPSCALING_DRIVERS = [ + "waifu2x", + "srmd", + "realsr", +] + +INTERPOLATION_DRIVERS = ["rife"] + +# fixed scaling ratios supported by the drivers +# that only support certain fixed scale ratios +DRIVER_FIXED_SCALING_RATIOS = { + "waifu2x": [1, 2], + "srmd": [2, 3, 4], + "realsr": [4], +} -def parse_arguments(): - """parse CLI arguments""" +class Video2X: + """ + Video2X class + + provides two vital functions: + - upscale: perform upscaling on a file + - interpolate: perform motion interpolation on a file + """ + + def __init__(self): + self.version = "5.0.0" + + def _get_video_info(self, path: pathlib.Path): + """ + get video file information with FFmpeg + + :param path pathlib.Path: video file path + :raises RuntimeError: raised when video stream isn't found + """ + # probe video file info + logger.info("Reading input video information") + for stream in ffmpeg.probe(path)["streams"]: + if stream["codec_type"] == "video": + video_info = stream + break + else: + raise RuntimeError("unable to find video stream") + + # get total number of frames to be processed + total_frames = int(cv2.VideoCapture(str(path)).get(cv2.CAP_PROP_FRAME_COUNT)) + frame_rate = cv2.VideoCapture(str(path)).get(cv2.CAP_PROP_FPS) + + return video_info["width"], video_info["height"], total_frames, frame_rate + + def _run( + self, + input_path: pathlib.Path, + width: int, + height: int, + total_frames: int, + frame_rate: float, + output_path: pathlib.Path, + output_width: int, + output_height: int, + Processor: object, + mode: str, + processes: int, + processing_settings: tuple, + ): + # initialize values + self.processor_processes = [] + self.processing_queue = multiprocessing.Queue() + processed_frames = multiprocessing.Manager().list([None] * total_frames) + self.processed = multiprocessing.Value("I", 0) + + # set up and start decoder thread + logger.info("Starting video decoder") + self.decoder = VideoDecoder( + input_path, + width, + height, + frame_rate, + self.processing_queue, + processing_settings, + ) + self.decoder.start() + + # in interpolate mode, the frame rate is doubled + if mode == "upscale": + progress = tqdm(total=total_frames, desc=f"UPSC {input_path.name}") + # elif mode == "interpolate": + else: + frame_rate *= 2 + progress = tqdm(total=total_frames, desc=f"INTERP {input_path.name}") + + # set up and start encoder thread + logger.info("Starting video encoder") + self.encoder = VideoEncoder( + input_path, + frame_rate, + output_path, + output_width, + output_height, + total_frames, + processed_frames, + self.processed, + ) + self.encoder.start() + + # create upscaler processes + for process_name in range(processes): + process = Processor(self.processing_queue, processed_frames) + process.name = str(process_name) + process.daemon = True + process.start() + self.processor_processes.append(process) + + # create progress bar + + try: + # wait for jobs in queue to deplete + while self.encoder.is_alive() is True: + for process in self.processor_processes: + if not process.is_alive(): + raise Exception("process died unexpectedly") + progress.n = self.processed.value + progress.refresh() + time.sleep(0.1) + + logger.info("Encoding has completed") + progress.n = self.processed.value + progress.refresh() + + # if SIGTERM is received or ^C is pressed + # TODO: pause and continue here + except (SystemExit, KeyboardInterrupt): + logger.warning("Exit signal received, terminating") + + finally: + # mark processing queue as closed + self.processing_queue.close() + + # stop upscaler processes + logger.info("Stopping upscaler processes") + for process in self.processor_processes: + process.terminate() + + # wait for processes to finish + for process in self.processor_processes: + process.join() + + # ensure both the decoder and the encoder have exited + self.decoder.stop() + self.encoder.stop() + self.decoder.join() + self.encoder.join() + + def upscale( + self, + input_path: pathlib.Path, + output_path: pathlib.Path, + output_width: int, + output_height: int, + noise: int, + processes: int, + threshold: float, + driver: str, + ) -> None: + + # get basic video information + width, height, total_frames, frame_rate = self._get_video_info(input_path) + + # automatically calculate output width and height if only one is given + if output_width == 0 or output_width is None: + output_width = output_height / height * width + + elif output_height == 0 or output_height is None: + output_height = output_width / width * height + + # sanitize output width and height to be divisible by 2 + output_width = int(math.ceil(output_width / 2.0) * 2) + output_height = int(math.ceil(output_height / 2.0) * 2) + + # start processing + self._run( + input_path, + width, + height, + total_frames, + frame_rate, + output_path, + output_width, + output_height, + Upscaler, + "upscale", + processes, + ( + output_width, + output_height, + noise, + threshold, + driver, + ), + ) + + def interpolate( + self, + input_path: pathlib.Path, + output_path: pathlib.Path, + processes: int, + threshold: float, + driver: str, + ) -> None: + + # get video basic information + width, height, original_frames, frame_rate = self._get_video_info(input_path) + + # calculate the number of total output frames + total_frames = original_frames * 2 - 1 + + # start processing + self._run( + input_path, + width, + height, + total_frames, + frame_rate, + output_path, + width, + height, + Interpolator, + "interpolate", + processes, + (threshold, driver), + ) + + +def parse_arguments() -> argparse.Namespace: + """ + parse command line arguments + + :rtype argparse.Namespace: command parsing results + """ parser = argparse.ArgumentParser( prog="video2x", formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False, ) - - # video options - video2x_options = parser.add_argument_group(_("Video2X Options")) - - video2x_options.add_argument( - "--help", action="help", help=_("show this help message and exit") + parser.add_argument("--help", action="help", help="show this help message and exit") + parser.add_argument( + "-v", "--version", help="show version information and exit", action="store_true" ) - - # if help is in arguments list - # do not require input and output path to be specified - require_input_output = True - if "-h" in sys.argv or "--help" in sys.argv: - require_input_output = False - - video2x_options.add_argument( + parser.add_argument( "-i", "--input", type=pathlib.Path, - help=_("source video file/directory"), - required=require_input_output, + help="input file/directory path", + required=True, ) - - video2x_options.add_argument( + parser.add_argument( "-o", "--output", type=pathlib.Path, - help=_("output video file/directory"), - required=require_input_output, + help="output file/directory path", + required=True, + ) + parser.add_argument( + "-p", "--processes", type=int, help="number of processes to launch", default=1 + ) + parser.add_argument( + "-l", + "--loglevel", + choices=["trace", "debug", "info", "success", "warning", "error", "critical"], + default="info", ) - video2x_options.add_argument( - "-c", - "--config", - type=pathlib.Path, - help=_("Video2X config file path"), - action="store", - default=pathlib.Path(__file__).parent.absolute() / "video2x.yaml", + # upscaler arguments + action = parser.add_subparsers( + help="action to perform", dest="action", required=True ) - video2x_options.add_argument("--log", type=pathlib.Path, help=_("log file path")) - - video2x_options.add_argument( - "-v", - "--version", - help=_("display version, lawful information and exit"), - action="store_true", + upscale = action.add_parser("upscale", help="upscale a file", add_help=False) + upscale.add_argument( + "--help", action="help", help="show this help message and exit" ) - - # scaling options - upscaling_options = parser.add_argument_group(_("Upscaling Options")) - - upscaling_options.add_argument( - "-r", "--ratio", help=_("scaling ratio"), action="store", type=float - ) - - upscaling_options.add_argument( - "-w", "--width", help=_("output width"), action="store", type=float - ) - - upscaling_options.add_argument( - "-h", "--height", help=_("output height"), action="store", type=float - ) - - upscaling_options.add_argument( + upscale.add_argument("-w", "--width", type=int, help="output width") + upscale.add_argument("-h", "--height", type=int, help="output height") + upscale.add_argument("-n", "--noise", type=int, help="denoise level", default=3) + upscale.add_argument( "-d", "--driver", - help=_("upscaling driver"), - choices=AVAILABLE_DRIVERS, - default="waifu2x_ncnn_vulkan", + choices=UPSCALING_DRIVERS, + help="driver to use for upscaling", + default=UPSCALING_DRIVERS[0], + ) + upscale.add_argument( + "-t", + "--threshold", + type=float, + help="if the % difference between two adjacent frames exceeds this value, two images are deemed the same", + default=0.1, ) - upscaling_options.add_argument( - "-p", - "--processes", - help=_("number of processes to use for upscaling"), - action="store", - type=int, - default=1, + # interpolator arguments + interpolate = action.add_parser( + "interpolate", help="interpolate frames for file", add_help=False + ) + interpolate.add_argument( + "--help", action="help", help="show this help message and exit" + ) + interpolate.add_argument( + "-d", + "--driver", + choices=UPSCALING_DRIVERS, + help="driver to use for upscaling", + default=INTERPOLATION_DRIVERS[0], + ) + interpolate.add_argument( + "-t", + "--threshold", + type=float, + help="if the % difference between two adjacent frames exceeds this value, no interpolation will be performed", + default=10, ) - upscaling_options.add_argument( - "--preserve_frames", - help=_("preserve extracted and upscaled frames"), - action="store_true", - ) - - # if no driver arguments are specified - if "--" not in sys.argv: - video2x_args = parser.parse_args() - return video2x_args, None - - # if driver arguments are specified - else: - video2x_args = parser.parse_args(sys.argv[1 : sys.argv.index("--")]) - wrapper = getattr( - importlib.import_module(f"wrappers.{video2x_args.driver}"), "WrapperMain" - ) - driver_args = wrapper.parse_arguments(sys.argv[sys.argv.index("--") + 1 :]) - return video2x_args, driver_args + return parser.parse_args() -def print_logo(): - """print video2x logo""" - print(LOGO) - print(f'\n{"Video2X Video Enlarger".rjust(40, " ")}') - print(f'\n{Avalon.FM.BD}{f"Version {CLI_VERSION}".rjust(36, " ")}{Avalon.FM.RST}\n') - - -def read_config(config_file: pathlib.Path) -> dict: - """read video2x configurations from config file - - Arguments: - config_file {pathlib.Path} -- video2x configuration file pathlib.Path - - Returns: - dict -- dictionary of video2x configuration +def main(): + """ + command line direct invocation + program entry point """ - with open(config_file, "r") as config: - return yaml.load(config, Loader=yaml.FullLoader) + try: + # parse command line arguments + args = parse_arguments() -# /////////////////// Execution /////////////////// # + # set logger level + if os.environ.get("LOGURU_LEVEL") is None: + os.environ["LOGURU_LEVEL"] = args.loglevel.upper() -# this is not a library -if __name__ != "__main__": - Avalon.error(_("This file cannot be imported")) - raise ImportError(f"{__file__} cannot be imported") + # set logger format + if os.environ.get("LOGURU_FORMAT") is None: + os.environ[ + "LOGURU_FORMAT" + ] = "{time:HH:mm:ss!UTC} | {level: <8} | {message}" -# print video2x logo -print_logo() + # display version and lawful informaition + if args.version: + print(LEGAL_INFO) + sys.exit(0) -# parse command line arguments -video2x_args, driver_args = parse_arguments() + # initialize upscaler object + video2x = Video2X() -# display version and lawful informaition -if video2x_args.version: - print(LEGAL_INFO) - sys.exit(0) + if args.action == "upscale": + video2x.upscale( + args.input, + args.output, + args.width, + args.height, + args.noise, + args.processes, + args.threshold, + args.driver, + ) -# additional checks on upscaling arguments -if video2x_args.ratio is not None and ( - video2x_args.width is not None or video2x_args.height is not None -): - Avalon.error(_("Specify either scaling ratio or scaling resolution, not both")) - sys.exit(1) - -elif video2x_args.ratio is None and ( - video2x_args.width is None or video2x_args.height is None -): - Avalon.error(_("Either scaling ratio or scaling resolution needs to be specified")) - sys.exit(1) - -# redirect output to both terminal and log file -if video2x_args.log is not None: - log_file = video2x_args.log.open(mode="a+", encoding="utf-8") -else: - log_file = tempfile.TemporaryFile( - mode="a+", suffix=".log", prefix="video2x_", encoding="utf-8" - ) - -original_stdout = sys.stdout -original_stderr = sys.stderr -sys.stdout = BiLogger(sys.stdout, log_file) -sys.stderr = BiLogger(sys.stderr, log_file) - -# read configurations from configuration file -config = read_config(video2x_args.config) - -# load waifu2x configuration -driver_settings = config[video2x_args.driver] -driver_settings["path"] = os.path.expandvars(driver_settings["path"]) - -# read FFmpeg configuration -ffmpeg_settings = config["ffmpeg"] -ffmpeg_settings["ffmpeg_path"] = os.path.expandvars(ffmpeg_settings["ffmpeg_path"]) - -# read Gifski configuration -gifski_settings = config["gifski"] -gifski_settings["gifski_path"] = os.path.expandvars(gifski_settings["gifski_path"]) - -# load video2x settings -extracted_frame_format = config["video2x"]["extracted_frame_format"].lower() -output_file_name_format_string = config["video2x"]["output_file_name_format_string"] -image_output_extension = config["video2x"]["image_output_extension"] -video_output_extension = config["video2x"]["video_output_extension"] -preserve_frames = config["video2x"]["preserve_frames"] - -# if preserve frames specified in command line -# overwrite config file options -if video2x_args.preserve_frames is True: - preserve_frames = True - -# if cache directory not specified -# use default path: %TEMP%\video2x -if config["video2x"]["video2x_cache_directory"] is None: - video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / "video2x" -else: - video2x_cache_directory = pathlib.Path(config["video2x"]["video2x_cache_directory"]) - -# overwrite driver_settings with driver_args -if driver_args is not None: - driver_args_dict = vars(driver_args) - for key in driver_args_dict: - if driver_args_dict[key] is not None: - driver_settings[key] = driver_args_dict[key] - -# start execution -try: - # start timer - begin_time = time.time() - - # initialize upscaler object - upscaler = Upscaler( - # required parameters - input_path=video2x_args.input, - output_path=video2x_args.output, - driver_settings=driver_settings, - ffmpeg_settings=ffmpeg_settings, - gifski_settings=gifski_settings, - # optional parameters - driver=video2x_args.driver, - scale_ratio=video2x_args.ratio, - scale_width=video2x_args.width, - scale_height=video2x_args.height, - processes=video2x_args.processes, - video2x_cache_directory=video2x_cache_directory, - extracted_frame_format=extracted_frame_format, - output_file_name_format_string=output_file_name_format_string, - image_output_extension=image_output_extension, - video_output_extension=video_output_extension, - preserve_frames=preserve_frames, - ) - - # run upscaler - upscaler.run() - - Avalon.info( - _("Program completed, taking {} seconds").format( - round((time.time() - begin_time), 5) - ) - ) - -except Exception: - - Avalon.error(_("An exception has occurred")) - traceback.print_exc() - - if video2x_args.log is not None: - log_file_path = video2x_args.log.absolute() - - # if log file path is not specified, create temporary file as permanent log file - # tempfile.TempFile does not have a name attribute and is not guaranteed to have - # a visible name on the file system - else: - log_file_path = tempfile.mkstemp(suffix=".log", prefix="video2x_")[1] - with open(log_file_path, "w", encoding="utf-8") as permanent_log_file: - log_file.seek(0) - permanent_log_file.write(log_file.read()) - - Avalon.error(_("The error log file can be found at: {}").format(log_file_path)) - -finally: - sys.stdout = original_stdout - sys.stderr = original_stderr - log_file.close() + elif args.action == "interpolate": + video2x.interpolate( + args.input, + args.output, + args.processes, + args.threshold, + args.driver, + ) + except Exception as e: + logger.exception(e) diff --git a/video2x/video2x.yaml b/video2x/video2x.yaml deleted file mode 100644 index bd2d500..0000000 --- a/video2x/video2x.yaml +++ /dev/null @@ -1,185 +0,0 @@ -# Name: Video2X Configuration File -# Creator: K4YT3X -# Date Created: October 23, 2018 -# Last Modified: September 28, 2020 -# Values here are the default values. Change the value here to -# save the default value permanently. -# Items commented out are parameters irrelevant to this context -# or parameters handled by Video2X internally. -waifu2x_caffe: - path: '%LOCALAPPDATA%\video2x\waifu2x-caffe\waifu2x-caffe-cui' - tta: 0 # <0|1> 8x slower and slightly high quality - gpu: 0 # gpu device no - batch_size: 1 # input batch size - crop_h: null # input image split size(height) - crop_w: null # input image split size(width) - crop_size: 128 # input image split size - output_depth: 8 # output image chanel depth bit - output_quality: -1 # output image quality - process: gpu # process mode - model_dir: null # path to custom model directory (don't append last / ) - #scale_height: 0 # custom scale height (specifying this will overwrite scale_ratio) - #scale_width: 0 # custom scale width (specifying this will overwrite scale_ratio) - #scale_ratio: null # custom scale ratio - noise_level: 3 # <0|1|2|3> noise reduction level - mode: noise_scale # image processing mode - output_extention: null # extension to output image file when output_path is (auto) or input_path is folder - input_extention_list: null # extension to input image file when input_path is folder - #output_path: null # path to output image file (when input_path is folder, output_path must be folder) - #input_path: null # (required) path to input image file -waifu2x_converter_cpp: - path: '%LOCALAPPDATA%\video2x\waifu2x-converter-cpp\waifu2x-converter-cpp' - #list-supported-formats: null # dump currently supported format list - #list-opencv-formats: null # (deprecated. Use --list-supported-formats) dump opencv supported format list - #list-processor # dump processor list - #output-format: null # The format used when running in recursive/folder mode - png-compression: 5 # Set PNG compression level (0-9), 9 = Max compression (slowest & smallest) - image-quality: -1 # JPEG & WebP Compression quality (-1-101, 0 being smallest size and lowest quality, -1 being default), use 101 for lossless WebP - block-size: 0 # block size - disable-gpu: false # disable GPU - force-OpenCL: false # force to use OpenCL on Intel Platform - processor: -1 # set target processor (-1 uses default device) - jobs: 0 # number of threads launching at the same time - model-dir: null # path to custom model directory (don't append last / ) default: models_rgb - #scale-ratio: 2.0 # custom scale ratio - noise-level: 1 # <0|1|2|3> noise reduction level - mode: noise-scale # image processing mode - log-level: 1 # <0|1|2|3|4> Set log level - silent: true # Enable silent mode. (same as --log-level 1) - tta: 0 # Enable Test-Time Augmentation mode. (0 or 1) - #generate-subdir: 0 # Generate sub folder when recursive directory is enabled. - #auto-naming: 0 # Add postfix to output name when output path is not specified. - #recursive-directory: 0 # Search recursively through directories to find more images to process. - #output: null # path to output image file or directory (you should use the full path) - #input: null # (required) path to input image file or directory (you should use the full path) -waifu2x_ncnn_vulkan: - path: '%LOCALAPPDATA%\video2x\waifu2x-ncnn-vulkan\waifu2x-ncnn-vulkan' - v: null # verbose output - #i: null # input-path: input image path (jpg/png/webp) or directory - #o: null # output-path: output image path (jpg/png/webp) or directory - 'n': 2 # noise-level: denoise level (-1/0/1/2/3, default=0) - s: 2 # scale: upscale ratio (1/2, default=2) - t: 400 # tile-size: tile size (>=32, default=400) - m: null # model-path: waifu2x model path (default=models-cunet) - g: 0 # gpu-id: gpu device to use (default=0) - j: '1:2:2' # thread count for load/proc/save (default=1:2:2) can be 1:2,2,2:2 for multi-gpu - x: false # enable tta mode - #f: png # output image format (jpg/png/webp, default=ext/png) -srmd_ncnn_vulkan: - path: '%LOCALAPPDATA%\video2x\srmd-ncnn-vulkan\srmd-ncnn-vulkan' - v: null # verbose output - #i: null # input-path: input image path (jpg/png) or directory - #o: null # output-path: output image path (png) or directory - 'n': 3 # noise-level: denoise level (-1/0/1/2/3/4/5/6/7/8/9/10, default=3) - s: 2 # upscale ratio (2/3/4, default=2) - t: 400 # tile-size: tile size (>=32, default=400) - m: null # srmd model path (default=models-srmd) - g: 0 # gpu device to use (default=0) - j: '1:2:2' # thread count for load/proc/save (default=1:2:2) - x: false # enable tta mode - #f: png # output image format (jpg/png/webp, default=ext/png) -realsr_ncnn_vulkan: - path: '%LOCALAPPDATA%\video2x\realsr-ncnn-vulkan\realsr-ncnn-vulkan' - v: null # verbose output - #i: null # input-path: input image path (jpg/png) or directory - #o: null # output-path: output image path (png) or directory - s: 4 # upscale ratio (4, default=4) - t: 0 # tile size (>=32/0=auto, default=0) - m: null # realsr model path (default=models-DF2K) - g: 0 # gpu device to use (default=0) - j: '1:2:2' # thread count for load/proc/save (default=1:2:2) - x: false # enable tta mode - #f: png # output image format (jpg/png/webp, default=ext/png) -anime4kcpp: - path: '%LOCALAPPDATA%\video2x\anime4kcpp\CLI\Anime4KCPP_CLI\Anime4KCPP_CLI' - #input: null # File for loading (string [=./pic/p1.png]) - #output: null # File for outputting (string [=output.png]) - passes: 2 # Passes for processing (int [=2]) - pushColorCount: 2 # Limit the number of color pushes (int [=2]) - strengthColor: 0.3 # Strength for pushing color,range 0 to 1,higher for thinner (double [=0.3]) - strengthGradient: 1.0 # Strength for pushing gradient,range 0 to 1,higher for sharper (double [=1]) - zoomFactor: 2.0 # zoom factor for resizing (double [=2]) - threads: 16 # Threads count for video processing (unsigned int [=16]) - fastMode: false # Faster but maybe low quality - videoMode: false # Video process - preview: null # Preview image - preprocessing: false # Enable pre processing - postprocessing: false # Enable post processing - preFilters: 4 # Enhancement filter, only working when preProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D) (unsigned int [=4]) - postFilters: 40 # Enhancement filter, only working when postProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D), so you can put 40 to enable Gaussian blur weak and Bilateral filter, which also is what I recommend for image that < 1080P, 48 for image that >= 1080P, and for performance I recommend to use 72 for video that < 1080P, 80 for video that >=1080P (unsigned int [=40]) - GPUMode: false # Enable GPU acceleration - CNNMode: false # Enable ACNet - HDN: false # Enable HDN mode for ACNet - HDNLevel: 1 # Set HDN level (int [=1]) - listGPUs: null # list GPUs - platformID: 0 # Specify the platform ID (unsigned int [=0]) - deviceID: 0 # Specify the device ID (unsigned int [=0]) - codec: mp4v # Specify the codec for encoding from mp4v(recommended in Windows), dxva(for Windows), avc1(H264, recommended in Linux), vp09(very slow), hevc(not support in Windowds), av01(not support in Windowds) (string [=mp4v]) - forceFps: 0 # Set output video fps to the specifying number, 0 to disable (float [=0]) - disableProgress: false # disable progress display - webVideo: null # process the video from URL - alpha: false # preserve the Alpha channel for transparent image -ffmpeg: - ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg\bin' - intermediate_file_name: 'intermediate.mkv' - # Step 1: Frame Extraction - # extract all frames from original input into temporary directory - extract_frames: - '-hwaccel': auto # automatically select hardware acceleration method - '-y': true - input_options: {} # empty dict, expand if necessary - output_options: - '-pix_fmt': rgb24 # extracted frames pixel format - '-qscale:v': null # output image quality control for JPEG (1-31, 1 being highest) - # Step 2: Video Assembly - # assemble all frames back into a video with only a video track - assemble_video: - '-hwaccel': auto # automatically select hardware acceleration method - '-y': true - input_options: - '-f': image2 # force image2 format - output_options: - '-vcodec': libx264 # video codec - '-pix_fmt': 'yuv420p' # overwrite default pixel format - '-crf': 17 # H.264 Constant Rate Factor - '-b:v': null # target average bitrate - '-vf': 'pad=ceil(iw/2)*2:ceil(ih/2)*2' # ensure output is divisible by 2, recommended for libx264 - '-tune': 'animation' # encoding tuning film/animation/grain/stillimage/fastdecode/zerolatency/psnr/ssim - # Step 3: Streams Migration - # migrate audio and subtitle streams from original - # video into the upscaled video - migrate_streams: - '-hwaccel': auto # automatically select hardware acceleration method - '-y': true - input_options: {} # empty dict, expand if necessary - output_options: - '-map': - - '0:v?' # copy video streams - - '1:a?' # copy audio streams - - '1:s?' # copy subtitle streams - - '1:d?' # copy data streams - - '1:t?' # copy fonts - '-c': copy # copy codec for all streams - # '-vf': 'minterpolate=''fps=60''' # minterpolate frame interpolation - '-map_metadata': 0 # copy known metadata tags - # '-movflags': 'use_metadata_tags' # copy custom/arbitrary metadata tags - '-pix_fmt': null - '-metadata': 'comment=Upscaled by Video2X' -gifski: - gifski_path: '%LOCALAPPDATA%\video2x\gifski\win\gifski' - # output: null # Destination file to write to - # fps: 20 # Animation frames per second (for PNG frames only) [default: 20] - fast: false # 3 times faster encoding, but 10% lower quality and bigger file - quality: 100 # Lower quality may give smaller file - #width: null # Maximum width - #height: null # Maximum height (if width is also set) - once: false # Do not loop the GIF - nosort: false # Use files exactly in the order given, rather than sorted - quiet: false # Do not show a progress bar -video2x: - video2x_cache_directory: null # default: %TEMP%\video2x, directory where cache files are stored, will be deleted if preserve_frames is not set to true - extracted_frame_format: png # png/jpg intermediate file format used for extracted frames during video processing - output_file_name_format_string: "{original_file_name}_output{extension}" # format string to use for generating output file names - image_output_extension: .png # image output extension during batch processing - video_output_extension: .mp4 # video output extension during batch processing - preserve_frames: false # if set to true, the cache directory won't be cleaned upon task completion diff --git a/video2x/video2x_gui.py b/video2x/video2x_gui.py deleted file mode 100755 index b998070..0000000 --- a/video2x/video2x_gui.py +++ /dev/null @@ -1,1996 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X GUI -Author: K4YT3X -Date Created: May 5, 2020 -Last Modified: December 13, 2020 -""" - -# local imports -from bilogger import BiLogger -from upscaler import UPSCALER_VERSION -from upscaler import Upscaler -from wrappers.ffmpeg import Ffmpeg - -# built-in imports -import contextlib -import json -import mimetypes -import os -import pathlib -import sys -import tempfile -import time -import traceback -import urllib -import yaml - -# third-party imports -from PyQt5 import uic -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -import magic - -GUI_VERSION = "2.8.1" - -LEGAL_INFO = f"""Video2X GUI Version: {GUI_VERSION}\\ -Upscaler Version: {UPSCALER_VERSION}\\ -Author: K4YT3X\\ -License: GNU GPL v3\\ -Github Page: [https://github.com/k4yt3x/video2x](https://github.com/k4yt3x/video2x)\\ -Contact: [k4yt3x@k4yt3x.com](mailto:k4yt3x@k4yt3x.com)""" - -AVAILABLE_DRIVERS = { - "Waifu2X Caffe": "waifu2x_caffe", - "Waifu2X Converter CPP": "waifu2x_converter_cpp", - "Waifu2X ncnn Vulkan": "waifu2x_ncnn_vulkan", - "SRMD ncnn Vulkan": "srmd_ncnn_vulkan", - "RealSR ncnn Vulkan": "realsr_ncnn_vulkan", - "Anime4KCPP": "anime4kcpp", - "DAIN ncnn Vulkan": "dain_ncnn_vulkan", - "RIFE ncnn Vulkan": "rife_ncnn_vulkan", -} - -# get current working directory before it is changed by drivers -CWD = pathlib.Path.cwd() - - -def resource_path(relative_path: str) -> pathlib.Path: - try: - base_path = pathlib.Path(sys._MEIPASS) - except AttributeError: - base_path = pathlib.Path(__file__).parent - return base_path / relative_path - - -class WorkerSignals(QObject): - progress = pyqtSignal(tuple) - error = pyqtSignal(Exception) - interrupted = pyqtSignal() - finished = pyqtSignal() - - -class ProgressMonitorWorkder(QRunnable): - def __init__(self, fn, *args, **kwargs): - super(ProgressMonitorWorkder, self).__init__() - self.fn = fn - self.args = args - self.kwargs = kwargs - self.signals = WorkerSignals() - self.kwargs["progress_callback"] = self.signals.progress - - @pyqtSlot() - def run(self): - try: - self.fn(*self.args, **self.kwargs) - except Exception: - pass - - -class UpscalerWorker(QRunnable): - def __init__(self, fn, *args, **kwargs): - super(UpscalerWorker, self).__init__() - - # Store constructor arguments (re-used for processing) - self.fn = fn - self.args = args - self.kwargs = kwargs - self.signals = WorkerSignals() - - @pyqtSlot() - def run(self): - - # Retrieve args/kwargs here; and fire processing using them - try: - self.fn(*self.args, **self.kwargs) - except (KeyboardInterrupt, SystemExit): - self.signals.interrupted.emit() - except Exception as e: - traceback.print_exc() - self.signals.error.emit(e) - else: - self.signals.finished.emit() - - -class InputTableModel(QAbstractTableModel): - def __init__(self, data): - super(InputTableModel, self).__init__() - self._data = data - - def data(self, index, role): - if role == Qt.DisplayRole: - - file_path = self._data[index.row()] - - if index.column() == 0: - return str(file_path.absolute()) - else: - - # determine file type - # if path is a folder - if file_path.is_dir(): - return "Folder" - - # if path is single file - # determine file type - elif file_path.is_file(): - try: - input_file_mime_type = magic.from_file( - str(file_path.absolute()), mime=True - ) - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] - except Exception: - input_file_type = input_file_subtype = None - - # in case python-magic fails to detect file type - # try guessing file mime type with mimetypes - if input_file_type not in ["image", "video"]: - input_file_mime_type = mimetypes.guess_type(file_path.name)[0] - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] - - if input_file_type == "image": - if input_file_subtype == "gif": - return "GIF" - return "Image" - - elif input_file_type == "video": - return "Video" - - else: - return "Unknown" - - else: - return "Unknown" - - def rowCount(self, index): - return len(self._data) - - def columnCount(self, index): - return 2 - - def removeRow(self, index): - self._data.pop(index) - - def headerData(self, section, orientation, role): - # section is the index of the column/row. - if role != Qt.DisplayRole: - return None - - horizontal_headers = ["File Path", "Type"] - - # return the correspondign header - if orientation == Qt.Horizontal: - return horizontal_headers[section] - - # simply return the line number - if orientation == Qt.Vertical: - return str(section) - - -class Video2XMainWindow(QMainWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - uic.loadUi(str(resource_path("video2x_gui.ui")), self) - - # redirect output to both terminal and log file - self.log_file = tempfile.TemporaryFile( - mode="a+", suffix=".log", prefix="video2x_", encoding="utf-8" - ) - sys.stdout = BiLogger(sys.stdout, self.log_file) - sys.stderr = BiLogger(sys.stderr, self.log_file) - - # create thread pool for upscaler workers - self.threadpool = QThreadPool() - - # set window title and icon - self.video2x_icon_path = str(resource_path("images/video2x.png")) - self.setWindowTitle(f"Video2X GUI {GUI_VERSION}") - self.setWindowIcon(QIcon(self.video2x_icon_path)) - - # register shortcut keys - QShortcut(QKeySequence(Qt.CTRL + Qt.Key_W), self, self.close) - QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q), self, self.close) - QShortcut(QKeySequence(Qt.CTRL + Qt.Key_I), self, self.select_input_file) - QShortcut(QKeySequence(Qt.CTRL + Qt.Key_O), self, self.select_output_file) - QShortcut( - QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_I), self, self.select_input_folder - ) - QShortcut( - QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_O), self, self.select_output_folder - ) - - # menu bar - self.action_exit = self.findChild(QAction, "actionExit") - self.action_exit.triggered.connect(self.close) - - self.action_shortcuts = self.findChild(QAction, "actionShortcuts") - self.action_shortcuts.triggered.connect(self.show_shortcuts) - - self.action_about = self.findChild(QAction, "actionAbout") - self.action_about.triggered.connect(self.show_about) - - # main tab - # select input file/folder - self.input_table_view = self.findChild(QTableView, "inputTableView") - self.input_table_view.dragEnterEvent = self.dragEnterEvent - self.input_table_view.dropEvent = self.dropEvent - - # initialize data in table - self.input_table_data = [] - self.input_table_model = InputTableModel(self.input_table_data) - self.input_table_view.setModel(self.input_table_model) - # stretch file path and fill columns horizontally - self.input_table_view.horizontalHeader().setSectionResizeMode( - 0, QHeaderView.Stretch - ) - - # input table buttons - self.input_select_file_button = self.findChild( - QPushButton, "inputSelectFileButton" - ) - self.input_select_file_button.clicked.connect(self.select_input_file) - self.input_select_folder_button = self.findChild( - QPushButton, "inputSelectFolderButton" - ) - self.input_select_folder_button.clicked.connect(self.select_input_folder) - self.input_delete_selected_button = self.findChild( - QPushButton, "inputDeleteSelectedButton" - ) - self.input_delete_selected_button.clicked.connect( - self.input_table_delete_selected - ) - self.input_clear_all_button = self.findChild(QPushButton, "inputClearAllButton") - self.input_clear_all_button.clicked.connect(self.input_table_clear_all) - - # other paths selection - # select output file/folder - self.output_line_edit = self.findChild(QLineEdit, "outputLineEdit") - self.enable_line_edit_file_drop(self.output_line_edit) - self.output_line_edit.setText(str((CWD / "output").absolute())) - self.output_select_file_button = self.findChild( - QPushButton, "outputSelectFileButton" - ) - self.output_select_file_button.clicked.connect(self.select_output_file) - self.output_select_folder_button = self.findChild( - QPushButton, "outputSelectFolderButton" - ) - self.output_select_folder_button.clicked.connect(self.select_output_folder) - - # config file - self.config_line_edit = self.findChild(QLineEdit, "configLineEdit") - self.enable_line_edit_file_drop(self.config_line_edit) - - if getattr(sys, "frozen", False): - self.config_line_edit.setText( - str((pathlib.Path(sys.executable).parent / "video2x.yaml").absolute()) - ) - elif __file__: - self.config_line_edit.setText( - str((pathlib.Path(__file__).parent / "video2x.yaml").absolute()) - ) - - self.config_select_file_button = self.findChild( - QPushButton, "configSelectButton" - ) - self.config_select_file_button.clicked.connect(self.select_config_file) - - # cache directory - self.cache_line_edit = self.findChild(QLineEdit, "cacheLineEdit") - self.enable_line_edit_file_drop(self.cache_line_edit) - self.cache_select_folder_button = self.findChild( - QPushButton, "cacheSelectFolderButton" - ) - self.cache_select_folder_button.clicked.connect(self.select_cache_folder) - - # express settings - self.driver_combo_box = self.findChild(QComboBox, "driverComboBox") - self.driver_combo_box.currentTextChanged.connect(self.update_gui_for_driver) - self.processes_spin_box = self.findChild(QSpinBox, "processesSpinBox") - self.scale_ratio_double_spin_box = self.findChild( - QDoubleSpinBox, "scaleRatioDoubleSpinBox" - ) - self.output_width_spin_box = self.findChild(QSpinBox, "outputWidthSpinBox") - self.output_width_spin_box.valueChanged.connect( - self.mutually_exclude_scale_ratio_resolution - ) - self.output_height_spin_box = self.findChild(QSpinBox, "outputHeightSpinBox") - self.output_height_spin_box.valueChanged.connect( - self.mutually_exclude_scale_ratio_resolution - ) - self.output_file_name_format_string_line_edit = self.findChild( - QLineEdit, "outputFileNameFormatStringLineEdit" - ) - self.image_output_extension_line_edit = self.findChild( - QLineEdit, "imageOutputExtensionLineEdit" - ) - self.video_output_extension_line_edit = self.findChild( - QLineEdit, "videoOutputExtensionLineEdit" - ) - self.preserve_frames_check_box = self.findChild( - QCheckBox, "preserveFramesCheckBox" - ) - - # frame preview - self.frame_preview_show_preview_check_box = self.findChild( - QCheckBox, "framePreviewShowPreviewCheckBox" - ) - self.frame_preview_keep_aspect_ratio_check_box = self.findChild( - QCheckBox, "framePreviewKeepAspectRatioCheckBox" - ) - self.frame_preview_label = self.findChild(QLabel, "framePreviewLabel") - - # currently processing - self.currently_processing_label = self.findChild( - QLabel, "currentlyProcessingLabel" - ) - self.current_progress_bar = self.findChild(QProgressBar, "currentProgressBar") - self.time_elapsed_label = self.findChild(QLabel, "timeElapsedLabel") - self.time_remaining_label = self.findChild(QLabel, "timeRemainingLabel") - self.rate_label = self.findChild(QLabel, "rateLabel") - self.frames_label = self.findChild(QLabel, "framesLabel") - - # overall progress - self.overall_progress_bar = self.findChild(QProgressBar, "overallProgressBar") - self.overall_progress_label = self.findChild(QLabel, "overallProgressLabel") - self.start_button = self.findChild(QPushButton, "startButton") - self.start_button.clicked.connect(self.start) - self.stop_button = self.findChild(QPushButton, "stopButton") - self.stop_button.clicked.connect(self.stop) - - # driver settings - # waifu2x-caffe - self.waifu2x_caffe_path_line_edit = self.findChild( - QLineEdit, "waifu2xCaffePathLineEdit" - ) - self.enable_line_edit_file_drop(self.waifu2x_caffe_path_line_edit) - self.waifu2x_caffe_path_select_button = self.findChild( - QPushButton, "waifu2xCaffePathSelectButton" - ) - self.waifu2x_caffe_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path(self.waifu2x_caffe_path_line_edit) - ) - self.waifu2x_caffe_mode_combo_box = self.findChild( - QComboBox, "waifu2xCaffeModeComboBox" - ) - self.waifu2x_caffe_noise_level_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeNoiseLevelSpinBox" - ) - self.waifu2x_caffe_process_combo_box = self.findChild( - QComboBox, "waifu2xCaffeProcessComboBox" - ) - self.waifu2x_caffe_model_combobox = self.findChild( - QComboBox, "waifu2xCaffeModelComboBox" - ) - self.waifu2x_caffe_crop_size_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeCropSizeSpinBox" - ) - self.waifu2x_caffe_output_quality_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeOutputQualitySpinBox" - ) - self.waifu2x_caffe_output_depth_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeOutputDepthSpinBox" - ) - self.waifu2x_caffe_batch_size_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeBatchSizeSpinBox" - ) - self.waifu2x_caffe_gpu_spin_box = self.findChild( - QSpinBox, "waifu2xCaffeGpuSpinBox" - ) - self.waifu2x_caffe_tta_check_box = self.findChild( - QCheckBox, "waifu2xCaffeTtaCheckBox" - ) - - # waifu2x-converter-cpp - self.waifu2x_converter_cpp_path_line_edit = self.findChild( - QLineEdit, "waifu2xConverterCppPathLineEdit" - ) - self.enable_line_edit_file_drop(self.waifu2x_converter_cpp_path_line_edit) - self.waifu2x_converter_cpp_path_edit_button = self.findChild( - QPushButton, "waifu2xConverterCppPathSelectButton" - ) - self.waifu2x_converter_cpp_path_edit_button.clicked.connect( - lambda: self.select_driver_binary_path( - self.waifu2x_converter_cpp_path_line_edit - ) - ) - self.waifu2x_converter_cpp_png_compression_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppPngCompressionSpinBox" - ) - self.waifu2x_converter_cpp_image_quality_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppImageQualitySpinBox" - ) - self.waifu2x_converter_cpp_block_size_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppBlockSizeSpinBox" - ) - self.waifu2x_converter_cpp_processor_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppProcessorSpinBox" - ) - self.waifu2x_converter_cpp_model_combo_box = self.findChild( - QComboBox, "waifu2xConverterCppModelComboBox" - ) - self.waifu2x_converter_cpp_noise_level_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppNoiseLevelSpinBox" - ) - self.waifu2x_converter_cpp_mode_combo_box = self.findChild( - QComboBox, "waifu2xConverterCppModeComboBox" - ) - self.waifu2x_converter_cpp_log_level_spin_box = self.findChild( - QSpinBox, "waifu2xConverterCppLogLevelSpinBox" - ) - self.waifu2x_converter_cpp_disable_gpu_check_box = self.findChild( - QCheckBox, "waifu2xConverterCppDisableGpuCheckBox" - ) - self.waifu2x_converter_cpp_force_opencl_check_box = self.findChild( - QCheckBox, "waifu2xConverterCppForceOpenclCheckBox" - ) - self.waifu2x_converter_cpp_tta_check_box = self.findChild( - QCheckBox, "waifu2xConverterCppTtaCheckBox" - ) - - # waifu2x-ncnn-vulkan - self.waifu2x_ncnn_vulkan_path_line_edit = self.findChild( - QLineEdit, "waifu2xNcnnVulkanPathLineEdit" - ) - self.enable_line_edit_file_drop(self.waifu2x_ncnn_vulkan_path_line_edit) - self.waifu2x_ncnn_vulkan_path_select_button = self.findChild( - QPushButton, "waifu2xNcnnVulkanPathSelectButton" - ) - self.waifu2x_ncnn_vulkan_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path( - self.waifu2x_ncnn_vulkan_path_line_edit - ) - ) - self.waifu2x_ncnn_vulkan_noise_level_spin_box = self.findChild( - QSpinBox, "waifu2xNcnnVulkanNoiseLevelSpinBox" - ) - self.waifu2x_ncnn_vulkan_tile_size_spin_box = self.findChild( - QSpinBox, "waifu2xNcnnVulkanTileSizeSpinBox" - ) - self.waifu2x_ncnn_vulkan_model_combo_box = self.findChild( - QComboBox, "waifu2xNcnnVulkanModelComboBox" - ) - self.waifu2x_ncnn_vulkan_gpu_id_spin_box = self.findChild( - QSpinBox, "waifu2xNcnnVulkanGpuIdSpinBox" - ) - self.waifu2x_ncnn_vulkan_jobs_line_edit = self.findChild( - QLineEdit, "waifu2xNcnnVulkanJobsLineEdit" - ) - self.waifu2x_ncnn_vulkan_tta_check_box = self.findChild( - QCheckBox, "waifu2xNcnnVulkanTtaCheckBox" - ) - - # srmd-ncnn-vulkan - self.srmd_ncnn_vulkan_path_line_edit = self.findChild( - QLineEdit, "srmdNcnnVulkanPathLineEdit" - ) - self.enable_line_edit_file_drop(self.srmd_ncnn_vulkan_path_line_edit) - self.srmd_ncnn_vulkan_path_select_button = self.findChild( - QPushButton, "srmdNcnnVulkanPathSelectButton" - ) - self.srmd_ncnn_vulkan_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path(self.srmd_ncnn_vulkan_path_line_edit) - ) - self.srmd_ncnn_vulkan_noise_level_spin_box = self.findChild( - QSpinBox, "srmdNcnnVulkanNoiseLevelSpinBox" - ) - self.srmd_ncnn_vulkan_tile_size_spin_box = self.findChild( - QSpinBox, "srmdNcnnVulkanTileSizeSpinBox" - ) - self.srmd_ncnn_vulkan_model_combo_box = self.findChild( - QComboBox, "srmdNcnnVulkanModelComboBox" - ) - self.srmd_ncnn_vulkan_gpu_id_spin_box = self.findChild( - QSpinBox, "srmdNcnnVulkanGpuIdSpinBox" - ) - self.srmd_ncnn_vulkan_jobs_line_edit = self.findChild( - QLineEdit, "srmdNcnnVulkanJobsLineEdit" - ) - self.srmd_ncnn_vulkan_tta_check_box = self.findChild( - QCheckBox, "srmdNcnnVulkanTtaCheckBox" - ) - - # realsr-ncnn-vulkan - self.realsr_ncnn_vulkan_path_line_edit = self.findChild( - QLineEdit, "realsrNcnnVulkanPathLineEdit" - ) - self.enable_line_edit_file_drop(self.realsr_ncnn_vulkan_path_line_edit) - self.realsr_ncnn_vulkan_path_select_button = self.findChild( - QPushButton, "realsrNcnnVulkanPathSelectButton" - ) - self.realsr_ncnn_vulkan_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path( - self.realsr_ncnn_vulkan_path_line_edit - ) - ) - self.realsr_ncnn_vulkan_tile_size_spin_box = self.findChild( - QSpinBox, "realsrNcnnVulkanTileSizeSpinBox" - ) - self.realsr_ncnn_vulkan_model_combo_box = self.findChild( - QComboBox, "realsrNcnnVulkanModelComboBox" - ) - self.realsr_ncnn_vulkan_gpu_id_spin_box = self.findChild( - QSpinBox, "realsrNcnnVulkanGpuIdSpinBox" - ) - self.realsr_ncnn_vulkan_jobs_line_edit = self.findChild( - QLineEdit, "realsrNcnnVulkanJobsLineEdit" - ) - self.realsr_ncnn_vulkan_tta_check_box = self.findChild( - QCheckBox, "realsrNcnnVulkanTtaCheckBox" - ) - - # anime4k - self.anime4kcpp_path_line_edit = self.findChild( - QLineEdit, "anime4kCppPathLineEdit" - ) - self.enable_line_edit_file_drop(self.anime4kcpp_path_line_edit) - self.anime4kcpp_path_select_button = self.findChild( - QPushButton, "anime4kCppPathSelectButton" - ) - self.anime4kcpp_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path(self.anime4kcpp_path_line_edit) - ) - self.anime4kcpp_passes_spin_box = self.findChild( - QSpinBox, "anime4kCppPassesSpinBox" - ) - self.anime4kcpp_push_color_count_spin_box = self.findChild( - QSpinBox, "anime4kCppPushColorCountSpinBox" - ) - self.anime4kcpp_strength_color_spin_box = self.findChild( - QDoubleSpinBox, "anime4kCppStrengthColorSpinBox" - ) - self.anime4kcpp_strength_gradient_spin_box = self.findChild( - QDoubleSpinBox, "anime4kCppStrengthGradientSpinBox" - ) - self.anime4kcpp_threads_spin_box = self.findChild( - QSpinBox, "anime4kCppThreadsSpinBox" - ) - self.anime4kcpp_pre_filters_spin_box = self.findChild( - QSpinBox, "anime4kCppPreFiltersSpinBox" - ) - self.anime4kcpp_post_filters_spin_box = self.findChild( - QSpinBox, "anime4kCppPostFiltersSpinBox" - ) - self.anime4kcpp_platform_id_spin_box = self.findChild( - QSpinBox, "anime4kCppPlatformIdSpinBox" - ) - self.anime4kcpp_device_id_spin_box = self.findChild( - QSpinBox, "anime4kCppDeviceIdSpinBox" - ) - self.anime4kcpp_codec_combo_box = self.findChild( - QComboBox, "anime4kCppCodecComboBox" - ) - self.anime4kcpp_fast_mode_check_box = self.findChild( - QCheckBox, "anime4kCppFastModeCheckBox" - ) - self.anime4kcpp_pre_processing_check_box = self.findChild( - QCheckBox, "anime4kCppPreProcessingCheckBox" - ) - self.anime4kcpp_post_processing_check_box = self.findChild( - QCheckBox, "anime4kCppPostProcessingCheckBox" - ) - self.anime4kcpp_gpu_mode_check_box = self.findChild( - QCheckBox, "anime4kCppGpuModeCheckBox" - ) - self.anime4kcpp_cnn_mode_check_box = self.findChild( - QCheckBox, "anime4kCppCnnModeCheckBox" - ) - self.anime4kcpp_hdn_check_box = self.findChild( - QCheckBox, "anime4kCppHdnCheckBox" - ) - self.anime4kcpp_hdn_level_spin_box = self.findChild( - QSpinBox, "anime4kCppHdnLevelSpinBox" - ) - self.anime4kcpp_force_fps_double_spin_box = self.findChild( - QDoubleSpinBox, "anime4kCppForceFpsDoubleSpinBox" - ) - self.anime4kcpp_disable_progress_check_box = self.findChild( - QCheckBox, "anime4kCppDisableProgressCheckBox" - ) - self.anime4kcpp_alpha_check_box = self.findChild( - QCheckBox, "anime4kCppAlphaCheckBox" - ) - - # FFmpeg settings - # global options - self.ffmpeg_path_line_edit = self.findChild(QLineEdit, "ffmpegPathLineEdit") - self.enable_line_edit_file_drop(self.ffmpeg_path_line_edit) - self.ffmpeg_path_select_button = self.findChild( - QPushButton, "ffmpegPathSelectButton" - ) - self.ffmpeg_path_select_button.clicked.connect( - lambda: self.select_driver_binary_path(self.ffmpeg_path_line_edit) - ) - self.ffmpeg_intermediate_file_name_line_edit = self.findChild( - QLineEdit, "ffmpegIntermediateFileNameLineEdit" - ) - - # extract frames - self.ffmpeg_extract_frames_output_options_pixel_format_line_edit = ( - self.findChild( - QLineEdit, "ffmpegExtractFramesOutputOptionsPixelFormatLineEdit" - ) - ) - self.ffmpeg_extract_frames_hardware_acceleration_check_box = self.findChild( - QCheckBox, "ffmpegExtractFramesHardwareAccelerationCheckBox" - ) - - # assemble video - self.ffmpeg_assemble_video_input_options_force_format_line_edit = ( - self.findChild( - QLineEdit, "ffmpegAssembleVideoInputOptionsForceFormatLineEdit" - ) - ) - self.ffmpeg_assemble_video_output_options_video_codec_line_edit = ( - self.findChild( - QLineEdit, "ffmpegAssembleVideoOutputOptionsVideoCodecLineEdit" - ) - ) - self.ffmpeg_assemble_video_output_options_pixel_format_line_edit = ( - self.findChild( - QLineEdit, "ffmpegAssembleVideoOutputOptionsPixelFormatLineEdit" - ) - ) - self.ffmpeg_assemble_video_output_options_crf_spin_box = self.findChild( - QSpinBox, "ffmpegAssembleVideoOutputOptionsCrfSpinBox" - ) - self.ffmpeg_assemble_video_output_options_tune_combo_box = self.findChild( - QComboBox, "ffmpegAssembleVideoOutputOptionsTuneComboBox" - ) - self.ffmpeg_assemble_video_output_options_bitrate_line_edit = self.findChild( - QLineEdit, "ffmpegAssembleVideoOutputOptionsBitrateLineEdit" - ) - self.ffmpeg_assemble_video_output_options_ensure_divisible_check_box = ( - self.findChild( - QCheckBox, "ffmpegAssembleVideoOutputOptionsEnsureDivisibleCheckBox" - ) - ) - self.ffmpeg_assemble_video_hardware_acceleration_check_box = self.findChild( - QCheckBox, "ffmpegAssembleVideoHardwareAccelerationCheckBox" - ) - - # migrate_streams - self.ffmpeg_migrate_streams_output_options_mapping_video_check_box_check_box = ( - self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsMappingVideoCheckBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_mapping_audio_check_box_check_box = ( - self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsMappingAudioCheckBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_mapping_subtitle_check_box_check_box = self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsMappingSubtitleCheckBox" - ) - self.ffmpeg_migrate_streams_output_options_mapping_data_check_box_check_box = ( - self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsMappingDataCheckBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_mapping_font_check_box_check_box = ( - self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsMappingFontCheckBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_pixel_format_line_edit = ( - self.findChild( - QLineEdit, "ffmpegMigrateStreamsOutputOptionsPixelFormatLineEdit" - ) - ) - self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box = ( - self.findChild( - QSpinBox, "ffmpegMigrateStreamsOutputOptionsFrameInterpolationSpinBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.valueChanged.connect( - self.mutually_exclude_frame_interpolation_stream_copy - ) - self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.textChanged.connect( - self.mutually_exclude_frame_interpolation_stream_copy - ) - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box = ( - self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsCopyStreamsCheckBox" - ) - ) - self.ffmpeg_migrate_streams_output_options_copy_known_metadata_tags_check_box = self.findChild( - QCheckBox, "ffmpegMigrateStreamsOutputOptionsCopyKnownMetadataTagsCheckBox" - ) - self.ffmpeg_migrate_streams_output_options_copy_arbitrary_metadata_tags_check_box = self.findChild( - QCheckBox, - "ffmpegMigrateStreamsOutputOptionsCopyArbitraryMetadataTagsCheckBox", - ) - self.ffmpeg_migrate_streams_hardware_acceleration_check_box = self.findChild( - QCheckBox, "ffmpegMigrateStreamsHardwareAccelerationCheckBox" - ) - - # Gifski settings - self.gifski_path_line_edit = self.findChild(QLineEdit, "gifskiPathLineEdit") - self.enable_line_edit_file_drop(self.gifski_path_line_edit) - self.gifski_quality_spin_box = self.findChild(QSpinBox, "gifskiQualitySpinBox") - self.gifski_fast_check_box = self.findChild(QCheckBox, "gifskiFastCheckBox") - self.gifski_once_check_box = self.findChild(QCheckBox, "gifskiOnceCheckBox") - self.gifski_quiet_check_box = self.findChild(QCheckBox, "gifskiQuietCheckBox") - - # Tools - self.ffprobe_plain_text_edit = self.findChild( - QPlainTextEdit, "ffprobePlainTextEdit" - ) - self.ffprobe_plain_text_edit.dropEvent = self.show_ffprobe_output - - # load configurations after GUI initialization - self.load_configurations() - - def load_configurations(self): - - # get config file path from line edit - config_file_path = pathlib.Path( - os.path.expandvars(self.config_line_edit.text()) - ) - - # if file doesn't exist, return - if not config_file_path.is_file(): - QErrorMessage(self).showMessage( - "Video2X configuration file not found, please specify manually." - ) - return - - # read configuration dict from config file - self.config = self.read_config(config_file_path) - - # load FFmpeg settings - self.ffmpeg_settings = self.config["ffmpeg"] - self.ffmpeg_settings["ffmpeg_path"] = str( - pathlib.Path( - os.path.expandvars(self.ffmpeg_settings["ffmpeg_path"]) - ).absolute() - ) - - # read Gifski configuration - self.gifski_settings = self.config["gifski"] - self.gifski_settings["gifski_path"] = str( - pathlib.Path( - os.path.expandvars(self.gifski_settings["gifski_path"]) - ).absolute() - ) - - # set cache directory path - if self.config["video2x"]["video2x_cache_directory"] is None: - self.config["video2x"]["video2x_cache_directory"] = str( - (pathlib.Path(tempfile.gettempdir()) / "video2x").absolute() - ) - self.cache_line_edit.setText(self.config["video2x"]["video2x_cache_directory"]) - - self.output_file_name_format_string_line_edit.setText( - self.config["video2x"]["output_file_name_format_string"] - ) - self.image_output_extension_line_edit.setText( - self.config["video2x"]["image_output_extension"] - ) - self.video_output_extension_line_edit.setText( - self.config["video2x"]["video_output_extension"] - ) - - # load preserve frames settings - self.preserve_frames_check_box.setChecked( - self.config["video2x"]["preserve_frames"] - ) - self.start_button.setEnabled(True) - - # waifu2x-caffe - settings = self.config["waifu2x_caffe"] - self.waifu2x_caffe_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.waifu2x_caffe_mode_combo_box.setCurrentText(settings["mode"]) - self.waifu2x_caffe_noise_level_spin_box.setValue(settings["noise_level"]) - self.waifu2x_caffe_process_combo_box.setCurrentText(settings["process"]) - self.waifu2x_caffe_crop_size_spin_box.setValue(settings["crop_size"]) - self.waifu2x_caffe_output_quality_spin_box.setValue(settings["output_quality"]) - self.waifu2x_caffe_output_depth_spin_box.setValue(settings["output_depth"]) - self.waifu2x_caffe_batch_size_spin_box.setValue(settings["batch_size"]) - self.waifu2x_caffe_gpu_spin_box.setValue(settings["gpu"]) - self.waifu2x_caffe_tta_check_box.setChecked(bool(settings["tta"])) - - # waifu2x-converter-cpp - settings = self.config["waifu2x_converter_cpp"] - self.waifu2x_converter_cpp_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.waifu2x_converter_cpp_png_compression_spin_box.setValue( - settings["png-compression"] - ) - self.waifu2x_converter_cpp_image_quality_spin_box.setValue( - settings["image-quality"] - ) - self.waifu2x_converter_cpp_block_size_spin_box.setValue(settings["block-size"]) - self.waifu2x_converter_cpp_processor_spin_box.setValue(settings["processor"]) - self.waifu2x_converter_cpp_noise_level_spin_box.setValue( - settings["noise-level"] - ) - self.waifu2x_converter_cpp_mode_combo_box.setCurrentText(settings["mode"]) - self.waifu2x_converter_cpp_log_level_spin_box.setValue(settings["log-level"]) - self.waifu2x_converter_cpp_disable_gpu_check_box.setChecked( - settings["disable-gpu"] - ) - self.waifu2x_converter_cpp_force_opencl_check_box.setChecked( - settings["force-OpenCL"] - ) - self.waifu2x_converter_cpp_tta_check_box.setChecked(bool(settings["tta"])) - - # waifu2x-ncnn-vulkan - settings = self.config["waifu2x_ncnn_vulkan"] - self.waifu2x_ncnn_vulkan_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.waifu2x_ncnn_vulkan_noise_level_spin_box.setValue(settings["n"]) - self.waifu2x_ncnn_vulkan_tile_size_spin_box.setValue(settings["t"]) - self.waifu2x_ncnn_vulkan_gpu_id_spin_box.setValue(settings["g"]) - self.waifu2x_ncnn_vulkan_jobs_line_edit.setText(settings["j"]) - self.waifu2x_ncnn_vulkan_tta_check_box.setChecked(settings["x"]) - - # srmd-ncnn-vulkan - settings = self.config["srmd_ncnn_vulkan"] - self.srmd_ncnn_vulkan_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.srmd_ncnn_vulkan_noise_level_spin_box.setValue(settings["n"]) - self.srmd_ncnn_vulkan_tile_size_spin_box.setValue(settings["t"]) - self.srmd_ncnn_vulkan_gpu_id_spin_box.setValue(settings["g"]) - self.srmd_ncnn_vulkan_jobs_line_edit.setText(settings["j"]) - self.srmd_ncnn_vulkan_tta_check_box.setChecked(settings["x"]) - - # realsr-ncnn-vulkan - settings = self.config["realsr_ncnn_vulkan"] - self.realsr_ncnn_vulkan_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.realsr_ncnn_vulkan_tile_size_spin_box.setValue(settings["t"]) - self.realsr_ncnn_vulkan_gpu_id_spin_box.setValue(settings["g"]) - self.realsr_ncnn_vulkan_jobs_line_edit.setText(settings["j"]) - self.realsr_ncnn_vulkan_tta_check_box.setChecked(settings["x"]) - - # anime4k - settings = self.config["anime4kcpp"] - self.anime4kcpp_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["path"])).absolute()) - ) - self.anime4kcpp_passes_spin_box.setValue(settings["passes"]) - self.anime4kcpp_push_color_count_spin_box.setValue(settings["pushColorCount"]) - self.anime4kcpp_strength_color_spin_box.setValue(settings["strengthColor"]) - self.anime4kcpp_strength_gradient_spin_box.setValue( - settings["strengthGradient"] - ) - self.anime4kcpp_threads_spin_box.setValue(settings["threads"]) - self.anime4kcpp_pre_filters_spin_box.setValue(settings["preFilters"]) - self.anime4kcpp_post_filters_spin_box.setValue(settings["postFilters"]) - self.anime4kcpp_platform_id_spin_box.setValue(settings["platformID"]) - self.anime4kcpp_device_id_spin_box.setValue(settings["deviceID"]) - self.anime4kcpp_codec_combo_box.setCurrentText(settings["codec"]) - self.anime4kcpp_fast_mode_check_box.setChecked(settings["fastMode"]) - self.anime4kcpp_pre_processing_check_box.setChecked(settings["preprocessing"]) - self.anime4kcpp_post_processing_check_box.setChecked(settings["postprocessing"]) - self.anime4kcpp_gpu_mode_check_box.setChecked(settings["GPUMode"]) - self.anime4kcpp_cnn_mode_check_box.setChecked(settings["CNNMode"]) - self.anime4kcpp_hdn_check_box.setChecked(settings["HDN"]) - self.anime4kcpp_hdn_level_spin_box.setValue(settings["HDNLevel"]) - self.anime4kcpp_force_fps_double_spin_box.setValue(settings["forceFps"]) - self.anime4kcpp_disable_progress_check_box.setChecked( - settings["disableProgress"] - ) - self.anime4kcpp_alpha_check_box.setChecked(settings["alpha"]) - - # ffmpeg - # global options - settings = self.config["ffmpeg"] - self.ffmpeg_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["ffmpeg_path"])).absolute()) - ) - self.ffmpeg_intermediate_file_name_line_edit.setText( - settings["intermediate_file_name"] - ) - - # extract frames - settings = self.config["ffmpeg"]["extract_frames"] - self.ffmpeg_extract_frames_output_options_pixel_format_line_edit.setText( - settings["output_options"]["-pix_fmt"] - ) - - # assemble video - settings = self.config["ffmpeg"]["assemble_video"] - self.ffmpeg_assemble_video_input_options_force_format_line_edit.setText( - settings["input_options"]["-f"] - ) - self.ffmpeg_assemble_video_output_options_video_codec_line_edit.setText( - settings["output_options"]["-vcodec"] - ) - self.ffmpeg_assemble_video_output_options_pixel_format_line_edit.setText( - settings["output_options"]["-pix_fmt"] - ) - self.ffmpeg_assemble_video_output_options_crf_spin_box.setValue( - settings["output_options"]["-crf"] - ) - self.ffmpeg_assemble_video_output_options_tune_combo_box.setCurrentText( - settings["output_options"]["-tune"] - ) - self.ffmpeg_assemble_video_output_options_bitrate_line_edit.setText( - settings["output_options"]["-b:v"] - ) - - # migrate streams - settings = self.config["ffmpeg"]["migrate_streams"] - self.ffmpeg_migrate_streams_output_options_pixel_format_line_edit.setText( - settings["output_options"]["-pix_fmt"] - ) - - # Gifski - settings = self.config["gifski"] - self.gifski_path_line_edit.setText( - str(pathlib.Path(os.path.expandvars(settings["gifski_path"])).absolute()) - ) - self.gifski_quality_spin_box.setValue(settings["quality"]) - self.gifski_fast_check_box.setChecked(settings["fast"]) - self.gifski_once_check_box.setChecked(settings["once"]) - self.gifski_quiet_check_box.setChecked(settings["quiet"]) - - def resolve_driver_settings(self): - - # waifu2x-caffe - self.config["waifu2x_caffe"]["path"] = os.path.expandvars( - self.waifu2x_caffe_path_line_edit.text() - ) - self.config["waifu2x_caffe"][ - "mode" - ] = self.waifu2x_caffe_mode_combo_box.currentText() - self.config["waifu2x_caffe"][ - "noise_level" - ] = self.waifu2x_caffe_noise_level_spin_box.value() - self.config["waifu2x_caffe"][ - "process" - ] = self.waifu2x_caffe_process_combo_box.currentText() - self.config["waifu2x_caffe"]["model_dir"] = str( - ( - pathlib.Path(self.config["waifu2x_caffe"]["path"]).parent - / "models" - / self.waifu2x_caffe_model_combobox.currentText() - ).absolute() - ) - self.config["waifu2x_caffe"][ - "crop_size" - ] = self.waifu2x_caffe_crop_size_spin_box.value() - self.config["waifu2x_caffe"][ - "output_quality" - ] = self.waifu2x_caffe_output_quality_spin_box.value() - self.config["waifu2x_caffe"][ - "output_depth" - ] = self.waifu2x_caffe_output_depth_spin_box.value() - self.config["waifu2x_caffe"][ - "batch_size" - ] = self.waifu2x_caffe_batch_size_spin_box.value() - self.config["waifu2x_caffe"]["gpu"] = self.waifu2x_caffe_gpu_spin_box.value() - self.config["waifu2x_caffe"]["tta"] = int( - self.waifu2x_caffe_tta_check_box.isChecked() - ) - - # waifu2x-converter-cpp - self.config["waifu2x_converter_cpp"]["path"] = os.path.expandvars( - self.waifu2x_converter_cpp_path_line_edit.text() - ) - self.config["waifu2x_converter_cpp"][ - "png-compression" - ] = self.waifu2x_converter_cpp_png_compression_spin_box.value() - self.config["waifu2x_converter_cpp"][ - "image-quality" - ] = self.waifu2x_converter_cpp_image_quality_spin_box.value() - self.config["waifu2x_converter_cpp"][ - "block-size" - ] = self.waifu2x_converter_cpp_block_size_spin_box.value() - self.config["waifu2x_converter_cpp"][ - "processor" - ] = self.waifu2x_converter_cpp_processor_spin_box.value() - self.config["waifu2x_converter_cpp"]["model-dir"] = str( - ( - pathlib.Path(self.config["waifu2x_converter_cpp"]["path"]).parent - / self.waifu2x_converter_cpp_model_combo_box.currentText() - ).absolute() - ) - self.config["waifu2x_converter_cpp"][ - "noise-level" - ] = self.waifu2x_converter_cpp_noise_level_spin_box.value() - self.config["waifu2x_converter_cpp"][ - "mode" - ] = self.waifu2x_converter_cpp_mode_combo_box.currentText() - self.config["waifu2x_converter_cpp"][ - "log-level" - ] = self.waifu2x_converter_cpp_log_level_spin_box.value() - self.config["waifu2x_converter_cpp"]["disable-gpu"] = bool( - self.waifu2x_converter_cpp_disable_gpu_check_box.isChecked() - ) - self.config["waifu2x_converter_cpp"]["force-OpenCL"] = bool( - self.waifu2x_converter_cpp_force_opencl_check_box.isChecked() - ) - self.config["waifu2x_converter_cpp"]["tta"] = int( - self.waifu2x_converter_cpp_tta_check_box.isChecked() - ) - - # waifu2x-ncnn-vulkan - self.config["waifu2x_ncnn_vulkan"]["path"] = os.path.expandvars( - self.waifu2x_ncnn_vulkan_path_line_edit.text() - ) - self.config["waifu2x_ncnn_vulkan"][ - "n" - ] = self.waifu2x_ncnn_vulkan_noise_level_spin_box.value() - self.config["waifu2x_ncnn_vulkan"][ - "t" - ] = self.waifu2x_ncnn_vulkan_tile_size_spin_box.value() - self.config["waifu2x_ncnn_vulkan"]["m"] = str( - ( - pathlib.Path(self.config["waifu2x_ncnn_vulkan"]["path"]).parent - / self.waifu2x_ncnn_vulkan_model_combo_box.currentText() - ).absolute() - ) - self.config["waifu2x_ncnn_vulkan"][ - "g" - ] = self.waifu2x_ncnn_vulkan_gpu_id_spin_box.value() - self.config["waifu2x_ncnn_vulkan"][ - "j" - ] = self.waifu2x_ncnn_vulkan_jobs_line_edit.text() - self.config["waifu2x_ncnn_vulkan"][ - "x" - ] = self.waifu2x_ncnn_vulkan_tta_check_box.isChecked() - - # srmd-ncnn-vulkan - self.config["srmd_ncnn_vulkan"]["path"] = os.path.expandvars( - self.srmd_ncnn_vulkan_path_line_edit.text() - ) - self.config["srmd_ncnn_vulkan"][ - "n" - ] = self.srmd_ncnn_vulkan_noise_level_spin_box.value() - self.config["srmd_ncnn_vulkan"][ - "t" - ] = self.srmd_ncnn_vulkan_tile_size_spin_box.value() - self.config["srmd_ncnn_vulkan"]["m"] = str( - ( - pathlib.Path(self.config["srmd_ncnn_vulkan"]["path"]).parent - / self.srmd_ncnn_vulkan_model_combo_box.currentText() - ).absolute() - ) - self.config["srmd_ncnn_vulkan"][ - "g" - ] = self.srmd_ncnn_vulkan_gpu_id_spin_box.value() - self.config["srmd_ncnn_vulkan"][ - "j" - ] = self.srmd_ncnn_vulkan_jobs_line_edit.text() - self.config["srmd_ncnn_vulkan"][ - "x" - ] = self.srmd_ncnn_vulkan_tta_check_box.isChecked() - - # realsr-ncnn-vulkan - self.config["realsr_ncnn_vulkan"]["path"] = os.path.expandvars( - self.realsr_ncnn_vulkan_path_line_edit.text() - ) - self.config["realsr_ncnn_vulkan"][ - "t" - ] = self.realsr_ncnn_vulkan_tile_size_spin_box.value() - self.config["realsr_ncnn_vulkan"]["m"] = str( - ( - pathlib.Path(self.config["realsr_ncnn_vulkan"]["path"]).parent - / self.realsr_ncnn_vulkan_model_combo_box.currentText() - ).absolute() - ) - self.config["realsr_ncnn_vulkan"][ - "g" - ] = self.realsr_ncnn_vulkan_gpu_id_spin_box.value() - self.config["realsr_ncnn_vulkan"][ - "j" - ] = self.realsr_ncnn_vulkan_jobs_line_edit.text() - self.config["realsr_ncnn_vulkan"][ - "x" - ] = self.realsr_ncnn_vulkan_tta_check_box.isChecked() - - # anime4k - self.config["anime4kcpp"]["path"] = os.path.expandvars( - self.anime4kcpp_path_line_edit.text() - ) - self.config["anime4kcpp"]["passes"] = self.anime4kcpp_passes_spin_box.value() - self.config["anime4kcpp"][ - "pushColorCount" - ] = self.anime4kcpp_push_color_count_spin_box.value() - self.config["anime4kcpp"][ - "strengthColor" - ] = self.anime4kcpp_strength_color_spin_box.value() - self.config["anime4kcpp"][ - "strengthGradient" - ] = self.anime4kcpp_strength_gradient_spin_box.value() - self.config["anime4kcpp"]["threads"] = self.anime4kcpp_threads_spin_box.value() - self.config["anime4kcpp"][ - "preFilters" - ] = self.anime4kcpp_pre_filters_spin_box.value() - self.config["anime4kcpp"][ - "postFilters" - ] = self.anime4kcpp_post_filters_spin_box.value() - self.config["anime4kcpp"][ - "platformID" - ] = self.anime4kcpp_platform_id_spin_box.value() - self.config["anime4kcpp"][ - "deviceID" - ] = self.anime4kcpp_device_id_spin_box.value() - self.config["anime4kcpp"][ - "codec" - ] = self.anime4kcpp_codec_combo_box.currentText() - self.config["anime4kcpp"]["fastMode"] = bool( - self.anime4kcpp_fast_mode_check_box.isChecked() - ) - self.config["anime4kcpp"]["preprocessing"] = bool( - self.anime4kcpp_pre_processing_check_box.isChecked() - ) - self.config["anime4kcpp"]["postprocessing"] = bool( - self.anime4kcpp_post_processing_check_box.isChecked() - ) - self.config["anime4kcpp"]["GPUMode"] = bool( - self.anime4kcpp_gpu_mode_check_box.isChecked() - ) - self.config["anime4kcpp"]["CNNMode"] = bool( - self.anime4kcpp_cnn_mode_check_box.isChecked() - ) - self.config["anime4kcpp"]["HDN"] = bool( - self.anime4kcpp_hdn_check_box.isChecked() - ) - self.config["anime4kcpp"][ - "HDNLevel" - ] = self.anime4kcpp_hdn_level_spin_box.value() - self.config["anime4kcpp"][ - "forceFps" - ] = self.anime4kcpp_force_fps_double_spin_box.value() - self.config["anime4kcpp"]["disableProgress"] = bool( - self.anime4kcpp_disable_progress_check_box.isChecked() - ) - self.config["anime4kcpp"]["alpha"] = bool( - self.anime4kcpp_alpha_check_box.isChecked() - ) - - # ffmpeg - self.config["ffmpeg"]["ffmpeg_path"] = os.path.expandvars( - self.ffmpeg_path_line_edit.text() - ) - self.config["ffmpeg"][ - "intermediate_file_name" - ] = self.ffmpeg_intermediate_file_name_line_edit.text() - - # extract frames - self.config["ffmpeg"]["extract_frames"]["output_options"][ - "-pix_fmt" - ] = self.ffmpeg_extract_frames_output_options_pixel_format_line_edit.text() - if not self.ffmpeg_extract_frames_hardware_acceleration_check_box.isChecked(): - self.config["ffmpeg"]["extract_frames"].pop("-hwaccel", None) - - # assemble video - self.config["ffmpeg"]["assemble_video"]["input_options"][ - "-f" - ] = self.ffmpeg_assemble_video_input_options_force_format_line_edit.text() - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-vcodec" - ] = self.ffmpeg_assemble_video_output_options_video_codec_line_edit.text() - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-pix_fmt" - ] = self.ffmpeg_assemble_video_output_options_pixel_format_line_edit.text() - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-crf" - ] = self.ffmpeg_assemble_video_output_options_crf_spin_box.value() - if ( - self.ffmpeg_assemble_video_output_options_tune_combo_box.currentText() - == "none" - ): - self.config["ffmpeg"]["assemble_video"]["output_options"]["-tune"] = None - else: - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-tune" - ] = self.ffmpeg_assemble_video_output_options_tune_combo_box.currentText() - if self.ffmpeg_assemble_video_output_options_bitrate_line_edit.text() != "": - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-b:v" - ] = self.ffmpeg_assemble_video_output_options_bitrate_line_edit.text() - else: - self.config["ffmpeg"]["assemble_video"]["output_options"]["-b:v"] = None - - if ( - self.ffmpeg_assemble_video_output_options_ensure_divisible_check_box.isChecked() - ): - # if video filter is enabled and is not empty and is not equal to divisible by two filter - # append divisible by two filter to the end of existing filter - if ( - "-vf" in self.config["ffmpeg"]["assemble_video"]["output_options"] - and len( - self.config["ffmpeg"]["assemble_video"]["output_options"]["-vf"] - ) - > 0 - and self.config["ffmpeg"]["assemble_video"]["output_options"]["-vf"] - != "pad=ceil(iw/2)*2:ceil(ih/2)*2" - ): - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-vf" - ] += ",pad=ceil(iw/2)*2:ceil(ih/2)*2" - else: - self.config["ffmpeg"]["assemble_video"]["output_options"][ - "-vf" - ] = "pad=ceil(iw/2)*2:ceil(ih/2)*2" - else: - self.config["ffmpeg"]["assemble_video"]["output_options"].pop("-vf", None) - - if not self.ffmpeg_assemble_video_hardware_acceleration_check_box.isChecked(): - self.config["ffmpeg"]["assemble_video"].pop("-hwaccel", None) - - # migrate streams - - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"] = [] - if ( - self.ffmpeg_migrate_streams_output_options_mapping_video_check_box_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"].append( - "0:v?" - ) - if ( - self.ffmpeg_migrate_streams_output_options_mapping_audio_check_box_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"].append( - "1:a?" - ) - if ( - self.ffmpeg_migrate_streams_output_options_mapping_subtitle_check_box_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"].append( - "1:s?" - ) - if ( - self.ffmpeg_migrate_streams_output_options_mapping_data_check_box_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"].append( - "1:d?" - ) - if ( - self.ffmpeg_migrate_streams_output_options_mapping_font_check_box_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"].append( - "1:t?" - ) - - # if the list is empty, delete the key - # otherwise parser will run into an error (key with no value) - if len(self.config["ffmpeg"]["migrate_streams"]["output_options"]["-map"]) == 0: - self.config["ffmpeg"]["migrate_streams"]["output_options"].pop("-map", None) - - self.config["ffmpeg"]["migrate_streams"]["output_options"][ - "-pix_fmt" - ] = self.ffmpeg_migrate_streams_output_options_pixel_format_line_edit.text() - - fps = ( - self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.value() - ) - if fps > 0: - if ( - "-vf" in self.config["ffmpeg"]["migrate_streams"]["output_options"] - and len( - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-vf"] - ) - > 0 - and "minterpolate=" - not in self.config["ffmpeg"]["migrate_streams"]["output_options"]["-vf"] - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"][ - "-vf" - ] += f",minterpolate='fps={fps}'" - else: - self.config["ffmpeg"]["migrate_streams"]["output_options"][ - "-vf" - ] = f"minterpolate='fps={fps}'" - else: - self.config["ffmpeg"]["migrate_streams"]["output_options"].pop("-vf", None) - - # copy source codec - if ( - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"]["-c"] = "copy" - else: - self.config["ffmpeg"]["migrate_streams"]["output_options"].pop("-c", None) - - # copy known metadata - if ( - self.ffmpeg_migrate_streams_output_options_copy_known_metadata_tags_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"][ - "-map_metadata" - ] = 0 - else: - self.config["ffmpeg"]["migrate_streams"]["output_options"].pop( - "-map_metadata", None - ) - - # copy arbitrary metadata - if ( - self.ffmpeg_migrate_streams_output_options_copy_arbitrary_metadata_tags_check_box.isChecked() - ): - self.config["ffmpeg"]["migrate_streams"]["output_options"][ - "-movflags" - ] = "use_metadata_tags" - else: - self.config["ffmpeg"]["migrate_streams"]["output_options"].pop( - "-movflags", None - ) - - # hardware acceleration - if not self.ffmpeg_migrate_streams_hardware_acceleration_check_box.isChecked(): - self.config["ffmpeg"]["migrate_streams"].pop("-hwaccel", None) - - # Gifski - self.config["gifski"]["gifski_path"] = os.path.expandvars( - self.gifski_path_line_edit.text() - ) - self.config["gifski"]["quality"] = self.gifski_quality_spin_box.value() - self.config["gifski"]["fast"] = self.gifski_fast_check_box.isChecked() - self.config["gifski"]["once"] = self.gifski_once_check_box.isChecked() - self.config["gifski"]["quiet"] = self.gifski_quiet_check_box.isChecked() - - def dragEnterEvent(self, event): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()] - for path in input_paths: - if (path.is_file() or path.is_dir()) and not self.input_table_path_exists( - path - ): - self.input_table_data.append(path) - - self.update_output_path() - self.update_input_table() - - def enable_line_edit_file_drop(self, line_edit: QLineEdit): - line_edit.dragEnterEvent = self.dragEnterEvent - line_edit.dropEvent = lambda event: line_edit.setText( - str(pathlib.Path(event.mimeData().urls()[0].toLocalFile()).absolute()) - ) - - def show_ffprobe_output(self, event): - input_paths = [pathlib.Path(u.toLocalFile()) for u in event.mimeData().urls()] - if not input_paths[0].is_file(): - return - - ffmpeg_object = Ffmpeg(self.ffmpeg_settings) - file_info_json = ffmpeg_object.probe_file_info(input_paths[0]) - self.ffprobe_plain_text_edit.setPlainText(json.dumps(file_info_json, indent=2)) - - @staticmethod - def read_config(config_file: pathlib.Path) -> dict: - """read video2x configurations from config file - - Arguments: - config_file {pathlib.Path} -- video2x configuration file pathlib.Path - - Returns: - dict -- dictionary of video2x configuration - """ - - with open(config_file, "r") as config: - return yaml.load(config, Loader=yaml.FullLoader) - - def mutually_exclude_scale_ratio_resolution(self): - if ( - self.output_width_spin_box.value() != 0 - or self.output_height_spin_box.value() != 0 - ): - self.scale_ratio_double_spin_box.setDisabled(True) - elif ( - self.output_width_spin_box.value() == 0 - and self.output_height_spin_box.value() == 0 - ): - self.scale_ratio_double_spin_box.setDisabled(False) - - def mutually_exclude_frame_interpolation_stream_copy(self): - if ( - self.ffmpeg_migrate_streams_output_options_frame_interpolation_spin_box.value() - > 0 - ): - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setChecked( - False - ) - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setDisabled( - True - ) - else: - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setChecked( - True - ) - self.ffmpeg_migrate_streams_output_options_copy_streams_check_box.setDisabled( - False - ) - - def update_gui_for_driver(self): - current_driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] - - # update preferred processes/threads count - if current_driver == "anime4kcpp": - self.processes_spin_box.setValue(16) - else: - self.processes_spin_box.setValue(1) - - def update_input_table(self): - # HACK: use insertRow, removeRow and signals - del self.input_table_model - self.input_table_model = InputTableModel(self.input_table_data) - self.input_table_view.setModel(self.input_table_model) - - def input_table_delete_selected(self): - indexes_to_delete = [i.row() for i in self.input_table_view.selectedIndexes()] - for index in sorted(indexes_to_delete, reverse=True): - del self.input_table_data[index] - - self.update_output_path() - self.update_input_table() - - def input_table_clear_all(self): - self.input_table_data = [] - self.update_output_path() - self.update_input_table() - - def input_table_path_exists(self, input_path: pathlib.Path) -> bool: - for path in self.input_table_data: - # not using Path.samefile since file may not exist - if str(path.absolute()) == str(input_path.absolute()): - return True - return False - - def select_file(self, *args, **kwargs) -> pathlib.Path: - file_selected = QFileDialog.getOpenFileName(self, *args, **kwargs) - if not isinstance(file_selected, tuple) or file_selected[0] == "": - return None - return pathlib.Path(file_selected[0]) - - def select_folder(self, *args, **kwargs) -> pathlib.Path: - folder_selected = QFileDialog.getExistingDirectory(self, *args, **kwargs) - if folder_selected == "": - return None - return pathlib.Path(folder_selected) - - def select_save_file(self, *args, **kwargs) -> pathlib.Path: - save_file_selected = QFileDialog.getSaveFileName(self, *args, **kwargs) - if not isinstance(save_file_selected, tuple) or save_file_selected[0] == "": - return None - return pathlib.Path(save_file_selected[0]) - - def update_output_path(self): - # if input list is empty - # clear output path - if len(self.input_table_data) == 0: - self.output_line_edit.setText("") - - # if there are multiple output files - # use cwd/output directory for output - elif len(self.input_table_data) > 1: - self.output_line_edit.setText(str((CWD / "output").absolute())) - - # if there's only one input file - # generate output file/directory name automatically - elif len(self.input_table_data) == 1: - input_path = self.input_table_data[0] - # give up if input path doesn't exist or isn't a file or a directory - if not input_path.exists() or not ( - input_path.is_file() or input_path.is_dir() - ): - return - - if input_path.is_file(): - - # generate suffix automatically - try: - input_file_mime_type = magic.from_file( - str(input_path.absolute()), mime=True - ) - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] - except Exception: - input_file_type = input_file_subtype = None - - # in case python-magic fails to detect file type - # try guessing file mime type with mimetypes - if input_file_type not in ["image", "video"]: - input_file_mime_type = mimetypes.guess_type(input_path.name)[0] - input_file_type = input_file_mime_type.split("/")[0] - input_file_subtype = input_file_mime_type.split("/")[1] - - # if input file is an image - if input_file_type == "image": - - # if file is a gif, use .gif - if input_file_subtype == "gif": - suffix = ".gif" - - # otherwise, use .png by default for all images - else: - suffix = self.image_output_extension_line_edit.text() - - # if input is video, use .mp4 as output by default - elif input_file_type == "video": - suffix = self.video_output_extension_line_edit.text() - - # if failed to detect file type - # use input file's suffix - else: - suffix = input_path.suffix - - output_path = ( - input_path.parent - / self.output_file_name_format_string_line_edit.text().format( - original_file_name=input_path.stem, extension=suffix - ) - ) - - elif input_path.is_dir(): - output_path = ( - input_path.parent - / self.output_file_name_format_string_line_edit.text().format( - original_file_name=input_path.stem, extension="" - ) - ) - - # try a new name with a different file ID - output_path_id = 0 - while output_path.exists(): - if input_path.is_file(): - output_path = input_path.parent / pathlib.Path( - f"{input_path.stem}_output_{output_path_id}{suffix}" - ) - elif input_path.is_dir(): - output_path = input_path.parent / pathlib.Path( - f"{input_path.stem}_output_{output_path_id}" - ) - output_path_id += 1 - - if not output_path.exists(): - self.output_line_edit.setText(str(output_path.absolute())) - - def select_input_file(self): - input_file = self.select_file("Select Input File") - if input_file is None or self.input_table_path_exists(input_file): - return - self.input_table_data.append(input_file) - self.update_output_path() - self.update_input_table() - - def select_input_folder(self): - input_folder = self.select_folder("Select Input Folder") - if input_folder is None or self.input_table_path_exists(input_folder): - return - self.input_table_data.append(input_folder) - self.update_output_path() - self.update_input_table() - - def select_output_file(self): - output_file = self.select_file("Select Output File") - if output_file is None: - return - self.output_line_edit.setText(str(output_file.absolute())) - - def select_output_folder(self): - output_folder = self.select_folder("Select Output Folder") - if output_folder is None: - return - self.output_line_edit.setText(str(output_folder.absolute())) - - def select_cache_folder(self): - cache_folder = self.select_folder("Select Cache Folder") - if cache_folder is None: - return - self.cache_line_edit.setText(str(cache_folder.absolute())) - - def select_config_file(self): - config_file = self.select_file( - "Select Config File", filter="(YAML files (*.yaml))" - ) - if config_file is None: - return - self.config_line_edit.setText(str(config_file.absolute())) - self.load_configurations() - - def select_driver_binary_path(self, driver_line_edit: QLineEdit): - driver_binary_path = self.select_file("Select Driver Binary File") - if driver_binary_path is None: - return - driver_line_edit.setText(str(driver_binary_path.absolute())) - - def show_shortcuts(self): - message_box = QMessageBox(self) - message_box.setWindowTitle("Video2X Shortcuts") - message_box.setTextFormat(Qt.MarkdownText) - shortcut_information = """**Ctrl+W**:\tExit application\\ -**Ctrl+Q**:\tExit application\\ -**Ctrl+I**:\tOpen select input file dialog\\ -**Ctrl+O**:\tOpen select output file dialog\\ -**Ctrl+Shift+I**:\tOpen select input folder dialog\\ -**Ctrl+Shift+O**:\tOpen select output folder dialog""" - message_box.setText(shortcut_information) - message_box.exec_() - - def show_about(self): - message_box = QMessageBox(self) - message_box.setWindowTitle("About Video2X") - message_box.setIconPixmap(QPixmap(self.video2x_icon_path).scaled(64, 64)) - message_box.setTextFormat(Qt.MarkdownText) - message_box.setText(LEGAL_INFO) - message_box.exec_() - - def show_information(self, message: str): - message_box = QMessageBox(self) - message_box.setWindowTitle("Information") - message_box.setIcon(QMessageBox.Information) - message_box.setText(message) - message_box.exec_() - - def show_warning(self, message: str): - message_box = QMessageBox(self) - message_box.setWindowTitle("Warning") - message_box.setIcon(QMessageBox.Warning) - message_box.setText(message) - message_box.exec_() - - def show_error(self, exception: Exception): - def _process_button_press(button_pressed): - # if the user pressed the save button, save log file to destination - if button_pressed.text() == "Save": - log_file_saving_path = self.select_save_file( - "Select Log File Saving Destination", "video2x_error.log" - ) - if log_file_saving_path is not None: - with open(log_file_saving_path, "w", encoding="utf-8") as log_file: - self.log_file.seek(0) - log_file.write(self.log_file.read()) - - # QErrorMessage(self).showMessage(message.replace('\n', '
')) - message_box = QMessageBox(self) - message_box.setWindowTitle("Error") - message_box.setIcon(QMessageBox.Critical) - message_box.setTextFormat(Qt.MarkdownText) - - error_message = """Upscaler ran into an error:\\ -{}\\ -Check the console output or the log file for details.\\ -You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new?assignees=K4YT3X&labels=bug&template=bug-report.md&title={}) to report this error.\\ -It\'s highly recommended to attach the log file.\\ -You can click \"Save\" to save the log file.""" - message_box.setText( - error_message.format(exception, urllib.parse.quote(str(exception))) - ) - - message_box.setStandardButtons(QMessageBox.Save | QMessageBox.Close) - message_box.setDefaultButton(QMessageBox.Save) - message_box.buttonClicked.connect(_process_button_press) - message_box.exec_() - - def progress_monitor(self, progress_callback: pyqtSignal): - - # initialize progress bar values - progress_callback.emit( - (time.time(), 0, 0, 0, 0, 0, [], pathlib.Path(), pathlib.Path()) - ) - - # keep querying upscaling process and feed information to callback signal - while self.upscaler.running: - - progress_callback.emit( - ( - self.upscaler.current_processing_starting_time, - self.upscaler.total_frames_upscaled, - self.upscaler.total_frames, - self.upscaler.total_processed, - self.upscaler.total_files, - self.upscaler.current_pass, - self.upscaler.scaling_jobs, - self.upscaler.current_input_file, - self.upscaler.last_frame_upscaled, - ) - ) - time.sleep(1) - - # upscale process will stop at 99% - # so it's set to 100 manually when all is done - progress_callback.emit( - ( - time.time(), - self.upscaler.total_frames, - self.upscaler.total_frames, - self.upscaler.total_files, - self.upscaler.total_files, - len(self.upscaler.scaling_jobs), - self.upscaler.scaling_jobs, - pathlib.Path(), - pathlib.Path(), - ) - ) - - def set_progress(self, progress_information: tuple): - current_processing_starting_time = progress_information[0] - total_frames_upscaled = progress_information[1] - total_frames = progress_information[2] - total_processed = progress_information[3] - total_files = progress_information[4] - current_pass = progress_information[5] - scaling_jobs = progress_information[6] - current_input_file = progress_information[7] - last_frame_upscaled = progress_information[8] - - # calculate fields based on frames and time elapsed - time_elapsed = time.time() - current_processing_starting_time - try: - rate = total_frames_upscaled / time_elapsed - time_remaining = (total_frames - total_frames_upscaled) / rate - except Exception: - rate = 0.0 - time_remaining = 0.0 - - # set calculated values in GUI - self.current_progress_bar.setMaximum(total_frames) - self.current_progress_bar.setValue(total_frames_upscaled) - self.frames_label.setText( - "Frames: {}/{}".format(total_frames_upscaled, total_frames) - ) - self.time_elapsed_label.setText( - "Time Elapsed: {}".format( - time.strftime("%H:%M:%S", time.gmtime(time_elapsed)) - ) - ) - self.time_remaining_label.setText( - "Time Remaining: {}".format( - time.strftime("%H:%M:%S", time.gmtime(time_remaining)) - ) - ) - self.rate_label.setText("Rate (FPS): {}".format(round(rate, 2))) - self.overall_progress_label.setText( - "Overall Progress: {}/{}".format(total_processed, total_files) - ) - self.overall_progress_bar.setMaximum(total_files) - self.overall_progress_bar.setValue(total_processed) - self.currently_processing_label.setText( - "Currently Processing: {} (pass {}/{})".format( - str(current_input_file.name), current_pass, len(scaling_jobs) - ) - ) - - # if show frame is checked, show preview image - if ( - self.frame_preview_show_preview_check_box.isChecked() - and last_frame_upscaled.is_file() - ): - last_frame_pixmap = QPixmap(str(last_frame_upscaled.absolute())) - # the -2 here behind geometry subtracts frame size from width and height - self.frame_preview_label.setPixmap( - last_frame_pixmap.scaled( - self.frame_preview_label.width() - 2, - self.frame_preview_label.height() - 2, - Qt.KeepAspectRatio, - ) - ) - - # if keep aspect ratio is checked, don't stretch image - if self.frame_preview_keep_aspect_ratio_check_box.isChecked(): - self.frame_preview_label.setScaledContents(False) - else: - self.frame_preview_label.setScaledContents(True) - - # display image in label - self.frame_preview_label.show() - - # if show frame is unchecked, clear image - elif self.frame_preview_show_preview_check_box.isChecked() is False: - self.frame_preview_label.clear() - - def reset_progress_display(self): - # reset progress display UI elements - self.current_progress_bar.setMaximum(100) - self.current_progress_bar.setValue(0) - self.frames_label.setText("Frames: {}/{}".format(0, 0)) - self.time_elapsed_label.setText( - "Time Elapsed: {}".format(time.strftime("%H:%M:%S", time.gmtime(0))) - ) - self.time_remaining_label.setText( - "Time Remaining: {}".format(time.strftime("%H:%M:%S", time.gmtime(0))) - ) - self.rate_label.setText("Rate (FPS): {}".format(0.0)) - self.overall_progress_label.setText("Overall Progress: {}/{}".format(0, 0)) - self.overall_progress_bar.setMaximum(100) - self.overall_progress_bar.setValue(0) - self.currently_processing_label.setText("Currently Processing:") - - def start(self): - - # start execution - try: - # start timer - self.begin_time = time.time() - - # resolve input and output directories from GUI - if len(self.input_table_data) == 0: - self.show_warning("Input path unspecified") - return - if self.output_line_edit.text().strip() == "": - self.show_warning("Output path unspecified") - return - - if len(self.input_table_data) == 1: - input_directory = self.input_table_data[0] - else: - input_directory = self.input_table_data - - # resolve output directory - output_directory = pathlib.Path( - os.path.expandvars(self.output_line_edit.text()) - ) - - # load driver settings from GUI - self.resolve_driver_settings() - - # load driver settings for the current driver - self.driver_settings = self.config[ - AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] - ] - - # get scale ratio or resolution - if self.scale_ratio_double_spin_box.isEnabled(): - scale_ratio = self.scale_ratio_double_spin_box.value() - scale_width = scale_height = None - - else: - scale_ratio = None - scale_width = self.output_width_spin_box.value() - scale_height = self.output_height_spin_box.value() - - self.upscaler = Upscaler( - # required parameters - input_path=input_directory, - output_path=output_directory, - driver_settings=self.driver_settings, - ffmpeg_settings=self.ffmpeg_settings, - gifski_settings=self.gifski_settings, - # optional parameters - driver=AVAILABLE_DRIVERS[self.driver_combo_box.currentText()], - scale_ratio=scale_ratio, - scale_width=scale_width, - scale_height=scale_height, - processes=self.processes_spin_box.value(), - video2x_cache_directory=pathlib.Path( - os.path.expandvars(self.cache_line_edit.text()) - ), - extracted_frame_format=self.config["video2x"][ - "extracted_frame_format" - ].lower(), - image_output_extension=self.image_output_extension_line_edit.text(), - video_output_extension=self.video_output_extension_line_edit.text(), - preserve_frames=bool(self.preserve_frames_check_box.isChecked()), - ) - - # run upscaler - worker = UpscalerWorker(self.upscaler.run) - worker.signals.error.connect(self.upscale_errored) - worker.signals.interrupted.connect(self.upscale_interrupted) - worker.signals.finished.connect(self.upscale_successful) - self.threadpool.start(worker) - - # start progress monitoring - progress_bar_worker = ProgressMonitorWorkder(self.progress_monitor) - progress_bar_worker.signals.progress.connect(self.set_progress) - self.threadpool.start(progress_bar_worker) - - self.start_button.setEnabled(False) - self.stop_button.setEnabled(True) - - except Exception as e: - traceback.print_exc() - self.upscale_errored(e) - - def upscale_errored(self, exception: Exception): - # send stop signal in case it's not triggered - with contextlib.suppress(AttributeError): - self.upscaler.running = False - - self.show_error(exception) - self.threadpool.waitForDone(5) - self.start_button.setEnabled(True) - self.stop_button.setEnabled(False) - self.reset_progress_display() - - def upscale_interrupted(self): - self.show_information("Upscale has been interrupted") - self.threadpool.waitForDone(5) - self.start_button.setEnabled(True) - self.stop_button.setEnabled(False) - self.reset_progress_display() - - def upscale_successful(self): - # if all threads have finished - self.threadpool.waitForDone(5) - self.show_information( - "Upscale finished successfully, taking {} seconds".format( - round((time.time() - self.begin_time), 5) - ) - ) - self.start_button.setEnabled(True) - self.stop_button.setEnabled(False) - self.reset_progress_display() - - def stop(self): - - try: - # if upscaler is running, ask the user for confirmation - if self.upscaler.running is True: - confirmation = QMessageBox.question( - self, - "Stopping Confirmation", - "Are you sure you want to want to stop the upscaling process?", - QMessageBox.Yes, - QMessageBox.No, - ) - # if the user indeed wants to stop processing - if confirmation == QMessageBox.Yes: - with contextlib.suppress(AttributeError): - self.upscaler.running = False - return True - # if the user doesn't want ot stop processing - else: - return False - - # if the upscaler is not running - else: - return True - - # if an AttributeError happens - # that means the upscaler object haven't been created yet - except AttributeError: - return True - - def closeEvent(self, event: QCloseEvent): - # try cleaning up temp directories - if self.stop(): - event.accept() - else: - event.ignore() - - -# this file shouldn't be imported -if __name__ == "__main__": - try: - app = QApplication(sys.argv) - window = Video2XMainWindow() - window.show() - app.exec_() - - # on GUI exception, print error message in console - # and hold window open using input() - except Exception: - traceback.print_exc() - input("Press enter to close") diff --git a/video2x/video2x_gui.pyproject b/video2x/video2x_gui.pyproject deleted file mode 100644 index 1864e5c..0000000 --- a/video2x/video2x_gui.pyproject +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": ["video2x_gui.ui","video2x_gui.py"] -} diff --git a/video2x/video2x_gui.ui b/video2x/video2x_gui.ui deleted file mode 100644 index ce67e09..0000000 --- a/video2x/video2x_gui.ui +++ /dev/null @@ -1,2949 +0,0 @@ - - - Video2xGui - - - - 0 - 0 - 737 - 1020 - - - - true - - - Video2X GUI - - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - true - - - - - 0 - 0 - 725 - 762 - - - - - - - 0 - - - - Main - - - - - - - 16777215 - 15 - - - - QFrame::NoFrame - - - **Drag and drop file or folder anywhere in this window to select input file/folder.** - - - Qt::MarkdownText - - - false - - - - - - - - 16777215 - 15 - - - - **Drop item on a specific input box to set the value of that box.** - - - Qt::MarkdownText - - - - - - - - 16777215 - 15 - - - - **Hover over an item to see its description (if applicable).** - - - Qt::MarkdownText - - - - - - - Input Selection - - - - - - - - - - - true - - - Select File - - - - - - - Select Folder - - - - - - - Delete Selected - - - - - - - Clear All - - - - - - - - - - - - Output Selection - - - - - - - - - 0 - 0 - - - - - 63 - 0 - - - - Output - - - - - - - - - - Select File - - - - - - - Select Folder - - - - - - - - - - - - 63 - 0 - - - - Output File Name Format String - - - - - - - <html><head/><body><p>Format string used to generate output file name(s).</p><p>For example, if the input is &quot;sample.mp4&quot;, and the format string is &quot;{original_file_name}_output{extension}&quot;, then the output file name will be &quot;sample_output.mp4&quot;.</p></body></html> - - - {original_file_name}_output{extension} - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - Other Paths Selection - - - - - - - - - 0 - 0 - - - - - 63 - 0 - - - - Config - - - - - - - - - - Select - - - - - - - - - - - - 63 - 0 - - - - Cache Folder - - - - - - - - - - Select Folder - - - - - - - - - - - - - - Express Settings - - - - - - - - Driver - - - - - - - <p style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Driver to use for - upscaling. </span></p> -<ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Waifu2X Caffe</span> - <ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span - style="font-size: 8pt; font-weight: 600;">requires Nvidia GPU and CUDA/cuDNN</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Caffe - implementation of waifu2x which is classic and stable</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Lots of models - available</span></li> - </ul> - </li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Waifu2X Converter - CPP</span> - <ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">CPP - implementation of waifu2x</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Uses OpenCL - and OpenCV for graphical processing</span></li> - </ul> - </li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Waifu2X NCNN - Vulkan</span> - <ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">NCNN - implementation of waifu2x</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Uses Vulkan - API for graphical processing</span></li> - </ul> - </li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">SRMD NCNN - Vulkan</span> - <ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">NCNN - implementation of SRMD</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Uses Vulkan - API for graphical processing</span></li> - </ul> - </li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Anime4KCPP</span> - <ul> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">CPP - implementation of Anime4K</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Very fast but - low quality</span></li> - <li style="-qt-block-indent: 0; text-indent: 0px; margin: 0px;"><span style="font-size: 8pt;">Multithreading - is preferred</span></li> - </ul> - </li> -</ul> - - - - Waifu2X ncnn Vulkan - - - - - Waifu2X Caffe - - - - - Waifu2X Converter CPP - - - - - SRMD ncnn Vulkan - - - - - RealSR ncnn Vulkan - - - - - Anime4KCPP - - - - - DAIN ncnn Vulkan - - - - - CAIN ncnn Vulkan - - - - - RIFE ncnn Vulkan - - - - - - - - - - - - Processes - - - - - - - Number of processes/threads to launch. Going to high for Waifu2X drivers might GPU's VRAM space. - - - 1 - - - - - - - - - - - Scale Ratio - - - - - - - <html><head/><body><p>Number of times to enlarge the video. Larger values will increase the time needed drastically.</p></body></html> - - - 0.000000000000000 - - - 9999.989999999999782 - - - 0.500000000000000 - - - 2.000000000000000 - - - - - - - - - - - Output Width - - - - - - - 99999 - - - - - - - - - - - Output Height - - - - - - - 99999 - - - - - - - - - - - Image Output Extension - - - - - - - - 0 - 0 - - - - .png - - - - - - - - - - - Video Output Extension - - - - - - - - 0 - 0 - - - - .mp4 - - - - - - - - - Check this to prevent Video2X from cleaning up its cache folder containing extracted and upscaled frames. - - - Preserve Frames - - - false - - - - - - - - - - Frame Preview - - - - - - - - - - QFrame::StyledPanel - - - - - - false - - - Qt::AlignCenter - - - - - - - Show Preview - - - true - - - - - - - Keep Aspect Ratio - - - true - - - - - - - - - - - - - - - - - Driver Settings - - - - - - 0 - - - - Waifu2X Caffe - - - - - - - - - - - Select Binary Path - - - - - - - - - - - Mode - - - - - - - image processing mode - - - - noise_scale - - - - - auto_scale - - - - - scale - - - - - noise - - - - - - - - - - - - Noise Level - - - - - - - noise reduction level - - - 3 - - - 3 - - - - - - - - - - - Process - - - - - - - process mode, cudnn requires CUDA and cuDNN to be installed - - - - gpu - - - - - cudnn - - - - - cpu - - - - - - - - - - - - Model - - - - - - - name of the built-in model to use - - - - cunet - - - - - anime_style_art - - - - - anime_style_art_rgb - - - - - photo - - - - - ukbench - - - - - upconv_7_anime_style_art_rgb - - - - - upconv_7_photo - - - - - upresnet10 - - - - - - - - - - - - Crop Size - - - - - - - input image split size - - - 9999 - - - 128 - - - - - - - - - - - Output Quality - - - - - - - output image quality - - - -1 - - - -1 - - - - - - - - - - - Output Depth - - - - - - - output image chanel depth bit - - - 9999 - - - 8 - - - - - - - - - - - Batch Size - - - - - - - input batch size - - - 9999 - - - 1 - - - - - - - - - - - GPU - - - - - - - gpu device no - - - - - - - - - - - 8x slower and slightly high quality - - - TTA - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Waifu2X Converter CPP - - - - - - - - - - - Select Binary Path - - - - - - - - - - - PNG Compression - - - - - - - 9 - - - 5 - - - - - - - - - - - Image Quality - - - - - - - -1 - - - 101 - - - -1 - - - - - - - - - - - Block Size - - - - - - - 9999 - - - - - - - - - - - Processor - - - - - - - -1 - - - 9999 - - - -1 - - - - - - - - - - - Model - - - - - - - - models_rgb - - - - - - - - - - - - Noise Level - - - - - - - 3 - - - 1 - - - - - - - - - - - Mode - - - - - - - - noise-scale - - - - - noise - - - - - scale - - - - - - - - - - - - Log Level - - - - - - - 4 - - - 1 - - - - - - - - - - - Disable GPU - - - - - - - Force OpenCL - - - - - - - TTA - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Waifu2X NCNN Vulkan - - - - - - - - - - - Select Binary Path - - - - - - - - - - - Noise Level - - - - - - - -1 - - - 3 - - - - - - - - - - - Tile Size - - - - - - - 32 - - - 99999 - - - 400 - - - - - - - - - - - Model - - - - - - - - models-cunet - - - - - models-upconv_7_anime_style_art_rgb - - - - - models-upconv_7_photo - - - - - - - - - - - - GPU ID - - - - - - - - - - - - - - Jobs - - - - - - - 1:2:2 - - - - - - - - - - - TTA - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - SRMD NCNN Vulkan - - - - - - - - - - - Select Binary Path - - - - - - - - - - - Noise Level - - - - - - - -1 - - - 10 - - - 3 - - - - - - - - - - - Tile Size - - - - - - - 32 - - - 99999 - - - 400 - - - - - - - - - - - Model - - - - - - - - models-srmd - - - - - - - - - - - - GPU ID - - - - - - - - - - - - - - Jobs - - - - - - - 1:2:2 - - - - - - - - - - - TTA - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - RealSR NCNN Vulkan - - - - - - - - - - - Select Binary Path - - - - - - - - - - - Tile Size - - - - - - - 0 - - - 99999 - - - 0 - - - - - - - - - - - Model - - - - - - - <html><head/><body><p>If the input file contains very little noise, DF2K will produce sharper outputs.</p><p>However, if the input is noisy, DF2K might produce artifacts. DF2K_JPEG will then be preferred.</p></body></html> - - - - models-DF2K_JPEG - - - - - models-DF2K - - - - - - - - - - - - GPU ID - - - - - - - - - - - - - - Jobs - - - - - - - 1:2:2 - - - - - - - - - TTA - - - - - - - Qt::Vertical - - - - 20 - 331 - - - - - - - - - Anime4K CPP - - - - - - - - Threads - - - - - - - 99999 - - - 16 - - - - - - - - - - - Passes - - - - - - - 99999 - - - 2 - - - - - - - - - - - Pre-Filters - - - - - - - 1 - - - 99999 - - - 4 - - - - - - - - - - - Platform ID - - - - - - - 99999 - - - - - - - - - - - Strength Gradient - - - - - - - 1.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - - - - - - Strength Color - - - - - - - 1.000000000000000 - - - 0.100000000000000 - - - 0.300000000000000 - - - - - - - - - Switches - - - - - - Fast Mode - - - - - - - Pre-Processing - - - - - - - Post-Processing - - - - - - - GPU Mode - - - - - - - CNN Mode - - - - - - - HDN - - - - - - - Disable Progress - - - - - - - Preserve Alpha Channel - - - - - - - - - - - - Device ID - - - - - - - 99999 - - - - - - - - - - - Codec - - - - - - - - mp4v - - - - - dxva - - - - - avc1 - - - - - vp09 - - - - - hevc - - - - - av01 - - - - - - - - - - - - - - - Select Binary Path - - - - - - - - - - - Push Color Count - - - - - - - 99999 - - - 2 - - - - - - - - - - - Post-Filters - - - - - - - 1 - - - 99999 - - - 40 - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - HDN Level - - - - - - - 1 - - - - - - - - - - - Force FPS - - - - - - - - - - - - - - - - - FFmpeg Settings - - - - - - 2 - - - - Global Options - - - - - - - - - - - Select FFmpeg Binary Directory - - - - - - - - - - - Intermediate File Name - - - - - - - intermediate.mkv - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Frame Extraction - - - - - - Input Options - - - - - - There are no input options for this stage. - - - - - - - - - - Output Options - - - - - - - - Pixel Format (-pix_fmt) - - - - - - - rgba64be - - - - - - - - - - - - General - - - - - - Hardware Acceleration (-hwaccel auto) - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Video Assembly - - - - - - Input Options - - - - - - - - Force Format (-f) - - - - - - - - 0 - 0 - - - - image2 - - - - - - - - - - - - Output Options - - - - - - - - Video Codec (-vcodec) - - - - - - - - 0 - 0 - - - - libx264 - - - - - - - - - - - Pixel Format (-pix_fmt) - - - - - - - - 0 - 0 - - - - yuv420p - - - - - - - - - - - H.264/H.265 Constant Rate Factor (-crf) - - - - - - - 51 - - - 17 - - - - - - - - - - - H.264/H.265 Tune (-tune) - - - - - - - <html><head/><body><p>- film (x264 only): use for high quality movie content; lowers deblocking</p><p>- animation (x264 only): good for cartoons; uses higher deblocking and more reference frames</p><p>- grain: preserves the grain structure in old, grainy film material</p><p>- stillimage: (x264 only) good for slideshow-like content</p><p>- fastdecode: allows faster decoding by disabling certain filters</p><p>- zerolatency: good for fast encoding and low-latency streaming</p><p>- psnr: ignore this as it is only used for codec development</p><p>- ssim: ignore this as it is only used for codec development </p></body></html> - - - - animation - - - - - film - - - - - grain - - - - - stillimage - - - - - fastdecode - - - - - zerolatency - - - - - psnr - - - - - ssim - - - - - none - - - - - - - - - - - - Target Average Bitrate (-b:v) - - - - - - - - 0 - 0 - - - - - - - - - - - - - Ensure output width and height are divisible by 2 (-vf: "pad=ceil(iw/2)*2:ceil(ih/2)*2") - - - true - - - - - - - - - - General - - - - - - Hardware Acceleration (-hwaccel auto) - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Streams Migration - - - - - - Input Options - - - - - - There are no input options for this stage. - - - - - - - - - - Output Options - - - - - - Mapping - - - - - - Copy additional video streams (-map 0:v?) - - - true - - - - - - - Copy audio streams (-map 1:a?) - - - true - - - - - - - Copy subtitle streams (-map 1:s?) - - - true - - - - - - - Copy data streams (-map 1:d?) - - - true - - - - - - - Copy fonts (-map 1:t?) - - - true - - - - - - - - - - Other Options - - - - - - - - Pixel Format (-pix_fmt) - - - - - - - - - - - - - - Frame Interpolation (-filter "minterpolate='fps=n'") - - - - - - - 9999 - - - - - - - - - Copy streams without re-encoding - - - Copy streams (-c copy) - - - true - - - - - - - Copy known metadata tags (-map_metadata 0) - - - true - - - - - - - Copy arbitrary metadata tags (-movflags use_metadata_tags) - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - Switches - - - - - - Hardware Acceleration (-hwaccel auto) - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - Gifski Settings - - - - - - - - - - - Select Gifski Binary - - - - - - - - - - - Quality - - - - - - - 1 - - - 100 - - - 100 - - - - - - - - - Switches - - - - - - Fast - - - - - - - Once - - - - - - - Quiet - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Tools - - - - - - 0 - - - - FFprobe - - - - - - Drop a file in this box to get its FFprobe output. - - - - - - - - - - - - - - - - - - - Current Processing Progress - - - - - - - 0 - 20 - - - - Currently Processing: - - - - - - - 0 - - - - - - - - - - 0 - 20 - - - - false - - - QFrame::StyledPanel - - - Time Elapsed: 00:00:00 - - - - - - - - 0 - 20 - - - - QFrame::StyledPanel - - - Time Remaining: 00:00:00 - - - - - - - - 0 - 20 - - - - QFrame::StyledPanel - - - Rate (FPS): 0.0 - - - - - - - QFrame::StyledPanel - - - Frames: 0/0 - - - - - - - - - - - - Overall Processing Progress - - - - - - 0 - - - - - - - - - - 0 - 20 - - - - - 16777215 - 16777215 - - - - QFrame::StyledPanel - - - Overall Progress: 0/0 - - - - - - - false - - - - 130 - 0 - - - - - 16777215 - 16777215 - - - - Start - - - - - - - false - - - - 130 - 0 - - - - - 16777215 - 16777215 - - - - Stop - - - - - - - - - - - - - - 0 - 0 - 737 - 21 - - - - - File - - - - - - Help - - - - - - - - - - - Exit - - - - - About - - - - - Shortcuts - - - - - - diff --git a/video2x/video2x_setup.py b/video2x/video2x_setup.py deleted file mode 100755 index 713c5a9..0000000 --- a/video2x/video2x_setup.py +++ /dev/null @@ -1,518 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X Setup Script -Creator: K4YT3X -Date Created: November 28, 2018 -Last Modified: December 13, 2020 - -Editor: BrianPetkovsek -Editor: SAT3LL -Editor: konqiDAM - -Description: This script helps installing all dependencies of video2x -and generates a configuration for it. - -Installation Details: -- ffmpeg: %LOCALAPPDATA%\\video2x\\ffmpeg -- waifu2x-caffe: %LOCALAPPDATA%\\video2x\\waifu2x-caffe -- waifu2x-cpp-converter: %LOCALAPPDATA%\\video2x\\waifu2x-converter-cpp -- waifu2x_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\waifu2x-ncnn-vulkan -- srmd_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\srmd-ncnn-vulkan -- realsr_ncnn_vulkan: %LOCALAPPDATA%\\video2x\\realsr-ncnn-vulkan -- anime4kcpp: %LOCALAPPDATA%\\video2x\\anime4kcpp -""" - -# built-in imports -from datetime import timedelta -import argparse -import contextlib -import os -import pathlib -import platform -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -import time -import traceback -import urllib -import zipfile - -# Some libraries don't come with default Python installation. -# Therefore, they will be installed during the Python dependency -# installation step and imported later in the script. - -SETUP_VERSION = "2.4.1" - -# global static variables -LOCALAPPDATA = pathlib.Path(os.getenv("localappdata")) -DRIVER_OPTIONS = [ - "all", - "ffmpeg", - "gifski", - "waifu2x_caffe", - "waifu2x_converter_cpp", - "waifu2x_ncnn_vulkan", - "srmd_ncnn_vulkan", - "realsr_ncnn_vulkan", - "anime4kcpp", -] - - -def parse_arguments(): - """parse command line arguments""" - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - parser.add_argument( - "-d", - "--driver", - help="driver to download and configure", - choices=DRIVER_OPTIONS, - default="all", - ) - parser.add_argument( - "-u", - "--uninstall", - help="uninstall Video2X dependencies from default location", - action="store_true", - ) - # parse arguments - return parser.parse_args() - - -class Video2xSetup: - """install dependencies for video2x video enlarger - - This library is meant to be executed as a stand-alone - script. All files will be installed under %LOCALAPPDATA%\\video2x. - """ - - def __init__(self, driver, download_python_modules): - self.driver = driver - self.download_python_modules = download_python_modules - self.trash = [] - - def run(self): - # regardless of which driver to install - # always ensure Python modules are installed and up-to-date - if self.download_python_modules: - print("\nInstalling Python libraries") - self._install_python_requirements() - - # if all drivers are to be installed - if self.driver == "all": - DRIVER_OPTIONS.remove("all") - for driver in DRIVER_OPTIONS: - getattr(self, f"_install_{driver}")() - - # install only the selected driver - else: - getattr(self, f"_install_{self.driver}")() - - # self._cleanup() - - def _install_python_requirements(self): - """Read requirements.txt and return its content""" - pip_install("requirements.txt") - - def _cleanup(self): - """Cleanup all the temp files downloaded""" - print("\nCleaning up temporary files") - - for file in self.trash: - try: - if file.is_dir(): - print(f"Deleting directory: {file}") - shutil.rmtree(file) - else: - print(f"Deleting file: {file}") - file.unlink() - except Exception: - print(f"Error deleting: {file}") - traceback.print_exc() - - def _install_ffmpeg(self): - """Install FFMPEG""" - print("\nInstalling FFmpeg") - - import patoolib - - latest_release = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z" - - ffmpeg_7z = download(latest_release, tempfile.gettempdir()) - self.trash.append(ffmpeg_7z) - - # if running in PyInstaller, add sys._MEIPASS\7z to path - # this directory contains 7za.exe and its DLL files - with contextlib.suppress(AttributeError): - os.environ["PATH"] += f";{sys._MEIPASS}\\7z" - - ffmpeg_directory = LOCALAPPDATA / "video2x" / "ffmpeg" - - # (ffmpeg_directory).mkdir(parents=True, exist_ok=True) - # pyunpack.Archive(ffmpeg_7z).extractall(ffmpeg_directory) - if (ffmpeg_directory).exists(): - shutil.rmtree(ffmpeg_directory) - patoolib.extract_archive(str(ffmpeg_7z), outdir=str(LOCALAPPDATA / "video2x")) - (LOCALAPPDATA / "video2x" / ffmpeg_7z.stem).rename(ffmpeg_directory) - - def _install_gifski(self): - print("\nInstalling Gifski") - import requests - - # Get latest release of Gifski via Github API - releases = requests.get( - "https://api.github.com/repos/ImageOptim/gifski/releases" - ).json() - for release in releases: - for asset in release["assets"]: - if re.search(r"gifski-.*\.tar\.xz", asset["browser_download_url"]): - gifski_tar_xz = download( - asset["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(gifski_tar_xz) - - # extract and rename - with tarfile.open(gifski_tar_xz) as archive: - archive.extractall(LOCALAPPDATA / "video2x" / "gifski") - - return - - def _install_waifu2x_caffe(self): - """Install waifu2x_caffe""" - print("\nInstalling waifu2x-caffe") - import requests - - # Get latest release of waifu2x-caffe via GitHub API - latest_release = requests.get( - "https://api.github.com/repos/lltcggie/waifu2x-caffe/releases/latest" - ).json() - - for a in latest_release["assets"]: - if "waifu2x-caffe.zip" in a["browser_download_url"]: - waifu2x_caffe_zip = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(waifu2x_caffe_zip) - - with zipfile.ZipFile(waifu2x_caffe_zip) as zipf: - zipf.extractall(LOCALAPPDATA / "video2x") - - def _install_waifu2x_converter_cpp(self): - """Install waifu2x_caffe""" - print("\nInstalling waifu2x-converter-cpp") - import requests - - # Get latest release of waifu2x-caffe via GitHub API - latest_release = requests.get( - "https://api.github.com/repos/DeadSix27/waifu2x-converter-cpp/releases/latest" - ).json() - - for a in latest_release["assets"]: - if re.search( - r"waifu2x-DeadSix27-win64_v[0-9]*\.zip", a["browser_download_url"] - ): - waifu2x_converter_cpp_zip = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(waifu2x_converter_cpp_zip) - - with zipfile.ZipFile(waifu2x_converter_cpp_zip) as zipf: - zipf.extractall(LOCALAPPDATA / "video2x" / "waifu2x-converter-cpp") - - def _install_waifu2x_ncnn_vulkan(self): - """Install waifu2x-ncnn-vulkan""" - print("\nInstalling waifu2x-ncnn-vulkan") - import requests - - # Get latest release of waifu2x-ncnn-vulkan via Github API - latest_release = requests.get( - "https://api.github.com/repos/nihui/waifu2x-ncnn-vulkan/releases/latest" - ).json() - - for a in latest_release["assets"]: - if re.search( - r"waifu2x-ncnn-vulkan-\d*-windows\.zip", a["browser_download_url"] - ): - waifu2x_ncnn_vulkan_zip = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(waifu2x_ncnn_vulkan_zip) - - # extract and rename - waifu2x_ncnn_vulkan_directory = LOCALAPPDATA / "video2x" / "waifu2x-ncnn-vulkan" - with zipfile.ZipFile(waifu2x_ncnn_vulkan_zip) as zipf: - zipf.extractall(LOCALAPPDATA / "video2x") - - # if directory already exists, remove it - if waifu2x_ncnn_vulkan_directory.exists(): - shutil.rmtree(waifu2x_ncnn_vulkan_directory) - - # rename the newly extracted directory - (LOCALAPPDATA / "video2x" / zipf.namelist()[0]).rename( - waifu2x_ncnn_vulkan_directory - ) - - def _install_srmd_ncnn_vulkan(self): - """Install srmd-ncnn-vulkan""" - print("\nInstalling srmd-ncnn-vulkan") - import requests - - # Get latest release of srmd-ncnn-vulkan via Github API - latest_release = requests.get( - "https://api.github.com/repos/nihui/srmd-ncnn-vulkan/releases/latest" - ).json() - - for a in latest_release["assets"]: - if re.search( - r"srmd-ncnn-vulkan-\d*-windows\.zip", a["browser_download_url"] - ): - srmd_ncnn_vulkan_zip = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(srmd_ncnn_vulkan_zip) - - # extract and rename - srmd_ncnn_vulkan_directory = LOCALAPPDATA / "video2x" / "srmd-ncnn-vulkan" - with zipfile.ZipFile(srmd_ncnn_vulkan_zip) as zipf: - zipf.extractall(LOCALAPPDATA / "video2x") - - # if directory already exists, remove it - if srmd_ncnn_vulkan_directory.exists(): - shutil.rmtree(srmd_ncnn_vulkan_directory) - - # rename the newly extracted directory - (LOCALAPPDATA / "video2x" / zipf.namelist()[0]).rename( - srmd_ncnn_vulkan_directory - ) - - def _install_realsr_ncnn_vulkan(self): - """Install realsr-ncnn-vulkan""" - print("\nInstalling realsr-ncnn-vulkan") - import requests - - # Get latest release of realsr-ncnn-vulkan via Github API - latest_release = requests.get( - "https://api.github.com/repos/nihui/realsr-ncnn-vulkan/releases/latest" - ).json() - - for a in latest_release["assets"]: - if re.search( - r"realsr-ncnn-vulkan-\d*-windows\.zip", a["browser_download_url"] - ): - realsr_ncnn_vulkan_zip = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(realsr_ncnn_vulkan_zip) - - # extract and rename - realsr_ncnn_vulkan_directory = LOCALAPPDATA / "video2x" / "realsr-ncnn-vulkan" - with zipfile.ZipFile(realsr_ncnn_vulkan_zip) as zipf: - zipf.extractall(LOCALAPPDATA / "video2x") - - # if directory already exists, remove it - if realsr_ncnn_vulkan_directory.exists(): - shutil.rmtree(realsr_ncnn_vulkan_directory) - - # rename the newly extracted directory - (LOCALAPPDATA / "video2x" / zipf.namelist()[0]).rename( - realsr_ncnn_vulkan_directory - ) - - def _install_anime4kcpp(self): - """Install Anime4KCPP""" - print("\nInstalling Anime4KCPP") - - import patoolib - import requests - - # get latest release of Anime4KCPP via Github API - # at the time of writing this portion, Anime4KCPP doesn't yet have a stable release - # therefore releases/latest won't work - latest_release = requests.get( - "https://api.github.com/repos/TianZerL/Anime4KCPP/releases/latest" - ).json() - - for a in latest_release["assets"]: - if re.search( - r"Anime4KCPP_CLI-.*-Win64-msvc\.7z", a["browser_download_url"] - ): - anime4kcpp_7z = download( - a["browser_download_url"], tempfile.gettempdir() - ) - self.trash.append(anime4kcpp_7z) - - # if running in PyInstaller, add sys._MEIPASS\7z to path - # this directory contains 7za.exe and its DLL files - with contextlib.suppress(AttributeError): - os.environ["PATH"] += f";{sys._MEIPASS}\\7z" - - # (LOCALAPPDATA / 'video2x' / 'anime4kcpp').mkdir(parents=True, exist_ok=True) - # pyunpack.Archive(anime4kcpp_7z).extractall(LOCALAPPDATA / 'video2x' / 'anime4kcpp') - if (LOCALAPPDATA / "video2x" / "anime4kcpp").exists(): - shutil.rmtree(LOCALAPPDATA / "video2x" / "anime4kcpp") - patoolib.extract_archive( - str(anime4kcpp_7z), outdir=str(LOCALAPPDATA / "video2x" / "anime4kcpp") - ) - - -def download(url, save_path, chunk_size=4096): - """Download file to local with requests library""" - from tqdm import tqdm - import requests - - save_path = pathlib.Path(save_path) - - # create target folder if it doesn't exist - save_path.mkdir(parents=True, exist_ok=True) - - # create requests stream for steaming file - stream = requests.get(url, stream=True, allow_redirects=True) - - # get file name - file_name = None - if "content-disposition" in stream.headers: - disposition = stream.headers["content-disposition"] - with contextlib.suppress(IndexError): - file_name = re.findall("filename=(.+)", disposition)[0].strip('"') - - if file_name is None: - # output_file = f'{save_path}\\{stream.url.split("/")[-1]}' - output_file = save_path / stream.url.split("/")[-1] - else: - output_file = save_path / file_name - - # decode url encoding - output_file = pathlib.Path(urllib.parse.unquote(str(output_file))) - - # get total size for progress bar if provided in headers - total_size = 0 - if "content-length" in stream.headers: - total_size = int(stream.headers["content-length"]) - - # print download information summary - print(f"Downloading: {url}") - print(f"Total size: {total_size}") - print(f"Chunk size: {chunk_size}") - print(f"Saving to: {output_file}") - - # Write content into file - with open(output_file, "wb") as output: - with tqdm(total=total_size, ascii=True) as progress_bar: - for chunk in stream.iter_content(chunk_size=chunk_size): - if chunk: - output.write(chunk) - progress_bar.update(len(chunk)) - - # return the full path of saved file - return output_file - - -def pip_install(file): - """Install python package via python pip module - - pip.main() is not available after pip 9.0.1, thus - pip module is not used in this case. - """ - return subprocess.run( - [sys.executable, "-m", "pip", "install", "-U", "-r", file] - ).returncode - - -if __name__ != "__main__": - raise ImportError("video2x_setup cannot be imported") - -try: - # set default exit code - EXIT_CODE = 0 - - # get start time - start_time = time.time() - - # check platform - if platform.system() != "Windows": - 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: {SETUP_VERSION}") - - # uninstall video2x dependencies - if args.uninstall: - if ( - input( - "Are you sure you want to uninstall all Video2X dependencies? [y/N]: " - ).lower() - == "y" - ): - try: - print(f'Removing: {LOCALAPPDATA / "video2x"}') - shutil.rmtree(LOCALAPPDATA / "video2x") - print("Successfully uninstalled all dependencies") - except FileNotFoundError: - print(f'Dependency folder does not exist: {LOCALAPPDATA / "video2x"}') - else: - print("Uninstallation aborted") - - # run installation - else: - # 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 - - # create setup install instance and run installer - setup = Video2xSetup(args.driver, download_python_modules) - setup.run() - - print("\nScript finished successfully") - -# let SystemExit signals pass through -except SystemExit as e: - raise e - -# 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") - - EXIT_CODE = 1 - -# regardless if script finishes successfully or not -# print script execution summary -finally: - # always try cleaning up trash - try: - setup._cleanup() - except Exception: - traceback.print_exc() - print("An error occurred while trying to cleanup files") - - print("Script finished") - print(f"Time taken: {timedelta(seconds=round(time.time() - start_time))}") - - # if the program is launched as an Windows PE file - # it might be launched from double clicking - # pause the window before it closes automatically - if sys.argv[0].endswith(".exe"): - input("Press [ENTER] to exit script") - - sys.exit(EXIT_CODE) diff --git a/video2x/video2x_setup_ubuntu.sh b/video2x/video2x_setup_ubuntu.sh deleted file mode 100755 index 92e123d..0000000 --- a/video2x/video2x_setup_ubuntu.sh +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/bash -e -# Name: Video2X Setup Script (Ubuntu) -# Creator: K4YT3X -# Date Created: June 5, 2020 -# Last Modified: September 4, 2020 - -# help message if input is incorrect of if -h/--help is specified -if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ "$#" -gt 2 ]; then - echo "usage: $0 INSTALLATION_PATH TEMP" - exit 0 -fi - -# set intallation path if specified -if [ ! -z "$1" ]; then - export INSTALLATION_PATH=$1 -else - export INSTALLATION_PATH="$HOME/.local/share" -fi - -# set temp directory location if specified -if [ ! -z "$2" ]; then - export TEMP=$2 -else - export TEMP="/tmp/video2x" -fi - -# environment variables -export DEBIAN_FRONTEND="noninteractive" - -# install basic utilities and add PPAs -apt-get update -apt-get install -y --no-install-recommends apt-utils software-properties-common - -# add PPAs and sources -add-apt-repository -y ppa:apt-fast/stable -add-apt-repository -y ppa:graphics-drivers/ppa -apt-get install -y --no-install-recommends apt-fast aria2 -apt-fast update - -# install runtime packages -apt-fast install -y --no-install-recommends ffmpeg libmagic1 nvidia-cuda-toolkit nvidia-driver-440 python3.8 - -# install compilation packages -apt-fast install -y --no-install-recommends git-core curl wget ca-certificates gnupg2 python3-dev python3-pip python3-setuptools python3-wheel - -# add Nvidia sources -curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub | apt-key add - -echo "deb https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64 /" >/etc/apt/sources.list.d/nvidia-ml.list -apt-fast update - -# install python3 packages -git clone --recurse-submodules --progress https://github.com/k4yt3x/video2x.git --depth=1 $INSTALLATION_PATH/video2x -python3.8 -m pip install -U pip -python3.8 -m pip install -U -r $INSTALLATION_PATH/video2x/src/requirements.txt -mkdir -v -p $INSTALLATION_PATH/video2x/src/dependencies - -# install gifski -# cargo from APT might be outdate and will result in gifski components not being built successfully -# apt-fast install -y --no-install-recommends cargo -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y -source $HOME/.cargo/env -cargo install gifski - -# install waifu2x-caffe -apt-fast install -y --no-install-recommends autoconf build-essential cmake gcc-8 libatlas-base-dev libboost-atomic-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-iostreams-dev libboost-python-dev libboost-system-dev libboost-thread-dev libcudnn7 libcudnn7-dev libgflags-dev libgoogle-glog-dev libhdf5-dev libleveldb-dev liblmdb-dev libopencv-dev libprotobuf-dev libsnappy-dev protobuf-compiler python-dev python-numpy texinfo yasm zlib1g-dev - -git clone --recurse-submodules --depth=1 --progress --recurse-submodules https://github.com/nagadomi/waifu2x-caffe-ubuntu.git $TEMP/waifu2x-caffe-ubuntu -git clone --recurse-submodules --progress --depth=1 https://github.com/nagadomi/caffe.git $TEMP/waifu2x-caffe-ubuntu/caffe - -export CC=/usr/bin/gcc-8 -export CXX=/usr/bin/g++-8 -mkdir -v -p $TEMP/waifu2x-caffe-ubuntu/build -cd $TEMP/waifu2x-caffe-ubuntu/build -cmake .. -DCMAKE_INSTALL_PREFIX=/usr -make -j$(nproc) install - -mv -v /tmp/video2x/waifu2x-caffe-ubuntu/bin $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-caffe -mv -v /tmp/video2x/waifu2x-caffe-ubuntu/build/waifu2x-caffe $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-caffe/waifu2x-caffe - -# install waifu2x-converter-cpp -apt-fast install -y --no-install-recommends build-essential cmake libopencv-dev beignet-opencl-icd mesa-opencl-icd nvidia-cuda-toolkit ocl-icd-opencl-dev opencl-headers -git clone --recurse-submodules --depth=1 --progress https://github.com/DeadSix27/waifu2x-converter-cpp $TEMP/waifu2x-converter-cpp -mkdir -v $TEMP/waifu2x-converter-cpp/build -cd $TEMP/waifu2x-converter-cpp/build -cmake .. -make -j$(nproc) -ldconfig -mv -v $TEMP/waifu2x-converter-cpp/build $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-converter-cpp -mv -v $TEMP/waifu2x-converter-cpp/models_rgb $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-converter-cpp/models_rgb - -# install waifu2x-ncnn-vulkan -# download libvulkan1 -apt-fast install -y --no-install-recommends libvulkan1 unzip jq - -# get latest release JSON as a string -echo "Fetching latest waifu2x-ncnn-vulkan release information using GitHub API" -waifu2x_ncnn_vulkan_latest_release=$(curl -s https://api.github.com/repos/nihui/waifu2x-ncnn-vulkan/releases/latest) - -# count the number of assets in this release -assets=$(echo "$waifu2x_ncnn_vulkan_latest_release" | jq -r '.assets | length') - -# iterate through each of the assets and see if the name of the asset matches what we're looking for -for i in $(seq $assets $END); do - if echo "$waifu2x_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].name" | egrep "^waifu2x-ncnn-vulkan-[0-9]*-linux\.zip$"; then - download_link=$(echo "$waifu2x_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].browser_download_url") - break - fi -done - -# check if download_link variable is set -if [ -z "$download_link" ]; then - echo "$waifu2x_ncnn_vulkan_latest_release" - echo "Error: unable to find waifu2x-ncnn-vulkan download link or GitHub API rate limit exceeded" - exit 1 -fi - -waifu2x_ncnn_vulkan_zip="$TEMP/waifu2x-ncnn-vulkan-linux.zip" -echo "Downloading $download_link to $waifu2x_ncnn_vulkan_zip" -aria2c "$download_link" --dir / -o "$waifu2x_ncnn_vulkan_zip" -unzip "$waifu2x_ncnn_vulkan_zip" -d $TEMP/waifu2x-ncnn-vulkan -mv -v $TEMP/waifu2x-ncnn-vulkan/waifu2x-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/waifu2x-ncnn-vulkan - -# install srmd-ncnn-vulkan -# download libvulkan1 -apt-fast install -y --no-install-recommends libvulkan1 unzip jq - -# get latest release JSON as a string -echo "Fetching latest srmd-ncnn-vulkan release information using GitHub API" -srmd_ncnn_vulkan_latest_release=$(curl -s https://api.github.com/repos/nihui/srmd-ncnn-vulkan/releases/latest) - -# count the number of assets in this release -assets=$(echo "$srmd_ncnn_vulkan_latest_release" | jq -r '.assets | length') - -# iterate through each of the assets and see if the name of the asset matches what we're looking for -for i in $(seq $assets $END); do - if echo "$srmd_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].name" | egrep "^srmd-ncnn-vulkan-[0-9]*-linux\.zip$"; then - download_link=$(echo "$srmd_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].browser_download_url") - break - fi -done - -# check if download_link variable is set -if [ -z "$download_link" ]; then - echo "$srmd_ncnn_vulkan_latest_release" - echo "Error: unable to find srmd-ncnn-vulkan download link or GitHub API rate limit exceeded" - exit 1 -fi - -srmd_ncnn_vulkan_zip="$TEMP/srmd-ncnn-vulkan-linux.zip" -echo "Downloading $download_link to $srmd_ncnn_vulkan_zip" -aria2c "$download_link" --dir / -o "$srmd_ncnn_vulkan_zip" -unzip "$srmd_ncnn_vulkan_zip" -d $TEMP/srmd-ncnn-vulkan -mv -v $TEMP/srmd-ncnn-vulkan/srmd-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/srmd-ncnn-vulkan - -# install realsr-ncnn-vulkan -# download libvulkan1 -apt-fast install -y --no-install-recommends libvulkan1 unzip jq - -# get latest release JSON as a string -echo "Fetching latest realsr-ncnn-vulkan release information using GitHub API" -realsr_ncnn_vulkan_latest_release=$(curl -s https://api.github.com/repos/nihui/realsr-ncnn-vulkan/releases/latest) - -# count the number of assets in this release -assets=$(echo "$realsr_ncnn_vulkan_latest_release" | jq -r '.assets | length') - -# iterate through each of the assets and see if the name of the asset matches what we're looking for -for i in $(seq $assets $END); do - if echo "$realsr_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].name" | egrep "^realsr-ncnn-vulkan-[0-9]*-linux\.zip$"; then - download_link=$(echo "$realsr_ncnn_vulkan_latest_release" | jq -r ".assets["$(($i - 1))"].browser_download_url") - break - fi -done - -# check if download_link variable is set -if [ -z "$download_link" ]; then - echo "$realsr_ncnn_vulkan_latest_release" - echo "Error: unable to find realsr-ncnn-vulkan download link or GitHub API rate limit exceeded" - exit 1 -fi - -realsr_ncnn_vulkan_zip="$TEMP/realsr-ncnn-vulkan-linux.zip" -echo "Downloading $download_link to $realsr_ncnn_vulkan_zip" -aria2c "$download_link" --dir / -o "$realsr_ncnn_vulkan_zip" -unzip "$realsr_ncnn_vulkan_zip" -d $TEMP/realsr-ncnn-vulkan -mv -v $TEMP/realsr-ncnn-vulkan/realsr-ncnn-vulkan-*-linux $INSTALLATION_PATH/video2x/src/dependencies/realsr-ncnn-vulkan - -# install Anime4KCPP -# install the latest cmake for compiling Anime4KCPP -aria2c https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.sh --dir / -o "$TEMP/cmake.sh" -mkdir $TEMP/cmake -bash "$TEMP/cmake.sh" --prefix=$TEMP/cmake --skip-license - -# build and install Anime4KCPP -apt-fast install -y --no-install-recommends libopencv-dev opencl-dev -git clone --recurse-submodules --depth=1 --progress https://github.com/TianZerL/Anime4KCPP.git $TEMP/anime4kcpp -mkdir -v $TEMP/anime4kcpp/build -cd $TEMP/anime4kcpp/build -$TEMP/cmake/bin/cmake -DBuild_GUI=OFF .. -make -j$(nproc) -mv -v $TEMP/anime4kcpp/build $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp -ln -s $INSTALLATION_PATH/video2x/src/dependencies/anime4kcpp/bin/libAnime4KCPPCore.so /usr/lib - -# rewrite config file values -python3.8 - < 1.0: - raise argparse.ArgumentTypeError(f"{value} is not between 0.0 and 1.0") - return value - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("-i", "--input", type=str, help=argparse.SUPPRESS) # help="File for loading") - parser.add_argument("-o", "--output", type=str, help=argparse.SUPPRESS) # help="File for outputting") - parser.add_argument("-p", "--passes", type=int, help="Passes for processing") - parser.add_argument("-n", "--pushColorCount", type=int, help="Limit the number of color pushes") - parser.add_argument("-c", "--strengthColor", type=WrapperMain.zero_to_one_float, help="Strength for pushing color,range 0 to 1,higher for thinner") - parser.add_argument("-g", "--strengthGradient", type=WrapperMain.zero_to_one_float, help="Strength for pushing gradient,range 0 to 1,higher for sharper") - parser.add_argument("-z", "--zoomFactor", type=float, help="zoom factor for resizing") - parser.add_argument("-t", "--threads", type=int, help="Threads count for video processing") - parser.add_argument("-f", "--fastMode", action="store_true", help="Faster but maybe low quality") - parser.add_argument("-v", "--videoMode", action="store_true", help="Video process") - parser.add_argument("-s", "--preview", action="store_true", help="Preview image") - parser.add_argument("-b", "--preprocessing", action="store_true", help="Enable pre processing") - parser.add_argument("-a", "--postprocessing", action="store_true", help="Enable post processing") - parser.add_argument("-r", "--preFilters", type=int, help="Enhancement filter, only working when preProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D)") - parser.add_argument("-e", "--postFilters", type=int, help="Enhancement filter, only working when postProcessing is true,there are 5 options by binary:Median blur=0000001, Mean blur=0000010, CAS Sharpening=0000100, Gaussian blur weak=0001000, Gaussian blur=0010000, Bilateral filter=0100000, Bilateral filter faster=1000000, you can freely combine them, eg: Gaussian blur weak + Bilateral filter = 0001000 | 0100000 = 0101000 = 40(D), so you can put 40 to enable Gaussian blur weak and Bilateral filter, which also is what I recommend for image that < 1080P, 48 for image that >= 1080P, and for performance I recommend to use 72 for video that < 1080P, 80 for video that >=1080P") - parser.add_argument("-q", "--GPUMode", action="store_true", help="Enable GPU acceleration") - parser.add_argument("-w", "--CNNMode", action="store_true", help="Enable ACNet") - parser.add_argument("-H", "--HDN", action="store_true", help="Enable HDN mode for ACNet") - parser.add_argument("-L", "--HDNLevel", type=int, help="Set HDN level") - parser.add_argument("-l", "--listGPUs", action="store_true", help="list GPUs") - parser.add_argument("-h", "--platformID", type=int, help="Specify the platform ID") - parser.add_argument("-d", "--deviceID", type=int, help="Specify the device ID") - parser.add_argument("-C", "--codec", type=str, help="Specify the codec for encoding from mp4v(recommended in Windows), dxva(for Windows), avc1(H264, recommended in Linux), vp09(very slow), hevc(not support in Windowds), av01(not support in Windowds) (string [=mp4v])") - parser.add_argument("-F", "--forceFps", type=float, help="Set output video fps to the specifying number, 0 to disable") - parser.add_argument("-D", "--disableProgress", action="store_true", help="disable progress display") - parser.add_argument("-W", "--webVideo", type=str, help="process the video from URL") - parser.add_argument("-A", "--alpha", action="store_true", help="preserve the Alpha channel for transparent image") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # self.driver_settings['zoomFactor'] = upscaler.scale_ratio - self.driver_settings["threads"] = upscaler.processes - - # append FFmpeg path to the end of PATH - # Anime4KCPP will then use FFmpeg to migrate audio tracks - os.environ["PATH"] += f';{upscaler.ffmpeg_settings["ffmpeg_path"]}' - - def set_scale_ratio(self, scale_ratio: float): - self.driver_settings["zoomFactor"] = scale_ratio - - def upscale(self, input_file, output_file): - """This is the core function for WAIFU2X class - - Arguments: - input_file {string} -- source directory path - output_file {string} -- output directory path - width {int} -- output video width - height {int} -- output video height - """ - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["input"] = input_file - self.driver_settings["output"] = output_file - - # Anime4KCPP will look for Anime4KCPPKernel.cl under the current working directory - # change the CWD to its containing directory so it will find it - if platform.system() == "Windows": - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # list to be executed - # initialize the list with waifu2x binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/ffmpeg.py b/video2x/wrappers/ffmpeg.py deleted file mode 100755 index b7dab3e..0000000 --- a/video2x/wrappers/ffmpeg.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Video2X FFmpeg Controller -Author: K4YT3X -Date Created: Feb 24, 2018 -Last Modified: January 23, 2021 - -Description: This class handles all FFmpeg related operations. -""" - -# built-in imports -import json -import pathlib -import subprocess -import sys - -# third-party imports -from avalon_framework import Avalon - - -class Ffmpeg: - """This class communicates with FFmpeg - - This class deals with FFmpeg. It handles extracting - frames, stripping audio, converting images into videos - and inserting audio tracks to videos. - """ - - def __init__(self, ffmpeg_settings, extracted_frame_format="png"): - self.ffmpeg_settings = ffmpeg_settings - - self.ffmpeg_path = pathlib.Path(self.ffmpeg_settings["ffmpeg_path"]) - self.ffmpeg_binary = self.ffmpeg_path / "ffmpeg" - self.ffmpeg_probe_binary = self.ffmpeg_path / "ffprobe" - - # video metadata - self.extracted_frame_format = extracted_frame_format - self.intermediate_file_name = pathlib.Path( - self.ffmpeg_settings["intermediate_file_name"] - ) - self.pixel_format = self.ffmpeg_settings["extract_frames"]["output_options"][ - "-pix_fmt" - ] - - def get_pixel_formats(self): - """Get a dictionary of supported pixel formats - - List all supported pixel formats and their - corresponding bit depth. - - Returns: - dictionary -- JSON dict of all pixel formats to bit depth - """ - execute = [self.ffmpeg_probe_binary, "-v", "quiet", "-pix_fmts"] - - # turn elements into str - execute = [str(e) for e in execute] - - Avalon.debug_info(f'Executing: {" ".join(execute)}') - - # initialize dictionary to store pixel formats - pixel_formats = {} - - # record all pixel formats into dictionary - for line in ( - subprocess.run(execute, check=True, stdout=subprocess.PIPE) - .stdout.decode() - .split("\n") - ): - try: - pixel_formats[" ".join(line.split()).split()[1]] = int( - " ".join(line.split()).split()[3] - ) - except (IndexError, ValueError): - pass - - # print pixel formats for debugging - Avalon.debug_info(str(pixel_formats)) - - return pixel_formats - - def get_number_of_frames(self, input_file: str) -> int: - """Count the number of frames in a video - - Args: - input_file (str): input file path - video_stream_index (int): index number of the video stream - - Returns: - int: number of frames in the video - """ - - execute = [ - self.ffmpeg_probe_binary, - "-v", - "quiet", - "-count_frames", - "-select_streams", - "v", - "-show_entries", - "stream=nb_read_frames", - "-of", - "default=nokey=1:noprint_wrappers=1", - input_file, - ] - - # turn elements into str - execute = [str(e) for e in execute] - - Avalon.debug_info(f'Executing: {" ".join(execute)}') - return int( - subprocess.run(execute, check=True, stdout=subprocess.PIPE) - .stdout.decode() - .strip() - .split("\n")[0] - .split("\r")[0] - ) - - def probe_file_info(self, input_video): - """Gets input video information - - This method reads input video information - using ffprobe in dictionary - - Arguments: - input_video {string} -- input video file path - - Returns: - dictionary -- JSON text of input video information - """ - - # this execution command needs to be hard-coded - # since video2x only strictly recignizes this one format - execute = [ - self.ffmpeg_probe_binary, - "-v", - "quiet", - "-print_format", - "json", - "-show_format", - "-show_streams", - "-i", - input_video, - ] - - # turn elements into str - execute = [str(e) for e in execute] - - Avalon.debug_info(f'Executing: {" ".join(execute)}') - json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout - return json.loads(json_str.decode("utf-8")) - - def extract_frames(self, input_file, extracted_frames): - """extract frames from video or GIF file""" - execute = [self.ffmpeg_binary] - - # load general options - execute.extend(self._read_configuration(phase="extract_frames")) - - # load input_options - execute.extend( - self._read_configuration(phase="extract_frames", section="input_options") - ) - - # specify input file - execute.extend(["-i", input_file]) - - # load output options - execute.extend( - self._read_configuration(phase="extract_frames", section="output_options") - ) - - # specify output file - execute.extend( - [ - extracted_frames - / f"extracted_%0d.{self.extracted_frame_format}" - # extracted_frames / f'frame_%06d.{self.extracted_frame_format}' - ] - ) - - return self._execute(execute) - - def assemble_video(self, framerate, upscaled_frames): - """Converts images into videos - - This method converts a set of images into a video - - Arguments: - framerate {float} -- target video framerate - resolution {string} -- target video resolution - upscaled_frames {string} -- source images directory - """ - execute = [ - self.ffmpeg_binary, - "-r", - str(framerate) - # '-s', - # resolution - ] - - # read other options - execute.extend(self._read_configuration(phase="assemble_video")) - - # read input options - execute.extend( - self._read_configuration(phase="assemble_video", section="input_options") - ) - - # WORKAROUND FOR WAIFU2X-NCNN-VULKAN - # Dev: SAT3LL - # rename all .png.png suffixes to .png - import re - - regex = re.compile(r"\.png\.png$", re.IGNORECASE) - for frame_name in upscaled_frames.iterdir(): - (upscaled_frames / frame_name).rename( - upscaled_frames / regex.sub(".png", str(frame_name)) - ) - # END WORKAROUND - - # append input frames path into command - execute.extend( - [ - "-i", - upscaled_frames / f"extracted_%d.{self.extracted_frame_format}" - # upscaled_frames / f'%06d.{self.extracted_frame_format}' - ] - ) - - # read FFmpeg output options - execute.extend( - self._read_configuration(phase="assemble_video", section="output_options") - ) - - # specify output file location - execute.extend([upscaled_frames / self.intermediate_file_name]) - - return self._execute(execute) - - def migrate_streams(self, input_video, output_video, upscaled_frames): - """Migrates audio tracks and subtitles from input video to output video - - Arguments: - input_video {string} -- input video file path - output_video {string} -- output video file path - upscaled_frames {string} -- directory containing upscaled frames - """ - execute = [self.ffmpeg_binary] - - # load general options - execute.extend(self._read_configuration(phase="migrate_streams")) - - # load input options - execute.extend( - self._read_configuration(phase="migrate_streams", section="input_options") - ) - - # load input file names - execute.extend( - [ - # input 1: upscaled intermediate file without sound - "-i", - upscaled_frames / self.intermediate_file_name, - # input 2: original video with streams to copy over - "-i", - input_video, - ] - ) - - # load output options - execute.extend( - self._read_configuration(phase="migrate_streams", section="output_options") - ) - - # load output video path - execute.extend([output_video]) - - return 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 each key in the section's dict - for key in source: - - if section: - value = self.ffmpeg_settings[phase][section][key] - else: - value = self.ffmpeg_settings[phase][key] - - # null or None means that leave this option out (keep default) - if ( - value is None - or value is False - or isinstance(value, dict) - or value == "" - ): - continue - - # if the value is a list, append the same argument and all values - elif isinstance(value, list): - - for subvalue in value: - configuration.append(key) - if value is not True: - configuration.append(str(subvalue)) - - # otherwise the value is typical - else: - configuration.append(key) - - # true means key is an option - if value is True: - continue - - configuration.append(str(value)) - - return configuration - - def _execute(self, execute): - # turn all list elements into string to avoid errors - execute = [str(e) for e in execute] - Avalon.debug_info(f'Executing: {" ".join(execute)}') - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/realsr_ncnn_vulkan.py b/video2x/wrappers/realsr_ncnn_vulkan.py deleted file mode 100755 index d75b9be..0000000 --- a/video2x/wrappers/realsr_ncnn_vulkan.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: RealSR ncnn Vulkan Driver -Creator: K4YT3X -Date Created: May 26, 2020 -Last Modified: September 21, 2020 - -Description: This class is a high-level wrapper -for realsr_ncnn_vulkan. -""" - -# built-in imports -import argparse -import os -import pathlib -import platform -import subprocess -import sys -import threading - -# third-party imports -from avalon_framework import Avalon - - -class WrapperMain: - """This class communicates with RealSR ncnn Vulkan engine - - An object will be created for this class, containing information - about the binary address and the processing method. When being called - by the main program, other detailed information will be passed to - the upscale function. - """ - - def __init__(self, driver_settings): - self.driver_settings = driver_settings - self.print_lock = threading.Lock() - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("-v", action="store_true", help="verbose output") - parser.add_argument("-i", type=str, help=argparse.SUPPRESS) # help="input image path (jpg/png) or directory") - parser.add_argument("-o", type=str, help=argparse.SUPPRESS) # help="output image path (png) or directory") - parser.add_argument("-s", type=int, help="upscale ratio") - parser.add_argument("-t", type=int, help="tile size (>=32/0=auto)") - parser.add_argument("-m", type=str, help="realsr model path") - parser.add_argument("-g", type=int, help="gpu device to use") - parser.add_argument("-j", type=str, help="thread count for load/proc/save") - parser.add_argument("-x", action="store_true", help="enable tta mode") - parser.add_argument("-f", type=str, help=argparse.SUPPRESS) # help="output image format (jpg/png/webp, default=ext/png)") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # self.driver_settings['s'] = int(upscaler.scale_ratio) - self.driver_settings["j"] = "{}:{}:{}".format( - upscaler.processes, upscaler.processes, upscaler.processes - ) - self.driver_settings["f"] = upscaler.extracted_frame_format.lower() - - def set_scale_ratio(self, scale_ratio: int): - self.driver_settings["s"] = int(scale_ratio) - - def upscale(self, input_directory, output_directory): - """This is the core function for RealSR ncnn Vulkan class - - Arguments: - input_directory {string} -- source directory path - output_directory {string} -- output directory path - ratio {int} -- output video ratio - """ - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["i"] = input_directory - self.driver_settings["o"] = output_directory - - # by default, realsr-ncnn-vulkan will look for the models under the current working directory - # change the working directory to its containing folder if model directory not specified - if self.driver_settings["m"] is None and platform.system() == "Windows": - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # list to be executed - # initialize the list with the binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/srmd_ncnn_vulkan.py b/video2x/wrappers/srmd_ncnn_vulkan.py deleted file mode 100755 index db6dfc2..0000000 --- a/video2x/wrappers/srmd_ncnn_vulkan.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: SRMD ncnn Vulkan Driver -Creator: K4YT3X -Date Created: April 26, 2020 -Last Modified: September 21, 2020 - -Description: This class is a high-level wrapper -for srmd_ncnn_vulkan. -""" - -# built-in imports -import argparse -import os -import pathlib -import platform -import subprocess -import sys -import threading - -# third-party imports -from avalon_framework import Avalon - - -class WrapperMain: - """This class communicates with SRMD ncnn Vulkan engine - - An object will be created for this class, containing information - about the binary address and the processing method. When being called - by the main program, other detailed information will be passed to - the upscale function. - """ - - def __init__(self, driver_settings): - self.driver_settings = driver_settings - self.print_lock = threading.Lock() - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("-v", action="store_true", help="verbose output") - parser.add_argument("-i", type=str, help=argparse.SUPPRESS) # help="input image path (jpg/png) or directory") - parser.add_argument("-o", type=str, help=argparse.SUPPRESS) # help="output image path (png) or directory") - parser.add_argument("-n", type=int, choices=range(-1, 11), help="denoise level") - parser.add_argument("-s", type=int, help="upscale ratio") - parser.add_argument("-t", type=int, help="tile size (>=32)") - parser.add_argument("-m", type=str, help="srmd model path") - parser.add_argument("-g", type=int, help="gpu device to use") - parser.add_argument("-j", type=str, help="thread count for load/proc/save") - parser.add_argument("-x", action="store_true", help="enable tta mode") - parser.add_argument("-f", type=str, help=argparse.SUPPRESS) # help="output image format (jpg/png/webp, default=ext/png)") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # self.driver_settings['s'] = int(upscaler.scale_ratio) - self.driver_settings["j"] = "{}:{}:{}".format( - upscaler.processes, upscaler.processes, upscaler.processes - ) - self.driver_settings["f"] = upscaler.extracted_frame_format.lower() - - def set_scale_ratio(self, scale_ratio: int): - self.driver_settings["s"] = int(scale_ratio) - - def upscale(self, input_directory, output_directory): - """This is the core function for SRMD ncnn Vulkan class - - Arguments: - input_directory {string} -- source directory path - output_directory {string} -- output directory path - ratio {int} -- output video ratio - """ - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["i"] = input_directory - self.driver_settings["o"] = output_directory - - # by default, srmd-ncnn-vulkan will look for the models under the current working directory - # change the working directory to its containing folder if model directory not specified - if self.driver_settings["m"] is None and platform.system() == "Windows": - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # list to be executed - # initialize the list with the binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/waifu2x_caffe.py b/video2x/wrappers/waifu2x_caffe.py deleted file mode 100755 index 66e1277..0000000 --- a/video2x/wrappers/waifu2x_caffe.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Waifu2x Caffe Driver -Author: K4YT3X -Date Created: Feb 24, 2018 -Last Modified: September 12, 2020 - -Description: This class is a high-level wrapper -for waifu2x-caffe. -""" - -# built-in imports -import argparse -import os -import pathlib -import subprocess -import sys -import threading - -# third-party imports -from avalon_framework import Avalon - - -class WrapperMain: - """This class communicates with waifu2x cui engine - - An object will be created for this class, containing information - about the binary address and the processing method. When being called - by the main program, other detailed information will be passed to - the upscale function. - """ - - def __init__(self, driver_settings): - self.driver_settings = driver_settings - self.print_lock = threading.Lock() - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("-t", "--tta", type=int, choices=range(2), help="8x slower and slightly high quality") - parser.add_argument("--gpu", type=int, help="gpu device no") - parser.add_argument("-b", "--batch_size", type=int, help="input batch size") - parser.add_argument("--crop_h", type=int, help="input image split size(height)") - parser.add_argument("--crop_w", type=int, help="input image split size(width)") - parser.add_argument("-c", "--crop_size", type=int, help="input image split size") - parser.add_argument("-d", "--output_depth", type=int, help="output image chaneel depth bit") - parser.add_argument("-q", "--output_quality", type=int, help="output image quality") - parser.add_argument("-p", "--process", choices=["cpu", "gpu", "cudnn"], help="process mode") - parser.add_argument("--model_dir", type=str, help="path to custom model directory (don\"t append last / )") - parser.add_argument("-h", "--scale_height", type=int, help="custom scale height") - parser.add_argument("-w", "--scale_width", type=int, help="custom scale width") - parser.add_argument("-s", "--scale_ratio", type=float, help="custom scale ratio") - parser.add_argument("-n", "--noise_level", type=int, choices=range(4), help="noise reduction level") - parser.add_argument("-m", "--mode", choices=["noise", "scale", "noise_scale", "auto_scale"], help="image processing mode") - parser.add_argument("-e", "--output_extention", type=str, help="extention to output image file when output_path is (auto) or input_path is folder") - parser.add_argument("-l", "--input_extention_list", type=str, help="extention to input image file when input_path is folder") - parser.add_argument("-o", "--output_path", type=str, help=argparse.SUPPRESS) # help="path to output image file (when input_path is folder, output_path must be folder)") - parser.add_argument("-i", "--input_path", type=str, help=argparse.SUPPRESS) # help="(required) path to input image file") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # use scale width and scale height if specified - # self.driver_settings['scale_ratio'] = upscaler.scale_ratio - self.driver_settings["output_extention"] = upscaler.extracted_frame_format - - # bit_depth will be 12 at this point - # it will up updated later - self.driver_settings["output_depth"] = 12 - - def set_scale_resolution(self, width: int, height: int): - self.driver_settings["scale_width"] = width - self.driver_settings["scale_height"] = height - self.driver_settings["scale_ratio"] = None - - def set_scale_ratio(self, scale_ratio: float): - self.driver_settings["scale_width"] = None - self.driver_settings["scale_height"] = None - self.driver_settings["scale_ratio"] = scale_ratio - - def upscale(self, input_directory, output_directory): - """start upscaling process""" - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["input_path"] = input_directory - self.driver_settings["output_path"] = output_directory - - # list to be executed - # initialize the list with waifu2x binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/waifu2x_converter_cpp.py b/video2x/wrappers/waifu2x_converter_cpp.py deleted file mode 100755 index 6149d49..0000000 --- a/video2x/wrappers/waifu2x_converter_cpp.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Waifu2x Converter CPP Driver -Author: K4YT3X -Date Created: February 8, 2019 -Last Modified: September 9, 2020 - -Description: This class is a high-level wrapper -for waifu2x-converter-cpp. -""" - -# built-in imports -import argparse -import os -import pathlib -import subprocess -import sys -import threading - -# third-party imports -from avalon_framework import Avalon - - -class WrapperMain: - """This class communicates with waifu2x cui engine - - An object will be created for this class, containing information - about the binary address and the processing method. When being called - by the main program, other detailed information will be passed to - the upscale function. - """ - - def __init__(self, driver_settings): - self.driver_settings = driver_settings - self.print_lock = threading.Lock() - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("--list-supported-formats", action="store_true", help="dump currently supported format list") - parser.add_argument("--list-opencv-formats", action="store_true", help="(deprecated. Use --list-supported-formats) dump opencv supported format list") - parser.add_argument("-l", "--list-processor", action="store_true", help="dump processor list") - parser.add_argument("-f", "--output-format", choices=["png", "jpg"], help="The format used when running in recursive/folder mode\nSee --list-supported-formats for a list of supported formats/extensions.") - parser.add_argument("-c", "--png-compression", type=int, choices=range(10), help="Set PNG compression level (0-9), 9 = Max compression (slowest & smallest)") - parser.add_argument("-q", "--image-quality", type=int, choices=range(-1, 102), help="JPEG & WebP Compression quality (0-101, 0 being smallest size and lowest quality), use 101 for lossless WebP") - parser.add_argument("--block-size", type=int, help="block size") - parser.add_argument("--disable-gpu", action="store_true", help="disable GPU") - parser.add_argument("--force-OpenCL", action="store_true", help="force to use OpenCL on Intel Platform") - parser.add_argument("-p", "--processor", type=int, help="set target processor") - parser.add_argument("-j", "--jobs", type=int, help="number of threads launching at the same time") - parser.add_argument("--model-dir", type=str, help="path to custom model directory (don\"t append last / )") - parser.add_argument("--scale-ratio", type=float, help="custom scale ratio") - parser.add_argument("--noise-level", type=int, choices=range(4), help="noise reduction level") - parser.add_argument("-m", "--mode", choices=["noise", "scale", "noise-scale"], help="image processing mode") - parser.add_argument("-v", "--log-level", type=int, choices=range(5), help="Set log level") - parser.add_argument("-s", "--silent", action="store_true", help="Enable silent mode. (same as --log-level 1)") - parser.add_argument("-t", "--tta", type=int, choices=range(2), help="Enable Test-Time Augmentation mode.") - parser.add_argument("-g", "--generate-subdir", type=int, choices=range(2), help="Generate sub folder when recursive directory is enabled.") - parser.add_argument("-a", "--auto-naming", type=int, choices=range(2), help="Add postfix to output name when output path is not specified.\nSet 0 to disable this.") - parser.add_argument("-r", "--recursive-directory", type=int, choices=range(2), help="Search recursively through directories to find more images to process.") - parser.add_argument("-o", "--output", type=str, help=argparse.SUPPRESS) # help="path to output image file or directory (you should use the full path)") - parser.add_argument("-i", "--input", type=str, help=argparse.SUPPRESS) # help="(required) path to input image file or directory (you should use the full path)") - parser.add_argument("--version", action="store_true", help="Displays version information and exits.") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # self.driver_settings['scale-ratio'] = upscaler.scale_ratio - self.driver_settings["jobs"] = upscaler.processes - self.driver_settings["output-format"] = upscaler.extracted_frame_format.lower() - - def set_scale_ratio(self, scale_ratio: float): - self.driver_settings["scale-ratio"] = scale_ratio - - def upscale(self, input_directory, output_directory): - """Waifu2x Converter Driver Upscaler - This method executes the upscaling of extracted frames. - - Arguments: - input_directory {string} -- source directory path - output_directory {string} -- output directory path - scale_ratio {int} -- frames' scale ratio - threads {int} -- number of threads - """ - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["input"] = input_directory - self.driver_settings["output"] = output_directory - - # models_rgb must be specified manually for waifu2x-converter-cpp - # if it's not specified in the arguments, create automatically - if self.driver_settings["model-dir"] is None: - self.driver_settings["model-dir"] = ( - pathlib.Path(self.driver_settings["path"]).parent / "models_rgb" - ) - - # list to be executed - # initialize the list with waifu2x binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) diff --git a/video2x/wrappers/waifu2x_ncnn_vulkan.py b/video2x/wrappers/waifu2x_ncnn_vulkan.py deleted file mode 100755 index bf087e1..0000000 --- a/video2x/wrappers/waifu2x_ncnn_vulkan.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Name: Waifu2x ncnn Vulkan Driver -Creator: SAT3LL -Date Created: June 26, 2019 -Last Modified: May 11, 2020 - -Editor: K4YT3X -Last Modified: September 21, 2020 - -Description: This class is a high-level wrapper -for waifu2x_ncnn_vulkan. -""" - -# built-in imports -import argparse -import os -import pathlib -import platform -import subprocess -import sys -import threading - -# third-party imports -from avalon_framework import Avalon - - -class WrapperMain: - """This class communicates with waifu2x ncnn vulkan engine - - An object will be created for this class, containing information - about the binary address and the processing method. When being called - by the main program, other detailed information will be passed to - the upscale function. - """ - - def __init__(self, driver_settings): - self.driver_settings = driver_settings - self.print_lock = threading.Lock() - - @staticmethod - def parse_arguments(arguments): - # fmt: off - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) - parser.error = lambda message: (_ for _ in ()).throw(AttributeError(message)) - parser.add_argument("--help", action="help", help="show this help message and exit") - parser.add_argument("-v", action="store_true", help="verbose output") - parser.add_argument("-i", type=str, help=argparse.SUPPRESS) # help="input image path (jpg/png/webp) or directory") - parser.add_argument("-o", type=str, help=argparse.SUPPRESS) # help="output image path (jpg/png/webp) or directory") - parser.add_argument("-n", type=int, choices=range(-1, 4), help="denoise level") - parser.add_argument("-s", type=int, help="upscale ratio") - parser.add_argument("-t", type=int, help="tile size (>=32)") - parser.add_argument("-m", type=str, help="waifu2x model path") - parser.add_argument("-g", type=int, help="gpu device to use") - parser.add_argument("-j", type=str, help="thread count for load/proc/save") - parser.add_argument("-x", action="store_true", help="enable tta mode") - parser.add_argument("-f", type=str, help=argparse.SUPPRESS) # help="output image format (jpg/png/webp, default=ext/png)") - return parser.parse_args(arguments) - # fmt: on - - def load_configurations(self, upscaler): - # self.driver_settings['s'] = int(upscaler.scale_ratio) - self.driver_settings["j"] = "{}:{}:{}".format( - upscaler.processes, upscaler.processes, upscaler.processes - ) - self.driver_settings["f"] = upscaler.extracted_frame_format.lower() - - def set_scale_ratio(self, scale_ratio: int): - self.driver_settings["s"] = int(scale_ratio) - - def upscale(self, input_directory, output_directory): - """This is the core function for waifu2x class - - Arguments: - input_directory {string} -- source directory path - output_directory {string} -- output directory path - ratio {int} -- output video ratio - """ - - # change the working directory to the binary's parent directory - # so the binary can find shared object files and other files - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # overwrite config file settings - self.driver_settings["i"] = input_directory - self.driver_settings["o"] = output_directory - - # by default, waifu2x-ncnn-vulkan will look for the models under the current working directory - # change the working directory to its containing folder if model directory not specified - if self.driver_settings["m"] is None and platform.system() == "Windows": - os.chdir(pathlib.Path(self.driver_settings["path"]).parent) - - # list to be executed - # initialize the list with waifu2x binary path as the first element - execute = [self.driver_settings["path"]] - - for key in self.driver_settings.keys(): - - value = self.driver_settings[key] - - # null or None means that leave this option out (keep default) - if key == "path" or value is None or value is False: - continue - else: - if len(key) == 1: - execute.append(f"-{key}") - else: - execute.append(f"--{key}") - - # true means key is an option - if value is not True: - execute.append(str(value)) - - # return the Popen object of the new process created - self.print_lock.acquire() - Avalon.debug_info( - f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}' - ) - self.print_lock.release() - return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr)