mirror of
				https://github.com/k4yt3x/video2x.git
				synced 2025-10-31 12:50:59 +01:00 
			
		
		
		
	added image and GIF upscale support
This commit is contained in:
		
							parent
							
								
									5cf3271aad
								
							
						
					
					
						commit
						e305d0188e
					
				| @ -3,6 +3,8 @@ colorama | |||||||
| patool | patool | ||||||
| psutil | psutil | ||||||
| pyqt5 | pyqt5 | ||||||
|  | python-magic | ||||||
|  | python-magic-bin | ||||||
| pyunpack | pyunpack | ||||||
| pyyaml | pyyaml | ||||||
| requests | requests | ||||||
|  | |||||||
							
								
								
									
										282
									
								
								src/upscaler.py
									
									
									
									
									
								
							
							
						
						
									
										282
									
								
								src/upscaler.py
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ | |||||||
| Name: Video2X Upscaler | Name: Video2X Upscaler | ||||||
| Author: K4YT3X | Author: K4YT3X | ||||||
| Date Created: December 10, 2018 | Date Created: December 10, 2018 | ||||||
| Last Modified: May 10, 2020 | Last Modified: May 11, 2020 | ||||||
| 
 | 
 | ||||||
| Description: This file contains the Upscaler class. Each | Description: This file contains the Upscaler class. Each | ||||||
| instance of the Upscaler class is an upscaler on an image or | instance of the Upscaler class is an upscaler on an image or | ||||||
| @ -16,6 +16,7 @@ from exceptions import * | |||||||
| from image_cleaner import ImageCleaner | from image_cleaner import ImageCleaner | ||||||
| from progress_monitor import ProgressMonitor | from progress_monitor import ProgressMonitor | ||||||
| from wrappers.ffmpeg import Ffmpeg | from wrappers.ffmpeg import Ffmpeg | ||||||
|  | from wrappers.gifski import Gifski | ||||||
| 
 | 
 | ||||||
| # built-in imports | # built-in imports | ||||||
| from fractions import Fraction | from fractions import Fraction | ||||||
| @ -24,7 +25,6 @@ import copy | |||||||
| import gettext | import gettext | ||||||
| import importlib | import importlib | ||||||
| import locale | import locale | ||||||
| import os |  | ||||||
| import pathlib | import pathlib | ||||||
| import queue | import queue | ||||||
| import re | import re | ||||||
| @ -36,6 +36,7 @@ import traceback | |||||||
| 
 | 
 | ||||||
| # third-party imports | # third-party imports | ||||||
| from avalon_framework import Avalon | from avalon_framework import Avalon | ||||||
|  | import magic | ||||||
| 
 | 
 | ||||||
| # internationalization constants | # internationalization constants | ||||||
| DOMAIN = 'video2x' | DOMAIN = 'video2x' | ||||||
| @ -67,12 +68,13 @@ class Upscaler: | |||||||
|         ArgumentError -- if argument is not valid |         ArgumentError -- if argument is not valid | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings): |     def __init__(self, input_path, output_path, driver_settings, ffmpeg_settings, gifski_settings): | ||||||
|         # mandatory arguments |         # mandatory arguments | ||||||
|         self.input = input_path |         self.input = input_path | ||||||
|         self.output = output_path |         self.output = output_path | ||||||
|         self.driver_settings = driver_settings |         self.driver_settings = driver_settings | ||||||
|         self.ffmpeg_settings = ffmpeg_settings |         self.ffmpeg_settings = ffmpeg_settings | ||||||
|  |         self.gifski_settings = gifski_settings | ||||||
| 
 | 
 | ||||||
|         # optional arguments |         # optional arguments | ||||||
|         self.driver = 'waifu2x_caffe' |         self.driver = 'waifu2x_caffe' | ||||||
| @ -86,9 +88,9 @@ class Upscaler: | |||||||
|         self.running = False |         self.running = False | ||||||
|         self.total_frames_upscaled = 0 |         self.total_frames_upscaled = 0 | ||||||
|         self.total_frames = 0 |         self.total_frames = 0 | ||||||
|         self.total_videos = 0 |         self.total_files = 0 | ||||||
|         self.total_processed = 0 |         self.total_processed = 0 | ||||||
|         self.current_input_video = pathlib.Path() |         self.current_input_file = pathlib.Path() | ||||||
|         self.last_frame_upscaled = pathlib.Path() |         self.last_frame_upscaled = pathlib.Path() | ||||||
| 
 | 
 | ||||||
|     def create_temp_directories(self): |     def create_temp_directories(self): | ||||||
| @ -154,10 +156,10 @@ class Upscaler: | |||||||
|                 Avalon.error(_('Input and output path type mismatch')) |                 Avalon.error(_('Input and output path type mismatch')) | ||||||
|                 Avalon.error(_('Input is single file but output is directory')) |                 Avalon.error(_('Input is single file but output is directory')) | ||||||
|                 raise ArgumentError('input output path type mismatch') |                 raise ArgumentError('input output path type mismatch') | ||||||
|             if not re.search(r'.*\..*$', str(self.output)): |             if self.output.suffix == '': | ||||||
|                 Avalon.error(_('No suffix found in output file path')) |                 Avalon.error(_('No suffix found in output file path')) | ||||||
|                 Avalon.error(_('Suffix must be specified for FFmpeg')) |                 Avalon.error(_('Suffix must be specified')) | ||||||
|                 raise ArgumentError('no output video suffix specified') |                 raise ArgumentError('no output file suffix specified') | ||||||
| 
 | 
 | ||||||
|         # if input is a directory |         # if input is a directory | ||||||
|         elif self.input.is_dir(): |         elif self.input.is_dir(): | ||||||
| @ -238,6 +240,14 @@ class Upscaler: | |||||||
|                 self.driver_settings['scale_width'] = None |                 self.driver_settings['scale_width'] = None | ||||||
|                 self.driver_settings['scale_height'] = None |                 self.driver_settings['scale_height'] = None | ||||||
| 
 | 
 | ||||||
|  |         # temporary file type check for Anime4KCPP | ||||||
|  |         # it doesn't support GIF processing yet | ||||||
|  |         if self.driver == 'anime4kcpp': | ||||||
|  |             for task in self.processing_queue.queue: | ||||||
|  |                 if task[0].suffix.lower() == '.gif': | ||||||
|  |                     Avalon.error(_('Anime4KCPP doesn\'t yet support GIF processing')) | ||||||
|  |                     raise AttributeError('Anime4KCPP doesn\'t yet support GIF file processing') | ||||||
|  | 
 | ||||||
|     def _upscale_frames(self): |     def _upscale_frames(self): | ||||||
|         """ Upscale video frames with waifu2x-caffe |         """ Upscale video frames with waifu2x-caffe | ||||||
| 
 | 
 | ||||||
| @ -393,9 +403,8 @@ class Upscaler: | |||||||
|         # load options from upscaler class into driver settings |         # load options from upscaler class into driver settings | ||||||
|         self.driver_object.load_configurations(self) |         self.driver_object.load_configurations(self) | ||||||
| 
 | 
 | ||||||
|         # parse arguments for waifu2x |         # initialize FFmpeg object | ||||||
|         # check argument sanity |         self.ffmpeg_object = Ffmpeg(self.ffmpeg_settings, self.image_format) | ||||||
|         self._check_arguments() |  | ||||||
| 
 | 
 | ||||||
|         # define processing queue |         # define processing queue | ||||||
|         self.processing_queue = queue.Queue() |         self.processing_queue = queue.Queue() | ||||||
| @ -408,17 +417,17 @@ class Upscaler: | |||||||
|             for input_path in self.input: |             for input_path in self.input: | ||||||
| 
 | 
 | ||||||
|                 if input_path.is_file(): |                 if input_path.is_file(): | ||||||
|                     output_video = self.output / input_path.name |                     output_path = self.output / input_path.name | ||||||
|                     self.processing_queue.put((input_path.absolute(), output_video.absolute())) |                     self.processing_queue.put((input_path.absolute(), output_path.absolute())) | ||||||
| 
 | 
 | ||||||
