Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
A lightweight Python CLI for repeatable creator workflows. Generate content ideas and tags,
or normalize a folder of image names from an interactive menu or scriptable subcommands.

![Animated terminal demo showing title generation, tag selection, and a dry-run image rename preview](https://raw.githubusercontent.com/HoungDev/creator-toolkit-cli/main/docs/assets/creator-toolkit-demo.gif)
![Short terminal animation showing title generation, tag selection, and a dry-run image rename preview](docs/assets/creator-toolkit-demo.gif)

<sub>The demo uses disposable sample files and a fixed seed for reproducible suggestions.</sub>
<sub>This seven-second loop uses real CLI output, disposable sample files, and a fixed seed for reproducible suggestions.</sub>

## Why Creator Toolkit?

Expand Down
Binary file modified docs/assets/creator-toolkit-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion docs/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

The animated README demo is generated from the current Creator Toolkit source. It calls the same
CLI entry logic shown to users, creates two non-sensitive sample image files in a temporary
directory, and removes that directory when rendering finishes.
directory, and removes that directory when rendering finishes. The renderer types each captured
command in short steps, reveals its real output line by line, and completes the loop in about seven
seconds.

## Rebuild the asset

Expand Down
50 changes: 45 additions & 5 deletions scripts/render_readme_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
WIDTH = 900
HEIGHT = 506
SEED = 2026
TYPE_STEPS = 5
TYPE_DURATION_MS = 90
REVEAL_DURATION_MS = 180
FINAL_HOLD_MS = 1400

BACKGROUND = "#0d1117"
PANEL = "#161b22"
Expand All @@ -28,6 +32,7 @@
PURPLE = "#a371f7"

Line = tuple[str, str]
Timeline = tuple[list[list[Line]], list[int]]


def _font(size: int, *, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
Expand Down Expand Up @@ -119,6 +124,38 @@ def _capture_scenes() -> list[tuple[str, list[Line]]]:
]


def _build_timeline(lines: list[Line]) -> Timeline:
"""Reveal a captured command and its output as a compact terminal animation."""
prompt_index = next(index for index, (kind, _) in enumerate(lines) if kind == "prompt")
prelude = lines[:prompt_index]
command = lines[prompt_index][1]
tail = lines[prompt_index + 1 :]

states: list[list[Line]] = []
durations: list[int] = []
checkpoints = sorted(
{
max(1, min(len(command), round(len(command) * step / TYPE_STEPS)))
for step in range(1, TYPE_STEPS + 1)
}
)
for checkpoint in checkpoints:
cursor = " ▌" if checkpoint < len(command) else ""
states.append([*prelude, ("prompt", command[:checkpoint] + cursor)])
durations.append(TYPE_DURATION_MS)

durations[-1] = REVEAL_DURATION_MS
visible = [*prelude, ("prompt", command)]
for line in tail:
visible.append(line)
if line[0] != "blank":
states.append(visible.copy())
durations.append(REVEAL_DURATION_MS)

durations[-1] = FINAL_HOLD_MS
return states, durations


def _draw_frame(label: str, lines: list[Line], step: int, total: int) -> Image.Image:
"""Draw one terminal-style frame."""
image = Image.new("RGB", (WIDTH, HEIGHT), BACKGROUND)
Expand Down Expand Up @@ -162,10 +199,13 @@ def _draw_frame(label: str, lines: list[Line], step: int, total: int) -> Image.I
def render(output: Path) -> None:
"""Capture CLI output and write the optimized looping GIF."""
scenes = _capture_scenes()
frames = [
_draw_frame(label, lines, step=index, total=len(scenes))
for index, (label, lines) in enumerate(scenes, start=1)
]
frames: list[Image.Image] = []
durations: list[int] = []
for index, (label, lines) in enumerate(scenes, start=1):
states, scene_durations = _build_timeline(lines)
frames.extend(_draw_frame(label, state, step=index, total=len(scenes)) for state in states)
durations.extend(scene_durations)

palette_frames = [
frame.quantize(colors=64, method=Image.Quantize.MEDIANCUT) for frame in frames
]
Expand All @@ -174,7 +214,7 @@ def render(output: Path) -> None:
output,
save_all=True,
append_images=palette_frames[1:],
duration=[2200, 2200, 3200],
duration=durations,
loop=0,
optimize=True,
disposal=2,
Expand Down