专业对话稿生成助手

Polar Sponsor
爱发电 赞助
.NET 9.0

专业对话稿生成助手:视频/音频→带说话人标注、可读性重排、金句高亮的对话稿,支持B站视频输入,适用于访谈、播客、会议等多场合。

专业对话稿生成助手

功能概述

专业对话稿生成助手是一项面向实际任务的技能,主要用于把视频/音频变成 专业对话稿(transcript) ——能区分"谁说的"、读着不累、可高亮金句、可直接粘进公众号;产物是 对话稿(给人读) ,不是字幕(subtitles,给画面看)——两者标准不同,见下文"可读性重排";

核心要点

  • 当前支持的输入源: B 站视频 (下载音频 → ASR 说话人分离 → 对话稿);
  • 音频下载通过 scripts/run.js 执行。
  • 它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。

使用与执行

从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;

结果检查与注意事项

若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。

专业对话稿生成助手

把视频/音频变成专业对话稿(transcript)——能区分"谁说的"、读着不累、可高亮金句、可直接粘进公众号。产物是对话稿(给人读),不是字幕(subtitles,给画面看)——两者标准不同,见下文"可读性重排"。

当前支持的输入源:B 站视频(下载音频 → ASR 说话人分离 → 对话稿)。音频下载通过 scripts/run.js 执行;语音转写通过 scripts/asr_transcribe.py 执行。

核心能力

  1. B站音频下载(输入源):通过 DASH 格式获取独立音频流,绕过反爬,体积小速度快
  2. 视频信息查询:不下载的情况下获取视频标题、时长、封面等信息
  3. 对话稿生成(说话人分离):把音频转写为带说话人标注的对话稿,区分"谁在说话"(A说的/B说的),适用于访谈、播客、会议等多人场景,避免流水账
  4. 可读性重排(Clean Verbatim):删填充词、合并同讲者碎片为段落,让稿子从"记录"变成"可读"
  5. 金句高亮:自动挑"对成长有启发"的金句并划线高亮
  6. 公众号直粘:HTML 全内联样式,粘进公众号编辑器不丢样式

环境准备(首次使用时执行)

每次对话第一次调用本 Skill 时,必须首先完成环境准备。

定位 run.js

路径固定为:~/.workbuddy/skills/bilibili-subtitle/scripts/run.js

将此路径记为 RUN_JS。

执行环境初始化

node "$RUN_JS" init

run.js init 依次完成:Python 3 检查 → requests 安装。

解析输出 JSON:

  • ok: true → 环境就绪,从返回的 skill_dir 字段提取值记为 SKILL_DIR,静默完成,进入对应子流程
  • ok: false → 按错误码映射表输出对应话术(见错误处理章节)

意图识别规则

按顺序判断,命中即停止:

第一关:用户消息中包含 B站链接(b23.tv、bilibili.com)或 BVID(BV 开头的字符串),且包含「下载音频」「下载」「音频」等词? → 是 → 【下载音频意图】执行前置流程 + 下载流程

第二关:包含 B站链接/BVID,想了解视频基本信息(如「这个视频多长」「什么内容」),但不需要下载? → 是 → 【信息查询意图】执行信息查询流程(无需环境准备)

第三关:用户想要「字幕」「转写」「文字稿」「对话稿」「逐字稿」「谁说的」「区分说话人」,或明确是访谈/播客/会议要区分发言人? → 是 → 【对话稿生成意图】执行对话稿生成流程(说话人分离)

第四关(兜底):用户含糊表达(如「帮我下载这个视频」),但未提供链接? → 是 → 询问:「请提供B站视频链接或BVID~」

BVID 提取规则

从用户消息中提取 BVID,支持以下格式:

  • 完整链接:https://www.bilibili.com/video/BV1DLdcB4ErM → BV1DLdcB4ErM
  • 短链接:https://b23.tv/NkqyQTP → 需先请求展开,再从重定向URL中提取 BVID
  • 纯 BVID:BV1DLdcB4ErM → 直接使用

短链接展开方法:用 curl 跟随重定向,从最终 URL 中提取 BV 开头的字符串。

下载音频流程

前提:前置流程已完成环境准备。

