mirror of
				https://github.com/k4yt3x/video2x.git
				synced 2025-10-31 04:40:59 +01:00 
			
		
		
		
	Merge branch 'master' of github.com:k4yt3x/video2x
This commit is contained in:
		
						commit
						7201c036d5
					
				
							
								
								
									
										54
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								README.md
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| <p align="center"> | ||||
|    <img src="https://user-images.githubusercontent.com/21986859/81626588-ae5ab800-93eb-11ea-918f-ebe98c2de40a.png"/> | ||||
|    <img src="https://user-images.githubusercontent.com/21986859/102733190-872a7880-4334-11eb-8e9e-0ca747f130b1.png"/> | ||||
| </p> | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 
 | ||||
| <!--# Video2X Lossless Video Enlarger--> | ||||
| 
 | ||||
| @ -232,56 +232,6 @@ Are you interested in how the idea of Video2X was born? Do you want to know the | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| # Full Usage | ||||
| 
 | ||||
| ## Video2X Options | ||||
| 
 | ||||
| ### -h, --help | ||||
|     show this help message and exit | ||||
| 
 | ||||
| ### -i INPUT, --input INPUT | ||||
|     source video file/directory | ||||
| 
 | ||||
| ### -o OUTPUT, --output OUTPUT | ||||
|     output video file/directory | ||||
| 
 | ||||
| ### -c CONFIG, --config CONFIG | ||||
|     video2x config file path | ||||
| 
 | ||||
| ### --log LOG | ||||
|     log file path (default: program_directory\video2x_%Y-%m-%d_%H-%M-%S.log) | ||||
| 
 | ||||
| ### --disable_logging | ||||
|     disable logging (default: False) | ||||
| 
 | ||||
| ### -v, --version | ||||
|     display version, lawful information and exit | ||||
| 
 | ||||
| ## Upscaling Options | ||||
| 
 | ||||
| ### -d DRIVER, --driver DRIVER | ||||
|     upscaling driver (default: waifu2x_caffe) | ||||
| 
 | ||||
| Available options are: | ||||
| 
 | ||||
| - waifu2x_caffe | ||||
| - waifu2x_converter_cpp | ||||
| - waifu2x_ncnn_vulkan | ||||
| - srmd_ncnn_vulkan | ||||
| - realsr_ncnn_vulkan | ||||
| - anime4kcpp | ||||
| 
 | ||||
| ### -r RATIO, --ratio RATIO | ||||
|     scaling ratio | ||||
| 
 | ||||
| ### -p PROCESSES, --processes PROCESSES | ||||
|     number of processes to use for upscaling (default: 1) | ||||
| 
 | ||||
| ### --preserve_frames | ||||
|     preserve extracted and upscaled frames (default: False) | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| Licensed under the GNU General Public License Version 3 (GNU GPL v3) | ||||
|  | ||||
| @ -41,6 +41,5 @@ class BiLogger(io.TextIOWrapper): | ||||
|         self.log_file.flush() | ||||
| 
 | ||||
|     def flush(self): | ||||
|         """ flush logger (for compability only) | ||||
|         """ | ||||
|         """flush logger (for compability only)""" | ||||
|         pass | ||||
|  | ||||
| @ -40,8 +40,7 @@ class ImageCleaner(threading.Thread): | ||||
|         self.running = False | ||||
| 
 | ||||
|     def run(self): | ||||
|         """ Run image cleaner | ||||
|         """ | ||||
|         """Run image cleaner""" | ||||
|         self.running = True | ||||
| 
 | ||||
|         while self.running: | ||||
| @ -49,8 +48,7 @@ class ImageCleaner(threading.Thread): | ||||
|             time.sleep(1) | ||||
| 
 | ||||
|     def stop(self): | ||||
|         """ Stop the image cleaner | ||||
|         """ | ||||
|         """Stop the image cleaner""" | ||||
|         self.running = False | ||||
|         self.join() | ||||
| 
 | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 29 KiB | 
										
											Binary file not shown.
										
									
								
							| @ -34,7 +34,15 @@ class ProgressMonitor(threading.Thread): | ||||
|     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: | ||||
|         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. | ||||
| @ -42,7 +50,13 @@ class ProgressMonitor(threading.Thread): | ||||
|             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())] | ||||
|                     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) | ||||
|  | ||||
							
								
								
									
										600
									
								
								src/upscaler.py
									
									
									
									
									
								
							
							
						
						
									
										600
									
								
								src/upscaler.py
									
									
									
									
									
								
							| @ -43,35 +43,39 @@ from tqdm import tqdm | ||||
| import magic | ||||
| 
 | ||||
| # internationalization constants | ||||
| DOMAIN = 'video2x' | ||||
| LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / 'locale' | ||||
| DOMAIN = "video2x" | ||||
| LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / "locale" | ||||
| 
 | ||||
| # getting default locale settings | ||||
| default_locale, encoding = locale.getdefaultlocale() | ||||
| language = gettext.translation(DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True) | ||||
| language = gettext.translation( | ||||
|     DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True | ||||
| ) | ||||
| language.install() | ||||
| _ = language.gettext | ||||
| 
 | ||||
| # version information | ||||
| UPSCALER_VERSION = '4.4.1' | ||||
| UPSCALER_VERSION = "4.4.1" | ||||
| 
 | ||||