|                 elif input_path.is_dir(): |                 elif input_path.is_dir(): | ||||||
|                     for input_video in [f for f in input_path.iterdir() if f.is_file()]: |                     for input_path in [f for f in input_path.iterdir() if f.is_file()]: | ||||||
|                         output_video = self.output / input_video.name |                         output_path = self.output / input_path.name | ||||||
|                         self.processing_queue.put((input_video.absolute(), output_video.absolute())) |                         self.processing_queue.put((input_path.absolute(), output_path.absolute())) | ||||||
| 
 | 
 | ||||||
|         # if input specified is single file |         # if input specified is single file | ||||||
|         elif self.input.is_file(): |         elif self.input.is_file(): | ||||||
|             Avalon.info(_('Upscaling single video file: {}').format(self.input)) |             Avalon.info(_('Upscaling single file: {}').format(self.input)) | ||||||
|             self.processing_queue.put((self.input.absolute(), self.output.absolute())) |             self.processing_queue.put((self.input.absolute(), self.output.absolute())) | ||||||
| 
 | 
 | ||||||
|         # if input specified is a directory |         # if input specified is a directory | ||||||
| @ -426,99 +435,147 @@ class Upscaler: | |||||||
| 
 | 
 | ||||||
|             # make output directory if it doesn't exist |             # make output directory if it doesn't exist | ||||||
|             self.output.mkdir(parents=True, exist_ok=True) |             self.output.mkdir(parents=True, exist_ok=True) | ||||||
|             for input_video in [f for f in self.input.iterdir() if f.is_file()]: |             for input_path in [f for f in self.input.iterdir() if f.is_file()]: | ||||||
|                 output_video = self.output / input_video.name |                 output_path = self.output / input_path.name | ||||||
|                 self.processing_queue.put((input_video.absolute(), output_video.absolute())) |                 self.processing_queue.put((input_path.absolute(), output_path.absolute())) | ||||||
| 
 | 
 | ||||||
|         # record video count for external calls |         # check argument sanity before running | ||||||
|         self.total_videos = self.processing_queue.qsize() |         self._check_arguments() | ||||||
| 
 | 
 | ||||||
|         while not self.processing_queue.empty(): |         # record file count for external calls | ||||||
|             self.current_input_video, output_video = self.processing_queue.get() |         self.total_files = self.processing_queue.qsize() | ||||||
|             # drivers that have native support for video processing |  | ||||||
|             if self.driver == 'anime4kcpp': |  | ||||||
|                 # append FFmpeg path to the end of PATH |  | ||||||
|                 # Anime4KCPP will then use FFmpeg to migrate audio tracks |  | ||||||
|                 os.environ['PATH'] += f';{self.ffmpeg_settings["ffmpeg_path"]}' |  | ||||||
|                 Avalon.info(_('Starting to upscale extracted images')) |  | ||||||
| 
 | 
 | ||||||
|                 # run Anime4KCPP |         try: | ||||||
|                 self.process_pool.append(self.driver_object.upscale(self.current_input_video, output_video)) |             while not self.processing_queue.empty(): | ||||||
|                 self._wait() |  | ||||||
|                 Avalon.info(_('Upscaling completed')) |  | ||||||
| 
 | 
 | ||||||
|             else: |                 # reset current processing progress for new job | ||||||
|                 try: |                 self.total_frames_upscaled = 0 | ||||||
|                     self.create_temp_directories() |                 self.total_frames = 0 | ||||||
| 
 | 
 | ||||||
|                     # initialize objects for ffmpeg and waifu2x-caffe |                 # get new job from queue | ||||||
|                     fm = Ffmpeg(self.ffmpeg_settings, self.image_format) |                 self.current_input_file, output_path = self.processing_queue.get() | ||||||
| 
 | 
 | ||||||
|                     Avalon.info(_('Reading video information')) |                 # get file type | ||||||
|                     video_info = fm.get_video_info(self.current_input_video) |                 input_file_mime_type = magic.from_file(str(self.current_input_file.absolute()), mime=True) | ||||||
|                     # analyze original video with FFprobe and retrieve framerate |                 input_file_type = input_file_mime_type.split('/')[0] | ||||||
|                     # width, height = info['streams'][0]['width'], info['streams'][0]['height'] |                 input_file_subtype = input_file_mime_type.split('/')[1] | ||||||
| 
 | 
 | ||||||
|                     # find index of video stream |                 # start handling input | ||||||
|                     video_stream_index = None |                 # if input file is a static image | ||||||
|                     for stream in video_info['streams']: |                 if input_file_type == 'image' and input_file_subtype != 'gif': | ||||||
|                         if stream['codec_type'] == 'video': |                     Avalon.info(_('Starting to upscale image')) | ||||||
|                             video_stream_index = stream['index'] |                     self.process_pool.append(self.driver_object.upscale(self.current_input_file, output_path)) | ||||||
|                             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') |  | ||||||
| 
 |  | ||||||
|                     # extract frames from video |  | ||||||
|                     self.process_pool.append((fm.extract_frames(self.current_input_video, self.extracted_frames))) |  | ||||||
|                     self._wait() |                     self._wait() | ||||||
| 
 |  | ||||||
|                     # get average frame rate of video stream |  | ||||||
|                     framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate'])) |  | ||||||
|                     fm.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] |  | ||||||
| 
 |  | ||||||
|                     if self.driver == 'waifu2x_caffe': |  | ||||||
|                         # get a dict of all pixel formats and corresponding bit depth |  | ||||||
|                         pixel_formats = fm.get_pixel_formats() |  | ||||||
| 
 |  | ||||||
|                         # try getting pixel format's corresponding bti depth |  | ||||||
|                         try: |  | ||||||
|                             self.driver_settings['output_depth'] = pixel_formats[fm.pixel_format] |  | ||||||
|                         except KeyError: |  | ||||||
|                             Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format)) |  | ||||||
|                             raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}') |  | ||||||
| 
 |  | ||||||
|                     Avalon.info(_('Framerate: {}').format(framerate)) |  | ||||||
| 
 |  | ||||||
|                     # width/height will be coded width/height x upscale factor |  | ||||||
|                     original_width = video_info['streams'][video_stream_index]['width'] |  | ||||||
|                     original_height = video_info['streams'][video_stream_index]['height'] |  | ||||||
|                     scale_width = int(self.scale_ratio * original_width) |  | ||||||
|                     scale_height = int(self.scale_ratio * original_height) |  | ||||||
| 
 |  | ||||||
|                     # upscale images one by one using waifu2x |  | ||||||
|                     Avalon.info(_('Starting to upscale extracted images')) |  | ||||||
|                     self._upscale_frames() |  | ||||||
|                     Avalon.info(_('Upscaling completed')) |                     Avalon.info(_('Upscaling completed')) | ||||||
| 
 | 
 | ||||||
|                     # frames to Video |                     # static images don't require GIF or video encoding | ||||||
|                     Avalon.info(_('Converting extracted frames into video')) |                     # go to the next task | ||||||
|  |                     self.processing_queue.task_done() | ||||||
|  |                     self.total_processed += 1 | ||||||
|  |                     continue | ||||||
| 
 | 
 | ||||||
|                     # use user defined output size |                 # if input file is a image/gif file or a video | ||||||
|                     self.process_pool.append(fm.assemble_video(framerate, |                 elif input_file_mime_type == 'image/gif' or input_file_type == 'video': | ||||||
|                                                                f'{scale_width}x{scale_height}', | 
 | ||||||
|                                                                self.upscaled_frames)) |                     # drivers that have native support for video processing | ||||||
|  |                     if input_file_type == 'video' and self.driver == 'anime4kcpp': | ||||||
|  |                         Avalon.info(_('Starting to upscale video with Anime4KCPP')) | ||||||
|  |                         # enable video processing mode for Anime4KCPP | ||||||
|  |                         self.driver_settings['videoMode'] = True | ||||||
|  |                         self.process_pool.append(self.driver_object.upscale(self.current_input_file, output_path)) | ||||||
|  |                         self._wait() | ||||||
|  |                         Avalon.info(_('Upscaling completed')) | ||||||
|  | 
 | ||||||
