Token导航 LogoToken导航TokenDH.com
效率操作浏览器clawhub未标认证来源可访问clear审计提醒

doubao-podcast豆宝播客

Agent Skill

doubao-podcast 用于处理音频、语音、转写和声音素材相关任务,适合在 OpenClaw 中需要整理音频流程、转写内容或生成配音素材时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

3,369

周安装

139

GitHub Stars

公开资料未说明

下载量

1,101
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:doubao-podcast(豆宝播客)
来源仓库:https://github.com/mileszhang001-boom/doubao-podcast
安装命令:
openclaw skills install doubao-podcast
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install doubao-podcast

简介

用于音频生成与流式 WebSocket 二进制帧解析。

  • 基于豆包/字节跳动播客 TTS API 实现文本转语音。
  • 支持分块处理与 audio_url 提取便于后续合成。
  • 安装前需配置 API 密钥与网络连通性测试。doubao-podcast 属于效率类 Skill,可作为该场景下的辅助能力补充。
  • 注意音频质量受语速、音色 ID 设置影响较大。

SKILL.md

name
doubao-podcast
description
|

豆包播客 TTS API 集成指南

基于 7 篇微信长文 POC + 线上生产环境的实战经验。覆盖从建连到拿到 mp3 的全流程,以及 11 条踩坑记录。

1. 接口概览

协议WebSocket 二进制协议 v3
地址wss://openspeech.bytedance.com/api/v3/sami/podcasttts
鉴权4 个 Header:X-Api-App-IdX-Api-Access-KeyX-Api-Resource-IdX-Api-App-Key
Resource IDvolc.service_type.10050
输出MP3, 96kbps, 24kHz mono(固定)

2. 二进制协议

豆包使用自定义二进制帧,不是标准 JSON WebSocket。

帧头(固定 4 字节)

[0x11, 0x14, 0x10, 0x00]
  • 第 2 字节高 4 位 = message_type(0xF = 错误帧)
  • 第 2 字节低 4 位 = flags(0x04 = 含 session_id)
  • 第 3 字节高 4 位 = serialization(1=JSON, 0=binary audio)

两种帧格式

Pre-connection:  header(4) + event_type(4, big-endian) + payload_size(4) + payload
Post-connection: header(4) + event_type(4) + sid_len(4) + session_id + payload_size(4) + payload

构造/解析代码

Python 版本见 scripts/generate_podcast.py。Node.js 版本:

const HEADER = Buffer.from([0x11, 0x14, 0x10, 0x00]);

function preFrame(event, payload) {
  const p = Buffer.from(JSON.stringify(payload));
  const e = Buffer.alloc(4); e.writeUInt32BE(event);
  const l = Buffer.alloc(4); l.writeUInt32BE(p.length);
  return Buffer.concat([HEADER, e, l, p]);
}

function postFrame(event, sid, payload) {
  const sb = Buffer.from(sid);
  const p = Buffer.from(JSON.stringify(payload));
  const e = Buffer.alloc(4); e.writeUInt32BE(event);
  const sl = Buffer.alloc(4); sl.writeUInt32BE(sb.length);
  const pl = Buffer.alloc(4); pl.writeUInt32BE(p.length);
  return Buffer.concat([HEADER, e, sl, sb, pl, p]);
}

function parseEvent(data) {
  const buf = Buffer.from(data);
  if (buf.length < 8) return { eventType: null, payload: {} };
  const mt = (buf[1] >> 4) & 0xF;
  const fl = buf[1] & 0xF;
  const ser = (buf[2] >> 4) & 0xF;
  if (mt === 0xF) { /* 错误帧 */ return { eventType: -1, payload: {} }; }
  const evt = buf.readUInt32BE(4);
  let off = 8, payload = {};
  if (fl & 0x04) {
    const sl = buf.readUInt32BE(off); off += 4 + sl;  // 跳过 session_id
    const pl = buf.readUInt32BE(off); off += 4;
    if (pl > 0 && ser === 1) try { payload = JSON.parse(buf.slice(off, off + pl).toString()); } catch {}
  }
  return { eventType: evt, payload };
}

3. 握手流程与事件表

客户端                                豆包 API
  │── StartConnection(event=1) ────→│
  │←── ConnectionStarted(event=50) ─│  ← session_id 在二进制帧中
  │── StartSession(event=100) ─────→│  ← 携带播客参数
  │←── SessionStarted(event=150) ───│
  │  ┌── 流式循环 ──────────────────┐
  │←─┤ 360: RoundStart (JSON)      │  ← 文案文本(第1轮为空!见坑10)
  │←─┤ 361: RoundResp (binary)     │  ← 音频块 ~4.6KB/chunk
  │←─┤ 362: RoundEnd (JSON)        │
  │  └─────────────────────────────┘
  │←── 363: PodcastEnd (JSON) ─────│  ← audio_url(⚠️ duration_sec 可能为 0,见坑9)
  │←── 152: SessionFinished ───────│  ← ⚠️ 经常不来,见坑2
  │── FinishConnection(event=2) ──→│

事件速查

event名称payload关键字段
1/2Start/FinishConnection{}
50ConnectionStarted二进制session_id 在帧中提取
100StartSessionJSON播客参数
360RoundStartJSONtext(第 1 轮为空)
361RoundRespbinary音频块
363PodcastEndJSONmeta_info.audio_url

提取 session_id(ConnectionStarted 帧)

