diff --git a/vnrecode/__main__.py b/vnrecode/__main__.py index 4e7fb68..39ef2d6 100644 --- a/vnrecode/__main__.py +++ b/vnrecode/__main__.py @@ -2,17 +2,19 @@ from .application import Application from .compress import Compress from .printer import Printer +from .params import Params from .config import Config from .utils import Utils def init(): config = Config.setup_config() + params = Params.setup(config.config, config.args) printer = Printer(config.args.source) - utils = Utils(config.config, printer) - compress = Compress(config.config, printer, utils) + utils = Utils(params, printer) + compress = Compress(params, printer, utils) - Application(config, compress, printer, utils).run() + Application(params, config.args, compress, printer, utils).run() if __name__ == "__main__": diff --git a/vnrecode/application.py b/vnrecode/application.py index 491267a..75e3107 100755 --- a/vnrecode/application.py +++ b/vnrecode/application.py @@ -7,9 +7,9 @@ import os class Application: - def __init__(self, config, compress, printer, utils): - self.config = config.config - self.args = config.args + def __init__(self, params, args, compress, printer, utils): + self.params = params + self.args = args self.compress = compress.compress self.printer = printer self.utils = utils @@ -35,7 +35,7 @@ class Application: self.printer.info(f'Compressing "{folder.replace(source, os.path.split(source)[-1])}" folder...') output = folder.replace(source, f"{source}_compressed") - with ThreadPoolExecutor(max_workers=self.config["FFMPEG"]["Workers"]) as executor: + with ThreadPoolExecutor(max_workers=self.params.workers) as executor: futures = [ executor.submit(self.compress, folder, file, source, output) for file in files if os.path.isfile(f'{folder}/{file}') diff --git a/vnrecode/compress.py b/vnrecode/compress.py index 5c1a465..7dbc6fc 100644 --- a/vnrecode/compress.py +++ b/vnrecode/compress.py @@ -40,13 +40,13 @@ class File: class Compress: - def __init__(self, config, printer, utils): - self.config = config + def __init__(self, params, printer, utils): + self.params = params self.printer = printer self.utils = utils def audio(self, folder, file, target_folder, extension): - bitrate = self.config['AUDIO']['BitRate'] + bitrate = self.params.audio_bitrate try: (FFmpeg() .input(f'{folder}/{file}') @@ -58,16 +58,16 @@ class Compress: except FFmpegError as e: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') self.utils.errors += 1 - if not self.config['FFMPEG']['HideErrors']: + if not self.params.hide_errors: self.printer.error(f"File {file} can't be processed! Error: {e}") self.printer.files(file, os.path.splitext(file)[0], extension, f"{bitrate}") return f'{target_folder}/{os.path.splitext(file)[0]}.{extension}' def video(self, folder, file, target_folder, extension): - if not self.config['VIDEO']['SkipVideo']: - codec = self.config['VIDEO']['Codec'] - crf = self.config['VIDEO']['CRF'] + if not self.params.video_skip: + codec = self.params.video_codec + crf = self.params.video_crf try: (FFmpeg() @@ -82,7 +82,7 @@ class Compress: except FFmpegError as e: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') self.utils.errors += 1 - if not self.config['FFMPEG']['HideErrors']: + if not self.params.hide_errors: self.printer.error(f"File {file} can't be processed! Error: {e}") else: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') @@ -90,20 +90,20 @@ class Compress: def image(self, folder, file, target_folder, extension): - quality = self.config['IMAGE']['Quality'] + quality = self.params.image_quality try: image = Image.open(f'{folder}/{file}') if (extension == "jpg" or extension == "jpeg" or - (extension == "webp" and not self.config['FFMPEG']['WebpRGBA'])): + (extension == "webp" and not self.params.webp_rgba)): if File.has_transparency(image): self.printer.warning(f"{file} has transparency. Changing to fallback...") - extension = self.config['IMAGE']['FallBackExtension'] + extension = self.params.image_fall_ext if File.has_transparency(image): image.convert('RGBA') - res_downscale = self.config['IMAGE']['ResDownScale'] + res_downscale = self.params.image_downscale if res_downscale != 1: width, height = image.size new_size = (int(width / res_downscale), int(height / res_downscale)) @@ -111,20 +111,20 @@ class Compress: image.save(self.utils.check_duplicates(f"{target_folder}/{os.path.splitext(file)[0]}.{extension}"), optimize=True, - lossless=self.config['IMAGE']['Lossless'], + lossless=self.params.image_lossless, quality=quality, minimize_size=True) self.printer.files(file, os.path.splitext(file)[0], extension, f"{quality}%") except Exception as e: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') self.utils.errors += 1 - if not self.config['FFMPEG']['HideErrors']: + if not self.params.hide_errors: self.printer.error(f"File {file} can't be processed! Error: {e}") return f'{target_folder}/{os.path.splitext(file)[0]}.{extension}' def unknown(self, folder, file, target_folder): - if self.config["FFMPEG"]["ForceCompress"]: + if self.params.force_compress: self.printer.unknown_file(file) try: (FFmpeg() @@ -135,7 +135,7 @@ class Compress: except FFmpegError as e: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') self.utils.errors += 1 - if not self.config['FFMPEG']['HideErrors']: + if not self.params.hide_errors: self.printer.error(f"File {file} can't be processed! Error: {e}") else: self.utils.add_unprocessed_file(f'{folder}/{file}', f'{target_folder}/{file}') @@ -145,15 +145,15 @@ class Compress: def compress(self, _dir, filename, source, output): match File.get_type(filename): case "audio": - out_file = self.audio(_dir, filename, output, self.config['AUDIO']['Extension']) + out_file = self.audio(_dir, filename, output, self.params.audio_ext) case "image": - out_file = self.image(_dir, filename, output, self.config['IMAGE']['Extension']) + out_file = self.image(_dir, filename, output, self.params.image_ext) case "video": - out_file = self.video(_dir, filename, output, self.config['VIDEO']['Extension']) + out_file = self.video(_dir, filename, output, self.params.video_ext) case "unknown": out_file = self.unknown(_dir, filename, output) - if self.config['FFMPEG']['MimicMode']: + if self.params.mimic_mode: try: os.rename(out_file, f'{_dir}/{filename}'.replace(source, f"{source}_compressed")) except FileNotFoundError: diff --git a/vnrecode/config.py b/vnrecode/config.py index a7cf0ed..4d1336c 100644 --- a/vnrecode/config.py +++ b/vnrecode/config.py @@ -1,8 +1,9 @@ -import os.path from argparse import Namespace, ArgumentParser from dataclasses import dataclass +from sysconfig import get_path from typing import Any import tomllib +import os @dataclass @@ -13,11 +14,30 @@ class Config: @classmethod def setup_config(cls): + default_config = os.path.join(get_path('purelib'), "vnrecode", "vnrecode.toml") parser = ArgumentParser(prog="vnrecode", description="Python utility to compress Visual Novel Resources" ) - parser.add_argument("source") - parser.add_argument("-c", "--config", default="vnrecode.toml") + parser.add_argument("source", help="SourceDir") + parser.add_argument("-c", "--config", default=default_config, help="ConfigFile") + parser.add_argument("-u", action="store_true", help="CopyUnprocessed") + parser.add_argument("-f", "--force", action="store_true", help="ForceCompress") + parser.add_argument("-m", "--mimic", action="store_true", help="MimicMode") + parser.add_argument("-s", "--silent", action="store_true", help="HideErrors") + parser.add_argument("--webprgba", action="store_true", help="WebpRGBA") + parser.add_argument("-j", "--jobs", type=int, help="Workers") + parser.add_argument("-ae", "--aext", help="Audio Extension") + parser.add_argument("-ab", "--abit", help="Audio Bitrate") + parser.add_argument("-id", "--idown", type=int, help="Image Downscale") + parser.add_argument("-ie", "--iext", help="Image Extension") + parser.add_argument("-ife", "--ifallext", help="Image Fallback Extension") + parser.add_argument("-il", "--ilossless", action="store_true", help="Image Lossless") + parser.add_argument("-iq", "--iquality", help="Image Quality") + parser.add_argument("--vcrf", help="Video CRF") + parser.add_argument("-vs", "--vskip", help="Video Skip") + parser.add_argument("-ve", "--vext", help="Video Extension") + parser.add_argument("-vc", "--vcodec", help="Video Codec") + args = parser.parse_args() if os.path.isfile(args.config): with open(args.config, "rb") as cfile: diff --git a/vnrecode/params.py b/vnrecode/params.py new file mode 100644 index 0000000..483d047 --- /dev/null +++ b/vnrecode/params.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass +from typing import Self + + +@dataclass +class Params: + + copy_unprocessed: bool + force_compress: bool + mimic_mode: bool + hide_errors: bool + webp_rgba: bool + workers: int + + audio_ext: str + audio_bitrate: str + + image_downscale: int + image_ext: str + image_fall_ext: str + image_lossless: str + image_quality: int + + video_crf: int + video_skip: bool + video_ext: str + video_codec: str + + @classmethod + def setup(cls, config, args) -> Self: + copy_unprocessed = config["FFMPEG"]["CopyUnprocessed"] if not args.u else args.u + force_compress = config["FFMPEG"]["ForceCompress"] if not args.force else args.force + mimic_mode = config["FFMPEG"]["MimicMode"] if not args.mimic else args.mimic + hide_errors = config["FFMPEG"]["HideErrors"] if not args.silent else args.silent + workers = config["FFMPEG"]["Workers"] if args.jobs is None else args.jobs + webp_rgba = config["FFMPEG"]["WebpRGBA"] if not args.webprgba else args.webprgba + audio_ext = config["AUDIO"]["Extension"] if args.aext is None else args.aext + audio_bitrate = config["AUDIO"]["BitRate"] if args.abit is None else args.abit + image_downscale = config["IMAGE"]["ResDownScale"] if args.idown is None else args.idown + image_ext = config["IMAGE"]["Extension"] if args.iext is None else args.iext + image_fall_ext = config["IMAGE"]["FallBackExtension"] if args.ifallext is None else args.ifallext + image_lossless = config["IMAGE"]["Lossless"] if not args.ilossless else args.ilossless + image_quality = config["IMAGE"]["Quality"] if args.iquality is None else args.iquality + video_crf = config["VIDEO"]["CRF"] if args.vcrf is None else args.vcrf + video_skip = config["VIDEO"]["SkipVideo"] if args.vskip is None else args.vskip + video_ext = config["VIDEO"]["Extension"] if args.vext is None else args.vext + video_codec = config["VIDEO"]["Codec"] if args.vcodec is None else args.vcodec + + return cls( + copy_unprocessed, force_compress, mimic_mode, hide_errors, webp_rgba, workers, + audio_ext, audio_bitrate, + image_downscale, image_ext, image_fall_ext, image_lossless, image_quality, + video_crf, video_skip, video_ext, video_codec + ) diff --git a/vnrecode/utils.py b/vnrecode/utils.py index 48df9e9..040da09 100644 --- a/vnrecode/utils.py +++ b/vnrecode/utils.py @@ -4,9 +4,9 @@ import os class Utils: - def __init__(self, config, printer): + def __init__(self, params, printer): self.errors = 0 - self.config = config + self.params = params self.printer = printer @staticmethod @@ -56,7 +56,7 @@ class Utils: self.get_compression(orig_folder, f"{orig_folder}_compressed") def add_unprocessed_file(self, orig_folder, new_folder): - if self.config['FFMPEG']['CopyUnprocessed']: + if self.params.copy_unprocessed: filename = orig_folder.split("/").pop() copyfile(orig_folder, new_folder) self.printer.info(f"File {filename} copied to compressed folder.")