|  |                     else: | ||||||
|  |                         self.create_temp_directories() | ||||||
|  | 
 | ||||||
|  |                         # get video information JSON using FFprobe | ||||||
|  |                         Avalon.info(_('Reading video information')) | ||||||
|  |                         video_info = self.ffmpeg_object.probe_file_info(self.current_input_file) | ||||||
|  |                         # analyze original video with FFprobe and retrieve framerate | ||||||
|  |                         # width, height = info['streams'][0]['width'], info['streams'][0]['height'] | ||||||
|  | 
 | ||||||
|  |                         # find index of video stream | ||||||
|  |                         video_stream_index = None | ||||||
|  |                         for stream in video_info['streams']: | ||||||
|  |                             if stream['codec_type'] == 'video': | ||||||
|  |                                 video_stream_index = stream['index'] | ||||||
|  |                                 break | ||||||
|  | 
 | ||||||
|  |                         # exit if no video stream found | ||||||
|  |                         if video_stream_index is None: | ||||||
|  |                             Avalon.error(_('Aborting: No video stream found')) | ||||||
|  |                             raise StreamNotFoundError('no video stream found') | ||||||
|  | 
 | ||||||
|  |                         # get average frame rate of video stream | ||||||
|  |                         framerate = float(Fraction(video_info['streams'][video_stream_index]['r_frame_rate'])) | ||||||
|  |                         # self.ffmpeg_object.pixel_format = video_info['streams'][video_stream_index]['pix_fmt'] | ||||||
|  | 
 | ||||||
|  |                         # extract frames from 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': | ||||||
|  |                             # 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] | ||||||
|  |                             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.info(_('Framerate: {}').format(framerate)) | ||||||
|  | 
 | ||||||
|  |                         # width/height will be coded width/height x upscale factor | ||||||
|  |                         # original_width = video_info['streams'][video_stream_index]['width'] | ||||||
|  |                         # original_height = video_info['streams'][video_stream_index]['height'] | ||||||
|  |                         # scale_width = int(self.scale_ratio * original_width) | ||||||
|  |                         # scale_height = int(self.scale_ratio * original_height) | ||||||
|  | 
 | ||||||
|  |                         # upscale images one by one using waifu2x | ||||||
|  |                         Avalon.info(_('Starting to upscale extracted frames')) | ||||||
|  |                         self._upscale_frames() | ||||||
|  |                         Avalon.info(_('Upscaling completed')) | ||||||
|  | 
 | ||||||
|  |                 # if file is none of: image, image/gif, video | ||||||
|  |                 # skip to the next task | ||||||
|  |                 else: | ||||||
|  |                     Avalon.error(_('File {} ({}) neither an image of a video').format(self.current_input_file, input_file_mime_type)) | ||||||
|  |                     Avalon.warning(_('Skipping this file')) | ||||||
|  |                     self.processing_queue.task_done() | ||||||
|  |                     self.total_processed += 1 | ||||||
|  |                     continue | ||||||
|  | 
 | ||||||
|  |                 # start handling output | ||||||
|  |                 # output can be either GIF or video | ||||||
|  | 
 | ||||||
|  |                 # if the desired output is gif file | ||||||
|  |                 if output_path.suffix.lower() == '.gif': | ||||||
|  |                     Avalon.info(_('Converting extracted frames into GIF image')) | ||||||
|  |                     gifski_object = Gifski(self.gifski_settings) | ||||||
|  |                     self.process_pool.append(gifski_object.make_gif(self.upscaled_frames, output_path, framerate, self.image_format)) | ||||||
|  |                     self._wait() | ||||||
|  |                     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)) | ||||||
|  |                     # f'{scale_width}x{scale_height}', | ||||||
|                     self._wait() |                     self._wait() | ||||||
|                     Avalon.info(_('Conversion completed')) |                     Avalon.info(_('Conversion completed')) | ||||||
| 
 | 
 | ||||||
|                     try: |                     try: | ||||||
|                         # migrate audio tracks and subtitles |                         # migrate audio tracks and subtitles | ||||||
|                         Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video')) |                         Avalon.info(_('Migrating audio, subtitles and other streams to upscaled video')) | ||||||
|                         self.process_pool.append(fm.migrate_streams(self.current_input_video, |                         self.process_pool.append(self.ffmpeg_object.migrate_streams(self.current_input_file, | ||||||
|                                                                     output_video, |                                                                                     output_path, | ||||||
|                                                                     self.upscaled_frames)) |                                                                                     self.upscaled_frames)) | ||||||
|                         self._wait() |                         self._wait() | ||||||
| 
 | 
 | ||||||
|                     # if failed to copy streams |                     # if failed to copy streams | ||||||
| @ -527,29 +584,32 @@ class Upscaler: | |||||||
|                         Avalon.error(_('Failed to migrate streams')) |                         Avalon.error(_('Failed to migrate streams')) | ||||||
|                         Avalon.warning(_('Trying to output video without additional streams')) |                         Avalon.warning(_('Trying to output video without additional streams')) | ||||||
| 
 | 
 | ||||||
|                         # construct output file path |                         if input_file_mime_type == 'image/gif': | ||||||
|                         output_video_path = output_video.parent / f'{output_video.stem}{fm.intermediate_file_name.suffix}' |                             (self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).replace(output_path) | ||||||
| 
 | 
 | ||||||
|                         # if output file already exists, cancel |  | ||||||
|                         if output_video_path.exists(): |  | ||||||
|                             Avalon.error(_('Output video file exists, aborting')) |  | ||||||
| 
 |  | ||||||
|                         # otherwise, rename intermediate file to the output file |  | ||||||
|                         else: |                         else: | ||||||
|                             Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute())) |                             # construct output file path | ||||||
|                             (self.upscaled_frames / fm.intermediate_file_name).rename(output_video_path) |                             output_video_path = output_path.parent / f'{output_path.stem}{self.ffmpeg_object.intermediate_file_name.suffix}' | ||||||
| 
 | 
 | ||||||
|                     # destroy temp directories |                             # if output file already exists, cancel | ||||||
|                     self.cleanup_temp_directories() |                             if output_video_path.exists(): | ||||||
|  |                                 Avalon.error(_('Output video file exists, aborting')) | ||||||
| 
 | 
 | ||||||
|                 except (Exception, KeyboardInterrupt, SystemExit) as e: |                             # otherwise, rename intermediate file to the output file | ||||||
|                     with contextlib.suppress(ValueError): |                             else: | ||||||
|                         self.cleanup_temp_directories() |                                 Avalon.info(_('Writing intermediate file to: {}').format(output_video_path.absolute())) | ||||||
|                         self.running = False |                                 (self.upscaled_frames / self.ffmpeg_object.intermediate_file_name).rename(output_video_path) | ||||||
|                     raise e |  | ||||||
| 
 | 
 | ||||||
|             # increment total number of videos processed |                 # increment total number of files processed | ||||||
|             self.total_processed += 1 |                 self.cleanup_temp_directories() | ||||||
|  |                 self.processing_queue.task_done() | ||||||
|  |                 self.total_processed += 1 | ||||||
|  | 
 | ||||||
|  |         except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||||
|  |             with contextlib.suppress(ValueError): | ||||||
|  |                 self.cleanup_temp_directories() | ||||||
|  |                 self.running = False | ||||||
|  |             raise e | ||||||
| 
 | 
 | ||||||
|         # signal upscaling completion |         # signal upscaling completion | ||||||
|         self.running = False |         self.running = False | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ __      __  _       _                  ___   __   __ | |||||||
| Name: Video2X Controller | Name: Video2X Controller | ||||||
| Creator: K4YT3X | Creator: K4YT3X | ||||||
| Date Created: Feb 24, 2018 | Date Created: Feb 24, 2018 | ||||||
| Last Modified: May 10, 2020 | Last Modified: May 11, 2020 | ||||||
| 
 | 
 | ||||||
