A local-first AI video studio. One canvas connects storyboards, assets, 3D previs, generation and editing,
and every step you take by hand, your agent can continue with the same tools.
opencinema.site · Quick start · Features · Roadmap · Architecture · Extend · Contribute · Launch film · 30 s
Real interface screenshot of a sample project. Generated results depend on the models, references and reviews you configure.
Generation tools answer how to produce a clip. OpenCinema answers how to finish a film.
- Previs first, then generate, then fix only what needs fixing. Block out movement and cameras in 3D, review each shot, and replace a single shot without redoing the film.
- One canvas for the whole film. Scripts, references, previs, generation, review and the edit plan are nodes on one canvas, and edges are reference relationships.
- Your agent works on the same canvas. Describe what you want in Codex, Claude Code or any MCP client. The agent reads and writes nodes through public MCP tools, and you can open an editor and change things by hand at any time. Both paths share one command path, one revision and one undo history.
- Swap the generation backend. Cloud models, local services and your existing ComfyUI workflows all plug into the same generation node.
- Everything stays local. The Go server listens on loopback only, a project is a plain folder on your disk, and credentials stay in local settings. Agents only ever see “configured” or “not configured”.
Status: 0.4.0, in development, unreleased. Source builds work on macOS and Linux; Windows archives are not supported yet. The workbench interface is Chinese only for now, so button names below include an English gloss. AI generation uses the provider you configure and may cost money. See Known limitations.
flowchart LR
A["Codex · Claude Code<br/>any MCP agent"] -->|MCP stdio| B["plugin bridge<br/>build/openCinema"]
B -->|Streamable HTTP| G["Go server<br/>authorization · files · tasks · providers"]
G <-->|SSE control channel| W["React workbench<br/>canvas · node plugins · editors"]
W -->|composer| G -->|host request queue| A
G --> P["generation services<br/>cloud APIs · local services · ComfyUI"]
G --> L["local tools<br/>FFmpeg · Video Depth Anything"]
An agent first calls getAppState to pin the project and canvas, then discovers tools from their live schemas and executes them. Side-effecting calls carry an operationId, and canvas writes carry expectedRevision. Conflicts are rejected, and unknown results are looked up rather than blindly retried.
| Tool | Version | Used for |
|---|---|---|
| Bun | 1.3.14+ | Dependencies, web and plugin builds |
| Go | 1.26+ | Compiling the local server |
| Node.js | 22+ | Only for Claude Code and Codex integration. A full release package needs only this |
| FFmpeg | Optional | Audio and video processing. Download it from Settings → FFmpeg if you don't have it |
| Python | 3.10–3.12, optional | Only for the Video Depth node |
git clone https://github.com/junjamrk/OpenCinema.git
cd OpenCinema
bun install --frozen-lockfile
bun run build
bun startOpen http://127.0.0.1:47920. The server listens on localhost only. Settings and credentials are stored in data/ inside the repository, which Git ignores.
Click 打开工程 (Open Project) and enter the absolute path of an existing folder, such as /Users/me/Movies/myFilm. An empty folder works too; the first canvas is created for you. After that, the home screen lists it under recent projects so you can get back in one click.
You can use both and switch per node at any time.
| Configure a media provider | Connect an agent | |
|---|---|---|
| Best for | Calling models directly | Planning, building canvases, batch work |
| Requires | A provider API key or local service | Claude Code, Codex or another MCP client |
| Cost | Billed by the provider | Host subscriptions do not cover every video model |
Configure a media provider
- Open Settings → 媒体服务 (Media Services). The list shows which providers are configured.
- Select a provider, enter the API key and save the connection.
- On a generation node, click the parameters button and choose a model.
Connect an agent
![]() |
Claude Code, from the root of the built repository: claude --plugin-dir ./build/openCinemaCodex: codex plugin marketplace add . --jsoncodex plugin add opencinema@openCinemaStandalone --jsonThen ask the agent to connect to the OpenCinema workbench. In Claude Code you can use Other MCP clients can copy a connection config from Settings → 宿主 Agent (Host Agent). See Claude setup, in Chinese, for details. |
Keep the workbench page open. Canvas and node tools run inside the page, and the agent operates the canvas through it.
- Drag an image onto the canvas.
- Press
Taband choose 视频生成 (Video Generation). - Drag a connection from the right port of the image node to the left port of the generation node.
- Select the generation node, click the parameters button above it, and pick a model, duration and aspect ratio.
- Describe the shot in the bottom input and press
⌘/Ctrl + Enter.
The node shows progress, and the finished video is saved to assets/ in your project. From there you can capture frames, review the take, or connect it to the next node.
- Open a project, drop in reference stills, a reference clip and an audio bed, and write a short brief.
- Block the action and the camera in Blockout, export RGB and Depth for the shot, and freeze them as reference nodes.
- Wire the brief, the still and the previs into a video generation node and set the reference roles.
- Type the request into the composer, for example “make the chase feel faster”. The agent shows the generation plan first. Confirm it, then generate candidates.
- Review each candidate. Rejected ones stay in the project.
- In the edit plan, replace only the shot that failed, add the audio track and render a new cut.
| Symptom | Fix |
|---|---|
| The send button is disabled | Read the hint under the input and use its connect-agent or configure-model button |
| No models to choose from | Configure a provider under Settings → 媒体服务, or add a custom one |
| Audio or video processing fails | Download FFmpeg under Settings → FFmpeg, or switch to the system install |
| Port 47920 is in use | Run build/server/openCinema -listen 127.0.0.1:47921 -webRoot build/web -dataDirectory data |
| The agent asks you to connect the workbench | Open the workbench page, enter the project, and ask the agent to read the state again |
Select one node and the composer acts on it alone. Marquee-select a group and the scope becomes those nodes. Select nothing and it is the whole canvas. Generation nodes can switch between generating and letting the agent adjust the node.
![]() |
![]() |
Every operation creates a new copy or node and keeps the original.
- Images: inpainting with brush or box selection, markup, crop and transform, grid split, lighting, camera angle and panorama. Lighting and camera changes become generation drafts.
- Video: trim to a new node, capture first, last or current frame, extract audio, separate audio and video, change speed, video analysis, transcription, and entry points for review and the edit plan.
- Audio: waveform editing, segment joining, speed change and transcription. Processing runs through local FFmpeg.
Place characters and set pieces on a white-model stage, block the action, record camera moves, and export RGB, Depth and Normal layers per shot plus a full animatic. Freezing an export turns it into reference nodes wired into video generation. The 3D Director node generates animation options from text for you to frame by hand.
Blockouts and relative depth are references for framing and motion rhythm. A plain video reference does not make a model reproduce motion exactly.
- Video Analysis: detects cut points and splits a clip into keyframes, shot segments and the audio track, with no Python or model API required.
- Motion: pose skeleton and 2D motion for clips up to 30 seconds, with optional relative depth and an exportable production pack. It is not a 3D camera solve.
- Video Depth: temporal relative depth from a locally prepared Video Depth Anything Small model. Builds never download models.
- Media Info: local codec, duration, frame rate and track inspection, with no upload.
- See the plan first.
getGenerationPlanreturns the final prompt, the actual model parameters and the role of every reference, without paying or submitting anything. - Review is a recorded human decision. Six checks cover direction, action, identity, continuity, unexpected cuts and deformation. Rejected or unreviewed clips cannot enter a composition. The software never judges quality for you.
- Replace one shot, keep the rest. The edit plan stores 2–16 clips with order, in and out points, format and one audio track. Replacing a clip leaves the others untouched, and rendering creates a new output node.
See the shot workflow, in Chinese.
![]() |
Image, video and audio generation can each go through your host agent or a configured model.
|
- fal and Replicate presets: Kling, FLUX and ElevenLabs on fal, Veo, FLUX and MiniMax Speech on Replicate. Add other models in Settings.
- Jobs survive restarts: after a disconnect or server restart, fal, Replicate, Ark, OpenAI-style, ComfyUI,
httpTaskandadapterjobs resume observing the original task instead of submitting a new paid request. Results are downloaded into the project and verified with FFprobe before they count as complete. - FFmpeg: use a system install or a per-platform, SHA-pinned download. Commands run through filter and option allowlists.
- Credentials are entered only in the local settings panel. A configured credential does not mean the account is authenticated, billing is authorized or generation has been verified.
See the provider docs, in Chinese.
ComfyUI does the generation and OpenCinema treats each workflow as a model, with no manifest to write:
- Get the workflow running in ComfyUI, then choose Export (API). The regular canvas save format cannot be imported.
- Open Settings → 媒体服务 → ComfyUI 工作流, enable the connection, enter the address (
http://127.0.0.1:8188by default) and click 保存并检测 (Save and Check). - Click 导入工作流 (Import Workflow), confirm which node receives the prompt, and mark each loader node as first frame, last frame, reference or keep as is.
- Pick the workflow in a generation node. Agents use it through the same node tools.
Workflows run as black boxes. Only the inputs you confirmed are replaced, seed and noise_seed change on every submission, and output must go through a save node. At import you can add extra ratios and durations and tick fields to adjust on the node.
Workflows run on ComfyUI, and custom nodes can execute arbitrary code. Only import workflows you trust. Agent tools cannot add or change workflows, so only you can import them in Settings.
- Collaboration: canvas collaboration on one server, with time-limited edit or read-only invites, per-user undo and reconnection. It does not start a public service or offer character-level co-editing.
- Asset library: global assets across projects that you can drag onto any canvas.
- Project export: download the project as an archive with canvases and assets.
- Media history: media nodes keep their generation history so you can switch back at any time.
| Layer | Where | Responsibility |
|---|---|---|
| Go server | apps/server |
Authorization and path boundaries, atomic file writes, the MCP endpoint and operationId records, media tasks and provider protocols, FFmpeg and the depth model, collaboration |
| React workbench | apps/web |
The canvas (@xyflow/react), node views, editors, the tool catalog, the SSE control channel to Go |
| Editing session | packages/workbenchCore |
Framework-independent document, revisions, undo and redo, and the save queue |
| Node SDK | packages/nodeScaffold · packages/nodeShared |
Plugin contract, Zod tool registration, build output, cross-plugin business such as generation, review and the edit plan |
| Node plugins | packages/nodes/* |
13 built-in nodes, the collaboration extension and the Gaussian Splat market plugin |
| MCP bridge and plugin package | packages/mcp · packages/openCinema |
stdio adapter, widget resource, launcher and leases, Codex and Claude manifests and skill |
Three invariants hold the design together: one source of truth for schemas (Zod → JSON Schema → Go → stdio), one command path for people and agents with shared revisions and undo, and traceable side effects (operationId idempotency, unknown is never retried automatically, outputs carry provenance and review records).
Most of OpenCinema is built from plugins, including all 13 built-in nodes. A plugin is an ESM module that runs in the workbench page, exchanges data through ports, and exposes capabilities as tools. Buttons and agents call the same tool, and agents discover it with no protocol changes.
Plugins can be developed outside this repository. The scaffold is not on npm yet, so pack it first from the repository root:
cd packages/nodeScaffold && npm packGenerate a project where you keep your plugins, replacing <repo> with the real path:
npx --yes --package <repo>/packages/nodeScaffold/toonflow-nodes-scaffold-0.4.0.tgz createOpenCinemaPlugin helloNode --name "Word Count" --scaffold file:<repo>/packages/nodeScaffold/toonflow-nodes-scaffold-0.4.0.tgzInstall, check and build it:
cd helloNode && npm install && npm run typecheck && npm run buildThen open Settings → 插件 → 安装插件包 (Plugins → Install package) and choose dist/install.json.
Example: a node that counts upstream text
Declare the plugin in the openCinema block of package.json. getService only returns services listed in requires:
"openCinema": {
"id": "helloNode",
"name": "Word Count",
"description": "Counts the characters of upstream text",
"category": "Text and media",
"requires": { "workbench": 1 }
}src/reactPlugin.ts:
import { z } from "zod";
import type { NodePluginDefinition } from "@toonflow/nodes-scaffold/contracts";
import type { NodeWorkbenchService } from "@toonflow/nodes-scaffold/workbenchServices";
import { openCinema, version } from "../package.json";
import PluginView from "./reactView";
const plugin: NodePluginDefinition<typeof PluginView> = {
...openCinema, version, apiVersion: 1, renderer: "react", view: PluginView,
createData: () => ({ label: openCinema.name, handles: [
{ id: "text", type: "target", dataType: "STRING", label: "Text" },
{ id: "count", type: "source", dataType: "STRING", label: "Count" },
], outputs: {} }),
activate(context) {
const workbench = context.getService<NodeWorkbenchService>("workbench");
context.registerTool({
name: "countText", description: "Counts the characters of upstream text and writes the result to the count output.",
parameters: z.strictObject({}),
outputSchema: z.strictObject({ count: z.number().int().nonnegative() }),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
async execute(_args, { signal }) {
workbench.assertWritable();
const text = workbench.getInputs("text").map(input => String(input.value ?? "")).join("");
await workbench.updateData({ outputs: { count: { dataType: "STRING", value: String(text.length) } } }, undefined, signal);
return { count: text.length };
},
});
},
};
export default plugin;src/reactView.tsx:
import type { ReactNodeViewProps } from "@toonflow/nodes-scaffold/react";
export default function PluginView({ node, controller, readOnly }: ReactNodeViewProps) {
const outputs = node.data?.outputs as { count?: { value?: string } } | undefined;
return <div className="nodrag">
<button type="button" disabled={readOnly} onClick={() => void controller.callNodeTool(node.id, "countText", {})}>Count</button>
<span>{outputs?.count?.value ?? "Not counted yet"}</span>
</div>;
}After installing, Word Count appears in the node palette, and getNodeTools returns node:countText with its schema.
Plugin parts and where code belongs
| Part | Purpose |
|---|---|
createData |
Initial node data and ports. Port types include STRING, IMAGE, VIDEO, AUDIO, FILE |
view |
The React view |
registerTool |
Publishes a tool with Zod parameters |
composer |
Optional hook into the bottom input |
requires |
Required services and host capabilities such as workbench, workspaceFiles or ffmpeg |
category, description |
The node's group and summary in the node palette |
onDispose |
Releases workers, listeners and other resources |
Node-specific logic stays in its plugin package, logic shared by several plugins goes in packages/nodeShared, and generic contracts go in packages/nodeScaffold.
To ship a plugin as a built-in node, move it to packages/nodes/<id>/, import the build config from @toonflow/nodes-scaffold/reactBuild, set outDir: "../../../build/nodes/<id>", add order to the openCinema block, add the workspace dependency to apps/web/package.json, and run bun run generate:nodes.
A plugin can also provide generation services by declaring providers in viteConfig.ts. They use the manifest format below and pass the same validation at install time.
Installed plugins run in the page.
requiresonly limitsgetServiceand is not a sandbox, so a plugin can still read and write the workspace. Only install packages you trust.
See the node SDK, in Chinese, for the full contract.
To add a plugin inside this repository, run bun packages/nodeScaffold/scripts/createNode.ts myNode "My node". It generates a skeleton that compiles and installs as-is, including an agent.md written for agents, which ships in the manifest and is read through getPluginConfiguration. Conventions are in the plugin development guide.
opencinema.site/market is a static catalog. In 设置 → 插件 → 插件市场 (Settings → Plugins → Plugin market) the workbench downloads a package, verifies it against the SHA-256 in the index, shows a review card and only then installs it through the local server. Agents do the same with getPluginMarket → installMarketplacePlugin. The market is install-only for now. To publish, open a pull request that adds an entry to site/src/marketCatalog.json.
The first plugin in the market is Gaussian Splat: view 3D Gaussian Splatting scans (PLY, SPLAT, KSPLAT, SPZ) on the canvas, set a camera, and render stills and turntable videos as references. It is not a built-in node. The whole three.js and GaussianSplats3D capability arrives through the plugin path, which shows that third-party and built-in nodes take the same route.
If a service matches one of these protocols, a JSON manifest is all you need:
| Field | Service shape |
|---|---|
videoProtocol: "openaiVideo" |
Video jobs with multipart submit, poll by ID and content download; local or self-hosted |
videoProtocol: "fal" |
fal queue |
videoProtocol: "replicate" |
Replicate predictions |
videoProtocol: "httpTask" |
Any task-style HTTP service. Declare auth, submit, polling, state mapping and output paths in the manifest, with no code |
videoProtocol: "adapter" |
Your own local program that exchanges JSON over stdin and stdout, for flows such as request signing |
protocol: "openai" |
images/generations style image API |
endpoints.image, etc. |
Synchronous JSON API returning result URLs |
Open Settings → 媒体服务 and add a custom provider. Start from a template, replace the address and model IDs, validate the manifest and add it, then enter the API key in the connection settings. Never put credentials in the manifest; the server rejects them.
Example manifest
{
"id": "remoteVideo",
"label": "My Video Service",
"apiUrl": "https://video.example.com/v1",
"videoProtocol": "openaiVideo",
"transport": "public",
"cancelMode": "none",
"endpoints": { "video": "videos" },
"rules": [{ "field": "apiKey", "title": "API Key", "type": "input", "value": "", "props": { "type": "password" } }],
"models": [{
"id": "deployedModelName",
"label": "Deployed video model",
"type": "video",
"mode": ["text", "endFrameOptional"],
"imageRatios": ["16:9", "9:16"],
"audio": false,
"durationResolutionMap": [{ "duration": [4, 8], "resolution": ["720p"] }],
"video": { "inputMap": { "firstFrame": "input_reference" } }
}]
}- For a local or LAN service, set
transportto"local"and use an explicit IP and port. The API key is optional, and image, video and audio all work. - Models can declare
parameters, a subset of JSON Schema. Nodes generate a form from it, and agents read and write the same values. - An
adapterexecutable must be placed by hand inproviderAdapters/<provider id>/under the data directory. A manifest cannot declare commands.
See the provider docs, in Chinese.
| Tier | Discovery | Contents |
|---|---|---|
| Core tools | tools/list |
getAppState, renderOpenCinema, canvas read and write, workspaceFiles, readWorkspaceImage, getOperation, the host request queue |
| Workbench extensions | getWorkbenchTools → workbenchTools |
Grouping, arranging, canvas management, asset library, provider and plugin management, FFmpeg and depth-model configuration |
| Node and workspace plugins | getNodeTools / getWorkspaceTools → nodeTools / workspaceTools |
Shot plan, generation, review, edit plan, Blockout, motion analysis and other specialist functions |
Every tier has live schemas, output validation and annotations, and errors carry a machine code and a recovery action. Hosts that support MCP Apps can open the workbench inside the conversation with renderOpenCinema. See the MCP SDK and the plugin notes, in Chinese.
bun run release archives an existing build/openCinema as openCinema-<version>-<platform>-<arch>.zip with checksums and a manifest. The unpacked folder needs only Node.js 22+ to load as a plugin. See the release guide, in Chinese.
bun install --frozen-lockfilebun run dev:webbun run dev:serverBefore opening a pull request:
bun run typecheckbun run build- Read the contributing guide first: new files and folders use lowerCamelCase, React component tags use PascalCase, canvas edits go through public commands or MCP tools, and file operations go through
@toonflow/file. - Explain the change: the problem, the final behavior, and the commands you actually ran. Include before and after screenshots for UI work, call and read-back results for agent capabilities, and real output specs for media tasks. Templates: pull request, issue.
- Do not commit
build/,data/, credentials, personal media or machine-specific absolute paths. - Do not add test files: testing in this repository is handled separately. Use type checks, builds and manual verification.
- New dependencies: explain why they are needed and their license, keep the lockfile, and update the third-party notices.
- Welcome directions: node and provider examples, English tool descriptions and interface strings, documentation and tutorials, performance on large canvases and media previews, Windows verification.
Security issues follow security.md, and community interaction follows the code of conduct. Issues · Changelog
Watch the 30-second launch film. Every frame in it is drawn in code, and nothing was produced by a video model. The storyboard and the source are in the repository. The earlier 58-second product concept film is at assets/promo/openCinemaPromo.mp4.
- Node and canvas tools run inside the workbench page. With the page closed, agents can work with project files but cannot run nodes.
- Installed plugins are not sandboxed.
requiresonly limitsgetService, and plugins still run in the page. - ComfyUI progress reflects the current node, not the whole workflow. Uploaded references stay in its
inputfolder because ComfyUI has no delete API. ComfyUI loses job records on restart. Such a job shows as unconfirmed and is never resubmitted automatically. - Pricing and balance queries are available for TF-router only. Actual cost comes from the balance difference and shows as unknown when tasks on the same account overlap.
- Release archives must match your OS and CPU architecture. Hosts, providers and platforms are verified separately, so a passing build does not mean everything works.
MIT, preserving the HBAI-Ltd copyright. Blockout and Motion are adapted from Sam Wasserman's tools of the same names (Apache-2.0). Fonts, icons, model weights, FFmpeg builds and user media keep their own terms, see the third-party notices. The distribution and documentation approach references Cowart.







