Skip to content

Commit 53927ea

Browse files
dgunningclaude
andcommitted
docs(media): add VHS demo-recording harness and dynamic README hero reel
Reproducible terminal demo clips (GIF+MP4) for README/docs/social, scripted with VHS and run entirely in Docker (no Homebrew needed). A custom image layers edgartools into Charm's vhs image; an off-camera PYTHONSTARTUP prelude installs a Rich displayhook so a bare REPL expression renders the formatted table. - scripts/vhs/: Dockerfile, pystartup.py, shared theme.tape, README, and two tapes — hero-reel (Company -> filings -> 10-K TOC -> financials) and the focused hero-income-statement. - Replace docs/images/edgartools-demo.gif with the 32s hero reel (3.4M -> 557K) and add the MP4 for docs embeds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bf1f52f commit 53927ea

9 files changed

Lines changed: 282 additions & 0 deletions

File tree

docs/images/edgartools-demo.gif

-2.9 MB
Loading
371 KB
Binary file not shown.

scripts/vhs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Working-dir renders. Published clips live in docs/images/.
2+
out/

scripts/vhs/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Recording image for EdgarTools demo clips.
2+
#
3+
# Built on Charm's official VHS image (bundles vhs + ttyd + ffmpeg + fonts) and
4+
# layers in a venv with edgartools so we can record a real REPL session — real
5+
# terminal, real Rich output — fully reproducibly, with no Homebrew on the host.
6+
#
7+
# Build: docker build -t edgartools-vhs scripts/vhs
8+
# Render: docker run --rm -e EDGAR_IDENTITY="$EDGAR_IDENTITY" \
9+
# -v "$PWD:/vhs" -w /vhs edgartools-vhs scripts/vhs/<tape>.tape
10+
FROM ghcr.io/charmbracelet/vhs:latest
11+
12+
# Debian trixie marks the system Python as externally managed (PEP 668), so we
13+
# install into an isolated venv rather than fighting --break-system-packages.
14+
RUN apt-get update \
15+
&& apt-get install -y --no-install-recommends python3-venv python3-pip \
16+
&& rm -rf /var/lib/apt/lists/*
17+
18+
RUN python3 -m venv /opt/venv
19+
ENV PATH="/opt/venv/bin:$PATH"
20+
RUN pip install --no-cache-dir --upgrade pip \
21+
&& pip install --no-cache-dir edgartools
22+
23+
# Off-camera REPL prelude: identity + pre-imports + Rich display hook.
24+
COPY pystartup.py /opt/vhs/pystartup.py
25+
ENV PYTHONSTARTUP=/opt/vhs/pystartup.py
26+
# Keep Rich/Python output deterministic and unbuffered for clean recording.
27+
ENV PYTHONUNBUFFERED=1
28+
ENV VHS_CONSOLE_WIDTH=100

scripts/vhs/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# EdgarTools demo recordings (VHS)
2+
3+
Reproducible terminal demo clips for the README, docs, and social — real
4+
terminal, real Rich output, scripted with [VHS](https://github.com/charmbracelet/vhs).
5+
6+
Each clip is defined by a `.tape` file and renders to **both a GIF and an MP4**
7+
from one source, so the same recording serves all three venues:
8+
9+
- **GIF** → README + social autoplay
10+
- **MP4** → docs-page embeds + social with captions/voiceover added in post
11+
12+
No Homebrew required. Everything runs in a Docker image that bundles
13+
`vhs` + `ttyd` + `ffmpeg` and a venv with `edgartools`.
14+
15+
## One-time setup
16+
17+
```bash
18+
# Build the recording image (installs edgartools inside the vhs image).
19+
docker build -t edgartools-vhs scripts/vhs
20+
```
21+
22+
Rebuild only when `Dockerfile` / `pystartup.py` change or to pick up a newer
23+
edgartools release.
24+
25+
## Render a clip
26+
27+
From the repo root (uses your `EDGAR_IDENTITY` for SEC access):
28+
29+
```bash
30+
docker run --rm \
31+
-e EDGAR_IDENTITY="$EDGAR_IDENTITY" \
32+
-v "$PWD:/vhs" -w /vhs \
33+
edgartools-vhs scripts/vhs/hero-income-statement.tape
34+
```
35+
36+
Outputs land in `scripts/vhs/out/`. Promote a final clip to the published
37+
location when happy (the README hero lives at `docs/images/`):
38+
39+
```bash
40+
cp scripts/vhs/out/hero-income-statement.gif docs/images/
41+
```
42+
43+
## How it's wired
44+
45+
| File | Role |
46+
|------|------|
47+
| `Dockerfile` | `FROM` Charm's vhs image; adds a venv with `edgartools` + the prelude. |
48+
| `pystartup.py` | Off-camera REPL prelude: `set_identity`, pre-imports, and a Rich display hook so a bare expression renders the formatted table in colour at a fixed width (`VHS_CONSOLE_WIDTH`, default 100). |
49+
| `theme.tape` | Shared "set design" (font, size, theme, typing speed). `Source`d by every tape — edit once to restyle the series. |
50+
| `*.tape` | One clip each. |
51+
| `out/` | Rendered GIFs/MP4s (working dir; not the published location). |
52+
53+
The tape pattern: launch `python -q` **hidden** (identity + imports load from
54+
`PYTHONSTARTUP`), `Ctrl+L` to a clean prompt, `Show`, then type the single
55+
on-camera expression. Viewers never see boilerplate.
56+
57+
## Clips
58+
59+
| Tape | Clip | Status |
60+
|------|------|--------|
61+
| `hero-reel.tape` | 🌟 Hero reel — 30s tour: Company → filings → 10-K (TOC) → financials | ✅ done |
62+
| `hero-income-statement.tape` | Focused hero — Apple income statement in three lines | ✅ done |
63+
| _(planned)_ | Insider trades (Form 4) — `Company("TSLA")…transactions` | todo |
64+
| _(planned)_ | 13F holdings — `get_filings("13F-HR")[0].obj().holdings` | todo |
65+
| _(planned)_ | Universal `find()` — ticker / CIK / accession | todo |
66+
| _(planned)_ | Live filings feed — `get_current_filings().filter(form="8-K")` | todo |
67+
68+
### Adding a clip
69+
70+
1. Copy `hero-income-statement.tape`.
71+
2. Change the two `Output` paths and the on-camera `Type` line.
72+
3. Adjust `Set Height` if the output is taller/shorter than the default 720
73+
(the hero uses 860 because the income statement is a tall table).
74+
4. Tune the post-`Enter` `Sleep` to cover the live SEC fetch (≈8–10s cold).
75+
76+
## Tips
77+
78+
- **Frame height = fit the output, no scroll.** If a table scrolls, its header
79+
is gone from the final hold frame. Raise `Set Height` until the whole result
80+
fits (≈21.6px per line at FontSize 18, plus padding).
81+
- **Determinism.** `pystartup.py` pins the Rich console width so layout is
82+
identical run to run regardless of PTY size. SEC values change over time —
83+
re-render to refresh numbers.
84+
- **Inspect a frame** without opening the video:
85+
`docker run --rm -v "$PWD:/vhs" -w /vhs --entrypoint ffmpeg edgartools-vhs \
86+
-sseof -2.5 -i scripts/vhs/out/<clip>.mp4 -frames:v 1 -update 1 -y /tmp/f.png`
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# HERO CLIP — "Apple's income statement in three lines."
2+
# The canonical README/social demo: tiny input, fully-formatted financials.
3+
#
4+
# Render (from repo root):
5+
# docker run --rm -e EDGAR_IDENTITY="$EDGAR_IDENTITY" \
6+
# -v "$PWD:/vhs" -w /vhs edgartools-vhs scripts/vhs/hero-income-statement.tape
7+
8+
Source scripts/vhs/theme.tape
9+
10+
# The income statement is a tall table — give this clip a taller frame so the
11+
# full statement (title through EPS) is visible on the final hold, not scrolled.
12+
Set Height 860
13+
14+
Output scripts/vhs/out/hero-income-statement.gif
15+
Output scripts/vhs/out/hero-income-statement.mp4
16+
17+
# --- Off-camera prelude: launch the REPL (identity + imports + Rich hook
18+
# come from PYTHONSTARTUP), then clear to a pristine prompt. ---
19+
Hide
20+
Type "python -q"
21+
Enter
22+
Sleep 6s
23+
Ctrl+L
24+
Show
25+
26+
# --- On camera: one expression, a full income statement. ---
27+
Sleep 800ms
28+
Type@90ms "Company('AAPL').get_financials().income_statement()"
29+
Sleep 1s
30+
Enter
31+
Sleep 9s
32+
33+
# Hold the final frame so the GIF reads at a glance and loops cleanly.
34+
Sleep 2500ms

scripts/vhs/hero-reel.tape

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# HERO REEL — the 30-second tour of EdgarTools.
2+
# Four beats, one company, escalating payoff:
3+
# 1. Company(...) → company panel
4+
# 2. .get_filings("10-K") → filings table
5+
# 3. .latest_tenk → the 10-K as a structured object (TOC tree)
6+
# 4. .income_statement → the financials
7+
#
8+
# Render (from repo root):
9+
# docker run --rm -e EDGAR_IDENTITY="$EDGAR_IDENTITY" \
10+
# -v "$PWD:/vhs" -w /vhs edgartools-vhs scripts/vhs/hero-reel.tape
11+
12+
Source scripts/vhs/theme.tape
13+
14+
# Snappier typing for a montage; taller frame so the income statement (the
15+
# payoff, shown last on a clean screen) is fully visible on the hold.
16+
Set TypingSpeed 55ms
17+
Set Height 820
18+
19+
Output scripts/vhs/out/hero-reel.gif
20+
Output scripts/vhs/out/hero-reel.mp4
21+
22+
# --- Off-camera: launch REPL (identity + imports via PYTHONSTARTUP), clean prompt. ---
23+
Hide
24+
Type "python -q"
25+
Enter
26+
Sleep 6s
27+
Ctrl+L
28+
Show
29+
30+
# --- Beat 1: start with a ticker. ---
31+
Sleep 700ms
32+
Type "company = Company('AAPL')"
33+
Enter
34+
Sleep 400ms
35+
Type "company"
36+
Enter
37+
Sleep 3s
38+
Sleep 1500ms
39+
Ctrl+L
40+
41+
# --- Beat 2: browse its filings. ---
42+
Sleep 400ms
43+
Type "company.get_filings(form='10-K').head(6)"
44+
Enter
45+
Sleep 3s
46+
Sleep 1800ms
47+
Ctrl+L
48+
49+
# --- Beat 3: the latest 10-K as a structured object — full table of contents. ---
50+
Sleep 400ms
51+
Type "tenk = company.latest_tenk"
52+
Enter
53+
Sleep 2500ms
54+
Type "tenk"
55+
Enter
56+
Sleep 3500ms
57+
Sleep 1800ms
58+
Ctrl+L
59+
60+
# --- Beat 4: the financials (payoff — hold long). ---
61+
Sleep 400ms
62+
Type "tenk.income_statement"
63+
Enter
64+
Sleep 4s
65+
Sleep 3500ms

scripts/vhs/pystartup.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Off-camera REPL prelude for VHS recordings.
2+
3+
Loaded via PYTHONSTARTUP inside the recording container so that on-camera we
4+
only ever type the one expression we want to demo. Responsibilities:
5+
6+
1. Set the SEC identity (required for EDGAR access).
7+
2. Pre-import the common entry points so typing is short and fetches are warm.
8+
3. Install a Rich display hook so a bare REPL expression (e.g.
9+
``Company("AAPL").get_financials().income_statement()``) renders the
10+
formatted table in full colour at a deterministic width — instead of the
11+
default ``repr()``.
12+
13+
Nothing here should print to stdout; the launch command is hidden in the tape,
14+
so any stray output would leak into the clean opening frame.
15+
"""
16+
import os
17+
import sys
18+
19+
from rich.console import Console
20+
21+
# Fixed width keeps table layout identical across renders regardless of the
22+
# PTY size VHS happens to allocate. force_terminal=True keeps colour on.
23+
_DEMO_WIDTH = int(os.environ.get("VHS_CONSOLE_WIDTH", "100"))
24+
_console = Console(width=_DEMO_WIDTH, force_terminal=True)
25+
26+
27+
def _rich_displayhook(value):
28+
"""Render REPL results through Rich (uses __rich__ when available)."""
29+
if value is None:
30+
return
31+
# Keep `_` working like the standard REPL does.
32+
import builtins
33+
builtins._ = value
34+
_console.print(value)
35+
36+
37+
sys.displayhook = _rich_displayhook
38+
39+
# Identity: prefer the env var passed into the container, fall back to a
40+
# generic demo identity so recordings never fail on a missing identity.
41+
from edgar import set_identity # noqa: E402
42+
43+
set_identity(os.environ.get("EDGAR_IDENTITY") or "EdgarTools Demo demo@edgartools.io")
44+
45+
# Common entry points — pre-imported so on-camera lines stay short.
46+
from edgar import ( # noqa: E402,F401
47+
Company,
48+
Fund,
49+
find,
50+
find_funds,
51+
get_current_filings,
52+
get_filings,
53+
)

scripts/vhs/theme.tape

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Shared "set design" for every EdgarTools demo clip.
2+
# Sourced by each tape so all clips share one look and pacing.
3+
# Edit here to restyle the whole series at once.
4+
5+
Set Shell "bash"
6+
Set FontFamily "JetBrains Mono"
7+
Set FontSize 18
8+
Set Width 1280
9+
Set Height 720
10+
Set Padding 28
11+
Set Margin 0
12+
Set Theme "Dracula"
13+
Set TypingSpeed 90ms
14+
Set PlaybackSpeed 1.0

0 commit comments

Comments
 (0)