| Editor: BrianPetkovsek | Editor: BrianPetkovsek | ||||||
| Last Modified: June 17, 2019 | Last Modified: June 17, 2019 | ||||||
| @ -181,6 +181,10 @@ driver_settings['path'] = os.path.expandvars(driver_settings['path']) | |||||||
| ffmpeg_settings = config['ffmpeg'] | ffmpeg_settings = config['ffmpeg'] | ||||||
| ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path']) | ffmpeg_settings['ffmpeg_path'] = os.path.expandvars(ffmpeg_settings['ffmpeg_path']) | ||||||
| 
 | 
 | ||||||
|  | # read Gifski configuration | ||||||
|  | gifski_settings = config['gifski'] | ||||||
|  | gifski_settings['gifski_path'] = os.path.expandvars(gifski_settings['gifski_path']) | ||||||
|  | 
 | ||||||
| # load video2x settings | # load video2x settings | ||||||
| image_format = config['video2x']['image_format'].lower() | image_format = config['video2x']['image_format'].lower() | ||||||
| preserve_frames = config['video2x']['preserve_frames'] | preserve_frames = config['video2x']['preserve_frames'] | ||||||
| @ -213,7 +217,8 @@ try: | |||||||
|     upscaler = Upscaler(input_path=video2x_args.input, |     upscaler = Upscaler(input_path=video2x_args.input, | ||||||
|                         output_path=video2x_args.output, |                         output_path=video2x_args.output, | ||||||
|                         driver_settings=driver_settings, |                         driver_settings=driver_settings, | ||||||
|                         ffmpeg_settings=ffmpeg_settings) |                         ffmpeg_settings=ffmpeg_settings, | ||||||
|  |                         gifski_settings=gifski_settings) | ||||||
| 
 | 
 | ||||||
|     # set upscaler optional options |     # set upscaler optional options | ||||||
|     upscaler.driver = video2x_args.driver |     upscaler.driver = video2x_args.driver | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| # Name: Video2X Configuration File | # Name: Video2X Configuration File | ||||||
| # Creator: K4YT3X | # Creator: K4YT3X | ||||||
| # Date Created: October 23, 2018 | # Date Created: October 23, 2018 | ||||||
| # Last Modified: May 9, 2020 | # Last Modified: May 11, 2020 | ||||||
| # Values here are the default values. Change the value here to | # Values here are the default values. Change the value here to | ||||||
| #   save the default value permanently. | #   save the default value permanently. | ||||||
| # Items commented out are parameters irrelevant to this context | # Items commented out are parameters irrelevant to this context | ||||||
| @ -87,7 +87,7 @@ anime4kcpp: | |||||||
|   zoomFactor: 2.0 # zoom factor for resizing (double [=2]) |   zoomFactor: 2.0 # zoom factor for resizing (double [=2]) | ||||||
|   threads: 16 # Threads count for video processing (unsigned int [=16]) |   threads: 16 # Threads count for video processing (unsigned int [=16]) | ||||||
|   fastMode: false # Faster but maybe low quality |   fastMode: false # Faster but maybe low quality | ||||||
|   videoMode: true # Video process |   videoMode: false # Video process | ||||||
|   preview: null # Preview image |   preview: null # Preview image | ||||||
|   preprocessing: False # Enable pre processing |   preprocessing: False # Enable pre processing | ||||||
|   postprocessing: False # Enable post processing |   postprocessing: False # Enable post processing | ||||||
| @ -101,9 +101,9 @@ anime4kcpp: | |||||||
| ffmpeg: | ffmpeg: | ||||||
|   ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg-latest-win64-static\bin' |   ffmpeg_path: '%LOCALAPPDATA%\video2x\ffmpeg-latest-win64-static\bin' | ||||||
|   intermediate_file_name: 'intermediate.mkv' |   intermediate_file_name: 'intermediate.mkv' | ||||||
|   # step 1: extract all frames from original video |   # step 1: extract all frames from original input | ||||||
|   # into temporary directory |   # into temporary directory | ||||||
|   video_to_frames: |   input_to_frames: | ||||||
|     output_options: |     output_options: | ||||||
|       '-qscale:v': null |       '-qscale:v': null | ||||||
|       '-pix_fmt': rgba64be |       '-pix_fmt': rgba64be | ||||||
| @ -138,6 +138,17 @@ ffmpeg: | |||||||
|       '-metadata': 'comment=Upscaled by Video2X' |       '-metadata': 'comment=Upscaled by Video2X' | ||||||
|     '-hwaccel': auto |     '-hwaccel': auto | ||||||
|     '-y': true |     '-y': true | ||||||
|  | gifski: | ||||||
|  |   gifski_path: '%LOCALAPPDATA%\video2x\gifski\win\gifski' | ||||||
|  |   # output: null # Destination file to write to | ||||||
|  |   # fps: 20 # Animation frames per second (for PNG frames only) [default: 20] | ||||||
|  |   fast: false # 3 times faster encoding, but 10% lower quality and bigger file | ||||||
|  |   quality: 100 # Lower quality may give smaller file | ||||||
|  |   width: null # Maximum width | ||||||
|  |   height: null # Maximum height (if width is also set) | ||||||
|  |   once: false # Do not loop the GIF | ||||||
|  |   nosort: false # Use files exactly in the order given, rather than sorted | ||||||
|  |   quiet: false # Do not show a progress bar | ||||||
| video2x: | video2x: | ||||||
|   video2x_cache_directory: null # default: %TEMP%\video2x |   video2x_cache_directory: null # default: %TEMP%\video2x | ||||||
|   image_format: png |   image_format: png | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| Creator: Video2X GUI | Creator: Video2X GUI | ||||||
| Author: K4YT3X | Author: K4YT3X | ||||||
| Date Created: May 5, 2020 | Date Created: May 5, 2020 | ||||||
| Last Modified: May 10, 2020 | Last Modified: May 11, 2020 | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| # local imports | # local imports | ||||||
| @ -25,6 +25,7 @@ import yaml | |||||||
| from PyQt5 import QtGui, uic | from PyQt5 import QtGui, uic | ||||||
| from PyQt5.QtCore import * | from PyQt5.QtCore import * | ||||||
| from PyQt5.QtWidgets import * | from PyQt5.QtWidgets import * | ||||||
|  | import magic | ||||||
| # QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt | # QObject, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QAbstractTableModel, Qt | ||||||
| 
 | 
 | ||||||
| VERSION = '2.0.0' | VERSION = '2.0.0' | ||||||
| @ -110,13 +111,34 @@ class InputTableModel(QAbstractTableModel): | |||||||
|     def data(self, index, role): |     def data(self, index, role): | ||||||
|         if role == Qt.DisplayRole: |         if role == Qt.DisplayRole: | ||||||
| 
 | 
 | ||||||
|  |             file_path = self._data[index.row()] | ||||||
|  | 
 | ||||||
|             if index.column() == 0: |             if index.column() == 0: | ||||||
|                 return str(self._data[index.row()].absolute()) |                 return str(file_path.absolute()) | ||||||
|             else: |             else: | ||||||
|                 if self._data[index.row()].is_file(): | 
 | ||||||
|                     return 'File' |                 # determine file type | ||||||
|                 elif self._data[index.row()].is_dir(): |                 # if path is a folder | ||||||
|  |                 if file_path.is_dir(): | ||||||
|                     return 'Folder' |                     return 'Folder' | ||||||
|  | 
 | ||||||
|  |                 # if path is single file | ||||||
|  |                 # determine file type | ||||||
|  |                 elif file_path.is_file(): | ||||||
|  |                     input_file_mime_type = magic.from_file(str(file_path.absolute()), mime=True) | ||||||
|  |                     input_file_type = input_file_mime_type.split('/')[0] | ||||||
|  |                     input_file_subtype = input_file_mime_type.split('/')[1] | ||||||
|  |                     if input_file_type == 'image': | ||||||
|  |                         if input_file_subtype == 'gif': | ||||||
|  |                             return 'GIF' | ||||||
|  |                         return 'Image' | ||||||
|  | 
 | ||||||
