Eight-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, create and publish talk-content Agent Skills with talk pages to a Jekyll shownotes site, verify a recorded screencast against its storyboard, and edit a Camtasia screencast into a speaker-first video with corrected captions and chapters, all informed by a 113-entry Presentation Patterns taxonomy (83 observable: 64 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
#!/usr/bin/env python3
"""Render a proxy still for every framing cue in a shot plan, plus a contact sheet.
Each still is the exact frame SETTLE seconds after its cue, once the move has
finished (decoded from the start, because the TSCC2 screen codec does not seek
accurately), cropped to the planned framing,
with the picture-in-picture footprint outlined. Look at every still: a page
load or a correct crop in numbers is not a visual pass.
Usage:
framing-stills.py <recording.trec> <shot-plan.json> --out <dir> [--stream 0:0]
Writes <dir>/still-NN.png and <dir>/sheet.png. Exit 0 on success, 1 when
ffmpeg fails, 2 on usage error.
"""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
# The sibling module resolves only after the sys.path insert above.
import camtasia_model as model # noqa: E402
SAMPLE_FPS = 10
SETTLE = 0.4 # seconds after a cue, so the move has finished
def crop_for(
zoom: float, x: float, y: float, width: int, height: int, canvas: model.Canvas
) -> tuple[int, int, int, int]:
"""ffmpeg crop (w, h, x, y) in source pixels for a planned framing."""
x0, y0, x1, y1 = model.visible_rect(zoom, x, y, canvas)
return (
round((x1 - x0) * width),
round((y1 - y0) * height),
round(x0 * width),
round(y0 * height),
)
def cue_frames(plan: dict) -> list[tuple[int, list[float]]]:
frames = []
for shot in plan["shots"]:
if shot["kind"] != "screen":
continue
for cue in shot["cues"]:
# After the move settles, but never before the cue or past the shot.
t = max(cue[0], min(cue[0] + SETTLE, shot["end"] - 0.2))
frames.append((round(t * SAMPLE_FPS), cue))
return sorted(frames, key=lambda f: f[0])
def launch(command: list[str]) -> subprocess.CompletedProcess[str]:
try:
return subprocess.run(command, capture_output=True, text=True, check=False)
except OSError as e:
raise ValueError(
f"cannot run {command[0]} ({e}); install ffmpeg, e.g. `brew install ffmpeg`"
) from e
def run(command: list[str]) -> None:
result = launch(command)
if result.returncode != 0:
raise ValueError(f"{command[0]} failed: {result.stderr.strip()[-400:]}")
def clear_outputs(out: Path) -> None:
"""Remove this script's earlier outputs, so a shorter plan leaves no stale stills."""
for pattern in ("still-*.png", "raw-*.png", "sheet.png"):
for old in out.glob(pattern):
old.unlink()
def render(recording: Path, plan: dict, out: Path, stream: str) -> list[Path]:
"""Render into a staging directory; replace the previous set only on success.
A plan with no screen shots has nothing to frame and renders nothing.
"""
if not cue_frames(plan):
if out.is_dir():
clear_outputs(out)
return []
staging = out.with_name(f".{out.name}.staging")
if staging.exists():
shutil.rmtree(staging)
staging.mkdir(parents=True)
try:
produced = render_into(recording, plan, staging, stream)
except (ValueError, OSError):
shutil.rmtree(staging)
raise
out.mkdir(parents=True, exist_ok=True)
clear_outputs(out)
for item in [*produced, staging / "sheet.png"]:
item.replace(out / item.name)
shutil.rmtree(staging)
return [out / p.name for p in produced]
def render_into(recording: Path, plan: dict, out: Path, stream: str) -> list[Path]:
frames = cue_frames(plan)
probe = launch(
[
"ffprobe",
"-v",
"error",
"-select_streams",
stream.split(":", 1)[-1],
"-show_entries",
"stream=width,height",
"-of",
"json",
str(recording),
],
)
if probe.returncode != 0:
raise ValueError(f"ffprobe failed: {probe.stderr.strip()[-400:]}")
streams = json.loads(probe.stdout or "{}").get("streams") or []
if not streams or "width" not in streams[0]:
raise ValueError(f"no video stream {stream} in {recording}")
info = streams[0]
width, height = int(info["width"]), int(info["height"])
canvas = model.plan_canvas(plan)
select = "+".join(f"eq(n,{n})" for n, _ in frames)
raw = out / "raw-%03d.png"
run(
[
"ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(recording),
"-map",
stream,
"-vf",
f"fps={SAMPLE_FPS},select='{select}'",
"-fps_mode",
"passthrough",
str(raw),
]
)
inset = plan.get("inset", {})
box_h = round(inset.get("height", 324))
box_w = round(inset.get("width", box_h * 16 / 9))
box_x = round(canvas.width / 2 + inset.get("x", 640) - box_w / 2)
box_y = round(canvas.height / 2 - inset.get("y", -346) - box_h / 2)
stills = []
for i, (_, cue) in enumerate(frames, 1):
cw, ch, cx, cy = crop_for(cue[1], cue[2], cue[3], width, height, canvas)
still = out / f"still-{i:02d}.png"
run(
[
"ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(out / f"raw-{i:03d}.png"),
"-vf",
f"crop={cw}:{ch}:{cx}:{cy},scale={int(canvas.width)}:{int(canvas.height)},"
f"drawbox=x={box_x}:y={box_y}:w={box_w}:h={box_h}:color=red@0.6:t=6",
str(still),
]
)
(out / f"raw-{i:03d}.png").unlink()
stills.append(still)
columns = 3
rows = -(-len(stills) // columns)
run(
[
"ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(out / "still-%02d.png"),
"-vf",
f"scale=960:-1,tile={columns}x{rows}",
"-frames:v",
"1",
str(out / "sheet.png"),
]
)
return stills
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").split("\n")[0])
parser.add_argument("recording", type=Path)
parser.add_argument("plan", type=Path)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--stream", default="0:0")
args = parser.parse_args(argv)
try:
plan = model.load_plan(args.plan)
except ValueError as e:
print(f"framing-stills: {e}", file=sys.stderr)
return 2
try:
stills = render(args.recording, plan, args.out, args.stream)
except ValueError as e:
print(f"framing-stills: {e}", file=sys.stderr)
return 1
except OSError as e:
print(
f"framing-stills: cannot write stills under {args.out}: {e}; choose a writable --out",
file=sys.stderr,
)
return 1
sheet = str(args.out / "sheet.png") if stills else None
print(json.dumps({"stills": [str(s) for s in stills], "sheet": sheet}))
return 0
if __name__ == "__main__":
sys.exit(main()).tessl-plugin
rules
skills
illustrations
presentation-creator
references
patterns
build
deliver
prepare
scripts
screencast-editor
screencast-recorder
shownotes-publisher
vault-clarification
vault-ingress
references
scripts
vault-profile