Skip to content

Commit

Permalink
pillow_png_tga_editor script calls can now contain multiple file or d…
Browse files Browse the repository at this point in the history
…irectory arguments.
  • Loading branch information
Shararamosh committed Dec 24, 2024
1 parent 35ba441 commit 1e66cec
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 43 deletions.
9 changes: 5 additions & 4 deletions localization/en_US/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
indexing_start: Indexing files...
indexing_stop: Indexing complete.
indexing_finish: Indexing complete.
files: Files
file_not_image: File %s is not recognized as image.
exception: "Exception for %s:"
Expand All @@ -9,8 +9,8 @@ converted_files: "Successfully converted files: %s."
failed_to_convert_files: "Failed to convert files:"
pending_removal_files: "The following files will be removed:"
pillow_png_tga_editor_name: Unreal Engine Import Texture Converter
pillow_png_tga_editor_desc: Batch converts image textures in the input directory and its sub-directories from any image format supported by PIL (Pillow) to PNG or RLE TGA depending on transparency.
input_path_arg: Input directory
pillow_png_tga_editor_desc: Batch converts image textures from any image format supported by PIL (Pillow) to PNG or RLE TGA depending on transparency.
input_paths_arg: Input files or directories
file_already_exists: File %s already exists.
mirror_concat_img_name: Image X-axis Mirror+Concat
mirror_concat_img_batch_name: Batch Image X-axis Mirror+Concat
Expand All @@ -19,4 +19,5 @@ image_files: Image files
select_image_files: Select image files
split_eyes_img_name: Eyes/Mouth Texture Splitter
split_eyes_img_desc: Splits image texture into 8 images each consisting of single eyes pair or mouth.
img_split_eyes_wrong_resolution: "File %s has unsupported resolution: %dx%d."
img_split_eyes_wrong_resolution: "File %s has unsupported resolution: %dx%d."
path_unknown: "%s is neither file, nor directory."
9 changes: 5 additions & 4 deletions localization/ru_RU/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
indexing_start: Индексирование файлов...
indexing_stop: Индексирование завершено.
indexing_finish: Индексирование завершено.
files: Файлы
file_not_image: Файл %s не распознан как изображение.
exception: "Исключение для %s:"
Expand All @@ -9,8 +9,8 @@ converted_files: "Успешно конвертировано файлов: %s."
failed_to_convert_files: "Не удалось конвертировать файлы:"
pending_removal_files: "Следующие файлы будут удалены:"
pillow_png_tga_editor_name: Unreal Engine Import Texture Converter
pillow_png_tga_editor_desc: Массово конвертирует текстуры из директории ввода и её под-директорий из любого формата изображений, поддерживаемого PIL (Pillow), в PNG или RLE TGA в зависимости от наличия прозрачности.
input_path_arg: Директория ввода
pillow_png_tga_editor_desc: Массово конвертирует текстуры из из любого формата изображений, поддерживаемого PIL (Pillow), в PNG или RLE TGA в зависимости от наличия прозрачности.
input_path_arg: Файлы и директории ввода
file_already_exists: Файл %s уже существует.
mirror_concat_img_name: Image X-axis Mirror+Concat
mirror_concat_img_batch_name: Batch Image X-axis Mirror+Concat
Expand All @@ -19,4 +19,5 @@ image_files: Файлы изображений
select_image_files: Выберите файлы изображений
split_eyes_img_name: Eyes/Mouth Texture Splitter
split_eyes_img_desc: Разделяет текстуру на 8 изображений, каждое из которых содержит одну пару глаз или рот.
img_split_eyes_wrong_resolution: "Файл %s имеет неподдерживаемое разрешение: %dx%d."
img_split_eyes_wrong_resolution: "Файл %s имеет неподдерживаемое разрешение: %dx%d."
path_unknown: "%s не является файлом или директорией."
70 changes: 35 additions & 35 deletions pillow_png_tga_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Скрипт, конвертирующий изображения в заданной директории из форматов, поддерживаемых библиотекой
PIL (Pillow) в форматы, читаемые Unreal Engine.
"""
# pylint: disable=import-error, line-too-long
# pylint: disable=import-error
import os
import sys
import argparse
Expand All @@ -25,22 +25,19 @@ def get_convertable_files(root_path: str) -> list[str]:
:return: Список путей к файлам.
"""
file_paths = []
logging.info(t("main.indexing_start"))
for subdir, _, files in os.walk(root_path):
for file in files:
file_path = os.path.abspath(os.path.join(subdir, file))
if os.path.splitext(file_path)[1].lower() not in SUPPORTED_EXTENSIONS:
continue
file_paths.append(file_path)
logging.info(t("main.indexing_stop"))
return file_paths


