feat: add wiki publishing tooling
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import argparse
|
||||
import base64
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
||||
ASSET_SOURCES = {
|
||||
"/vulture/vulture_shuttle_full_stack_original.png": "data/KSP/wiki-upload/vulture_shuttle_full_stack_at_launch_pad.jpg",
|
||||
"/vulture/vulture_shuttle_ignition_original.png": "data/KSP/wiki-upload/vulture_shuttle_ignition.jpg",
|
||||
"/vulture/vulture_ascent_original.png": "data/KSP/wiki-upload/vulture_ascent.jpg",
|
||||
"/vulture/vulture_booster_recovery_original.png": "data/KSP/wiki-upload/vulture_booster_recovery.jpg",
|
||||
"/vulture/vulture_booster_separation_original.png": "data/KSP/wiki-upload/vulture_booster_separation.jpg",
|
||||
"/echo/echo_takeoff_side_view.png": "data/KSP/wiki-upload-echo-current/echo_takeoff_side_view.png",
|
||||
"/echo/echo_mk2_cross_section.png": "data/KSP/wiki-upload-echo-current/echo_mk2_cross_section.png",
|
||||
"/echo/echo_vtvl.png": "data/KSP/wiki-upload-echo-current/echo_vtvl.png",
|
||||
"/echo/echo_approach_guanghan_station.png": "data/KSP/wiki-upload-echo-current/echo_approach_guanghan_station.png",
|
||||
"/echo/echo_high_energy_return_braking.png": "data/KSP/wiki-upload-echo-current/echo_high_energy_return_braking.png",
|
||||
"/enterprise/enterprise_takeoff_side_view.png": "data/KSP/wiki-upload-enterprise-current/enterprise_takeoff_side_view.png",
|
||||
"/enterprise/enterprise_payload_bay_view.png": "data/KSP/wiki-upload-enterprise-current/enterprise_payload_bay_view.png",
|
||||
"/enterprise/enterprise_dorsal_airframe_view.png": "data/KSP/wiki-upload-enterprise-current/enterprise_dorsal_airframe_view.png",
|
||||
}
|
||||
|
||||
HTML_FILES = [
|
||||
"data/wiki/vulture_shuttle_zh.html",
|
||||
"data/wiki/vulture_shuttle_en.html",
|
||||
"data/wiki/echo_shuttle_zh.html",
|
||||
"data/wiki/echo_shuttle_en.html",
|
||||
"data/wiki/enterprise_shuttle_zh.html",
|
||||
"data/wiki/enterprise_shuttle_en.html",
|
||||
]
|
||||
|
||||
IMG_RE = re.compile(r"<img\b[^>]*>", re.IGNORECASE)
|
||||
SRC_RE = re.compile(r'src="[^"]*"', re.IGNORECASE)
|
||||
ASSET_RE = re.compile(r'data-wiki-asset="([^"]+)"', re.IGNORECASE)
|
||||
|
||||
|
||||
def jpeg_name(asset_path):
|
||||
return asset_path.strip("/").replace("/", "__").rsplit(".", 1)[0] + ".jpg"
|
||||
|
||||
|
||||
def write_compressed_jpeg(repo_root, source, target, max_dimension, quality):
|
||||
image = Image.open(repo_root / source)
|
||||
image = ImageOps.exif_transpose(image)
|
||||
image.thumbnail((max_dimension, max_dimension), Image.Resampling.LANCZOS)
|
||||
if image.mode in ("RGBA", "LA"):
|
||||
background = Image.new("RGB", image.size, (255, 255, 255))
|
||||
background.paste(image, mask=image.getchannel("A"))
|
||||
image = background
|
||||
elif image.mode != "RGB":
|
||||
image = image.convert("RGB")
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
image.save(target, format="JPEG", quality=quality, optimize=True, progressive=True)
|
||||
|
||||
|
||||
def build_data_uris(repo_root, embed_dir, max_dimension, quality):
|
||||
data_uris = {}
|
||||
for asset_path, source in ASSET_SOURCES.items():
|
||||
source_path = repo_root / source
|
||||
if not source_path.exists():
|
||||
raise FileNotFoundError(f"Missing source image for {asset_path}: {source}")
|
||||
target = repo_root / embed_dir / jpeg_name(asset_path)
|
||||
write_compressed_jpeg(repo_root, source, target, max_dimension, quality)
|
||||
encoded = base64.b64encode(target.read_bytes()).decode("ascii")
|
||||
data_uris[asset_path] = f"data:image/jpeg;base64,{encoded}"
|
||||
print(f"embedded_source asset={asset_path} file={target.as_posix()} bytes={target.stat().st_size}")
|
||||
return data_uris
|
||||
|
||||
|
||||
def rewrite_img_tag(match, data_uris):
|
||||
tag = match.group(0)
|
||||
asset_match = ASSET_RE.search(tag)
|
||||
if not asset_match:
|
||||
return tag
|
||||
asset_path = asset_match.group(1)
|
||||
data_uri = data_uris.get(asset_path)
|
||||
if not data_uri:
|
||||
return tag
|
||||
if not SRC_RE.search(tag):
|
||||
return tag
|
||||
return SRC_RE.sub(f'src="{data_uri}"', tag, count=1)
|
||||
|
||||
|
||||
def restore_img_tag(match):
|
||||
tag = match.group(0)
|
||||
asset_match = ASSET_RE.search(tag)
|
||||
if not asset_match or not SRC_RE.search(tag):
|
||||
return tag
|
||||
return SRC_RE.sub(f'src="{asset_match.group(1)}"', tag, count=1)
|
||||
|
||||
|
||||
def rewrite_html(repo_root, html_files, data_uris):
|
||||
changed = 0
|
||||
for relative in html_files:
|
||||
path = repo_root / relative
|
||||
original = path.read_text(encoding="utf-8")
|
||||
updated = IMG_RE.sub(lambda match: rewrite_img_tag(match, data_uris), original)
|
||||
if updated != original:
|
||||
path.write_text(updated, encoding="utf-8", newline="\n")
|
||||
changed += 1
|
||||
count = updated.count('src="data:image/jpeg;base64,')
|
||||
print(f"html_updated file={relative} data_uri_count={count}")
|
||||
else:
|
||||
print(f"html_unchanged file={relative}")
|
||||
return changed
|
||||
|
||||
|
||||
def restore_html(repo_root, html_files):
|
||||
changed = 0
|
||||
for relative in html_files:
|
||||
path = repo_root / relative
|
||||
original = path.read_text(encoding="utf-8")
|
||||
updated = IMG_RE.sub(restore_img_tag, original)
|
||||
if updated != original:
|
||||
path.write_text(updated, encoding="utf-8", newline="\n")
|
||||
changed += 1
|
||||
count = updated.count('src="data:image/jpeg;base64,')
|
||||
print(f"html_restored file={relative} remaining_data_uri_count={count}")
|
||||
else:
|
||||
print(f"html_unchanged file={relative}")
|
||||
return changed
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Embed compressed image fallbacks in Wiki.js HTML pages.")
|
||||
parser.add_argument("--embed-dir", default="data/KSP/wiki-embed-current")
|
||||
parser.add_argument("--max-dimension", type=int, default=1600)
|
||||
parser.add_argument("--quality", type=int, default=82)
|
||||
parser.add_argument("--restore-asset-src", action="store_true", help="Restore image src attributes from data-wiki-asset paths")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
repo_root = Path.cwd()
|
||||
if args.restore_asset_src:
|
||||
changed = restore_html(repo_root, HTML_FILES)
|
||||
print(f"restore_summary html_changed={changed}")
|
||||
return 0
|
||||
data_uris = build_data_uris(repo_root, Path(args.embed_dir), args.max_dimension, args.quality)
|
||||
changed = rewrite_html(repo_root, HTML_FILES, data_uris)
|
||||
print(f"embed_summary assets={len(data_uris)} html_changed={changed}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user