|  |                     elif input_file_type == 'video': | ||||||
|  |                         return 'Video' | ||||||
|  | 
 | ||||||
|  |                     else: | ||||||
|  |                         return 'Unknown' | ||||||
|  | 
 | ||||||
|                 else: |                 else: | ||||||
|                     return 'Unknown' |                     return 'Unknown' | ||||||
| 
 | 
 | ||||||
| @ -373,6 +395,10 @@ class Video2XMainWindow(QMainWindow): | |||||||
|         self.ffmpeg_settings = self.config['ffmpeg'] |         self.ffmpeg_settings = self.config['ffmpeg'] | ||||||
|         self.ffmpeg_settings['ffmpeg_path'] = str(pathlib.Path(os.path.expandvars(self.ffmpeg_settings['ffmpeg_path'])).absolute()) |         self.ffmpeg_settings['ffmpeg_path'] = str(pathlib.Path(os.path.expandvars(self.ffmpeg_settings['ffmpeg_path'])).absolute()) | ||||||
| 
 | 
 | ||||||
|  |         # read Gifski configuration | ||||||
|  |         self.gifski_settings = self.config['gifski'] | ||||||
|  |         self.gifski_settings['gifski_path'] = str(pathlib.Path(os.path.expandvars(self.gifski_settings['gifski_path'])).absolute()) | ||||||
|  | 
 | ||||||
|         # set cache directory path |         # set cache directory path | ||||||
|         if self.config['video2x']['video2x_cache_directory'] is None: |         if self.config['video2x']['video2x_cache_directory'] is None: | ||||||
|             self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute()) |             self.config['video2x']['video2x_cache_directory'] = str((pathlib.Path(tempfile.gettempdir()) / 'video2x').absolute()) | ||||||
| @ -585,7 +611,34 @@ class Video2XMainWindow(QMainWindow): | |||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         if input_path.is_file(): |         if input_path.is_file(): | ||||||
|             output_path = input_path.parent / f'{input_path.stem}_output.mp4' | 
 | ||||||
|  |             # generate suffix automatically | ||||||
|  |             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] | ||||||
|  | 
 | ||||||
|  |             # if input file is an image | ||||||
|  |             if input_file_type == 'image': | ||||||
|  | 
 | ||||||
|  |                 # if file is a gif, use .gif | ||||||
|  |                 if input_file_subtype == 'gif': | ||||||
|  |                     suffix = '.gif' | ||||||
|  | 
 | ||||||
|  |                 # otherwise, use .png by default for all images | ||||||
|  |                 else: | ||||||
|  |                     suffix = '.png' | ||||||
|  | 
 | ||||||
|  |             # if input is video, use .mp4 as output by default | ||||||
|  |             elif input_file_type == 'video': | ||||||
|  |                 suffix = '.mp4' | ||||||
|  | 
 | ||||||
|  |             # if failed to detect file type | ||||||
|  |             # use input file's suffix | ||||||
|  |             else: | ||||||
|  |                 suffix = input_path.suffix | ||||||
|  | 
 | ||||||
|  |             output_path = input_path.parent / f'{input_path.stem}_output{suffix}' | ||||||
|  | 
 | ||||||
|         elif input_path.is_dir(): |         elif input_path.is_dir(): | ||||||
|             output_path = input_path.parent / f'{input_path.stem}_output' |             output_path = input_path.parent / f'{input_path.stem}_output' | ||||||
| 
 | 
 | ||||||
| @ -593,7 +646,7 @@ class Video2XMainWindow(QMainWindow): | |||||||
|         output_path_id = 0 |         output_path_id = 0 | ||||||
|         while output_path.exists() and output_path_id <= 1000: |         while output_path.exists() and output_path_id <= 1000: | ||||||
|             if input_path.is_file(): |             if input_path.is_file(): | ||||||
|                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}.mp4') |                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}{suffix}') | ||||||
|             elif input_path.is_dir(): |             elif input_path.is_dir(): | ||||||
|                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}') |                 output_path = input_path.parent / pathlib.Path(f'{input_path.stem}_output_{output_path_id}') | ||||||
|             output_path_id += 1 |             output_path_id += 1 | ||||||
| @ -693,8 +746,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | |||||||
|                                     self.upscaler.total_frames_upscaled, |                                     self.upscaler.total_frames_upscaled, | ||||||
|                                     self.upscaler.total_frames, |                                     self.upscaler.total_frames, | ||||||
|                                     self.upscaler.total_processed, |                                     self.upscaler.total_processed, | ||||||
|                                     self.upscaler.total_videos, |                                     self.upscaler.total_files, | ||||||
|                                     self.upscaler.current_input_video, |                                     self.upscaler.current_input_file, | ||||||
|                                     self.upscaler.last_frame_upscaled)) |                                     self.upscaler.last_frame_upscaled)) | ||||||
|             time.sleep(1) |             time.sleep(1) | ||||||
| 
 | 
 | ||||||
| @ -707,8 +760,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | |||||||
|         total_frames_upscaled = progress_information[1] |         total_frames_upscaled = progress_information[1] | ||||||
|         total_frames = progress_information[2] |         total_frames = progress_information[2] | ||||||
|         total_processed = progress_information[3] |         total_processed = progress_information[3] | ||||||
|         total_videos = progress_information[4] |         total_files = progress_information[4] | ||||||
|         current_input_video = progress_information[5] |         current_input_file = progress_information[5] | ||||||
|         last_frame_upscaled = progress_information[6] |         last_frame_upscaled = progress_information[6] | ||||||
| 
 | 
 | ||||||
|         # calculate fields based on frames and time elapsed |         # calculate fields based on frames and time elapsed | ||||||
| @ -727,10 +780,10 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | |||||||
|         self.time_elapsed_label.setText('Time Elapsed: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_elapsed)))) |         self.time_elapsed_label.setText('Time Elapsed: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_elapsed)))) | ||||||
|         self.time_remaining_label.setText('Time Remaining: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_remaining)))) |         self.time_remaining_label.setText('Time Remaining: {}'.format(time.strftime("%H:%M:%S", time.gmtime(time_remaining)))) | ||||||
|         self.rate_label.setText('Rate (FPS): {}'.format(round(rate, 2))) |         self.rate_label.setText('Rate (FPS): {}'.format(round(rate, 2))) | ||||||
|         self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_videos)) |         self.overall_progress_label.setText('Overall Progress: {}/{}'.format(total_processed, total_files)) | ||||||
|         self.overall_progress_bar.setMaximum(total_videos) |         self.overall_progress_bar.setMaximum(total_files) | ||||||
|         self.overall_progress_bar.setValue(total_processed) |         self.overall_progress_bar.setValue(total_processed) | ||||||
|         self.currently_processing_label.setText('Currently Processing: {}'.format(str(current_input_video.name))) |         self.currently_processing_label.setText('Currently Processing: {}'.format(str(current_input_file.name))) | ||||||
| 
 | 
 | ||||||
|         # if show frame is checked, show preview image |         # if show frame is checked, show preview image | ||||||
|         if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file(): |         if self.frame_preview_show_preview_check_box.isChecked() and last_frame_upscaled.is_file(): | ||||||
| @ -798,7 +851,8 @@ You can [submit an issue on GitHub](https://github.com/k4yt3x/video2x/issues/new | |||||||
|             self.upscaler = Upscaler(input_path=input_directory, |             self.upscaler = Upscaler(input_path=input_directory, | ||||||
|                                      output_path=output_directory, |                                      output_path=output_directory, | ||||||
|                                      driver_settings=self.driver_settings, |                                      driver_settings=self.driver_settings, | ||||||
|                                      ffmpeg_settings=self.ffmpeg_settings) |                                      ffmpeg_settings=self.ffmpeg_settings, | ||||||
|  |                                      gifski_settings=self.gifski_settings) | ||||||
| 
 | 
 | ||||||
|             # set optional options |             # set optional options | ||||||
|             self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] |             self.upscaler.driver = AVAILABLE_DRIVERS[self.driver_combo_box.currentText()] | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ import re | |||||||
| import shutil | import shutil | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
|  | import tarfile | ||||||
| import tempfile | import tempfile | ||||||
| import time | import time | ||||||
| import traceback | import traceback | ||||||
| @ -42,12 +43,19 @@ import zipfile | |||||||
| # later in the script. | # later in the script. | ||||||
| # import requests | # import requests | ||||||
| 
 | 
 | ||||||