| # 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'] | ||||
| AVAILABLE_DRIVERS = [ | ||||
|     "waifu2x_caffe", | ||||
|     "waifu2x_converter_cpp", | ||||
|     "waifu2x_ncnn_vulkan", | ||||
|     "srmd_ncnn_vulkan", | ||||
|     "realsr_ncnn_vulkan", | ||||
|     "anime4kcpp", | ||||
| ] | ||||
| 
 | ||||
| # 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_ncnn_vulkan": [1, 2], | ||||
|     "srmd_ncnn_vulkan": [2, 3, 4], | ||||
|     "realsr_ncnn_vulkan": [4], | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -91,17 +95,18 @@ class Upscaler: | ||||
|         driver_settings: dict, | ||||
|         ffmpeg_settings: dict, | ||||
|         gifski_settings: dict, | ||||
|         driver: str = 'waifu2x_caffe', | ||||
|         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 | ||||
|         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, | ||||
|     ): | ||||
| 
 | ||||
|         # required parameters | ||||
| @ -137,109 +142,155 @@ class Upscaler: | ||||
|         self.last_frame_upscaled = pathlib.Path() | ||||
| 
 | ||||
|     def create_temp_directories(self): | ||||
|         """create temporary directories | ||||
|         """ | ||||
|         """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' | ||||
|             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 ( | ||||
|             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)) | ||||
|                 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)) | ||||
|                 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)) | ||||
|         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 | ||||
|         """ | ||||
|         """delete temp directories when done""" | ||||
|         if not self.preserve_frames: | ||||
|             for directory in [self.extracted_frames, self.upscaled_frames, self.video2x_cache_directory]: | ||||
|             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)) | ||||
|                     print(_("Cleaning up cache directory: {}").format(directory)) | ||||
|                     shutil.rmtree(directory) | ||||
|                 except FileNotFoundError: | ||||
|                     pass | ||||
|                 except OSError: | ||||
|                     print(_('Unable to delete: {}').format(directory)) | ||||
|                     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') | ||||
|                 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') | ||||
|                     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') | ||||
|                         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') | ||||
|                 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') | ||||
|                 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') | ||||
|                     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') | ||||
|             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']) | ||||
|         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') | ||||
|         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')) | ||||
|         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 | ||||
| @ -255,20 +306,24 @@ class Upscaler: | ||||
| 
 | ||||
|                 else: | ||||
|                     if len(key) == 1: | ||||
|                         driver_arguments.append(f'-{key}') | ||||
|                         driver_arguments.append(f"-{key}") | ||||
|                     else: | ||||
|                         driver_arguments.append(f'--{key}') | ||||
|                         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 = 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])) | ||||
|             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): | ||||
|     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 | ||||
| @ -285,7 +340,9 @@ class Upscaler: | ||||
| 
 | ||||
|         # initialize waifu2x driver | ||||
|         if self.driver not in AVAILABLE_DRIVERS: | ||||
|             raise UnrecognizedDriverError(_('Unrecognized driver: {}').format(self.driver)) | ||||
|             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] | ||||
| @ -308,7 +365,13 @@ class Upscaler: | ||||
|             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']: | ||||
|         if self.driver in [ | ||||
|             "waifu2x_converter_cpp", | ||||
|             "waifu2x_ncnn_vulkan", | ||||
|             "srmd_ncnn_vulkan", | ||||
|             "realsr_ncnn_vulkan", | ||||
|             "anime4kcpp", | ||||
|         ]: | ||||
|             process_directories = [input_directory] | ||||
| 
 | ||||
|         else: | ||||
| @ -318,20 +381,26 @@ class Upscaler: | ||||
|                 # move image | ||||
|                 image.rename(process_directories[0] / image.name) | ||||
|                 # rotate list | ||||
|                 process_directories = process_directories[-1:] + process_directories[:-1] | ||||
|                 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)) | ||||
|             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')) | ||||
|         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)) | ||||
|         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 | ||||
| @ -339,38 +408,39 @@ class Upscaler: | ||||
|             self._wait() | ||||
|         except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|             # cleanup | ||||
|             Avalon.debug_info(_('Killing progress monitor')) | ||||
|             Avalon.debug_info(_("Killing progress monitor")) | ||||
|             self.progress_monitor.stop() | ||||
| 
 | ||||
|             Avalon.debug_info(_('Killing upscaled image cleaner')) | ||||
|             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': | ||||
|         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)) | ||||
|                 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')) | ||||
|         Avalon.debug_info(_("Killing progress monitor")) | ||||
|         self.progress_monitor.stop() | ||||
| 
 | ||||
|         Avalon.debug_info(_('Killing upscaled image cleaner')) | ||||
|         Avalon.debug_info(_("Killing upscaled image cleaner")) | ||||
|         self.image_cleaner.stop() | ||||
| 
 | ||||
|     def _terminate_subprocesses(self): | ||||
|         Avalon.warning(_('Terminating all processes')) | ||||
|         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')) | ||||
|         """wait for subprocesses in process pool to complete""" | ||||
|         Avalon.debug_info(_("Main process waiting for subprocesses to exit")) | ||||
| 
 | ||||
|         try: | ||||
|             # while process pool not empty | ||||
| @ -389,22 +459,32 @@ class Upscaler: | ||||
| 
 | ||||
|                     # 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) | ||||
|                         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)) | ||||
|                         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')) | ||||
|             Avalon.warning(_("Stop signal received")) | ||||
|             self._terminate_subprocesses() | ||||
|             raise e | ||||
| 
 | ||||
