docs: limit the video clip spec to caption notes

The first release reads YouTube and Bilibili captions in the page and saves a note. Summaries use the instance default model, and speech recognition stays out.
This commit is contained in:
tianma-if committed 2026-10-05 23:29:46 +08:00
1 parent 3458044067
commit d87aedaddd
1 file changed
+189 -209
+189 -209
View File
@@ -1,281 +1,261 @@
# EdgeEver 浏览器插件:视频内容抓取、字幕识别、AI 归纳与转存笔记技术方案(YouTube & 哔哩哔哩)
# EdgeEver 网页剪藏:YouTube 与哔哩哔哩视频笔记
本文档梳理 EdgeEver 浏览器扩展(Chrome / Firefox / Edge 等)支持**网络视频内容智能提取、AI 总结归纳并一键转存笔记**的产品设计与技术实现方案。初期重点适配 **YouTube** 与 **哔哩哔哩(Bilibili)** 两大主流长视频平台,整体架构设计采用插件化适配器模式(Adapter Pattern),以便未来平滑扩展更多音视频平台。
用户在 YouTube 或哔哩哔哩的视频播放页点一次,把当前这一集保存成 EdgeEver 笔记。笔记带来源、可点击的时间戳、平台字幕实录;工作区已经配置默认模型时,再补一句话总结和分段大纲。字幕在用户正在看的页面里读取。插件不下载音视频,也不自己选择模型供应商。
## 第一期
---
第一期只做这两类播放页:
## 1. 核心目标与用户场景
- YouTube:`youtube.com`、`www.youtube.com`、`m.youtube.com` 的 `/watch` 与 `/shorts/{id}`,以及 `youtu.be/{id}`。
- 哔哩哔哩:`bilibili.com`、`www.bilibili.com`、`m.bilibili.com` 的 `/video/BV…` 与 `/video/av…`。分 P 以 URL 的 `p` 为准,没有 `p` 时保存第 1 P。
### 1.1 核心目标
* **一键总结转存**:用户在 YouTube 或哔哩哔哩视频播放页浏览时,可通过**右键上下文菜单**或**插件弹窗快捷键**,一键对当前视频(或选定分 P)进行智能提炼并保存笔记。
* **精准元数据提取**:自动捕获视频标题、UP主/频道名称、分P信息、封面、视频时长与规范化源链接。
* **智能结构化提炼**:由 AI 归纳生成**一句话结论(TL;DR)、带精确跳转时间戳的分段重点大纲、核心知识点清单与思维框架**。
* **精准回溯播放**:大纲中的所有时间戳均渲染为可点击跳转链接,点击即可直接定位到原视频对应秒数播放。
* **高性价比与秒级响应**:优先直接获取官方人工字幕与平台自动生成字幕(耗时 `<1s`,成本 `$0`);对于无字幕冷门视频,提供透明降级的语音识别(ASR)转录机制。
* **可扩展架构**:核心业务抽象为统一接口,初期适配 YouTube 与 Bilibili,未来低成本接入 Vimeo、播客、小红书视频等更多平台。
规范化后的来源链接只保留视频和分 P。播放列表、电台和 `t` 参数不写入来源链接;时间戳链接单独生成。
### 1.2 平台范围与阶段规划
* **初期适配**:
1. **YouTube**:全球最大的视频分享平台,覆盖中英文为主的科技、教程、演讲等内容。
2. **哔哩哔哩(Bilibili)**:国内泛知识、学术公开课、技能教程与长视频核心社区。
* **未来扩展**:Vimeo、播客平台(Apple Podcasts / 小宇宙 Web 版)、Twitter/X 视频、小红书视频等。
直播、首播、尚未开播的预告、番剧、影视、课程页,以及 `music.youtube.com`、嵌入式播放器和其他站点,第一期提示不支持,不创建笔记。
---
## 本期不做
## 2. 整体架构与分级处理流程
- 不从音视频流做语音识别,不把音频送到 Groq、Gemini、Cloudflare Workers AI 或任何写死的供应商。
- 插件不保存模型 API Key,不调用 `/api/v1/plugins/ai/generate/prepare`。该接口会把供应商凭据返回给调用方。
- 不注入常驻 content script。播放页的地址就能确定视频,不需要像时间线那样记住指针下的条目。
- 不增加清单里的 `commands` 快捷键。
- 不承诺秒级完成,也不把模型费用写进产品文案。
- 不为了这一期接入 Vimeo、播客、小红书或 X 的视频。
核心设计哲学:**“免费原生字幕优先(毫秒级 / 零成本),无字幕透明降级转录”**。
## 触发
```mermaid
flowchart TD
A[用户在视频播放页触发右键菜单或快捷键] --> B[Content Script 捕获当前 Tab URL 与宿主环境]
B --> C{识别目标平台}
C -- YouTube --> D1[调用 YouTubeAdapter<br/>提取页面上下文与字幕轨]
C -- 哔哩哔哩 --> D2[调用 BilibiliAdapter<br/>解析分P信息与 CC/AI 字幕]
C -- 其他平台 --> D3[提示暂不支持该平台]
右键菜单新增一条「保存视频笔记到 EdgeEver」。它只出现在上面两类播放页,上下文是 `page` 和 `video`。选中文字仍走现有的选区命令,图片仍走现有的图片命令。Chrome 和 Firefox 会在同一页面同时出现多条本扩展菜单时,把它们收进子菜单。`registerClipMenus` 会清空并重建全部菜单,新条目的 `documentUrlPatterns` 必须与选区、图片、X、GitHub、小红书、知乎、Reddit 互不重叠。
D1 --> E{检测是否存在可用字幕轨?}
D2 --> E
工具栏弹窗发给后台的 `captureCurrentPage`,在当前标签是受支持的播放页时走同一条视频保存。其他网页仍按现在的正文提取保存。
E -- 存在官方或自动字幕 (90%+ 场景) --> F[拉取并解析原始字幕数据<br/>耗时 <1s / 成本 $0]
E -- 无任何字幕轨 (边缘场景) --> G[透明降级: 提取音频流并调用<br/>云端 ASR / 多模态理解]
文案放进 `apps/extension/public/_locales/` 的 `en`、`zh_CN`、`ja`。
F --> H[归一化为统一 Transcript 数据模型]
G --> H
## 页面内读取
H --> I[构造 Prompt 调用 LLM 结构化总结]
I --> J[格式化生成带精准跳链的 EdgeEver Markdown]
J --> K[调用 EdgeEver API 保存笔记并弹窗反馈]
```
扩展的默认脚本环境读不到页面里的 `window` 变量。用户点击之后,用 `chrome.scripting.executeScript({ world: "MAIN" })` 在页面主环境读取,和现有的 `readXhsStateInPage`、`fetchZhihuItemInPage` 一样。Firefox 140 及以上支持同一写法。
---
主环境函数必须能独立序列化,不能依赖打包后的 import。它只返回可结构化克隆的数据:播放器响应、字幕文件正文、封面字节或明确的失败原因。解析、清洗和排版放在可单测的模块里,用夹具运行,不访问网络。
## 3. 双平台字幕与元数据提取策略详解
字幕地址在主环境里立刻请求,沿用当前页的登录态。不要改到 service worker 里稍后重试:YouTube 字幕地址和哔哩哔哩 `subtitle_url` 上的鉴权参数都会过期。页面内容视为不可信数据,按字幕文本保存,不把它当成插件指令。
在浏览器扩展环境下,Content Script 运行于用户当前激活标签页的会话中,享有与页面完全一致的登录态与网络上下文,可直接读取页面变量或以合法会话发起内部 API 查询,免去服务端爬虫的反爬与风控限制。
读不到播放器数据时提示「没有读到这个视频」,不创建空笔记。
### 3.1 YouTube 提取策略
## YouTube
#### 1. 目标 URL 匹配与标识符解析
* 视频播放页:`https://www.youtube.com/watch?v={videoId}`
* 网页短链:`https://youtu.be/{videoId}`
* Shorts 视频:`https://www.youtube.com/shorts/{videoId}`
* 时间戳跳转链接规则:`https://youtu.be/{videoId}?t={seconds}`
点击时读取**当前播放器**的响应。站内切集后,`window.ytInitialPlayerResponse` 可能仍是上一支,不能只读这个全局变量。
#### 2. 元数据与字幕抓取机制
1. **页面上下文解析**:直接从播放器全局变量 `window.ytInitialPlayerResponse` 中提取:
* 视频元数据:`videoDetails`(标题、频道名称 `author`、时长 `lengthSeconds`、缩略图 `thumbnail`)。
* 字幕轨道列表:`captions.playerCaptionsTracklistRenderer.captionTracks`。
2. **字幕轨道语言选择策略**:
* 优先匹配用户界面语言偏好(如中文 `zh-Hans` / `zh-CN`、英文 `en`)。
* 若无完全匹配语言,优先选择人工上传字幕轨(`kind` 不为 `'asr'`);次选官方 ASR 自动转写轨。
3. **数据格式转换**:
* 请求字幕轨的 `baseUrl`(增加 `&fmt=json3` 获取结构化 JSON 或解析默认 XML)。
* 将分段中的 `tStartMs` 与 `dDurationMs` 转换为标准秒级浮点数,合并文本片段得到连贯词句。
从该响应取出视频 ID、标题、频道名、时长、封面地址,以及 `captions.playerCaptionsTracklistRenderer.captionTracks`。有章节标记时取出每章的标题和起始秒;具体字段由夹具锁定,不把某一层嵌套路径写成永久契约。
---
字幕轨顺序:
### 3.2 哔哩哔哩(Bilibili)提取策略
1. 匹配浏览器界面语言(`zh*`、`en`、`ja`)。
2. 同语言下优先人工字幕。`kind` 缺失或不是 `asr` 视为人工字幕。
3. 再选该语言的自动字幕,然后才是其他语言的人工字幕和自动字幕。
#### 1. 目标 URL 匹配与分 P 标识解析
* 标准视频页:`https://www.bilibili.com/video/{bvid}` 或 `https://www.bilibili.com/video/av{aid}`
* 分 P 视频:URL 包含 `?p={partIndex}` 参数(如 `?p=2` 代表第 2 集/分P)。
* 时间戳跳转链接规则:`https://www.bilibili.com/video/{bvid}/?p={partIndex}&t={seconds}`(若为单 P 则可省略 `p`)。
用轨道上的 `baseUrl` 请求字幕,优先加 `fmt=json3`。失败时再解析轨道原本的 XML。`json3` 的正文在 `events[].segs[].utf8`,时间在 `tStartMs` 与 `dDurationMs`。只有换行、没有正文的事件丢掉。
#### 2. 分 P 识别与上下文解析
Bilibili 的长教程与公开课普遍采用**多 P(Multi-part)**形式,每一 P 拥有独立的 `cid`,且对应不同的字幕轨道与时长。
1. **获取视频整体信息**:
* 从页面变量 `window.__INITIAL_STATE__` 中直接读取 `videoData`:
* `bvid`、`aid`、主标题 `title`、封面 `pic`、UP主 `owner.name` 与 `owner.mid`。
* 分 P 列表 `pages`:包含各分 P 的 `cid`、`page`、`part`(分P子标题)、`duration`。
2. **确定当前播放分 P**:
* 解析当前 URL 的 `?p=` 参数;若无参数,则匹配页面 DOM 高亮项(如 `.cur-list .on`)或默认为 `p=1`。
* 取出当前分 P 对应的 `cid` 与分 P 子标题。
对外时间戳:
#### 3. 字幕数据获取机制
Bilibili 页面已支持官方 CC 字幕和 AI 自动生成字幕(如 `lan: "ai-zh"`、`lan_doc: "中文(自动生成)"`)。
1. **字幕轨道查询**:
* 方式 A(页面预加载):直接从 `__INITIAL_STATE__.videoData.subtitle` 读取已注入的字幕列表。
* 方式 B(内部 Player 接口):向 `https://api.bilibili.com/x/player/v2?cid={cid}&bvid={bvid}` 发起 GET 请求(带当前 Tab Cookie),从响应的 `subtitle.subtitles` 获取字幕轨道清单。
2. **字幕轨偏好选择**:
* 优先选择人工审核的 CC 轨(`is_machine === 0`);
* 次选平台自动生成的 AI 轨(`is_machine === 1`,通常为 `ai-zh`)。
3. **Bilibili 字幕 JSON 解析**:
* 请求 `subtitle_url`(如 `//aisubtitle.hdslb.com/...`,注意补全 `https:` 协议前缀)。
* 结构为标准的 JSON 对象:
```json
{
"body": [
{ "from": 0.0, "to": 3.25, "content": "各位小伙伴大家好,本节课我们讲解..." },
{ "from": 3.5, "to": 7.18, "content": "如何使用 TypeScript 构建可扩展应用架构。" }
]
}
```
* 直接提取 `from`(秒)、`to`(秒)与 `content`,与统一模型无缝契合。
- 普通视频:`https://www.youtube.com/watch?v={id}&t={seconds}s`
- Shorts:`https://www.youtube.com/shorts/{id}?t={seconds}`
---
`seconds` 为非负整数。
## 4. 无字幕视频的透明降级转录机制
## 哔哩哔哩
对于部分既没有人工 CC 字幕、平台也未自动生成字幕的视频,采取三级降级策略:
元数据和分 P 从当前页状态读取:`bvid`、`aid`、标题、封面、UP 主,以及 `pages` 里的 `cid`、`page`、`part`、`duration`。当前 P 来自 URL 的 `p`;没有该参数时为第 1 P。客户端切 P 后以点击时的页面状态为准。
| 方案 | 技术实现 | 优势 | 成本与体验权衡 | 适用场景 |
| :--- | :--- | :--- | :--- | :--- |
| **首选:平台原生字幕** | 读取 YouTube / Bilibili 官方字幕数据 | 毫秒级提取、零服务器成本、高准确率 | 无 | 主流场景(90%+ 视频) |
| **二级降级:高性价比云端 ASR** | 提取低码率音频切片,调用 Groq Whisper API 或 Cloudflare Workers AI | 转录速度快(30~50x 实时速),精度达到 Whisper Large 水准 | 成本极低(约 $0.03/小时),需服务端转发音频切片 | 官方无字幕时的首选降级 |
| **三级降级:Gemini 多模态一步提炼** | 将音频分片/URI 直传 Gemini 1.5/2.0 原生音视频理解接口 | 免去单独 ASR 与文字拼接,直接输出带时间戳大纲与总结 | 输入 Token 按多模态计费,对长视频网络上行要求高 | 适合短视频或重点片段深研 |
字幕列表以页面主环境里签名后的 `https://api.bilibili.com/x/player/wbi/v2` 为准,请求带当前页 Cookie。未签名的 `/x/player/v2` 不作为契约。页面状态里如果已经有字幕列表,可以作为同一解析器的另一路输入。签名使用页面上已有的 WBI 材料,在主环境内完成。
### 用户端反馈交互体验
* **原生字幕就绪**:插件弹窗/通知显示:“检测到平台字幕,正在生成 AI 结构化总结…”(1~3 秒完成)。
* **触发无字幕 ASR**:插件显示:“当前视频暂无可用字幕,正在转录音频(约需 10~20 秒)…”,完成后自动保存并提示。
字幕项使用 `lan`、`lan_doc`、`subtitle_url`、`type`、`ai_type`、`ai_status`。不读取 `is_machine`。选择顺序与 YouTube 相同:先匹配界面语言,`lan` 不以 `ai-` 开头的视为人工字幕,`ai-zh` 这类轨道视为平台自动字幕。`subtitle_url` 缺协议时补 `https:`,并立刻请求。正文使用 `body[].from`、`body[].to`、`body[].content`,单位是秒。
---
播放器响应里的 `view_points` 在同时具备起始秒和标题时当作章节。
## 5. 归一化数据模型与适配器接口设计
对外时间戳:`https://www.bilibili.com/video/{bvid}?t={seconds}`。多于 1 P 时加上 `p={page}`。
为保证多平台扩展时不改动核心 Pipeline 与 LLM 逻辑,设计清晰的统一接口抽象:
### 5.1 数据模型规范
## 数据模型
```typescript
// 归一化字幕片段
export interface TranscriptSegment {
start: number; // 开始时间(秒,浮点数)
end: number; // 结束时间(秒,浮点数)
text: string; // 文本内容
export interface TranscriptCue {
start: number; // 秒
end: number;
text: string;
}
// 归一化视频元数据
export interface VideoMetadata {
platform: "youtube" | "bilibili" | string;
videoId: string; // YouTube Video ID 或 Bilibili BV 号
partIndex?: number; // 分 P 序号(如 1, 2)
partTitle?: string; // 分 P 标题(如 "P2: 核心配置与架构设计")
title: string; // 视频主标题
author: string; // 频道名或 UP 主昵称
authorUrl?: string; // 创作者主页链接
duration: number; // 视频时长(秒)
thumbnailUrl: string; // 封面图链接
sourceUrl: string; // 规范化源视频播放链接
hasSubtitles: boolean; // 是否成功取得原生字幕
subtitleType?: "creator" | "platform_ai" | "asr_fallback";
export interface VideoChapter {
start: number;
title: string;
}
export type SubtitleOrigin = "creator" | "platform_auto";
export interface VideoCapture {
platform: "youtube" | "bilibili";
videoId: string;
partIndex?: number;
partTitle?: string;
title: string;
author: string;
authorUrl?: string;
duration: number;
sourceUrl: string;
cues: TranscriptCue[];
subtitleOrigin?: SubtitleOrigin;
chapters: VideoChapter[];
thumbnail?: { bytes: Uint8Array; mimeType: string };
}
export interface VideoOutline {
tldr: string;
sections: { start: number; text: string }[];
takeaways: string[];
}
```
### 5.2 适配器接口抽象(`VideoPlatformAdapter`)
清洗字幕时解码 HTML 实体、合并空白、丢掉空句。发给模型的输入再把相邻字幕收成大约 30–60 秒一块,并保留每块的起始秒。笔记里的实录仍按清洗后的字幕逐条保存,避免模型输入的粗分块变成用户看到的全文。
```typescript
export interface VideoPlatformAdapter {
readonly platform: string;
## 笔记正文
/** 判断当前 Tab URL 是否属于该平台 */
canHandle(url: URL): boolean;
笔记标题用视频标题,截断到创建接口的 160 字上限。分 P 子标题写在来源信息里。笔记本用现有的 `notebookForClip`。标签只加 `web-clip`。
/** 从页面上下文中提取视频元数据与分 P 信息 */
extractMetadata(document: Document, windowContext: unknown): Promise<VideoMetadata>;
封面只有在主环境拿到图片字节时才保存,复用现有图片笔记的资源上传。Markdown 使用本地资源地址:
/** 获取归一化字幕片段;若无字幕返回 null */
extractTranscript(metadata: VideoMetadata, windowContext: unknown): Promise<TranscriptSegment[] | null>;
/** 将时间(秒)格式化为平台原生支持的时间戳跳转 URL */
formatTimestampUrl(metadata: VideoMetadata, seconds: number): string;
}
```markdown
![封面](/api/v1/resources/{resourceId}/blob)
```
任何未来新增的平台(如 `VimeoAdapter`、`XiaohongshuVideoAdapter` 等),仅需实现上述 4 个方法,即可接入完整的 EdgeEver 剪藏与总结流程。
拿不到字节,或 Token 没有 `write:resources` 时,省略封面,笔记照常创建。不把 `hdslb.com`、`ytimg.com` 或其他外链图写进正文。哔哩哔哩图床校验来源页,外链会在笔记里裂开。
---
## 6. AI 总结 Prompt 结构与笔记渲染规范
### 6.1 Prompt 设计规范
* **角色设定**:专业视频内容分析师与知识提炼专家。
* **输入内容**:
* 视频元信息:平台名称、标题、作者、分 P 信息、时长。
* 合并整理后的时间戳字幕段落(将短句按 30~60 秒合并为语意块,减少 Token 浪费)。
* **输出规范**:
1. **一句话核心总结 (TL;DR)**:提炼全篇核心价值与核心论点。
2. **分段重点大纲 (Key Highlights)**:
* 关键节点必须附带格式为 `[mm:ss](跳转链接)` 的跳转标记。
* 阐述该时间段的核心内容、演示过程或核心论证。
3. **核心知识点与干货清单 (Takeaways)**:
* 提炼专业概念、关键参数、技术选型或实践建议。
4. **完整字幕实录折叠区 (Transcript)**:以 `<details>` 标签包裹,便于全文检索。
### 6.2 目标 EdgeEver Markdown 笔记形态
正文由代码组装。模型不输出 Markdown,也不回写实录。标题和字段名走扩展的界面语言。下面是简体中文界面下、总结成功时的形态:
```markdown
# 打造第二大脑:从零构建个人知识库系统
> **来源**:[影视飓风 - 打造第二大脑:从零构建个人知识库系统](https://www.bilibili.com/video/BV1xx411c7xx?p=1)
> **平台**:哔哩哔哩 (P1: 知识库的核心逻辑) | **时长**:24:15
> **字幕来源**:平台 AI 字幕 | **保存时间**:2026-10-05
> **来源**:[影视飓风 - 打造第二大脑:从零构建个人知识库系统](https://www.bilibili.com/video/BV1xx411c7xx?p=1)
> **平台**:哔哩哔哩 · P1 知识库的核心逻辑 · 24:15
> **字幕**:平台自动字幕 · **保存时间**:2026-10-05
![视频封面](https://i0.hdslb.com/bfs/archive/xxxxxx.jpg)
![封面](/api/v1/resources/res_example/blob)
---
## 核心总结
## 💡 核心总结 (TL;DR)
视频系统性梳理了知识管理的核心困境,提出了“输入-整理-输出”闭环流,并演示了如何基于标签网络与双链系统构建抗遗忘的第二大脑。
视频说明知识管理为什么要形成输入、整理和输出的闭环。
## 📌 分段大纲与精彩看点
- [00:00](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=0) 导语:为什么传统文件夹分类注定失效
- [04:12](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=252) 核心模型拆解:PARA 框架与渐进式总结法的融合
- [11:35](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=695) 现场演示:从碎片收集到结构化笔记的转化全流程
- [18:50](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=1130) 工具选择哲学:避免陷入“工具挑选强迫症”
- [22:30](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=1350) 总结与实践行动建议
## 分段大纲
## 🔑 核心知识点与干货清单
- **知识折旧率**:未经主动加工的收藏内容,其复用价值随时间指数级衰减。
- **渐进式总结法(Progressive Summarization)**:
1. 第一层:原始实录抓取;
2. 第二层:加粗关键短语;
3. 第三层:提炼摘要总结;
4. 第四层:转化为个人项目行动。
- [00:00](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=0) 为什么只靠文件夹分类会失效
- [04:12](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=252) PARA 与渐进式总结如何叠在一起
## 要点
- 未经整理的收藏,之后很难再被用到。
<details>
<summary><b>点击展开完整字幕实录 (Transcript)</b></summary>
<summary>字幕实录</summary>
- [00:00](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=0) 大家好,今天我们来聊知识管理。
- [00:25](https://www.bilibili.com/video/BV1xx411c7xx?p=1&t=25) 很多人收藏之后就没有再打开。
[00:00] 大家好,我是 Tim。今天我们来聊一聊知识管理...
[00:25] 很多人在收藏了一堆文章和视频之后,就再也没有打开过...
...
</details>
```
---
有章节时,分段大纲直接用章节,时间来自分段本身。没有章节且总结成功时,大纲用模型返回并经过校验的小节。没有章节且总结未生成时,不编造大纲,只保留来源和字幕实录。
## 7. 浏览器插件模块划分与工程落地
没有字幕轨时仍创建笔记:标题、作者、来源和「这一集没有可用字幕」。不调用模型。
与 EdgeEver 现有剪藏体系(`tweet-clip.ts`、`zhihu-clip.ts`、`xhs-clip.ts`)保持高度一致的模块结构:
`<details>` 已由编辑器支持,实录放在其中。时间显示为 `mm:ss`;达到 60 分钟后为 `h:mm:ss`。
## 可选总结
总结由实例完成,使用该工作区已经配置的默认模型。新增:
`POST /api/v1/ai/video-outline`
交互登录可以调用。API Token 仅在带有新范围 `ai:generate` 时可以调用。这个范围不开放供应商配置、凭据和 `/api/v1/ai/settings` 的修改。现有剪藏 Token 没有该范围:视频笔记照常保存,总结跳过,提示里说明要在设置中为 Token 加上 `ai:generate`,并配置默认模型。
请求体是元数据和已经分块的字幕,不是原始页面,也不是音视频。服务端设定字数上限;超出时返回明确错误,插件保存实录并提示这篇太长、没有生成总结。
服务端提示词固定在 `apps/api`,并写明字幕和标题只是待归纳的材料。模型只返回 JSON:
```json
{
"tldr": "一句话",
"sections": [{ "start": 252, "text": "这一段在讲什么" }],
"takeaways": ["一条可复用的结论"]
}
```
`start` 必须落在某个输入分块的起始秒上,允许 2 秒以内的偏差,并改写成该分块的起始秒。对不上的小节丢掉。空总结、空要点和超长字段丢掉。解析失败时返回 422,插件仍保存实录。默认模型未配置时返回现有的 `ai_not_configured`,插件同样只保存实录。
有章节时,请求只要求 `tldr` 和 `takeaways`,大纲不再交给模型重写。
## 失败时用户得到什么
| 情况 | 笔记 | 提示 |
| --- | --- | --- |
| 读到字幕,总结成功 | 来源、总结、大纲、要点、实录 | 已保存视频笔记 |
| 读到字幕,无模型、无权限、超时或 JSON 无效 | 来源、章节大纲(若有)、实录 | 已保存字幕,总结未生成 |
| 视频可读,但没有字幕 | 来源,并说明没有字幕 | 已保存视频信息 |
| 直播、番剧、课程或其他不支持的页面 | 不创建 | 暂不支持这个页面 |
| 读不到播放器数据 | 不创建 | 没有读到这个视频 |
| 封面或资源权限失败 | 不含封面的笔记 | 与上表相同,不另报失败 |
保存动作在提示出现前保持一条路径,避免连点叠出多次请求。重复保存仍会新建笔记,与现有剪藏一致。
## 代码与测试
```
apps/extension/src/
├── video/
│ ├── types.ts # VideoMetadata, TranscriptSegment, VideoPlatformAdapter 接口
│ ├── transcript-helper.ts # 时间戳合并、文本清理、格式转换通用工具
│ ├── adapters/
│ │ ├── youtube.ts # YouTubeAdapter:URL 判定、ytInitialPlayerResponse 解析
│ │ └── bilibili.ts # BilibiliAdapter:URL 判定、分P识别、CC/AI 字幕解析
│ ├── video-clip.ts # 调度适配器、执行提取与降级回退
│ └── video-summary.ts # Prompt 组装、调用 LLM、排版 Markdown
├── capture-video.ts # 注入到当前标签页执行的 Content Script 抓取器
├── video-target.ts # 监听右键菜单目标,记录当前激活的目标视频与分 P
└── background.ts # 注册 contextMenus ("保存视频总结"),调度完整保存流
│ ├── types.ts
│ ├── transcript.ts # 清洗、分块、时间格式、Markdown
│ ├── youtube.ts # 地址识别、播放器 JSON 解析、时间戳
│ ├── bilibili.ts # 地址识别、分 P、字幕 JSON、时间戳
│ └── video-note.ts # 组装笔记、决定是否请求总结
├── background.ts # 菜单、MAIN 世界读取、创建笔记
apps/api/src/ # POST /api/v1/ai/video-outline
```
---
页面主环境读取函数放在扩展源码里,通过 `executeScript` 的 `func` 注入。解析函数保持纯函数,测试不打开浏览器。
## 8. 实施推进计划
测试至少覆盖:
1. **第一阶段:多平台原生字幕与 AI 总结闭环(MVP)**
* 完成 `VideoPlatformAdapter` 抽象及 `youtube.ts`、`bilibili.ts` 适配器。
* 支持 YouTube(多语言+ASR字幕)与 Bilibili(分P识别+CC/AI字幕)的零成本极速提取。
* 打通 LLM 结构化总结并写入 EdgeEver 笔记库。
2. **第二阶段:无字幕 ASR 降级通道**
* 集成轻量音频提取与云端 ASR(Groq Whisper / Cloudflare Workers AI)降级通道。
* 优化长视频分块并发处理与进度提示。
3. **第三阶段:体验增强与平台拓展**
* 支持多模板切换(如“精简摘要”、“教程笔记模版”、“播客对话模版”)。
* 接入更多音视频平台(Vimeo、小宇宙播客、Twitter/X 视频)。
- YouTube:`json3`、人工轨与 `asr`、Shorts 链接、过期全局变量不能覆盖当前播放器响应。
- 哔哩哔哩:分 P、`ai-zh` 与人工轨、协议相对的字幕地址、`view_points`。
- Markdown:章节大纲、非法 `start` 被丢弃、实录在 `<details>` 内、正文不含图床外链。
- 菜单:新条目的 URL 模式与现有命令不相交。
- 接口:无 `ai:generate` 的 Token 得到 403;未配置模型得到 `ai_not_configured`;合法 JSON 被规范化。插件在 403、409、422 和网络失败时仍能交出实录笔记。
夹具使用脱敏的播放器 JSON,不把真实 Cookie 或字幕地址上的鉴权参数放进仓库。
## 商店与权限
实现阶段不需要新的主机权限。读取和字幕请求发生在用户点击之后的页面主环境,实例请求继续走用户已经授予的实例地址。
提交商店前要提高 `apps/extension/package.json` 的版本。当前已上传版本不能重复提交。商店说明只加一行:可以把 YouTube 或哔哩哔哩播放页保存为视频笔记。不要在首句堆叠站点名。
同步这些说明:
- `apps/extension/STORE_LISTING.md`
- `apps/extension/FIREFOX_STORE_LISTING.md`
- `apps/extension/README.md`
- 权限用途:新菜单只在受支持的播放页、用户点击后运行。
- 数据披露:页面内容发给用户配置的 EdgeEver 实例。Token 带有 `ai:generate` 且工作区已配置默认模型时,实例把字幕文本发给该模型供应商。插件不把页面或音频发给其他第三方。
产品发布流程不上传扩展。商店包仍由单独的 Submit Web Clipper 提交。
## 以后
新的视频站点沿用同一条保存路径:主环境返回 `VideoCapture`,纯函数解析,代码排版。那之前不为想象中的站点加抽象层。
语音识别若以后要做,另写方案。它至少要同时满足:用户在这一次保存里明确同意,音频只发给用户自己配置且支持音频的模型,失败时保留已经写好的笔记,并且不依赖某家云厂商的独占接口。
## 风险与验证
价值是把正在看的一集收成可检索、可跳回原片的笔记。影响范围是剪藏菜单、弹窗在播放页上的行为、一个新的只读生成接口和 Token 范围。不改数据库迁移、安装器和自动升级。
最坏情况是菜单重建让现有保存命令折叠或消失,解析失败却写出空笔记,或者新接口把供应商凭据返回给插件。实现时用现有菜单测试锁住 URL 模式,没有字幕正文就不写实录,接口响应里不包含凭据。
回滚不依赖数据迁移。扩展版本独立于产品 Release;有问题的包停在商店审核之前即可。新接口是追加的,旧客户端不调用它。
合入前用真实页面各点一次保存:普通 YouTube 视频、Shorts、站内切到下一支之后的视频、哔哩哔哩单 P、分 P、有人工字幕、只有自动字幕、没有字幕、未登录。确认笔记出现在默认笔记本,时间戳能打开对应秒数,现有图片和选区命令仍留在右键菜单第一级。