插件开发指南
面向为 PixiuCut 开发草稿工具的第三方开发者。按本文的方法名、字段与错误码实现即可,无需阅读应用源码。
一、插件能做什么
插件是一个草稿转换器:读取用户草稿,写出一个或多个新草稿。需要成片时,可以委托应用把已保存的草稿送进任务中心导出。
输入 = 草稿(以及用户自己的素材文件)
输出 = 新草稿(一个或多个)
可选 = 委托应用把草稿导出到用户指定的目录
适合的场景:
- 用一条模板草稿批量替换素材,生成多个变体,再排队导出
- 读取视频信息或自备音频,调用自己的语音识别服务,写回带字幕的新草稿
- 把其它软件的工程描述转成 PixiuCut 草稿
- 把草稿同步到自己的存储
二、五分钟示例
一个最小插件只需要两个文件:
hello-plugin/
├── manifest.json
└── index.html
manifest.json
{
"id": "com.example.hello",
"name": "Hello Plugin",
"version": "1.0.0",
"description": "我的第一个插件",
"author": "Your Name",
"main": "index.html",
"sdkVersion": "^1.0.0",
"permissions": ["draft.read", "draft.write", "ui.notify"],
"slots": [{ "id": "home.shortcut", "label": "Hello" }],
"window": { "width": 640, "height": 480 }
}
在页面里直接使用全局对象 PixiuCutPlugin
const sdk = PixiuCutPlugin
async function init() {
const drafts = await sdk.draft.list()
console.log(`已连接,当前有 ${drafts.length} 个草稿`)
await sdk.ready()
}
async function makeVariant() {
const drafts = await sdk.draft.list()
if (!drafts.length) return sdk.ui.notify('请先创建一个草稿', 'warning')
const { blob } = await sdk.draft.readBlob(drafts[0].id)
await sdk.draft.create(drafts[0].name + '-副本', blob)
await sdk.ui.notify('已生成副本', 'success')
}
init().catch((e) => console.error(e))
<script src="sdk.js">。应用的本地页面服务会在返回插件 HTML 时自动注入 /sdk.js。页面运行在沙箱 iframe(sandbox="allow-scripts allow-forms allow-modals",不含 allow-same-origin)中,因此不能用 localStorage,配置请写到 fs.dataDir()。三、插件包与 manifest
没有规定的构建工具。原生 HTML,或 React / Vue / Svelte 构建出的单个入口页都可以。构建时把 PixiuCutPlugin 当作外部全局变量,不要打进包里。静态资源使用相对插件根的路径。
manifest 字段
| 字段 | 要求 |
|---|---|
id | 必填。反向域名,全局唯一,安装目录名与之相同 |
name / version / author | 必填。version 为 semver |
main | 入口 HTML,默认 index.html |
sdkVersion | 目标 SDK,当前写 ^1.0.0 |
appVersion | 可选。兼容的应用版本范围 |
permissions | 见第四节,安装时展示给用户 |
slots | 首页入口,id 固定 home.shortcut,可带相对路径 icon |
window | 可选。默认 800×600、可调整,下限 480×360 |
mcp | 可选。向本机助手暴露工具 |
四、权限模型
安装时用户会看到权限说明,标为高危的项会单独提示。只声明完成功能所需要的权限。
| 权限 | 风险 | 可调用 |
|---|---|---|
draft.read | 低 | list、readBlob、draft.changed |
draft.write | 中 | create、writeBlob、remove、rename、stageMedia |
fs.read / fs.write | 高 | 文件读写 |
net.fetch / net.local | 高 | 外网 / 本机回环请求 |
media.probe | 低 | probe、extractAudio、getEncodeCaps |
media.render | 高 | 批量切片 startSplitJob / cancelSplitJob |
export.render | 高 | enqueue、cancel、getStatus 及导出事件 |
process.spawn | 高 | 启动外部进程 |
ui.notify / ui.pickFiles / ui.pickDirectory | 低 | 通知、选文件/目录 |
以下方法默认开放,不必声明权限:plugin.ready/resize/close/log/packageDir/setBusy、ui.getAppInfo、license.isActive/trialDaysLeft、events.subscribe、fs.dataDir 及其读写。
五、SDK API 速查
全局对象 window.PixiuCutPlugin,异步方法返回 Promise,失败抛出 PluginError(err.code 为错误码)。默认单次调用 60 秒超时。
草稿
const drafts = await PixiuCutPlugin.draft.list()
// { id, name, dir, created, modified, durationUs?, thumbnail, type }
const { blob, version } = await PixiuCutPlugin.draft.readBlob(draftId)
const summary = await PixiuCutPlugin.draft.create(name, blob?) // 传 blob 则以它为模板
const res = await PixiuCutPlugin.draft.writeBlob(id, blob, version) // version 用于冲突检测
await PixiuCutPlugin.draft.remove(id)
await PixiuCutPlugin.draft.rename(id, newName)
const { path } = await PixiuCutPlugin.draft.stageMedia(draftId, srcPath)
媒体探测
const info = await PixiuCutPlugin.media.probe(filePath)
// { success, type, width, height, durationUs, frameRate, codec }
const audio = await PixiuCutPlugin.media.extractAudio(filePath, { format: 'wav' })
委托导出
const { queueId } = await PixiuCutPlugin.export.enqueue(
[{ draftId: 'proj_…', outputDir: 'D:/exports', fileName: '成片标题' }],
{ config: { format: 'mp4', vcodec: 'libx264', resolution: 1080, frameRate: 30 } },
)
PixiuCutPlugin.on('export.progress', ({ draftId, percent }) => {})
PixiuCutPlugin.on('export.queueIdle', ({ queueId }) => {})
界面与生命周期
await PixiuCutPlugin.ui.notify('操作完成', 'success')
const files = await PixiuCutPlugin.ui.pickFiles({ multiple: true })
const dir = await PixiuCutPlugin.ui.pickDirectory()
await PixiuCutPlugin.ready()
await PixiuCutPlugin.setBusy(true) // 长任务期间
const root = await PixiuCutPlugin.packageDir()
const data = await PixiuCutPlugin.fs.dataDir() // 免权限读写
fs.* 文件操作、net.fetch 网络代理、process.* 外部进程、media.startSplitJob 批量切片、mcp.* 向本机助手登记工具等能力,完整签名见随软件分发的《插件开发指南》原文。六、草稿格式
草稿 blob 是 JSON 字符串,format_version 固定为 "1.2"。时间字段单位一律是微秒(µs);project_info.created/modified/last_opened 是秒级 Unix 时间。
{
"format_version": "1.2",
"project_info": { "id": "proj_…", "name": "我的视频", "created": 1700000000,
"modified": 1700000000, "duration": 0, "status": "editing" },
"project_settings": {
"video": { "width": 1080, "height": 1920, "frame_rate": 30 },
"audio": { "sample_rate": 48000, "channels": 2 }
},
"tracks": [
{ "id": "track_1", "type": "video", "name": "主视频轨",
"main_track": 1, "clips": [] }
]
}
clip.type 为整数:1 视频、2 音频、3 图片、4 文本、5 纯色、6 字幕。媒体片段需备齐 name / type / start_time / in_point / out_point / media_duration / duration,其中 duration = (out_point - in_point) / speed_factor。字幕轨只放一个容器片段(type:6,subtitle_container:true),每句台词是 subtitle_track.items 里的一条。
七、错误码
| 错误码 | 含义 |
|---|---|
PERMISSION_DENIED | 未声明或用户未授予该权限 |
UNKNOWN_METHOD | 方法或事件名不存在 |
NOT_FOUND | 草稿、文件或进程句柄不存在 |
DRAFT_BUSY / EXPORT_BUSY | 草稿被剪辑器打开 / 剪辑器已打开不能入队 |
CONFLICT | etag 冲突,或目标文件已存在 |
INVALID_DRAFT | JSON 无法解析或格式非法 |
PAYLOAD_TOO_LARGE | 超过单次体积上限 |
ENGINE_UNAVAILABLE | 剪辑引擎未就绪 |
TIMEOUT | 超过 SDK 默认 60 秒 |
INTERNAL | 应用内部错误 |
八、发布
打成一个 zip:解压后的根目录就是 manifest.json 和入口 HTML,不要多包一层,体积不超过 80MB。
- 从 zip 安装:插件管理 → 已安装 →「从 zip 安装」
- 本地目录:以
manifest.id为文件夹名放到应用插件目录,点「刷新」 - 发现目录:向官方目录维护者提供记录(id、name、latestVersion、downloadUrl 等),用户在「插件 → 发现」中安装
安装后默认启用,首页出现快捷入口,不必重启。更新时安装目录被替换,fs.dataDir() 数据目录默认保留。
九、常见问题
可以用 React / Vue / Svelte 吗?
可以。构建产物是一个入口 HTML。不要把 SDK 打进包,将 PixiuCutPlugin 标为外部全局变量。
可以调用 Node.js 吗?
不可以。系统能力都走 SDK 方法。需要 ffmpeg 或自带程序时,用 process.spawn 启动绝对路径,或把程序放进插件包内。
能直接连剪辑引擎吗?
不能。探测、抽音轨、切片和导出都通过 SDK,由应用代为调用。
插件之间能互相调用吗?
不能。交接方式是插件 A 创建草稿,用户再打开插件 B 读取它。