SSkilltecabyclaudinhocode
Enviar skill
← Voltar para o catálogo

lark-video2note

Produtividade

把抖音 / 哔哩哔哩 / 小红书 / YouTube 等平台的视频或音频链接,转成飞书妙记 + 飞书文档归档。当用户**明确要求**把视频转成笔记 / 文稿 / 总结 / 逐字稿 / 归档 / 留档,或显式使用 /video2note 时使用。替代 Get 笔记的能力。⚠️ 仅看到 URL 而无明确意图时,先按全局 CLAUDE.md「URL 链接处理」规则做 triage,不要直接触发本 skill。

2estrelas
Ver no GitHub ↗Autor: teemowengLicença: MIT

视频链接 → 飞书妙记 + 飞书文档

前置条件:先读 ../lark-shared/SKILL.md。 已经登录过 lark-cli,且 scope 至少包含: drive:drivedocs:documentminutes:minutes:readonlyminutes:minutes.artifacts:readminutes:minutes.transcript:export。 缺 scope 时按 lark-shared 的提示让用户授权后再继续。

触发场景

仅在以下两种情况触发本 skill:

  1. 用户明确表达要把视频转成笔记:消息里包含视频 URL(或分享口令,如「2.33 复制打开抖音…https://v.douyin.com/xxx/」)**且**伴随明确动词——"转成笔记"、"留档"、"归档"、"整理这个视频"、"转成逐字稿/文稿"、"沉淀一下"、"帮我看里面讲了啥" 等
  2. 显式触发:用户使用 /video2note <url>$video2note <url>

⚠️ 仅看到 URL 而无明确意图时不要触发。按全局 CLAUDE.md「URL 链接处理」规则先做 triage——看一眼是什么内容,问用户想做什么,再决定是否调用本 skill。

配置

skill 的归档目标飞书云盘文件夹 token 存在 config.json 里(首次使用前请运行 bash scripts/setup.sh)。

Step 0 时务必先读取:

SKILL_ROOT=~/.claude/skills/lark-video2note
[ -f "$SKILL_ROOT/config.json" ] || { echo "缺少 config.json,请先跑 bash $SKILL_ROOT/scripts/setup.sh" >&2; exit 1; }
FOLDER_TOKEN=$(python3 -c "import json;print(json.load(open('$SKILL_ROOT/config.json'))['folder_token'])")

之后所有 lark-cli drive +upload --folder-tokenlark-cli docs +create --parent-token 都用 $FOLDER_TOKEN 而不是写死的字符串。

  • 工作目录:/tmp/video2note/<timestamp>/(每次新建,跑完保留 mp4 直到归档完成)

工具

平台下载方式逐字稿来源
抖音iesdouyin.com/share/video/<id>/ 解析 play_addr妙记 ASR
B站yt-dlp优先平台字幕(默认带 Chrome cookies,B站字幕需登录态)→ 妙记兜底
YouTubeyt-dlp优先 auto-captions(默认带 Chrome cookies)→ 妙记兜底
小红书yt-dlp --cookies-from-browser chrome妙记 ASR

两个核心脚本

scripts/download.sh:下载视频 mp4。输入 URL + 输出目录,输出一行 JSON:

{"file":"...","title":"...","platform":"douyin","author":"...","duration_s":485,"source_url":"...","size_bytes":...}

scripts/get-captions.sh:尝试从 B站 / YouTube 抓现成字幕。成功输出:

{"transcript_file":"$WORK/transcript.txt","language":"zh-CN","lines":243}

失败 exit 1(适用任何没字幕的视频或非 B站/YT 平台)。

流程

# Step 0: 加载配置 + 准备工作目录(静默初始化,正常时不向 stdout 输出)
SKILL_ROOT=~/.claude/skills/lark-video2note
[ -f "$SKILL_ROOT/config.json" ] || {
  echo "❌ 缺少 config.json,请先运行:bash $SKILL_ROOT/scripts/setup.sh" >&2; exit 1;
}
FOLDER_TOKEN=$(python3 -c "import json;print(json.load(open('$SKILL_ROOT/config.json'))['folder_token'])")
TS=$(date +%s)
WORK="/tmp/video2note/$TS"
mkdir -p "$WORK"

# 把状态写到约定路径,后续 Step 用 `WORK=$(cat /tmp/video2note_current_work)` /
# `FOLDER_TOKEN=$(cat /tmp/video2note_current_folder)` 读回。每个 Bash 工具调用
# 是独立 shell 进程,环境变量不持续——只能通过文件或 inline 重新赋值传递。
# ⚠️ 不要 `echo "WORK=$WORK"` 当回显——空载 init 应该完全安静,输出留给真有事时。
echo "$WORK" > /tmp/video2note_current_work
echo "$FOLDER_TOKEN" > /tmp/video2note_current_folder

# Step 1: 下载视频(自动识别平台)
# 关键:捕获 exit code
set +e
META=$(bash ~/.claude/skills/lark-video2note/scripts/download.sh "<URL_OR_SHARE_TEXT>" "$WORK" 2>"$WORK/.dl-err")
DL_EXIT=$?
set -e

# Exit code 2 = "这不是视频"。立即停止本 skill 流程,路由到 defuddle
if [ $DL_EXIT -eq 2 ]; then
  # 把 .dl-err 里 NOT_A_VIDEO 那段原样转给用户,并主动建议改走 defuddle
  # 不要继续 Step 2-4,不要上传、不要建妙记、不要建 docx
  cat "$WORK/.dl-err" >&2
  # 然后用 defuddle 抓那条 URL 的文本内容,按用户原意做总结/归档
  exit 0
fi

# 其它非零 exit 是真失败(网络/cookies/格式),按"失败回退"小节处理
# ⚠️ 不要写 `[ $DL_EXIT -ne 0 ] && { ... }` 形式——当 DL_EXIT=0 时 test 退 1,
#    会变成整个 Bash 工具调用的退出码,触发假阳性 "Error: Exit code 1"。
#    用 if 块包起来,避免把判断结果当退出码。
if [ $DL_EXIT -ne 0 ]; then
  cat "$WORK/.dl-err" >&2
  exit $DL_EXIT
fi

# 解析 META:file / title / platform / author / duration_s / source_url

# Step 2: 拿逐字稿 —— 先试平台字幕,没有再走妙记
# 关键判断:抖音 / 小红书直接跳过,进 Step 2b。B站 / YouTube 先试 Step 2a
PLATFORM=$(echo "$META" | python3 -c 'import sys,json;print(json.load(sys.stdin)["platform"])')

# Step 2a: 尝试平台字幕(仅 B站 / YouTube 有意义)
TRANSCRIPT_FILE=""
MINUTE_URL=""
if [ "$PLATFORM" = "bilibili" ] || [ "$PLATFORM" = "youtube" ]; then
  if CAPTIONS=$(bash ~/.claude/skills/lark-video2note/scripts/get-captions.sh "<URL>" "$WORK" 2>/dev/null); then
    TRANSCRIPT_FILE=$(echo "$CAPTIONS" | python3 -c 'import sys,json;print(json.load(sys.stdin)["transcript_file"])')
    # 拿到字幕 → 跳过妙记,直奔 Step 3
  fi
fi

# Step 2b: 没拿到字幕 → 走完整妙记 ASR 流程(上传云盘 → 生成妙记 → 拉产物)
if [ -z "$TRANSCRIPT_FILE" ]; then
  # 2b-i: 上传到飞书云盘 视频笔记 文件夹
  # 注意:drive +upload 要求 --file 是相对于 CWD 的相对路径
  cd "$WORK"
  FILE_BASENAME=$(basename "$(echo "$META" | python3 -c 'import sys,json;print(json.load(sys.stdin)["file"])')")
  TITLE=$(echo "$META" | python3 -c 'import sys,json;print(json.load(sys.stdin)["title"])')
  lark-cli drive +upload \
    --file "./$FILE_BASENAME" \
    --folder-token $FOLDER_TOKEN \
    --name "$TITLE.mp4"
  # → 记录 data.file_token 为 FILE_TOKEN

  # 2b-ii: 生成妙记(异步)
  lark-cli minutes +upload --file-token "$FILE_TOKEN"
  # → 返回 data.minute_url;从 URL 末段取 minute_token

  # 2b-iii: 等妙记 ASR 转写完成,拉逐字稿
  # 用 wait-for-minute.sh 轮询,不要再写固定 sleep。
  # 短视频 ~30-60s 退出;长视频自适应;最长 10 分钟兜底。
  #
  # 就绪信号:transcript.txt 里出现「说话人」标签(ASR 真实产出的结构标记)。
  # 不等 artifacts.summary——飞书 AI 总结是独立流水线,比 ASR 慢 3-10 分钟,
  # 而本 skill 用不到它(自己基于逐字稿写更密的版本,见 Step 3)。
  #
  # ⚠️ vc +notes 会把 transcript 下载到调用时的 CWD 下(minutes/<token>/transcript.txt),
  #    不是绝对路径。脚本内部已 cd 到 $WORK 之类的环境时仍要确保 CWD 正确。
  cd "$WORK"
  bash ~/.claude/skills/lark-video2note/scripts/wait-for-minute.sh "$MINUTE_TOKEN" > _notes.json
  # → _notes.json 是 vc +notes 的完整响应
  # → data.notes[0].artifacts.transcript_file 是相对 CWD 的逐字稿路径
  # → AI summary 字段忽略(我们自己写更密的)
  TRANSCRIPT_FILE="$WORK/minutes/$MINUTE_TOKEN/transcript.txt"
fi

# 走到这里:TRANSCRIPT_FILE 一定有了
# 字幕路径下 MINUTE_URL 为空,文档里就不写「飞书妙记」那一行
# 妙记路径下 MINUTE_URL 写到元信息 callout 里

# Step 3: 由你(Claude)基于逐字稿撰写结构化文档,并落成飞书 docx
# 重要:即使有妙记 summary 也不要直接用,那个总结太瘦
# 用下方「文档结构」模板,自己读完逐字稿写一版密度更高的版本
# 把内容写进 note.xml(CWD 必须是 $WORK)

# 3a. 短视频(XML < 40KB,对应大约 ≤ 20 分钟内容)— 一次性创建
cd "$WORK"
# ... 写入 note.xml(见下方模板)...

# ⚠️ 上传前必须先过 schema 校验。validator 抓常见 XML 错误(<text> 标签、
#    snake_case 属性、<docx> 根标签等),失败时**立即停下,改正 note.xml 后重试**。
#    不要带着违规 XML 硬上传——服务器会静默吞掉/转义未知标签,文档表面"创建成功"
#    但渲染稀烂,bug 拖到用户打开飞书才发现。
bash ~/.claude/skills/lark-video2note/scripts/validate-docx-xml.sh ./note.xml || exit 1

lark-cli docs +create \
  --api-version v2 \
  --parent-token $FOLDER_TOKEN \
  --content @./note.xml
# 注意:v2 用 --content(不是 --markdown)和 --parent-token(不是 --folder-token)
# --content 支持 @file,但 file 必须是 CWD 相对路径
# 文档默认 XML 格式,标题从 <title> 自动提取,不要传 --title
# → 返回 data.document.url

# 3b. 长视频(XML ≥ 40KB,对应大约 > 20 分钟内容)— 骨架 + 分段追加
# 长内容一次性 POST 容易超字数限制或被截。建议拆 3 段:
#   ① 创建骨架:标题 + 元信息 callout + 执行摘要 + 核心论点 + 占位 heading
#   ② 用 docs +update --command append 追加:完整论证链路 + Demo + 名词解释 + 金句
#   ③ 再 append:逐字稿章节
# 每一段 .xml 都要先过 validate-docx-xml.sh 再 append。
# 例:
#   bash ~/.claude/skills/lark-video2note/scripts/validate-docx-xml.sh ./part2.xml || exit 1
#   lark-cli docs +update --api-version v2 --doc <url_or_token> \
#       --mode append --content @./part2.xml
# 写入前用 wc -c note.xml 看大小,> 40KB 走 3b
# 参考:飞书 docs v2 推荐"先建骨架再 append",详见 lark-doc skill

# Step 4: 给用户回结果
# 输出 docx URL(绝对路径形式,方便 ⌘+Click)
# 如果走的是妙记路径,附带妙记 URL;走字幕路径则不提

文档结构(11 大块,固定顺序)

使用飞书 docx XML 格式(不要用 markdown,markdown 不支持 callout)。 模板和示例见 references/doc-template.xml

⚠️ XML schema 是硬约束,不允许"发挥"。常见踩坑:

错的写法正确写法
<docx> / <?xml...?> 当根标签直接 <title>...</title> 开头,无外层根
<text>正文</text><p>正文</p> ——飞书 docx 没有 <text> 标签,写出来会被原样转义成可见字符串
background_color="light-blue"(下划线)background-color="light-blue"(连字符)
emoji_id="round_pushpin"emoji="📍"(直接放 emoji 字符,不是名称 id)
把所有内容塞进一个 <callout> 包起来<h1> / <p> / <ul> / <callout> 按结构拼,11 大块各就各位

属性名一律 kebab-case;段落标签只能用 <p>;emoji 必须是字符不是名称。不在 references/doc-template.xml 出现的标签或属性都视为非法。生成完 note.xmlscripts/validate-docx-xml.sh 会做一次硬性校验,发现违规直接 fail,必须先改对再上传。

1. <title>{视频标题}</title>
2. 📍 来源信息 callout(light-blue)— 平台/作者/时长/原视频/妙记/归档日期
3. ✨ 执行摘要 callout(light-yellow)— 一段话讲清作者真正想说什么,3-5 行
4. 🎯 核心论点 — 一句话立场
5. 🧭 完整论证链路 — 按视频推进逻辑切 4-6 个 h2 小节,每节用「叙述段落 + 必要时穿插 bullet」的方式写
6. 🎬 Demo 实操(如视频里有 demo)— 几段叙述讲清楚步骤
7. 📚 名词解释 — 只解释 2-4 个真正生僻的(如 Ontology、FDE、AIP),用大白话 + 类比
8. 💬 金句原文 callo

Como adicionar

/plugin marketplace add teemoweng/lark-video2note

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Comentários · Nenhum comentário

Entre para comentar. Entrar

  • Ainda não há comentários. Seja o primeiro.