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
"""Check that the pointer stays inside the framed view of every screen shot.
A zoom that moves the row the presenter is pointing at out of frame looks fine
in every still, because the pointer is not in the screen pixels. This replays
the recorded pointer path against the shot plan's framing.
Usage:
audit-framing.py <shot-plan.json> <pointer.json> [--step 0.25]
A sample outside the view is `pointing` when the pointer has come to rest there
after moving during this shot: the presenter put it on something the framing
cuts off. A pointer still where an earlier shot left it is `resting`, and one
travelling into frame is neither; neither is a framing error. Stdout: JSON per screen shot. Exit 0 when no shot has a
`pointing` miss, 1 when one does, 2 on usage error.
"""
from __future__ import annotations
import argparse
import bisect
import json
import math
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
def audit(plan: dict, samples: list[list[float]], step: float = 0.25) -> list[dict]:
canvas = model.plan_canvas(plan)
times = [s[0] for s in samples]
def pointer_at(t: float) -> tuple[float, float]:
i = max(0, bisect.bisect_right(times, t) - 1)
return samples[i][1], samples[i][2]
def moved_between(a: float, b: float) -> bool:
lo = max(0, bisect.bisect_right(times, a) - 1)
hi = bisect.bisect_right(times, b)
return len({(s[1], s[2]) for s in samples[lo:hi]}) > 1
report = []
for shot in plan["shots"]:
if shot["kind"] != "screen":
continue
cues = shot["cues"]
misses = []
t = shot["start"]
while t < shot["end"]:
if any(0 < c[0] - t < model.MOVE_SECONDS for c in cues[1:]):
t += step
continue
cue = [c for c in cues if c[0] <= t][-1]
x0, y0, x1, y1 = model.visible_rect(cue[1], cue[2], cue[3], canvas)
px, py = pointer_at(t)
if not (x0 <= px <= x1 and y0 <= py <= y1):
if moved_between(t - step, t):
kind = "travelling"
elif moved_between(shot["start"], t):
kind = "pointing"
else:
kind = "resting"
misses.append({"t": round(t, 2), "pointer": [px, py], "kind": kind})
t += step
report.append(
{
"label": shot.get("label", ""),
"start": shot["start"],
"pointing": [m for m in misses if m["kind"] == "pointing"],
"resting_samples": sum(m["kind"] == "resting" for m in misses),
}
)
return report
def load_samples(path: Path) -> list[list[float]]:
"""Pointer samples from trec-pointer.py; refuses an empty or malformed path."""
try:
samples = json.loads(path.read_text(encoding="utf-8")).get("samples")
except (OSError, json.JSONDecodeError, AttributeError) as e:
raise ValueError(f"cannot read pointer samples {path}: {e}") from e
if not isinstance(samples, list) or not samples:
raise ValueError(f"{path} holds no pointer samples; rerun trec-pointer.py")
for row in samples:
if not (
isinstance(row, list)
and len(row) == 3
and all(
isinstance(v, (int, float))
and not isinstance(v, bool)
and math.isfinite(v)
for v in row
)
):
raise ValueError(
f"{path}: every sample must be [seconds, x, y] finite numbers, got {row!r}"
)
if any(b[0] < a[0] for a, b in zip(samples, samples[1:])):
raise ValueError(
f"{path}: samples must be in time order; regenerate it with trec-pointer.py"
)
return samples
def positive_seconds(value: str) -> float:
step = float(value)
if not math.isfinite(step) or step <= 0:
raise argparse.ArgumentTypeError("--step must be a positive number of seconds")
return step
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").split("\n")[0])
parser.add_argument("plan", type=Path)
parser.add_argument("pointer", type=Path)
parser.add_argument("--step", type=positive_seconds, default=0.25)
args = parser.parse_args(argv)
try:
plan = model.load_plan(args.plan)
samples = load_samples(args.pointer)
except (ValueError, OSError, KeyError) as e:
print(f"audit-framing: {e}", file=sys.stderr)
return 2
report = audit(plan, samples, args.step)
print(json.dumps(report, indent=1))
failed = [r for r in report if r["pointing"]]
for r in failed:
first = r["pointing"][0]
print(
f"audit-framing: '{r['label']}' frames out the pointer at {first['t']}s "
f"(pointer at {first['pointer']}); hold a framing that contains it",
file=sys.stderr,
)
return 1 if failed else 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