Skip to main content

tourcast

An aperture with a cursor at its centre

Record a hands-free walkthrough of a web app as an MP4.

Write the tour as YAML. Playwright walks the app, a synthetic cursor moves to each target, and the camera zooms and dims to direct attention. When the product changes, run it again — the video is regenerated, not re-shot. Demo-as-Code.

python -m http.server 8765 --directory examples/demo-site &
tourcast record examples/scenarios/demo-tour.yaml -o demo.mp4

That example needs nothing but this repository: the demo site is checked in.

Commands

Four of them, and every one takes a single scenario file — tourcast <verb> <scenario>.

Command What it does
tourcast record <scenario> records the tour
tourcast check <scenario> validates the spec without opening a browser
tourcast probe <scenario> surveys the target app; records nothing
tourcast init <name> scaffolds a profile and a scenario

probe exists because scenarios should be written from measurement, not guesswork. It walks the sidebar, clicks the first row of every table and reports what actually happened — whether the row opens a detail page or a modal, how far each screen scrolls, which tabs exist.

How it works

scenario.yaml  ->  world (Playwright)  ->  frames (CDP screencast)  ->  camera  ->  MP4
                        |                                                 |
                        |  navigation, clicks, the real pointer           |  crop, dim,
                        |                                                 |  cursor, timing

Two decisions shape everything else.

The camera never touches the page. Zoom is a crop rectangle over captured frames, not a CSS transform. A transform would re-evaluate viewport units and media queries, so the video would no longer show the app as users see it — and it would break differently in every app, which makes a shared tool impossible.

The cursor is composited after capture. It is not injected into the DOM. Its path is recorded as data and drawn onto each output frame, so its motion is independent of browser paint timing. That also means it works unchanged inside iframes.

Profiles keep it app-agnostic

Everything the engine would otherwise need to know about an app lives in a profile.

profiles/<app>.yaml    base URL, login recipe, selector aliases, ready states
scenarios/*.yaml       order of movement, dwell times, emphasis
tourcast/              the engine — knows no app

Recording a new app means adding one profile and one scenario. The engine is not touched — the claim was checked by recording a second, deliberately different app (no login, direct page entry instead of an iframe): one profile and one scenario were added and no engine line changed.

Scenarios never contain raw selectors — only aliases the profile defines. When a selector breaks, exactly one file changes.

Install

pipx install tourcast          # or: pip install tourcast
playwright install chromium

ffmpeg must be on PATH:

brew install ffmpeg            # macOS
sudo apt install ffmpeg        # Debian/Ubuntu
winget install Gyan.FFmpeg     # Windows

Fonts are the machine's job, not the tool's. tourcast draws no text, and Chromium can only use fonts the OS knows about — so recording an app in a language whose font is missing produces a valid video full of empty boxes, with no error. On a bare container, install them and verify:

sudo apt install fonts-noto-core fonts-noto-cjk
python tools/check-glyphs.py    # exits 1 if a script renders as tofu

Non-goals

Kept out on purpose, so a request to add them has a clear answer.

Not doing Why
Interactive HTML demos That is Arcade/Storylane territory. The asset here is the spec-to-video pipeline, not an editor
A no-code GUI editor Scenarios must be text for diffs, review and CI to work
An E2E test framework Add assertions and you are reinventing Playwright. A failed step means "stop recording", nothing more
Branching and loops in YAML A sequential tour does not need them. If they are ever needed, the answer is a Python API, not a bigger YAML
Captions and narration Out of scope for now; the rationale is in docs/decisions/02-ARCHITECTURE.md

Status

Working end to end. Measured on the pipeline itself:

Area Verified Measurement
Capture CDP + PNG beats Playwright's recordVideo PSNR 53.12dB vs 48.43dB, no queue backlog at 3840x2160
Cursor lands on the element it is explaining 3/3 on target, 0.5px error, legible on light and dark
Timing the video preserves real time length within +4ms of plan; intervals within one frame
Camera zoom leaves the page untouched and stays lossless DOM metrics identical, 0 upscaled frames at 2.0x
Spotlight attention is actually directed target 200 vs surroundings 90 luma — exactly the requested opacity
Portability a new app needs only a profile second app recorded with no engine change

Documentation

Split by kind, not by language — a translated journal would only diverge from the original.

Reference — what you read to use the tool. English, single source.

Document Contents
docs/reference/scenario.md Every scenario key, click_any, camera directives, validation, exit codes
docs/reference/profile.md Profile keys, login, sessions, aliases, and how to survey an app

Decisions — why it is built this way. A working journal in Korean, with an English index.

Document Contents
docs/decisions/INDEX.md Start here. Every decision in one line with its consequence and a pointer
docs/decisions/ Measurements, failed approaches, and phase-by-phase acceptance criteria

The index is worth a minute before changing anything in tourcast/: several entries are invariants that fail silently when broken — the supersampling launch flag being the clearest one.

Development

python3 -m venv .venv
.venv/bin/pip install playwright pyyaml pillow pytest
.venv/bin/playwright install chromium

.venv/bin/python -m pytest tests/ -q                 # no browser needed, runs in under a second
python -m http.server 8765 --directory examples/demo-site &
.venv/bin/python -m tourcast.cli record examples/scenarios/demo-tour.yaml -o out/demo.mp4

Profiles and scenarios that carry credentials are named *.local.yaml and are gitignored.

CI (.github/workflows/ci.yml) runs the tests on 3.11–3.13, then records the example tour on Linux and checks the run report — a blurry zoom or a cursor that never moved fails the build, since neither shows up as a non-zero exit code.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tourcast-0.1.0.tar.gz (67.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tourcast-0.1.0-py3-none-any.whl (62.6 kB view details)

Uploaded Python 3

File details

Details for the file tourcast-0.1.0.tar.gz.

File metadata

  • Download URL: tourcast-0.1.0.tar.gz
  • Upload date:
  • Size: 67.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tourcast-0.1.0.tar.gz
Algorithm Hash digest
SHA256 928d81df660eb0affb342df2769b448c45c95d873ff2291cac1e3cf2178174c1
MD5 4289fe6229722bc30d2480384de71151
BLAKE2b-256 f97c5a5acd57463dd8dddbdf57cb637bbff551165a9450a711fb79969de9f495

See more details on using hashes here.

Provenance

The following attestation bundles were made for tourcast-0.1.0.tar.gz:

Publisher: publish.yml on AzuraLight/tourcast

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tourcast-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tourcast-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 62.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tourcast-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e1ec78423d4f0223b899c500406f8b3296933e7d832b78dfb58b0b8e0df6828
MD5 1b8d93934f65f425d2e4df7d83451c2d
BLAKE2b-256 d03d945470cc8076b952752589df78a16b22d315cdcbdd85699ebd9e036fb30f

See more details on using hashes here.

Provenance

The following attestation bundles were made for tourcast-0.1.0-py3-none-any.whl:

Publisher: publish.yml on AzuraLight/tourcast

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page