Skill Seekers 视频源输出结构与 SKILL.md 集成指南
Skill Seekers 视频源输出结构与 SKILL.md 集成指南
本文基于仓库 docs/plans/video/05_VIDEO_OUTPUT.md 展开,并结合 video_models.py、video_scraper.py 与测试用例进行源码级印证。
Skill Seekers 的视频源支持将 YouTube / Vimeo 视频、播放列表与本地视频文件转换为 Claude AI Skill。本文聚焦输出层设计:视频抓取完成后,如何把转录文本、OCR 识别的屏幕代码、幻灯片文本与关键帧组织成统一的 Skill 产物——包括输出目录结构、reference 文件格式、SKILL.md 的 Video Tutorials 章节、元数据 JSON 格式、与既有 page 管线的兼容性,以及面向 RAG 的专用分块策略。读完本文,你将掌握视频源在 Skill Seekers 中的完整落盘结构,并能据此检查、复用或二次开发视频型 Skill 的产物。
本系列文档还包括 01_VIDEO_SOURCE_OVERVIEW.md 与 02_VIDEO_DATA_MODELS.md,本文是其中的输出规范(第 5 篇),对应实现位于 video_scraper.py 的 build_skill() 与 _generate_reference_md()、_generate_skill_md()。
1. 输出目录结构总览
视频源与文档源、GitHub 源共用同一套 Skill 输出根目录,视频专属数据集中在新增的 video_data/ 目录中:
output/{skill_name}/
├── SKILL.md # 主 Skill 文件(含 Video Tutorials 章节)
├── references/
│ ├── getting_started.md # 来自文档(既有)
│ ├── api.md # 来自文档(既有)
│ ├── video_react-hooks-tutorial.md # ← 视频 reference 文件
│ ├── video_project-setup-guide.md # ← 视频 reference 文件
│ └── video_advanced-patterns.md # ← 视频 reference 文件
├── video_data/ # ← 新增:视频专属数据
│ ├── metadata.json # VideoScraperResult 完整元数据
│ ├── transcripts/
│ │ ├── abc123def45.json # 每部视频的原始转录
│ │ ├── xyz789ghi01.json
│ │ └── ...
│ ├── segments/
│ │ ├── abc123def45_segments.json # 每部视频的对齐分段
│ │ ├── xyz789ghi01_segments.json
│ │ └── ...
│ └── frames/ # 仅 --visual 启用时生成
│ ├── abc123def45/
│ │ ├── frame_045.00_terminal.png
│ │ ├── frame_052.30_code.png
│ │ ├── frame_128.00_slide.png
│ │ └── ...
│ └── xyz789ghi01/
│ └── ...
├── pages/ # 既有 page 格式
│ ├── page_001.json # 来自文档(既有)
│ ├── video_abc123def45.json # ← 视频的 page 格式
│ └── ...
└── {skill_name}_data/ # 原始抓取数据(既有)
从源码结构看,该目录骨架由 VideoScraper.build_skill() 落实——它依次创建 references/ 与 video_data/ 目录(video_scraper.py),为每部视频生成一个 reference 文件、写入 video_data/metadata.json,最后生成 SKILL.md。
2. Reference 文件格式
每个视频在 references/ 下产生一个 Markdown 文件,文件名由视频标题清洗后加 video_ 前缀得到。
2.1 命名规范
video_{sanitized_title}.md
清洗规则(对应实现见 _sanitize_filename(),video_scraper.py):
- 转小写
- 空格与特殊字符替换为连字符
- 合并连续连字符
- 截断至 60 字符
- 示例:"React Hooks Tutorial for Beginners" →
video_react-hooks-tutorial-for-beginners.md
# video_scraper.py 中实际生成逻辑
sanitized = _sanitize_filename(video.title) or video.video_id or f"video_{hash(video.title) % 10000:04d}"
ref_filename = f"video_{sanitized}.md"
标题缺失时回退到 video_id,再回退到基于 hash() 的四位编号——三者保证文件名唯一。
2.2 文件结构
# {Video Title}
> **Source:** {channel_name} | **Duration:** {HH:MM:SS} | **Published:** {date}
> **URL:** {url}
> **Views:** {view_count} | **Likes:** {like_count}
> **Tags:** {tag1}, {tag2}, {tag3}
{description_summary (前 200 字符)}
---
## Table of Contents
{由章节标题 / 分段标题自动生成}
---
{segments 渲染为小节}
### {Chapter Title 或 "Segment N"} ({MM:SS} - {MM:SS})
{合并内容:转录 + 代码块 + 幻灯片文本}
```{language}
{屏幕上显示的代码}
{下一章} ({MM:SS} - {MM:SS})
{内容继续...}
Key Takeaways
{AI 生成的要点总结 —— 在 enhance 阶段填充}
Code Examples
{视频中所有代码块的汇总列表}
`_generate_reference_md()` 严格按此骨架产出:先写标题与元数据块(来源、时长、发布日期、URL、观看/点赞数、标签),接着输出描述摘要(实现中截取前 300 字符,<a href="https://link.gitcode.com/i/76861d664344cc74b6bbc507218be141" target="_blank">video_scraper.py</a>),再生成目录与各分段。实现中还补充了两个文档骨架之外的进阶区块:
- **Code Timeline**:当启用了基于共识的屏幕文本追踪(`TextGroupTimeline`)时,输出每个代码组(`TextGroup`)的出现时间段、最终文本与编辑历史(`+` 新增 / `-` 删除 / `~` 修改行),见 <a href="https://link.gitcode.com/i/78b473b88e1b7e184fbf9c3de99c0706" target="_blank">video_scraper.py</a>;
- **Audio-Visual Alignment**:屏幕代码与旁白转录的时间对齐配对,见 <a href="https://link.gitcode.com/i/9fa621b0180baeb61f0fb4f4ba90c65d" target="_blank">video_scraper.py</a>。
### 2.3 完整示例
以下为文档中的完整示例,展示了分段渲染、代码块与 Key Takeaways 的最终形态:
```markdown
# React Hooks Tutorial for Beginners
> **Source:** [React Official](https://youtube.com/@reactofficial) | **Duration:** 30:32 | **Published:** 2026-01-15
> **URL:** [https://youtube.com/watch?v=abc123def45](https://youtube.com/watch?v=abc123def45)
> **Views:** 1,500,000 | **Likes:** 45,000
> **Tags:** react, hooks, tutorial, javascript, web development
Learn React Hooks from scratch in this comprehensive tutorial. We'll cover useState, useEffect, useContext, and custom hooks with practical examples.
---
## Table of Contents
- [Intro](#intro-0000---0045)
- [Project Setup](#project-setup-0045---0300)
- [useState Hook](#usestate-hook-0300---0900)
- [useEffect Hook](#useeffect-hook-0900---1500)
- [Custom Hooks](#custom-hooks-1500---2200)
- [Best Practices](#best-practices-2200---2800)
- [Wrap Up](#wrap-up-2800---3032)
---
### Intro (00:00 - 00:45)
Welcome to this React Hooks tutorial. Today we'll learn about the most important hooks in React and how to use them effectively in your applications. By the end of this video, you'll understand useState, useEffect, useContext, and how to create your own custom hooks.
---
### Project Setup (00:45 - 03:00)
Let's start by setting up our React project. We'll use Create React App which gives us a great starting point with all the tooling configured.
**Terminal command:**
```bash
npx create-react-app hooks-demo
cd hooks-demo
npm start
Code shown in editor:
import React from 'react';
function App() {
return (
<div className="App">
<h1>Hooks Demo</h1>
</div>
);
}
export default App;
useState Hook (03:00 - 09:00)
The useState hook is the most fundamental hook in React. It lets you add state to functional components. Before hooks, you needed class components for state management.
Code shown in editor:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
Key Takeaways
- useState 用于在函数组件中管理简单状态值
- useEffect 处理副作用(数据获取、订阅、DOM 更新)
- 始终在 useEffect 中提供依赖数组以控制执行时机
- 自定义 Hook 用于抽取可复用的有状态逻辑
- 遵循 Hooks 规则:只在顶层调用 Hooks、只在 React 函数中调用
Code Examples
Counter with useState
const [count, setCount] = useState(0);
Data Fetching with useEffect
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
Custom Hook: useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
文档中还给出了本地视频片段(clip)的处理说明:使用 --start-time / --end-time 截取片段时,reference 文件的 Duration 会标注为 Clip {start}-{end} (of {original}) 形式,见 video_scraper.py。
3. SKILL.md 的 Video Tutorials 章节
视频内容以独立章节形式集成进 SKILL.md,遵循既有章节模式。
3.1 章节排布
# {Skill Name}
## Overview
{既有 overview 章节}
## Quick Reference
{既有 quick reference}
## Getting Started
{来自 docs/github}
## Core Concepts
{来自 docs/github}
## API Reference
{来自 docs/github}
## Video Tutorials ← 新增章节(来自视频源)
## Code Examples
{汇总自所有源}
## References
{文件清单}
3.2 章节内容
## Video Tutorials
This skill includes knowledge extracted from {N} video tutorial(s) totaling {HH:MM:SS} of content.
### {Video Title 1}
**Source:** {channel} | {duration} | {view_count} views
{summary 或首个分段内容(缩写)}
**Topics covered:** {章节标题或检测到的主题}
→ Full transcript: references/video_{sanitized_title}.md
---
### {Video Title 2}
...
### Key Patterns from Videos
{AI 生成、跨多部视频反复出现的模式总结}
### Code Examples from Videos
{按主题组织的、来自全部视频的代码块汇总}
```{language}
// From: {video_title} at {timestamp}
{code}
实现细节(`_generate_skill_md()`,<a href="https://link.gitcode.com/i/4bed4db9a6991d87420fdfccee85dafa" target="_blank">video_scraper.py</a>):
- **Overview 段落**自动统计视频总数、总时长,并在启用了视觉抽取时追加关键帧数、含屏上文本的关键帧数与检测到的代码块数;
- 每部视频输出 `**Source:**`(频道名链接到视频 URL)、`**Topics covered:**`(取自各分段的 `chapter_title`)、首个分段转录前 200 字符作为预览,以及指向完整转录的 `references/video_*.md` 相对链接;
- 存在警告时追加 `## Notes` 章节(例如"使用了 YouTube 自动生成字幕");
- 末尾的 `## References` 以列表形式列出全部视频 reference 文件。
### 3.3 播放列表分组
当视频源是播放列表时,SKILL.md 章节以播放列表标题分组:
```markdown
## Video Tutorials
### React Complete Course (12 videos, 6:30:00 total)
1. **Introduction to React** (15:00) — Components, JSX, virtual DOM
2. **React Hooks Deep Dive** (30:32) — useState, useEffect, custom hooks
3. **State Management** (28:15) — Context API, Redux patterns
...
→ Full transcripts in references/ (video_*.md files)
播放列表上下文在数据模型层由 VideoInfo.playlist_title / playlist_index / playlist_total 承载(video_models.py),分段数据则由 VideoSegment 的 index / chapter_title 提供分组依据。
4. 元数据 JSON 格式
video_data/ 下存放三类 JSON:总览元数据、原始转录、对齐分段。源码中由 save_extracted_data() 与 build_skill() 负责写出(json.dump(..., indent=2, ensure_ascii=False)),并可通过 --from-json 重新加载后直接构建 Skill(load_extracted_data())。
4.1 video_data/metadata.json — 完整抓取结果
对应 VideoScraperResult(video_models.py)序列化:
{
"scraper_version": "3.2.0",
"extracted_at": "2026-02-27T14:30:00Z",
"processing_time_seconds": 125.4,
"config": {
"visual_extraction": true,
"whisper_model": "base",
"segmentation_strategy": "hybrid",
"max_videos": 20
},
"summary": {
"total_videos": 5,
"total_duration_seconds": 5420.0,
"total_segments": 42,
"total_code_blocks": 18,
"total_keyframes": 156,
"languages": ["en"],
"categories_found": ["getting_started", "hooks", "advanced"]
},
"videos": [
{
"video_id": "abc123def45",
"title": "React Hooks Tutorial for Beginners",
"duration": 1832.0,
"segments_count": 7,
"code_blocks_count": 5,
"transcript_source": "youtube_manual",
"transcript_confidence": 0.95,
"content_richness_score": 0.88,
"reference_file": "references/video_react-hooks-tutorial-for-beginners.md"
}
],
"warnings": [
"Video xyz789: Auto-generated captions used (manual not available)"
],
"errors": []
}
实际序列化的 VideoScraperResult.to_dict() 直接输出 videos(每项为完整的 VideoInfo.to_dict(),含 segments 全量数据)、total_duration_seconds、total_segments、total_code_blocks、processing_time_seconds、warnings 与 errors。测试 tests/test_video_scraper.py 的 test_video_scraper_result_serialization、test_build_skill_from_loaded_data 验证了该往返过程:build_skill() 后 SKILL.md、references/、video_data/ 均存在。
4.2 video_data/transcripts/{video_id}.json — 原始转录
对应 TranscriptSegment 序列化(每条含 text / start / end / confidence / words / source):
{
"video_id": "abc123def45",
"transcript_source": "youtube_manual",
"language": "en",
"segments": [
{
"text": "Welcome to this React Hooks tutorial.",
"start": 0.0,
"end": 2.5,
"confidence": 1.0,
"words": null
},
{
"text": "Today we'll learn about the most important hooks.",
"start": 2.5,
"end": 5.8,
"confidence": 1.0,
"words": null
}
]
}
TranscriptSource 枚举(video_models.py)区分了转录来源:youtube_manual(人工字幕)、youtube_auto_generated(YouTube 自动生成)、whisper(本地 faster-whisper)、subtitle_file(SRT/VTT 字幕文件)与 none。confidence 默认值语义:YouTube 人工字幕 1.0、自动字幕 0.8、Whisper 取模型概率。
4.3 video_data/segments/{video_id}_segments.json — 对齐分段
对应 VideoSegment.to_dict()(video_models.py),是抓取管线的核心输出:
{
"video_id": "abc123def45",
"segmentation_strategy": "chapters",
"segments": [
{
"index": 0,
"start_time": 0.0,
"end_time": 45.0,
"duration": 45.0,
"chapter_title": "Intro",
"category": "getting_started",
"content_type": "explanation",
"transcript": "Welcome to this React Hooks tutorial...",
"transcript_confidence": 0.95,
"has_code_on_screen": false,
"has_slides": false,
"keyframes_count": 2,
"code_blocks_count": 0,
"confidence": 0.95
}
]
}
每个 VideoSegment 聚合三条抽取流(与 02_VIDEO_DATA_MODELS.md 定义一致):
- 流 1:ASR(音频) —
transcript、words(词级时间戳)、transcript_confidence; - 流 2:OCR(视觉) —
keyframes、ocr_text、detected_code_blocks、has_code_on_screen / has_slides / has_diagram; - 流 3:元数据 —
chapter_title、topic、category。
分段策略的优先级(SegmentationStrategy,见 video_models.py):YouTube 章节边界 → 语义边界(NLP 主题切换检测)→ 时间窗口(默认 3–5 分钟)→ 场景切换(scene_change)→ hybrid 组合。测试 test_segment_by_chapters、test_segment_by_time_window 覆盖了对应路径。
5. Page JSON 格式(兼容层)
为与既有基于 pages/*.json 的构建管线兼容,每部视频同时产出一个 page JSON。这样视频内容可以与其他源一样流入同一套构建流程——文档中明确指出该格式与 doc_scraper.py 中的 build_skill() 兼容,后者读取 pages/*.json 构建 Skill。
5.1 pages/video_{video_id}.json
{
"url": "https://www.youtube.com/watch?v=abc123def45",
"title": "React Hooks Tutorial for Beginners",
"content": "{全部分段的合并内容}",
"category": "tutorials",
"source_type": "video",
"metadata": {
"video_id": "abc123def45",
"duration": 1832.0,
"channel": "React Official",
"view_count": 1500000,
"chapters": 7,
"transcript_source": "youtube_manual",
"has_visual_extraction": true
},
"code_blocks": [
{
"language": "jsx",
"code": "const [count, setCount] = useState(0);",
"source": "video_ocr",
"timestamp": 195.0
}
],
"extracted_at": "2026-02-27T14:30:00Z"
}
关键点:source_type 被标记为 "video",code_blocks[].source 标记为 "video_ocr"(代码块来自视频 OCR 而非文档),使下游可以识别视频来源的内容。这一设计让视频 Skill 与纯文档 Skill 在构建、增强、打包阶段无缝共用管线。
6. 面向 RAG 的视频分块策略
启用 --chunk-for-rag 时,视频分块与文本文档不同——视频内容本身具备自然边界(章节/分段),无需再依赖纯长度切分。
6.1 分块策略
对每个 VideoSegment:
若 segment.duration <= chunk_duration_threshold(默认 300s / 5 分钟):
→ 作为单个 chunk 输出
否则若分段内含子小节(代码块与讲解交错):
→ 在代码块边界处切分
→ 每个 chunk = 讲解 + 关联代码块
否则(无清晰子小节的长分段):
→ 在句子边界处切分
→ 目标 chunk 大小:config.chunk_size tokens
→ 重叠:config.chunk_overlap tokens
该策略与仓库中 rag_chunker.py 的通用语义分块(段落边界、可配置 chunk 大小/重叠、代码块回填)保持一致,视频场景额外利用 VideoSegment.duration 作为一等切分信号。
6.2 每个 chunk 的 RAG 元数据
{
"text": "chunk content...",
"metadata": {
"source": "video",
"source_type": "youtube",
"video_id": "abc123def45",
"video_title": "React Hooks Tutorial",
"channel": "React Official",
"timestamp_start": 180.0,
"timestamp_end": 300.0,
"timestamp_url": "https://youtube.com/watch?v=abc123def45&t=180",
"chapter": "useState Hook",
"category": "hooks",
"content_type": "live_coding",
"has_code": true,
"language": "en",
"confidence": 0.94,
"view_count": 1500000,
"upload_date": "2026-01-15"
}
}
timestamp_url 字段尤其有价值——它让 RAG 系统可以直接链接到视频中的相关时刻(&t=180 秒跳转),实现"检索即定位"。配合 timestamp_start / timestamp_end 与 chapter 信息,回答问题时可以追溯到具体上下文片段。
7. 输出示例
7.1 最小输出(仅转录,单视频)
output/react-hooks-video/
├── SKILL.md # 含视频章节的 Skill
├── references/
│ └── video_react-hooks-tutorial.md # 按章节组织的完整转录
├── video_data/
│ ├── metadata.json # 抓取元数据
│ ├── transcripts/
│ │ └── abc123def45.json # 原始转录
│ └── segments/
│ └── abc123def45_segments.json # 对齐分段
└── pages/
└── video_abc123def45.json # page 兼容格式
7.2 完整输出(视觉抽取,5 部视频播放列表)
output/react-complete/
├── SKILL.md
├── references/
│ ├── video_intro-to-react.md
│ ├── video_react-hooks-deep-dive.md
│ ├── video_state-management.md
│ ├── video_react-router.md
│ └── video_testing-react-apps.md
├── video_data/
│ ├── metadata.json
│ ├── transcripts/
│ │ ├── abc123def45.json
│ │ ├── def456ghi78.json
│ │ ├── ghi789jkl01.json
│ │ ├── jkl012mno34.json
│ │ └── mno345pqr67.json
│ ├── segments/
│ │ ├── abc123def45_segments.json
│ │ ├── def456ghi78_segments.json
│ │ ├── ghi789jkl01_segments.json
│ │ ├── jkl012mno34_segments.json
│ │ └── mno345pqr67_segments.json
│ └── frames/
│ ├── abc123def45/
│ │ ├── frame_045.00_terminal.png
│ │ ├── frame_052.30_code.png
│ │ ├── frame_128.00_slide.png
│ │ └── ... (50+ frames)
│ ├── def456ghi78/
│ │ └── ...
│ └── ...
└── pages/
├── video_abc123def45.json
├── video_def456ghi78.json
├── video_ghi789jkl01.json
├── video_jkl012mno34.json
└── video_mno345pqr67.json
7.3 混合源输出(docs + github + video)
output/react-unified/
├── SKILL.md # 汇总全部源头的统一 Skill
├── references/
│ ├── getting_started.md # 来自 docs
│ ├── hooks.md # 来自 docs
│ ├── api_reference.md # 来自 docs
│ ├── architecture.md # 来自 GitHub 分析
│ ├── patterns.md # 来自 GitHub 分析
│ ├── video_react-hooks-tutorial.md # 来自视频
│ ├── video_react-conf-keynote.md # 来自视频
│ └── video_advanced-patterns.md # 来自视频
├── video_data/
│ └── ... (视频专属数据)
├── pages/
│ ├── page_001.json # 来自 docs
│ ├── page_002.json
│ ├── video_abc123def45.json # 来自视频
│ └── video_def456ghi78.json
└── react_data/
└── pages/ # 原始抓取数据
8. 如何在统一 CLI 中触发视频输出
视频输出由 create 命令的视频参数驱动,参数定义集中在 arguments/video.py 与 arguments/create.py:
| 参数 | 说明 |
|---|---|
--url |
视频 URL(YouTube / Vimeo) |
--video-file |
本地视频文件路径 |
--playlist |
播放列表 URL |
--languages |
转录语言偏好(逗号分隔,默认 en) |
--visual |
启用视觉抽取(需 video-full 依赖) |
--whisper-model |
Whisper 模型大小(默认 base) |
--visual-interval |
视觉扫描间隔秒数(默认 0.7) |
--visual-min-gap |
抽取帧之间的最小间隔秒数(默认 0.5) |
--visual-similarity |
重复帧检测的像素差阈值;越小保留越多帧(默认 3.0) |
--vision-ocr |
对低置信度代码帧使用配置的 vision API 兜底 OCR |
--start-time / --end-time |
截取片段(秒、MM:SS 或 HH:MM:SS;仅单视频可用) |
--from-json |
从已抽取的 JSON 构建 Skill(跳过重新抓取) |
--chunk-for-rag |
为 RAG 分块(arguments/common.py) |
--setup |
自动检测 GPU 并安装视觉抽取依赖(PyTorch、easyocr 等) |
校验约束(VideoSourceConfig.validate(),video_models.py):url / playlist / channel / path / directory 五选一且只能选一个;--start-time/--end-time 不能与 --playlist 同时使用;--start-time 必须早于 --end-time。
9. 验证与测试
仓库测试 tests/test_video_scraper.py 覆盖了本文所述输出规范的关键路径:
test_build_skill_from_loaded_data:验证build_skill()之后SKILL.md、references/、video_data/均被创建;test_video_segment_serialization/test_video_info_serialization/test_video_scraper_result_serialization:验证各层 JSON 往返序列化;test_segment_by_chapters/test_segment_by_time_window:验证分段策略(章节优先、时间窗口回退);- 引用文件识别相关测试:确认
references/video_*.md会被识别为视频来源(source: video_tutorial),而api_reference.md等非视频引用不会被误判。
这意味着你可以在自己的环境中运行 python -m pytest tests/test_video_scraper.py(需先按 requirements.txt 安装依赖)来验证视频输出管线的行为与本文描述一致。
10. 小结
- 视频源的 Skill 产物由
SKILL.md(含Video Tutorials章节)、references/video_*.md(每视频一份、按章节组织)、video_data/(元数据 / 转录 / 分段 / 关键帧)与pages/video_*.json(兼容层)四部分组成; metadata.json承载VideoScraperResult全量信息,可通过--from-json断点续建;- 视频分段是 ASR + OCR + 元数据三条流对齐的最小单元,
VideoSegment既是 reference 文件的渲染素材,也是 RAG 分块的自然边界; timestamp_url使视频型 RAG 检索结果可直接跳转到对应时间点;- 播放列表与混合源场景下,视频内容以分组/并列方式与文档、GitHub 内容统一编排,互不冲突。