执行下载

node "$RUN_JS" download --bvid "" --output "<输出路径>"

解析输出 JSON:

  • ok: true → 下载成功,提取 audioPath、title、duration、fileSizeMB,进入展示结果步骤
  • ok: false → 按错误码映射表输出

展示结果

✅ 音频下载完成!

📹 视频:{title}({duration}分{durationSec}秒)
🎵 文件大小:{fileSizeMB} MB
📁 保存路径:{audioPath}

视频信息查询流程

无需环境准备,直接调用。

执行查询

node "$RUN_JS" info --bvid ""

解析输出 JSON:

  • ok: true → 查询成功,提取 title、duration、durationText、cover、desc、owner
  • ok: false → 按错误码映射表输出

展示结果

📹 视频信息

标题:{title}
UP主:{owner}
时长:{durationText}
封面:{cover}
简介:{desc}

对话稿生成流程(说话人分离)

目标:把音频转成带说话人标注的对话稿,能区分"谁说的",而不是一段流水账。 方案:腾讯云 ASR「录音文件识别 + 说话人分离」(模式1)+ LLM 身份映射。

端到端流程总览(每步产出 + 常见失败点)

B站视频(BVID)
  │ [run.js download] 下载 m4a(48kHz 立体声,几百 MB)
  ▼
audio.m4a
  │ [asr_transcribe.py transcode] ffmpeg 转 16k 单声道 mp3
  ▼
audio_16k.mp3(几十~一百 MB)
  │ [asr_transcribe.py run] 上传 COS → CreateRecTask(说话人分离) → 轮询
  ▼
<结果目录>/asr_result_detail.json   ← 带 SpeakerId + 时间戳的结构化片段(核心产出)
<结果目录>/.asr_task_id             ← 任务 ID,保存失败时可凭它重新拉取救数据
  │ [asr_transcribe.py build] 按说话人聚合成轮次 + 身份映射
  ▼
dialogue.txt / dialogue.html         ← 最终交付(带【姓名】+时间戳)

常见失败点:转码参数错 → COS 上传失败 → CreateRecTask 非法参数 → 保存时 SDK 对象序列化失败(见下方"经验教训")。

前置说明(首次务必先向用户交代)

  1. B站多数视频无官方字幕,需自己做 ASR。可先用 x/web-interface/view 查 subtitle.list 确认;为空则必须走本流程。
  2. 需要腾讯云账号:SecretId/SecretKey + 一个 COS 存储桶。新用户有免费额度(录音文件识别 10 小时,当月有效、过期作废,别提前领;当天用正好)。
    • ⚠️ Key 安全:SecretKey 一旦在对话/文件里出现,任务结束后建议到 https://console.cloud.tencent.com/cam/capi 禁用并重新生成,避免泄露。
  3. 凭证绝不硬编码:通过环境变量 TENCENT_SECRET_ID / TENCENT_SECRET_KEY / TENCENT_COS_BUCKET / TENCENT_COS_REGION 传入,或复制 asr_config.example.json 为 asr_config.json 填写(该文件不得提交到公共仓库)。

环境依赖

pip install tencentcloud-sdk-python cos-python-sdk-v5   # Python SDK
# 系统需安装 ffmpeg(转码用)

Step 1:下载音频(若还没有)

复用【下载音频流程】拿到 m4a 文件。

Step 2:转码为 16k 单声道

腾讯云 16k_zh 引擎要求 16kHz 单声道,B站音频通常是 48kHz 立体声,必须转码:

python3 scripts/asr_transcribe.py transcode --input "<下载的m4a>" --output "<输出.mp3>"

Step 3:上传 + 识别 + 轮询(一步到位)

export TENCENT_SECRET_ID="..."
export TENCENT_SECRET_KEY="..."
export TENCENT_COS_BUCKET="your-bucket-appid"
export TENCENT_COS_REGION="ap-shanghai"
python3 scripts/asr_transcribe.py run --input "<16k单声道.mp3>" --output-dir "<结果目录>"

