npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@speclip/pi-subtitles

v0.3.1

Published

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

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

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

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

无 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,已有听校修正需通过 text 和 correctionReason 保留。详见局部审阅接口。

需要全片重新分组时,读取 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 多字词也优先保持整体。

顶层 patches、groups 与下方兼容的完整 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 同时返回 findings 和 review。阅读速度、短时显示等是审阅提示;不能用它们掩盖结构错误。review.text、review.sync、review.visual 分别记录听校、同步和视觉结果,可在修订时传 { status: "passed", evidence: "实际核对的音频/视频及范围" };未提供的项目会重置为 pending。结构通过不代表听校通过。

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