|         except (Exception, subprocess.CalledProcessError) as e: | ||||
|             Avalon.error(_('Subprocess execution ran into an error')) | ||||
|             Avalon.error(_("Subprocess execution ran into an error")) | ||||
|             self._terminate_subprocesses() | ||||
|             raise e | ||||
| 
 | ||||
| @ -422,20 +502,24 @@ class Upscaler: | ||||
|         self.process_pool = [] | ||||
| 
 | ||||
|         # load driver modules | ||||
|         DriverWrapperMain = getattr(importlib.import_module(f'wrappers.{self.driver}'), 'WrapperMain') | ||||
|         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) | ||||
|         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)) | ||||
|         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(): | ||||
| @ -470,39 +554,53 @@ class Upscaler: | ||||
|             # get file type | ||||
|             # try python-magic if it's available | ||||
|             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] | ||||
|                 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 = '' | ||||
|                 input_file_mime_type = input_file_type = input_file_subtype = "" | ||||
| 
 | ||||
|             # 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']: | ||||
|             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] | ||||
|                 input_file_type = input_file_mime_type.split("/")[0] | ||||
|                 input_file_subtype = input_file_mime_type.split("/")[1] | ||||
| 
 | ||||
|             Avalon.debug_info(_('File MIME type: {}').format(input_file_mime_type)) | ||||
|             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') | ||||
|             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" | ||||
|                 ) | ||||
| 
 | ||||
|             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) | ||||
|             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, | ||||
|                 ) | ||||
| 
 | ||||
|             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) | ||||
|             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')) | ||||
|                 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 | ||||
| @ -512,14 +610,24 @@ class Upscaler: | ||||
| 
 | ||||
|             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 = 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)) | ||||
|             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() | ||||
| @ -527,22 +635,28 @@ class Upscaler: | ||||
|         # record file count for external calls | ||||
|         self.total_files = self.processing_queue.qsize() | ||||
| 
 | ||||
|         Avalon.info(_('Loaded files into processing queue')) | ||||
|         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())) | ||||
|             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() | ||||
|                 ( | ||||
|                     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')) | ||||
|                 Avalon.info(_("Reading file information")) | ||||
|                 file_info = self.ffmpeg_object.probe_file_info(self.current_input_file) | ||||
| 
 | ||||
|                 # create temporary directories for storing frames | ||||
| @ -550,50 +664,61 @@ class Upscaler: | ||||
| 
 | ||||
|                 # 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')) | ||||
|                 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) | ||||
|                     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']) | ||||
|                     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')) | ||||
|                     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'] | ||||
|                     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') | ||||
|                         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']) | ||||
|                     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')) | ||||
|                     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']) | ||||
|                     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, video_stream_index) | ||||
|                         self.total_frames = self.ffmpeg_object.get_number_of_frames( | ||||
|                             self.current_input_file, video_stream_index | ||||
|                         ) | ||||
| 
 | ||||
|                 # calculate scale width/height/ratio and scaling jobs if required | ||||
|                 Avalon.info(_('Calculating scaling parameters')) | ||||
|                 Avalon.info(_("Calculating scaling parameters")) | ||||
| 
 | ||||
|                 # create a local copy of the global output settings | ||||
|                 output_scale = self.scale_ratio | ||||
| @ -624,7 +749,9 @@ class Upscaler: | ||||
|                 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]) | ||||
|                     supported_scaling_ratios = sorted( | ||||
|                         DRIVER_FIXED_SCALING_RATIOS[self.driver] | ||||
|                     ) | ||||
| 
 | ||||
|                     remaining_scaling_ratio = math.ceil(output_scale) | ||||
|                     self.scaling_jobs = [] | ||||
| @ -654,46 +781,68 @@ class Upscaler: | ||||
|                                         break | ||||
| 
 | ||||
|                                 if found is False: | ||||
|                                     self.scaling_jobs.append(supported_scaling_ratios[-1]) | ||||
|                                     remaining_scaling_ratio /= supported_scaling_ratios[-1] | ||||
|                                     self.scaling_jobs.append( | ||||
|                                         supported_scaling_ratios[-1] | ||||
|                                     ) | ||||
|                                     remaining_scaling_ratio /= supported_scaling_ratios[ | ||||
|                                         -1 | ||||
|                                     ] | ||||
| 
 | ||||
|                 else: | ||||
|                     self.scaling_jobs = [output_scale] | ||||
| 
 | ||||
|                 # 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)) | ||||
|                 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))) | ||||
|                 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 | ||||
|                             ) | ||||
|                         ) | ||||
|                     ) | ||||
|                     self._wait() | ||||
| 
 | ||||
|                 # if driver is waifu2x-caffe | ||||
|                 # pass pixel format output depth information | ||||
|                 if self.driver == 'waifu2x_caffe': | ||||
|                 if self.driver == "waifu2x_caffe": | ||||
|                     # get a dict of all pixel formats and corresponding bit depth | ||||
|                     pixel_formats = self.ffmpeg_object.get_pixel_formats() | ||||
| 
 | ||||
|                     # try getting pixel format's corresponding bti depth | ||||
|                     try: | ||||
|                         self.driver_settings['output_depth'] = pixel_formats[self.ffmpeg_object.pixel_format] | ||||
|                         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}') | ||||
|                         Avalon.error( | ||||
|                             _("Unsupported pixel format: {}").format( | ||||
|                                 self.ffmpeg_object.pixel_format | ||||
|                             ) | ||||
|                         ) | ||||
|                         raise UnsupportedPixelError( | ||||
|                             f"unsupported pixel format {self.ffmpeg_object.pixel_format}" | ||||
|                         ) | ||||
| 
 | ||||
