mirror of
				https://github.com/k4yt3x/video2x.git
				synced 2025-10-31 04:40:59 +01:00 
			
		
		
		
	better exception handling, soft task interruption, GUI stop button, GUI folder processing, better argument checks
This commit is contained in:
		
							parent
							
								
									134e8b7080
								
							
						
					
					
						commit
						e9c1c22788
					
				| @ -5,8 +5,8 @@ | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: \n" | ||||
| "POT-Creation-Date: 2020-05-04 19:14-0400\n" | ||||
| "PO-Revision-Date: 2020-05-04 19:16-0400\n" | ||||
| "POT-Creation-Date: 2020-05-07 15:54-0400\n" | ||||
| "PO-Revision-Date: 2020-05-07 15:55-0400\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: \n" | ||||
| "Language: zh_CN\n" | ||||
| @ -17,107 +17,155 @@ msgstr "" | ||||
| "X-Generator: Poedit 2.3\n" | ||||
| "Plural-Forms: nplurals=1; plural=0;\n" | ||||
| 
 | ||||
| #: upscaler.py:85 | ||||
| msgid "Extracted frames are being saved to: {}" | ||||
| msgstr "提取的帧将被保存到:{}" | ||||
| 
 | ||||
| #: upscaler.py:87 | ||||
| msgid "Upscaled frames are being saved to: {}" | ||||
| msgstr "已放大的帧将被保存到:{}" | ||||
| 
 | ||||
| #: upscaler.py:97 | ||||
| msgid "Cleaning up cache directory: {}" | ||||
| msgstr "清理缓存目录:{}" | ||||
| 
 | ||||
| #: upscaler.py:100 | ||||
| msgid "Unable to delete: {}" | ||||
| msgstr "无法删除:{}" | ||||
| 
 | ||||
| #: upscaler.py:107 | ||||
| msgid "You must specify input video file/directory path" | ||||
| msgstr "您必须指定输入视频文件/目录路径" | ||||
| 
 | ||||
| #: upscaler.py:110 | ||||
| msgid "You must specify output video file/directory path" | ||||
| msgstr "您必须指定输出视频文件/目录路径" | ||||
| 
 | ||||
| #: upscaler.py:113 | ||||
| msgid "Selected driver accepts only scaling ratio" | ||||
| msgstr "所选驱动程序仅接受缩放比率" | ||||
| 
 | ||||
| #: upscaler.py:116 | ||||
| msgid "Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan" | ||||
| msgstr "waifu2x_ncnn_vulkan 的缩放比必须为 1 或 2" | ||||
| 
 | ||||
| #: upscaler.py:119 | ||||
| msgid "Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan" | ||||
| msgstr "srmd_ncnn_vulkan 的缩放比必须为 2、3 或 4" | ||||
| 
 | ||||
| #: upscaler.py:122 | ||||
| msgid "You can only specify either scaling ratio or output width and height" | ||||
| msgstr "您只能指定缩放比或输出宽度和高度两者之一" | ||||
| 
 | ||||
| #: upscaler.py:125 | ||||
| msgid "You must specify both width and height" | ||||
| msgstr "您必须同时指定宽度和高度" | ||||
| 
 | ||||
| #: upscaler.py:142 | ||||
| #: progress_monitor.py:42 | ||||
| msgid "Upscaling Progress" | ||||
| msgstr "放大进度" | ||||
| 
 | ||||
| #: upscaler.py:179 | ||||
| #: upscaler.py:104 | ||||
| msgid "Specified or default cache directory is a file/link" | ||||
| msgstr "指定或默认的缓存目录是文件/链接" | ||||
| 
 | ||||
| #: upscaler.py:110 | ||||
| msgid "Creating cache directory {}" | ||||
| msgstr "创建缓存目录 {}" | ||||
| 
 | ||||
| #: upscaler.py:113 | ||||
| msgid "Unable to create {}" | ||||
| msgstr "无法创建 {}" | ||||
| 
 | ||||
| #: upscaler.py:118 | ||||
| msgid "Extracted frames are being saved to: {}" | ||||
| msgstr "提取的帧将被保存到:{}" | ||||
| 
 | ||||
| #: upscaler.py:120 | ||||
| msgid "Upscaled frames are being saved to: {}" | ||||
| msgstr "已放大的帧将被保存到:{}" | ||||
| 
 | ||||
| #: upscaler.py:130 | ||||
| msgid "Cleaning up cache directory: {}" | ||||
| msgstr "清理缓存目录:{}" | ||||
| 
 | ||||
| #: upscaler.py:133 | ||||
| msgid "Unable to delete: {}" | ||||
| msgstr "无法删除:{}" | ||||
| 
 | ||||
| #: upscaler.py:140 upscaler.py:151 | ||||
| msgid "Input and output path type mismatch" | ||||
| msgstr "输入和输出路径类型不匹配" | ||||
| 
 | ||||
| #: upscaler.py:141 | ||||
| msgid "Input is single file but output is directory" | ||||
| msgstr "所选的输入路径是单个文件,但输出路径是目录" | ||||
| 
 | ||||
| #: upscaler.py:144 | ||||
| msgid "No suffix found in output file path" | ||||
| msgstr "在输出文件路径中未找到后缀" | ||||
| 
 | ||||
| #: upscaler.py:145 | ||||
| msgid "Suffix must be specified for FFmpeg" | ||||
| msgstr "必须为 FFmpeg 指定后缀" | ||||
| 
 | ||||
| #: upscaler.py:152 | ||||
| msgid "Input is directory but output is existing single file" | ||||
| msgstr "输入是目录,但输出是现有的单个文件" | ||||
| 
 | ||||
| #: upscaler.py:157 | ||||
| msgid "Input path is neither a file nor a directory" | ||||
| msgstr "输入路径既不是文件也不是目录" | ||||
| 
 | ||||
| #: upscaler.py:166 | ||||
| msgid "FFmpeg or FFprobe cannot be found under the specified path" | ||||
| msgstr "在指定的路径下找不到 FFmpeg 或 FFprobe" | ||||
| 
 | ||||
| #: upscaler.py:167 upscaler.py:177 | ||||
| msgid "Please check the configuration file settings" | ||||
| msgstr "请检查配置文件设置" | ||||
| 
 | ||||
| #: upscaler.py:176 | ||||
| msgid "Specified driver executable directory doesn't exist" | ||||
| msgstr "指定驱动的可执行文件不存在" | ||||
| 
 | ||||
| #: upscaler.py:203 | ||||
| msgid "Failed to parse driver argument: {}" | ||||
| msgstr "解析驱动程序参数失败:{}" | ||||
| 
 | ||||
| #: upscaler.py:218 | ||||
| msgid "Unrecognized driver: {}" | ||||
| msgstr "无法识别的驱动名称:{}" | ||||
| 
 | ||||
| #: upscaler.py:258 | ||||
| #: upscaler.py:290 | ||||
| msgid "Starting progress monitor" | ||||
| msgstr "启动进度监视器" | ||||
| 
 | ||||
| #: upscaler.py:295 | ||||
| msgid "Starting upscaled image cleaner" | ||||
| msgstr "启动已放大图像清理程序" | ||||
| 
 | ||||
| #: upscaler.py:264 | ||||
| msgid "Main process waiting for subprocesses to exit" | ||||
| msgstr "主进程开始等待子进程结束" | ||||
| #: upscaler.py:304 upscaler.py:321 | ||||
| msgid "Killing progress monitor" | ||||
| msgstr "终结进度监视器" | ||||
| 
 | ||||
| #: upscaler.py:266 | ||||
| msgid "Subprocess {} exited with code {}" | ||||
| msgstr "子进程 {} 结束,返回码 {}" | ||||
| 
 | ||||
| #: upscaler.py:274 upscaler.py:287 | ||||
| #: upscaler.py:307 upscaler.py:324 | ||||
| msgid "Killing upscaled image cleaner" | ||||
| msgstr "终结已放大图像清理程序" | ||||
| 
 | ||||
| #: upscaler.py:313 upscaler.py:368 | ||||
| #: upscaler.py:328 | ||||
| msgid "Terminating all processes" | ||||
| msgstr "正在终止所有进程" | ||||
| 
 | ||||
| #: upscaler.py:335 | ||||
| msgid "Main process waiting for subprocesses to exit" | ||||
| msgstr "主进程开始等待子进程结束" | ||||
| 
 | ||||
| #: upscaler.py:354 upscaler.py:358 | ||||
| msgid "Subprocess {} exited with code {}" | ||||
| msgstr "子进程 {} 结束,返回码 {}" | ||||
| 
 | ||||
| #: upscaler.py:364 | ||||
| msgid "Stop signal received" | ||||
| msgstr "收到停止信号" | ||||
| 
 | ||||
