Deep-Dive Code Analysis

Pireel Studio 源码级架构拆解

一个开源、零后端、纯浏览器运行的 AI 视频编辑器。从 ASR 转录到 WebCodecs 导出,从 LLM 分镜规划到 GSAP 动画合成——把 9000 行 TypeScript 看透。

#video-editor#WebCodecs#GSAP#MCP#MediaPipe#LLM

一、整体定位

Pireel 不是万能剪辑工具,而是一个垂直品类加速器——专攻"单人口播短视频"场景。核心思路:编辑器是 Canvas,AI Agent 是画笔。

338⭐
GitHub Stars
AGPL-3.0
License
4 pkgs
Monorepo
20+
设计主题
99.3%
TypeScript

二、模块分层

apps/studio-oss/ ← Vite 外壳,最薄的一层(React 入口) packages/ studio-ui/ ← React 编辑器 UI(工作台、时间轴、面板、导出) studio-engine/ ← 纯逻辑核心(零 DOM、零 React、可单元测试) studio-frames/ ← 设计主题包(20+ 套颜色/字体/布局方言 + 内容模板) ui/ ← 共享基础组件、主题 tokens
关键设计:studio-engine 不含任何浏览器依赖,同一套逻辑跑在三个地方——React 工作台 UI、服务端 MCP 离线执行、vitest 单元测试。

三、核心数据模型:Composition

整个编辑器的状态就是一棵纯数据树——数据与渲染彻底分离。

Block — "数据即模板"

