Reusable Python library and CLI for narrated demo videos built around Manim, TTS (OpenAI or xAI Grok), and ffmpeg composition. Aimed at long-form, scripted explainers that walk through how a system works.
Prose + PlantUML sources for how Courseforge repositories fit together live under docs/suite/. Regenerate PNGs with ./scripts/render-suite-diagrams.sh — Java, Graphviz (dot), and the vendored JAR in third_party/plantuml/ (CI installs Graphviz and runs the same script; see .github/workflows/render-suite-diagrams.yml). The rendered site at courseforge.github.io pulls this tree on each publish from courseforge/infrastructure.
docgen no longer ships any Playwright-driven UI demo path. The previous
demo-function, playwright, discover-tests, vhs, tape-lint, sync-vhs,
per-function-*, and catalog commands — together with their config blocks
(vhs:, playwright:, playwright_test:, discover_tests:, catalog:,
per_function:) and the playwright, playwright_test, and vhs visual_map
types — have been removed.
Why: a UI-test-driven recorder turned out to be a fragile, project-specific concern
that pulled pytest-playwright, Node Playwright, VHS / ttyd, browser binaries,
trace parsing, and a discovery catalog into a generic library. The same goal is now
being prototyped in a consumer project (CourseForge tools/courseforge/demogen/)
with the “LLM emits a validated automation spec, a deterministic runner translates
it to Playwright” pattern. Once that contract stabilises a small portion may be
backported into docgen, but docgen itself stays Playwright-free.
If you still need the legacy behaviour, pin a pre-removal commit
(pip install docgen @ git+https://github.com/jmjava/documentation-generator.git@<sha>).
- TTS narration — generate MP3 audio from Markdown scripts via OpenAI
gpt-4o-mini-tts, or xAI/v1/ttswhenai.providerisgrok. - Word-level timestamps without Whisper — the default
localengine aligns the known narration text against the TTS mp3 offline (ffmpegsilencedetect- proportional interpolation); no API call or transcription. Network
transcription (
timestamps.engine: whisper) uses OpenAIwhisper-1or xAI/v1/sttwhen the provider is Grok. Both engines write the sametiming.jsonshape.
- proportional interpolation); no API call or transcription. Network
transcription (
- Manim animations (default: declarative scene specs) — primary visual surface.
Prefer
animations/specs/*.scene.yamlviadocgen scene-spec-generatescene-compile. Ongenerate-all, if no specs exist yet, the pipeline auto-runs scene-spec-generate; later runs retime-compile specs against freshtiming.json(no OpenAI) unless you pass--regen-scene-specs. Specs auto-paginate when they exceed the frame stack budget, reject unsafe width / subject-beat gaps, and map each paced label to await_wordindex. Hand-maintained custom Manim classes may still live inanimations/scenes.pyoutsideBEGIN/END GENERATED SCENEmarkers (use--skip-scene-retimeto bypass the declarative stage).
- Image assets in Manim scenes — a scene-spec box may be an image
element (
image: images/<name>.png+prompt:);docgen image-generaterenders the prompt via OpenAI Images (defaultgpt-image-1) or xAI Imagine (grok-imagine-image-2.0whenai.providerisgrok). The compiled scene shows it with the_imagehelper.generate-allfills in missing assets automatically. - ffmpeg composition — combine narration audio and Manim video into final segments, with a freeze-tail guard.
- Validation — A/V drift, freeze ratio, OCR error scan, layout, narration lint,
Manim scene lint, timing_sync (stale
timing.jsonvs regenerated mp3 — hard fail), story_end (paced visual story finishes long before narration — hard fail), and av_sync (OCR check that scene-spec label anchors appear on screen near their spoken time — soft warning). - GitHub Pages — auto-generate
index.html, deploy workflow, LFS rules,.gitignore. - Wizard — local web GUI to bootstrap narration scripts from existing project docs.
No IDE lock-in: maintenance workflows are docgen CLI + YAML + shell/CI (and
an LLM/TTS provider where a command calls the API). The wizard is a local Flask
app, not a plugin tied to one editor.
Install docgen once (venv / pipx / this Cloud environment). Pass the consumer
checkout or clone URL; nothing from this library is copied into that project's
src/.
# Local checkout of the product repo
docgen --repo /path/to/course-builder init --defaults
docgen --repo /path/to/course-builder yaml-generate
docgen --repo /path/to/course-builder generate-all
# GitHub URL or org/repo shorthand (shallow clone into DOCGEN_REPO_CACHE)
docgen --repo github.com/acme/course-builder generate-all
# equivalent: DOCGEN_REPO=acme/course-builder docgen generate-all
# pasted GitHub page URLs work: https://github.com/acme/app/tree/main--repo looks for docs/demos/docgen.yaml (then any other docgen.yaml under
the checkout). repo_root in that yaml still points at the consumer so
narration/scene prompts read their sources.
The same docgen CLI runs in Cursor Cloud automation, local Cursor,
and Claude Code / Copilot / a plain shell. It is not an editor plugin.
docgen ai-status prints the resolved provider and which env var supplied
the key (never the secret).
| Host | What to set |
|---|---|
| Cursor Cloud | CURSOR_API_KEY is injected. Cloud OPENAI_API_KEY=crsr_… is skipped. |
| Local Cursor | OPENAI_API_KEY in .env (or CURSOR_API_KEY if you have one). |
| Claude Code / Copilot / CI | OPENAI_API_KEY in .env. Or only ANTHROPIC_API_KEY for chat (narration-generate, scene-spec-generate). TTS and images still need OpenAI or Grok. |
| Optional Grok | ai.provider: grok and XAI_API_KEY. |
Default provider is OpenAI Chat/Images. Auth order: CURSOR_API_KEY, then
OPENAI_API_KEY. If neither is usable and ANTHROPIC_API_KEY is set, chat
switches to Claude automatically.
Image models stay the same knob as OpenAI — they are not remapped when using the Cursor key:
image_generation:
model: gpt-image-1 # or dall-e-3, gpt-image-1-mini, …
size: 1536x1024
# quality: highdocgen ai-status
docgen image-generate --all --model gpt-image-1
docgen image-generate --all --model dall-e-3 --size 1024x1024To substitute Grok for chat, TTS, Whisper, and image calls:
ai:
provider: grok # openai | grok | anthropicexport DOCGEN_AI_PROVIDER=grok # overrides yaml
export XAI_API_KEY=xai-...
docgen --repo /path/to/consumer generate-allExisting OpenAI model names in YAML are remapped at call time (gpt-4o →
grok-4.6, gpt-image-1 → grok-imagine-image-2.0, TTS voice coral →
eve). Chat and images use the OpenAI SDK at https://api.x.ai/v1. TTS and
STT use xAI POST /v1/tts and POST /v1/stt. Keep timestamps.engine: local
unless you specifically want network STT.
A Cursor Cloud environment that already has ffmpeg / tesseract / Manim build
deps can run the full pipeline using the injected CURSOR_API_KEY. Local
Cursor and Claude Code use .env (OPENAI_API_KEY, or ANTHROPIC_API_KEY for
chat). Optional: XAI_API_KEY for Grok. Grant the consumer as a repository
dependency if you clone by URL.
docgen is a pip-installable CLI/library. Consumer projects keep only a
bundle (docs/demos/docgen.yaml, hints, narration, …). They should not
copy or submodule this repository into application source.
# Project / CI venv
pip install 'docgen @ git+https://github.com/jmjava/documentation-generator.git'
# Prefer a SHA pin in CI:
# pip install 'docgen @ git+https://github.com/jmjava/documentation-generator.git@<sha>'
# Optional extras
pip install 'docgen[manim] @ git+https://github.com/jmjava/documentation-generator.git'
pip install 'docgen[gui] @ git+https://github.com/jmjava/documentation-generator.git' # pywebview desktop window
# Freeze the Vue GUI only (not the full Manim CLI): pip install 'docgen[packaging]' && pyinstaller packaging/docgen-gui.spec
# Isolated global CLI (no project venv)
pipx install 'docgen @ git+https://github.com/jmjava/documentation-generator.git'
# or: uv tool install 'docgen @ git+https://github.com/jmjava/documentation-generator.git'
docgen --versiondocgen init writes requirements-docgen.txt + a bundle README.md with the same guidance.
git clone https://github.com/jmjava/documentation-generator.git
cd documentation-generator
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Optional: pip install -e ".[gui]" # pywebview window for `docgen gui`
# Optional: pip install -e ".[packaging]" # PyInstaller (see packaging/README.md)
pytest
docgen benchmark # required: scene-timing corpus vs committed baselineCI runs ruff, pytest, and a required benchmark job (docgen benchmark).
CI also installs ffmpeg and tesseract via apt for unit tests — see .github/workflows/ci.yml.
Roadmap: milestones/README.md.
From inside the consumer bundle (library already on PATH):
cd your-project/docs/demos # bundle only — library is on PATH via pip
pip install -r requirements-docgen.txt # after docgen init, or use the pip line above
docgen wizard # optional: bootstrap narration from project docs
docgen generate-all # TTS → timestamps → scene retime → Manim → compose → validate
docgen validate --pre-pushFrom a machine / Cloud environment that has docgen installed but is not the consumer repo:
docgen --repo /path/to/your-project generate-all| Command | Description |
|---|---|
docgen --repo PATH_OR_URL … |
Target a consumer checkout or clone URL (also DOCGEN_REPO). Finds docs/demos/docgen.yaml. Does not copy this library into the consumer src/ |
docgen --version |
Show installed version + recommended pip install line (external tool) |
docgen init [TARGET_DIR] [--defaults] [--segments-file FILE] |
Scaffold a bundle: docgen.yaml, requirements-docgen.txt, wrapper scripts, directories |
docgen wizard [--port 8501] |
Local web GUI: focus files, revise narration, asset freshness / rebuild-from-here, Vue Benchmark view, and a Tool tab to upgrade the installed docgen package (pip) + rewrite requirements-docgen.txt |
docgen gui [--view benchmark] [--browser] [--smoke] |
Desktop GUI (Vue + Flask). Install docgen[gui] for a pywebview window; --browser uses the system browser; --smoke is a headless HTTP check |
docgen freeze [--dist DIR] [--smoke] |
PyInstaller onedir for docgen-gui only (pip install 'docgen[packaging]'). Not the full Manim CLI |
docgen tts [--segment 01] [--dry-run] |
Generate TTS audio |
docgen timestamps [--engine local|whisper] |
Extract word/segment timestamps from TTS audio → timing.json (default local: offline narration-text alignment; whisper: OpenAI transcription) |
docgen image-generate [--segment 01 | --all | --spec PATH] [--force] [--dry-run] [--model …] [--size …] |
Generate scene-spec image assets (image: + prompt: boxes) via the OpenAI Images API into the bundle |
docgen manim [--scene StackDAGScene] |
Render Manim animations |
docgen compose [01 02 03] [--ffmpeg-timeout 900] |
Compose segments (audio + video) |
docgen validate [--max-drift 2.75] [--pre-push] |
Run all validation checks |
docgen lint [--segment 01] |
Narration lint only |
docgen concat [--config full-demo] |
Concatenate full demo files |
docgen pages [--force] |
Generate index.html, pages.yml, .gitattributes, .gitignore |
docgen generate-all [--skip-tts] [--skip-manim] [--retry-manim] [--regen-scene-specs] [--skip-scene-retime] |
Full pipeline: TTS → timestamps → scene specs → Manim → compose → validate. Missing animations/specs/*.scene.yaml auto-generates; existing specs retime offline; --regen-scene-specs forces LLM rewrite |
docgen rebuild-after-audio [--regen-scene-specs] |
Timestamps → scene retime → Manim → compose → validate (skips TTS) |
docgen clean-bundle [-y] [--delete-config] [--keep-narration] |
Remove regenerable outputs under the bundle |
docgen narration-generate --segment 01 [--extra-path REL] [--hint TEXT] [--dry-run] [--force] [--revise --revision-notes TEXT] |
Generate narration .md from repo sources + owner hints (OpenAI); --revise edits the existing script in place |
docgen yaml-generate [--merge-defaults] [--llm] [--dry-run] [--list-gaps] |
Merge defaults into docgen.yaml; optional OpenAI refresh of tts.instructions / wizard.system_prompt (rewrites the file — review in Git) |
docgen scene-compile [SPEC.scene.yaml | --all] [--retime] [--dry-run] |
Compile declarative scene YAML into animations/scenes.py. --all --retime re-derives wait_word from current timing.json with no OpenAI; unmatched labels fail closed (or set pace: none) |
docgen scene-spec-generate [--segment 01 | --all] [--compile] [--print-only] [--output PATH] [--hint …] [--model …] |
Call OpenAI to emit YAML only (same schema as scene-compile); rejects frame-budget overflow and subject-beat coverage failures (hold board on same topic; cover topic shifts; no invented labels — not a blind count); auto-paginate + word-alignment; optionally writes animations/specs/<stem>.scene.yaml and --compiles into scenes.py |
docgen benchmark [--case ID] [--format text|json] [--update-baseline] [--gui] |
Score the standard scene-timing corpus (execute compiled construct() on the real _TimedScene clock, no Manim). Diffs a committed baseline so clock changes are measured, not guessed. --gui opens the Vue view |
Create a docgen.yaml in your demos directory. Use docgen init to scaffold
a fresh layout, then docgen yaml-generate to fill in defaults from the files
already on disk. (docgen yaml-generate also keeps
manim_scene_generation.segments in step with visual_map.)
The visual_map key is maintainer-owned per-segment wiring. Supported types
are manim, mixed, still, and image.
If docgen.yaml sets env_file (often .env), variables are loaded with
shell-first semantics: anything already exported in the process (including
your IDE or CI) is not replaced by the file. To make the file win, set
DOCGEN_ENV_OVERRIDES=1 so every key from env_file overwrites the
environment, or DOCGEN_ENV_OVERRIDES=OPENAI_API_KEY,OTHER_KEY for specific
keys only.
When CURSOR_API_KEY, OPENAI_API_KEY, XAI_API_KEY, or ANTHROPIC_API_KEY is present in both the shell and env_file,
docgen prints a one-line hint to stderr so a silent 401 from the wrong key is
easier to diagnose.
Under narration_from_source in docgen.yaml, the project owner lists
optional hints (strings) that steer the model (audience, terminology, what to
avoid). The chat model generates the narration .md from your repo context
(context.paths / context.globs, relative to repo_root) plus those hints; the
result is what docgen tts reads. See docgen.narrate_from_source.
narration_from_source:
model: gpt-4o-mini
temperature: 0.65
max_context_bytes: 120000
hints:
- "Audience: contributors new to this repo."
- "Do not mention unreleased product codenames."
context:
paths:
- README.md
globs:
- "src/**/*.py"
segments:
"01":
hints:
- "This segment covers the install wizard only."
context:
paths:
- docs/install.mdvalidation:
max_drift_sec: 2.75
max_freeze_ratio: 0.25 # trailing-frame pad vs narration length (compose freeze guard + validate)
timing_sync: # audio ↔ timing.json staleness (hard fail in --pre-push)
enabled: true
max_tail_gap_sec: 3.0 # mp3 may run this much past the last transcribed word
max_end_overrun_sec: 1.0 # transcript may extend this far past the mp3
story_end: # last paced reveal vs audio end (hard fail in --pre-push)
enabled: true
max_early_sec: 40.0 # idle after last paced box
max_early_ratio: 0.45 # and idle / audio_end (both must exceed to fail)
av_sync: # OCR anchor check (soft warning in --pre-push)
enabled: true
tolerance_sec: 3.0
prefer_scene_spec_labels: true # OCR anchors from paced box labels when specs exist
visual_types: [manim] # only check types with on-screen text
```yaml
ai:
provider: openai # openai | grok (xAI). Override with DOCGEN_AI_PROVIDER.
timestamps:
engine: local # local (default, offline) or whisper (OpenAI whisper-1 / xAI STT)
silence_noise_db: -35.0 # ffmpeg silencedetect threshold for the local engine
min_silence_sec: 0.3
image_generation: # scene-spec image elements (docgen image-generate)
model: gpt-image-1 # Cursor/OpenAI Images; Grok remaps gpt-image-* to Imagine
size: 1536x1024
# quality: high # optional, model-specific
manim:
quality: 1080p30 # supports 480p15, 720p30, 1080p30, 1080p60, 1440p30, 1440p60, 2160p60
manim_path: "" # optional explicit binary path (relative to docgen.yaml or absolute)
font: "Liberation Sans"
min_font_size: 14
validation:
subject_beat_coverage:
enabled: true # scene-spec-generate + validate: cover narration topic beats
compose:
ffmpeg_timeout_sec: 300 # can also be overridden with: docgen compose --ffmpeg-timeout N- ffmpeg — composition and probing
- tesseract-ocr — OCR validation
- Manim — primary visuals (optional install:
pip install docgen[manim])
See milestone-doc-generator.md for the full design document.