| #: upscaler.py:369 | ||||
| msgid "Subprocess execution ran into an error" | ||||
| msgstr "子进程执行遇到错误" | ||||
| 
 | ||||
| #: upscaler.py:395 | ||||
| msgid "Upscaling single video file: {}" | ||||
| msgstr "放大单个视频文件:{}" | ||||
| 
 | ||||
| #: upscaler.py:414 upscaler.py:477 | ||||
| msgid "Starting to upscale extracted images" | ||||
| msgstr "开始对提取的帧进行放大" | ||||
| 
 | ||||
| #: upscaler.py:316 upscaler.py:370 | ||||
| #: upscaler.py:423 upscaler.py:479 | ||||
| msgid "Upscaling completed" | ||||
| msgstr "放大完成" | ||||
| 
 | ||||
| #: upscaler.py:324 | ||||
| #: upscaler.py:432 | ||||
| msgid "Reading video information" | ||||
| msgstr "读取视频信息" | ||||
| 
 | ||||
| #: upscaler.py:338 | ||||
| #: upscaler.py:446 | ||||
| msgid "Aborting: No video stream found" | ||||
| msgstr "程序中止:文件中未找到视频流" | ||||
| 
 | ||||
| #: upscaler.py:355 | ||||
| #: upscaler.py:464 | ||||
| msgid "Unsupported pixel format: {}" | ||||
| msgstr "不支持的像素格式:{}" | ||||
| 
 | ||||
| #: upscaler.py:358 | ||||
| #: upscaler.py:467 | ||||
| msgid "Framerate: {}" | ||||
| msgstr "帧率:{}" | ||||
| 
 | ||||
| #: upscaler.py:373 | ||||
| #: upscaler.py:482 | ||||
| msgid "Converting extracted frames into video" | ||||
| msgstr "将提取的帧转换为视频" | ||||
| 
 | ||||
| #: upscaler.py:377 | ||||
| #: upscaler.py:487 | ||||
| msgid "Conversion completed" | ||||
| msgstr "转换已完成" | ||||
| 
 | ||||
| #: upscaler.py:380 | ||||
| #: upscaler.py:490 | ||||
| msgid "Migrating audio tracks and subtitles to upscaled video" | ||||
| msgstr "将音轨和字幕迁移到放大后的视频" | ||||
| 
 | ||||
| @ -187,58 +235,34 @@ msgstr "缩放比" | ||||
| msgid "This file cannot be imported" | ||||
| msgstr "此文件无法被当作模块导入" | ||||
| 
 | ||||
| #: video2x.py:193 | ||||
| msgid "Specified driver executable directory doesn't exist" | ||||
| msgstr "指定驱动的可执行文件不存在" | ||||
| 
 | ||||
| #: video2x.py:194 | ||||
| msgid "Please check the configuration file settings" | ||||
| msgstr "请检查配置文件设置" | ||||
| 
 | ||||
| #: video2x.py:211 | ||||
| msgid "Specified cache directory is a file/link" | ||||
| msgstr "指定的缓存目录是文件/链接" | ||||
| 
 | ||||
| #: video2x.py:218 | ||||
| msgid "Creating cache directory {}" | ||||
| msgstr "创建缓存目录 {}" | ||||
| 
 | ||||
| #: video2x.py:224 | ||||
| msgid "Unable to create {}" | ||||
| msgstr "无法创建 {}" | ||||
| 
 | ||||
| #: video2x.py:237 | ||||
| msgid "Upscaling single video file: {}" | ||||
| msgstr "放大单个视频文件:{}" | ||||
| 
 | ||||
| #: video2x.py:241 | ||||
| msgid "Input and output path type mismatch" | ||||
| msgstr "输入和输出路径类型不匹配" | ||||
| 
 | ||||
| #: video2x.py:242 | ||||
| msgid "Input is single file but output is directory" | ||||
| msgstr "所选的输入路径是单个文件,但输出路径是目录" | ||||
| 
 | ||||
| #: video2x.py:245 | ||||
| msgid "No suffix found in output file path" | ||||
| msgstr "在输出文件路径中未找到后缀" | ||||
| 
 | ||||
| #: video2x.py:246 | ||||
| msgid "Suffix must be specified for FFmpeg" | ||||
| msgstr "必须为 FFmpeg 指定后缀" | ||||
| 
 | ||||
| #: video2x.py:270 | ||||
| msgid "Upscaling videos in directory: {}" | ||||
| msgstr "放大该文件夹中的所有视频:{}" | ||||
| 
 | ||||
| #: video2x.py:295 | ||||
| msgid "Input path is neither a file nor a directory" | ||||
| msgstr "输入路径既不是文件也不是目录" | ||||
| 
 | ||||
| #: video2x.py:298 | ||||
| msgid "Program completed, taking {} seconds" | ||||
| msgstr "程序执行完毕,总计花费 {} 秒" | ||||
| 
 | ||||
| #: video2x.py:301 | ||||
| #: video2x.py:227 | ||||
| msgid "An exception has occurred" | ||||
| msgstr "发生了异常" | ||||
| 
 | ||||
| #~ 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 can only specify either scaling ratio or output width and height" | ||||
| #~ msgstr "您只能指定缩放比或输出宽度和高度两者之一" | ||||
| 
 | ||||
| #~ msgid "You must specify both width and height" | ||||
| #~ msgstr "您必须同时指定宽度和高度" | ||||
| 
 | ||||
| #~ msgid "Upscaling videos in directory: {}" | ||||
| #~ msgstr "放大该文件夹中的所有视频:{}" | ||||
|  | ||||
							
								
								
									
										61
									
								
								src/progress_monitor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/progress_monitor.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
| Name: Video2X Upscale Progress Monitor | ||||
| Author: BrianPetkovsek | ||||
| Date Created: May 7, 2020 | ||||
| Last Modified: May 7, 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 | ||||
|          | ||||
|         # get number of extracted frames | ||||
|         self.upscaler.total_frames = 0 | ||||
|         for directory in self.extracted_frames_directories: | ||||
|             self.upscaler.total_frames += len([f for f in directory.iterdir() if str(f).lower().endswith(self.upscaler.image_format.lower())]) | ||||
| 
 | ||||
|         with tqdm(total=self.upscaler.total_frames, ascii=True, desc=_('Upscaling Progress')) 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): | ||||
|                     self.upscaler.total_frames_upscaled = len([f for f in self.upscaler.upscaled_frames.iterdir() if str(f).lower().endswith(self.upscaler.image_format.lower())]) | ||||
| 
 | ||||
|                     # 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() | ||||
							
								
								
									
										413
									
								
								src/upscaler.py
									
									
									
									
									
								
							
							
						
						
									
										413
									
								
								src/upscaler.py
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ | ||||
| Name: Video2X Upscaler | ||||
| Author: K4YT3X | ||||
| Date Created: December 10, 2018 | ||||
| Last Modified: May 6, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Description: This file contains the Upscaler class. Each | ||||
| instance of the Upscaler class is an upscaler on an image or | ||||
| @ -14,6 +14,7 @@ a folder. | ||||
| # local imports | ||||
| from exceptions import * | ||||
| from image_cleaner import ImageCleaner | ||||
| from progress_monitor import ProgressMonitor | ||||
| from wrappers.ffmpeg import Ffmpeg | ||||
| 
 | ||||
| # built-in imports | ||||
| @ -25,8 +26,10 @@ import importlib | ||||
| import locale | ||||
| import os | ||||
| import pathlib | ||||
| import queue | ||||
| import re | ||||
| import shutil | ||||
| import subprocess | ||||
| import sys | ||||
| import tempfile | ||||
| import threading | ||||
| @ -35,7 +38,6 @@ import traceback | ||||
| 
 | ||||
| # third-party imports | ||||
| from avalon_framework import Avalon | ||||
| from tqdm import tqdm | ||||
| 
 | ||||
| # internationalization constants | ||||
| DOMAIN = 'video2x' | ||||
| @ -67,10 +69,10 @@ class Upscaler: | ||||
|         ArgumentError -- if argument is not valid | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, input_video, output_video, driver_settings, ffmpeg_settings): | ||||
|     def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings): | ||||
|         # mandatory arguments | ||||
|         self.input_video = input_video | ||||
|         self.output_video = output_video | ||||
|         self.input_path = input_path | ||||
|         self.output_path = output_path | ||||
|         self.driver_settings = driver_settings | ||||
|         self.ffmpeg_settings = ffmpeg_settings | ||||
| 
 | ||||
| @ -84,14 +86,33 @@ class Upscaler: | ||||
|         self.image_format = 'png' | ||||
|         self.preserve_frames = False | ||||
| 
 | ||||