脚本内部:上传到 COS → 生成预签名 URL → CreateRecTask(16k_zh + SpeakerDiarization=1 + SpeakerNumber=0)→ 轮询 DescribeTaskStatus → 保存结果。 输出 asr_result_full.json(含 ResultDetail,带 speaker ID 和时间戳)。

保存失败?用 rescue 救数据(不要重跑识别) 若 run 因保存/序列化逻辑出错导致结果存空(典型症状:asr_result_detail.json 缺失或 ResultDetail 为空、最终稿无说话人标记),不要重跑 run(浪费额度+3~5分钟)。直接凭已生成的 .asr_task_id 重新拉取:

python3 scripts/asr_transcribe.py rescue --output-dir "<结果目录>"

该命令读取 .asr_task_id 调 DescribeTaskStatus 重新取结果并正确保存(用 _serialize() 序列化),秒级免费。

Step 4:LLM 身份映射(关键,让稿子有对话感)

腾讯云返回的说话人是数字 ID(SPEAKER_0/1/...),不知道谁是谁。由 assistant(LLM)读取全文语义完成映射:

  • 主持人特征:开场白、提问为主、串场、报节目名 → 映射为主持人真实姓名
  • 嘉宾特征:回答为主、被介绍 → 映射为嘉宾真实姓名
  • 过滤噪音说话人:16k 引擎不支持指定人数,笑声/背景音可能被误判为多余 speaker,出现极少量、无意义的 ID 时应合并或剔除
  • 若某段落归属明显异常(如提问内容出现在嘉宾名下),按语义纠正

实操判定法(已验证有效)

不要只凭开场独白下结论,分两步交叉验证:

  1. 看开场角色定位:谁在介绍谁?被介绍/被"你和我说"的是嘉宾,主动介绍、报节目名的是主持人。
  2. 抽一段连续交替对话(两人频繁切换处,跳过开场独白)看指代:
    • 一直对对方用「你」、自述「我帮你…」「你看你…」→ 主持人
    • 自述「我那段时间…」「我是第X届…」讲自己经历、且主要是被问 → 嘉宾
  3. 一致性复核:确认映射在全文稳定。本次案例:开场判定 Speaker0=鲁豫,再取中段连续对话验证「你/我」指代一致后才定稿(避免独白误导)。
  4. 映射结果格式:--speaker-map "0:鲁豫,1:张泉灵"(ID 顺序按 ASR 输出,不同视频不同)。

Step 5:聚合为带身份标注的对话稿

用 build 子命令把 ResultDetail(按说话人聚合成轮次)一键生成对话稿:

python3 scripts/asr_transcribe.py build 
  --input-dir "<结果目录>" 
  --speaker-map "0:鲁豫,1:张泉灵" 
  --out-name dialogue
  • --speaker-map 的映射来自 Step 4 的 LLM 判定(不同视频不同)
  • 输出 <结果目录>/dialogue.txt(纯文本,带【姓名】+时间戳)与 dialogue.html(配色对话稿,配色可在 build_dialogue.py 里改 NAME_COLOR)
  • 聚合取字段:每个片段优先用 FinalSentence(已合并标点、无词间空格,直接可读);SliceSentence 带词间空格仅适合对齐,不要直接出稿。连续同说话人片段自动并为一轮。

若未提供 --speaker-map,输出用 Speaker0/Speaker1 占位,后续再替换。

可读性重排:两遍工作流(Clean Verbatim)⭐

build 直接出的 dialogue.txt 是 Strict Verbatim(全保留口语词+碎片)——每个 ASR 句是一轮,满屏"呃/嗯/然后"和一两句的碎片,读着极累。访谈/播客逐字稿给人读时,行业事实标准是 Clean Verbatim(智能逐字)。完整工作流是两遍:

dialogue.txt (Strict Verbatim, 含 P0 修正)
  │ 第一遍:P0 最小修正(修错别字/标点/专名,保留口语词)
  ▼
dialogue_clean.txt
  │ 第二遍:Clean Verbatim 可读性重排
  │   - 删无意义填充词/口误/false starts
  │   - 合并连续同讲者碎片为段落
  │   - 保留情绪感叹、故事细节、对话节奏
  ▼
dialogue_clean_v2.txt + dialogue_clean_v2.html

