arivertisements/preview.py
Arija A. 62977a0d58
Hide HTML overflow in preview embed
Signed-off-by: Arija A. <ari@ari.lt>
2025-08-11 19:19:41 +03:00

203 lines
5.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Preview Arivertisements embed locally"""
import html
import http.server
import os
import socketserver
import sys
import threading
import typing as t
import webbrowser
from warnings import filterwarnings as filter_warnings
PORT: t.Final[int] = 8000
def parse_meta(filepath: str) -> t.Dict[str, str]:
"""Parse metadata file into a dictionary"""
meta: t.Dict[str, str] = {}
with open(filepath, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or ":" not in line:
continue
key, val = line.split(":", 1)
meta[key.lower()] = val.strip()
for rkey in ("filename", "alt", "author", "contact", "statement"):
if rkey not in meta:
raise ValueError(f"{rkey} missing in metadata {filepath!r}!")
return meta
def build_embed_html(meta: t.Dict[str, str], image_filename: str) -> str:
"""Build the exact embed HTML with metadata values injected"""
bg: str = meta.get("bg", "#fff")
fg: str = meta.get("fg", "#000")
description: str = f'{meta.get("alt")} (image by {meta.get("author")})'
license_: str = meta.get("license", "CC-BY-NC-SA 4.0")
alt_text: str = meta.get("alt", "Alternative text")
to_link: str = meta.get("to", "#")
author: str = meta.get("author", "Joe Doe")
contact: str = meta.get("contact", "example@example.com").replace(" at ", "@")
# statement: str = meta.get("statement", "")
source: t.Optional[str] = meta.get("source")
description_esc: str = html.escape(description)
license_esc: str = html.escape(license_)
alt_text_esc: str = html.escape(alt_text)
to_link_esc: str = html.escape(to_link)
author_esc: str = html.escape(author)
contact_esc: str = "".join(f"&#{ord(c)};" for c in contact)
# statement_esc = html.escape(statement)
# source_esc: str = html.escape(source) if source else ""
source_html: str = (
f'(<a href="{source}" rel="noopener noreferrer" target="_blank">source here</a>) '
if source
else ""
)
img_src = f"/img/{html.escape(image_filename)}"
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{html.escape(image_filename)}</title>
<meta name="description" content="{description_esc}" />
<meta name="author" content="{author_esc}" />
<meta name="license" content="{license_esc}" />
<meta name="theme-color" content="{bg}" />
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: {bg};
color: {fg};
}}
html {{
overflow: hidden;
}}
body {{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
font-family: monospace;
}}
p {{
font-size: 0.75em;
padding: 0.5ch 0 0 1ch;
height: 14px;
width: 100%;
text-align: center;
}}
img {{
display: block;
max-width: 100%;
width: 722px;
height: auto;
}}
</style>
</head>
<body>
<a href="{to_link_esc}" rel="noopener noreferrer" target="_blank">
<img src="{img_src}" loading="lazy" width="722" height="84" alt="{alt_text_esc}" />
</a>
<p><strong>&copy; <strong>{author_esc}</strong> &lt;<a href="mailto:{contact_esc}">contact</a>&gt; under {license_esc}. {source_html}| <a href="https://ad.ari.lt/" rel="noopener noreferrer" target="_blank">Arivertisements</a></strong></p>
</body>
</html>"""
class PreviewHandler(http.server.SimpleHTTPRequestHandler):
"""HTTP handler serving the preview HTML at /"""
def __init__(self, *args: t.Any, html_content: str = "", **kwargs: t.Any) -> None:
self.html_content = html_content
super().__init__(*args, **kwargs)
def do_GET(self) -> None:
if self.path in ("/", "/index.html"):
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(self.html_content.encode("utf-8"))
else:
super().do_GET()
def run_server(server: socketserver.TCPServer) -> None:
"""Run the HTTP server"""
server.serve_forever()
def main() -> int:
"""Entry point"""
if len(sys.argv) != 2:
print("Usage: python3 preview.py image-name.png")
return 1
image_name: str = sys.argv[1]
img_path: str = os.path.join("img", image_name)
meta_path: str = os.path.join("meta", os.path.splitext(image_name)[0] + ".txt")
if not os.path.isfile(img_path):
print(f"Image file '{img_path}' not found.")
return 1
if not os.path.isfile(meta_path):
print(f"Meta file '{meta_path}' not found.")
return 1
try:
meta: t.Dict[str, str] = parse_meta(meta_path)
except Exception as e:
print(f"Error parsing meta file: {e}")
return 1
html_content: str = build_embed_html(meta, image_name)
socketserver.TCPServer.allow_reuse_address = True
handler_class = lambda *args, **kwargs: PreviewHandler(
*args, html_content=html_content, **kwargs
)
with socketserver.TCPServer(("", PORT), handler_class) as httpd:
print(f"Serving preview at http://127.0.0.1:{PORT}")
thread: threading.Thread = threading.Thread(
target=run_server, args=(httpd,), daemon=True
)
thread.start()
webbrowser.open_new(f"http://127.0.0.1:{PORT}")
try:
while thread.is_alive():
thread.join(0.5)
except KeyboardInterrupt:
print("\nShutting down server...")
httpd.shutdown()
httpd.server_close()
return 0
return 0
if __name__ == "__main__":
assert main.__annotations__.get("return") is int, "main() should return an integer"
filter_warnings("error", category=Warning)
raise SystemExit(main())