def batch_convert_files(root_path: str, file_paths: list[str]) -> list[
def batch_convert_files(file_paths: list[str]) -> list[
str]:
"""
Конвертация изображений в нужный формат при соблюдении условий.
:param root_path: Корневой путь к директории (для вычисления относительного пути к файлу).
:param file_paths: Список путей к файлам.
:return: Список путей к изображениям, которые нужно удалить.
"""
Expand All @@ -56,23 +53,21 @@ def batch_convert_files(root_path: str, file_paths: list[str]) -> list[
file_path in file_paths}
for future in as_completed(future_convert_file):
file_path = future_convert_file[future]
file_path_rel = os.path.relpath(file_path, root_path)
pbar.set_postfix_str(file_path_rel)
pbar.set_postfix_str(file_path)
try:
new_file_path = future.result()
except PIL.UnidentifiedImageError:
error_files.append(file_path)
logging.info(t("main.file_not_image"),
os.path.relpath(file_path, root_path))
logging.info(t("main.file_not_image"), file_path)
except FileExistsError as e:
already_exist_files.append(file_path)
logging.info(e)
except OSError as e:
logging.info(t("main.exception"), file_path_rel)
logging.info(t("main.exception"), file_path)
logging.info(e)
else:
if new_file_path != "":
logging.info("%s -> %s", file_path_rel, new_file_path[len(root_path) + 1:])
logging.info("%s -> %s", file_path, new_file_path)
if file_path not in obsolete_files:
obsolete_files.append(file_path)
if new_file_path in obsolete_files:
Expand All @@ -84,7 +79,7 @@ def batch_convert_files(root_path: str, file_paths: list[str]) -> list[
pbar.update(1)
pbar.set_postfix_str("")
pbar.close()
log_statistics(root_path, error_files, resave_success, already_exist_files, obsolete_files)
log_statistics(error_files, resave_success, already_exist_files, obsolete_files)
return obsolete_files


Expand All @@ -98,21 +93,19 @@ def convert_file(file_path: str) -> str:
return resave_img(PIL.Image.open(file_path))


def batch_remove_files(root_path: str, file_paths: list[str]):
def batch_remove_files(file_paths: list[str]):
"""
Удаление файлов.
:param root_path: Корневой путь к директории (для вычисления относительного пути к файлу).
:param file_paths: Список путей к файлам.
"""
with ThreadPoolExecutor() as executor:
future_remove_file = {executor.submit(remove_wrapper, file_path): file_path for file_path in
file_paths}
for future in as_completed(future_remove_file):
file_path = future_remove_file[future]
try:
future.result()
except OSError as e:
logging.info(t("main.exception_remove"), os.path.relpath(file_path, root_path))
logging.info(t("main.exception_remove"), future_remove_file[future])
logging.info(e)


Expand All @@ -124,53 +117,60 @@ def remove_wrapper(file_path: str):
os.remove(file_path)


def log_statistics(root_path: str, error_files: list[str], resave_success: int,
def log_statistics(error_files: list[str], resave_success: int,
already_exist_files: list[str], obsolete_files: list[str]):
"""
Печать статистики после прохода по файлам из директории.
:param root_path: Путь к изначальной директории.
:param error_files: Список с путями к файлам, к которым не удалось получить доступ.
:param resave_success: Количество файлов, которые были конвертированы.
:param already_exist_files: Список с путями к файлам, которые не удалось конвертировать, так как файл с новым путём уже существует.
:param already_exist_files: Список с путями к файлам, которые не удалось конвертировать, так как
файл с новым путём уже существует.
:param obsolete_files: Файлы, которые необходимо удалить после конвертации.
"""
if len(error_files) > 0:
logging.info(t("main.failed_to_open_files"))
for error_file in error_files:
logging.info(os.path.relpath(error_file, root_path))
logging.info(error_file)
if resave_success > 0:
logging.info(t("main.converted_files"), resave_success)
if len(already_exist_files) > 0:
logging.info(t("main.failed_to_convert_files"))
for already_exist_file in already_exist_files:
logging.info(os.path.relpath(already_exist_file, root_path))
logging.info(already_exist_file)
if len(obsolete_files) > 0:
logging.info(t("main.pending_removal_files"))
for obsolete_file in obsolete_files:
logging.info(os.path.relpath(obsolete_file, root_path))
logging.info(obsolete_file)


def execute_convert(root_path) -> str | int:
def execute_convert(input_paths: list[str]) -> str | int:
"""
Конвертация изображения в заданной директории из форматов, поддерживаемых библиотекой Pillow
в форматы, читаемые Unreal Engine.
Конвертация изображений из форматов, поддерживаемых библиотекой Pillow в форматы, читаемые
Unreal Engine.
:param input_paths: Список путей к файлам или директориям с файлами.
:return: Код ошибки или строка с ошибкой.
"""
if root_path == "" or not os.path.isdir(root_path):
if sys.platform == "win32":
return 144 # ERROR_DIR_NOT_ROOT
return os.EX_OK
file_paths = get_convertable_files(root_path)
obsolete_files = batch_convert_files(root_path, file_paths)
batch_remove_files(root_path, obsolete_files)
file_paths = []
logging.info(t("main.indexing_start"))
for input_path in input_paths:
if input_path == "":
continue
if os.path.isfile(input_path):
file_paths.append(input_path)
elif os.path.isdir(input_path):
file_paths += get_convertable_files(input_path)
file_paths = list(dict.fromkeys(file_paths))
logging.info(t("main.indexing_finish"))
obsolete_files = batch_convert_files(file_paths)
batch_remove_files(obsolete_files)
return os.EX_OK


if __name__ == "__main__":
init_app("images/Pillows_Hat_Icon.tga")
parser = argparse.ArgumentParser(prog=t("main.pillow_png_tga_editor_name"),
description=t("main.pillow_png_tga_editor_desc"))
parser.add_argument("input_path", nargs="?", type=str, default="",
help=t("main.input_path_arg"))
parser.add_argument("input_paths", nargs="*", type=str, default="",
help=t("main.input_paths_arg"))
args = parser.parse_args()
sys.exit(execute_convert(args.input_path if args.input_path != "" else askdirectory()))
sys.exit(execute_convert([askdirectory()] if len(args.input_paths) < 1 else args.input_paths))

0 comments on commit 1e66cec

Please sign in to comment.