为什么分两遍:第一遍保"准确"(不丢任何词,方便回查原文/跳回听音);第二遍保"可读"(去口语噪音、合并碎片)。两版都留着,用户按需取用。

Clean Verbatim 操作手册(已实战验证):

  • 删:无实义的 呃/嗯/Um/啊/那个/就是(填充)/然后(仅连接填充)/对吧/是不是/嘛/呀;false starts("我,但是我"→"但是我"、"我是在。"悬空碎片直接删);口吃半截("我我"→"我")。
  • 留:情绪感叹(天呐/我的天呐/哎呀)、表强调的重复(不不不/非常非常/退回来退回来)、所有具体故事细节与实词、说话人个性化表达(有意义时)。
  • 合并:连续同一说话人多轮 → 一个段落(一个 turn);跨说话人不合并(保对话感);合并后单段 >180 汉字则在句末(。!?)切分。
  • 不动:说话人归属、原意、补原文没有的内容。
  • 专名/误识:沿用第一遍已知修正(奥迪A7L、《再见爱人》、《姐姐当家》第二季、凤凰(卫视)、泉灵/张泉灵、鲁豫、黄浦区、卢湾、癌症);新误识按语义修,不确定处保留原文并标注。

并行执行模板(19 块 / 7 agent,50 轮每块):

  1. 切分:dialogue_clean.txt 按 50 轮/块切到 proofread/v2_chunks/(项目目录,别用 /tmp,见教训 6)。
  2. 派 7 个 general-purpose 子代理并行,每块独立读 chunk_XX.txt、写 proofread/v2_clean/clean_XX.txt,prompt 含上述操作手册 + 一个 before/after 示例(如"逼单"段)。
  3. 终检脚本:拼接 19 块 → 合并相邻同讲者段落(顺带归一化过度切分)→ 按 180 字切过长段 → 输出 dialogue_clean_v2.txt;校验字符数合并前后一致(防丢内容)。
  4. build_clean_v2_html.py 生成配色 HTML(配色常量 NAME_COLOR/NAME_BORDER 可换主题,如蔷薇紫、红蓝等)。

金句高亮(可选增强)⭐

给对话稿里"对成长有启发"的金句加划线高亮,让稿子从"记录"变成"可反复回看的成长素材"。

工作流(已实战验证):

  1. 把 dialogue_clean_v2.txt 按 240 段/块切到 proofread/highlight_chunks/。
  2. 派 4 个 general-purpose 子代理并行挑金句(标准:人生选择/自我认知/做事方法/关系洞察/有被引用价值;避开纯叙事),每块返回 JSON:[{speaker, time, quote, reason}],quote 必须 15-80 字、逐字照抄原文。
  3. 逐字校验:脚本读 4 个 JSON,对每条 quote 在 v2 原文里精确子串匹配。agent 常会改字/编造,本次 31 条里 2 条不匹配,手动从原文取真实句子替换。校验通过率必须 100% 再进入下一步。
  4. 改 build_clean_v2_html.py:加 load_quotes() + apply_highlights(),在段落 HTML 里给金句子串包 (纯色底,公众号安全)。
  5. 重新生成 HTML。本次 960 段、31 处高亮。

坑:agent 挑金句时倾向改字"润色",导致高亮文字和稿子对不上。必须逐字校验,把 agent 返回的 quote 当作"待校验"而非"已确认"。

专业依据(对外解释用):GoTranscript / Rev / 广电总局字幕规范 均将 Clean Verbatim 列为访谈、播客、学术逐字稿标准;字幕(subtitles,限行/无标点/去口气词)≠ 逐字稿(transcript,有标点/分段/分讲者),给人读的产物用逐字稿标准。

可选增强(按需)

  • LLM 纠错:说话人分离约 90%+ 准确率,长访谈在笑声/叠话/音乐处有少量边界错分。可再跑一轮 LLM,读 dialogue.txt 修掉明显错分、补标点断句。
  • 按话题分段:192 分钟长稿可让 LLM 按话题切分并加小标题,降低阅读负担。
  • 公众号排版:把对话稿 HTML 粘进公众号编辑器前,必须确保所有样式是内联 style="..."(见教训 8),否则