Metadata-Version: 2.4
Name: scribecast
Version: 0.1.0
Summary: Turn any note into a narrated video. scribecast façade + vidkit engine (HyperFrames / Manim / Remotion), edge-tts narration, ffmpeg mux. CLI + library + MCP server.
Author: Prax
License: MIT
Project-URL: Homepage, https://github.com/praxstack/vidkit
Project-URL: Repository, https://github.com/praxstack/vidkit
Project-URL: Issues, https://github.com/praxstack/vidkit/issues
Keywords: video,notes,narration,edge-tts,manim,remotion,hyperframes,ffmpeg,mcp,obsidian
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: narrate
Requires-Dist: edge-tts>=6.1; extra == "narrate"
Provides-Extra: manim
Requires-Dist: manim>=0.18; extra == "manim"
Requires-Dist: numpy>=1.24; extra == "manim"
Requires-Dist: pillow>=10.0; extra == "manim"
Provides-Extra: all
Requires-Dist: edge-tts>=6.1; extra == "all"
Requires-Dist: manim>=0.18; extra == "all"
Requires-Dist: numpy>=1.24; extra == "all"
Requires-Dist: pillow>=10.0; extra == "all"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: pytest-xdist>=3; extra == "test"
Requires-Dist: coverage>=7; extra == "test"

# scribecast

Turn any note — from gbrain, PraxVault, ksum, a file, stdin, or several **merged** — into a
**narrated video**. scribecast is the façade; **vidkit** is the engine. Both ship in ONE
pip-installable, delete-proof wheel (this repo).

```
note ref ──► resolver ──► script (cards) ──► render (engine) ──► narrate (edge-tts) ──► mux ──► mp4
             gbrain/vault/                    hyperframes/                              (vidkit, video
             ksum/file/stdin/merge            manim/remotion                            duration authoritative)
```

scribecast **imports vidkit** (`from vidkit_core import ...`) — it never shells out to a vendored
copy. Note sources are read by calling the existing local CLIs (gbrain/ksum/praxvault-ask) as
subprocess *for note resolution only*.

## Install (delete-proof wheel — NOT editable)
```bash
pip install .                 # ships vidkit_core + vqkit + scribecast in one wheel
pip install .[narrate]        # + edge-tts narration
pip install .[manim]          # + manim engine (heavy, opt-in)
pip install .[all]            # narrate + manim
pip install .[test]           # pytest + pytest-xdist + coverage (for the test suite)
```
After install you can delete this source dir — the `scribecast` and `vidkit` CLIs keep working
(verified by the delete-proof scenario; see `scenarios/run_scenarios.py::s10_delete_proof`).
The Remotion engine additionally needs Node.js + the `remotion_kit` (with `npm install` run).

## Use — three interfaces, one core

**CLI (humans, Raycast, scripts):**
```bash
scribecast render file:notes.md -o out.mp4              # render a file note
scribecast render gbrain:two-brain-architecture          # render a gbrain page
scribecast render file:a.md --merge file:b.md file:c.md  # merge notes into one video
scribecast render file:n.md --engine manim --voice en-GB-RyanNeural
scribecast render file:n.md --engine remotion            # 1080p React/Remotion engine
scribecast recommend file:n.md       # advisory engine pick (user always decides)
scribecast sources                   # list note sources + availability
scribecast config                    # effective config + where each value came from
scribecast -v render file:n.md       # -v = INFO logs, -vv = DEBUG (to stderr)
```

**Library (Python):**
```python
from scribecast import render_note, resolve, recommend_engine
res = render_note("file:notes.md", out="out.mp4")        # -> RenderResult(output, engine, duration, ...)
text = resolve(["gbrain:slug-a", "file:b.md"])           # merge
rec  = recommend_engine(text)                            # advisory
```

**MCP (any agent — Claude/Hermes/Cursor/workers):**
```bash
python -m scribecast.mcp        # stdio JSON-RPC: render_note_video, recommend_engine, list_sources, list_voices
```
Zero mcp-sdk dependency (minimal stdio JSON-RPC) so it installs with the base wheel. Logs go to
stderr (stdout is the JSON-RPC transport).

