Buzz 插件系统完全指南:内置插件、生命周期钩子与自定义插件开发
导读
本文是 Buzz 离线语音转写与翻译工具的插件(Plugin)使用与开发指南。插件自 Buzz 1.4.5 起引入,允许在不修改核心应用的前提下扩展转写流水线:既可以在转写前预处理音频(降噪、语言识别)、在转写后修改结果(字幕重排),也可以在转写保存后执行自定义动作(导出 DOCX、生成 AI 摘要、跳过重复任务)。读完本文,你将掌握插件的启用、排序与配置方式,理解 6 个内置插件的实现原理,并能够基于 BuzzPlugin 基类从零编写、打包和安装属于自己的插件。
插件机制概述:扩展点而非分支
Buzz 的插件体系将"转写流水线"分解为若干可插拔的时机点(hook)。根据 buzz/plugins/base.py 的文档字符串,插件是一个包含 plugin.py 模块的文件夹,该模块定义且仅定义一个 BuzzPlugin 子类;子类通过 metadata 类属性声明身份、配置模式与(可选的)pip 依赖,并覆写自己关心的生命周期钩子。
插件被刻意设计为"无需修改核心应用"的扩展:
- 可在转写前处理音频,并返回一个新的文件路径交给后续转写;
- 可修改或替换转写结果的分段列表;
- 可在一条转写记录保存到数据库后执行副作用,例如导出额外格式或生成摘要。
生命周期钩子(Hook)一览
| 钩子方法 | 运行时机 | 线程 | 典型用途 |
|---|---|---|---|
before_transcription |
转写开始前、语音提取之后 | 转写工作线程 | 音频预处理(降噪),返回新文件路径替换 task.file_path |
check_skip |
转写前 | 工作线程 | 返回分段列表则跳过转写;返回 None 则继续 |
after_transcription |
转写完成后、落库之前 | 后台线程 | 修改/重排结果分段,返回值作为新的分段列表 |
on_complete |
转写已保存到数据库之后 | 后台线程 | 副作用:写 Notes、导出文件、调用外部 API |
以 PluginManager.process_completed(buzz/plugins/manager.py)为例,可以清楚看到后处理链的次序:先执行所有启用插件的 after_transcription,再把最终分段持久化到数据库(该调用被 marshaling 回主线程),最后执行 on_complete。
线程安全契约(插件作者必读)
base.py 的模块注释明确了宿主强制的线程契约:
before_transcription运行在转写工作线程上,可以读/改源音频文件并返回新路径,但不得触碰数据库、Qt 控件或任何主线程绑定的对象;after_transcription与on_complete运行在后台线程,但通过PluginContext.transcription_service访问数据库是被允许的——宿主会用MainThreadInvoker把分段保存等调用转发回主线程(底层原因是 QSqlDatabase 默认连接是线程亲和的,见 buzz/plugins/post_processing.py);插件同样永远不要直接操作 Qt 控件。
钩子在队列工作线程中的实际调用点位于 buzz/file_transcriber_queue_worker.py:转写前先执行 run_before_transcription,随后执行 run_check_skip 决定是否跳过;process_completed 则由 buzz/widgets/main_window.py 在任务完成后触发。每个钩子都以 enabled_plugins_in_order() 的结果按序调用,且单个插件抛出的异常会被捕获记录,不会中断其他插件。
管理插件:启用、排序、配置与安装
在 Buzz 中打开 Help → Plugins 即可进入插件管理对话框(对应 buzz/widgets/plugins_dialog/plugins_dialog.py),支持以下操作:
- 启用/禁用:使用每个插件旁边的开关切换;
- 调整顺序:拖动插件改变执行顺序(顺序影响同一钩子的调用先后,见下文"执行顺序"说明);
- 配置:点击插件旁的设置图标打开 plugin_settings_dialog.py,编辑各配置项;
- 添加社区插件:点击 Add by URL...,粘贴一个
.zip文件的链接即可安装; - 移除:选择插件后点击 Remove(会同时清理其密码字段在系统 keyring 中的密钥与存储的配置,见
PluginManager.remove)。
插件存储位置与加载
- 插件目录为
<用户缓存目录>/Buzz/plugins/<plugin_id>/(通过platformdirs.user_cache_dir("Buzz")计算); - 第三方依赖统一安装到
<用户缓存目录>/Buzz/plugins_deps/,安装结果记录在.installed.json标记文件中,避免重复安装; - 首次启动时,源码树中内置的 6 个插件会被复制到用户目录;
copy_bundled_plugins采用内容比对(忽略__pycache__、*.pyc),仅当内置版本发生变化时才覆盖,不会动用户自行安装的插件; discover_plugin_dirs仅扫描包含plugin.py入口模块的子目录。
安装来自 zip 的插件(内部机制)
PluginManager.add_from_url → loader.download_and_extract 的安装流程包含多重保护(loader.py):
- 从 URL 下载 zip(60 秒超时);
- 解压前做 zip-slip 路径穿越防护,拒绝任何试图逃出目标目录的成员;
- 支持 zip 顶层直接放插件文件,或嵌套在单一包裹目录中(GitHub 风格 zip 的常见形态);
- 先加载验证、后提交安装:先用
load_plugin_from_dir验证 zip 内容是一个合法插件(存在plugin.py、恰好一个BuzzPlugin子类、有metadata.id),验证通过才复制进插件目录; - 安装后自动启用该插件。
安装后,插件声明的 pip_dependencies 会被 pip install --target plugins_deps_dir 到独立依赖目录并加入 sys.path,因此第三方依赖不会污染主应用环境。
启用状态与顺序的持久化
PluginManager 使用 QSettings 的 plugins 分组持久化 order(顺序列表)与 enabled(各插件开关状态)。_reconcile_order 会在初始化时清理已失效的插件 id,并把新发现的插件追加到顺序末尾。
执行顺序值得注意:同一钩子按顺序依次调用,前一个插件的结果会传递给后一个。例如 after_transcription 中后一个插件拿到的是前一个插件修改后的分段列表;before_transcription 中后一个插件看到的 task.file_path 是前一个插件返回的新路径;而 check_skip 则是第一个返回非 None 结果的插件生效(run_check_skip 的短路语义)。
内置插件详解
内置插件由 loader.py 中的 BUNDLED_PLUGIN_IDS 定义:ai_summary、transcript_resizer、export_docx、enhanced_language_detection、skip_already_transcribed、deep_filter_net,其源码即位于 buzz/plugins 目录,是编写自定义插件的最佳参考起点。
AI Summary:转录后自动生成摘要
ai_summary/plugin.py 在 on_complete 钩子中,把整段转录文本拼接后发送到 OpenAI 兼容的 chat completions API,并将生成的摘要写入转录记录的 Notes 字段和/或源音频旁的文本文件。
配置项(来自 metadata.config_fields):
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
api_url |
TEXT | https://api.openai.com/v1 |
API 基础地址,可指向 OpenAI、Ollama 等兼容服务 |
api_key |
PASSWORD | 空 | API 密钥,存入系统 keyring,不写入 QSettings |
model |
TEXT | gpt-4o-mini |
使用的模型名 |
prompt |
TEXTAREA | 内置提示词 | 摘要生成提示词 |
save_to_notes |
BOOL | true |
是否写入 Notes 字段 |
save_to_file |
BOOL | false |
是否写入文本文件 |
output_folder |
TEXT | 空 | 文件输出目录,留空则保存在源文件旁 |
实现细节:该插件不声明 pip 依赖,因为 openai 包已随 Buzz 捆绑;请求超时 120 秒;文件输出命名为 {源文件名}.summary.txt。注意 PASSWORD 类型字段的读取与写入都走 keyring_store,密钥名称为 plugin:{plugin_id}:{field_key}(见 manager.py 的 _secret_name),这是插件配置安全性的关键设计。
Enhanced Language Detection:转写前自动语言识别
enhanced_language_detection/plugin.py 在 before_transcription 钩子中,针对"语言留空(自动检测)"的文件,先用本地 whisper.cpp 执行一次快速的 detect-language 预检测:
- 优先使用本机已下载的最大 whisper.cpp 模型(排除 CUSTOM、LUMII 等特殊用途模型);
- 若没有任何 whisper.cpp 模型,且配置项
download_tiny_if_missing(默认开启)为真,则自动下载 tiny 模型用于检测; - 检测到的语言写回
task.transcription_options.language,从而驱动实际转写、并渲染导出文件名中的{{ language }}占位符;同时通过update_transcription_language更新数据库记录,保证之后从转录查看器导出时语言一致; - 若用户已显式选择语言(代码中
"en"被视为未选择的默认值,仍会触发检测),则跳过检测。
该插件的场景是批量转写语言未知的文件,用一次极快的 whisper.cpp 预检换取主转写的准确性与导出文件命名的正确性。
Export to DOCX:导出为 Word 文档
export_docx/plugin.py 在 on_complete 钩子中把转录导出为 Microsoft Word .docx 文件。亮点是零第三方依赖——它直接用标准库 zipfile 手写 OOXML 的三个必要部件([Content_Types].xml、_rels/.rels、word/document.xml)生成 docx,从而规避冻结版应用中 python-docx 依赖 lxml 二进制轮子无法从插件依赖缓存加载的问题。
配置项:
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
output_folder |
TEXT | 空 | 输出目录,留空则保存在源文件旁 |
include_timestamps |
BOOL | false |
是否包含每段的时间戳 |
未开启时间戳时,文本按段落组织,分段规则与 TXT 导出器一致:相邻分段间隔达到 PARAGRAPH_SPLIT_TIME = 2000 毫秒即另起一段。文件名沿用源文件名({stem}.docx),标题取自文件名。
Resize Transcript:自动字幕重排
transcript_resizer/plugin.py 在 on_complete 钩子中,把**启用了词级时间戳(word-level timings)**的转录重新分组为适合字幕尺寸的分段,并替换原结果。它镜像了 transcription_resizer_widget.py 的 "Merge" 行为,但转为每次转写后自动执行。stable_whisper、srt_equalizer 等重型导入被推迟到钩子内部,避免拖慢应用启动。
配置项:
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
merge_by_gap |
BOOL | true |
按静音间隙合并词 |
merge_gap_seconds |
TEXT | 0.2 |
合并的间隙阈值(秒) |
split_by_punctuation |
BOOL | true |
按标点拆分 |
punctuation |
TEXT | .* /./. /。/?/? /?/!/! /!/,/, |
拆分的标点规则串 |
split_by_max_length |
BOOL | true |
按最大长度拆分 |
max_length |
TEXT | 42 |
最大字幕长度(字符) |
实现要点:
- 若转写未启用词级时间戳,插件直接跳过;
- 内部把 Buzz 分段转换为 stable_whisper 期望的
get_transcript数据源,以regroup规则字符串(形如mg=0.2++42+1_sp=..._sl=42)驱动重排,并关闭 VAD 与静音抑制以保持时间轴一致; - 中文、日文、泰文等无空格语言(
NON_SPACE_LANGUAGES = {"zh", "ja", "th", "lo", "km", "my"})分词时不插入空格分隔符; - 规则字符串可用环境变量
BUZZ_MERGE_REGROUP_RULE整体覆盖,便于高级用户微调; - 重排后通过
replace_transcription_segments替换数据库中的分段。
DeepFilterNet Noise Reduction:转写前降噪
deep_filter_net/plugin.py 在 before_transcription 钩子中用 DeepFilterNet3 模型移除背景噪声:对 task.file_path 指向的音频执行 init_df → load_audio → enhance → save_audio,输出带 _DeepFilterNet3.wav 后缀的新文件并返回该路径作为后续转写的音频源。插件声明依赖 deepfilternet>=0.5.6(这是唯一声明了 pip 依赖的内置插件,由 PluginManager._install_deps_if_needed 负责安装)。
配置项:
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
keep_denoised_file |
BOOL | false |
转写完成后是否保留降噪文件 |
默认情况下,降噪文件只是中间产物——on_complete 钩子会把它删除;开启"Keep denoised file after transcription"即可保留。若降噪阶段失败,插件记录错误并返回 None,转写继续使用原始音频,保证流水线不被单点故障阻断。
Skip Already Transcribed:跳过已转写文件
skip_already_transcribed/plugin.py 通过 check_skip 钩子实现"重复导入不重复转写"。支持两种检测方式(可同时启用):
- Check for existing result files(默认开启):在音频同目录(以及任务的输出目录)查找同名的
.txt、.srt、.vtt结果文件,若找到则解析其内容为分段并导入,任务标记为 Skipped,不再运行模型;TXT 直接作为整段文本,SRT/VTT 会解析时间轴并转换为Segment; - Check in transcription database(默认关闭):按文件名查询数据库中已有的已完成转写记录,命中则把旧分段复制到新记录,同样标记为 Skipped。
该插件的典型场景是:重新导入一个"部分文件已转写"的文件夹,或对文件夹进行自动监听(folder watch)时,避免对已有结果的音频重复跑模型。注意实现中 _speech 后缀("Extract speech" 功能产生的临时文件)会被剥离后再匹配结果文件。
编写自己的插件:从模板到完整实现
内置插件源码是最佳起点。你也可以直接把 buzz/plugins 目录指给 AI 编码助手,请其按需生成插件。
插件骨架
一个插件文件夹必须包含 plugin.py,其中定义恰好一个 BuzzPlugin 子类。最小骨架如下(结构参照 tests/plugins/plugin_system_test.py 中的 VALID_PLUGIN):
from buzz.plugins.base import (
BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType,
)
class MyPlugin(BuzzPlugin):
metadata = PluginMetadata(
id="my_plugin", # 唯一 id,用于目录命名与状态持久化
name="My Plugin", # UI 中显示的名称
description="...", # 可选
version="1.0.0", # 可选
pip_dependencies=[], # 可选:需要额外安装的 PyPI 包
config_fields=[ # 可选:设置对话框中展示的配置项
ConfigField(key="text", label="Text", default="hello"),
ConfigField(key="flag", label="Flag",
type=ConfigFieldType.BOOL, default=True),
ConfigField(key="secret", label="Secret",
type=ConfigFieldType.PASSWORD), # 存入系统 keyring
],
)
def before_transcription(self, task, context):
return task.file_path + ".processed" # 返回新路径则替换音频源
def after_transcription(self, task, segments, context):
return segments # 返回修改后的分段列表
def check_skip(self, task, context):
return None # 返回分段列表则跳过转写
def on_complete(self, transcription_id, task, segments, context):
pass # 转写落库后的副作用
关键 API 说明
ConfigFieldType四选一:TEXT(单行输入)、TEXTAREA(多行输入)、BOOL(复选框)、PASSWORD(掩码输入,存系统 keyring);PluginContext提供config(已解析的配置字典,键为ConfigField.key)、transcription_service(数据库服务,后台线程调用会被自动 marshal 回主线程)、settings、log(专属 loggerbuzz.plugin.<id>);- 插件目录下的
locale/<locale>.json(如lv_LV.json)可提供多语言文案,plugin_gettext(__file__)会按当前 UI locale 加载翻译,缺失时回退原文(见 base.py 的plugin_gettext)。
打包与安装
将插件文件夹(含 plugin.py 与可选 locale/ 目录)打包成 zip 后,在 Help → Plugins → Add by URL... 粘贴 zip 链接即可安装。zip 内可以是顶层插件文件,也可以是单个包裹目录(GitHub 仓库 zip 的常见形态);安装过程会自动校验合法性、安装声明的 pip 依赖,并默认启用。
质量保障:插件的测试视角
仓库为插件系统提供了较完整的测试覆盖,可作为自研插件的验收参照:
- tests/plugins/plugin_system_test.py 覆盖:合法插件加载、缺少子类时报
PluginLoadError、管理器的发现与排序、配置持久化与 PASSWORD 字段密钥存储、启停/移动、zip 安装与移除等; - tests/plugins/loader_test.py 覆盖:内置插件复制与内容比对、zip-slip 防护、嵌套目录解包、
download_and_extract的校验逻辑等。
插件生态与社区
如果你构建了有用的插件,可以与 Buzz 社区分享,或提交合并请求申请将其收录为内置插件。内置插件的更新会通过 copy_bundled_plugins 的内容比对机制自动同步到用户目录;而用户自行安装的插件目录不会被内置更新覆盖,这一设计在"可升级性"与"用户自定义不被破坏"之间取得了平衡。
结语
Buzz 的插件体系围绕"四个生命周期钩子 + 声明式元数据 + 独立依赖目录"展开:核心应用只负责在正确的时机分发事件,而具体的音频预处理、结果改写与落库后动作全部交给插件自治。对于普通用户,六个内置插件已覆盖降噪、语言识别、字幕重排、DOCX 导出、AI 摘要与重复跳过等高频需求;对于开发者,同一套 BuzzPlugin 契约既可写出数十字的最小插件,也能承载依赖第三方库的复杂流水线——唯一的底线是遵守线程安全契约,不要在工作线程里碰数据库和 Qt 控件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.22 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python430
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2.01 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python48868
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go21143
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34551