|                 # upscale images one by one using waifu2x | ||||
|                 Avalon.info(_('Starting to upscale extracted frames')) | ||||
|                 Avalon.info(_("Starting to upscale extracted frames")) | ||||
|                 upscale_begin_time = time.time() | ||||
| 
 | ||||
|                 self.current_pass = 1 | ||||
|                 if self.driver == 'waifu2x_caffe': | ||||
|                 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]) | ||||
| @ -706,22 +855,39 @@ class Upscaler: | ||||
|                     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))) | ||||
|                 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')) | ||||
|                 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')): | ||||
|                 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) | ||||
|                     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 | ||||
| @ -732,71 +898,117 @@ class Upscaler: | ||||
| 
 | ||||
|                 # start handling output | ||||
|                 # output can be either GIF or video | ||||
|                 if input_file_type == 'image' and input_file_subtype != 'gif': | ||||
|                 if input_file_type == "image" and input_file_subtype != "gif": | ||||
| 
 | ||||
|                     Avalon.info(_('Exporting image')) | ||||
|                     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) | ||||
|                     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': | ||||
|                 else: | ||||
| 
 | ||||
|                     # if the desired output is gif file | ||||
|                     if output_path.suffix.lower() == '.gif': | ||||
|                         Avalon.info(_('Converting extracted frames into GIF image')) | ||||
|                     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.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')) | ||||
|                         Avalon.info(_("Conversion completed")) | ||||
| 
 | ||||
|                     # 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)) | ||||
|                         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')) | ||||
|                         Avalon.info(_("Conversion completed")) | ||||
| 
 | ||||
|                         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, | ||||
|                             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.upscaled_frames, | ||||
|                                 ) | ||||
|                             ) | ||||
|                             self._wait() | ||||
| 
 | ||||
|                         # 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')) | ||||
|                             Avalon.error(_("Failed to migrate streams")) | ||||
|                             Avalon.warning( | ||||
|                                 _("Trying to output video without additional streams") | ||||
|                             ) | ||||
| 
 | ||||
|                             if input_file_mime_type == 'image/gif': | ||||
|                             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) | ||||
|                                 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 | ||||
|                                 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')) | ||||
|                                     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')) | ||||
|                                     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) | ||||
|                                 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() | ||||
|  | ||||
							
								
								
									
										193
									
								
								src/video2x.py
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								src/video2x.py
									
									
									
									
									
								
							| @ -71,75 +71,143 @@ import yaml | ||||
| from avalon_framework import Avalon | ||||
| 
 | ||||
| # internationalization constants | ||||
| DOMAIN = 'video2x' | ||||
| LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / 'locale' | ||||
| DOMAIN = "video2x" | ||||
| LOCALE_DIRECTORY = pathlib.Path(__file__).parent.absolute() / "locale" | ||||
| 
 | ||||
| # getting default locale settings | ||||
| default_locale, encoding = locale.getdefaultlocale() | ||||
| language = gettext.translation(DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True) | ||||
| language = gettext.translation( | ||||
|     DOMAIN, LOCALE_DIRECTORY, [default_locale], fallback=True | ||||
| ) | ||||
| language.install() | ||||
| _ = language.gettext | ||||
| 
 | ||||
| CLI_VERSION = '4.3.1' | ||||
| CLI_VERSION = "4.3.1" | ||||
| 
 | ||||
