Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,29 @@ examples/skills/*/dist/
coverage/
*.log
.tmp-tests/
.tmp-video-studio-e2e/
.commandcode/

# LocalAnt runtime state accidentally created at the repo root (gateway home = cwd)
/approvals.json
/config.json
/secrets.json
/token
/vault.key
/audit/
/output/
/backups/
/logs/

# Local render / e2e scratch
.tmp-e2e-render/
.tmp-video-studio-variants/
.tmp-localant-five-visual-styles/

# One-off dev scratch files
/x1
/x2
/xrun.cjs
/serve-output.cjs
/patch_remotion_template.py
/scripts/x5.ts
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ localant tools profile coding # switch profile
localant tools list # see what's exposed
```

Optional product surfaces such as **LocalAnt Video Studio** and the generated-image
**Asset bridge** are disabled for ChatGPT by default. Enable them from
Dashboard → Settings → Optional ChatGPT tools when you want those tools
advertised over MCP.

Every tool's risk level (0–4) and how each family is gated is documented in
[docs/tools.md](docs/tools.md).

Expand Down Expand Up @@ -382,6 +387,18 @@ All routes share one validation path (magic-byte sniff → MIME allowlist →
SVG-safety scan → sha256 → atomic write with backup). Risk 2 (no approval in
`open` mode). See [docs/asset-bridge.md](docs/asset-bridge.md).

For Video Studio, save the generated image first, then attach it to a scene with
`video_studio_add_asset`. The image is copied into the project workspace and
passed to Remotion as a scene visual, so ChatGPT-generated images can become
animated short-video material.

For large generated images, use `asset_upload_chunk` repeatedly and finish with
`asset_commit_upload`; the committed file can then be attached with
`video_studio_add_asset`.

These tools are not advertised to ChatGPT unless the Asset bridge feature is
enabled in Dashboard → Settings → Optional ChatGPT tools.

## Browser automation

Playwright-based (optional peer dependency), using an **isolated profile** by
Expand Down
43 changes: 42 additions & 1 deletion docs/asset-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,46 @@ asset_save_image {

Returns `{ path, bytes, mimeType, sha256, source, backupId? }`.

### Large generated images

When ChatGPT can read the image bytes but a single `asset_save_image` base64
payload would be too large, stream it in chunks:

```jsonc
asset_upload_chunk {
"uploadId": "generated-scene-001",
"index": 0,
"data": "base64 text chunk"
}

asset_commit_upload {
"uploadId": "generated-scene-001",
"chunks": 12,
"destination": "/absolute/path/to/scene-001.png"
}
```

`asset_commit_upload` assembles the chunks, then runs the exact same image
validation and atomic write path as `asset_save_image`. Chunk payloads are not
recorded in the audit log.

## Video Studio handoff

Use `asset_save_image` first, then import the saved file into a Video Studio
scene:

```jsonc
video_studio_add_asset {
"projectId": "mqu-example",
"sceneId": "scene-001",
"path": "/absolute/path/to/chatgpt-generated.png"
}
```

`video_studio_add_asset` copies PNG/JPEG/WebP files into the project's
`assets/` directory, records the scene `assetPath`, and passes the image through
`render-props.json` so Remotion renders it as the scene visual.

## Validation (every route)

Before anything is written, the resolved bytes must pass:
Expand Down Expand Up @@ -87,7 +127,8 @@ never leaves a corrupt asset in place.
## Profiles

`asset_save_image` is exposed in the `coding` and `full` profiles (not
`minimal`).
`minimal`) only after the Asset bridge feature is enabled from Dashboard →
Settings → Optional ChatGPT tools.

```bash
localant tools profile coding
Expand Down
1 change: 1 addition & 0 deletions docs/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Ready-to-read references live in [`examples/skills/`](../examples/skills):
| `file-organizer` | Filesystem writes — sort a folder by type/date. A "local hands" chore ChatGPT can't do itself. |
| `local-backup` | Shell allowlist (`tar` only) — timestamped `.tar.gz` snapshots. |
| `article-publisher` | Network + secrets + git — publish to Zenn/Qiita/note. |
| `flowkit` | Local REST API — control a running FlowKit video-generation server. |

## Generating a skill from ChatGPT

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# LocalAnt Video Studio Remotion + VOICEVOX Design

## Goal

LocalAnt Video Studio generates presentation-style short videos for OSS introductions, app introductions, and product introductions. It does not call paid external text-to-video APIs. The primary path is local Remotion rendering with local VOICEVOX narration.

## Primary Pipeline

1. Create a script and scene manifest.
2. Detect VOICEVOX Engine at `http://127.0.0.1:50021` or configured endpoint.
3. Fetch `/speakers` and select a speaker/style.
4. For each scene, call `/audio_query`, then `/synthesis`, and write a scene WAV.
5. Probe each scene WAV with `ffprobe`; scene durations and total project duration are derived from actual audio.
6. Generate captions from audio-derived timings: `output.srt`, `output.ass`, and `words.json`.
7. Write `render/render-props.json` and `render/motion-plan.json`.
8. Render `output/output.mp4` and `output/thumbnail.jpg` with Remotion.
9. Review with `ffprobe`; fail if the rendered video is shorter than the narration audio.

## Fallbacks

Remotion is the primary renderer. The existing static ffmpeg slide renderer remains a fallback only when Remotion is unavailable. VOICEVOX is the primary Japanese TTS. macOS `say` is preview fallback only, and ffmpeg silence is the last local fallback for automated tests.

## Dashboard

The Dashboard Video Studio card shows the primary renderer, fallback renderer, VOICEVOX endpoint, speaker count, selected speaker, voice quality, and render readiness. Project rows continue exposing Generate, Review, Prepare Publish, Browser Upload Assist, and Publish.

## Verification

`pnpm build`, `pnpm test`, and `pnpm video-studio:e2e` must pass. The E2E output must contain `output.mp4`, `thumbnail.jpg`, `output.srt`, `output.ass`, `words.json`, `render-props.json`, and `motion-plan.json`. When VOICEVOX is running, E2E must use VOICEVOX; otherwise it must clearly report fallback audio in the result while keeping the primary status visible.
8 changes: 7 additions & 1 deletion docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ is risk 0; a `git_reset --hard` is risk 4), so ranges are shown.
|--------|------:|------------|-------|
| Read / Search | 7 | 0 | `read`, `read_file_range`, `grep`, `glob`, `list_files`, `get_file_info`. Never mutates. |
| Edit | 10 | 2–3 | `write`, `edit`, `multi_edit`, `apply_patch`, `move_file`, `copy_file`, `create_directory`, `delete_file`. |
| Asset bridge | 1 | 2 | `asset_save_image` — one tool, `source.kind` = `base64` / `url` / `latest_download`. Magic-byte + SSRF + SVG-safety checked. See [asset-bridge.md](asset-bridge.md). |
| Asset bridge | 3 | 2 | `asset_save_image`, plus `asset_upload_chunk` / `asset_commit_upload` for large generated images. Video Studio can attach saved images with `video_studio_add_asset`. Magic-byte + SSRF + SVG-safety checked. See [asset-bridge.md](asset-bridge.md). |
| Video Studio | 17 | 0–4 | Remotion + VOICEVOX short-video workflow. Disabled for ChatGPT by default; enable it from Dashboard → Settings → Optional ChatGPT tools. |
| Shell | 16 | 0–3 | `bash`, background shell control, `command_exists`. Screened by CommandGuard + PathGuard. |
| Git | 22 | 0–4 | Status/diff/log are 0; `git_commit`/`git_add` are 2–3; `git_reset`/destructive ops reach 4. |
| Validate / Project | 8 | 0–3 | `project_run_tests`/`lint`/`typecheck`/`build`/`validation`; reads scripts at 0. |
Expand Down Expand Up @@ -60,3 +61,8 @@ The advertised surface is narrowed by the active **tool profile**:
localant tools profile coding
localant tools list
```

Optional product surfaces are also gated by Dashboard feature toggles. LocalAnt
Video Studio and the generated-image Asset bridge are disabled for ChatGPT by
default; enable them in Dashboard → Settings → Optional ChatGPT tools before
they appear in MCP `tools/list`.
130 changes: 130 additions & 0 deletions docs/video-studio/oss-research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# OSS Research

## Compared projects

- name: Remotion
- url: https://github.com/remotion-dev/remotion and https://www.remotion.dev/docs/
- stars if available: about 51.2k on GitHub when checked on 2026-06-25
- last activity if available: latest release v4.0.482 on 2026-06-22 when checked
- license: custom Remotion license, with company-license requirements in some cases
- stack: TypeScript, React, browser rendering, programmatic composition
- useful ideas: composition objects, props-driven rendering, preview/render separation, reusable animated templates
- risks: heavier than a pure FFmpeg fallback and Remotion's custom license should stay visible to operators

- name: MoviePy
- url: https://github.com/Zulko/moviepy and https://zulko.github.io/moviepy/
- stars if available: GitHub project is a widely used Python video editing library
- last activity if available: active public repository when checked
- license: MIT
- stack: Python, FFmpeg-backed clip composition
- useful ideas: timeline-style clip composition, audio/video/text overlay separation, cross-platform fallback
- risks: adds Python runtime dependency and package installation surface to LocalAnt

- name: WhisperX
- url: https://github.com/m-bain/whisperX
- stars if available: public GitHub project
- last activity if available: active public repository when checked
- license: BSD-style license in repository
- stack: Python, Whisper, alignment models
- useful ideas: word-level timestamp structure, future speech-to-caption alignment, diarization-ready JSON
- risks: large ML/runtime dependencies; not suitable as required default

- name: FFmpeg
- url: https://ffmpeg.org/
- stars if available: not applicable
- last activity if available: active upstream project
- license: LGPL/GPL depending on build options
- stack: native CLI
- useful ideas: final render, audio muxing, ASS subtitle burn-in, thumbnail extraction, ffprobe validation
- risks: local install required; codec/filter availability varies by build

- name: Aegisub / ASS subtitle design
- url: https://github.com/TypesettingTools/Aegisub
- stars if available: public GitHub project
- last activity if available: active public repository when checked
- license: BSD-style license in repository
- stack: C++, ASS subtitle editor
- useful ideas: style/timing separation, safe area, outline/shadow readability, karaoke-style future extension
- risks: editor code is not needed; LocalAnt should emit ASS directly

- name: OpenShorts
- url: https://github.com/mutonby/openshorts
- stars if available: GitHub badge present; exact count can drift
- last activity if available: active enough to have current docs/site when checked
- license: MIT
- stack: self-hosted Docker app, short-video workflows
- useful ideas: clip generation, AI shorts, YouTube Studio style workflow grouping
- risks: full platform scope is larger than LocalAnt's first pass

- name: gyoridavid/short-video-maker
- url: https://github.com/gyoridavid/short-video-maker
- stars if available: public GitHub project
- last activity if available: active public repository when checked
- license: repository license should be checked before copying any code
- stack: MCP/REST, Kokoro TTS, Whisper, Pexels, Remotion
- useful ideas: text to TTS to captions to Remotion render pipeline, MCP-compatible public surface
- risks: Pexels/API-backed media search and ML dependencies are not acceptable as mandatory defaults here

- name: VOICEVOX Engine
- url: https://github.com/VOICEVOX/voicevox_engine and https://voicevox.github.io/voicevox_engine/api/
- stars if available: public GitHub project
- last activity if available: active public repository when checked
- license: LGPL-3.0 for the engine; voice library terms are separate
- stack: local HTTP TTS engine, `/speakers`, `/audio_query`, `/synthesis`
- useful ideas: free local Japanese narration, explicit speaker/style selection, scene-level WAV generation
- risks: the engine must be running locally; character/voice terms must be respected by users

## Design decisions for LocalAnt

- what to adopt: Remotion's scene/composition/props model as the primary renderer, VOICEVOX Engine as the primary Japanese TTS, MoviePy's separation of clips/audio/overlays, WhisperX's word-timing JSON shape, FFmpeg/ffprobe for fallback and validation, ASS subtitles for readable Shorts/Reels captions, and a browser-upload-assist provider that stops before submit.
- what not to adopt: paid external video generation APIs, cloud text-to-video APIs, mandatory Python/ML stack, API-only publishers as the default path, or any copied OSS source.
- why: the product requirement is a free local fallback that can generate a real uploadable MP4 from ChatGPT/MCP. Optional paid or reviewed APIs can exist later, but they cannot be required for video generation.

## Implementation mapping

No source code copied. The implementation only adopts architecture patterns and file-format ideas from the projects above.

- Remotion mapping:
- OSS idea: represent videos as parameterized compositions, separate preview/editing from final render, and reuse templates.
- LocalAnt implementation: `VideoProject`, `VideoScene`, `render/render-props.json`, and `render/motion-plan.json` drive a Remotion composition with animated background, card, title, captions, progress bar, and CTA. Dashboard preview/review is separated from `video_studio_render_video`. The `generate_video` tool orchestrates reusable steps instead of hiding everything in a single opaque command.
- Not adopted: Lambda/Cloud Run rendering or remote render services.

- VOICEVOX mapping:
- OSS idea: run Japanese TTS locally through a documented HTTP engine.
- LocalAnt implementation: status checks `http://127.0.0.1:50021/speakers` by default, selects a speaker/style, then calls `/audio_query` and `/synthesis` per scene. Scene WAV durations are measured with `ffprobe`, and video timings are derived from those durations.
- Not adopted: bundled VOICEVOX engine binaries, remote TTS, or paid voice APIs.

- MoviePy mapping:
- OSS idea: think in clips and layers: image/video clip, audio clip, text overlay, final composition.
- LocalAnt implementation: each scene becomes a generated visual clip, each narration segment becomes a WAV clip, captions are emitted as SRT/ASS/words, and FFmpeg composes the final MP4. The project directory keeps each layer in `assets/`, `audio/`, `captions/`, `render/`, and `output/`.
- Not adopted: Python dependency, MoviePy runtime, or dynamic Python package installation.

- WhisperX mapping:
- OSS idea: word-level timestamp data enables later subtitle alignment, highlighting, and diarization.
- LocalAnt implementation: `captions/words.json` uses `{ word, start, end, sceneId }` entries so the initial deterministic caption splitter can later be replaced by true WhisperX-style alignment without changing downstream render/review APIs.
- Not adopted: ML transcription, GPU requirements, diarization, or model downloads as mandatory defaults.

- FFmpeg mapping:
- OSS idea: use CLI primitives for final rendering, audio muxing, thumbnail extraction, ffprobe validation, progress overlays, and optional subtitle filters.
- LocalAnt implementation: FFmpeg static-slide rendering is fallback. `ffprobe` remains mandatory for audio length measurement and review validation, including failing when rendered video is shorter than narration.
- Compatibility decision: FFmpeg subtitle rendering requires libass-enabled builds. Since this Mac's FFmpeg build did not expose `ass/subtitles` filters, LocalAnt still writes `.ass` and `.srt`, but burns readable caption text into generated scene visuals so output remains uploadable on common free FFmpeg installs.

- Aegisub / ASS mapping:
- OSS idea: keep subtitle style, safe area, outline, timing, and text content separate.
- LocalAnt implementation: `output.ass` uses a dedicated style block with large white text, outline/shadow, bottom safe area, and per-scene dialogue events. The renderer can consume this directly later when libass is available.
- Not adopted: Aegisub editor/runtime or UI code.

- OpenShorts / short-video-maker mapping:
- OSS idea: creator-facing pipeline should be script -> scene plan -> TTS/audio -> captions -> render -> publish preparation, exposed through an automation API.
- LocalAnt implementation: MCP tools follow that pipeline exactly and keep one-shot `video_studio_generate_video` as a wrapper over explicit steps. The browser publisher mirrors the practical OSS pattern of using local/browser upload flows when official APIs require review.
- Not adopted: Pexels/media API search, paid model calls, mandatory Kokoro/Whisper/Remotion stack, or unreviewed third-party API posting as the default.

## Final architecture

- renderer: Remotion first. It renders animated presentation-style shorts from `render-props.json`; `builtin-ffmpeg` static slides are fallback only.
- script generator: deterministic template generator that works without an LLM API.
- asset generator: free local generated visuals. It writes per-scene PNGs using FFmpeg color/image generation when available, with SVG placeholders retained for inspection.
- audio generator: VOICEVOX first for Japanese narration. macOS `say` is preview fallback, and FFmpeg silence is only the last local fallback for tests or setup-limited environments.
- caption system: SRT, ASS, and `words.json` are generated from scene timings, with the JSON shaped for future WhisperX word alignment.
- publisher system: browser upload assist and dry-run metadata first. Official APIs are readiness-checked but not required.
- dashboard integration: dashboard routes call the same MCP tools so ChatGPT and the local UI share one implementation.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,22 @@
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"video-studio:e2e": "tsx scripts/video-studio-e2e.ts",
"validate": "pnpm build && pnpm test",
"start": "node packages/cli/dist/bin.js start",
"setup": "node packages/cli/dist/bin.js setup",
"postinstall": "node scripts/postinstall.mjs"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"@remotion/bundler": "^4.0.482",
"@remotion/renderer": "^4.0.482",
"commander": "^15.0.0",
"express": "^5.0.1",
"nanoid": "^5.0.9",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"remotion": "^4.0.482",
"tsx": "^4.19.2",
"zod": "^4.4.3"
},
Expand All @@ -71,6 +77,8 @@
"@localant/skill-sdk": "workspace:*",
"@types/express": "^5.0.0",
"@types/node": "^22.10.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@vitest/coverage-v8": "^2.1.9",
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,9 @@ toolsCmd
.description("List tools exposed under the active profile")
.action(() => {
const gw = createGateway();
const profile = gw.config().tools.profile;
const tools = gw.registry.list().filter((t) => isToolInProfile(t.name, profile));
const toolsConfig = gw.config().tools;
const profile = toolsConfig.profile;
const tools = gw.registry.list().filter((t) => isToolInProfile(t.name, profile, toolsConfig.features));
console.log(c.bold(`Profile: ${profile} (${tools.length} tools)`));
for (const t of tools) console.log(` ${t.name} ${c.gray(`[risk ${t.risk}]`)}`);
});
Expand All @@ -580,7 +581,7 @@ toolsCmd
if (!["minimal", "coding", "full"].includes(name)) {
return console.log(fail("Profile must be one of: minimal, coding, full"));
}
gw.saveConfig({ ...gw.config(), tools: { profile: name as "minimal" | "coding" | "full" } });
gw.saveConfig({ ...gw.config(), tools: { ...gw.config().tools, profile: name as "minimal" | "coding" | "full" } });
console.log(ok(`Tool profile set to '${name}'. Restart the gateway for it to take effect.`));
});

Expand Down
Loading