573in1/tools/buildResourceArchive.py

172 lines
4.5 KiB
Python
Raw Normal View History

2023-05-30 18:08:52 +02:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "0.4.2"
2023-05-30 18:08:52 +02:00
__author__ = "spicyjpeg"
import json
2023-05-30 18:08:52 +02:00
from argparse import ArgumentParser, FileType, Namespace
from pathlib import Path
from typing import Any, ByteString
2023-05-30 18:08:52 +02:00
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
import lz4.block
from common.assets import *
from PIL import Image
2023-05-30 18:08:52 +02:00
## Main
def createParser() -> ArgumentParser:
parser = ArgumentParser(
description = \
"Parses a JSON file containing a list of resources to convert, "
"generates the respective files and packs them into a ZIP archive.",
add_help = False
)
group = parser.add_argument_group("Tool options")
group.add_argument(
"-h", "--help",
action = "help",
help = "Show this help message and exit"
)
group = parser.add_argument_group("Compression options")
2023-05-30 18:08:52 +02:00
group.add_argument(
"-c", "--compression",
type = str,
choices = ( "none", "deflate", "lz4" ),
default = "deflate",
help = "Set default compression algorithm (default DEFLATE)"
)
group.add_argument(
"-l", "--compress-level",
2023-05-30 18:08:52 +02:00
type = int,
default = 9,
help = "Set default DEFLATE and LZ4 compression level (default 9)",
2023-05-30 18:08:52 +02:00
metavar = "0-9"
)
group = parser.add_argument_group("File paths")
group.add_argument(
"-s", "--source-dir",
type = Path,
help = \
2023-05-30 18:08:52 +02:00
"Set path to directory containing source files (same directory as "
"resource list by default)",
metavar = "dir"
2023-05-30 18:08:52 +02:00
)
group.add_argument(
2024-06-03 23:12:31 +02:00
"configFile",
2023-05-30 18:08:52 +02:00
type = FileType("rt"),
2024-06-03 23:12:31 +02:00
help = "Path to JSON configuration file",
2023-05-30 18:08:52 +02:00
)
group.add_argument(
"output",
type = Path,
help = "Path to ZIP file to generate"
)
return parser
def main():
parser: ArgumentParser = createParser()
args: Namespace = parser.parse_args()
2024-06-03 23:12:31 +02:00
with args.configFile as _file:
configFile: dict[str, Any] = json.load(_file)
sourceDir: Path = \
args.source_dir or Path(_file.name).parent
2023-05-30 18:08:52 +02:00
2024-06-03 23:12:31 +02:00
assetList: list[dict[str, Any]] = configFile["resources"]
2023-05-30 18:08:52 +02:00
with ZipFile(args.output, "w", allowZip64 = False) as _zip:
for asset in assetList:
match asset.get("type", "file").strip():
case "empty":
2024-06-03 23:12:31 +02:00
data: ByteString = bytes(int(asset.get("size", 0)))
2023-05-30 18:08:52 +02:00
case "text":
with open(sourceDir / asset["source"], "rt") as _file:
data: ByteString = _file.read().encode("ascii")
case "binary":
with open(sourceDir / asset["source"], "rb") as _file:
data: ByteString = _file.read()
case "tim":
ix: int = int(asset["imagePos"]["x"])
iy: int = int(asset["imagePos"]["y"])
cx: int = int(asset["clutPos"]["x"])
cy: int = int(asset["clutPos"]["y"])
image: Image.Image = Image.open(sourceDir / asset["source"])
image.load()
if image.mode != "P":
image = image.quantize(
int(asset["quantize"]), dither = Image.NONE
2023-05-30 18:08:52 +02:00
)
data: ByteString = generateIndexedTIM(image, ix, iy, cx, cy)
case "metrics":
if "metrics" in asset:
metrics: dict = asset["metrics"]
else:
with open(sourceDir / asset["source"], "rt") as _file:
metrics: dict = json.load(_file)
data: ByteString = generateFontMetrics(metrics)
case "palette":
if "palette" in asset:
palette: dict = asset["palette"]
else:
with open(sourceDir / asset["source"], "rt") as _file:
palette: dict = json.load(_file)
data: ByteString = generateColorPalette(palette)
2023-05-30 18:08:52 +02:00
case "strings":
if "strings" in asset:
strings: dict = asset["strings"]
else:
with open(sourceDir / asset["source"], "rt") as _file:
strings: dict = json.load(_file)
data: ByteString = generateStringTable(strings)
case _type:
raise KeyError(f"unsupported asset type '{_type}'")
compressLevel: int | None = \
asset.get("compressLevel", args.compress_level)
match asset.get("compression", args.compression).strip():
case "none" | None:
_zip.writestr(asset["name"], data, ZIP_STORED)
2023-05-30 18:08:52 +02:00
case "deflate":
_zip.writestr(
asset["name"], data, ZIP_DEFLATED, compressLevel
)
case "lz4":
# ZIP archives do not "officially" support LZ4 compression,
# so the entry is stored as an uncompressed file.
compressed: bytes = lz4.block.compress(
data,
mode = "high_compression",
compression = compressLevel,
store_size = False
)
_zip.writestr(asset["name"], compressed, ZIP_STORED)
case _type:
raise KeyError(f"unsupported compression type '{_type}'")
2023-05-30 18:08:52 +02:00
if __name__ == "__main__":
main()