| LEGAL_INFO = _('''Video2X CLI Version: {} | ||||
| LEGAL_INFO = _( | ||||
|     """Video2X CLI Version: {} | ||||
| Upscaler Version: {} | ||||
| 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(CLI_VERSION, UPSCALER_VERSION) | ||||
| 
 | ||||
| LOGO = r''' | ||||
| LOGO = r""" | ||||
|     __      __  _       _                  ___   __   __ | ||||
|     \ \    / / (_)     | |                |__ \  \ \ / / | ||||
|      \ \  / /   _    __| |   ___    ___      ) |  \ V / | ||||
|       \ \/ /   | |  / _` |  / _ \  / _ \    / /    > < | ||||
|        \  /    | | | (_| | |  __/ | (_) |  / /_   / . \ | ||||
|         \/     |_|  \__,_|  \___|  \___/  |____| /_/ \_\ | ||||
| ''' | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def parse_arguments(): | ||||
|     """ parse CLI arguments | ||||
|     """ | ||||
|     parser = argparse.ArgumentParser(prog='video2x', formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) | ||||
|     """parse CLI arguments""" | ||||
|     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')) | ||||
|     video2x_options = parser.add_argument_group(_("Video2X Options")) | ||||
| 
 | ||||
|     video2x_options.add_argument( | ||||
|         "--help", action="help", help=_("show this help message and exit") | ||||
|     ) | ||||
| 
 | ||||
|     # 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: | ||||
|     if "-h" in sys.argv or "--help" in sys.argv: | ||||
|         require_input_output = False | ||||
|     video2x_options.add_argument('-i', '--input', type=pathlib.Path, help=_('source video file/directory'), required=require_input_output) | ||||
|     video2x_options.add_argument('-o', '--output', type=pathlib.Path, help=_('output video file/directory'), required=require_input_output) | ||||
| 
 | ||||
|     video2x_options.add_argument('-c', '--config', type=pathlib.Path, help=_('Video2X config file path'), action='store', | ||||
|                                  default=pathlib.Path(__file__).parent.absolute() / 'video2x.yaml') | ||||
|     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') | ||||
|     video2x_options.add_argument( | ||||
|         "-i", | ||||
|         "--input", | ||||
|         type=pathlib.Path, | ||||
|         help=_("source video file/directory"), | ||||
|         required=require_input_output, | ||||
|     ) | ||||
| 
 | ||||
|     video2x_options.add_argument( | ||||
|         "-o", | ||||
|         "--output", | ||||
|         type=pathlib.Path, | ||||
|         help=_("output video file/directory"), | ||||
|         required=require_input_output, | ||||
|     ) | ||||
| 
 | ||||
|     video2x_options.add_argument( | ||||
|         "-c", | ||||
|         "--config", | ||||
|         type=pathlib.Path, | ||||
|         help=_("Video2X config file path"), | ||||
|         action="store", | ||||
|         default=pathlib.Path(__file__).parent.absolute() / "video2x.yaml", | ||||
|     ) | ||||
| 
 | ||||
|     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", | ||||
|     ) | ||||
| 
 | ||||
|     # 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('-d', '--driver', help=_('upscaling driver'), choices=AVAILABLE_DRIVERS, default='waifu2x_ncnn_vulkan') | ||||
|     upscaling_options.add_argument('-p', '--processes', help=_('number of processes to use for upscaling'), action='store', type=int, default=1) | ||||
|     upscaling_options.add_argument('--preserve_frames', help=_('preserve extracted and upscaled frames'), action='store_true') | ||||
|     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( | ||||
|         "-d", | ||||
|         "--driver", | ||||
|         help=_("upscaling driver"), | ||||
|         choices=AVAILABLE_DRIVERS, | ||||
|         default="waifu2x_ncnn_vulkan", | ||||
|     ) | ||||
| 
 | ||||
|     upscaling_options.add_argument( | ||||
|         "-p", | ||||
|         "--processes", | ||||
|         help=_("number of processes to use for upscaling"), | ||||
|         action="store", | ||||
|         type=int, | ||||
|         default=1, | ||||
|     ) | ||||
| 
 | ||||
|     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: | ||||
|     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:]) | ||||
|         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 | ||||
| 
 | ||||
| 
 | ||||
| @ -160,16 +228,16 @@ def read_config(config_file: pathlib.Path) -> dict: | ||||
|         dict -- dictionary of video2x configuration | ||||
|     """ | ||||
| 
 | ||||
|     with open(config_file, 'r') as config: | ||||
|     with open(config_file, "r") as config: | ||||
|         return yaml.load(config, Loader=yaml.FullLoader) | ||||
| 
 | ||||
| 
 | ||||
| # /////////////////// Execution /////////////////// # | ||||
| 
 | ||||
| # this is not a library | ||||
| if __name__ != '__main__': | ||||
|     Avalon.error(_('This file cannot be imported')) | ||||
|     raise ImportError(f'{__file__} cannot be imported') | ||||
| if __name__ != "__main__": | ||||
|     Avalon.error(_("This file cannot be imported")) | ||||
|     raise ImportError(f"{__file__} cannot be imported") | ||||
| 
 | ||||
| # print video2x logo | ||||
| print_logo() | ||||
| @ -183,15 +251,19 @@ if video2x_args.version: | ||||
|     sys.exit(0) | ||||
| 
 | ||||
| # 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')) | ||||
| 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) | ||||
| 
 | ||||
| # 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') | ||||
|     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') | ||||
|     log_file = tempfile.TemporaryFile( | ||||
|         mode="a+", suffix=".log", prefix="video2x_", encoding="utf-8" | ||||
|     ) | ||||
| 
 | ||||
| original_stdout = sys.stdout | ||||
| original_stderr = sys.stderr | ||||
| @ -203,22 +275,22 @@ config = read_config(video2x_args.config) | ||||
| 
 | ||||
| # load waifu2x configuration | ||||
| driver_settings = config[video2x_args.driver] | ||||
| driver_settings['path'] = os.path.expandvars(driver_settings['path']) | ||||
| 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']) | ||||
| 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']) | ||||
| 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'] | ||||
| 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 | ||||
| @ -227,10 +299,10 @@ if video2x_args.preserve_frames is 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') | ||||
| 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']) | ||||
|     video2x_cache_directory = pathlib.Path(config["video2x"]["video2x_cache_directory"]) | ||||
| 
 | ||||
| # overwrite driver_settings with driver_args | ||||
| if driver_args is not None: | ||||
| @ -252,7 +324,6 @@ try: | ||||
|         driver_settings=driver_settings, | ||||
|         ffmpeg_settings=ffmpeg_settings, | ||||
|         gifski_settings=gifski_settings, | ||||
| 
 | ||||
|         # optional parameters | ||||
|         driver=video2x_args.driver, | ||||
|         scale_ratio=video2x_args.ratio, | ||||
| @ -264,17 +335,21 @@ try: | ||||
|         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 | ||||
|         preserve_frames=preserve_frames, | ||||
|     ) | ||||
| 
 | ||||
|     # run upscaler | ||||
|     upscaler.run() | ||||
| 
 | ||||
|     Avalon.info(_('Program completed, taking {} seconds').format(round((time.time() - begin_time), 5))) | ||||
|     Avalon.info( | ||||
|         _("Program completed, taking {} seconds").format( | ||||
|             round((time.time() - begin_time), 5) | ||||
|         ) | ||||
|     ) | ||||
| 
 | ||||
| except Exception: | ||||
| 
 | ||||
