150 lines
6.2 KiB
Python
150 lines
6.2 KiB
Python
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())
|