@speclip/pi-subtitles

Agent-reviewed subtitle timing, layout, and standard track export for Pi

Packages

Package details

extensionskillprompt

Install @speclip/pi-subtitles from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@speclip/pi-subtitles
Package
@speclip/pi-subtitles
Version
0.3.1
Published
Sep 10, 2026
Downloads
271/mo · 200/wk
Author
tapcli
License
MIT
Types
extension, skill, prompt
Size
95.2 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions/subtitles/index.ts"
  ],
  "skills": [
    "./skills"
  ],
  "prompts": [
    "./prompts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@speclip/pi-subtitles

Pi 使用的字幕决策插件包。它把 ASR 词级时间戳映射到完成 A-roll 剪辑后的成片时间轴,由 Agent 审阅断句、标点和换行,再导出可追溯的 UTF-8 SRT / ASS 字幕轨道。

它不直接烧录视频,也不把任意 FFmpeg 参数塞进 Agent 工具。字幕判断与视频渲染保持分离:本包负责“字幕写什么、何时出现、如何分行”,支持字幕轨道的渲染器负责最终画面合成与视觉验收。

能力边界

  • 输入:pi-speech 兼容的词级 JSON 转录,也兼容常见 { word, start, end } 秒级结构。
  • A-roll 映射:直接读取渲染回执,支持多源、回接、重复片段和实际帧/采样边界;生成、修订及导出均检查剪辑点。
  • 自动分段:结合标点、停顿、字数、时长和阅读速度生成初稿。
  • Agent 审阅:通过不可变 revision 修正 ASR 文本、标点和断句;横屏项目强制单行,审阅阶段也不能绕过。
  • 导出:标准 SRT;可选带横屏/竖屏安全区预设的 ASS;两者都不覆盖已有文件。
  • 溯源:绑定原转录哈希、精确 revision、输出哈希和持久化导出回执。

B-roll 只覆盖画面而保留 A-roll 主音频时,不会改变字幕时间。若后续改变了语速、主音频或 A-roll 片段顺序,应新建字幕项目。

环境要求

  • Node.js 22.19+
  • Pi 0.84.1–0.85.x
  • 一份含词级时间戳的 JSON 转录

安装与验证

npm install
npm run check
pi install ./

也可以安装发布包:

pi install npm:@speclip/pi-subtitles

工具工作流

1. 创建字幕项目

A-roll 已渲染时,优先直接传官方回执和原始转录,不需要自行生成派生转录:

subtitles_create {
  projectId: "launch-captions",
  renderReceiptPath: "analysis/render_receipt.json",
  transcriptPath: "transcripts/launch.json",
  layoutPreset: "landscape",
  protectedPhrases: ["跷跷板"]
}

回执必须含 pi-mediatimelineTiming,工具核对成片和源文件哈希,按实际输出边界映射。回接和重复片段分别保留独立字词 ID;帧取整造成的字尾截短返回 render-tail-clipped 听校提示,真正跨原片剪点的词仍会报错。

多个主素材用 sourceTranscripts: [{ sourcePath, transcriptPath }] 替代单个 transcriptPath,每个主素材恰好绑定一份转录。回执模式不能同时传 sourceDurationMstimelineSegments

以下是兼容保留的旧输入方式;它没有经过渲染校验的跨插件时间轴绑定。

无 A-roll 剪辑时,只传源时长:

subtitles_create {
  projectId: "launch-captions",
  transcriptPath: "transcripts/launch.json",
  sourceDurationMs: 93224,
  layoutPreset: "landscape"
}

旧模式有剪辑时,传按播放顺序排列的保留源区间,可回接、可重复:

subtitles_create {
  projectId: "launch-captions",
  transcriptPath: "transcripts/launch.json",
  sourceDurationMs: 93224,
  timelineSegments: [
    { id: "a-001", sourceStartMs: 0, sourceEndMs: 12400 },
    { id: "a-002", sourceStartMs: 13100, sourceEndMs: 28700 },
    { id: "a-003", sourceStartMs: 29400, sourceEndMs: 93224 }
  ]
}

计算关系为:

成片时间 = 前面所有保留片段的累计时长 + 词源时间 - 当前片段源起点

若一个词跨过剪辑边界,工具会拒绝创建,要求回到 A-roll 调整到词间安全边界。

2. 分页审阅全部字幕

subtitles_get {
  projectId: "launch-captions",
  offset: 0,
  limit: 100
}

自动结果只是初稿。Agent 应结合实际音频检查文本、术语、标点、语义分组、阅读速度和画幅布局,并继续翻页直到 hasMore: false

layoutPreset 默认为 landscape:横屏字幕严格单行,每条最多 16 个可见字符,目标 8–16 字。portrait 允许最多两行。两种画幅都以 1.5–4.5 秒、约 9 字/秒为节奏参考;这些节奏值用于找自然断点,不要求每条机械一致。

3. 写入 Agent 审阅版本

少量改字、拆句或合句,优先使用局部 patches,不必重传全部字幕:

subtitles_apply {
  projectId: "launch-captions",
  expectedRevision: 1,
  changeReason: "听校后修正术语",
  compact: true,
  patches: [
    { fromCueId: "cue-021", text: "如果你买256GB的", correctionReason: "对照该句原声确认容量" }
  ]
}

文字补丁保留原时间,仅作用于一条字幕。局部拆分/合并时,先用 subtitles_get { projectId, view: "words", cueId: "cue-021" } 获取该条的字词;以 patches: [{ fromCueId, toCueId, groups: [...] }] 提交目标范围的完整词分组。工具自动计算时间,保留其他字幕;相交补丁、漏词、跨剪点和未知 ID 会整体拒绝。所有补丁 ID 均针对 expectedRevision,成功后重新读取新 ID。组内默认文字来自原 ASR,已有听校修正需通过 textcorrectionReason 保留。详见局部审阅接口

需要全片重新分组时,读取 subtitles_get { projectId, view: "words", offset: 0, limit: 200 } 的所有字词页,按稳定 ID 提交完整分组,让工具计算时间:

subtitles_apply {
  projectId: "launch-captions",
  expectedRevision: 1,
  changeReason: "按语义重新断句,保持全部字词",
  groups: [
    { startWordId: "a-001:word-0", endWordId: "a-001:word-8" }
    // 继续列出全部保留字词,不能只提交一页
  ]
}

分组不得遗漏、重复、乱序或跨剪辑片段。可选 text 用于标点、空格或听校后的纠错;实质文字变化必须说明 correctionReason。工具按组内真实字词时间生成字幕,短字幕仅借用同片段内的静音。protectedPhrases 用于初稿的短语保护,原 ASR 多字词也优先保持整体。

顶层 patchesgroups 与下方兼容的完整 cues 三选一。直接提交 cues 可精确改显示时间,但文字覆盖变化会产生 text-changed 提示。所有修订均校验非重叠、片段边界、视频末端和行长。

subtitles_apply {
  projectId: "launch-captions",
  expectedRevision: 1,
  changeReason: "核对音频后修正产品名和语义标点",
  cues: [
    { id: "cue-001", beginMs: 320, endMs: 2840, text: "这是修正后的第一条字幕。" }
  ]
}

cues 必须是完整列表,不是某一页。时间必须递增、不重叠,且不能超过成片时长。expectedRevision 防止并发审阅互相覆盖;每次成功都会产生新的不可变快照。

subtitles_get 同时返回 findingsreview。阅读速度、短时显示等是审阅提示;不能用它们掩盖结构错误。review.textreview.syncreview.visual 分别记录听校、同步和视觉结果,可在修订时传 { status: "passed", evidence: "实际核对的音频/视频及范围" };未提供的项目会重置为 pending。结构通过不代表听校通过。

四个工具均可显式传 compact: true 减少重复输出;默认保留原有完整响应。create/apply 返回版本、哈希、字幕总数和诊断计数;export 保留完整导出回执与 subtitleTrack;get 省略完整项目并只附当前字幕页相关诊断。使用 subtitles_get { view: "findings", offset, limit } 阅读全部诊断,包含全局字尾截短等提示。view: "words" 正确返回 words,其 totalhasMore 均以词数计算;指定 cueId 后以筛选结果计算分页。

4. 导出标准轨道

subtitles_export {
  projectId: "launch-captions",
  revision: 2,
  srtPath: "deliverables/launch.zh-CN.srt",
  assPath: "deliverables/launch.zh-CN.ass"
}

工具会按项目画幅自动使用安全区样式,并返回 SRT / ASS 文件引用、SHA-256 和持久化导出回执。横屏使用固定 1920×1080 ASS 设计坐标,竖屏使用 1080×1920;这与源视频是 1080p 还是 4K 无关,libass 会等比缩放。不要把 4K 源分辨率直接写成 ASS 的 PlayRes

省略 assStyle 即使用默认样式。需要微调前,调用 subtitles_get { projectId, view: "style" } 获取当前画幅的默认值、固定字段和真实数值范围,无需查源码或试错。

横屏和竖屏统一使用 Source Han Sans SC Heavy。白色字形不再附着黑色描边,而是使用向右下偏移的半透明黑色柔化阴影;横屏保持下方安全区,竖屏仍上移并扩大右边距,避开常见交互区。字体、阴影和几何参数只能在安全范围内微调。渲染环境必须真实安装该字体,不能依赖静默字体回退。

本包的完成状态是“字幕轨道已生成并校验”,不是“字幕已经烧进视频”。最后应把轨道交给支持字幕的渲染器,并检查实际成片中的同步、遮挡、安全区、字体回退和漏字。

5. 预览与烧录交接

导出返回 subtitleTrack: { sourcePath, format, mode: "burn-in", exportReceiptPath }(回执模式才有 exportReceiptPath)。直接放入 pi-media 的 timeline edit_apply,保留其他编辑字段。渲染器核对轨道哈希和主音频时间轴指纹;B-roll/BGM 调整不改变该指纹,主素材、源区间、顺序或输出帧率改变则拒绝旧字幕。

先用支持新接口的 pi-media 生成抽样证据:

media_subtitle_preview {
  videoPath: "deliverables/clean.mp4",
  exportReceiptPath: ".subtitles/projects/launch-captions/exports/<exportId>.json",
  sampleTimesSeconds: [5, 55, 77],
  clip: { startSeconds: 54, durationSeconds: 4 }
}

选择视频内 1–6 个时间点,短视频最长 15 秒。返回 PNG、可听短视频、字体选择日志和报告路径;视觉与同步状态保持 pending,由实际查看/听校后更新。最终视频仍需 render + review。字幕渲染环境需有 libass;可用 PI_MEDIA_SUBTITLE_FFMPEG_BINARY 指定二进制。

内部保留精确毫秒,导出时 SRT 量化为毫秒、ASS 为厘秒,且时间不能越过镜头边界。格式精度导致零时长时明确失败,不静默输出坏字幕。

工作区数据

项目状态保存在:

.subtitles/projects/<projectId>/
├── project.json
├── words.json                 # 不可变字词映射、源字词位置、片段边界与诊断
├── snapshots/<revision>.json
└── exports/<exportId>.json

新字段均为可选字段,旧项目与原有四个工具仍兼容;升级不会原地改写旧项目。源转录与导出文件都不会被覆盖。源转录字节变化后,旧项目会拒绝继续导出,避免把新内容误配到旧时间轴。

开发

npm ci
npm run check
npm pack --dry-run

GitHub Release 标签必须与 package.json 完全一致,例如 v0.2.1。发布工作流会在标签快照上重新执行检查并通过 npm OIDC 发布。

License

MIT