**Obsidian plugin** (`obsidian-plugin/`): render the *current note* from inside Obsidian. It is a
thin consumer of the installed `scribecast` CLI (no vendoring). Commands: "Render active note to
video", "Recommend engine for active note"; settings for binary path / engine / voice / output dir.
Build: `cd obsidian-plugin && npm install && npm run build`, then copy `manifest.json` + `main.js`
into `<vault>/.obsidian/plugins/scribecast/`.

## Logging
The package uses the stdlib `logging` framework with library-correct discipline: importing
scribecast is silent (a `NullHandler` is attached), so it never pollutes a host application's
output. Logging is enabled by the entry points:
- CLI: `-v` (INFO) / `-vv` (DEBUG) — records go to stderr.
- MCP: always configured to stderr on server start.
- env: `SCRIBECAST_LOG_LEVEL=DEBUG|INFO|WARNING|ERROR`.
Every important path logs: note resolution (+ subprocess run/timeout/failure), engine selection,
each render stage (script → render → narrate → mux → probe → done), and MCP tool dispatch + errors.

## Configurable (flag > env > config-file > default)
- config file: `$SCRIBECAST_CONFIG` or `~/.scribecast/config.toml`
- env: `SCRIBECAST_ENGINE`, `SCRIBECAST_VOICE`, `SCRIBECAST_SOURCE`, `SCRIBECAST_OUT_DIR`, ...
- flags: `--engine --voice --source -o ...`
- `scribecast config` shows the effective value AND which layer set it.

## Engine selection — recommend, never decide
The selector RECOMMENDS one of `hyperframes | manim | remotion` from note content (math/LaTeX →
manim, web/React → remotion, else hyperframes). **The user's explicit `--engine` always wins** —
the recommendation is advisory and is always shown for transparency. (Heuristic today; an LLM
recommender can be added without changing the user-wins contract.)

## Default engine: `hyperframes` (cards)
The default renderer builds titled text cards with Pillow → an ffmpeg image-sequence mp4 → narration
→ mux. Needs only Pillow + ffmpeg (no browser, no Manim). All three engines work:
- **hyperframes** (default, dep-light): Pillow text-cards → ffmpeg, 1280×720.
- **manim** (opt-in `[manim]`): cinematic via `vqkit.CinematicScene` (MovingCameraScene + MathTex), 1280×720. Math/LaTeX spans render as real equations; prose renders as Text.
- **remotion** (opt-in): React/Remotion `ScribecastCards` composition (spring entrances, gradient), 1920×1080. Needs Node + `remotion_kit`. License-gated for orgs > 3 (Remotion License v1).

A forced `--engine` that lacks its dependency raises a clear error — never a silent fallback.

## Sources
| source | how | tool |
|---|---|---|
| `file` | read a local path | — |
| `stdin` | piped text | — |
| `gbrain` | `gbrain get <slug>` | gbrain |
| `vault` | `praxvault-ask <query>` | praxvault-ask |
| `ksum` | `ksum <url\|file> --no-save` | ksum |
| `openmemory` | `openmemory get <id>` | openmemory (optional) |
| `merge` | multiple refs → one video | — |

A missing source tool raises a clear `ResolverError` (never silent-empty).

## Tests
```bash
pytest -q                     # 126 fast unit tests in ~1s (real-render tests deselected)
pytest -q --run-slow          # full 163 tests incl. real manim/remotion/ffmpeg renders
pytest -q --run-slow -n auto  # full set in parallel (pytest-xdist)
coverage run --source=scribecast -m pytest --run-slow && coverage report   # 100% (692/692)
python scenarios/run_scenarios.py   # 10 realistic end-to-end scenarios (pass/fail + evidence)
python scenarios/benchmark.py       # per-engine timing baseline -> scenarios/baseline.json
```
Real-render tests are auto-marked `slow` (see `tests/conftest.py`) and deselected by default for a
fast dev loop; `--run-slow` runs them and the coverage gate enforces 100%. The mux-truncation
regression test (`tests/test_mux_no_truncation.py`) proves a short narration never truncates a
longer video — the bug this project fixed.