| VERSION = '1.8.0' | VERSION = '2.0.0' | ||||||
| 
 | 
 | ||||||
| # global static variables | # global static variables | ||||||
| LOCALAPPDATA = pathlib.Path(os.getenv('localappdata')) | LOCALAPPDATA = pathlib.Path(os.getenv('localappdata')) | ||||||
| VIDEO2X_CONFIG = pathlib.Path(__file__).parent.absolute() / 'video2x.yaml' | VIDEO2X_CONFIG = pathlib.Path(__file__).parent.absolute() / 'video2x.yaml' | ||||||
| DRIVER_OPTIONS = ['all', 'ffmpeg', 'waifu2x_caffe', 'waifu2x_converter_cpp', 'waifu2x_ncnn_vulkan', 'anime4kcpp', 'srmd_ncnn_vulkan'] | DRIVER_OPTIONS = ['all', | ||||||
|  |                   'ffmpeg', | ||||||
|  |                   'gifski', | ||||||
|  |                   'waifu2x_caffe', | ||||||
|  |                   'waifu2x_converter_cpp', | ||||||
|  |                   'waifu2x_ncnn_vulkan', | ||||||
|  |                   'anime4kcpp', | ||||||
|  |                   'srmd_ncnn_vulkan'] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def parse_arguments(): | def parse_arguments(): | ||||||
| @ -76,32 +84,21 @@ class Video2xSetup: | |||||||
|         self.trash = [] |         self.trash = [] | ||||||
| 
 | 
 | ||||||
|     def run(self): |     def run(self): | ||||||
|  |         # regardless of which driver to install | ||||||
|  |         # always ensure Python modules are installed and up-to-date | ||||||
|         if self.download_python_modules: |         if self.download_python_modules: | ||||||
|             print('\nInstalling Python libraries') |             print('\nInstalling Python libraries') | ||||||
|             self._install_python_requirements() |             self._install_python_requirements() | ||||||
| 
 | 
 | ||||||
|  |         # if all drivers are to be installed | ||||||
|         if self.driver == 'all': |         if self.driver == 'all': | ||||||
|             self._install_ffmpeg() |             DRIVER_OPTIONS.remove('all') | ||||||
|             self._install_waifu2x_caffe() |             for driver in DRIVER_OPTIONS: | ||||||
|             self._install_waifu2x_converter_cpp() |                 getattr(self, f'_install_{driver}')() | ||||||
|             self._install_waifu2x_ncnn_vulkan() |  | ||||||
|             self._install_anime4kcpp() |  | ||||||
|             self._install_srmd_ncnn_vulkan() |  | ||||||
|         elif self.driver == 'ffmpeg': |  | ||||||
|             self._install_ffmpeg() |  | ||||||
|         elif self.driver == 'waifu2x_caffe': |  | ||||||
|             self._install_waifu2x_caffe() |  | ||||||
|         elif self.driver == 'waifu2x_converter_cpp': |  | ||||||
|             self._install_waifu2x_converter_cpp() |  | ||||||
|         elif self.driver == 'waifu2x_ncnn_vulkan': |  | ||||||
|             self._install_waifu2x_ncnn_vulkan() |  | ||||||
|         elif self.driver == 'anime4kcpp': |  | ||||||
|             self._install_anime4kcpp() |  | ||||||
|         elif self.driver == 'srmd_ncnn_vulkan': |  | ||||||
|             self._install_srmd_ncnn_vulkan() |  | ||||||
| 
 | 
 | ||||||
|         print('\nGenerating Video2X configuration file') |         # install only the selected driver | ||||||
|         self._generate_config() |         else: | ||||||
|  |             getattr(self, f'_install_{self.driver}')() | ||||||
| 
 | 
 | ||||||
|         print('\nCleaning up temporary files') |         print('\nCleaning up temporary files') | ||||||
|         self._cleanup() |         self._cleanup() | ||||||
| @ -139,6 +136,22 @@ class Video2xSetup: | |||||||
|         with zipfile.ZipFile(ffmpeg_zip) as zipf: |         with zipfile.ZipFile(ffmpeg_zip) as zipf: | ||||||
|             zipf.extractall(LOCALAPPDATA / 'video2x') |             zipf.extractall(LOCALAPPDATA / 'video2x') | ||||||
| 
 | 
 | ||||||
|  |     def _install_gifski(self): | ||||||
|  |         print('\nInstalling Gifski') | ||||||
|  |         import requests | ||||||
|  | 
 | ||||||
|  |         # Get latest release of waifu2x-ncnn-vulkan via Github API | ||||||
|  |         latest_release = requests.get('https://api.github.com/repos/ImageOptim/gifski/releases/latest').json() | ||||||
|  | 
 | ||||||
|  |         for a in latest_release['assets']: | ||||||
|  |             if re.search(r'gifski-.*\.tar\.xz', a['browser_download_url']): | ||||||
|  |                 gifski_tar_gz = download(a['browser_download_url'], tempfile.gettempdir()) | ||||||
|  |                 self.trash.append(gifski_tar_gz) | ||||||
|  | 
 | ||||||
|  |         # extract and rename | ||||||
|  |         with tarfile.open(gifski_tar_gz) as archive: | ||||||
|  |             archive.extractall(LOCALAPPDATA / 'video2x' / 'gifski') | ||||||
|  | 
 | ||||||
