首页
/ graphify 音视频转写全指南:将视频语料经 Whisper 转为文档并接入知识图谱

graphify 音视频转写全指南:将视频语料经 Whisper 转为文档并接入知识图谱

2026-09-06 19:15:43作者:冯梦姬Eddie

本指南以 graphify 的技能参考文档 transcribe video and audio(即流水线中的 Step 2.5)为核心,系统讲解如何把 .mp4.mov.mp3 等视频与音频文件先转写成文本,再当作普通文档参与后续实体抽取与建图。读完你将掌握:转写环节的触发条件、基于图分析结果(god node 标签)自动生成 Whisper 领域提示词的方法、完整可复现的转写命令、--whisper-model/环境变量的正确用法,以及 graphify/transcribe.py 底层的缓存、推理与错误处理机制。

Step 2.5 在 graphify 流水线中的位置与触发条件

graphify 的默认构建流程由技能主文档 graphify/skill.md 编排,依次经过安装校验(Step 1)、文件检测(Step 2)、音视频转写(Step 2.5)、实体与关系抽取(Step 3)、建图与聚类(Step 4)等环节。Step 2.5 是一个完全条件化的步骤

  • 只有当 Step 2 的 detect 返回了 ≥1 个 video 文件时才执行;
  • 若检测结果为 0 个视频,整步直接跳过,主流程跳入 Step 3;
  • 技能参考文档开头明确写着"Load this only when detect reported one or more video files. A corpus with no video never reads this."——即宿主 Agent 应做到"按需加载"参考文档,无视频语料时连这份参考都不必读取。
Corpus: X files · ~Y words
  code:     N files (.py .ts .go ...)
  docs:     N files (.md .txt ...)
  papers:   N files (.pdf ...)
  images:   N files
  video:    N files (.mp4 .mp3 ...)   ← detect 摘要中的 video 类别

video 类别是如何被识别的

detect 阶段通过文件扩展名把音视频统一归类为 FileType.VIDEO。在 graphify/detect.py 中定义了枚举成员 VIDEO = "video",而扩展名白名单同时出现在 detect 与转写模块中,二者保持严格一致(见 graphify/detect.pygraphify/transcribe.py):

类别 扩展名
视频容器 .mp4 .mov .webm .mkv .avi .m4v
音频文件 .mp3 .wav .m4a .ogg

classify_file() 在扩展名命中该集合时返回 FileType.VIDEO(见 graphify/detect.py)。检测结果随后写入 graphify-out/.graphify_detect.json,其中 files.video 字段即是后续转写的输入清单。

为什么要先转写:音视频无法被直接抽取

视频与音频文件是二进制媒体流,既不能像代码那样走确定性的 AST 抽取(Part A),也不能像 Markdown 那样交给语义子代理直接阅读。因此参考文档给出的核心策略是:

先把它们转写成纯文本(transcript),然后在 Step 3 中把转录文本当作普通文档文件对待

这一步保证了下游的抽取(Extraction)、聚类(Cluster)、查询(Query)对"视频"与"文档"一视同仁——转写前它们是 video 类别,转写后它们以 .txt 形式进入 document 处理管线。

策略核心:让宿主模型自述"一句话领域提示",再交给 Whisper

语音识别系统在给定领域上下文时准确率更高。graphify 的做法很有特色:不额外调用一次外部 LLM 服务,而是让正在执行任务的宿主 Agent(它本身就是语言模型)基于已有图分析结果,用一句话描述语料领域,然后作为 initial_prompt 喂给 Whisper。

提示词素材从哪里来:god node 标签

参考文档建议宿主模型从以下位置读取"顶层 god node 标签":

  • graphify-out/.graphify_detect.json(本次检测结果);
  • graphify-out/.graphify_analysis.json(若存在,来自上一次运行的图分析 sidecar,其中 gods 字段记录了各社区的枢纽节点)。

然后据此写出一句式的领域提示,例如:

detect/分析给出的标签 自动生成的领域提示句
transformer, attention, encoder, decoder "Machine learning research on transformer architectures and attention mechanisms. Use proper punctuation and paragraph breaks."
kubernetes, deployment, pod, helm "DevOps discussion about Kubernetes deployments and Helm charts. Use proper punctuation and paragraph breaks."

