Skip to content
Projects

LINJIANG12/video2book has 41 GitHub stars and 3 forks. GitHot measured +1 stars in 24 hours. Last checked 2026-11-11 12:54 UTC. Last push 2026-11-03.

How these counts are measured

把 B 站长视频和系列网课,一键变成能直接当教材读的深度长文与备考笔记

41 stars3 forks +1 in 24h

Measured star growth

in 24h
+1
in 7 days
Collecting history
in 15 days
Collecting history
in 1 month
Collecting history

Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.

README.en.md

Original

Video2Book

Turn Bilibili, YouTube and Douyin long videos, plus local course media, into per-block deep-dive textbooks, compiled modular books and mindmap review notes — transcripts first from subtitles.
Two-stage pipeline · Plan-first · Subtitle-first · Listen only for gaps · Three deliverable tracks · Pre-delivery machine gates · Python 3.10+ · Unified multi-platform media engine

Quick Start License: MIT

Runtime: Python 3.10 or later System dependency: ffmpeg

Listening channel: Model Context Protocol Host: Claude Code Host: Codex Host: OpenCode

简体中文 · English

Give it a course URL or a directory, and it first cuts the course into 40–60 minute knowledge blocks from episode durations, takes block transcripts from subtitles when available (only blocks without subtitles fall back to audio transcription), then writes one module article per block and compiles them into modular textbooks and review notes.

[!CAUTION] This tool batch-fetches Bilibili video metadata and audio streams, and can store your login credential. Use it only on content you are entitled to access, and comply with Bilibili's terms of service and applicable law. SESSDATA grants access to your account: do not copy, upload or share it.

Table of Contents

Overview

Video2Book is a skill for AI coding assistants that turns a course into a textbook. It accepts a Bilibili collection, a YouTube channel or playlist, a Douyin collection or a local course directory, writes one deep-dive article per block (a block covers consecutive episodes), and consolidates those articles into a modular book and mindmap review notes.

The hard part of a long course is that you cannot finish listening to it, let alone remember it. This skill first cuts the course into 40–60 minute knowledge blocks from the course metadata (oversized episodes are split into upper/lower halves); the block plan is written to block_plan.json at the workspace root, and the planning stage downloads no audio at all. Block transcripts prefer Bilibili Chinese subtitles: a block with complete subtitles never downloads a byte of audio, and only subtitle-less blocks have their audio materialized on the existing plan and handed to a listening channel. Writer roles read the block transcript to write one module article per block — they never touch audio. Per-episode splitting is an optional after-the-fact lookup, not part of the main chain. The number of audio-reading calls therefore drops from "one per episode" to "one per block", and to zero when the whole course has subtitles (measured: 936 episodes of 9 courses → 381 blocks, a 2.46× reduction).

Output comes in three tracks, each in its own directory and usable on its own: per-episode articles, compiled modular textbooks, and cross-module review notes. Every deliverable passes a machine gate before delivery — filler prose, hollow headings and per-episode flat headings get caught by scripts rather than by you while reading.

Subtitle-first blocks are written directly; blocks without subtitles fall back to listening. A listening channel is only needed when that fallback actually happens, and it is delivered by the companion repository omni-media: use its read_audio (zero credentials) when the host has a native audio modality, or its read_media (delegated to an external model) when the host is text-only — both are channels of the same package. A course with complete subtitles never touches audio and therefore never needs it; if a block genuinely lacks subtitles and no listening channel is mounted, the pipeline stops to ask you to mount one. Once installed, one command runs an entire course.

Back to top

Preview

The three deliverable tracks land in separate directories and never overwrite each other:

output/<course_workspace>/
├── articles/      模块XX_<block title>_精读长文.md  # module article (one per block)
├── textbooks/     模块01_<topic>_精读全书.md   # textbook (volume = book, chapter = block; named by content)
├── notes/         笔记XX_<theme>_笔记.md       # cross-module review note
├── subtitles/     BLKxx_*_逐字稿.md           # block transcript (sole source of truth)
├── block_plan.json                          # v4 logical block plan (sole source of block boundaries)
├── audio/         PXX_*.m4a                  # 16 kHz mono audio slices
├── parts.json                               # episode topology (Stage 2 numbering basis)
└── manifest.json                            # ledger (rebuildable from disk via sync)