|         # other internal members and signals | ||||
|         self.stop_signal = False | ||||
|         self.total_frames_upscaled = 0 | ||||
|         self.total_frames = 0 | ||||
| 
 | ||||
|     def create_temp_directories(self): | ||||
|         """create temporary directory | ||||
|         """create temporary directories | ||||
|         """ | ||||
| 
 | ||||
|         # create a new temp directory if the current one is not found | ||||
|         if not self.video2x_cache_directory.exists(): | ||||
|         # 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)) | ||||
| @ -113,65 +134,74 @@ class Upscaler: | ||||
|                     traceback.print_exc() | ||||
| 
 | ||||
|     def _check_arguments(self): | ||||
|         # check if arguments are valid / all necessary argument | ||||
|         # values are specified | ||||
|         if not self.input_video: | ||||
|             Avalon.error(_('You must specify input video file/directory path')) | ||||
|             raise ArgumentError('input video path not specified') | ||||
|         if not self.output_video: | ||||
|             Avalon.error(_('You must specify output video file/directory path')) | ||||
|             raise ArgumentError('output video path not specified') | ||||
|         if (self.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and self.scale_width and self.scale_height: | ||||
|             Avalon.error(_('Selected driver accepts only scaling ratio')) | ||||
|             raise ArgumentError('selected driver supports only scaling ratio') | ||||
|         if self.driver == 'waifu2x_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio > 2 or not self.scale_ratio.is_integer()): | ||||
|             Avalon.error(_('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')) | ||||
|             raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan') | ||||
|         if self.driver == 'srmd_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio not in [2, 3, 4]): | ||||
|             Avalon.error(_('Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan')) | ||||
|             raise ArgumentError('scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan') | ||||
|         if (self.scale_width or self.scale_height) and self.scale_ratio: | ||||
|             Avalon.error(_('You can only specify either scaling ratio or output width and height')) | ||||
|             raise ArgumentError('both scaling ration and width/height specified') | ||||
|         if (self.scale_width and not self.scale_height) or (not self.scale_width and self.scale_height): | ||||
|             Avalon.error(_('You must specify both width and height')) | ||||
|             raise ArgumentError('only one of width or height is specified') | ||||
|         # if input is a file | ||||
|         if self.input_path.is_file(): | ||||
|             if self.output_path.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 not re.search(r'.*\..*$', str(self.output_path)): | ||||
|                 Avalon.error(_('No suffix found in output file path')) | ||||
|                 Avalon.error(_('Suffix must be specified for FFmpeg')) | ||||
|                 raise ArgumentError('no output video suffix specified') | ||||
| 
 | ||||
|     def _progress_bar(self, extracted_frames_directories): | ||||
|         """ This method prints a progress bar | ||||
|         # if input is a directory | ||||
|         elif self.input_path.is_dir(): | ||||
|             if self.output_path.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') | ||||
| 
 | ||||