两个例子都保留了统一的句子尾缀 Use proper punctuation and paragraph breaks.,提醒 Whisper 输出带标点、有段落分隔的规范文本,便于后续按行/段切分抽取。

纯视频语料的回退

参考文档特别强调一个例外:如果语料中只有视频文件、没有任何其他文档或代码,则没有 god node 可用,应直接使用通用回退提示词:

"Use proper punctuation and paragraph breaks."

这个回退策略与源码中的常量一一对应——graphify/transcribe.py 定义了 _FALLBACK_PROMPT = "Use proper punctuation and paragraph breaks."

源码中的兜底:build_whisper_prompt

当宿主 Agent 不在场(例如纯脚本/CLI 调用)时,模块提供了自动兜底函数 build_whisper_prompt(god_nodes)(见 graphify/transcribe.py),其逻辑顺序为:

  1. god_nodes 为空 → 返回 _FALLBACK_PROMPT
  2. 存在环境变量 GRAPHIFY_WHISPER_PROMPT直接返回该覆盖值(等价于宿主模型已写好领域提示);
  3. 否则取前 10 个 god node 的 label,去空后拼出 Technical discussion about {前 5 个标签}. Use proper punctuation and paragraph breaks.

这解释了参考文档中"必须 export 为 GRAPHIFY_WHISPER_PROMPT,转写器读取的就是这个名字"的指令来源——它是 build_whisper_prompttranscribe() 之间传递定制提示的唯一通道。

实际操作:从环境变量导出到执行转写

Step 1:导出 Whisper 领域提示

将上面写好的领域提示句以环境变量形式交给下一步命令。必须使用 export(而非普通赋值),因为转写发生在独立的 Python 子进程中,未导出的 shell 变量不会被子进程继承:

export GRAPHIFY_WHISPER_PROMPT="<你写好的那句领域提示>"

Step 2:执行批量转写

参考文档给出的完整命令(此处保留原样,仅附加逐段注释):

export GRAPHIFY_WHISPER_MODEL=base   # 或用户在命令行传入的 --whisper-model 值(必须 export)
export GRAPHIFY_WHISPER_PROMPT="<the one-sentence domain hint you composed in Step 1>"
$(cat graphify-out/.graphify_python) -c "
import json, os, sys
from pathlib import Path
from graphify.transcribe import transcribe_all

detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
video_files = detect.get('files', {}).get('video', [])
prompt = os.environ.get('GRAPHIFY_WHISPER_PROMPT', 'Use proper punctuation and paragraph breaks.')