A review note opens directly with an H1 heading for the theme, and concept blocks adopt a high-density, multi-level indented skeleton (excerpt from a real artifact):

# 计算机网络体系结构与物理层

## 网络体系结构与分层模型

### 协议与分层体系

* **计算机网络协议(Network Protocol)**
    * > **定义**:为进行网络中的数据交换而建立的规则、标准或约定的集合。
    * 三要素:语法(数据与控制信息的结构形式)、语义(发出何种控制信息、完成何种动作与做出何种响应)、同步(事件实现顺序的详细说明)。
    * 职能范围:控制网络中各层实体之间的通信交互与差错控制。

* **开放系统互连参考模型(Open Systems Interconnection,OSI/RM)**
    * > **定义**:由 ISO 制定的七层国际标准化网络体系结构模型。
    * 层级划分:物理层 → 数据链路层 → 网络层 → 传输层 → 会话层 → 表示层 → 应用层。

The default reading environment for all three tracks is Typora, with VS Code Markmap, XMind import and plain-text viewing also supported. Inline math requires "Inline Math" to be enabled under Typora's Preferences → Markdown, otherwise $…$ renders as raw source.

Real artifacts: the companion repository video2book-courses archives the complete deliverables of running the full pipeline across dozens of courses (including modular articles, textbooks, review notes and transcripts; see the repository's latest index). Read there first if you want to judge the output quality before installing.

Back to top

Quick Start

Prerequisites

python --version   # 3.10 or later
ffmpeg -version    # on PATH

Install

[!IMPORTANT] Step 3, the listening channel, is only needed for the subtitle-gap fallback: when a Bilibili course has complete subtitles, Stage 1 downloads no audio and calls no listening channel. Both services come from the companion repository omni-media; pick one. If a block genuinely lacks subtitles and nothing is mounted, the pipeline stops to ask you to mount one.

# 1) The skill itself: copy this one directory
cp -r skills/video2book ~/.claude/skills/            # Claude Code
cp -r skills/video2book ~/.codex/skills/             # Codex
cp -r skills/video2book ~/.config/opencode/skills/   # OpenCode

# 2) Optional: install the CLI (then `video2book` replaces `python src/cli.py`)
pip install -e .

# 3) Listening channel (only required as the fallback for blocks without subtitles): both services live in the omni-media repo
cd .. && git clone https://github.com/LINJIANG12/omni-media.git

#    Channel A: host has a native audio modality (read_audio in its tool list), zero credentials
cd omni-media && pip install -e .
omni-media status                    # diagnose system deps, host mounts and real config paths
omni-media apply --target codex      # mount to the host; valid values come from live `status` output
#    this entry pins --mode native and exposes read_audio only

#    Channel B: host is text-only (only read_media) — use the other entry instead
#    same package, no reinstall; only the registration name and its pinned --mode ext differ
omni-media config init               # generate config.json, fill in endpoint and api_key
omni-media-ext status
omni-media-ext apply --target codex

Run

Run inside the skill directory (the one holding SKILL.md); --article-type is required:

python src/cli.py pipeline "https://www.bilibili.com/video/BV14VqVBrEhc" --all --article-type learning

Deliverables land in <products_root>/<course_workspace>/: articles in articles/, modular textbooks in textbooks/, notes in notes/.

Back to top

How It Works

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '14px'}}}%%
flowchart TD
    A[Multi-platform ingestion<br/>Bilibili · YouTube · Douyin · local] --> B[Course metadata<br/>episodes + durations → parts.json]
    B --> M[Plan-first packing<br/>block_plan.json (no audio yet)]
    M --> S{Bilibili subtitles complete?}
    S -->|complete block| T[Write the block transcript<br/>no download, no listening]
    S -->|subtitle-less block| N[Materialize only those blocks<br/>FFmpeg 16 kHz mono]
    N --> C{Course duration ≤ 60 min}
    C -->|Yes| D[Main agent transcribes serially]
    C -->|No| E[Dispatch transcriber roles<br/>block by block]
    D --> F[Block listening transcription<br/>read_audio / read_media]
    E --> F
    T --> W[Writer roles read block transcripts<br/>one article per block]
    F --> W
    W --> G[Module articles<br/>articles/模块XX_*_精读长文.md]
    G --> H[Stage 2 two-pass aggregation<br/>module plan → note merge]
    H --> I[Modular textbooks textbooks/<br/>review notes notes/]

    classDef start fill:#3B82F6,stroke:#2563EB,color:#fff,stroke-width:2px
    classDef process fill:#10B981,stroke:#059669,color:#fff,stroke-width:2px
    classDef decision fill:#F59E0B,stroke:#D97706,color:#fff,stroke-width:2px
    classDef data fill:#8B5CF6,stroke:#7C3AED,color:#fff,stroke-width:2px

    class A start
    class B,M,T,N,D,E,F,W,H process
    class S,C decision
    class G,I data
  • Audio is read only by the transcriber roles, only for subtitle-less blocks: The logical block plan is computed from metadata into block_plan.json; blocks with complete subtitles produce their transcript directly, and only the remaining blocks fetch audio under audio/blocks/ on that same locked plan — never re-planned. Writer roles read block transcripts only and never touch audio.
  • Physical audio is recycled once every deliverable exists: the per-episode source audio and the block-level concatenations are the only bulky intermediates (a 145-episode course is about 476 MB, and each block file is merely a concat copy of its episodes), while no transcript ever references an audio path. pipeline deletes them at the close-out step only when all four conditions hold — every block transcript complete, every module article written, notes merged, textbooks compiled; if any is missing it skips and prints what is missing. --keep-audio opts out, and the ledger lands in manifest.json under audio_purge.
  • Dispatch thresholds live in src/core/budget.py: under 60 minutes total the main agent handles work serially; over 60 minutes it must dispatch — three transcriber roles consuming the block queue, plus writer roles (one sub-agent per block, one module article per block). The window fallback applies only to transcriber roles on Channel A: a block whose computed audio tokens exceed 60% of the context window must be read in continuation chunks.
  • Module level needs no plan, and note merging never stalls: a block is the knowledge module (audio is packed into 40–60 minute blocks, each block titled by semantically combining its episodes' names), so textbooks simply compile block articles in block order; notes merge blocks into a number of notes (note_plan.json, one note may span several blocks). Unknown or duplicate block claims are rescued in place (first-come-wins, orphan blocks get fallback notes), the command always exits normally, and the on-disk note_plan.json is never overwritten by fallback results.
  • Stage 1 and Stage 2 are decoupled by content boundaries, so a long course can resume from a breakpoint.
  • The tool layer only prepares task files, dispatch payloads and gates; writing the articles and notes is done by the host agent (usually sub-agents). "Who wrote it" and "did it really listen" are discipline clauses the tool layer cannot verify.

Back to top

Usage

Quick examples (run them from the skill directory; --article-type is mandatory):

# Process an entire Bilibili course collection
python src/cli.py pipeline "https://www.bilibili.com/video/BV14VqVBrEhc" --all --article-type learning

# Process a local course directory
python src/cli.py pipeline "D:\courses\software_engineering" --all --article-type learning

For everything else see the CLI cookbook (the single source for CLI detail):

  • Per platform / per range fetching: Bilibili collections and legacy collections where every episode is its own BV, YouTube channels, Douyin creator collections, --page / --range;
  • Dispatch queue and stage gate: queue_tracker.py payloads for the transcriber / writer / note side, plus one-line status;
  • Textbooks and notes: cluster-articles / cluster-notes (with --force);
  • Quality checks and close-out: the unified check gate (--stage1 release, judged by two-layer entity coverage over English identifiers and Chinese terminology / --deliver audit / --fix-numbering heading cleanup, report-only unless --apply is passed), plus audit-names (lists English proper nouns that cannot be found in the transcripts), cleanup and sync (already run automatically).

Five note-quality checks are fatal and fail the delivery outright: boilerplate filler, hollow headings, per-episode headings, inline quote fragments and episode voice. Sentence truncation and missing structure are advisory; add --require-structure to gate on them. Rendering fatals are GitHub alert blocks, bare ASCII art outside fences and fence pairing; a missing fence language tag is advisory unless you pass --require-lang.

Back to top

Requirements

  • Python — 3.10 or later, from requires-python in pyproject.toml
  • Python dependencies — yt-dlp >= 2024.0.0, requests >= 2.28.0, see pyproject.toml
  • External program — ffmpeg, on PATH, a hard prerequisite only when subtitle-less blocks have to be extracted and sliced (a course with complete subtitles downloads no audio and never needs it); ffprobe is optional (falls back to ffmpeg -i for duration parsing)
  • Listening channel — either read_audio or read_media, provided by the companion repository omni-media (can be substituted by subtitles on Bilibili)
  • Operating system — OS-independent, see the classifiers in pyproject.toml

Back to top

Configuration

Environment variables

Variable Description Default Required
BVB_OUTPUT_DIR Products root; a relative value resolves against the working directory (use an absolute path to pin it) <working_dir>/output by default; <container_root>/output when working inside that container No
BVB_HOME Container root, the common parent of skill/, omni-media/ and output/ Located via the .bvb-home marker; absent when there is no marker — and that does not affect usability No
BVB_AUDIO_TOKENS_PER_SEC Audio token factor; set around 100 for the OpenAI input_audio scale 32 No
BVB_CONTEXT_WINDOW_TOKENS Context window budget 1000000 No
OMNI_MEDIA_DIR Override for the listening service repository directory (legacy OMNI_MEDIA_MCP_DIR still accepted) <container_root>/omni-media No
BVB_DEBUG Set to 1 to re-raise stack traces verbatim unset No

Environment variables must be set before the process starts. A single run can also switch the products root with --base-dir <path>; command-line arguments take precedence over environment variables.

The container root is optional. With nothing configured, products land in output/ under the working directory you run commands from — install it anywhere and work there. Only when a container marker exists (a .bvb-home in an ancestor directory, or BVB_HOME) and you are working inside that container does the products root become <container_root>/output. The listening channel needs no configuration either: the agent decides between read_audio and read_media from its own tool list at use time, wherever the MCP service is installed.

Credentials

When processing Bilibili collections, providing SESSDATA is recommended for stable concurrent fetching and to avoid 412 rate limiting. To obtain it: log in at bilibili.com → F12 → Application → Cookies → https://www.bilibili.com → copy the SESSDATA value.

python src/cli.py login --sessdata "<SESSDATA>"   # persist (required argument, not interactive)
python src/cli.py info                            # show credential source and masked fingerprint
python src/cli.py logout                          # revoke the stored copy

The command-line --sessdata always takes precedence over the local store. Local media and non-Bilibili tasks skip this automatically.

Back to top

Project Structure

skill/
├── skills/video2book/          # the skill itself; this is the only directory you install
│   ├── SKILL.md                # skill contract, the single source of truth for the Agent
│   ├── src/                    # toolchain
│   │   ├── cli.py              # entry point: 10 subcommands
│   │   ├── core/               # paths, audio budget, pipeline, fetching, deliverable lint
│   │   │   └── ingestion/      # unified media engine (Bilibili / local / YouTube / Douyin)
│   │   └── generator/          # task files, prompt templates and semantic aggregation
│   ├── scripts/                # dispatch queue, selfcheck and no-install runner
│   ├── tools/                  # batch dispatch scripts (v2b_dispatch / v2b_env / v2b_prompt)
│   └── references/             # install guide, delivery matrix, CLI cookbook, host tool maps
├── agents/                     # skill metadata for the generic agents side
├── .claude-plugin/             # Claude Code plugin manifest
├── .codex-plugin/              # Codex plugin manifest
├── .opencode/                  # OpenCode install notes
├── AGENTS.md / CLAUDE.md       # repository-level entry notes auto-loaded per platform
└── pyproject.toml              # package metadata and CLI entry points

Three domains are kept isolated: the code root (this repository), the container home root (optional), and the products root. By default the products root is output/ under the working directory; when you work inside that container it is <container_root>/output — either way it stays out of the code directory. If you install the skill inside a git repository and do not want products committed, add output/ to that repository's .gitignore.

Back to top

Commands

Three equivalent entry points with identical behaviour:

  • Repository-preferred: python src/cli.py <subcommand>
  • No-install script: python scripts/run.py <subcommand> (runs from any working directory)
  • System command: video2book <subcommand> (available after pip install -e .)

The four most common examples (--article-type is mandatory; missing it exits with code 4):

python src/cli.py pipeline "https://www.bilibili.com/video/BV14VqVBrEhc" --all --article-type learning
python scripts/queue_tracker.py --next-module 5 --json    # writer payloads (task file / transcript / target path)
python src/cli.py cluster-notes "<url or local path>"     # block → note aggregation, export note task files
python src/cli.py check --deliver --strict                 # pre-delivery check (note quality + render)

All 10 subcommands, every flag, the full argument list, per-scenario examples and the exit codes (0–4) live in the CLI cookbook — the single source for CLI detail; this README no longer duplicates them.

Back to top

Tech Stack

Runtime

  • Python 3.10 or later — the only runtime; the Bilibili and local chains need the standard library (urllib) only
  • setuptools — build backend, see pyproject.toml

External dependencies

  • FFmpeg — audio extraction and slicing, 16 kHz mono (system program, must be on PATH)
  • yt-dlp / requests — declared in pyproject.toml; imported on demand by the YouTube chain (yt_dlp in youtube.py) and the Douyin chain (requests in dyaudio). Those two chains cannot run without them.

Ingestion and listening

  • Multi-platform media engine — src/core/ingestion/, registering unified providers for Bilibili / local / YouTube / Douyin behind an IngestionCoordinator
  • MCP — the protocol used by the host's native listening path (read_audio / read_media)

Back to top

FAQ

Can I use it without omni-media?

No. A Bilibili course with complete subtitles can produce transcripts directly through pipeline; blocks without subtitles still need a listening channel. Both read_audio and read_media come from omni-media; when a subtitle-missing block cannot be materialized, the pipeline stops and asks you to mount one.

Which entry should I mount?

Prioritize direct-to-disk writers. Prefer mounting the omni-media-ext entry (pinned --mode ext, calling read_media which supports output_file direct-to-disk writing with 0 tokens in context, preventing summary pollution). If read_media is not available or the host has native audio modalities and prefers zero credentials, mount the omni-media entry (pinned --mode native, using read_audio to fetch slices). Both are channels of the same package, so you install once and switch by registration name. They share the same pagination contract.

The listening channel is installed but no transcript comes back

First check which layer the error came from. If the tool reports that the error came from the endpoint's upstream, the MCP service and the gateway process are both fine — the gateway itself cannot obtain upstream credentials, or the upstream is unreachable. omni-media status cannot detect this, because it only performs local diagnostics (Python / ffmpeg, config file and host mount state) and issues no transcription request. Check that your local proxy/accelerator is running and can reach the upstream, and do not change endpoint settings such as /audio/transcriptions or model — rerunning will not help either.

If instead the error says the transcription endpoint returned the model's own outline/plan N times in a row, that is an occasional upstream-model behaviour: the service already re-reads within max_retries. If it still fails, retry the slice or re-read it with a smaller duration_minutes.

Why is --article-type mandatory?

The article style is confirmed by the user. Two prompts are provided: learning (recommended) and legacy. Four further shapes — consulting, interview, review and livestream — are registered but have no prompts. When the type is missing, unspellable, or maps to a shape without a prompt, the command prints the style menu and exits with code 4, writing no task file.

Will an imperfect Stage 2 plan stall the pipeline?

No. Merging never stalls: claims referencing unknown blocks are dropped, duplicate claims are first-come-wins, and unclaimed blocks each get a fallback note — the command always exits normally, and the on-disk note_plan.json is never overwritten by fallback results. Re-running after filling in the plan replaces them automatically.

How do I confirm channels and products?

python src/cli.py info prints the readiness of Python / ffmpeg / ffprobe, both listening channels and the three domain paths. omni-media status diagnoses system dependencies, host mounts and real config paths. python scripts/queue_tracker.py --summary reports Stage 1 completeness and dispatch advice.

Do credentials end up in version control?

No. This repository excludes the Bilibili SESSDATA, and omni-media excludes its config.json (which holds api_key), committing only the template. Channel A needs no credentials at all.

Back to top

Security

  • Credential storage: login writes the Bilibili SESSDATA in plaintext to .sessdata.json under the products root, a path already excluded by ignore rules, so it does not enter version control.
  • Revocation: if you suspect a leak, log out of Bilibili to invalidate the value, then run logout to clear the local copy.
  • Access scope: the tool only reads public Bilibili video metadata and audio streams, plus the local media files you point it at.
  • Reporting: this repository has no SECURITY.md yet; report security issues via a repository issue.

Back to top

License

MIT

Back to top

Explore related projects