Block { id, templateId // 模板注册表 key("title"/"stat"/"list"/"caption"/"custom"…) slots // 键值数据(纯 JSON,不含 HTML) startSec, durationSec // 时间位置 trackIndex // 图层(0=视频,1=字幕,2+=组件) box, contentBox // 归一化位置 [0..1] bg, border, opacity // 视觉样式 rotation, personLayer // 旋转 + 人像前后关系 }
Block 只存数据不存渲染结果。渲染时走 renderBlock(block) → 查模板注册表 → 实时生成 innerHTML + GSAP timeline。换主题 = 只改 CSS 变量,Block 不动。

VideoShot — "画布渲染模式"

VideoShot { id, srcStart, srcEnd // 源视频时间区间 treatment // 取景: full | punch-in | corner-br | corner-tl | split-l | split-r treatSize // 取景尺寸 0–100 personMatte // 人像抠图开关 filter // 颜色分级 (brightness/contrast/saturate) transIn // 切入转场(gl-transitions WebGL 混合器,10 种特效) }

重大家设计选择:视频轨 = 一个 <canvas> 元素。父页面用自己的 VideoDecoder 解码,通过 postMessage 逐帧推送画面到 iframe 内的 Canvas。这解决了经典"解码僵尸"问题——重建文档不会重建解码器。

四、全流程管道:七段式从视频到 mp4

① ASR
→
② Vision
→
③ Plan
→
④ Layout
→
⑤ Compose
→
⑥ Assemble
→
⑦ Export

① ASR 转录

音频 → 词级时间戳。输出带 {start, end, text} 的句子数组,后续逐词弹出字幕就靠这个。

② 视觉分析(视频帧被"读"三次)

分析技术产出
场景检测HSV 三通道均值差(PySceneDetect 算法)画面切点列表 → 决定视频裁切位置
人脸安全区MediaPipe MODNet 逐帧推理最大空矩形 → 图形精确避开人脸
调色板视频帧主色调提取{accent, paper, panel} → 图形颜色与画面协调

③ Plan — LLM 当"导演"写分镜

System prompt 核心指令:把台本句子合并成 Scene(每 Scene = 一个语义单元),给每 Scene 配取景方式 + 设计图形类型。

LLM 输出 DraftPlan JSON: scenes: [ { from:0, to:0, framing:"full", graphic:{component:"title", brief:"产品名大字", data:"智能手表", size:"card"} } { from:1, to:1, framing:"full", graphic:{component:"metric", brief:"两个大字", data:"钛合金\n35克", size:"card"} } { from:2, to:2, framing:"full", graphic:{component:"comparison", brief:"双栏对比", data:"14天|7天", size:"card"} } { from:3, to:3, framing:"corner", graphic:{component:"callout", brief:"大字口号", data:"限时三天", size:"poster"} } ]

④ build-draft — "副导演"布局占位

核心函数 layoutFromPlan(plan, {video, sentences, visual}) → Composition。遍历每个 Scene:

⑤ compose — LLM 逐块生成 HTML + GSAP

每个占位块 → 拼装 prompt(主题变量 + 调色板 + 安全区 + 设计指令)→ LLM 生成:

// LLM 输出的原始文本 钛合金材质配合精确到克的重量控制。 ```html <div class="wrap"> <div class="card"> <div class="kicker">材质</div> <div class="metric"><span class="num">钛合金</span></div> <div class="rule"></div> <div class="num">35<span class="unit">g</span></div> </div> </div> <style> #ph_metric .wrap { ... } #ph_metric .card { background:var(--panel); border-radius:var(--radius); ... } </style> ``` ```js tl.from('#ph_metric .card', {autoAlpha:0, scale:0.95, duration:0.4}, 0); tl.from('#ph_metric .num', {autoAlpha:0, y:30, duration:0.5}, 0.3); ```

然后 apply_block 走自动 lint 校验(CSS 作用域 check / 无全局选择器 / 无 setTimeout/random / 颜色用 CSS 变量 / HTML+timeline 配对完整),通过后占位块变成真实内容。

⑥ assembleHtml — Composition → 自包含 HTML 文档

所有内容拼成一个完整的独立 HTML:

<!doctype html> <html> <head> <link href="fonts.googleapis.com/css2?family=Noto+Sans+SC:..." /> <style>:root{ --accent:#d8472f; --font-head:"Noto Sans SC"... }</style> </head> <body> <div id="root"> <canvas id="vidEl" width="1080" height="1920" ...></canvas> <!-- 每个 block 的渲染 DOM --> <div id="ph_metric" data-hf-box="1" data-start="3.3" data-duration="3.8" style="left:6%;top:10%;width:88%;height:46%;overflow:hidden"> <div data-hf-content>...卡片内容...</div> </div> </div> <script src="/vendor/gsap.min.js"></script> <script> window.__timelines = {}; // 每个 block 的时间线用 new Function 沙箱执行 (function(){var tl=gsap.timeline({paused:true}); try{new Function('tl',"...GSAP body...")(tl)}catch(e){} window.__timelines['ph_metric']=tl; })(); </script> </body> </html>
这份 HTML 是预览 iframe 和导出渲染器的同一份源码——WYSIWYG 不靠 hack,靠同一份文档在不同模式下跑。

⑦ Export — WebCodecs 渲染 mp4

mediabunny pipeline: BlobSource(原始视频) → VideoTrack(解码帧) CanvasSource(逐帧捕获 iframe 画面) ← 同时渲染所有图形+字幕 AudioSampleSource(原始音频 + 0.05s 淡入淡出) → Mp4OutputFormat(H.264) → BufferTarget → Blob → 下载

五、关键技术细节

GSAP — 为什么用它而不是 CSS Animation

CSS AnimationGSAP Timeline
精确 seek 到任意秒❌ 只能 play/pause✅ tl.seek(5.3)
多元素统一编排❌ 各自独立✅ 一条 timeline 管所有
LLM 可生成⚠️ 复杂 keyframe✅ 纯文本一行搞定
容错隔离❌ 全局污染✅ new Function 沙箱

人像抠图(Person Matte)

MediaPipe MODNet(父页面)→ 生成蒙版 → postMessage 推送 iframe → Canvas destination-in 合成 → 背景替换层在蒙版下方 → marching squares 轮廓追迹做外描边。

MCP 协议 — Agent 接口

Pireel 通过 streamable HTTP JSON-RPC 暴露全套编辑工具:

📊
数据工具
get_state, cut_narration, split_shot… 服务端可离线执行
🖥️
浏览器桥接
extract_asr, capture_frame… 需要浏览器 Tab
🧠
BYO-brain
Agent 自带模型生成,不烧 Pireel 额度
🔐
OAuth 登录
标准 RFC 8414 / 9728 授权流程
// OpenClaw 注册 Pireel MCP openclaw mcp set pireel \ --url https://pireel.com/api/studio/mcp \ --transport http openclaw mcp login pireel

六、本地部署

# 三步跑起来 git clone https://github.com/pireel/pireel.git cd pireel pnpm install pnpm dev # → http://localhost:5173

开箱即用 vs 需要接 Provider

功能状态备注
拖入视频、编辑、时间轴、预览✅纯浏览器
20+ 设计主题、素材上传、草稿持久化✅localStorage + OPFS
WebCodecs 导出 mp4✅需 Chromium
ASR 转录、LLM 分镜、AI 图形生成❌改 src/providers.ts 接 API
人像抠图⚠️需 ONNX 模型文件

五个 Provider 接口(composer / planner / transcriber / vault / projects),每个就一两个方法,合同很薄。改 apps/studio-oss/src/providers.ts 就能接自己的后端。

七、适合什么场景

适合原因
抖音/小红书口播9:16 竖屏 + 逐词弹幕 + 数据大字报
产品介绍/测评人物讲参数 → 自动抽数字叠 metric 卡
知识科普自动生成对比卡/流程图/要点列表
直播切片二创30 分钟直播 → 剪 highlight 自动配图形
播客/音频可视化一张封面 + 音频 → 章节标题卡+关键词弹出
不适合:多镜头跳切、剧情 Vlog、复杂转场编排、3D/粒子特效、多人对话、长视频(>15 分钟)。判断标准——你的视频是"一个人对着镜头讲一件事"就是对的路子,不是就换别的。
终极总结:Pireel 是把"一个人对着镜头讲话"变成"设计感内容"的加速器。适合每天要出多条口播短视频的人——把重复的设计劳动自动化了。

八、技术栈一览

层级技术用途
构建pnpm workspace + Vite 8 + TypeScript 5monorepo 管理
前端React 19 + Tailwind CSS 4编辑器 UI
动画GSAP (GreenSock)精准时间线 + LLM 可生成
视频解码WebCodecs (mediabunny)浏览器端解码/编码/导出
AI 推理MediaPipe (MODNet) + ONNX Runtime Web人像抠图、人脸检测
AI 生成Vercel AI SDK (ai + @ai-sdk/react)LLM 调用(compose/plan)
协议MCP (Model Context Protocol)外部 Agent 驱动接口
语法高亮Shiki代码块展示
MarkdownStreamdown文档渲染
转场gl-transitions (WebGL)10 种像素级画面转场
状态管理fast-json-patch + jsondiffpatch增量同步 & 版本管理