|     def _install_waifu2x_caffe(self): |     def _install_waifu2x_caffe(self): | ||||||
|         """ Install waifu2x_caffe |         """ Install waifu2x_caffe | ||||||
|         """ |         """ | ||||||
| @ -248,42 +261,6 @@ class Video2xSetup: | |||||||
|             # rename the newly extracted directory |             # rename the newly extracted directory | ||||||
|             (LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(srmd_ncnn_vulkan_directory) |             (LOCALAPPDATA / 'video2x' / zipf.namelist()[0]).rename(srmd_ncnn_vulkan_directory) | ||||||
| 
 | 
 | ||||||
|     def _generate_config(self): |  | ||||||
|         """ Generate video2x config |  | ||||||
|         """ |  | ||||||
|         import yaml |  | ||||||
| 
 |  | ||||||
|         # open current video2x configuration file as template |  | ||||||
|         with open(VIDEO2X_CONFIG, 'r') as template: |  | ||||||
|             template_dict = yaml.load(template, Loader=yaml.FullLoader) |  | ||||||
|             template.close() |  | ||||||
| 
 |  | ||||||
|         # configure only the specified drivers |  | ||||||
|         if self.driver == 'all': |  | ||||||
|             template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui') |  | ||||||
|             template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp' / 'waifu2x-converter-cpp') |  | ||||||
|             template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan') |  | ||||||
|             template_dict['srmd_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'srmd-ncnn-vulkan' / 'srmd-ncnn-vulkan') |  | ||||||
|             template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI') |  | ||||||
|         elif self.driver == 'waifu2x_caffe': |  | ||||||
|             template_dict['waifu2x_caffe']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-caffe' / 'waifu2x-caffe-cui') |  | ||||||
|         elif self.driver == 'waifu2x_converter_cpp': |  | ||||||
|             template_dict['waifu2x_converter_cpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-converter-cpp' / 'waifu2x-converter-cpp') |  | ||||||
|         elif self.driver == 'waifu2x_ncnn_vulkan': |  | ||||||
|             template_dict['waifu2x_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'waifu2x-ncnn-vulkan' / 'waifu2x-ncnn-vulkan') |  | ||||||
|         elif self.driver == 'srmd_ncnn_vulkan': |  | ||||||
|             template_dict['srmd_ncnn_vulkan']['path'] = str(LOCALAPPDATA / 'video2x' / 'srmd-ncnn-vulkan' / 'srmd-ncnn-vulkan') |  | ||||||
|         elif self.driver == 'anime4kcpp': |  | ||||||
|             template_dict['anime4kcpp']['path'] = str(LOCALAPPDATA / 'video2x' / 'anime4kcpp' / 'CLI' / 'Anime4KCPP_CLI' / 'Anime4KCPP_CLI') |  | ||||||
| 
 |  | ||||||
|         template_dict['ffmpeg']['ffmpeg_path'] = str(LOCALAPPDATA / 'video2x' / 'ffmpeg-latest-win64-static' / 'bin') |  | ||||||
|         template_dict['video2x']['video2x_cache_directory'] = None |  | ||||||
|         template_dict['video2x']['preserve_frames'] = False |  | ||||||
| 
 |  | ||||||
|         # write configuration into file |  | ||||||
|         with open(VIDEO2X_CONFIG, 'w') as config: |  | ||||||
|             yaml.dump(template_dict, config) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def download(url, save_path, chunk_size=4096): | def download(url, save_path, chunk_size=4096): | ||||||
|     """ Download file to local with requests library |     """ Download file to local with requests library | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ class WrapperMain: | |||||||
|         self.driver_settings['zoomFactor'] = upscaler.scale_ratio |         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"]}' | ||||||
|  | 
 | ||||||
|     def upscale(self, input_file, output_file): |     def upscale(self, input_file, output_file): | ||||||
|         """This is the core function for WAIFU2X class |         """This is the core function for WAIFU2X class | ||||||
| 
 | 
 | ||||||
| @ -90,14 +94,14 @@ class WrapperMain: | |||||||
| 
 | 
 | ||||||
|         # list to be executed |         # list to be executed | ||||||
|         # initialize the list with waifu2x binary path as the first element |         # initialize the list with waifu2x binary path as the first element | ||||||
|         execute = [self.driver_settings.pop('path')] |         execute = [self.driver_settings['path']] | ||||||
| 
 | 
 | ||||||
|         for key in self.driver_settings.keys(): |         for key in self.driver_settings.keys(): | ||||||
| 
 | 
 | ||||||
|             value = self.driver_settings[key] |             value = self.driver_settings[key] | ||||||
| 
 | 
 | ||||||
|             # null or None means that leave this option out (keep default) |             # null or None means that leave this option out (keep default) | ||||||
|             if value is None or value is False: |             if key == 'path' or value is None or value is False: | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 if len(key) == 1: |                 if len(key) == 1: | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ Description: This class handles all FFmpeg related operations. | |||||||
| # built-in imports | # built-in imports | ||||||
| import json | import json | ||||||
| import pathlib | import pathlib | ||||||
|  | import shlex | ||||||
| import subprocess | import subprocess | ||||||
| 
 | 
 | ||||||
| # third-party imports | # third-party imports | ||||||
| @ -36,7 +37,7 @@ class Ffmpeg: | |||||||
|         # video metadata |         # video metadata | ||||||
|         self.image_format = image_format |         self.image_format = image_format | ||||||
|         self.intermediate_file_name = pathlib.Path(self.ffmpeg_settings['intermediate_file_name']) |         self.intermediate_file_name = pathlib.Path(self.ffmpeg_settings['intermediate_file_name']) | ||||||
|         self.pixel_format = None |         self.pixel_format = self.ffmpeg_settings['input_to_frames']['output_options']['-pix_fmt'] | ||||||
| 
 | 
 | ||||||
|     def get_pixel_formats(self): |     def get_pixel_formats(self): | ||||||
|         """ Get a dictionary of supported pixel formats |         """ Get a dictionary of supported pixel formats | ||||||
| @ -49,8 +50,8 @@ class Ffmpeg: | |||||||
|         """ |         """ | ||||||
|         execute = [ |         execute = [ | ||||||
|             self.ffmpeg_probe_binary, |             self.ffmpeg_probe_binary, | ||||||
|             '-v', |             # '-v', | ||||||
|             'quiet', |             # 'quiet', | ||||||
|             '-pix_fmts' |             '-pix_fmts' | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
| @ -74,7 +75,7 @@ class Ffmpeg: | |||||||
| 
 | 
 | ||||||
|         return pixel_formats |         return pixel_formats | ||||||
| 
 | 
 | ||||||
|     def get_video_info(self, input_video): |     def probe_file_info(self, input_video): | ||||||
|         """ Gets input video information |         """ Gets input video information | ||||||
| 
 | 
 | ||||||
|         This method reads input video information |         This method reads input video information | ||||||
| @ -104,31 +105,25 @@ class Ffmpeg: | |||||||
|         # turn elements into str |         # turn elements into str | ||||||
|         execute = [str(e) for e in execute] |         execute = [str(e) for e in execute] | ||||||
| 
 | 
 | ||||||
|         Avalon.debug_info(f'Executing: {" ".join(execute)}') |         Avalon.debug_info(f'Executing: {shlex.join(execute)}') | ||||||
|         json_str = subprocess.run(execute, check=True, stdout=subprocess.PIPE).stdout |         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_video, extracted_frames): |     def extract_frames(self, input_file, extracted_frames): | ||||||
|         """Extract every frame from original videos |         """ extract frames from video or GIF file | ||||||
| 
 |  | ||||||
|         This method extracts every frame from input video using FFmpeg |  | ||||||
| 
 |  | ||||||
|         Arguments: |  | ||||||
|             input_video {string} -- input video path |  | ||||||
|             extracted_frames {string} -- video output directory |  | ||||||
|         """ |         """ | ||||||
|         execute = [ |         execute = [ | ||||||
|             self.ffmpeg_binary |             self.ffmpeg_binary | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         execute.extend(self._read_configuration(phase='video_to_frames')) |         execute.extend(self._read_configuration(phase='input_to_frames')) | ||||||
| 
 | 
 | ||||||
|         execute.extend([ |         execute.extend([ | ||||||
|             '-i', |             '-i', | ||||||
|             input_video |             input_file | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|         execute.extend(self._read_configuration(phase='video_to_frames', section='output_options')) |         execute.extend(self._read_configuration(phase='input_to_frames', section='output_options')) | ||||||
| 
 | 
 | ||||||
|         execute.extend([ |         execute.extend([ | ||||||
|             extracted_frames / f'extracted_%0d.{self.image_format}' |             extracted_frames / f'extracted_%0d.{self.image_format}' | ||||||
| @ -136,7 +131,7 @@ class Ffmpeg: | |||||||
| 
 | 
 | ||||||
|         return(self._execute(execute)) |         return(self._execute(execute)) | ||||||
| 
 | 
 | ||||||
|     def assemble_video(self, framerate, resolution, upscaled_frames): |     def assemble_video(self, framerate, upscaled_frames): | ||||||
|         """Converts images into videos |         """Converts images into videos | ||||||
| 
 | 
 | ||||||
|         This method converts a set of images into a video |         This method converts a set of images into a video | ||||||
| @ -149,9 +144,9 @@ class Ffmpeg: | |||||||
|         execute = [ |         execute = [ | ||||||
|             self.ffmpeg_binary, |             self.ffmpeg_binary, | ||||||
|             '-r', |             '-r', | ||||||
|             str(framerate), |             str(framerate) | ||||||
|             '-s', |             # '-s', | ||||||
|             resolution |             # resolution | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         # read other options |         # read other options | ||||||
| @ -274,17 +269,9 @@ class Ffmpeg: | |||||||
|         return configuration |         return configuration | ||||||
| 
 | 
 | ||||||
|     def _execute(self, execute): |     def _execute(self, execute): | ||||||
|         """ execute command |  | ||||||
| 
 |  | ||||||
|         Arguments: |  | ||||||
|             execute {list} -- list of arguments to be executed |  | ||||||
| 
 |  | ||||||
|         Returns: |  | ||||||
|             int -- execution return code |  | ||||||
|         """ |  | ||||||
|         # turn all list elements into string to avoid errors |         # turn all list elements into string to avoid errors | ||||||
|         execute = [str(e) for e in execute] |         execute = [str(e) for e in execute] | ||||||
| 
 | 
 | ||||||
|         Avalon.debug_info(f'Executing: {execute}') |         Avalon.debug_info(f'Executing: {shlex.join(execute)}') | ||||||
| 
 | 
 | ||||||
|         return subprocess.Popen(execute) |         return subprocess.Popen(execute) | ||||||
|  | |||||||
							
								
								
									
										70
									
								
								src/wrappers/gifski.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/wrappers/gifski.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | """ | ||||||
|  | Name: Gifski Wrapper | ||||||
|  | Creator: K4YT3X | ||||||
|  | Date Created: May 11, 2020 | ||||||
|  | Last Modified: May 11, 2020 | ||||||
|  | 
 | ||||||
|  | Description: High-level wrapper for Gifski. | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | # built-in imports | ||||||
|  | import pathlib | ||||||
|  | import subprocess | ||||||
|  | 
 | ||||||
|  | # third-party imports | ||||||
|  | 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, image_format: str) -> subprocess.Popen: | ||||||
|  |         execute = [ | ||||||
|  |             self.gifski_settings['gifski_path'], | ||||||
|  |             '-o', | ||||||
|  |             output_path, | ||||||
|  |             '--fps', | ||||||
|  |             int(round(framerate, 0)) | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         # load configurations from config file | ||||||
|  |         execute.extend(self._load_configuration()) | ||||||
|  | 
 | ||||||
|  |         # append frames location | ||||||
|  |         execute.extend([upscaled_frames / f'extracted_*.{image_format}']) | ||||||
|  | 
 | ||||||
|  |         return(self._execute(execute)) | ||||||
|  | 
 | ||||||
|  |     def _load_configuration(self): | ||||||
|  | 
 | ||||||
|  |         configuration = [] | ||||||
|  | 
 | ||||||
|  |         for key in self.gifski_settings.keys(): | ||||||
|  | 
 | ||||||
|  |             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: | ||||||
|  |                 continue | ||||||
|  |             else: | ||||||
|  |                 if len(key) == 1: | ||||||
|  |                     configuration.append(f'-{key}') | ||||||
|  |                 else: | ||||||
|  |                     configuration.append(f'--{key}') | ||||||
|  | 
 | ||||||
|  |                 # true means key is an option | ||||||
|  |                 if value is not True: | ||||||
|  |                     configuration.append(str(value)) | ||||||
|  |         return configuration | ||||||
|  | 
 | ||||||
|  |     def _execute(self, execute: list) -> subprocess.Popen: | ||||||
|  |         # turn all list elements into string to avoid errors | ||||||
|  |         execute = [str(e) for e in execute] | ||||||
|  | 
 | ||||||
|  |         Avalon.debug_info(f'Executing: {execute}') | ||||||
|  | 
 | ||||||
|  |         return subprocess.Popen(execute) | ||||||
| @ -77,14 +77,14 @@ class WrapperMain: | |||||||
| 
 | 
 | ||||||
|         # list to be executed |         # list to be executed | ||||||
|         # initialize the list with the binary path as the first element |         # initialize the list with the binary path as the first element | ||||||
|         execute = [self.driver_settings.pop('path')] |         execute = [self.driver_settings['path']] | ||||||
| 
 | 
 | ||||||
|         for key in self.driver_settings.keys(): |         for key in self.driver_settings.keys(): | ||||||
| 
 | 
 | ||||||
|             value = self.driver_settings[key] |             value = self.driver_settings[key] | ||||||
| 
 | 
 | ||||||
|             # null or None means that leave this option out (keep default) |             # null or None means that leave this option out (keep default) | ||||||
|             if value is None or value is False: |             if key == 'path' or value is None or value is False: | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 if len(key) == 1: |                 if len(key) == 1: | ||||||
|  | |||||||
| @ -56,8 +56,8 @@ class WrapperMain: | |||||||
|         parser.add_argument('-m', '--mode', choices=['noise', 'scale', 'noise_scale', 'auto_scale'], help='image processing mode') |         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('-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('-l', '--input_extention_list', type=str, help='extention to input image file when input_path is folder') | ||||||
|         parser.add_argument('-o', '--output', type=str, help=argparse.SUPPRESS)  # help='path to output image file (when input_path is folder, output_path must be 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_file', type=str, help=argparse.SUPPRESS)  # help='(required) path to input image file') |         parser.add_argument('-i', '--input_path', type=str, help=argparse.SUPPRESS)  # help='(required) path to input image file') | ||||||
|         return parser.parse_args(arguments) |         return parser.parse_args(arguments) | ||||||
| 
 | 
 | ||||||
|     def load_configurations(self, upscaler): |     def load_configurations(self, upscaler): | ||||||
| @ -79,14 +79,14 @@ class WrapperMain: | |||||||
| 
 | 
 | ||||||
|         # list to be executed |         # list to be executed | ||||||
|         # initialize the list with waifu2x binary path as the first element |         # initialize the list with waifu2x binary path as the first element | ||||||
|         execute = [self.driver_settings.pop('path')] |         execute = [self.driver_settings['path']] | ||||||
| 
 | 
 | ||||||
|         for key in self.driver_settings.keys(): |         for key in self.driver_settings.keys(): | ||||||
| 
 | 
 | ||||||
|             value = self.driver_settings[key] |             value = self.driver_settings[key] | ||||||
| 
 | 
 | ||||||
|             # null or None means that leave this option out (keep default) |             # null or None means that leave this option out (keep default) | ||||||
|             if value is None or value is False: |             if key == 'path' or value is None or value is False: | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 if len(key) == 1: |                 if len(key) == 1: | ||||||
|  | |||||||
| @ -93,14 +93,14 @@ class WrapperMain: | |||||||
| 
 | 
 | ||||||
|         # list to be executed |         # list to be executed | ||||||
|         # initialize the list with waifu2x binary path as the first element |         # initialize the list with waifu2x binary path as the first element | ||||||
|         execute = [self.driver_settings.pop('path')] |         execute = [self.driver_settings['path']] | ||||||
| 
 | 
 | ||||||
|         for key in self.driver_settings.keys(): |         for key in self.driver_settings.keys(): | ||||||
| 
 | 
 | ||||||
|             value = self.driver_settings[key] |             value = self.driver_settings[key] | ||||||
| 
 | 
 | ||||||
|             # null or None means that leave this option out (keep default) |             # null or None means that leave this option out (keep default) | ||||||
|             if value is None or value is False: |             if key == 'path' or value is None or value is False: | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 if len(key) == 1: |                 if len(key) == 1: | ||||||
|  | |||||||
| @ -80,14 +80,14 @@ class WrapperMain: | |||||||
| 
 | 
 | ||||||
|         # list to be executed |         # list to be executed | ||||||
|         # initialize the list with waifu2x binary path as the first element |         # initialize the list with waifu2x binary path as the first element | ||||||
|         execute = [self.driver_settings.pop('path')] |         execute = [self.driver_settings['path']] | ||||||
| 
 | 
 | ||||||
|         for key in self.driver_settings.keys(): |         for key in self.driver_settings.keys(): | ||||||
| 
 | 
 | ||||||
|             value = self.driver_settings[key] |             value = self.driver_settings[key] | ||||||
| 
 | 
 | ||||||
|             # null or None means that leave this option out (keep default) |             # null or None means that leave this option out (keep default) | ||||||
|             if value is None or value is False: |             if key == 'path' or value is None or value is False: | ||||||
|                 continue |                 continue | ||||||
|             else: |             else: | ||||||
|                 if len(key) == 1: |                 if len(key) == 1: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user