Files
045-scrape-movies/tools/render_terminal_png.py
2026-10-28 14:14:15 +08:00

76 lines
2.4 KiB
Python

"""@brief Render a command and its output as a terminal-style PNG screenshot.
The screenshots of the README and the wiki are made with this script, so no
desktop is captured. Needs Pillow (`pip install -e ".[dev]"`).
"""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
BACKGROUND = (30, 30, 30)
TITLE_BAR = (50, 50, 50)
TEXT = (212, 212, 212)
PROMPT = (96, 200, 96)
FONT_SIZE = 16
PADDING = 16
BAR_HEIGHT = 30
MAX_COLUMNS = 110
MAX_LINES = 40
FONT_FILES = (
"consola.ttf",
"DejaVuSansMono.ttf",
"LiberationMono-Regular.ttf",
"Menlo.ttc",
)
def _load_font() -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
for name in FONT_FILES:
try:
return ImageFont.truetype(name, FONT_SIZE)
except OSError:
continue
return ImageFont.load_default()
def _fit(lines: list[str]) -> list[str]:
fitted = [
line if len(line) <= MAX_COLUMNS else line[: MAX_COLUMNS - 3] + "..."
for line in lines
]
if len(fitted) > MAX_LINES:
fitted = fitted[:MAX_LINES] + ["..."]
return fitted
def render(command: str, output: str, target: Path) -> None:
"""@brief Draw the command and its output and save the PNG.
@param command The command line shown after the prompt.
@param output The text the command printed.
@param target The PNG file to write.
"""
font = _load_font()
lines = _fit(output.rstrip().splitlines())
prompt = "$ "
columns = max([len(prompt + command), *(len(line) for line in lines)])
char_width = font.getlength("M")
line_height = FONT_SIZE + 6
width = int(columns * char_width) + 2 * PADDING
height = BAR_HEIGHT + 2 * PADDING + line_height * (len(lines) + 1)
image = Image.new("RGB", (max(width, 480), height), BACKGROUND)
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, image.width, BAR_HEIGHT), fill=TITLE_BAR)
for index, color in enumerate(((255, 95, 86), (255, 189, 46), (39, 201, 63))):
x = PADDING + index * 22
draw.ellipse((x, 9, x + 12, 21), fill=color)
y = BAR_HEIGHT + PADDING
draw.text((PADDING, y), prompt, font=font, fill=PROMPT)
draw.text((PADDING + font.getlength(prompt), y), command, font=font, fill=TEXT)
for line in lines:
y += line_height
draw.text((PADDING, y), line, font=font, fill=TEXT)
target.parent.mkdir(parents=True, exist_ok=True)
image.save(target)