|     Avalon.error(_('An exception has occurred')) | ||||
|     Avalon.error(_("An exception has occurred")) | ||||
|     traceback.print_exc() | ||||
| 
 | ||||
|     if video2x_args.log is not None: | ||||
| @ -284,12 +359,12 @@ except Exception: | ||||
|     # 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_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)) | ||||
|     Avalon.error(_("The error log file can be found at: {}").format(log_file_path)) | ||||
| 
 | ||||
| finally: | ||||
|     sys.stdout = original_stdout | ||||
|  | ||||
							
								
								
									
										1668
									
								
								src/video2x_gui.py
									
									
									
									
									
								
							
							
						
						
									
										1668
									
								
								src/video2x_gui.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -27,8 +27,7 @@ from avalon_framework import Avalon | ||||
| 
 | ||||
| 
 | ||||
| class WrapperMain: | ||||
|     """ Anime4K CPP wrapper | ||||
|     """ | ||||
|     """Anime4K CPP wrapper""" | ||||
| 
 | ||||
|     def __init__(self, driver_settings): | ||||
|         self.driver_settings = driver_settings | ||||
| @ -38,53 +37,55 @@ class WrapperMain: | ||||
|     def zero_to_one_float(value): | ||||
|         value = float(value) | ||||
|         if value < 0.0 or value > 1.0: | ||||
|             raise argparse.ArgumentTypeError(f'{value} is not between 0.0 and 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') | ||||
|         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 | ||||
|         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"]}' | ||||
|         os.environ["PATH"] += f';{upscaler.ffmpeg_settings["ffmpeg_path"]}' | ||||
| 
 | ||||
|     def set_scale_ratio(self, scale_ratio: float): | ||||
|         self.driver_settings['zoomFactor'] = scale_ratio | ||||
|         self.driver_settings["zoomFactor"] = scale_ratio | ||||
| 
 | ||||
|     def upscale(self, input_file, output_file): | ||||
|         """This is the core function for WAIFU2X class | ||||
| @ -98,33 +99,33 @@ class WrapperMain: | ||||
| 
 | ||||
|         # 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) | ||||
|         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 | ||||
|         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) | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -132,6 +133,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
| @ -27,17 +27,21 @@ class Ffmpeg: | ||||
|     and inserting audio tracks to videos. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, ffmpeg_settings, extracted_frame_format='png'): | ||||
|     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' | ||||
|         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'] | ||||
|         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 | ||||
| @ -48,12 +52,7 @@ class Ffmpeg: | ||||
|         Returns: | ||||
|             dictionary -- JSON dict of all pixel formats to bit depth | ||||
|         """ | ||||
|         execute = [ | ||||
|             self.ffmpeg_probe_binary, | ||||
|             '-v', | ||||
|             'quiet', | ||||
|             '-pix_fmts' | ||||
|         ] | ||||
|         execute = [self.ffmpeg_probe_binary, "-v", "quiet", "-pix_fmts"] | ||||
| 
 | ||||
|         # turn elements into str | ||||
|         execute = [str(e) for e in execute] | ||||
| @ -64,9 +63,15 @@ class Ffmpeg: | ||||
|         pixel_formats = {} | ||||
| 
 | ||||
|         # record all pixel formats into dictionary | ||||
|         for line in subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout.decode().split('\n'): | ||||
|         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]) | ||||
|                 pixel_formats[" ".join(line.split()).split()[1]] = int( | ||||
|                     " ".join(line.split()).split()[3] | ||||
|                 ) | ||||
|             except (IndexError, ValueError): | ||||
|                 pass | ||||
| 
 | ||||
| @ -88,23 +93,27 @@ class Ffmpeg: | ||||
| 
 | ||||