|         This method prints a progress bar by keeping track | ||||
|         of the amount of frames in the input directory | ||||
|         and the output directory. This is originally | ||||
|         suggested by @ArmandBernard. | ||||
|         """ | ||||
|         # if input is neither | ||||
|         else: | ||||
|             Avalon.error(_('Input path is neither a file nor a directory')) | ||||
|             raise FileNotFoundError(f'{self.input_path} is neither file nor directory') | ||||
|          | ||||
|         # check Fmpeg 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']) | ||||
| 
 | ||||
|         # get number of extracted frames | ||||
|         self.total_frames = 0 | ||||
|         for directory in extracted_frames_directories: | ||||
|             self.total_frames += len([f for f in directory.iterdir() if str(f).lower().endswith(self.image_format.lower())]) | ||||
|         # check if driver settings | ||||
|         driver_settings = copy.deepcopy(self.driver_settings) | ||||
|         driver_path = driver_settings.pop('path') | ||||
| 
 | ||||
|         with tqdm(total=self.total_frames, ascii=True, desc=_('Upscaling Progress')) as progress_bar: | ||||
|         # 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) | ||||
| 
 | ||||
|             # 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 not self.progress_bar_exit_signal: | ||||
|         # 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(): | ||||
| 
 | ||||
|                 with contextlib.suppress(FileNotFoundError): | ||||
|                     self.total_frames_upscaled = len([f for f in self.upscaled_frames.iterdir() if str(f).lower().endswith(self.image_format.lower())]) | ||||
|                     delta = self.total_frames_upscaled - previous_cycle_frames | ||||
|                     previous_cycle_frames = self.total_frames_upscaled | ||||
|                 value = driver_settings[key] | ||||
| 
 | ||||
|                     # if upscaling is finished | ||||
|                     if self.total_frames_upscaled >= self.total_frames: | ||||
|                         return | ||||
|                 if value is None or value is False: | ||||
|                     continue | ||||
| 
 | ||||
|                     # adds the delta into the progress bar | ||||
|                     progress_bar.update(delta) | ||||
|                 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)) | ||||
| 
 | ||||
|                 time.sleep(1) | ||||
|             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): | ||||
|         """ Upscale video frames with waifu2x-caffe | ||||
| @ -183,16 +213,10 @@ class Upscaler: | ||||
|             w2 {Waifu2x Object} -- initialized waifu2x object | ||||
|         """ | ||||
| 
 | ||||
|         # progress bar process exit signal | ||||
|         self.progress_bar_exit_signal = False | ||||
| 
 | ||||
|         # initialize waifu2x driver | ||||
|         if self.driver not in AVAILABLE_DRIVERS: | ||||
|             raise UnrecognizedDriverError(_('Unrecognized driver: {}').format(self.driver)) | ||||
| 
 | ||||
|         # create a container for all upscaler processes | ||||
|         upscaler_processes = [] | ||||
| 
 | ||||
|         # list all images in the extracted frames | ||||
|         frames = [(self.extracted_frames / f) for f in self.extracted_frames.iterdir() if f.is_file] | ||||
| 
 | ||||
| @ -234,7 +258,7 @@ class Upscaler: | ||||
| 
 | ||||
|             # if the driver being used is waifu2x-caffe | ||||
|             if self.driver == 'waifu2x_caffe': | ||||
|                 upscaler_processes.append(driver.upscale(process_directory, | ||||
|                 self.process_pool.append(driver.upscale(process_directory, | ||||
|                                                          self.upscaled_frames, | ||||
|                                                          self.scale_ratio, | ||||
|                                                          self.scale_width, | ||||
| @ -244,7 +268,7 @@ class Upscaler: | ||||
| 
 | ||||
|             # if the driver being used is waifu2x-converter-cpp | ||||
|             elif self.driver == 'waifu2x_converter_cpp': | ||||
|                 upscaler_processes.append(driver.upscale(process_directory, | ||||
|                 self.process_pool.append(driver.upscale(process_directory, | ||||
|                                                          self.upscaled_frames, | ||||
|                                                          self.scale_ratio, | ||||
|                                                          self.processes, | ||||
| @ -252,55 +276,99 @@ class Upscaler: | ||||
| 
 | ||||
|             # if the driver being used is waifu2x-ncnn-vulkan | ||||
|             elif self.driver == 'waifu2x_ncnn_vulkan': | ||||
|                 upscaler_processes.append(driver.upscale(process_directory, | ||||
|                 self.process_pool.append(driver.upscale(process_directory, | ||||
|                                                          self.upscaled_frames, | ||||
|                                                          self.scale_ratio)) | ||||
| 
 | ||||
|             # if the driver being used is srmd_ncnn_vulkan | ||||
|             elif self.driver == 'srmd_ncnn_vulkan': | ||||
|                 upscaler_processes.append(driver.upscale(process_directory, | ||||
|                 self.process_pool.append(driver.upscale(process_directory, | ||||
|                                                          self.upscaled_frames, | ||||
|                                                          self.scale_ratio)) | ||||
| 
 | ||||
|         # start progress bar in a different thread | ||||
|         progress_bar = threading.Thread(target=self._progress_bar, args=(process_directories,)) | ||||
|         progress_bar.start() | ||||
|         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')) | ||||
|         image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_processes)) | ||||
|         image_cleaner.start() | ||||
|         self.image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(self.process_pool)) | ||||
|         self.image_cleaner.start() | ||||
| 
 | ||||
|         # wait for all process to exit | ||||
|         try: | ||||
|             Avalon.debug_info(_('Main process waiting for subprocesses to exit')) | ||||
|             for process in upscaler_processes: | ||||
|                 Avalon.debug_info(_('Subprocess {} exited with code {}').format(process.pid, process.wait())) | ||||
|         except (KeyboardInterrupt, SystemExit): | ||||
|             Avalon.warning('Exit signal received') | ||||
|             Avalon.warning('Killing processes') | ||||
|             for process in upscaler_processes: | ||||
|                 process.terminate() | ||||
|             self._wait() | ||||
|         except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|             # cleanup | ||||
|             Avalon.debug_info(_('Killing progress monitor')) | ||||
|             self.progress_monitor.stop() | ||||
| 
 | ||||
|             # cleanup and exit with exit code 1 | ||||
|             Avalon.debug_info(_('Killing upscaled image cleaner')) | ||||
|             image_cleaner.stop() | ||||
|             self.progress_bar_exit_signal = True | ||||
|             sys.exit(1) | ||||
|             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 self.upscaled_frames.iterdir() if f.is_file()]: | ||||
|                 renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.image_format}', f'.{self.image_format}', str(image.name)) | ||||
|                 renamed = re.sub(f'_\\[.*\\]\\[x(\\d+(\\.\\d+)?)\\]\\.{self.image_format}', | ||||
|                                  f'.{self.image_format}', | ||||
|                                  str(image.name)) | ||||
|                 (self.upscaled_frames / image).rename(self.upscaled_frames / renamed) | ||||
| 
 | ||||
|         # upscaling done, kill the clearer | ||||
|         Avalon.debug_info(_('Killing upscaled image cleaner')) | ||||
|         image_cleaner.stop() | ||||
|         # upscaling done, kill helper threads | ||||
|         Avalon.debug_info(_('Killing progress monitor')) | ||||
|         self.progress_monitor.stop() | ||||
| 
 | ||||
|         # pass exit signal to progress bar thread | ||||
|         self.progress_bar_exit_signal = True | ||||
|         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.stop_signal is True: | ||||
|                     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 | ||||
| 
 | ||||
|     def run(self): | ||||
|         """ Main controller for Video2X | ||||
| @ -308,94 +376,125 @@ class Upscaler: | ||||
|         This function controls the flow of video conversion | ||||
|         and handles all necessary functions. | ||||
|         """ | ||||
|          | ||||
|         # external stop signal when called in a thread | ||||
|         self.stop_signal = False | ||||
| 
 | ||||
|         # define process pool to contain processes | ||||
|         self.process_pool = [] | ||||
| 
 | ||||
|         # parse arguments for waifu2x | ||||
|         # check argument sanity | ||||
|         self._check_arguments() | ||||
| 
 | ||||
|         # convert paths to absolute paths | ||||
|         self.input_video = self.input_video.absolute() | ||||
|         self.output_video = self.output_video.absolute() | ||||
|         # define processing queue | ||||
|         processing_queue = queue.Queue() | ||||
| 
 | ||||
|         # drivers that have native support for video processing | ||||
|         if self.driver == 'anime4kcpp': | ||||
|             # append FFmpeg path to the end of PATH | ||||
|             # Anime4KCPP will then use FFmpeg to migrate audio tracks | ||||
|             os.environ['PATH'] += f';{self.ffmpeg_settings["ffmpeg_path"]}' | ||||
|             Avalon.info(_('Starting to upscale extracted images')) | ||||
|         # if input specified is single file | ||||
|         if self.input_path.is_file(): | ||||
|             Avalon.info(_('Upscaling single video file: {}').format(self.input_path)) | ||||
|             processing_queue.put((self.input_path.absolute(), self.output_path.absolute())) | ||||
| 
 | ||||
|             # import and initialize Anime4KCPP wrapper | ||||
|             DriverWrapperMain = getattr(importlib.import_module('wrappers.anime4kcpp'), 'WrapperMain') | ||||
|             driver = DriverWrapperMain(copy.deepcopy(self.driver_settings)) | ||||
|         # if input specified is a directory | ||||
|         elif self.input_path.is_dir(): | ||||
| 
 | ||||
|             # run Anime4KCPP | ||||
|             driver.upscale(self.input_video, self.output_video, self.scale_ratio, self.processes).wait() | ||||
|             Avalon.info(_('Upscaling completed')) | ||||
|             # make output directory if it doesn't exist | ||||
|             self.output_path.mkdir(parents=True, exist_ok=True) | ||||
|             for input_video in [f for f in self.input_path.iterdir() if f.is_file()]: | ||||
|                 output_video = self.output_path / input_video.name | ||||
|                 processing_queue.put((input_video.absolute(), output_video.absolute())) | ||||
| 
 | ||||
|         else: | ||||
|             self.create_temp_directories() | ||||
|         while not processing_queue.empty(): | ||||
|             input_video, output_video = processing_queue.get() | ||||
|             # drivers that have native support for video processing | ||||
|             if self.driver == 'anime4kcpp': | ||||
|                 # append FFmpeg path to the end of PATH | ||||
|                 # Anime4KCPP will then use FFmpeg to migrate audio tracks | ||||
|                 os.environ['PATH'] += f';{self.ffmpeg_settings["ffmpeg_path"]}' | ||||
|                 Avalon.info(_('Starting to upscale extracted images')) | ||||
| 
 | ||||
|             # initialize objects for ffmpeg and waifu2x-caffe | ||||
|             fm = Ffmpeg(self.ffmpeg_settings, self.image_format) | ||||
|                 # import and initialize Anime4KCPP wrapper | ||||
|                 DriverWrapperMain = getattr(importlib.import_module('wrappers.anime4kcpp'), 'WrapperMain') | ||||
|                 driver = DriverWrapperMain(copy.deepcopy(self.driver_settings)) | ||||
| 
 | ||||
|             Avalon.info(_('Reading video information')) | ||||
|             video_info = fm.get_video_info(self.input_video) | ||||
|             # analyze original video with ffprobe and retrieve framerate | ||||
|             # width, height = info['streams'][0]['width'], info['streams'][0]['height'] | ||||
|                 # run Anime4KCPP | ||||
|                 self.process_pool.append(driver.upscale(input_video, output_video, self.scale_ratio, self.processes)) | ||||
|                 self._wait() | ||||
|                 Avalon.info(_('Upscaling completed')) | ||||
| 
 | ||||
|             # find index of video stream | ||||
|             video_stream_index = None | ||||
|             for stream in video_info['streams']: | ||||
|                 if stream['codec_type'] == 'video': | ||||
|                     video_stream_index = stream['index'] | ||||
|                     break | ||||
|             else: | ||||
|                 try: | ||||
|                     self.create_temp_directories() | ||||
| 
 | ||||
|             # 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') | ||||
|                     # initialize objects for ffmpeg and waifu2x-caffe | ||||
|                     fm = Ffmpeg(self.ffmpeg_settings, self.image_format) | ||||
| 
 | ||||
|             # extract frames from video | ||||
|             fm.extract_frames(self.input_video, self.extracted_frames) | ||||
|                     Avalon.info(_('Reading video information')) | ||||
|                     video_info = fm.get_video_info(input_video) | ||||
|                     # analyze original video with ffprobe and retrieve framerate | ||||
|                     # width, height = info['streams'][0]['width'], info['streams'][0]['height'] | ||||
| 
 | ||||
|             # get average frame rate of video stream | ||||
|             framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate'])) | ||||
|             fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] | ||||
|                     # find index of video stream | ||||
|                     video_stream_index = None | ||||
|                     for stream in video_info['streams']: | ||||
|                         if stream['codec_type'] == 'video': | ||||
|                             video_stream_index = stream['index'] | ||||
|                             break | ||||
| 
 | ||||
|             # get a dict of all pixel formats and corresponding bit depth | ||||
|             pixel_formats = fm.get_pixel_formats() | ||||
|                     # 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') | ||||
| 
 | ||||
|             # try getting pixel format's corresponding bti depth | ||||
|             try: | ||||
|                 self.bit_depth = pixel_formats[fm.pixel_format] | ||||
|             except KeyError: | ||||
|                 Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format)) | ||||
|                 raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}') | ||||
|                     # extract frames from video | ||||
|                     self.process_pool.append((fm.extract_frames(input_video, self.extracted_frames))) | ||||
|                     self._wait() | ||||
| 
 | ||||
|             Avalon.info(_('Framerate: {}').format(framerate)) | ||||
|                     # get average frame rate of video stream | ||||
|                     framerate = float(Fraction(video_info['streams'][video_stream_index]['avg_frame_rate'])) | ||||
|                     fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] | ||||
| 
 | ||||
|             # width/height will be coded width/height x upscale factor | ||||
|             if self.scale_ratio: | ||||
|                 original_width = video_info['streams'][video_stream_index]['width'] | ||||
|                 original_height = video_info['streams'][video_stream_index]['height'] | ||||
|                 self.scale_width = int(self.scale_ratio * original_width) | ||||
|                 self.scale_height = int(self.scale_ratio * original_height) | ||||
|                     # get a dict of all pixel formats and corresponding bit depth | ||||
|                     pixel_formats = fm.get_pixel_formats() | ||||
| 
 | ||||
|             # upscale images one by one using waifu2x | ||||
|             Avalon.info(_('Starting to upscale extracted images')) | ||||
|             self._upscale_frames() | ||||
|             Avalon.info(_('Upscaling completed')) | ||||
|                     # try getting pixel format's corresponding bti depth | ||||
|                     try: | ||||
|                         self.bit_depth = pixel_formats[fm.pixel_format] | ||||
|                     except KeyError: | ||||
|                         Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format)) | ||||
|                         raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}') | ||||
| 
 | ||||
|             # frames to Video | ||||
|             Avalon.info(_('Converting extracted frames into video')) | ||||
|                     Avalon.info(_('Framerate: {}').format(framerate)) | ||||
| 
 | ||||
|             # use user defined output size | ||||
|             fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames) | ||||
|             Avalon.info(_('Conversion completed')) | ||||
|                     # width/height will be coded width/height x upscale factor | ||||
|                     if self.scale_ratio: | ||||
|                         original_width = video_info['streams'][video_stream_index]['width'] | ||||
|                         original_height = video_info['streams'][video_stream_index]['height'] | ||||
|                         self.scale_width = int(self.scale_ratio * original_width) | ||||
|                         self.scale_height = int(self.scale_ratio * original_height) | ||||
| 
 | ||||
|             # migrate audio tracks and subtitles | ||||
|             Avalon.info(_('Migrating audio tracks and subtitles to upscaled video')) | ||||
|             fm.migrate_audio_tracks_subtitles(self.input_video, self.output_video, self.upscaled_frames) | ||||
|                     # upscale images one by one using waifu2x | ||||
|                     Avalon.info(_('Starting to upscale extracted images')) | ||||
|                     self._upscale_frames() | ||||
|                     Avalon.info(_('Upscaling completed')) | ||||
| 
 | ||||
|             # destroy temp directories | ||||
|             self.cleanup_temp_directories() | ||||
|                     # frames to Video | ||||
|                     Avalon.info(_('Converting extracted frames into video')) | ||||
| 
 | ||||
|                     # use user defined output size | ||||
|                     self.process_pool.append(fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames)) | ||||
|                     self._wait() | ||||
|                     Avalon.info(_('Conversion completed')) | ||||
| 
 | ||||
|                     # migrate audio tracks and subtitles | ||||
|                     Avalon.info(_('Migrating audio tracks and subtitles to upscaled video')) | ||||
|                     self.process_pool.append(fm.migrate_audio_tracks_subtitles(input_video, output_video, self.upscaled_frames)) | ||||
|                     self._wait() | ||||
| 
 | ||||
|                     # destroy temp directories | ||||
|                     self.cleanup_temp_directories() | ||||
| 
 | ||||
|                 except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|                     with contextlib.suppress(ValueError): | ||||
|                         self.cleanup_temp_directories() | ||||
|                     raise e | ||||
|  | ||||
							
								
								
									
										134
									
								
								src/video2x.py
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/video2x.py
									
									
									
									
									
								
							| @ -13,7 +13,7 @@ __      __  _       _                  ___   __   __ | ||||
| Name: Video2X Controller | ||||
| Creator: K4YT3X | ||||
| Date Created: Feb 24, 2018 | ||||
| Last Modified: May 4, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Editor: BrianPetkovsek | ||||
| Last Modified: June 17, 2019 | ||||
| @ -181,20 +181,6 @@ config = read_config(video2x_args.config) | ||||
| driver_settings = config[video2x_args.driver] | ||||
| driver_settings['path'] = os.path.expandvars(driver_settings['path']) | ||||
| 
 | ||||
| # 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] | ||||
| 
 | ||||
| # check if driver path exists | ||||
| if not pathlib.Path(driver_settings['path']).exists(): | ||||
|     if not pathlib.Path(f'{driver_settings["path"]}.exe').exists(): | ||||
|         Avalon.error(_('Specified driver executable directory doesn\'t exist')) | ||||
|         Avalon.error(_('Please check the configuration file settings')) | ||||
|         raise FileNotFoundError(driver_settings['path']) | ||||
| 
 | ||||
| # read FFmpeg configuration | ||||
| ffmpeg_settings = config['ffmpeg'] | ||||
| ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path']) | ||||
| @ -202,113 +188,41 @@ ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path | ||||
| # load video2x settings | ||||
| image_format = config['video2x']['image_format'].lower() | ||||
| preserve_frames = config['video2x']['preserve_frames'] | ||||
| video2x_cache_directory = config['video2x']['video2x_cache_directory'] | ||||
| 
 | ||||
| # load cache directory | ||||
| if config['video2x']['video2x_cache_directory'] is not None: | ||||
|     video2x_cache_directory = pathlib.Path(config['video2x']['video2x_cache_directory']) | ||||
| else: | ||||
|     video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x' | ||||
| 
 | ||||
| if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir(): | ||||
|     Avalon.error(_('Specified cache directory is a file/link')) | ||||
|     raise FileExistsError('Specified cache directory is a file/link') | ||||
| 
 | ||||
| # if cache directory doesn't exist | ||||
| # ask the user if it should be created | ||||
| elif not video2x_cache_directory.exists(): | ||||
|     try: | ||||
|         Avalon.debug_info(_('Creating cache directory {}').format(video2x_cache_directory)) | ||||
|         video2x_cache_directory.mkdir(parents=True, exist_ok=True) | ||||
|     # there can be a number of exceptions here | ||||
|     # PermissionError, FileExistsError, etc. | ||||
|     # therefore, we put a catch-them-all here | ||||
|     except Exception as exception: | ||||
|         Avalon.error(_('Unable to create {}').format(video2x_cache_directory)) | ||||
|         raise exception | ||||
| 
 | ||||
| # 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() | ||||
| 
 | ||||
|     # if input specified is a single file | ||||
|     if video2x_args.input.is_file(): | ||||
|     # initialize upscaler object | ||||
|     upscaler = Upscaler(input_path=video2x_args.input, | ||||
|                         output_path=video2x_args.output, | ||||
|                         driver_settings=driver_settings, | ||||
|                         ffmpeg_settings=ffmpeg_settings) | ||||
| 
 | ||||
|         # upscale single video file | ||||
|         Avalon.info(_('Upscaling single video file: {}').format(video2x_args.input)) | ||||
|     # set upscaler optional options | ||||
|     upscaler.driver = video2x_args.driver | ||||
|     upscaler.scale_width = video2x_args.width | ||||
|     upscaler.scale_height = video2x_args.height | ||||
|     upscaler.scale_ratio = video2x_args.ratio | ||||
|     upscaler.processes = video2x_args.processes | ||||
|     upscaler.video2x_cache_directory = video2x_cache_directory | ||||
|     upscaler.image_format = image_format | ||||
|     upscaler.preserve_frames = preserve_frames | ||||
| 
 | ||||
|         # check for input output format mismatch | ||||
|         if video2x_args.output.is_dir(): | ||||
|             Avalon.error(_('Input and output path type mismatch')) | ||||
|             Avalon.error(_('Input is single file but output is directory')) | ||||
|             raise Exception('input output path type mismatch') | ||||
|         if not re.search(r'.*\..*$', str(video2x_args.output)): | ||||
|             Avalon.error(_('No suffix found in output file path')) | ||||
|             Avalon.error(_('Suffix must be specified for FFmpeg')) | ||||
|             raise Exception('No suffix specified') | ||||
| 
 | ||||
|         upscaler = Upscaler(input_video=video2x_args.input, | ||||
|                             output_video=video2x_args.output, | ||||
|                             driver_settings=driver_settings, | ||||
|                             ffmpeg_settings=ffmpeg_settings) | ||||
| 
 | ||||
|         # set optional options | ||||
|         upscaler.driver = video2x_args.driver | ||||
|         upscaler.scale_width = video2x_args.width | ||||
|         upscaler.scale_height = video2x_args.height | ||||
|         upscaler.scale_ratio = video2x_args.ratio | ||||
|         upscaler.processes = video2x_args.processes | ||||
|         upscaler.video2x_cache_directory = video2x_cache_directory | ||||
|         upscaler.image_format = image_format | ||||
|         upscaler.preserve_frames = preserve_frames | ||||
| 
 | ||||
|         # run upscaler | ||||
|         upscaler.run() | ||||
| 
 | ||||
|     # if input specified is a directory | ||||
|     elif video2x_args.input.is_dir(): | ||||
|         # upscale videos in a directory | ||||
|         Avalon.info(_('Upscaling videos in directory: {}').format(video2x_args.input)) | ||||
| 
 | ||||
|         # make output directory if it doesn't exist | ||||
|         video2x_args.output.mkdir(parents=True, exist_ok=True) | ||||
| 
 | ||||
|         for input_video in [f for f in video2x_args.input.iterdir() if f.is_file()]: | ||||
|             output_video = video2x_args.output / input_video.name | ||||
|             upscaler = Upscaler(input_video=input_video, | ||||
|                                 output_video=output_video, | ||||
|                                 driver_settings=driver_settings, | ||||
|                                 ffmpeg_settings=ffmpeg_settings) | ||||
| 
 | ||||
|             # set optional options | ||||
|             upscaler.driver = video2x_args.driver | ||||
|             upscaler.scale_width = video2x_args.width | ||||
|             upscaler.scale_height = video2x_args.height | ||||
|             upscaler.scale_ratio = video2x_args.ratio | ||||
|             upscaler.processes = video2x_args.processes | ||||
|             upscaler.video2x_cache_directory = video2x_cache_directory | ||||
|             upscaler.image_format = image_format | ||||
|             upscaler.preserve_frames = preserve_frames | ||||
| 
 | ||||
|             # run upscaler | ||||
|             upscaler.run() | ||||
|     else: | ||||
|         Avalon.error(_('Input path is neither a file nor a directory')) | ||||
|         raise FileNotFoundError(f'{video2x_args.input} is neither file nor directory') | ||||
|     # 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() | ||||
| 
 | ||||
|     # try cleaning up temp directories | ||||
|     with contextlib.suppress(Exception): | ||||
|         upscaler.cleanup_temp_directories() | ||||
| 
 | ||||
| finally: | ||||
|     # remove Video2X cache directory | ||||
|     with contextlib.suppress(FileNotFoundError): | ||||
|         if not preserve_frames: | ||||
|             shutil.rmtree(video2x_cache_directory) | ||||
|  | ||||
| @ -15,7 +15,6 @@ import contextlib | ||||
| import os | ||||
| import pathlib | ||||
| import re | ||||
| import shutil | ||||
| import sys | ||||
| import tempfile | ||||
| import time | ||||
| @ -54,6 +53,7 @@ def resource_path(relative_path: str) -> pathlib.Path: | ||||
| class WorkerSignals(QObject): | ||||
|     progress = pyqtSignal(tuple) | ||||
|     error = pyqtSignal(str) | ||||
|     interrupted = pyqtSignal() | ||||
|     finished = pyqtSignal() | ||||
| 
 | ||||
| class ProgressBarWorker(QRunnable): | ||||
| @ -89,11 +89,13 @@ class UpscalerWorker(QRunnable): | ||||
|         # 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: | ||||
|             error_message = traceback.format_exc() | ||||
|             print(error_message, file=sys.stderr) | ||||
|             self.signals.error.emit(error_message) | ||||
|         finally: | ||||
|         else: | ||||
|             self.signals.finished.emit() | ||||
| 
 | ||||
| class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
| @ -141,7 +143,7 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
| 
 | ||||
|         # express settings | ||||
|         self.driver_combo_box = self.findChild(QtWidgets.QComboBox, 'driverComboBox') | ||||
|         self.driver_combo_box.currentTextChanged.connect(self.update_driver_constraints) | ||||
|         self.driver_combo_box.currentTextChanged.connect(self.update_gui_for_driver) | ||||
|         self.processes_spin_box = self.findChild(QtWidgets.QSpinBox, 'processesSpinBox') | ||||
|         self.scale_ratio_double_spin_box = self.findChild(QtWidgets.QDoubleSpinBox, 'scaleRatioDoubleSpinBox') | ||||
|         self.preserve_frames_check_box = self.findChild(QtWidgets.QCheckBox, 'preserveFramesCheckBox') | ||||
| @ -258,25 +260,10 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|         self.ffmpeg_settings = self.config['ffmpeg'] | ||||
|         self.ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(self.ffmpeg_settings['ffmpeg_path']) | ||||
| 
 | ||||
|         # load cache directory, create it if necessary | ||||
|         if self.config['video2x']['video2x_cache_directory'] is not None: | ||||
|             video2x_cache_directory = pathlib.Path(self.config['video2x']['video2x_cache_directory']) | ||||
|         else: | ||||
|             video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x' | ||||
| 
 | ||||
|         if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir(): | ||||
|             self.show_error('Specified cache directory is a file/link') | ||||
|             raise FileExistsError('Specified cache directory is a file/link') | ||||
| 
 | ||||
|         # if cache directory doesn't exist | ||||
|         # ask the user if it should be created | ||||
|         elif not video2x_cache_directory.exists(): | ||||
|             try: | ||||
|                 video2x_cache_directory.mkdir(parents=True, exist_ok=True) | ||||
|             except Exception as exception: | ||||
|                 self.show_error(f'Unable to create cache directory: {video2x_cache_directory}') | ||||
|                 raise exception | ||||
|         self.cache_line_edit.setText(str(video2x_cache_directory.absolute())) | ||||
|         # set cache directory path | ||||
|         if self.config['video2x']['video2x_cache_directory'] is None: | ||||
|             video2x_cache_directory = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute()) | ||||
|         self.cache_line_edit.setText(video2x_cache_directory) | ||||
| 
 | ||||
|         # load preserve frames settings | ||||
|         self.preserve_frames_check_box.setChecked(self.config['video2x']['preserve_frames']) | ||||
| @ -399,8 +386,10 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|         self.config['anime4kcpp']['postprocessing'] = bool(self.anime4kcpp_post_processing_check_box.checkState()) | ||||
|         self.config['anime4kcpp']['GPUMode'] = bool(self.anime4kcpp_gpu_mode_check_box.checkState()) | ||||
| 
 | ||||
|     def update_driver_constraints(self): | ||||
|     def update_gui_for_driver(self): | ||||
|         current_driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||
|          | ||||
|         # update scale ratio constraints | ||||
|         if current_driver in ['waifu2x_caffe', 'waifu2x_converter_cpp', 'anime4kcpp']: | ||||
|             self.scale_ratio_double_spin_box.setMinimum(0.0) | ||||
|             self.scale_ratio_double_spin_box.setMaximum(999.0) | ||||
| @ -413,14 +402,29 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|             self.scale_ratio_double_spin_box.setMinimum(2.0) | ||||
|             self.scale_ratio_double_spin_box.setMaximum(4.0) | ||||
|             self.scale_ratio_double_spin_box.setValue(2.0) | ||||
|          | ||||
|         # update preferred processes/threads count | ||||
|         if current_driver == 'anime4kcpp': | ||||
|             self.processes_spin_box.setValue(16) | ||||
|         else: | ||||
|             self.processes_spin_box.setValue(1) | ||||
|      | ||||
|     def select_file(self, *args, **kwargs) -> pathlib.Path: | ||||
|         file_selected = QtWidgets.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 = QtWidgets.QFileDialog.getExistingDirectory(self, *args, **kwargs) | ||||
|         if folder_selected == '': | ||||
|             return None | ||||
|         return pathlib.Path(folder_selected) | ||||
| 
 | ||||
|     def select_input_file(self): | ||||
|         input_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Input File') | ||||
|         if not isinstance(input_file, tuple) or input_file[0] == '': | ||||
|          | ||||
|         if (input_file := self.select_file('Select Input File')) is None: | ||||
|             return | ||||
| 
 | ||||
|         input_file = pathlib.Path(input_file[0]) | ||||
| 
 | ||||
|         self.input_line_edit.setText(str(input_file.absolute())) | ||||
| 
 | ||||
|         # try to set an output file name automatically | ||||
| @ -435,11 +439,9 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|             self.output_line_edit.setText(str(output_file.absolute())) | ||||
| 
 | ||||
|     def select_input_folder(self): | ||||
|         input_folder = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Input Folder') | ||||
|         if input_folder == '': | ||||
|             return | ||||
| 
 | ||||
|         input_folder = pathlib.Path(input_folder) | ||||
|         if (input_folder := self.select_folder('Select Input Folder')) is None: | ||||
|             return | ||||
| 
 | ||||
|         self.input_line_edit.setText(str(input_folder.absolute())) | ||||
| 
 | ||||
| @ -455,40 +457,30 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|             self.output_line_edit.setText(str(output_folder.absolute())) | ||||
| 
 | ||||
|     def select_output_file(self): | ||||
|         output_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Output File') | ||||
|         if not isinstance(output_file, tuple): | ||||
|         if (output_file := self.select_file('Select Output File')) is None: | ||||
|             return | ||||
| 
 | ||||
|         self.output_line_edit.setText(str(pathlib.Path(output_file[0]).absolute())) | ||||
|         self.output_line_edit.setText(str(output_file.absolute())) | ||||
| 
 | ||||
|     def select_output_folder(self): | ||||
|         output_folder = QtWidgets.QFileDialog.getSaveFileName(self, 'Select Output Folder') | ||||
|         if output_folder == '': | ||||
|         if (output_folder := self.select_folder('Select Output Folder')) is None: | ||||
|             return | ||||
| 
 | ||||
|         self.output_line_edit.setText(str(pathlib.Path(output_folder).absolute())) | ||||
|         self.output_line_edit.setText(str(output_folder.absolute())) | ||||
| 
 | ||||
|     def select_cache_folder(self): | ||||
|         cache_folder = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Cache Folder') | ||||
|         if cache_folder == '': | ||||
|         if (cache_folder := self.select_folder('Select Cache Folder')) is None: | ||||
|             return | ||||
| 
 | ||||
|         self.cache_line_edit.setText(str(pathlib.Path(cache_folder).absolute())) | ||||
|         self.cache_line_edit.setText(str(cache_folder.absolute())) | ||||
| 
 | ||||
|     def select_config_file(self): | ||||
|         config_file = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Config File', filter='(YAML files (*.yaml))') | ||||
|         if not isinstance(config_file, tuple): | ||||
|         if (config_file := self.select_file('Select Config File', filter='(YAML files (*.yaml))')) is None: | ||||
|             return | ||||
| 
 | ||||
|         self.config_line_edit.setText(str(pathlib.Path(config_file[0]).absolute())) | ||||
|         self.config_line_edit.setText(str(config_file.absolute())) | ||||
|         self.load_configurations() | ||||
|      | ||||
|     def select_driver_binary_path(self, driver_line_edit): | ||||
|         driver_binary_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Driver Binary File') | ||||
|         if not isinstance(driver_binary_path, tuple) or driver_binary_path[0] == '': | ||||
|         if (driver_binary_path := self.select_file('Select Driver Binary File')) is None: | ||||
|             return | ||||
| 
 | ||||
|         driver_line_edit.setText(str(pathlib.Path(driver_binary_path[0]).absolute())) | ||||
|         driver_line_edit.setText(str(driver_binary_path.absolute())) | ||||
| 
 | ||||
|     def show_error(self, message: str): | ||||
|         QtWidgets.QErrorMessage(self).showMessage(message.replace('\n', '<br>')) | ||||
| @ -504,19 +496,24 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|         message_box.exec_() | ||||
| 
 | ||||
|     def start_progress_bar(self, progress_callback): | ||||
| 
 | ||||
|         # initialize variables early | ||||
|         self.upscaler.progress_bar_exit_signal = False | ||||
|         self.upscaler.total_frames_upscaled = 0 | ||||
|         self.upscaler.total_frames = 1 | ||||
|         # wait for progress monitor to come online | ||||
|         while 'progress_monitor' not in self.upscaler.__dict__: | ||||
|             if self.upscaler.stop_signal: | ||||
|                 return | ||||
|             time.sleep(0.1) | ||||
| 
 | ||||
|         # initialize progress bar values | ||||
|         upscale_begin_time = time.time() | ||||
|         progress_callback.emit((0, 0, 0, upscale_begin_time)) | ||||
| 
 | ||||
|         # keep querying upscaling process and feed information to callback signal | ||||
|         while not self.upscaler.progress_bar_exit_signal: | ||||
|             progress_callback.emit((int(100 * self.upscaler.total_frames_upscaled / self.upscaler.total_frames), | ||||
|         while self.upscaler.progress_monitor.running: | ||||
|             try: | ||||
|                 progress_percentage = int(100 * self.upscaler.total_frames_upscaled / self.upscaler.total_frames) | ||||
|             except ZeroDivisionError: | ||||
|                 progress_percentage = 0 | ||||
| 
 | ||||
|             progress_callback.emit((progress_percentage, | ||||
|                                    self.upscaler.total_frames_upscaled, | ||||
|                                    self.upscaler.total_frames, | ||||
|                                    upscale_begin_time)) | ||||
| @ -574,109 +571,60 @@ class Video2XMainWindow(QtWidgets.QMainWindow): | ||||
|             # load driver settings for the current driver | ||||
|             self.driver_settings = self.config[AVAILABLE_DRIVERS[self.driver_combo_box.currentText()]] | ||||
| 
 | ||||
|             # if input specified is a single file | ||||
|             if input_directory.is_file(): | ||||
|             self.upscaler = Upscaler(input_path=input_directory, | ||||
|                                      output_path=output_directory, | ||||
|                                      driver_settings=self.driver_settings, | ||||
|                                      ffmpeg_settings=self.ffmpeg_settings) | ||||
| 
 | ||||
|                 # check for input output format mismatch | ||||
|                 if output_directory.is_dir(): | ||||
|                     self.show_error('Input and output path type mismatch\n\ | ||||
|                                      Input is single file but output is directory') | ||||
|                     raise Exception('input output path type mismatch') | ||||
|                 if not re.search(r'.*\..*$', str(output_directory)): | ||||
|                     self.show_error('No suffix found in output file path\n\ | ||||
|                                      Suffix must be specified for FFmpeg') | ||||
|                     raise Exception('No suffix specified') | ||||
|             # set optional options | ||||
|             self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||
|             self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value() | ||||
|             self.upscaler.processes = self.processes_spin_box.value() | ||||
|             self.upscaler.video2x_cache_directory = pathlib.Path(os.path.expandvars(self.cache_line_edit.text())) | ||||
|             self.upscaler.image_format = self.config['video2x']['image_format'].lower() | ||||
|             self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.checkState()) | ||||
| 
 | ||||
|                 self.upscaler = Upscaler(input_video=input_directory, | ||||
|                                          output_video=output_directory, | ||||
|                                          driver_settings=self.driver_settings, | ||||
|                                          ffmpeg_settings=self.ffmpeg_settings) | ||||
|             # start progress bar | ||||
|             if AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] != 'anime4kcpp': | ||||
|                 progress_bar_worker = ProgressBarWorker(self.start_progress_bar) | ||||
|                 progress_bar_worker.signals.progress.connect(self.set_progress) | ||||
|                 self.threadpool.start(progress_bar_worker) | ||||
| 
 | ||||
|                 # set optional options | ||||
|                 self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||
|                 self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value() | ||||
|                 self.upscaler.processes = self.processes_spin_box.value() | ||||
|                 self.upscaler.video2x_cache_directory = pathlib.Path(os.path.expandvars(self.cache_line_edit.text())) | ||||
|                 self.upscaler.image_format = self.config['video2x']['image_format'].lower() | ||||
|                 self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.checkState()) | ||||
| 
 | ||||
|                 # start progress bar | ||||
|                 if AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] != 'anime4kcpp': | ||||
|                     progress_bar_worker = ProgressBarWorker(self.start_progress_bar) | ||||
|                     progress_bar_worker.signals.progress.connect(self.set_progress) | ||||
|                     self.threadpool.start(progress_bar_worker) | ||||
| 
 | ||||
|                 # run upscaler | ||||
|                 worker = UpscalerWorker(self.upscaler.run) | ||||
|                 worker.signals.error.connect(self.upscale_errored) | ||||
|                 worker.signals.finished.connect(self.upscale_completed) | ||||
|                 self.threadpool.start(worker) | ||||
|                 self.start_button.setEnabled(False) | ||||
|                 # self.stop_button.setEnabled(True) | ||||
| 
 | ||||
|             # if input specified is a directory | ||||
|             elif input_directory.is_dir(): | ||||
|                 # upscale videos in a directory | ||||
| 
 | ||||
|                 # make output directory if it doesn't exist | ||||
|                 output_directory.mkdir(parents=True, exist_ok=True) | ||||
| 
 | ||||
|                 for input_video in [f for f in input_directory.iterdir() if f.is_file()]: | ||||
|                     output_video = output_directory / input_video.name | ||||
|                     self.upscaler = Upscaler(input_video=input_video, | ||||
|                                              output_video=output_video, | ||||
|                                              driver_settings=self.driver_settings, | ||||
|                                              ffmpeg_settings=self.ffmpeg_settings) | ||||
| 
 | ||||
|                     # set optional options | ||||
|                     self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||
|                     self.upscaler.scale_ratio = self.scale_ratio_double_spin_box.value() | ||||
|                     self.upscaler.processes = self.processes_spin_box.value() | ||||
|                     self.upscaler.video2x_cache_directory = pathlib.Path(os.path.expandvars(self.cache_line_edit.text())) | ||||
|                     self.upscaler.image_format = self.config['video2x']['image_format'].lower() | ||||
|                     self.upscaler.preserve_frames = bool(self.preserve_frames_check_box.checkState()) | ||||
| 
 | ||||
|                     # start progress bar | ||||
|                     if AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] != 'anime4kcpp': | ||||
|                         progress_bar_worker = ProgressBarWorker(self.start_progress_bar) | ||||
|                         self.threadpool.start(progress_bar_worker) | ||||
| 
 | ||||
|                     # run upscaler | ||||
|                     worker = UpscalerWorker(self.upscaler.run) | ||||
|                     worker.signals.error.connect(self.upscale_errored) | ||||
|                     worker.signals.finished.connect(self.upscale_completed) | ||||
|                     self.threadpool.start(worker) | ||||
|                     self.start_button.setEnabled(False) | ||||
|                     # self.stop_button.setEnabled(True) | ||||
|             else: | ||||
|                 self.show_error('Input path is neither a file nor a directory') | ||||
|                 raise FileNotFoundError(f'{input_directory} is neither file nor directory') | ||||
|             # run upscaler | ||||
|             worker = UpscalerWorker(self.upscaler.run) | ||||
|             worker.signals.error.connect(self.upscale_errored) | ||||
|             worker.signals.finished.connect(self.upscale_completed) | ||||
|             worker.signals.interrupted.connect(self.upscale_interrupted) | ||||
|             self.threadpool.start(worker) | ||||
|             self.start_button.setEnabled(False) | ||||
|             self.stop_button.setEnabled(True) | ||||
| 
 | ||||
|         except Exception: | ||||
|             self.upscale_errored(traceback.format_exc()) | ||||
|             self.upscale_completed() | ||||
|      | ||||
|     def upscale_errored(self, error_message): | ||||
|         self.show_error(f'Upscaler ran into an error:\n{error_message}') | ||||
| 
 | ||||
|         # try cleaning up temp directories | ||||
|         with contextlib.suppress(Exception): | ||||
|             self.upscaler.progress_bar_exit_signal = True | ||||
|             self.upscaler.cleanup_temp_directories() | ||||
| 
 | ||||
|     def upscale_completed(self): | ||||
|         # if all threads have finished | ||||
|         if self.threadpool.activeThreadCount() == 0: | ||||
|             self.show_message('Program completed, taking {} seconds'.format(round((time.time() - self.begin_time), 5))) | ||||
|             # remove Video2X cache directory | ||||
|             with contextlib.suppress(FileNotFoundError): | ||||
|                 if not bool(self.preserve_frames_check_box.checkState()): | ||||
|                     shutil.rmtree(pathlib.Path(os.path.expandvars(self.cache_line_edit.text()))) | ||||
|             self.start_button.setEnabled(True) | ||||
|             self.stop_button.setEnabled(False) | ||||
|      | ||||
|     def upscale_interrupted(self): | ||||
|         self.show_message('Upscale has been interrupted') | ||||
|         self.start_button.setEnabled(True) | ||||
|         self.stop_button.setEnabled(False) | ||||
| 
 | ||||
|     def stop(self): | ||||
|         # TODO unimplemented yet | ||||
|         pass | ||||
|         with contextlib.suppress(AttributeError): | ||||
|             self.upscaler.stop_signal = True | ||||
| 
 | ||||
|     def closeEvent(self, event): | ||||
|         # try cleaning up temp directories | ||||
|         self.stop() | ||||
|         event.accept() | ||||
| 
 | ||||
| 
 | ||||
| # this file shouldn't be imported | ||||
|  | ||||
| @ -1206,6 +1206,16 @@ | ||||
|                   <string>vp09</string> | ||||
|                  </property> | ||||
|                 </item> | ||||
|                 <item> | ||||
|                  <property name="text"> | ||||
|                   <string>hevc</string> | ||||
|                  </property> | ||||
|                 </item> | ||||
|                 <item> | ||||
|                  <property name="text"> | ||||
|                   <string>av01</string> | ||||
|                  </property> | ||||
|                 </item> | ||||
|                </widget> | ||||
|               </item> | ||||
|              </layout> | ||||
|  | ||||
| @ -13,6 +13,8 @@ for waifu2x-caffe. | ||||
| # built-in imports | ||||
| import argparse | ||||
| import os | ||||
| import pathlib | ||||
| import platform | ||||
| import shlex | ||||
| import subprocess | ||||
| import threading | ||||
| @ -32,6 +34,7 @@ class WrapperMain: | ||||
|     @staticmethod | ||||
|     def parse_arguments(arguments): | ||||
|         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=pathlib.Path, help='File for loading') | ||||
|         # parser.add_argument('-o', '--output', type=pathlib.Path, help='File for outputting') | ||||
| @ -70,6 +73,11 @@ class WrapperMain: | ||||
|         self.driver_settings['output'] = output_file | ||||
|         self.driver_settings['zoomFactor'] = zoom_factor | ||||
|         self.driver_settings['threads'] = threads | ||||
|          | ||||
|         # 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 | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Name: Video2X FFmpeg Controller | ||||
| Author: K4YT3X | ||||
| Date Created: Feb 24, 2018 | ||||
| Last Modified: November 15, 2019 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Description: This class handles all FFmpeg related operations. | ||||
| """ | ||||
| @ -131,7 +131,7 @@ class Ffmpeg: | ||||
|             extracted_frames / f'extracted_%0d.{self.image_format}' | ||||
|         ]) | ||||
| 
 | ||||
