protoreel 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoreel-0.1.0/.claude/settings.json +3 -0
- protoreel-0.1.0/.github/workflows/ci.yml +31 -0
- protoreel-0.1.0/.github/workflows/release.yml +31 -0
- protoreel-0.1.0/.gitignore +11 -0
- protoreel-0.1.0/CHANGELOG.md +11 -0
- protoreel-0.1.0/CLAUDE.md +32 -0
- protoreel-0.1.0/CONTRIBUTING.md +28 -0
- protoreel-0.1.0/HANDOFF.md +21 -0
- protoreel-0.1.0/LICENSE +21 -0
- protoreel-0.1.0/PKG-INFO +192 -0
- protoreel-0.1.0/README.md +167 -0
- protoreel-0.1.0/docs/architecture.md +63 -0
- protoreel-0.1.0/examples/README.md +8 -0
- protoreel-0.1.0/examples/like-button.html +67 -0
- protoreel-0.1.0/examples/like-button.json +10 -0
- protoreel-0.1.0/pyproject.toml +54 -0
- protoreel-0.1.0/scripts/ship.sh +104 -0
- protoreel-0.1.0/src/protoreel/__init__.py +4 -0
- protoreel-0.1.0/src/protoreel/__main__.py +5 -0
- protoreel-0.1.0/src/protoreel/audio.py +29 -0
- protoreel-0.1.0/src/protoreel/browser.py +37 -0
- protoreel-0.1.0/src/protoreel/cli.py +176 -0
- protoreel-0.1.0/src/protoreel/encoder.py +127 -0
- protoreel-0.1.0/src/protoreel/mcp_server.py +264 -0
- protoreel-0.1.0/src/protoreel/page_scripts/__init__.py +47 -0
- protoreel-0.1.0/src/protoreel/page_scripts/background.js +32 -0
- protoreel-0.1.0/src/protoreel/page_scripts/clock.js +40 -0
- protoreel-0.1.0/src/protoreel/page_scripts/cursor.js +14 -0
- protoreel-0.1.0/src/protoreel/renderer.py +270 -0
- protoreel-0.1.0/src/protoreel/settings.py +89 -0
- protoreel-0.1.0/src/protoreel/studio/__init__.py +1 -0
- protoreel-0.1.0/src/protoreel/studio/server.py +277 -0
- protoreel-0.1.0/src/protoreel/studio/static/index.html +591 -0
- protoreel-0.1.0/src/protoreel/takes.py +112 -0
- protoreel-0.1.0/src/protoreel/timeline.py +239 -0
- protoreel-0.1.0/src/protoreel/workspace.py +158 -0
- protoreel-0.1.0/tests/conftest.py +22 -0
- protoreel-0.1.0/tests/test_mcp.py +42 -0
- protoreel-0.1.0/tests/test_render.py +28 -0
- protoreel-0.1.0/tests/test_settings.py +28 -0
- protoreel-0.1.0/tests/test_stdio.py +16 -0
- protoreel-0.1.0/tests/test_studio.py +64 -0
- protoreel-0.1.0/tests/test_takes.py +26 -0
- protoreel-0.1.0/tests/test_timeline.py +54 -0
- protoreel-0.1.0/uv.lock +1313 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, macos-latest]
|
|
14
|
+
python: ["3.10", "3.13"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- name: Install
|
|
22
|
+
run: |
|
|
23
|
+
uv venv
|
|
24
|
+
uv pip install -e ".[dev]"
|
|
25
|
+
uv run python -m playwright install --with-deps chromium
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: |
|
|
28
|
+
uv run ruff check src tests
|
|
29
|
+
uv run ruff format --check src tests
|
|
30
|
+
- name: Test
|
|
31
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Push a tag like v0.2.0 to build Protoreel and publish it to PyPI.
|
|
4
|
+
# One-time setup: on pypi.org, add this repository as a "trusted publisher"
|
|
5
|
+
# (workflow: release.yml, environment: pypi). No API token is needed.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write # lets PyPI verify this workflow instead of using a password
|
|
16
|
+
contents: write # lets the job create the GitHub release
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
- name: Check the tag matches the package version
|
|
21
|
+
run: |
|
|
22
|
+
version=$(grep -oP '__version__ = "\K[^"]+' src/protoreel/__init__.py)
|
|
23
|
+
test "v$version" = "${GITHUB_REF_NAME}" || { echo "Tag ${GITHUB_REF_NAME} does not match version $version"; exit 1; }
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
28
|
+
- name: Create GitHub release
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ github.token }}
|
|
31
|
+
run: gh release create "${GITHUB_REF_NAME}" dist/* --title "${GITHUB_REF_NAME}" --notes-file CHANGELOG.md
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
First open-source release, grown out of a personal "Recorder Studio" script.
|
|
6
|
+
|
|
7
|
+
- MCP server with `list_workspace`, `record_video`, `render_take`, `preview_frame`, `open_studio` and `check_setup`
|
|
8
|
+
- Studio web app for hand-captured takes; takes are now saved to `takes/` so they can be re-rendered later
|
|
9
|
+
- Scripts gain `click`, `hover`, `type` and `scroll` actions, CSS-selector points, and late element lookup
|
|
10
|
+
- Bundled ffmpeg fallback and automatic Chromium install
|
|
11
|
+
- Six social formats from 1080 to 4K, optional backdrop and caption, prototype sound
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Protoreel — notes for Claude Code
|
|
2
|
+
|
|
3
|
+
Protoreel films HTML prototypes into social videos. It is a Python package with a CLI, an MCP
|
|
4
|
+
server and a small web Studio. Read `docs/architecture.md` for the map of modules.
|
|
5
|
+
|
|
6
|
+
## Strict rule: authorship
|
|
7
|
+
|
|
8
|
+
Never add Claude, Claude Code or Cowork as an author or co-author of anything in this project.
|
|
9
|
+
No `Co-Authored-By` trailers, no "Generated with Claude Code" lines in commits, pull requests,
|
|
10
|
+
releases or files. Commits are authored by the repository owner only.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv venv && source .venv/bin/activate && uv pip install -e ".[dev]" # set up
|
|
16
|
+
python -m playwright install chromium # once per machine
|
|
17
|
+
pytest -q # ~30 s, real renders
|
|
18
|
+
ruff check src tests && ruff format src tests # lint + format
|
|
19
|
+
protoreel studio --folder ~/Playwright # run the Studio
|
|
20
|
+
protoreel record examples/like-button.html examples/like-button.json # scripted render
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Conventions
|
|
24
|
+
|
|
25
|
+
- Keep it readable for a junior developer: one job per module, plain names, short docstrings,
|
|
26
|
+
comments that explain *why*.
|
|
27
|
+
- Never print to stdout from code the MCP server runs: stdout carries the MCP protocol.
|
|
28
|
+
Progress goes to stderr (CLI) or `ctx.report_progress` (MCP).
|
|
29
|
+
- Errors a user can fix raise `ValueError` / `FileNotFoundError` with a helpful message;
|
|
30
|
+
`mcp_server.user_errors` passes those messages through to the AI client.
|
|
31
|
+
- The MCP SDK is v2 (`mcp.server.mcpserver.MCPServer`), not v1's `FastMCP`.
|
|
32
|
+
- Version lives in `src/protoreel/__init__.py`; update `CHANGELOG.md` with it.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping! Protoreel aims to stay small and readable, so a new contributor can
|
|
4
|
+
understand any file in one sitting.
|
|
5
|
+
|
|
6
|
+
## Set up
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/iamkingsleey/protoreel && cd protoreel
|
|
10
|
+
uv venv && source .venv/bin/activate
|
|
11
|
+
uv pip install -e ".[dev]"
|
|
12
|
+
python -m playwright install chromium
|
|
13
|
+
pytest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## House rules
|
|
17
|
+
|
|
18
|
+
- **Name things for what they mean** to someone making videos (`take`, `frame`, `format`), not how they're implemented.
|
|
19
|
+
- **One job per module.** If a file starts doing two things, split it. [docs/architecture.md](docs/architecture.md) is the map; update it when the map changes.
|
|
20
|
+
- **Explain why, not what,** in comments. Short docstrings on every public function.
|
|
21
|
+
- **Readable errors.** Anything a user can fix should say what went wrong and what to do.
|
|
22
|
+
- `ruff check` and `ruff format` before you push; CI runs both, plus the tests.
|
|
23
|
+
|
|
24
|
+
## Making a release
|
|
25
|
+
|
|
26
|
+
1. Bump `__version__` in `src/protoreel/__init__.py` and add a section to `CHANGELOG.md`.
|
|
27
|
+
2. Commit, then tag: `git tag v0.2.0 && git push --tags`.
|
|
28
|
+
3. The *Release* workflow builds the package and publishes it to PyPI.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Handing Protoreel to Claude Code
|
|
2
|
+
|
|
3
|
+
Open Terminal and run:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd ~/Playwright/protoreel
|
|
7
|
+
claude
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Then paste this prompt:
|
|
11
|
+
|
|
12
|
+
> Read CLAUDE.md and docs/architecture.md, and follow its authorship rule strictly: never add
|
|
13
|
+
> yourself as a co-author or add "Generated with Claude" lines anywhere. Then ship Protoreel for me by running
|
|
14
|
+
> `scripts/ship.sh ~/Playwright`. If a step fails, fix the cause and re-run the script
|
|
15
|
+
> (it is safe to re-run). If GitHub login is needed, ask me to run `gh auth login`.
|
|
16
|
+
> When it finishes, walk me through the PyPI "pending publisher" step it prints, then push
|
|
17
|
+
> the v0.1.0 tag and watch the Release workflow until it succeeds. Finally, confirm the
|
|
18
|
+
> MCP server works by listing the protoreel tools.
|
|
19
|
+
|
|
20
|
+
That's it. After this, you never need `Recorder.command` again: ask Claude to
|
|
21
|
+
"open the Protoreel studio", or to record a prototype for you.
|
protoreel-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kingsley Mkpandiok
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
protoreel-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: protoreel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn HTML prototypes into frame-perfect social videos — from a Studio app or any MCP client.
|
|
5
|
+
Project-URL: Homepage, https://github.com/iamkingsleey/protoreel
|
|
6
|
+
Project-URL: Issues, https://github.com/iamkingsleey/protoreel/issues
|
|
7
|
+
Author: Kingsley Mkpandiok
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: animation,design,mcp,playwright,prototype,screen-recording,video
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Multimedia :: Video
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: imageio-ffmpeg>=0.5
|
|
18
|
+
Requires-Dist: mcp<3,>=2.0
|
|
19
|
+
Requires-Dist: playwright>=1.45
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Protoreel
|
|
27
|
+
|
|
28
|
+
**Turn HTML prototypes into frame-perfect social videos — by hand in a small Studio app, or by asking your AI assistant.**
|
|
29
|
+
|
|
30
|
+
Protoreel opens your prototype in a headless browser, plays an interaction (clicks, drags, typing, scrolling) with a smooth on-screen cursor, and films it one frame at a time into an MP4. Because the page's clock only moves when Protoreel says so, videos never stutter or drop frames, whatever machine you run them on. If your prototype makes sound, the sound comes along too.
|
|
31
|
+
|
|
32
|
+
- **Six social formats** — 4:5 feed, 9:16 reels, 1:1, 16:9, and two Dribbble sizes — up to 4K
|
|
33
|
+
- **An MCP server** — Claude, Cursor, VS Code and other MCP clients can record videos for you
|
|
34
|
+
- **A Studio app** — perform the interaction yourself, then render it in any format
|
|
35
|
+
- **Sound** — prototypes can hand Protoreel their audio, mixed under the video
|
|
36
|
+
- **No setup beyond one command** — Chromium and ffmpeg are fetched for you
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/) (one line to install). Then:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uvx protoreel setup --folder ~/Prototypes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That downloads Chromium (once), creates the folder, and prints the config for your AI client. Put your `.html` prototypes in that folder.
|
|
49
|
+
|
|
50
|
+
### Use it from Claude, Cursor or VS Code (MCP)
|
|
51
|
+
|
|
52
|
+
**Claude Desktop / Cowork** — open *Settings → Developer → Edit Config* and add:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"protoreel": {
|
|
58
|
+
"command": "uvx",
|
|
59
|
+
"args": ["protoreel", "mcp", "--folder", "/Users/you/Prototypes"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If Claude says it can't start the server, replace `"uvx"` with the full path that `which uvx` prints (desktop apps don't always see your shell's PATH). `protoreel setup` prints the snippet with the full path filled in.
|
|
66
|
+
|
|
67
|
+
**Claude Code**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
claude mcp add protoreel -- uvx protoreel mcp --folder ~/Prototypes
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Cursor** (`.cursor/mcp.json`) uses the same block as Claude Desktop. **VS Code** (`.vscode/mcp.json`) uses the key `"servers"` instead of `"mcpServers"`.
|
|
74
|
+
|
|
75
|
+
Restart the app, then just ask:
|
|
76
|
+
|
|
77
|
+
> *"Record checkout.html as a 9:16 video: tap the Add to bag button, wait a second, then open the bag."*
|
|
78
|
+
|
|
79
|
+
> *"Open the Protoreel studio for like-button.html so I can record it myself."*
|
|
80
|
+
|
|
81
|
+
> *"Re-render my last take as a 4:5 and a 16:9."*
|
|
82
|
+
|
|
83
|
+
### Use the Studio yourself
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uvx protoreel studio --folder ~/Prototypes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Pick a prototype, choose a format, press **Capture**, perform the interaction with your mouse, press **Esc**, then **Render**. Every take is saved to `takes/`, so you (or your AI assistant) can render it again later in another format.
|
|
90
|
+
|
|
91
|
+
### Use the command line
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uvx protoreel record like-button.html examples/like-button.json --format 9x16 --zoom 2.4
|
|
95
|
+
uvx protoreel render-take like-button_20261013-101500.json --format 16x9 --res 4k
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## The MCP tools
|
|
101
|
+
|
|
102
|
+
| Tool | What it does |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `list_workspace` | Lists prototypes, recent takes and recent videos |
|
|
105
|
+
| `record_video` | Films a prototype following a script (below) |
|
|
106
|
+
| `render_take` | Films a take captured in the Studio, in any format |
|
|
107
|
+
| `preview_frame` | Returns one frame as an image, to check framing and zoom |
|
|
108
|
+
| `open_studio` | Opens the Studio in your browser |
|
|
109
|
+
| `check_setup` | Checks Chromium and ffmpeg (and can install Chromium) |
|
|
110
|
+
|
|
111
|
+
## Scripts
|
|
112
|
+
|
|
113
|
+
A script is a list of actions, played in order. Elements are found with CSS selectors *at the moment each action starts*, so menus that open mid-video work naturally.
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"start": ["80%", "85%"],
|
|
118
|
+
"actions": [
|
|
119
|
+
{"wait": 400},
|
|
120
|
+
{"click": "#add-to-bag", "dur": 800},
|
|
121
|
+
{"wait": 900},
|
|
122
|
+
{"type": "Lekki Phase 1", "delay": 60},
|
|
123
|
+
{"key": "Enter"},
|
|
124
|
+
{"drag": {"by": [0, -220]}, "dur": 900},
|
|
125
|
+
{"scroll": {"to": "#reviews"}, "dur": 1000},
|
|
126
|
+
{"wait": 1200}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Action | Meaning |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `{"wait": 500}` | hold still for 500 ms |
|
|
134
|
+
| `{"move": POINT, "dur": 600}` / `{"hover": POINT}` | glide the pointer to a point |
|
|
135
|
+
| `{"click": POINT, "dur": 500}` | glide there, then press and release (`{"click": true}` clicks in place) |
|
|
136
|
+
| `{"drag": {"to": POINT} or {"by": [dx, dy]}, "dur": 900}` | press, move, release |
|
|
137
|
+
| `{"down": true}` / `{"up": true}` | press or release in place |
|
|
138
|
+
| `{"key": "Enter"}` | press a key ([names](https://playwright.dev/docs/api/class-keyboard#keyboard-press)) |
|
|
139
|
+
| `{"type": "Hello", "delay": 70}` | type text, one character every 70 ms |
|
|
140
|
+
| `{"scroll": {"by": [0, 400]} or {"to": POINT}, "dur": 800}` | scroll smoothly |
|
|
141
|
+
|
|
142
|
+
A **POINT** is `[x, y]` in CSS pixels, `["50%", "60%"]` of the frame, `"#selector"`, or `{"el": "#selector", "x": 0.5, "y": 0.5}` for a spot inside an element. Any action can take `"ease"`: `smooth` (default, like a hand), `linear`, `in`, `out` or `inout`.
|
|
143
|
+
|
|
144
|
+
## Formats and zoom
|
|
145
|
+
|
|
146
|
+
| Format | Size at 1080 | Typical use |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `4x5` | 1080 × 1350 | Instagram / LinkedIn feed |
|
|
149
|
+
| `9x16` | 1080 × 1920 | Reels, TikTok, Shorts |
|
|
150
|
+
| `1x1` | 1080 × 1080 | Square |
|
|
151
|
+
| `16x9` | 1920 × 1080 | Landscape, YouTube, X |
|
|
152
|
+
| `4x3` | 1600 × 1200 | Dribbble |
|
|
153
|
+
| `3x2` | 1800 × 1200 | Dribbble wide |
|
|
154
|
+
|
|
155
|
+
**Zoom** (0.6–3.6, default 2) is how large the prototype appears: the page is laid out at *format ÷ zoom* CSS pixels and rendered sharply at that scale. **Resolution** (`1080`, `2k`, `3k`, `4k`) adds pixels without changing the framing.
|
|
156
|
+
|
|
157
|
+
## Sound
|
|
158
|
+
|
|
159
|
+
Headless browsers can't record their own audio, so prototypes that make sound opt in with one function:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
// Return a base64 WAV of the sounds that played between startMs and startMs + durationMs
|
|
163
|
+
// (on the page's clock, i.e. performance.now()). A Promise of the string is fine too.
|
|
164
|
+
window.__recorderAudio = function (startMs, durationMs) { /* ... */ };
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The usual pattern is to log each sound as it plays and replay the log into an `OfflineAudioContext`. [`examples/like-button.html`](examples/like-button.html) shows the whole thing in about 15 lines. Pages without the function produce silent videos.
|
|
168
|
+
|
|
169
|
+
## Folder layout
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
~/Prototypes/ your .html prototypes (full pages or bare fragments)
|
|
173
|
+
~/Prototypes/renders/ finished videos
|
|
174
|
+
~/Prototypes/takes/ takes captured in the Studio
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Set the folder with `--folder`, or the `PROTOREEL_HOME` environment variable (default `~/Protoreel`).
|
|
178
|
+
|
|
179
|
+
## Troubleshooting
|
|
180
|
+
|
|
181
|
+
- **"Chromium is not installed"** — run `uvx protoreel setup`. Behind a strict network? Point `PROTOREEL_CHROMIUM` at an existing Chrome/Chromium.
|
|
182
|
+
- **The Studio port is busy** — another Studio is probably running; `protoreel studio --port 8800` picks another.
|
|
183
|
+
- **The video is silent** — the page doesn't define `window.__recorderAudio`; see *Sound*.
|
|
184
|
+
- **Clicks miss** — use a selector rather than coordinates, and check with `preview_frame`.
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
Protoreel is small on purpose and written to be easy to read. Start with [docs/architecture.md](docs/architecture.md), then see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT © Kingsley Mkpandiok
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Protoreel
|
|
2
|
+
|
|
3
|
+
**Turn HTML prototypes into frame-perfect social videos — by hand in a small Studio app, or by asking your AI assistant.**
|
|
4
|
+
|
|
5
|
+
Protoreel opens your prototype in a headless browser, plays an interaction (clicks, drags, typing, scrolling) with a smooth on-screen cursor, and films it one frame at a time into an MP4. Because the page's clock only moves when Protoreel says so, videos never stutter or drop frames, whatever machine you run them on. If your prototype makes sound, the sound comes along too.
|
|
6
|
+
|
|
7
|
+
- **Six social formats** — 4:5 feed, 9:16 reels, 1:1, 16:9, and two Dribbble sizes — up to 4K
|
|
8
|
+
- **An MCP server** — Claude, Cursor, VS Code and other MCP clients can record videos for you
|
|
9
|
+
- **A Studio app** — perform the interaction yourself, then render it in any format
|
|
10
|
+
- **Sound** — prototypes can hand Protoreel their audio, mixed under the video
|
|
11
|
+
- **No setup beyond one command** — Chromium and ffmpeg are fetched for you
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/) (one line to install). Then:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uvx protoreel setup --folder ~/Prototypes
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That downloads Chromium (once), creates the folder, and prints the config for your AI client. Put your `.html` prototypes in that folder.
|
|
24
|
+
|
|
25
|
+
### Use it from Claude, Cursor or VS Code (MCP)
|
|
26
|
+
|
|
27
|
+
**Claude Desktop / Cowork** — open *Settings → Developer → Edit Config* and add:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"protoreel": {
|
|
33
|
+
"command": "uvx",
|
|
34
|
+
"args": ["protoreel", "mcp", "--folder", "/Users/you/Prototypes"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If Claude says it can't start the server, replace `"uvx"` with the full path that `which uvx` prints (desktop apps don't always see your shell's PATH). `protoreel setup` prints the snippet with the full path filled in.
|
|
41
|
+
|
|
42
|
+
**Claude Code**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add protoreel -- uvx protoreel mcp --folder ~/Prototypes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Cursor** (`.cursor/mcp.json`) uses the same block as Claude Desktop. **VS Code** (`.vscode/mcp.json`) uses the key `"servers"` instead of `"mcpServers"`.
|
|
49
|
+
|
|
50
|
+
Restart the app, then just ask:
|
|
51
|
+
|
|
52
|
+
> *"Record checkout.html as a 9:16 video: tap the Add to bag button, wait a second, then open the bag."*
|
|
53
|
+
|
|
54
|
+
> *"Open the Protoreel studio for like-button.html so I can record it myself."*
|
|
55
|
+
|
|
56
|
+
> *"Re-render my last take as a 4:5 and a 16:9."*
|
|
57
|
+
|
|
58
|
+
### Use the Studio yourself
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uvx protoreel studio --folder ~/Prototypes
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Pick a prototype, choose a format, press **Capture**, perform the interaction with your mouse, press **Esc**, then **Render**. Every take is saved to `takes/`, so you (or your AI assistant) can render it again later in another format.
|
|
65
|
+
|
|
66
|
+
### Use the command line
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uvx protoreel record like-button.html examples/like-button.json --format 9x16 --zoom 2.4
|
|
70
|
+
uvx protoreel render-take like-button_20261013-101500.json --format 16x9 --res 4k
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## The MCP tools
|
|
76
|
+
|
|
77
|
+
| Tool | What it does |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `list_workspace` | Lists prototypes, recent takes and recent videos |
|
|
80
|
+
| `record_video` | Films a prototype following a script (below) |
|
|
81
|
+
| `render_take` | Films a take captured in the Studio, in any format |
|
|
82
|
+
| `preview_frame` | Returns one frame as an image, to check framing and zoom |
|
|
83
|
+
| `open_studio` | Opens the Studio in your browser |
|
|
84
|
+
| `check_setup` | Checks Chromium and ffmpeg (and can install Chromium) |
|
|
85
|
+
|
|
86
|
+
## Scripts
|
|
87
|
+
|
|
88
|
+
A script is a list of actions, played in order. Elements are found with CSS selectors *at the moment each action starts*, so menus that open mid-video work naturally.
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"start": ["80%", "85%"],
|
|
93
|
+
"actions": [
|
|
94
|
+
{"wait": 400},
|
|
95
|
+
{"click": "#add-to-bag", "dur": 800},
|
|
96
|
+
{"wait": 900},
|
|
97
|
+
{"type": "Lekki Phase 1", "delay": 60},
|
|
98
|
+
{"key": "Enter"},
|
|
99
|
+
{"drag": {"by": [0, -220]}, "dur": 900},
|
|
100
|
+
{"scroll": {"to": "#reviews"}, "dur": 1000},
|
|
101
|
+
{"wait": 1200}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Action | Meaning |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `{"wait": 500}` | hold still for 500 ms |
|
|
109
|
+
| `{"move": POINT, "dur": 600}` / `{"hover": POINT}` | glide the pointer to a point |
|
|
110
|
+
| `{"click": POINT, "dur": 500}` | glide there, then press and release (`{"click": true}` clicks in place) |
|
|
111
|
+
| `{"drag": {"to": POINT} or {"by": [dx, dy]}, "dur": 900}` | press, move, release |
|
|
112
|
+
| `{"down": true}` / `{"up": true}` | press or release in place |
|
|
113
|
+
| `{"key": "Enter"}` | press a key ([names](https://playwright.dev/docs/api/class-keyboard#keyboard-press)) |
|
|
114
|
+
| `{"type": "Hello", "delay": 70}` | type text, one character every 70 ms |
|
|
115
|
+
| `{"scroll": {"by": [0, 400]} or {"to": POINT}, "dur": 800}` | scroll smoothly |
|
|
116
|
+
|
|
117
|
+
A **POINT** is `[x, y]` in CSS pixels, `["50%", "60%"]` of the frame, `"#selector"`, or `{"el": "#selector", "x": 0.5, "y": 0.5}` for a spot inside an element. Any action can take `"ease"`: `smooth` (default, like a hand), `linear`, `in`, `out` or `inout`.
|
|
118
|
+
|
|
119
|
+
## Formats and zoom
|
|
120
|
+
|
|
121
|
+
| Format | Size at 1080 | Typical use |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| `4x5` | 1080 × 1350 | Instagram / LinkedIn feed |
|
|
124
|
+
| `9x16` | 1080 × 1920 | Reels, TikTok, Shorts |
|
|
125
|
+
| `1x1` | 1080 × 1080 | Square |
|
|
126
|
+
| `16x9` | 1920 × 1080 | Landscape, YouTube, X |
|
|
127
|
+
| `4x3` | 1600 × 1200 | Dribbble |
|
|
128
|
+
| `3x2` | 1800 × 1200 | Dribbble wide |
|
|
129
|
+
|
|
130
|
+
**Zoom** (0.6–3.6, default 2) is how large the prototype appears: the page is laid out at *format ÷ zoom* CSS pixels and rendered sharply at that scale. **Resolution** (`1080`, `2k`, `3k`, `4k`) adds pixels without changing the framing.
|
|
131
|
+
|
|
132
|
+
## Sound
|
|
133
|
+
|
|
134
|
+
Headless browsers can't record their own audio, so prototypes that make sound opt in with one function:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
// Return a base64 WAV of the sounds that played between startMs and startMs + durationMs
|
|
138
|
+
// (on the page's clock, i.e. performance.now()). A Promise of the string is fine too.
|
|
139
|
+
window.__recorderAudio = function (startMs, durationMs) { /* ... */ };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The usual pattern is to log each sound as it plays and replay the log into an `OfflineAudioContext`. [`examples/like-button.html`](examples/like-button.html) shows the whole thing in about 15 lines. Pages without the function produce silent videos.
|
|
143
|
+
|
|
144
|
+
## Folder layout
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
~/Prototypes/ your .html prototypes (full pages or bare fragments)
|
|
148
|
+
~/Prototypes/renders/ finished videos
|
|
149
|
+
~/Prototypes/takes/ takes captured in the Studio
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Set the folder with `--folder`, or the `PROTOREEL_HOME` environment variable (default `~/Protoreel`).
|
|
153
|
+
|
|
154
|
+
## Troubleshooting
|
|
155
|
+
|
|
156
|
+
- **"Chromium is not installed"** — run `uvx protoreel setup`. Behind a strict network? Point `PROTOREEL_CHROMIUM` at an existing Chrome/Chromium.
|
|
157
|
+
- **The Studio port is busy** — another Studio is probably running; `protoreel studio --port 8800` picks another.
|
|
158
|
+
- **The video is silent** — the page doesn't define `window.__recorderAudio`; see *Sound*.
|
|
159
|
+
- **Clicks miss** — use a selector rather than coordinates, and check with `preview_frame`.
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
Protoreel is small on purpose and written to be easy to read. Start with [docs/architecture.md](docs/architecture.md), then see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT © Kingsley Mkpandiok
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# How Protoreel is built
|
|
2
|
+
|
|
3
|
+
Protoreel is about 1,800 lines of Python (half of it comments and docstrings) plus three small JavaScript files and the Studio page. Each module does one job.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
src/protoreel/
|
|
7
|
+
├── cli.py command line: studio, record, render-take, mcp, setup
|
|
8
|
+
├── mcp_server.py the MCP server and its six tools
|
|
9
|
+
├── renderer.py opens a page, plays an interaction, films it frame by frame
|
|
10
|
+
├── timeline.py scripts and takes → one input state per frame
|
|
11
|
+
├── takes.py takes captured in the Studio: load, save, smooth, re-centre
|
|
12
|
+
├── settings.py formats, resolutions and defaults
|
|
13
|
+
├── workspace.py the folder of prototypes, renders and takes; a tiny file server
|
|
14
|
+
├── encoder.py ffmpeg: PNG frames in, MP4 out, plus the soundtrack
|
|
15
|
+
├── audio.py asks the page for its sound (window.__recorderAudio)
|
|
16
|
+
├── browser.py launches Chromium, installing it on first use
|
|
17
|
+
├── page_scripts/ JavaScript injected into the prototype
|
|
18
|
+
│ ├── clock.js freezes time so we can step it
|
|
19
|
+
│ ├── cursor.js the on-screen pointer
|
|
20
|
+
│ └── background.js optional backdrop colour
|
|
21
|
+
└── studio/
|
|
22
|
+
├── server.py the Studio's web server and JSON API
|
|
23
|
+
└── static/index.html the Studio app (plain HTML/CSS/JS)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The one idea that makes it work: a frozen clock
|
|
27
|
+
|
|
28
|
+
Normally a browser animates in real time, so a slow machine (or a slow screenshot) drops frames.
|
|
29
|
+
`page_scripts/clock.js` replaces `requestAnimationFrame`, `setTimeout`, `setInterval`,
|
|
30
|
+
`performance.now`, `Date.now` and every CSS/Web Animation with versions driven by a number we
|
|
31
|
+
control. The renderer then repeats, for every frame:
|
|
32
|
+
|
|
33
|
+
1. apply this frame's input (move the mouse, press, type, scroll) — `renderer.Player.step`
|
|
34
|
+
2. advance the page's clock by exactly 1/60 s — `window.__advance(ms)`
|
|
35
|
+
3. take a screenshot and pipe it to ffmpeg — `encoder.VideoEncoder`
|
|
36
|
+
|
|
37
|
+
So a 5-second video at 60 fps is exactly 300 screenshots, each showing precisely the right moment.
|
|
38
|
+
|
|
39
|
+
## From an interaction to frames
|
|
40
|
+
|
|
41
|
+
`timeline.py` turns both kinds of interaction into the same stream of `FrameInput` objects:
|
|
42
|
+
|
|
43
|
+
- **Takes** (`take_frames`) replay captured events at their recorded times.
|
|
44
|
+
- **Scripts** (`script_frames`) expand each action into frames. Element positions are looked up
|
|
45
|
+
*when the action starts*, so earlier actions can open menus or move things around.
|
|
46
|
+
|
|
47
|
+
The renderer doesn't know or care which kind it is playing.
|
|
48
|
+
|
|
49
|
+
## Where things run
|
|
50
|
+
|
|
51
|
+
| You run | What happens |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `protoreel studio` | `studio.server.Studio` serves the app at http://127.0.0.1:8765 |
|
|
54
|
+
| `protoreel mcp` | `mcp_server.build_server` serves tools over stdio; `open_studio` starts the Studio in a background thread |
|
|
55
|
+
| `protoreel record` | `renderer.render` directly |
|
|
56
|
+
|
|
57
|
+
Every render serves the prototype's folder from a throwaway local web server
|
|
58
|
+
(`workspace.PrototypeServer`), so pages load over `http://` and behave like they do in a real browser.
|
|
59
|
+
|
|
60
|
+
## Tests
|
|
61
|
+
|
|
62
|
+
`pytest` runs everything, including real renders, a real Studio and a real MCP session over
|
|
63
|
+
stdio (about 30 seconds). Unit tests for the timeline and takes need no browser.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
`like-button.html` is a tiny prototype with an animation, a counter and sound (see how it
|
|
4
|
+
implements `window.__recorderAudio`). `like-button.json` is a script that likes it.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
uvx protoreel record examples/like-button.html examples/like-button.json --format 1x1 --zoom 2.5
|
|
8
|
+
```
|