Files
acdl/scripts/inline_images.py
Jon Chery 485d105bcd docs(P3a): inline images for self-contained HTML (REQ-268)
New scripts/inline_images.py (stdlib only: base64, re, mimetypes) —
base64-embeds all relative-path <img src='assets/...'> images into
the rendered HTML so it's redistributable without the assets/ folder.
MIME-sniffs by extension (.png->image/png, .svg->image/svg+xml, etc).
render_slides.sh Step 3 invokes it after the MARP HTML render, before
staging. Verified: 2 images inlined, 0 file-path refs remaining.

---ci---
project: acdl
phase: 3
milestone: v1.23
status: execute
phase_role: execution
---/ci---
2026-08-12 00:17:19 +00:00

72 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""scripts/inline_images.py — base64-embed all relative-path images in an
HTML file so it becomes self-contained (redistributable without the
assets/ folder).
Usage: python scripts/inline_images.py <html-path>
Stdlib only (base64, re, mimetypes, sys, pathlib).
"""
from __future__ import annotations
import base64
import mimetypes
import re
import sys
from pathlib import Path
IMG_SRC_RE = re.compile(
r'(<img\b[^>]*\bsrc=")(assets/[^"]+)("[^>]*>)',
re.IGNORECASE,
)
def _mime_for(path: Path) -> str:
ext = path.suffix.lower()
if ext == ".svg":
return "image/svg+xml"
guessed, _ = mimetypes.guess_type(str(path))
return guessed or "application/octet-stream"
def inline(html_path: Path) -> int:
html = html_path.read_text(encoding="utf-8")
repo_root = html_path.parent.parent.parent
count = 0
def replacer(match: re.Match[str]) -> str:
nonlocal count
prefix, rel_src, suffix = match.group(1), match.group(2), match.group(3)
img_path = html_path.parent / rel_src
if not img_path.exists():
print(f" WARNING: image not found: {rel_src}", file=sys.stderr)
return match.group(0)
mime = _mime_for(img_path)
data = base64.b64encode(img_path.read_bytes()).decode("ascii")
count += 1
return f'{prefix}data:{mime};base64,{data}{suffix}'
new_html = IMG_SRC_RE.sub(replacer, html)
if count > 0:
html_path.write_text(new_html, encoding="utf-8")
return count
def main() -> int:
if len(sys.argv) != 2:
print("Usage: python scripts/inline_images.py <html-path>", file=sys.stderr)
return 1
html_path = Path(sys.argv[1])
if not html_path.exists():
print(f"ERROR: {html_path} not found", file=sys.stderr)
return 1
count = inline(html_path)
print(f"Inlined {count} image(s) into {html_path}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())