|         self._execute(execute) | ||||
|         return(self._execute(execute)) | ||||
| 
 | ||||
|     def convert_video(self, framerate, resolution, upscaled_frames): | ||||
|         """Converts images into videos | ||||
| @ -180,7 +180,7 @@ class Ffmpeg: | ||||
|             upscaled_frames / 'no_audio.mp4' | ||||
|         ]) | ||||
| 
 | ||||
|         self._execute(execute) | ||||
|         return(self._execute(execute)) | ||||
| 
 | ||||
|     def migrate_audio_tracks_subtitles(self, input_video, output_video, upscaled_frames): | ||||
|         """ Migrates audio tracks and subtitles from input video to output video | ||||
| @ -209,7 +209,7 @@ class Ffmpeg: | ||||
|             output_video | ||||
|         ]) | ||||
| 
 | ||||
|         self._execute(execute) | ||||
|         return(self._execute(execute)) | ||||
| 
 | ||||
|     def _read_configuration(self, phase, section=None): | ||||
|         """ read configuration from JSON | ||||
| @ -284,4 +284,4 @@ class Ffmpeg: | ||||
| 
 | ||||
|         Avalon.debug_info(f'Executing: {execute}') | ||||
| 
 | ||||
|         return subprocess.run(execute, check=True).returncode | ||||
|         return subprocess.Popen(execute) | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Name: SRMD NCNN Vulkan Driver | ||||
| Creator: K4YT3X | ||||
| Date Created: April 26, 2020 | ||||
| Last Modified: May 5, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Description: This class is a high-level wrapper | ||||
| for srmd_ncnn_vulkan. | ||||
| @ -39,6 +39,7 @@ class WrapperMain: | ||||
|     @staticmethod | ||||
|     def parse_arguments(arguments): | ||||
|         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=pathlib.Path, help='input image path (jpg/png) or directory') | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Name: Waifu2x Caffe Driver | ||||
| Author: K4YT3X | ||||
| Date Created: Feb 24, 2018 | ||||
| Last Modified: May 4, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Description: This class is a high-level wrapper | ||||
| for waifu2x-caffe. | ||||
| @ -37,6 +37,7 @@ class WrapperMain: | ||||
|     @staticmethod | ||||
|     def parse_arguments(arguments): | ||||
|         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') | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Name: Waifu2x Converter CPP Driver | ||||
| Author: K4YT3X | ||||
| Date Created: February 8, 2019 | ||||
| Last Modified: May 4, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Description: This class is a high-level wrapper | ||||
| for waifu2x-converter-cpp. | ||||
| @ -38,6 +38,7 @@ class WrapperMain: | ||||
|     @staticmethod | ||||
|     def parse_arguments(arguments): | ||||
|         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') | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| Name: Waifu2x NCNN Vulkan Driver | ||||
| Creator: SAT3LL | ||||
| Date Created: June 26, 2019 | ||||
| Last Modified: May 5, 2020 | ||||
| Last Modified: May 7, 2020 | ||||
| 
 | ||||
| Editor: K4YT3X | ||||
| Last Modified: February 22, 2020 | ||||
| @ -42,6 +42,7 @@ class WrapperMain: | ||||
|     @staticmethod | ||||
|     def parse_arguments(arguments): | ||||
|         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=pathlib.Path, help='input image path (jpg/png) or directory') | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user