|         execute = [ | ||||
|             self.ffmpeg_probe_binary, | ||||
|             '-v', | ||||
|             'quiet', | ||||
|             '-count_frames', | ||||
|             '-select_streams', | ||||
|             f'v:{video_stream_index}', | ||||
|             '-show_entries', | ||||
|             'stream=nb_read_frames', | ||||
|             '-of', | ||||
|             'default=nokey=1:noprint_wrappers=1', | ||||
|             input_file | ||||
|             "-v", | ||||
|             "quiet", | ||||
|             "-count_frames", | ||||
|             "-select_streams", | ||||
|             f"v:{video_stream_index}", | ||||
|             "-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()) | ||||
|         return int( | ||||
|             subprocess.run(execute, check=True, stdout=subprocess.PIPE) | ||||
|             .stdout.decode() | ||||
|             .strip() | ||||
|         ) | ||||
| 
 | ||||
|     def probe_file_info(self, input_video): | ||||
|         """Gets input video information | ||||
| @ -123,14 +132,14 @@ class Ffmpeg: | ||||
|         # 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 | ||||
|             "-v", | ||||
|             "quiet", | ||||
|             "-print_format", | ||||
|             "json", | ||||
|             "-show_format", | ||||
|             "-show_streams", | ||||
|             "-i", | ||||
|             input_video, | ||||
|         ] | ||||
| 
 | ||||
|         # turn elements into str | ||||
| @ -138,37 +147,38 @@ class Ffmpeg: | ||||
| 
 | ||||
|         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')) | ||||
|         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 | ||||
|         ] | ||||
|         """extract frames from video or GIF file""" | ||||
|         execute = [self.ffmpeg_binary] | ||||
| 
 | ||||
|         # load general options | ||||
|         execute.extend(self._read_configuration(phase='extract_frames')) | ||||
|         execute.extend(self._read_configuration(phase="extract_frames")) | ||||
| 
 | ||||
|         # load input_options | ||||
|         execute.extend(self._read_configuration(phase='extract_frames', section='input_options')) | ||||
|         execute.extend( | ||||
|             self._read_configuration(phase="extract_frames", section="input_options") | ||||
|         ) | ||||
| 
 | ||||
|         # specify input file | ||||
|         execute.extend([ | ||||
|             '-i', | ||||
|             input_file | ||||
|         ]) | ||||
|         execute.extend(["-i", input_file]) | ||||
| 
 | ||||
|         # load output options | ||||
|         execute.extend(self._read_configuration(phase='extract_frames', section='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}' | ||||
|         execute.extend( | ||||
|             [ | ||||
|                 extracted_frames | ||||
|                 / f"extracted_%0d.{self.extracted_frame_format}" | ||||
|                 # extracted_frames / f'frame_%06d.{self.extracted_frame_format}' | ||||
|         ]) | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
|         return self._execute(execute) | ||||
| 
 | ||||
|     def assemble_video(self, framerate, upscaled_frames): | ||||
|         """Converts images into videos | ||||
| @ -182,43 +192,50 @@ class Ffmpeg: | ||||
|         """ | ||||
|         execute = [ | ||||
|             self.ffmpeg_binary, | ||||
|             '-r', | ||||
|             "-r", | ||||
|             str(framerate) | ||||
|             # '-s', | ||||
|             # resolution | ||||
|         ] | ||||
| 
 | ||||
|         # read other options | ||||
|         execute.extend(self._read_configuration(phase='assemble_video')) | ||||
|         execute.extend(self._read_configuration(phase="assemble_video")) | ||||
| 
 | ||||
|         # read input options | ||||
|         execute.extend(self._read_configuration(phase='assemble_video', section='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) | ||||
| 
 | ||||
|         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))) | ||||
|             (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}' | ||||
|         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')) | ||||
|         execute.extend( | ||||
|             self._read_configuration(phase="assemble_video", section="output_options") | ||||
|         ) | ||||
| 
 | ||||
|         # specify output file location | ||||
|         execute.extend([ | ||||
|             upscaled_frames / self.intermediate_file_name | ||||
|         ]) | ||||
|         execute.extend([upscaled_frames / self.intermediate_file_name]) | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
|         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 | ||||
| @ -228,37 +245,37 @@ class Ffmpeg: | ||||
|             output_video {string} -- output video file path | ||||
|             upscaled_frames {string} -- directory containing upscaled frames | ||||
|         """ | ||||
|         execute = [ | ||||
|             self.ffmpeg_binary | ||||
|         ] | ||||
|         execute = [self.ffmpeg_binary] | ||||
| 
 | ||||
|         # load general options | ||||
|         execute.extend(self._read_configuration(phase='migrate_streams')) | ||||
|         execute.extend(self._read_configuration(phase="migrate_streams")) | ||||
| 
 | ||||
|         # load input options | ||||
|         execute.extend(self._read_configuration(phase='migrate_streams', section='input_options')) | ||||
|         execute.extend( | ||||
|             self._read_configuration(phase="migrate_streams", section="input_options") | ||||
|         ) | ||||
| 
 | ||||
|         # load input file names | ||||
|         execute.extend([ | ||||
| 
 | ||||
|         execute.extend( | ||||
|             [ | ||||
|                 # input 1: upscaled intermediate file without sound | ||||
|             '-i', | ||||
|                 "-i", | ||||
|                 upscaled_frames / self.intermediate_file_name, | ||||
| 
 | ||||
|                 # input 2: original video with streams to copy over | ||||
|             '-i', | ||||
|             input_video | ||||
|         ]) | ||||
|                 "-i", | ||||
|                 input_video, | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|         # load output options | ||||
|         execute.extend(self._read_configuration(phase='migrate_streams', section='output_options')) | ||||
|         execute.extend( | ||||
|             self._read_configuration(phase="migrate_streams", section="output_options") | ||||
|         ) | ||||
| 
 | ||||
|         # load output video path | ||||
|         execute.extend([ | ||||
|             output_video | ||||
|         ]) | ||||
|         execute.extend([output_video]) | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
|         return self._execute(execute) | ||||
| 
 | ||||
|     def _read_configuration(self, phase, section=None): | ||||
|         """read configuration from JSON | ||||
| @ -290,7 +307,12 @@ class Ffmpeg: | ||||
|                 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 == '': | ||||
|             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 | ||||
|  | ||||
| @ -19,30 +19,37 @@ from avalon_framework import Avalon | ||||
| 
 | ||||
| 
 | ||||
| class Gifski: | ||||
| 
 | ||||
|     def __init__(self, gifski_settings): | ||||
|         self.gifski_settings = gifski_settings | ||||
| 
 | ||||
|     def make_gif(self, upscaled_frames: pathlib.Path, output_path: pathlib.Path, framerate: float, extracted_frame_format: str, output_width: int, output_height: int) -> subprocess.Popen: | ||||
|     def make_gif( | ||||
|         self, | ||||
|         upscaled_frames: pathlib.Path, | ||||
|         output_path: pathlib.Path, | ||||
|         framerate: float, | ||||
|         extracted_frame_format: str, | ||||
|         output_width: int, | ||||
|         output_height: int, | ||||
|     ) -> subprocess.Popen: | ||||
|         execute = [ | ||||
|             self.gifski_settings['gifski_path'], | ||||
|             '-o', | ||||
|             self.gifski_settings["gifski_path"], | ||||
|             "-o", | ||||
|             output_path, | ||||
|             '--fps', | ||||
|             "--fps", | ||||
|             int(round(framerate, 0)), | ||||
|             '--width', | ||||
|             "--width", | ||||
|             output_width, | ||||
|             '--height', | ||||
|             output_height | ||||
|             "--height", | ||||
|             output_height, | ||||
|         ] | ||||
| 
 | ||||
|         # load configurations from config file | ||||
|         execute.extend(self._load_configuration()) | ||||
| 
 | ||||
|         # append frames location | ||||
|         execute.extend([upscaled_frames / f'extracted_*.{extracted_frame_format}']) | ||||
|         execute.extend([upscaled_frames / f"extracted_*.{extracted_frame_format}"]) | ||||
| 
 | ||||
|         return(self._execute(execute)) | ||||
|         return self._execute(execute) | ||||
| 
 | ||||
|     def _load_configuration(self): | ||||
| 
 | ||||
| @ -53,13 +60,13 @@ class Gifski: | ||||
|             value = self.gifski_settings[key] | ||||
| 
 | ||||
|             # null or None means that leave this option out (keep default) | ||||
|             if key == 'gifski_path' or value is None or value is False: | ||||
|             if key == "gifski_path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     configuration.append(f'-{key}') | ||||
|                     configuration.append(f"-{key}") | ||||
|                 else: | ||||
|                     configuration.append(f'--{key}') | ||||
|                     configuration.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -70,6 +77,6 @@ class Gifski: | ||||
|         # turn all list elements into string to avoid errors | ||||
|         execute = [str(e) for e in execute] | ||||
| 
 | ||||
|         Avalon.debug_info(f'Executing: {execute}') | ||||
|         Avalon.debug_info(f"Executing: {execute}") | ||||
| 
 | ||||
|         return subprocess.Popen(execute, stdout=sys.stdout, stderr=sys.stderr) | ||||
|  | ||||
| @ -38,28 +38,32 @@ class WrapperMain: | ||||
| 
 | ||||
|     @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)') | ||||
|         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() | ||||
|         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) | ||||
|         self.driver_settings["s"] = int(scale_ratio) | ||||
| 
 | ||||
|     def upscale(self, input_directory, output_directory): | ||||
|         """This is the core function for RealSR NCNN Vulkan class | ||||
| @ -72,33 +76,33 @@ class WrapperMain: | ||||
| 
 | ||||
|         # 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) | ||||
|         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 | ||||
|         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) | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -106,6 +110,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
| @ -38,29 +38,33 @@ class WrapperMain: | ||||
| 
 | ||||
|     @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)') | ||||
|         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() | ||||
|         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) | ||||
|         self.driver_settings["s"] = int(scale_ratio) | ||||
| 
 | ||||
