diff --git a/src/upscaler.py b/src/upscaler.py index d77bc0d..1aebb5a 100755 --- a/src/upscaler.py +++ b/src/upscaler.py @@ -82,9 +82,9 @@ class Upscaler: # create temp directories for extracted frames and upscaled frames self.extracted_frames = pathlib.Path(tempfile.mkdtemp(dir=self.video2x_cache_directory)) - Avalon.debug_info(f'Extracted frames are being saved to: {self.extracted_frames}') + 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(f'Upscaled frames are being saved to: {self.upscaled_frames}') + Avalon.debug_info(_('Upscaled frames are being saved to: {}').format(self.upscaled_frames)) def cleanup_temp_directories(self): """delete temp directories when done @@ -94,35 +94,35 @@ class Upscaler: try: # avalon framework cannot be used if python is shutting down # therefore, plain print is used - print(f'Cleaning up cache directory: {directory}') + print(_('Cleaning up cache directory: {}').format(directory)) shutil.rmtree(directory) except (OSError, FileNotFoundError): - print(f'Unable to delete: {directory}') + print(_('Unable to delete: {}').format(directory)) traceback.print_exc() def _check_arguments(self): # check if arguments are valid / all necessary argument # values are specified if not self.input_video: - Avalon.error('You must specify input video file/directory path') + Avalon.error(_('You must specify input video file/directory path')) raise ArgumentError('input video path not specified') if not self.output_video: - Avalon.error('You must specify output video file/directory path') + Avalon.error(_('You must specify output video file/directory path')) raise ArgumentError('output video path not specified') if (self.driver in ['waifu2x_converter', 'waifu2x_ncnn_vulkan', 'anime4k']) and self.scale_width and self.scale_height: - Avalon.error('Selected driver accepts only scaling ratio') + Avalon.error(_('Selected driver accepts only scaling ratio')) raise ArgumentError('selected driver supports only scaling ratio') if self.driver == 'waifu2x_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio > 2 or not self.scale_ratio.is_integer()): - Avalon.error('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan') + Avalon.error(_('Scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan')) raise ArgumentError('scaling ratio must be 1 or 2 for waifu2x_ncnn_vulkan') if self.driver == 'srmd_ncnn_vulkan' and self.scale_ratio is not None and (self.scale_ratio not in [2, 3, 4]): - Avalon.error('Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan') + Avalon.error(_('Scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan')) raise ArgumentError('scaling ratio must be one of 2, 3 or 4 for srmd_ncnn_vulkan') if (self.scale_width or self.scale_height) and self.scale_ratio: - Avalon.error('You can only specify either scaling ratio or output width and height') + Avalon.error(_('You can only specify either scaling ratio or output width and height')) raise ArgumentError('both scaling ration and width/height specified') if (self.scale_width and not self.scale_height) or (not self.scale_width and self.scale_height): - Avalon.error('You must specify both width and height') + Avalon.error(_('You must specify both width and height')) raise ArgumentError('only one of width or height is specified') def _progress_bar(self, extracted_frames_directories): @@ -139,7 +139,7 @@ class Upscaler: for directory in extracted_frames_directories: self.total_frames += len([f for f in directory.iterdir() if str(f).lower().endswith(self.image_format.lower())]) - with tqdm(total=self.total_frames, ascii=True, desc='Upscaling Progress') as progress_bar: + with tqdm(total=self.total_frames, ascii=True, desc=_('Upscaling Progress')) as progress_bar: # tqdm update method adds the value to the progress # bar instead of setting the value. Therefore, a delta @@ -176,7 +176,7 @@ class Upscaler: # initialize waifu2x driver if self.driver not in AVAILABLE_DRIVERS: - raise UnrecognizedDriverError(f'Unrecognized driver: {self.driver}') + raise UnrecognizedDriverError(_('Unrecognized driver: {}').format(self.driver)) # create a container for all upscaler processes upscaler_processes = [] @@ -255,15 +255,15 @@ class Upscaler: progress_bar.start() # create the clearer and start it - Avalon.debug_info('Starting upscaled image cleaner') + Avalon.debug_info(_('Starting upscaled image cleaner')) image_cleaner = ImageCleaner(self.extracted_frames, self.upscaled_frames, len(upscaler_processes)) image_cleaner.start() # wait for all process to exit try: - Avalon.debug_info('Main process waiting for subprocesses to exit') + Avalon.debug_info(_('Main process waiting for subprocesses to exit')) for process in upscaler_processes: - Avalon.debug_info(f'Subprocess {process.pid} exited with code {process.wait()}') + Avalon.debug_info(_('Subprocess {} exited with code {}').format(process.pid, process.wait())) except (KeyboardInterrupt, SystemExit): Avalon.warning('Exit signal received') Avalon.warning('Killing processes') @@ -271,7 +271,7 @@ class Upscaler: process.terminate() # cleanup and exit with exit code 1 - Avalon.debug_info('Killing upscaled image cleaner') + Avalon.debug_info(_('Killing upscaled image cleaner')) image_cleaner.stop() self.progress_bar_exit_signal = True sys.exit(1) @@ -284,7 +284,7 @@ class Upscaler: (self.upscaled_frames / image).rename(self.upscaled_frames / renamed) # upscaling done, kill the clearer - Avalon.debug_info('Killing upscaled image cleaner') + Avalon.debug_info(_('Killing upscaled image cleaner')) image_cleaner.stop() # pass exit signal to progress bar thread @@ -310,10 +310,10 @@ class Upscaler: # 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') + Avalon.info(_('Starting to upscale extracted images')) driver = Anime4kCpp(self.driver_settings) driver.upscale(self.input_video, self.output_video, self.scale_ratio, self.processes).wait() - Avalon.info('Upscaling completed') + Avalon.info(_('Upscaling completed')) else: self.create_temp_directories() @@ -321,7 +321,7 @@ class Upscaler: # initialize objects for ffmpeg and waifu2x-caffe fm = Ffmpeg(self.ffmpeg_settings, self.image_format) - Avalon.info('Reading video information') + Avalon.info(_('Reading video information')) video_info = fm.get_video_info(self.input_video) # analyze original video with ffprobe and retrieve framerate # width, height = info['streams'][0]['width'], info['streams'][0]['height'] @@ -335,7 +335,7 @@ class Upscaler: # exit if no video stream found if video_stream_index is None: - Avalon.error('Aborting: No video stream found') + Avalon.error(_('Aborting: No video stream found')) raise StreamNotFoundError('no video stream found') # extract frames from video @@ -352,10 +352,10 @@ class Upscaler: try: self.bit_depth = pixel_formats[fm.pixel_format] except KeyError: - Avalon.error(f'Unsupported pixel format: {fm.pixel_format}') + Avalon.error(_('Unsupported pixel format: {}').format(fm.pixel_format)) raise UnsupportedPixelError(f'unsupported pixel format {fm.pixel_format}') - Avalon.info(f'Framerate: {framerate}') + Avalon.info(_('Framerate: {}').format(framerate)) # width/height will be coded width/height x upscale factor if self.scale_ratio: @@ -365,19 +365,19 @@ class Upscaler: self.scale_height = int(self.scale_ratio * original_height) # upscale images one by one using waifu2x - Avalon.info('Starting to upscale extracted images') + Avalon.info(_('Starting to upscale extracted images')) self._upscale_frames() - Avalon.info('Upscaling completed') + Avalon.info(_('Upscaling completed')) # frames to Video - Avalon.info('Converting extracted frames into video') + Avalon.info(_('Converting extracted frames into video')) # use user defined output size fm.convert_video(framerate, f'{self.scale_width}x{self.scale_height}', self.upscaled_frames) - Avalon.info('Conversion completed') + Avalon.info(_('Conversion completed')) # migrate audio tracks and subtitles - Avalon.info('Migrating audio tracks and subtitles to upscaled video') + Avalon.info(_('Migrating audio tracks and subtitles to upscaled video')) fm.migrate_audio_tracks_subtitles(self.input_video, self.output_video, self.upscaled_frames) # destroy temp directories diff --git a/src/video2x.py b/src/video2x.py index b9abab4..3a61a78 100755 --- a/src/video2x.py +++ b/src/video2x.py @@ -55,7 +55,9 @@ from upscaler import Upscaler # built-in imports import argparse import contextlib +import gettext import importlib +import locale import pathlib import re import shutil @@ -68,14 +70,24 @@ import yaml # third-party imports from avalon_framework import Avalon +# internationalization constants +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.install() +_ = language.gettext + VERSION = '4.0.0' -LEGAL_INFO = f'''Video2X Version: {VERSION} +LEGAL_INFO = _('''Video2X Version: {} Author: K4YT3X License: GNU GPL v3 Github Page: https://github.com/k4yt3x/video2x -Contact: k4yt3x@k4yt3x.com''' +Contact: k4yt3x@k4yt3x.com''').format(VERSION) LOGO = r''' __ __ _ _ ___ __ __ @@ -90,23 +102,24 @@ LOGO = r''' def parse_arguments(): """ parse CLI arguments """ - parser = argparse.ArgumentParser(prog='video2x', formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(prog='video2x', formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=False) # video options - general_options = parser.add_argument_group('General Options') - general_options.add_argument('-i', '--input', type=pathlib.Path, help='source video file/directory') - general_options.add_argument('-o', '--output', type=pathlib.Path, help='output video file/directory') - general_options.add_argument('-c', '--config', type=pathlib.Path, help='video2x config file location', action='store', + general_options = parser.add_argument_group(_('General Options')) + general_options.add_argument('-h', '--help', action='help', help=_('show this help message and exit')) + general_options.add_argument('-i', '--input', type=pathlib.Path, help=_('source video file/directory')) + general_options.add_argument('-o', '--output', type=pathlib.Path, help=_('output video file/directory')) + general_options.add_argument('-c', '--config', type=pathlib.Path, help=_('video2x config file path'), action='store', default=pathlib.Path(__file__).parent.absolute() / 'video2x.yaml') - general_options.add_argument('-d', '--driver', help='upscaling driver', choices=AVAILABLE_DRIVERS, default='waifu2x_caffe') - general_options.add_argument('-p', '--processes', help='number of processes to use for upscaling', action='store', type=int, default=1) - general_options.add_argument('-v', '--version', help='display version, lawful information and exit', action='store_true') + general_options.add_argument('-d', '--driver', help=_('upscaling driver'), choices=AVAILABLE_DRIVERS, default='waifu2x_caffe') + general_options.add_argument('-p', '--processes', help=_('number of processes to use for upscaling'), action='store', type=int, default=1) + general_options.add_argument('-v', '--version', help=_('display version, lawful information and exit'), action='store_true') # scaling options - scaling_options = parser.add_argument_group('Scaling Options') - scaling_options.add_argument('--width', help='output video width', action='store', type=int) - scaling_options.add_argument('--height', help='output video height', action='store', type=int) - scaling_options.add_argument('-r', '--ratio', help='scaling ratio', action='store', type=float) + scaling_options = parser.add_argument_group(_('Scaling Options')) + scaling_options.add_argument('--width', help=_('output video width'), action='store', type=int) + scaling_options.add_argument('--height', help=_('output video height'), action='store', type=int) + scaling_options.add_argument('-r', '--ratio', help=_('scaling ratio'), action='store', type=float) # if no driver arguments are specified if '--' not in sys.argv: @@ -146,7 +159,7 @@ def read_config(config_file: pathlib.Path) -> dict: # this is not a library if __name__ != '__main__': - Avalon.error('This file cannot be imported') + Avalon.error(_('This file cannot be imported')) raise ImportError(f'{__file__} cannot be imported') # print video2x logo @@ -176,8 +189,8 @@ if driver_args is not None: # check if driver path exists if not pathlib.Path(driver_settings['path']).exists(): if not pathlib.Path(f'{driver_settings["path"]}.exe').exists(): - Avalon.error('Specified driver executable directory doesn\'t exist') - Avalon.error('Please check the configuration file settings') + Avalon.error(_('Specified driver executable directory doesn\'t exist')) + Avalon.error(_('Please check the configuration file settings')) raise FileNotFoundError(driver_settings['path']) # read FFmpeg configuration @@ -194,20 +207,20 @@ else: video2x_cache_directory = pathlib.Path(tempfile.gettempdir()) / 'video2x' if video2x_cache_directory.exists() and not video2x_cache_directory.is_dir(): - Avalon.error('Specified cache directory is a file/link') + Avalon.error(_('Specified cache directory is a file/link')) raise FileExistsError('Specified cache directory is a file/link') # if cache directory doesn't exist # ask the user if it should be created elif not video2x_cache_directory.exists(): try: - Avalon.debug_info(f'Creating cache directory {video2x_cache_directory}') + Avalon.debug_info(_('Creating cache directory {}').format(video2x_cache_directory)) video2x_cache_directory.mkdir(parents=True, exist_ok=True) # there can be a number of exceptions here # PermissionError, FileExistsError, etc. # therefore, we put a catch-them-all here except Exception as exception: - Avalon.error(f'Unable to create {video2x_cache_directory}') + Avalon.error(_('Unable to create {}').format(video2x_cache_directory)) raise exception @@ -220,16 +233,16 @@ try: if video2x_args.input.is_file(): # upscale single video file - Avalon.info(f'Upscaling single video file: {video2x_args.input}') + Avalon.info(_('Upscaling single video file: {}').format(video2x_args.input)) # check for input output format mismatch if video2x_args.output.is_dir(): - Avalon.error('Input and output path type mismatch') - Avalon.error('Input is single file but output is directory') + Avalon.error(_('Input and output path type mismatch')) + Avalon.error(_('Input is single file but output is directory')) raise Exception('input output path type mismatch') if not re.search(r'.*\..*$', str(video2x_args.output)): - Avalon.error('No suffix found in output file path') - Avalon.error('Suffix must be specified for FFmpeg') + Avalon.error(_('No suffix found in output file path')) + Avalon.error(_('Suffix must be specified for FFmpeg')) raise Exception('No suffix specified') upscaler = Upscaler(input_video=video2x_args.input, @@ -253,7 +266,7 @@ try: # if input specified is a directory elif video2x_args.input.is_dir(): # upscale videos in a directory - Avalon.info(f'Upscaling videos in directory: {video2x_args.input}') + Avalon.info(_('Upscaling videos in directory: {}').format(video2x_args.input)) # make output directory if it doesn't exist video2x_args.output.mkdir(parents=True, exist_ok=True) @@ -278,13 +291,13 @@ try: # run upscaler upscaler.run() else: - Avalon.error('Input path is neither a file nor a directory') + Avalon.error(_('Input path is neither a file nor a directory')) raise FileNotFoundError(f'{video2x_args.input} is neither file nor directory') - Avalon.info(f'Program completed, taking {round((time.time() - begin_time), 5)} seconds') + 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() # try cleaning up temp directories