explainer-studio 0.1.5__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.
- explainer_studio-0.1.5/.gitignore +231 -0
- explainer_studio-0.1.5/LICENSE +41 -0
- explainer_studio-0.1.5/PKG-INFO +149 -0
- explainer_studio-0.1.5/README.md +133 -0
- explainer_studio-0.1.5/brand/brand.css +145 -0
- explainer_studio-0.1.5/brand/fonts/JetBrainsMono.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/OFL-JetBrainsMono.txt +93 -0
- explainer_studio-0.1.5/brand/fonts/OFL-PTSerif.txt +93 -0
- explainer_studio-0.1.5/brand/fonts/OFL-SourceSans3.txt +93 -0
- explainer_studio-0.1.5/brand/fonts/PTSerif-Bold.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/PTSerif-BoldItalic.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/PTSerif-Italic.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/PTSerif-Regular.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/SourceSans3-Bold.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/SourceSans3-Italic.ttf +0 -0
- explainer_studio-0.1.5/brand/fonts/SourceSans3.ttf +0 -0
- explainer_studio-0.1.5/brand/frame.md +108 -0
- explainer_studio-0.1.5/brand/pronunciations.yaml +8 -0
- explainer_studio-0.1.5/brand/vendor/gsap.min.js +11 -0
- explainer_studio-0.1.5/pyproject.toml +56 -0
- explainer_studio-0.1.5/templates/scenes/blank.html +46 -0
- explainer_studio-0.1.5/templates/scenes/outro.html +50 -0
- explainer_studio-0.1.5/templates/scenes/points.html +55 -0
- explainer_studio-0.1.5/templates/scenes/quote.html +41 -0
- explainer_studio-0.1.5/templates/scenes/stat.html +57 -0
- explainer_studio-0.1.5/templates/scenes/title.html +46 -0
- explainer_studio-0.1.5/templates/script.md +51 -0
- explainer_studio-0.1.5/templates/thumbnail.html +28 -0
- explainer_studio-0.1.5/tools/studio/__init__.py +0 -0
- explainer_studio-0.1.5/tools/studio/build.py +196 -0
- explainer_studio-0.1.5/tools/studio/chatterbox_worker.py +96 -0
- explainer_studio-0.1.5/tools/studio/cli.py +265 -0
- explainer_studio-0.1.5/tools/studio/common.py +160 -0
- explainer_studio-0.1.5/tools/studio/music.py +66 -0
- explainer_studio-0.1.5/tools/studio/render.py +271 -0
- explainer_studio-0.1.5/tools/studio/script.py +158 -0
- explainer_studio-0.1.5/tools/studio/voice.py +633 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
episodes/*/build/
|
|
4
|
+
episodes/*/audio/tts/
|
|
5
|
+
episodes/*/assets/brand/
|
|
6
|
+
episodes/*/.hyperframes/
|
|
7
|
+
episodes/*/_render_*.html
|
|
8
|
+
out/
|
|
9
|
+
.env
|
|
10
|
+
|
|
11
|
+
# --- GitHub Python template ---
|
|
12
|
+
# Byte-compiled / optimized / DLL files
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.py[codz]
|
|
15
|
+
*$py.class
|
|
16
|
+
|
|
17
|
+
# C extensions
|
|
18
|
+
*.so
|
|
19
|
+
|
|
20
|
+
# Distribution / packaging
|
|
21
|
+
.Python
|
|
22
|
+
build/
|
|
23
|
+
develop-eggs/
|
|
24
|
+
dist/
|
|
25
|
+
downloads/
|
|
26
|
+
eggs/
|
|
27
|
+
.eggs/
|
|
28
|
+
lib/
|
|
29
|
+
lib64/
|
|
30
|
+
parts/
|
|
31
|
+
sdist/
|
|
32
|
+
var/
|
|
33
|
+
wheels/
|
|
34
|
+
share/python-wheels/
|
|
35
|
+
*.egg-info/
|
|
36
|
+
.installed.cfg
|
|
37
|
+
*.egg
|
|
38
|
+
MANIFEST
|
|
39
|
+
|
|
40
|
+
# PyInstaller
|
|
41
|
+
# Usually these files are written by a python script from a template
|
|
42
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
43
|
+
*.manifest
|
|
44
|
+
*.spec
|
|
45
|
+
|
|
46
|
+
# Installer logs
|
|
47
|
+
pip-log.txt
|
|
48
|
+
pip-delete-this-directory.txt
|
|
49
|
+
|
|
50
|
+
# Unit test / coverage reports
|
|
51
|
+
htmlcov/
|
|
52
|
+
.tox/
|
|
53
|
+
.nox/
|
|
54
|
+
.coverage
|
|
55
|
+
.coverage.*
|
|
56
|
+
.cache
|
|
57
|
+
nosetests.xml
|
|
58
|
+
coverage.xml
|
|
59
|
+
*.cover
|
|
60
|
+
*.py.cover
|
|
61
|
+
*.lcov
|
|
62
|
+
.hypothesis/
|
|
63
|
+
.pytest_cache/
|
|
64
|
+
cover/
|
|
65
|
+
|
|
66
|
+
# Translations
|
|
67
|
+
*.mo
|
|
68
|
+
*.pot
|
|
69
|
+
|
|
70
|
+
# Django stuff:
|
|
71
|
+
*.log
|
|
72
|
+
local_settings.py
|
|
73
|
+
db.sqlite3
|
|
74
|
+
db.sqlite3-journal
|
|
75
|
+
|
|
76
|
+
# Flask stuff:
|
|
77
|
+
instance/
|
|
78
|
+
.webassets-cache
|
|
79
|
+
|
|
80
|
+
# Scrapy stuff:
|
|
81
|
+
.scrapy
|
|
82
|
+
|
|
83
|
+
# Sphinx documentation
|
|
84
|
+
docs/_build/
|
|
85
|
+
|
|
86
|
+
# PyBuilder
|
|
87
|
+
.pybuilder/
|
|
88
|
+
target/
|
|
89
|
+
|
|
90
|
+
# Jupyter Notebook
|
|
91
|
+
.ipynb_checkpoints
|
|
92
|
+
|
|
93
|
+
# IPython
|
|
94
|
+
profile_default/
|
|
95
|
+
ipython_config.py
|
|
96
|
+
|
|
97
|
+
# pyenv
|
|
98
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
99
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
100
|
+
# .python-version
|
|
101
|
+
|
|
102
|
+
# pipenv
|
|
103
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
104
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
105
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
106
|
+
# install all needed dependencies.
|
|
107
|
+
# Pipfile.lock
|
|
108
|
+
|
|
109
|
+
# uv
|
|
110
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
111
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
112
|
+
# commonly ignored for libraries.
|
|
113
|
+
# uv.lock
|
|
114
|
+
|
|
115
|
+
# poetry
|
|
116
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
117
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
118
|
+
# commonly ignored for libraries.
|
|
119
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
120
|
+
# poetry.lock
|
|
121
|
+
# poetry.toml
|
|
122
|
+
|
|
123
|
+
# pdm
|
|
124
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
125
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
126
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
127
|
+
# pdm.lock
|
|
128
|
+
# pdm.toml
|
|
129
|
+
.pdm-python
|
|
130
|
+
.pdm-build/
|
|
131
|
+
|
|
132
|
+
# pixi
|
|
133
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
134
|
+
# pixi.lock
|
|
135
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
136
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
137
|
+
.pixi/*
|
|
138
|
+
!.pixi/config.toml
|
|
139
|
+
|
|
140
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
141
|
+
__pypackages__/
|
|
142
|
+
|
|
143
|
+
# Celery stuff
|
|
144
|
+
celerybeat-schedule*
|
|
145
|
+
celerybeat.pid
|
|
146
|
+
|
|
147
|
+
# Redis
|
|
148
|
+
*.rdb
|
|
149
|
+
*.aof
|
|
150
|
+
*.pid
|
|
151
|
+
|
|
152
|
+
# RabbitMQ
|
|
153
|
+
mnesia/
|
|
154
|
+
rabbitmq/
|
|
155
|
+
rabbitmq-data/
|
|
156
|
+
|
|
157
|
+
# ActiveMQ
|
|
158
|
+
activemq-data/
|
|
159
|
+
|
|
160
|
+
# SageMath parsed files
|
|
161
|
+
*.sage.py
|
|
162
|
+
|
|
163
|
+
# Environments
|
|
164
|
+
.env
|
|
165
|
+
.envrc
|
|
166
|
+
.venv
|
|
167
|
+
env/
|
|
168
|
+
venv/
|
|
169
|
+
ENV/
|
|
170
|
+
env.bak/
|
|
171
|
+
venv.bak/
|
|
172
|
+
|
|
173
|
+
# Spyder project settings
|
|
174
|
+
.spyderproject
|
|
175
|
+
.spyproject
|
|
176
|
+
|
|
177
|
+
# Rope project settings
|
|
178
|
+
.ropeproject
|
|
179
|
+
|
|
180
|
+
# mkdocs/Zensical documentation
|
|
181
|
+
/site
|
|
182
|
+
|
|
183
|
+
# mypy
|
|
184
|
+
.mypy_cache/
|
|
185
|
+
.dmypy.json
|
|
186
|
+
dmypy.json
|
|
187
|
+
|
|
188
|
+
# Pyre type checker
|
|
189
|
+
.pyre/
|
|
190
|
+
|
|
191
|
+
# pytype static type analyzer
|
|
192
|
+
.pytype/
|
|
193
|
+
|
|
194
|
+
# Cython debug symbols
|
|
195
|
+
cython_debug/
|
|
196
|
+
|
|
197
|
+
# PyCharm
|
|
198
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
199
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
201
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
202
|
+
# .idea/
|
|
203
|
+
|
|
204
|
+
# Abstra
|
|
205
|
+
# Abstra is an AI-powered process automation framework.
|
|
206
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
207
|
+
# Learn more at https://abstra.io/docs
|
|
208
|
+
.abstra/
|
|
209
|
+
|
|
210
|
+
# Visual Studio Code
|
|
211
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
212
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
213
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
214
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
215
|
+
# .vscode/
|
|
216
|
+
# Temporary file for partial code execution
|
|
217
|
+
tempCodeRunnerFile.py
|
|
218
|
+
|
|
219
|
+
# Ruff stuff:
|
|
220
|
+
.ruff_cache/
|
|
221
|
+
|
|
222
|
+
# PyPI configuration file
|
|
223
|
+
.pypirc
|
|
224
|
+
|
|
225
|
+
# Marimo
|
|
226
|
+
marimo/_static/
|
|
227
|
+
marimo/_lsp/
|
|
228
|
+
__marimo__/
|
|
229
|
+
|
|
230
|
+
# Streamlit
|
|
231
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 World Socialist Web Site
|
|
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.
|
|
22
|
+
|
|
23
|
+
-------------------------------------------------------------------------------
|
|
24
|
+
Scope
|
|
25
|
+
|
|
26
|
+
The MIT licence above covers the studio software and its documentation:
|
|
27
|
+
tools/, templates/, brand/brand.css, brand/frame.md, brand/pronunciations.yaml,
|
|
28
|
+
docs/, the .github/ workflows, pyproject.toml, README.md, AGENTS.md and CLAUDE.md.
|
|
29
|
+
|
|
30
|
+
It does NOT cover editorial content. Everything under episodes/ and scripts/
|
|
31
|
+
(scripts, narration audio, timings, scene designs, thumbnails and the videos
|
|
32
|
+
made from them) is Copyright (c) 2026 World Socialist Web Site, all rights
|
|
33
|
+
reserved.
|
|
34
|
+
|
|
35
|
+
Third-party files keep their own licences:
|
|
36
|
+
- brand/fonts/: PT Serif, Source Sans 3 and JetBrains Mono, SIL Open Font
|
|
37
|
+
License 1.1 (see the OFL-*.txt files alongside them).
|
|
38
|
+
- brand/vendor/gsap.min.js: GSAP, GreenSock Standard License
|
|
39
|
+
(https://gsap.com/standard-license), as stated in the file header.
|
|
40
|
+
- episodes/*/assets/media/: photographs, maps and footage remain under the
|
|
41
|
+
terms of their original sources, credited on screen where they are used.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: explainer-studio
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Command-line studio for narrated explainer videos (HyperFrames + Kokoro TTS + ffmpeg)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: <3.14,>=3.12
|
|
8
|
+
Requires-Dist: kokoro-onnx>=0.6.1
|
|
9
|
+
Requires-Dist: misaki>=0.9
|
|
10
|
+
Requires-Dist: num2words>=0.5
|
|
11
|
+
Requires-Dist: numpy>=2.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: soundfile>=0.12
|
|
14
|
+
Requires-Dist: spacy<3.9,>=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Explainer Studio
|
|
18
|
+
|
|
19
|
+
[](https://github.com/JoeMountWSWS/explainer-studio/actions/workflows/ci.yml)
|
|
20
|
+
|
|
21
|
+
A command-line studio for narrated explainer videos in the style of [wsws.org](https://www.wsws.org).
|
|
22
|
+
You write a script in Markdown. The studio narrates it with local TTS, aligns every word to the audio,
|
|
23
|
+
and turns each scene into a [HyperFrames](https://www.npmjs.com/package/hyperframes) HTML composition
|
|
24
|
+
that is timed to the narration. It then renders a mastered 1920×1080 MP4 for YouTube, with captions
|
|
25
|
+
and chapter markers.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
script.md ──► narration (Kokoro, local; or ElevenLabs) ──► word timings (Whisper)
|
|
29
|
+
──► scenes/<id>.html (HyperFrames + GSAP, timed to cues) ──► cached per-scene renders
|
|
30
|
+
──► out/<slug>.mp4 (−14 LUFS) + out/<slug>.srt + out/<slug>.chapters.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
# Installation
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Linux (developed on Arch with an AMD GPU; the render uses Chromium's GPU capture)
|
|
38
|
+
- [uv](https://docs.astral.sh/uv/), with Python 3.12 or 3.13 (`kokoro-onnx` doesn't support 3.14 yet)
|
|
39
|
+
- Node.js with `npx`. The HyperFrames CLI is fetched on demand at the version pinned in `tools/studio/common.py`.
|
|
40
|
+
- ffmpeg and Chromium
|
|
41
|
+
- Optional: an ElevenLabs API key in `.env` (`ELEVENLABS_API_KEY=…`) for cloud narration
|
|
42
|
+
|
|
43
|
+
The first `sync` downloads the Kokoro-82M model to `~/.cache/hyperframes/tts` and spaCy's
|
|
44
|
+
`en_core_web_sm` to `~/.cache/hyperframes/spacy`.
|
|
45
|
+
|
|
46
|
+
# Usage
|
|
47
|
+
|
|
48
|
+
Making a video with the studio is a loop of writing and listening. You write the script as plain
|
|
49
|
+
Markdown, and the studio reads it aloud, works out exactly when each word is spoken, and hands those
|
|
50
|
+
timings to your scenes so the visuals move in step with the voice. Change a sentence and only that
|
|
51
|
+
sentence is re-recorded and only the affected scenes are re-rendered, so you can refine a five-minute
|
|
52
|
+
video in seconds rather than starting over. Everything runs from one command, `uv run studio`, and
|
|
53
|
+
nothing leaves your machine unless you choose cloud narration.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
With the [requirements](#requirements) in place, either install the `studio` command from PyPI or
|
|
58
|
+
clone the repository.
|
|
59
|
+
|
|
60
|
+
**From PyPI**, to make videos in a folder of your own:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install explainer-studio # or run it without installing: uvx --from explainer-studio studio …
|
|
64
|
+
mkdir my-videos && cd my-videos
|
|
65
|
+
studio new my-video "My Video"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The folder you run `studio` in becomes the workspace. It holds `episodes/`, `out/` and `.env`, and
|
|
69
|
+
`studio` finds it from any subdirectory (or set `STUDIO_ROOT`). The house style (`brand/`) and scene
|
|
70
|
+
templates ship with the package. To customise them, copy `brand/` or `templates/` from this repository
|
|
71
|
+
into the workspace, and the studio uses your copy instead. Music beds and voice clips go in the
|
|
72
|
+
workspace's `brand/music/` and `brand/voices/`. Use `studio` wherever this README says `uv run studio`.
|
|
73
|
+
|
|
74
|
+
**From a clone**, to work on the studio itself or to use the example episodes:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone git@github.com:JoeMountWSWS/explainer-studio.git
|
|
78
|
+
cd explainer-studio
|
|
79
|
+
uv sync
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run commands from the repository root (or any folder below it). Confirm the install with
|
|
83
|
+
`uv run studio --help`. The first `sync` of an episode downloads the Kokoro voice model and the
|
|
84
|
+
spaCy English model, so it takes a little longer than later runs.
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv run studio new my-video "My Video" # scaffold episodes/my-video/
|
|
90
|
+
$EDITOR episodes/my-video/script.md # write the script
|
|
91
|
+
uv run studio sync my-video # narrate, align, stub scene files, write index.html
|
|
92
|
+
uv run studio status my-video # words, estimated vs actual length, cues per scene
|
|
93
|
+
# design each episodes/my-video/scenes/<id>.html
|
|
94
|
+
uv run studio preview my-video # HyperFrames Studio in the browser, with narration
|
|
95
|
+
uv run studio check my-video # lint, layout and contrast audits (must pass)
|
|
96
|
+
uv run studio render my-video --draft # quick review copy -> out/my-video-draft.mp4
|
|
97
|
+
uv run studio render my-video # final -> out/my-video.mp4 + .srt + .chapters.txt
|
|
98
|
+
uv run studio thumbnail my-video # out/my-video-thumbnail.jpg
|
|
99
|
+
uv run studio voices my-video # audition Kokoro voice blends -> out/voice-samples/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Only scenes that have changed are re-rendered. Changing a paragraph of narration re-synthesises
|
|
103
|
+
that paragraph and nothing else, and re-times the visuals that depend on it.
|
|
104
|
+
|
|
105
|
+
## Writing a script
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
---
|
|
109
|
+
title: The Paris Commune
|
|
110
|
+
voice: bf_emma*0.6 + bf_isabella*0.4
|
|
111
|
+
speed: 0.85
|
|
112
|
+
---
|
|
113
|
+
## hook: Seventy-two days
|
|
114
|
+
> template: title
|
|
115
|
+
|
|
116
|
+
In the spring of 1871, the working class took power in a major city. {sub} This is the story of the Paris Commune.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each `## id: Chapter title` heading is a scene and a YouTube chapter. `> template:` chooses the
|
|
120
|
+
starting pattern for the scene file (`title`, `stat`, `quote`, `points`, `outro`, `blank`). Other `>`
|
|
121
|
+
lines are visual notes and are never spoken. `{name}` marks a cue that the scene can animate on,
|
|
122
|
+
and `{pause 1.5}` inserts a pause.
|
|
123
|
+
|
|
124
|
+
The full documentation is at <https://joemountwsws.github.io/explainer-studio/>. [AGENTS.md](AGENTS.md) is the reference for coding agents: every front-matter setting, voice choice, the rules for
|
|
125
|
+
authoring scenes, the render pipeline and known problems. Read [brand/frame.md](brand/frame.md)
|
|
126
|
+
before designing scenes.
|
|
127
|
+
|
|
128
|
+
## Layout
|
|
129
|
+
|
|
130
|
+
| Path | What it is |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `tools/studio/` | The Python CLI (`uv run studio …`) |
|
|
133
|
+
| `brand/` | House style shared by every episode: design spec, CSS, fonts, GSAP, pronunciations |
|
|
134
|
+
| `templates/` | Starter script, scene patterns and thumbnail |
|
|
135
|
+
| `episodes/<slug>/` | One HyperFrames project per video. `script.md` is the source of truth |
|
|
136
|
+
| `docs/` | The documentation site (MkDocs), including notes such as British ElevenLabs voices |
|
|
137
|
+
| `out/` | Rendered deliverables (gitignored) |
|
|
138
|
+
|
|
139
|
+
Worked examples: `episodes/demo-paris-commune` (Kokoro, 38s) and `episodes/demo-elevenlabs`
|
|
140
|
+
(the same script narrated by ElevenLabs).
|
|
141
|
+
|
|
142
|
+
## Working with coding agents
|
|
143
|
+
|
|
144
|
+
The studio is designed to be driven by Claude Code or another coding agent. `AGENTS.md` (loaded
|
|
145
|
+
through `CLAUDE.md`) gives the agent the conventions it needs, the HyperFrames skills cover the
|
|
146
|
+
composition format and motion, and the `make-explainer-video` skill
|
|
147
|
+
(`.claude/skills/make-explainer-video/`) walks Claude through the whole workflow. Start `claude` in
|
|
148
|
+
the repository root and ask for a video, or run `/make-explainer-video`. See
|
|
149
|
+
[Working with a coding agent](https://joemountwsws.github.io/explainer-studio/coding-agents/).
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Explainer Studio
|
|
2
|
+
|
|
3
|
+
[](https://github.com/JoeMountWSWS/explainer-studio/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A command-line studio for narrated explainer videos in the style of [wsws.org](https://www.wsws.org).
|
|
6
|
+
You write a script in Markdown. The studio narrates it with local TTS, aligns every word to the audio,
|
|
7
|
+
and turns each scene into a [HyperFrames](https://www.npmjs.com/package/hyperframes) HTML composition
|
|
8
|
+
that is timed to the narration. It then renders a mastered 1920×1080 MP4 for YouTube, with captions
|
|
9
|
+
and chapter markers.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
script.md ──► narration (Kokoro, local; or ElevenLabs) ──► word timings (Whisper)
|
|
13
|
+
──► scenes/<id>.html (HyperFrames + GSAP, timed to cues) ──► cached per-scene renders
|
|
14
|
+
──► out/<slug>.mp4 (−14 LUFS) + out/<slug>.srt + out/<slug>.chapters.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
# Installation
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Linux (developed on Arch with an AMD GPU; the render uses Chromium's GPU capture)
|
|
22
|
+
- [uv](https://docs.astral.sh/uv/), with Python 3.12 or 3.13 (`kokoro-onnx` doesn't support 3.14 yet)
|
|
23
|
+
- Node.js with `npx`. The HyperFrames CLI is fetched on demand at the version pinned in `tools/studio/common.py`.
|
|
24
|
+
- ffmpeg and Chromium
|
|
25
|
+
- Optional: an ElevenLabs API key in `.env` (`ELEVENLABS_API_KEY=…`) for cloud narration
|
|
26
|
+
|
|
27
|
+
The first `sync` downloads the Kokoro-82M model to `~/.cache/hyperframes/tts` and spaCy's
|
|
28
|
+
`en_core_web_sm` to `~/.cache/hyperframes/spacy`.
|
|
29
|
+
|
|
30
|
+
# Usage
|
|
31
|
+
|
|
32
|
+
Making a video with the studio is a loop of writing and listening. You write the script as plain
|
|
33
|
+
Markdown, and the studio reads it aloud, works out exactly when each word is spoken, and hands those
|
|
34
|
+
timings to your scenes so the visuals move in step with the voice. Change a sentence and only that
|
|
35
|
+
sentence is re-recorded and only the affected scenes are re-rendered, so you can refine a five-minute
|
|
36
|
+
video in seconds rather than starting over. Everything runs from one command, `uv run studio`, and
|
|
37
|
+
nothing leaves your machine unless you choose cloud narration.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
With the [requirements](#requirements) in place, either install the `studio` command from PyPI or
|
|
42
|
+
clone the repository.
|
|
43
|
+
|
|
44
|
+
**From PyPI**, to make videos in a folder of your own:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv tool install explainer-studio # or run it without installing: uvx --from explainer-studio studio …
|
|
48
|
+
mkdir my-videos && cd my-videos
|
|
49
|
+
studio new my-video "My Video"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The folder you run `studio` in becomes the workspace. It holds `episodes/`, `out/` and `.env`, and
|
|
53
|
+
`studio` finds it from any subdirectory (or set `STUDIO_ROOT`). The house style (`brand/`) and scene
|
|
54
|
+
templates ship with the package. To customise them, copy `brand/` or `templates/` from this repository
|
|
55
|
+
into the workspace, and the studio uses your copy instead. Music beds and voice clips go in the
|
|
56
|
+
workspace's `brand/music/` and `brand/voices/`. Use `studio` wherever this README says `uv run studio`.
|
|
57
|
+
|
|
58
|
+
**From a clone**, to work on the studio itself or to use the example episodes:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone git@github.com:JoeMountWSWS/explainer-studio.git
|
|
62
|
+
cd explainer-studio
|
|
63
|
+
uv sync
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run commands from the repository root (or any folder below it). Confirm the install with
|
|
67
|
+
`uv run studio --help`. The first `sync` of an episode downloads the Kokoro voice model and the
|
|
68
|
+
spaCy English model, so it takes a little longer than later runs.
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv run studio new my-video "My Video" # scaffold episodes/my-video/
|
|
74
|
+
$EDITOR episodes/my-video/script.md # write the script
|
|
75
|
+
uv run studio sync my-video # narrate, align, stub scene files, write index.html
|
|
76
|
+
uv run studio status my-video # words, estimated vs actual length, cues per scene
|
|
77
|
+
# design each episodes/my-video/scenes/<id>.html
|
|
78
|
+
uv run studio preview my-video # HyperFrames Studio in the browser, with narration
|
|
79
|
+
uv run studio check my-video # lint, layout and contrast audits (must pass)
|
|
80
|
+
uv run studio render my-video --draft # quick review copy -> out/my-video-draft.mp4
|
|
81
|
+
uv run studio render my-video # final -> out/my-video.mp4 + .srt + .chapters.txt
|
|
82
|
+
uv run studio thumbnail my-video # out/my-video-thumbnail.jpg
|
|
83
|
+
uv run studio voices my-video # audition Kokoro voice blends -> out/voice-samples/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Only scenes that have changed are re-rendered. Changing a paragraph of narration re-synthesises
|
|
87
|
+
that paragraph and nothing else, and re-times the visuals that depend on it.
|
|
88
|
+
|
|
89
|
+
## Writing a script
|
|
90
|
+
|
|
91
|
+
```markdown
|
|
92
|
+
---
|
|
93
|
+
title: The Paris Commune
|
|
94
|
+
voice: bf_emma*0.6 + bf_isabella*0.4
|
|
95
|
+
speed: 0.85
|
|
96
|
+
---
|
|
97
|
+
## hook: Seventy-two days
|
|
98
|
+
> template: title
|
|
99
|
+
|
|
100
|
+
In the spring of 1871, the working class took power in a major city. {sub} This is the story of the Paris Commune.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Each `## id: Chapter title` heading is a scene and a YouTube chapter. `> template:` chooses the
|
|
104
|
+
starting pattern for the scene file (`title`, `stat`, `quote`, `points`, `outro`, `blank`). Other `>`
|
|
105
|
+
lines are visual notes and are never spoken. `{name}` marks a cue that the scene can animate on,
|
|
106
|
+
and `{pause 1.5}` inserts a pause.
|
|
107
|
+
|
|
108
|
+
The full documentation is at <https://joemountwsws.github.io/explainer-studio/>. [AGENTS.md](AGENTS.md) is the reference for coding agents: every front-matter setting, voice choice, the rules for
|
|
109
|
+
authoring scenes, the render pipeline and known problems. Read [brand/frame.md](brand/frame.md)
|
|
110
|
+
before designing scenes.
|
|
111
|
+
|
|
112
|
+
## Layout
|
|
113
|
+
|
|
114
|
+
| Path | What it is |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `tools/studio/` | The Python CLI (`uv run studio …`) |
|
|
117
|
+
| `brand/` | House style shared by every episode: design spec, CSS, fonts, GSAP, pronunciations |
|
|
118
|
+
| `templates/` | Starter script, scene patterns and thumbnail |
|
|
119
|
+
| `episodes/<slug>/` | One HyperFrames project per video. `script.md` is the source of truth |
|
|
120
|
+
| `docs/` | The documentation site (MkDocs), including notes such as British ElevenLabs voices |
|
|
121
|
+
| `out/` | Rendered deliverables (gitignored) |
|
|
122
|
+
|
|
123
|
+
Worked examples: `episodes/demo-paris-commune` (Kokoro, 38s) and `episodes/demo-elevenlabs`
|
|
124
|
+
(the same script narrated by ElevenLabs).
|
|
125
|
+
|
|
126
|
+
## Working with coding agents
|
|
127
|
+
|
|
128
|
+
The studio is designed to be driven by Claude Code or another coding agent. `AGENTS.md` (loaded
|
|
129
|
+
through `CLAUDE.md`) gives the agent the conventions it needs, the HyperFrames skills cover the
|
|
130
|
+
composition format and motion, and the `make-explainer-video` skill
|
|
131
|
+
(`.claude/skills/make-explainer-video/`) walks Claude through the whole workflow. Start `claude` in
|
|
132
|
+
the repository root and ask for a video, or run `/make-explainer-video`. See
|
|
133
|
+
[Working with a coding agent](https://joemountwsws.github.io/explainer-studio/coding-agents/).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Explainer house style, after wsws.org. Tokens mirror brand/frame.md.
|
|
2
|
+
`studio sync` copies this file (with fonts/ and gsap) into each episode's assets/brand/. */
|
|
3
|
+
|
|
4
|
+
@font-face { font-family: "PT Serif"; src: url("fonts/PTSerif-Regular.ttf") format("truetype"); font-weight: 400; font-style: normal; }
|
|
5
|
+
@font-face { font-family: "PT Serif"; src: url("fonts/PTSerif-Bold.ttf") format("truetype"); font-weight: 700; font-style: normal; }
|
|
6
|
+
@font-face { font-family: "PT Serif"; src: url("fonts/PTSerif-Italic.ttf") format("truetype"); font-weight: 400; font-style: italic; }
|
|
7
|
+
@font-face { font-family: "PT Serif"; src: url("fonts/PTSerif-BoldItalic.ttf") format("truetype"); font-weight: 700; font-style: italic; }
|
|
8
|
+
@font-face { font-family: "Source Sans 3"; src: url("fonts/SourceSans3.ttf") format("truetype"); font-weight: 200 900; font-style: normal; }
|
|
9
|
+
@font-face { font-family: "Source Sans 3"; src: url("fonts/SourceSans3-Italic.ttf") format("truetype"); font-weight: 200 900; font-style: italic; }
|
|
10
|
+
@font-face { font-family: "JetBrains Mono"; src: url("fonts/JetBrainsMono.ttf") format("truetype"); font-weight: 100 800; }
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
--paper: #ffffff;
|
|
14
|
+
--panel: #f4f4f4;
|
|
15
|
+
--ink: #231f20;
|
|
16
|
+
--ink-2: #333333;
|
|
17
|
+
--ink-soft: #555555;
|
|
18
|
+
--rule: #cccccc;
|
|
19
|
+
--red: #e71e25;
|
|
20
|
+
--red-wash: #ffdfdf;
|
|
21
|
+
--tag: #1b2129;
|
|
22
|
+
|
|
23
|
+
--font-serif: "PT Serif", Georgia, serif;
|
|
24
|
+
--font-sans: "Source Sans 3", "Helvetica Neue", Arial, sans-serif;
|
|
25
|
+
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
|
26
|
+
|
|
27
|
+
--safe-x: 192px; /* title-safe inset (10%) */
|
|
28
|
+
--safe-y: 108px;
|
|
29
|
+
--hairline: 2px solid var(--rule);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Full-bleed white page. */
|
|
33
|
+
.paper { position: absolute; inset: 0; background: var(--paper); }
|
|
34
|
+
|
|
35
|
+
/* Title-safe content box. Put scene content inside one of these. */
|
|
36
|
+
.stage {
|
|
37
|
+
position: absolute;
|
|
38
|
+
inset: var(--safe-y) var(--safe-x);
|
|
39
|
+
color: var(--ink-2);
|
|
40
|
+
font-family: var(--font-serif);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Reel (9:16, 1080x1920) content box. Platform UI covers the top ~200px, the right ~140px
|
|
44
|
+
(like/comment/share) and the bottom ~480px (caption, handle, audio). Burned captions sit
|
|
45
|
+
in the band y 1220-1440, so scene content ends by y 1180. */
|
|
46
|
+
.stage-reel {
|
|
47
|
+
position: absolute;
|
|
48
|
+
inset: 220px 72px 740px 72px;
|
|
49
|
+
color: var(--ink-2);
|
|
50
|
+
font-family: var(--font-serif);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Heavy ink rule + uppercase label, like the site's "TOP STORIES" head.
|
|
54
|
+
<div class="section-head"><div class="section-rule"></div><div class="label">History</div></div>
|
|
55
|
+
Draw the rule on with scaleX (transform-origin is left). */
|
|
56
|
+
.section-head { display: flex; flex-direction: column; gap: 18px; }
|
|
57
|
+
.section-rule { height: 6px; background: var(--ink); transform-origin: left center; }
|
|
58
|
+
|
|
59
|
+
.label {
|
|
60
|
+
font-family: var(--font-sans);
|
|
61
|
+
font-weight: 700;
|
|
62
|
+
font-size: 26px;
|
|
63
|
+
letter-spacing: 0.12em;
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
color: var(--ink);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Dark category tag ("PERSPECTIVE"). */
|
|
69
|
+
.tag {
|
|
70
|
+
display: inline-block;
|
|
71
|
+
align-self: flex-start;
|
|
72
|
+
background: var(--tag);
|
|
73
|
+
color: #ffffff;
|
|
74
|
+
font-family: var(--font-sans);
|
|
75
|
+
font-weight: 700;
|
|
76
|
+
font-size: 24px;
|
|
77
|
+
letter-spacing: 0.12em;
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
padding: 10px 18px 9px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.headline {
|
|
83
|
+
font-family: var(--font-serif);
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
font-size: 104px;
|
|
86
|
+
line-height: 1.12;
|
|
87
|
+
color: var(--ink);
|
|
88
|
+
margin: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.standfirst {
|
|
92
|
+
font-family: var(--font-serif);
|
|
93
|
+
font-style: italic;
|
|
94
|
+
font-weight: 400;
|
|
95
|
+
font-size: 48px;
|
|
96
|
+
line-height: 1.35;
|
|
97
|
+
color: var(--ink-soft);
|
|
98
|
+
margin: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.body {
|
|
102
|
+
font-family: var(--font-serif);
|
|
103
|
+
font-weight: 400;
|
|
104
|
+
font-size: 46px;
|
|
105
|
+
line-height: 1.4;
|
|
106
|
+
color: var(--ink-2);
|
|
107
|
+
margin: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Source / byline / date line. */
|
|
111
|
+
.byline {
|
|
112
|
+
font-family: var(--font-sans);
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
font-size: 28px;
|
|
115
|
+
color: var(--ink-soft);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Blockquote with red left rule. */
|
|
119
|
+
.quote {
|
|
120
|
+
border-left: 6px solid var(--red);
|
|
121
|
+
padding-left: 56px;
|
|
122
|
+
font-family: var(--font-serif);
|
|
123
|
+
color: var(--ink);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.italic { font-style: italic; }
|
|
127
|
+
.soft { color: var(--ink-soft); }
|
|
128
|
+
.red { color: var(--red); }
|
|
129
|
+
.sans { font-family: var(--font-sans); }
|
|
130
|
+
.mono { font-family: var(--font-mono); font-weight: 500; }
|
|
131
|
+
.hairline { height: 0; border-top: var(--hairline); }
|
|
132
|
+
.panel { background: var(--panel); padding: 40px 48px; }
|
|
133
|
+
|
|
134
|
+
/* Highlight wash: <span id="x-key" class="hl">phrase</span>
|
|
135
|
+
Sweep it in on a cue: tl.to("#x-key", { backgroundSize: "100% 82%", duration: 0.5 }, cue("key")) */
|
|
136
|
+
.hl {
|
|
137
|
+
background-image: linear-gradient(var(--red-wash), var(--red-wash));
|
|
138
|
+
background-repeat: no-repeat;
|
|
139
|
+
background-position: left 70%;
|
|
140
|
+
background-size: 0% 82%;
|
|
141
|
+
padding: 0 0.06em;
|
|
142
|
+
margin: 0 -0.06em;
|
|
143
|
+
-webkit-box-decoration-break: clone;
|
|
144
|
+
box-decoration-break: clone;
|
|
145
|
+
}
|