import argparse
import hashlib
import json
import os
import subprocess
import tempfile
import zipfile
MANIFEST_NAME = "opencc-resource-manifest.json"
def parse_args():
parser = argparse.ArgumentParser(
description="Convert a text-backed resource zip to an ocd2-backed resource zip."
)
parser.add_argument(
"--input", required=True, help="Input resource zip file (text-backed)"
)
parser.add_argument(
"--output", required=True, help="Output resource zip file (ocd2-backed)"
)
parser.add_argument(
"--opencc-dict", required=True, help="Path to opencc_dict executable"
)
return parser.parse_args()
def convert_dict_references(value):
if isinstance(value, dict):
converted = {}
for key, child in value.items():
if key == "type" and child == "text":
converted[key] = "ocd2"
elif (
key == "file"
and isinstance(child, str)
and child.endswith(".txt")
):
converted[key] = child[:-4] + ".ocd2"
else:
converted[key] = convert_dict_references(child)
return converted
if isinstance(value, list):
return [convert_dict_references(child) for child in value]
return value
def sha256_hash(data):
return hashlib.sha256(data).hexdigest()
def main():
args = parse_args()
if not os.path.exists(args.input):
raise FileNotFoundError(f"Input zip file not found: {args.input}")
output_dir = os.path.dirname(args.output)
if output_dir:
os.makedirs(output_dir, exist_ok=True)
with zipfile.ZipFile(args.input, "r") as in_zip:
try:
manifest_content = in_zip.read(MANIFEST_NAME).decode("utf-8")
except KeyError:
raise ValueError(
f"Manifest file {MANIFEST_NAME} not found in the input zip"
)
manifest = json.loads(manifest_content)
in_names = in_zip.namelist()
new_entries = {}
out_contents = {} out_zipinfos = {}
with tempfile.TemporaryDirectory() as temp_dir:
txt_files = [name for name in in_names if name.endswith(".txt")]
for name in txt_files:
in_zip.extract(name, temp_dir)
for name in txt_files:
base_name = name[:-4]
ocd2_name = base_name + ".ocd2"
txt_path = os.path.join(temp_dir, name)
ocd2_path = os.path.join(temp_dir, ocd2_name)
subprocess.run(
[
args.opencc_dict,
"-i",
txt_path,
"-o",
ocd2_path,
"-f",
"text",
"-t",
"ocd2",
],
check=True,
)
with open(ocd2_path, "rb") as f:
ocd2_data = f.read()
out_contents[ocd2_name] = ocd2_data
orig_info = in_zip.getinfo(name)
info = zipfile.ZipInfo(ocd2_name)
info.date_time = orig_info.date_time
info.compress_type = zipfile.ZIP_STORED
info.external_attr = 0o644 << 16
out_zipinfos[ocd2_name] = info
new_entries[ocd2_name] = {
"sha256": sha256_hash(ocd2_data),
"size": len(ocd2_data),
}
json_files = [
name
for name in in_names
if name.endswith(".json") and name != MANIFEST_NAME
]
for name in json_files:
config_content = in_zip.read(name).decode("utf-8")
config = json.loads(config_content)
converted_config = convert_dict_references(config)
new_json_str = (
json.dumps(converted_config, ensure_ascii=False, indent=2)
+ "\n"
)
new_json_bytes = new_json_str.encode("utf-8")
out_contents[name] = new_json_bytes
orig_info = in_zip.getinfo(name)
info = zipfile.ZipInfo(name)
info.date_time = orig_info.date_time
info.compress_type = zipfile.ZIP_STORED
info.external_attr = 0o644 << 16
out_zipinfos[name] = info
new_entries[name] = {
"sha256": sha256_hash(new_json_bytes),
"size": len(new_json_bytes),
}
new_manifest = {
"manifest_version": manifest.get("manifest_version", 1),
"source_url": manifest.get("source_url", ""),
"commit_id": manifest.get("commit_id", ""),
"source_dirty": manifest.get("source_dirty", False),
"build_time_utc": manifest.get("build_time_utc", ""),
"hash_algorithm": "sha256",
"entries": new_entries,
}
new_manifest_str = (
json.dumps(
new_manifest, ensure_ascii=False, indent=2, sort_keys=True
)
+ "\n"
)
new_manifest_bytes = new_manifest_str.encode("utf-8")
with zipfile.ZipFile(args.output, "w") as out_zip:
for name in sorted(out_contents.keys()):
out_zip.writestr(out_zipinfos[name], out_contents[name])
manifest_info = zipfile.ZipInfo(MANIFEST_NAME)
manifest_info.date_time = in_zip.getinfo(MANIFEST_NAME).date_time
manifest_info.compress_type = zipfile.ZIP_STORED
manifest_info.external_attr = 0o644 << 16
out_zip.writestr(manifest_info, new_manifest_bytes)
if __name__ == "__main__":
main()