transcript_paths = transcribe_all(video_files, initial_prompt=prompt)
# 由 Python 写 JSON(而不是 shell 的 '>' 重定向):transcribe_all/Whisper
# 会向 stdout 打印进度,重定向会污染 JSON 文件 (#1392)。
Path('graphify-out/.graphify_transcripts.json').write_text(json.dumps(transcript_paths, ensure_ascii=False), encoding=\"utf-8\")
print(f'Transcribed {len(transcript_paths)} file(s)', file=sys.stderr)
"

命令逐段拆解

片段 作用
export GRAPHIFY_WHISPER_MODEL=base 指定 Whisper 模型。参考文档默认 base;若用户传入 --whisper-model <name>,则改为对应名称,且同样需要 export。
$(cat graphify-out/.graphify_python) 展开为 Skill Step 1 探测并写入的正确 Python 解释器路径(兼容 uv tool、pipx、venv、系统安装),保证 import graphify 命中的与安装时一致。
detect.get('files', {}).get('video', []) 从 Step 2 写入的检测 sidecar 读取待转写文件清单。
os.environ.get('GRAPHIFY_WHISPER_PROMPT', …) 读取 Step 1 导出的领域提示;缺失时回退到通用提示词。
transcribe_all(video_files, initial_prompt=prompt) 对每个视频/音频文件执行转写,返回转录 .txt 路径列表。
用 Python write_text 写 JSON 关键实现细节:Whisper/转写器会向 stdout 打印进度,若改用 shell > 重定向,这些输出会混入 JSON 导致解析失败。测试 test_transcribe_force_reruns 依赖转写器 stdout 行为,此设计在源码与参考中均有注释(#1392)。
print(..., file=sys.stderr) 统计信息显式走 stderr,避免污染任何重定向到 stdout 的结果文件。

转写完成后:把转录文档接入 Step 3 语义抽取

transcribe_all() 结束后,宿主 Agent 需要完成以下收尾动作(参考文档原文要求):

  1. graphify-out/.graphify_transcripts.json 读回转录文件路径(该 JSON 的内容是 list[str],每个元素是一个 .txt 绝对或相对路径);
  2. 将这些路径追加进文档列表,再分发 Step 3B 的语义子代理;
  3. 在输出中打印转写统计:Transcribed N video file(s) -> treating as docs
  4. 容错要求:若单个文件转写失败,打印一条 warning 并继续处理其余文件,绝不整体中断。

这与源码行为一致:transcribe_all()(见 graphify/transcribe.py)对每个文件用 try/except 包裹 transcribe(),任何异常仅打印 warning: could not transcribe {vf}: {exc} 并继续;空输入直接返回 []

转录文本实际落在 graphify-out/transcripts/ 目录(默认输出根来自 graphify/paths.pyout_path(),可被 GRAPHIFY_OUT 环境变量整体迁移),命名规则为 原文件名去除扩展名 + ".txt"

Whisper 模型选择与依赖安装

支持的命令行参数

技能主文档的 Usage 区块展示了端到端入口:

/graphify <path> --whisper-model medium   # use a larger Whisper model for better transcription accuracy

当用户带 --whisper-model 运行时,宿主 Agent 必须把它转换成 GRAPHIFY_WHISPER_MODEL 环境变量并 export,供 _model_name() 读取(见 graphify/transcribe.py):

def _model_name() -> str:
    return os.environ.get("GRAPHIFY_WHISPER_MODEL", _DEFAULT_MODEL)  # _DEFAULT_MODEL = "base"

也就是说模型名的优先级是:环境变量 GRAPHIFY_WHISPER_MODEL > 内置默认 base。参考文档与 skill.md 均以 base 为默认、以 medium 作为"更高精度"的示例,说明该参数接受的是 Whisper/faster-whisper 的模型规格名(tiny/base/small/medium/large 等),具体可用集合取决于已安装的 faster-whisper 版本。

依赖安装(按需 optional extra)

转写相关依赖属于可选特性,见 pyproject.toml[project.optional-dependencies]video extra:

video = ["faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9"]
  • faster-whisper:负责本地 Whisper 推理,且声明 python_version >= '3.11',即低版本 Python 下无法启用本特性;
  • yt-dlp:负责从 YouTube 等 URL 下载音频流。

源码采用懒加载策略(见 graphify/transcribe.py):仅当真正遇到视频文件、transcribe()/download_audio() 被调用时才 import,缺依赖时抛出带安装指引的 ImportError

Video transcription requires faster-whisper. Run: pip install 'graphifyy[video]'
YouTube/URL download requires yt-dlp. Run: pip install 'graphifyy[video]'

因此一个"纯代码语料"的常规 /graphify . 即使没装任何 extra 也完全不受影响。测试 tests/test_transcribe.py 专门验证了 faster-whisper 缺失时该异常会正确向上抛出。

源码级原理:transcribe() 的一次完整调用

核心函数 transcribe()(见 graphify/transcribe.py)的执行链如下:

transcribe(path, output_dir=None, initial_prompt=None, force=False)
  ├─ 1. 确定输出目录:默认 GRAPHIFY_OUT/transcripts,自动 mkdir
  ├─ 2. 若是 URL 前缀(http:// https:// www.)→ 先 download_audio() 下载音频
  ├─ 3. 命中缓存:transcript 已存在且未 force → 直接返回缓存路径(不推理)
  ├─ 4. 懒加载 faster-whisper → WhisperModel(model, device="cpu", compute_type="int8")
  ├─ 5. model.transcribe(audio, beam_size=5, initial_prompt=prompt)
  ├─ 6. 逐 segment 去空行拼接为文本 → 写 <stem>.txt (UTF-8)
  └─ 7. 打印 lang / segments 信息,返回 transcript 路径

值得注意的工程细节:

  • 缓存机制:转录路径以音频文件 stem 命名。已存在的转录直接复用,force=True 才强制重转。测试 tests/test_transcribe.pytests/test_transcribe.py 分别覆盖了"命中缓存跳过推理"与"force 强制重跑"两条路径——后者通过 mock _get_whisper 验证写入的是新内容。
  • 本地 CPU 推理WhisperModel(model_name, device="cpu", compute_type="int8") 说明开箱即为 CPU + INT8 量化推理,无需 GPU;beam_size=5 是转录解码参数。
  • 语言自检:转录完成后从 info.language 读取检测到的语言并打印,便于人工核验。
  • 容错:单文件异常在 transcribe_all() 层被捕获并降级为 warning。

对 URL 语料的支持:download_audio

除本地文件外,参考文档未展开但源码完整支持 URL 语料transcribe() 依据前缀(http://https://www.,见 graphify/transcribe.py)判定输入是否为 URL,是则调用 download_audio()(见 graphify/transcribe.py):

  1. 先经 graphify/security.pyvalidate_url() 校验,拦截私有 IP、非法 scheme 等目标后才交给 yt-dlp(这是 graphify 运行于任意克隆/共享语料时的安全基线);
  2. 用 URL 的 SHA-1 前 12 位生成稳定文件名 yt_<hash>.<ext>,避免 yt-dlp 使用不可控的视频标题作文件名;
  3. 检查缓存目录中是否已有 .m4a/.opus/.mp3/.ogg/.wav/.webm 同名音频,命中则复用("cached audio"),否则以 bestaudio 格式下载。

该函数同样被 URL 摄入链路复用——在 graphify/ingest.py 中,当 /graphify add <url> 遇到 youtube 类型 URL 时即调用 download_audio() 将音频存入语料,随后进入同样的检测→转写流程。

可靠性保障:参考文档的版本化与测试覆盖

  • 多平台技能分发:这份 transcribe.md 参考文档会随 skillgen 渲染进各 Agent 平台的 skill 包中;tools/skillgen/expected/graphify__skills__agents__references__transcribe.md 正是该参考在 agents 平台下的期望产物快照,用于测试多平台安装一致性(见 tests/test_install_references.py)。
  • 单测覆盖关键契约tests/test_transcribe.py 验证了以下与本文档强相关的行为——扩展名集合的构成、领域提示的三种生成路径(空节点回退 / GRAPHIFY_WHISPER_PROMPT 覆盖 / 标签拼接)、transcribe_all 的空输入、缓存命中与 force 语义、失败文件跳过不中断。这些测试共同构成了"参考文档承诺、源码实现、测试锁定"的闭环。

小结:视频语料进图的完整链路

把零散音视频变成可查询知识图谱,本质是一条"降维"链路:

  1. detect 识别 .mp4/.mov/.mp3/.wav… 等为 video 类别并写入 graphify-out/.graphify_detect.json
  2. Step 1(宿主 Agent)依据 god node 标签自撰一句领域提示并 export GRAPHIFY_WHISPER_PROMPT,同时 export GRAPHIFY_WHISPER_MODEL(默认 base);
  3. Step 2$(cat graphify-out/.graphify_python) 启动 Python,调用 graphify.transcribe.transcribe_all() 批量转写,结果 .txt 落盘 graphify-out/transcripts/,路径清单由 Python 写入 graphify-out/.graphify_transcripts.json
  4. 宿主 Agent 将转录路径并入文档清单,交给 Step 3 语义抽取,随后与代码的 AST 抽取结果合并、聚类、建图、生成报告。

全程无需额外 LLM API 调用(提示词由宿主模型自产),依赖项按需安装(graphifyy[video]),默认 CPU/INT8 即可运行,并以缓存、按文件容错与输出 JSON 的写入纪律保证可重复性与可审计性——这正是 graphify 把"任何语料"(code、docs、papers、images、videos)统一纳入知识图谱的关键一环。

登录后查看全文
热门项目推荐
相关项目推荐