|     def upscale(self, input_directory, output_directory): | ||||
|         """This is the core function for SRMD ncnn Vulkan class | ||||
| @ -73,33 +77,33 @@ class WrapperMain: | ||||
| 
 | ||||
|         # 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) | ||||
|         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 | ||||
|         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) | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -107,6 +111,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
| @ -37,77 +37,78 @@ class WrapperMain: | ||||
| 
 | ||||
|     @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') | ||||
|         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 | ||||
|         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 | ||||
|         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 | ||||
|         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 | ||||
|         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 | ||||
|         """ | ||||
|         """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) | ||||
|         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 | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -115,6 +116,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
| @ -37,42 +37,44 @@ class WrapperMain: | ||||
| 
 | ||||
|     @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.') | ||||
|         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() | ||||
|         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 | ||||
|         self.driver_settings["scale-ratio"] = scale_ratio | ||||
| 
 | ||||
|     def upscale(self, input_directory, output_directory): | ||||
|         """Waifu2x Converter Driver Upscaler | ||||
| @ -87,33 +89,35 @@ class WrapperMain: | ||||
| 
 | ||||
|         # 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) | ||||
|         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 | ||||
|         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' | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -121,6 +125,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
| @ -41,29 +41,33 @@ class WrapperMain: | ||||
| 
 | ||||
|     @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)') | ||||
|         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() | ||||
|         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) | ||||
|         self.driver_settings["s"] = int(scale_ratio) | ||||
| 
 | ||||
|     def upscale(self, input_directory, output_directory): | ||||
|         """This is the core function for waifu2x class | ||||
| @ -76,33 +80,33 @@ class WrapperMain: | ||||
| 
 | ||||
|         # 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) | ||||
|         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 | ||||
|         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) | ||||
|         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']] | ||||
|         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: | ||||
|             if key == "path" or value is None or value is False: | ||||
|                 continue | ||||
|             else: | ||||
|                 if len(key) == 1: | ||||
|                     execute.append(f'-{key}') | ||||
|                     execute.append(f"-{key}") | ||||
|                 else: | ||||
|                     execute.append(f'--{key}') | ||||
|                     execute.append(f"--{key}") | ||||
| 
 | ||||
|                 # true means key is an option | ||||
|                 if value is not True: | ||||
| @ -110,6 +114,8 @@ class WrapperMain: | ||||
| 
 | ||||
|         # return the Popen object of the new process created | ||||
|         self.print_lock.acquire() | ||||
|         Avalon.debug_info(f'[upscaler] Subprocess {os.getpid()} executing: {" ".join(execute)}') | ||||
|         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) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user