CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

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

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

transcript.pyskills/screencast-editor/scripts/

#!/usr/bin/env python3
"""Export Camtasia's word-level transcript of a take, for planning cuts.

Camtasia recognizes speech when dynamic captions are added, and stores one
keyframe per word on the source audio track. Its word onsets were measured more
accurate than Whisper's, so shot boundaries are planned from these.

Usage:
    transcript.py <project.cmproj | project.tscproj>

Stdout: {"words": [[seconds, word], ...], "sentences": [{"start", "end", "text"}]}
Times are source seconds. Exit 0 on success, 1 when the project has no
transcript, 2 on usage error.
"""

from __future__ import annotations

import argparse
import json
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 sentences(words: list[tuple[float, str]]) -> list[dict]:
    """Group words into sentences; `end` is the onset of the next sentence.

    The last sentence has no `end`: nothing after it marks where its final word
    stops, and inventing one would cut that word off.
    """
    out: list[dict] = []
    current: list[tuple[float, str]] = []
    for t, w in words:
        current.append((t, w))
        if model.SENTENCE_END.search(w):
            out.append(
                {"start": current[0][0], "text": " ".join(x for _, x in current)}
            )
            current = []
    if current:
        out.append({"start": current[0][0], "text": " ".join(x for _, x in current)})
    for a, b in zip(out, out[1:]):
        a["end"] = b["start"]
    return out


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=(__doc__ or "").split("\n")[0])
    parser.add_argument("project", type=Path)
    args = parser.parse_args(argv)
    try:
        project = model.load_project(model.project_file(args.project))
        words = model.transcript_words(project)
    except ValueError as e:
        print(f"transcript: {e}", file=sys.stderr)
        return 1
    json.dump(
        {"words": [[round(t, 3), w] for t, w in words], "sentences": sentences(words)},
        sys.stdout,
        indent=1,
    )
    print()
    return 0


if __name__ == "__main__":
    sys.exit(main())

skills

README.md

tile.json