Chrome MV3 Tab Capture + 飞书妙记 + 企业微信 aibot:一个端到端直播录音 AI 整理推送工作流
一个开发者想用工具链把"录播 → AI 转写 → 推到团队群"全自动化时会遇到的真实问题:每个环节都有坑。本文按一条端到端流水线组织——从录制 webm,到 ASR 出文字稿,到最终 push 给企业微信——记 6 个核心踩坑全过程,附带两个 CLI 小细节。
TL;DR
- 录:Chrome MV3 Tab Capture API(用
chrome.tabs.getMediaStreamId)+ offscreen document 跑 MediaRecorder - 转:ffmpeg → 飞书妙记 ASR(lark-cli 触发)
- 读:lark-cli 拉智能纪要(摘要 + 章节时间轴 + 关键词)
- 推:wecom-cli aibot send → 企业微信智能机器人 → 群/个人
- 串:30 行 Python 脚本
douyin-flow.py把四段合成一条命令 - 耗时:单场 46 分钟直播,从录完到团队群收到结构化推送 ≈ 3 分钟
全链路
录 (Chrome 扩展) 转 (ffmpeg + 飞书妙记) 读 (lark-cli) 推 (wecom-cli)
─────────────── → ────────────── → ────────── → ──────────
chrome.tabs ffmpeg -ac 1 -ar minutes +detail aibot send
.getMediaStreamId() 8000 -b:a 16k --summary markdown
↓ ↓ --chapter ↓
webm ≤5MB mp3 --keyword 企业微信群
↓ ↓ ↓ (或个人 ID)
飞书云盘上传 智能纪要
↓ ↓
触发 ASR {summary, chapters[]}
↓ ↓
飞书妙记智能纪要 ───── → ─────
(1~3 分钟)
1. 录制
这一段把"空气里的声音"变成 webm 文件。
工具链
chrome.tabs.getMediaStreamId()—— MV3 推荐 Tab Capture 入口- Chrome MV3 manifest v3 —— 扩展必须用 MV3
chrome.offscreen—— 后台跑 MediaRecorder 的"不可见 tab"MediaRecorder(Opus in WebM) —— 把 MediaStream 编码成 webm blob
关键代码
// service-worker.js — MV3 正确写法
const streamId = await chrome.tabs.getMediaStreamId(tabId);
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: { chromeMediaSource: 'tab', chromeMediaSourceId: streamId }
}
});
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
mediaRecorder.ondataavailable = (ev) => chunks.push(ev.data);
mediaRecorder.start(1000); // 每秒一片
这一段踩到的 3 个坑
坑 ① Chrome MV3 service worker 不持久
MV3 把 service worker 改成 event-driven,5 分钟 idle 必杀。把录制状态放 service worker 模块作用域,重启就丢。第二次点按钮判断"是否在录"永远 false,导致反复报 Cannot capture a tab with an active stream。
修法:offscreen document 当 source of truth。offscreen 是 Chrome 内部常驻 HTML 页面,比 SW 寿命长,重启也不丢。所有 state 全在 offscreen 内部,service worker 只做转发。
坑 ② tabCapture 老 API 路径
MV3 下 chrome.tabCapture 整个 deprecated。chrome.tabCapture.getMediaStreamId 还是能用,但官方推荐路径是 chrome.tabs.getMediaStreamId(tabId)。
修法:用新的 chrome.tabs.getMediaStreamId,streamId 拿去 navigator.mediaDevices.getUserMedia({audio: {mandatory: {chromeMediaSource: 'tab', chromeMediaSourceId}}}):
// 错 → deprecated
const streamId = await chrome.tabCapture.getMediaStreamId({ targetTabId: tabId });
// 对 → MV3 推荐
const streamId = await chrome.tabs.getMediaStreamId(tabId);
坑 ③ MediaRecorder 出错时 stream 残留
MediaRecorder 在直播流中断、目标 tab 静音、Chrome 内部失败时会进入 error 状态。但默认没有 onerror handler——stream 残留 → 下次录制报 "active stream"。
修法:补 mediaRecorder.onerror 自动 cleanup + 每次开始前 force-stop 兜底 + 设最大时长(1 小时)防止 Chrome bug 累积让内存爆炸。
外加一个隐藏陷阱:chrome.downloads.download() 在 service worker / popup 里能用,offscreen 里不行——offscreen 是真 HTML 页面,直接用标准 <a download>.click():
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
document.body.appendChild(a);
a.click();
2. 转写
这一段把 webm 变成结构化文字稿。
工具链
- ffmpeg —— webm → mp3
- 飞书妙记 —— 字节跳动自家 ASR,中文顶级
- lark-cli —— 飞书官方 Rust CLI(含 AI Agent Skills)
关键命令
# ① 转 mp3 — 8kHz mono 16kbps (46 分直播 ≈5MB)
ffmpeg -y -loglevel error -i in.webm -ac 1 -ar 8000 -b:a 16k out.mp3
# ② 上传飞书云盘 + 触发 ASR
lark-cli drive +upload --file out.mp3 --as user --format json
lark-cli minutes +upload --file-token <drive_file_token> --as user --format json
这一段的核心坑
坑 ④ 飞书 Drive 单文件 20MB 限制
errcode 1061043, "file size beyond limit"
直播 mp3 默认 64kbps → 46 分钟直播约 22MB 超过 20MB。降一档:
ffmpeg -i in.webm -ac 1 -ar 8000 -b:a 16k out.mp3
8kHz 采样对中文语音 ASR 足够(飞书内部模型 16kHz 但能 upsample),文件大小砍到 1/4。这条对所有 ASR 引擎都通用。
3. 读取
这一段把 ASR 出的原始文字稿变成结构化纪要。
工具
lark-cli minutes +detail—— 拿转写后的智能纪要- AI 自动产出:摘要 + 章节 + 关键词 + 待办
- 输出:JSON + 文件 dump 到
--output-dir
关键命令
lark-cli minutes +detail \
--minute-tokens <minute_token> \
--summary --chapter --keyword --transcript \
--output-dir ./minutes --overwrite \
--as user --format json
返回结构:
{
"data": {
"minutes": [{
"note_id": "...",
"artifacts": {
"summary": "...",
"chapters": [{"start_ms": 0, "title": "...", "summary_content": "..."}],
"keywords": [...]
}
}]
}
}
这一段的核心坑
坑 ⑤ API 字段嵌套变化
// 老版本
{data: {minute: {token: "..."}}}
// 当前版本
{data: {minute_token: "..."}} ← 直接是字符串
跑脚本第一遍踩过——data['minute']['token'] 拿到 undefined。兼容写法:
mt = data.get('data', {}).get('minute_token') \
or data.get('data', {}).get('minute', {}).get('token')
4. 推送
这一段把结构化纪要发到企业微信。
工具链
- 企业微信智能机器人(aibot)—— 客户端能
@触发 + 服务端能 push wecom-cli—— 企业微信官方 Rust CLI(含 12 个 AI Agent Skills)
关键命令
wecom-cli message aibot send \
--chat-id "<企业微信 userid 或 chat_id>" \
--msg-type markdown \
--markdown '{"content":"## 📊 直播自动整理\n..."}'
这一段踩到的 2 个坑
坑 ⑥ 个人微信 API 不开放 + 内容超 4096 字节静默截断
个人微信:
全行业死结。个人号 API 永久没开放。最终路径:企业微信智能机器人 → 推到企微群 → 群里的外部微信成员在个人微信端看到推送(腾讯官方「企业微信联系人」通道)。但不在群里 → 不装企微 → 收不到。
4096 字节静默截断:
智能纪要内容长会超过 4096 字节——超过的部分被静默截断,没有错误提示。
修法:
content = f"## 直播自动整理\n\n摘要(前 1500 字):\n{summary[:1500]}\n\n" + \
'\n'.join(f"- {ch['title']}" for ch in chapters[:5]) + \
f"\n\n[查看完整妙记](https://feishu.cn/minutes/{note_id})"
拆条(摘要 + 章节两条),或整体截断到 ~4000 字节 + 末行加飞书妙记链接让用户去浏览完整版。
CLI 小坑合集(不是核心,但踩过)
lark-clipath 安全:--file必须相对路径,abs 路径一票否决wecom-cliaibot send 不支持--format:传--format json会报 "unexpected argument"- aibot 不能 push 非 session 外部 userid:Bot 必须先在群里互动过
串联:douyin-flow.py(30 行核心)
import json, subprocess, time, shutil
from pathlib import Path
# ① ffmpeg — 8kHz/16kbps mono
mp3 = f"{Path(webm).stem}-small.mp3"
subprocess.run(['ffmpeg','-y','-loglevel','error','-i',webm,
'-ac','1','-ar','8000','-b:a','16k', mp3])
# ② lark-cli — 上传 + 触发 ASR
shutil.copy(mp3, '.')
ft = json.loads(subprocess.run(['lark-cli','drive','+upload','--file',mp3,
'--as','user','--format','json'], capture_output=True).stdout)['data']['file_token']
mt = json.loads(subprocess.run(['lark-cli','minutes','+upload','--file-token',ft,
'--as','user','--format','json'], capture_output=True).stdout)['data']['minute_token']
# ③ 等转写完(轮询 20s,~3 分钟;分钟内轮询一次 detail)
for _ in range(15):
d = json.loads(subprocess.run(['lark-cli','minutes','+detail',
'--minute-tokens', mt, '--summary','--chapter','--keyword',
'--as','user','--output-dir','./minutes','--overwrite'],
capture_output=True).stdout)
if d.get('ok') and d['data']['minutes'][0]['artifacts'].get('summary'):
arts = d['data']['minutes'][0]['artifacts']
break
time.sleep(20)
# ④ wecom-cli — 推送给企业微信
content = f"## 直播自动整理\n\n摘要:{arts['summary'][:1500]}\n\n" + \
'\n'.join(f"- {ch['title']}" for ch in arts.get('chapters', []))
wecom = shutil.which('wecom-cli')
subprocess.run([wecom,'message','aibot','send','--chat-id','<userid>',
'--msg-type','markdown','--markdown',
json.dumps({'content':content}, ensure_ascii=False)])
跑一条命令:
python douyin-flow.py douyin-xxx.webm
~3 分钟后你企业微信里就有一条结构化推送。
适合谁 / 不适合谁
| ✅ 适合 | ⚠️ 不适合 | ❌ 不适合 |
|---|---|---|
| 每天/每周固定直播、会议、讲座 | 1 小时内多场直播 | 想绕过腾讯限制推个人微信 |
| 想自动整理成结构化笔记 | 强实时推送需求 | 单场直播 < 1 分钟 |
| 能接受企微 vs 个人微信的限制 | 多语言直播(中文最佳) | 完全离线环境 |
总结
工具链最不显眼的价值不是「自动化」——是让一个原本只能一个人靠手工的活儿,可以同时给 10 个人用。你录了一场公开直播,把它分享给团队——这件事以前要你自己听、自己整理、自己转告同事。链路通之后,主播专心讲,团队专心听。
完整工具链清单:
- lark-cli —— 飞书官方 Rust CLI(含 12 个 AI Agent Skills)
- wecom-cli —— 企业微信官方 Rust CLI(含 12 个 AI Agent Skills)
- ffmpeg —— 视频处理老牌工具
- Chrome MV3 Tab Capture + offscreen —— 浏览器原生录音
- 企业微信智能机器人 aibot —— 2026 年合规推送的最优路径
每个坑都有一两个修复帖可参考,欢迎在评论里分享你的版本。
如果对你有用,欢迎点赞 + 收藏 + 在评论里分享你踩到的其他坑。