const buf = Buffer.from(data);
let off = 8;
const sidLen = buf.readUInt32BE(off); off += 4;
const sessionId = buf.slice(off, off + sidLen).toString();

4. 两种输入模式

input_url(URL 文章)

{
  input_info: { input_url: "https://...", return_audio_url: true },  // ⚠️ url 在 input_info 内
  use_head_music: true, use_tail_music: false,
  audio_config: { format: "mp3", sample_rate: 24000, speech_rate: 0 },
  speaker_info: { random_order: true, speakers: ["zh_male_dayixiansheng_v2_saturn_bigtts", "zh_female_mizaitongxue_v2_saturn_bigtts"] }
}

input_text(短文本 < 200 字)

{
  input_text: "文本内容...",  // ⚠️ text 在顶层,不是 input_info
  audio_config: { ... },
  speaker_info: { ... }
}

5. 实战踩坑记录(11 条)

坑 1:input_url vs input_text 参数位置

❌ {"input_url": "https://..."}                  → 顶层没这字段,静默失败
✅ {"input_info": {"input_url": "https://..."}}  → URL 模式正确用法
✅ {"input_text": "你好世界"}                     → 短文本模式正确用法

搞反了不会报错,只会得到空结果。

坑 2:PodcastEnd 之后不要等 SessionFinished

SessionFinished(152) 经常不来或等 10 分钟+。PodcastEnd(363) 拿到 audio_url 后立即 break

if (eventType === 363) {
  const url = payload.meta_info?.audio_url || '';
  ws.close();  // 立即关闭!
  resolve({ audioUrl: url });
}

不加 break,5 分钟播客可能跑 15 分钟。这是最致命的坑。

坑 3:超时设 900s

文章长度播客时长生成耗时
~2000字~5 min~2.5 min
~5000字~10 min~4 min
~25000字~30 min~10 min

统一设 TIMEOUT = 900(15 分钟)。

坑 4:WebSocket 连接参数

Python: ping_timeout=120,不设的话长文生成时会心跳超时断开。 Node.js: ws 库默认无 ping,不需要额外设置。

坑 5:连接断开但已有音频块

音频块是完整 MP3 片段,直接拼接就是可播放文件。断连时不要丢弃已收到的块。

坑 6:audio_url 24 小时过期

CDN URL 签名过期后返回 403。生成完成后立即下载 mp3 到自己的存储

坑 7:Python nohup 输出缓冲

后台运行加 python -u(unbuffered)才能看到实时日志。

坑 8:浏览器 WebSocket 不支持自定义 Header ⭐

浏览器 new WebSocket(url) 无法设置 HTTP Header。豆包需要 4 个鉴权 Header → 必须走服务端代理

推荐架构:

浏览器 ── POST /api/podcast ──→ 服务端 ── WSS + Header ──→ 豆包
       ← SSE 流式进度 ────────         ← 二进制帧 ──────

服务端用 Node.js ws 库(支持自定义 Header)连接豆包,通过 SSE(Server-Sent Events)把进度推给浏览器。凭证存在服务端,不暴露给前端。

坑 9:duration_sec 经常返回 0 ⭐

PodcastEnd 的 meta_info.duration_sec 实测经常为 0 或不返回

备用方案:根据音频大小估算(96kbps = 12KB/s):

const durationSec = meta.duration_sec || Math.round(totalAudioBytes / 12000);

坑 10:RoundStart 第一轮文案为空 ⭐

第 1 轮是片头音乐,text 字段为空字符串。第 2 轮开始才有实际文案内容。

如果需要从文案中提取标题/主题,从 Round 2+ 的文案中取。

坑 11:微信文章标题无法从服务端抓取 ⭐

从中国云服务器 fetch 微信文章会返回"环境异常"页面(反爬)。标题提取备用方案:

  1. 优先:fetch 文章页面提取 <title>og:title(非微信 URL 有效)
  2. 备用:从 RoundStart 第 2 轮文案提取主题(去口语化前缀如"今天我们聊的是")
  3. 兜底:使用 fallback 文本

6. 内容存储与去重(生产架构)

豆包 token 昂贵,必须避免重复生成。推荐架构:

POST /generate {url}
  → 查 store:相同 url 已有?
    → YES:直接返回缓存(0 token)
    → NO:调豆包生成 → 下载 mp3 到本地 → 写入 store → 返回

Store 数据模型:

{ "id": "gen_xxx", "source_url": "https://...", "title": "...", "audio_file": "gen_xxx.mp3", "duration_sec": 682 }

音频下载到本地后不再依赖豆包 CDN(24h 过期)。

7. 性能基线

指标
生成速度2.7× 实时(1 分钟播客 ≈ 22s 生成)
首 tokenavg 6s
Token 消耗~800 字/分钟播客,~3000 token/分钟
音频块~4.6KB/chunk
总测试量7 篇 + 线上多篇,全部成功

8. 错误处理

场景表现处理
建连失败event=-1检查凭证,重试
内容过滤错误码 50302102提示不支持,不重试
心跳超时静默断开Python: ping_timeout=120
audio_url 过期CDN 403用本地存储的 mp3

9. 参考实现

文件语言说明
scripts/generate_podcast.pyPython批量生成 + CLI,POC 验证用
项目 server/podcast-api.jsNode.js生产环境:HTTP API + SSE + 内容存储

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

79.61%
按下载量换算877

安全审计

VirusTotal

通过

ClawScan

可疑

Static analysis

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills