Buzz 插件系统完全指南:从插件契约到 Hook 调度、本地化与分发打包
Buzz 插件系统完全指南从插件契约到 Hook 调度、本地化与分发打包【免费下载链接】buzzBuzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper.项目地址: https://gitcode.com/GitHub_Trending/buz/buzz本篇指南以 Buzz 项目官方插件开发文档 buzz/plugins/AGENTS.md 为骨架结合仓库内 插件契约实现、插件加载器、插件管理器、线程编组、参考插件 ai_summary 及 插件系统测试 展开系统讲解 Buzz 插件系统的运行机制与开发全流程。读完本文你将掌握插件在转录流水线中的注入点与线程约束、BuzzPlugin插件契约与四个生命周期 Hook 的用法、配置字段与系统钥匙串keyring安全存储、JSON 本地化机制、依赖声明、zip 打包分发以及把插件内置进 Buzz 的完整清单。插件系统概览不修改核心也能扩展转录流水线Buzz 是一个基于 OpenAI Whisper 的离线音频转写与翻译桌面应用。插件系统的设计目标非常明确在不修改 Buzz 核心代码的前提下扩展转录流水线见 buzz/plugins/base.py 的模块说明。一个插件就是一个文件夹内含一个名为plugin.py的入口模块且必须且只能定义一个BuzzPlugin的子类。插件可以做的事情包括在转写前处理或替换源音频例如降噪、说话人分离前的预处理在转写后修改或替换转写结果segments 片段列表运行自定义代码并导入 Buzz 内部模块声明pip 依赖加载时自动安装进用户缓存目录暴露配置字段单行文本、多行文本、复选框、密码由 Buzz 自动生成设置对话框提供本地化的名称、描述与标签。插件存放位置与加载目录内置插件bundled位于源码树buzz/plugins/plugin_id/首次启动时被拷贝到用户可写目录。当前内置插件清单定义在 loader.py 的 BUNDLED_PLUGIN_IDSai_summary、transcript_resizer、export_docx、enhanced_language_detection、skip_already_transcribed、deep_filter_net拷贝逻辑见loader.copy_bundled_plugins()loader.py。用户安装的插件位于user_cache_dir/Buzz/plugins/plugin_id/其中user_cache_dir由platformdirs.user_cache_dir(Buzz)解析loader.py。插件依赖安装到user_cache_dir/Buzz/plugins_deps/该目录会被插入sys.pathloader.ensure_deps_on_path()loader.py从而让插件可以import自己声明的第三方包。普通用户在图形界面中通过Help → Plugins管理插件按 URL 添加一个.zip文件、启用/禁用、调整执行顺序、编辑设置。架构关键文件速览插件系统由四个核心模块组成与 AGENTS.md 的 Architecture 一节对应文件职责buzz/plugins/base.py插件契约BuzzPlugin、PluginMetadata、ConfigField、ConfigFieldType、PluginContext、plugin_gettext本地化翻译函数buzz/plugins/loader.py插件发现discover_plugin_dirs、zip 下载与解压download_and_extract、动态导入load_plugin_from_dir、内置插件拷贝copy_bundled_pluginsbuzz/plugins/manager.pyPluginManager插件注册表、启用状态/执行顺序/配置持久化、依赖安装、Hook 调度buzz/plugins/post_processing.py在 UI 线程之外运行转写后 Hook并把数据库访问编排回主线程MainThreadInvoker、MainThreadServiceProxy插件契约继承 BuzzPlugin 并声明 metadata一个最小可用的插件如下完整契约代码见 base.pyfrom buzz.plugins.base import ( BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType, PluginContext, plugin_gettext, ) _ plugin_gettext(__file__) # 本地化翻译函数详见本地化一节 class MyPlugin(BuzzPlugin): metadata PluginMetadata( idmy_plugin, # 稳定且唯一必须与文件夹名一致 name_(My Plugin), description_(What this plugin does.), version1.0.0, pip_dependencies[], # 例如 [python-docx1.1] config_fields[ ConfigField(keyapi_key, label_(API key), typeConfigFieldType.PASSWORD), ConfigField(keyenabled_flag, label_(Some flag), typeConfigFieldType.BOOL, defaultTrue), ], ) def before_transcription(self, task, context: PluginContext): # 运行在 WORKER 线程。可以处理 task.file_path 指向的音频。 # 返回一个新的文件路径以替换源音频或返回 None 保持原样。 # 此处禁止访问数据库或 Qt。 return None def after_transcription(self, task, segments, context: PluginContext): # 运行在 BACKGROUND 线程。返回可能被修改过的Segment 列表。 # 不需要修改时原样返回 segments。 return segments def on_complete(self, transcription_id, task, segments, context: PluginContext): # 运行在 BACKGROUND 线程且发生在转写结果保存到数据库之后。 # 用于基于已持久化转写记录的副作用操作例如写笔记或导出文件。 return None关键约束由 base.py 明确声明metadata是必须设置的类属性所有 Hook 都是可选的默认实现为 no-op不改变流水线行为。PluginMetadata的字段为id必填、name必填、description、version、pip_dependencies、config_fields见 base.py。从加载器 loader.load_plugin_from_dir 可以看到加载时插件会被动态 import随后通过inspect.getmembers查找BuzzPlugin子类恰好找到 0 个或 2 个以上都会抛出PluginLoadError并要求metadata.id存在最后实例化插件对象。因此一个文件夹恰好一个插件子类是硬性约定违反它插件将无法加载。Hook 与线程模型在哪个线程做什么事四个生命周期 Hook 及其线程约束汇总如下Hook线程用途check_skipworker返回list[Segment]以完全跳过本次转写或返回None继续。在before_transcription之后运行。通过context.transcription_service访问数据库是安全的会被编排到主线程。before_transcriptionworker处理/替换源音频返回新的文件路径after_transcriptionbackground在保存前修改/替换转写结果的Segment列表on_completebackground保存完成后的副作用写笔记、导出文件、上传等Hook 只对已启用的插件按用户自定义的顺序执行由PluginManager.enabled_plugins_in_order()保证manager.py。任一 Hook 抛出的异常都会被logger.error记录绝不会中断流水线或其他插件——这是插件系统容错性的核心保证manager.py 中每个run_*调度方法都包了 try/except。为什么区分线程因为 Buzz 的数据库使用线程亲和thread-affine的QSqlDatabase默认连接所有数据库访问必须发生在主线程同时插件工作例如 AI 摘要的网络调用不应阻塞 UI因此在后台线程运行任何数据库访问通过PluginContext.transcription_service被透明地编排回主线程。这一机制由 post_processing.py 的MainThreadInvoker跨线程调度 callable 并阻塞等待返回值与MainThreadServiceProxy把服务的每个方法调用包装为跨线程调用实现。process_completedmanager.py展示了完整链路after_transcription→ 主线程持久化 →on_complete。PluginContext每个 Hook 都能拿到的上下文PluginContextbase.py会被传给每一个 Hook包含四个字段context.config— 已解析的配置字典以ConfigField.key为键包含从钥匙串取回的密码值缺失的值回退到默认值。context.transcription_service— 数据库访问对象。后台 Hook 中安全可用调用自动编排到主线程。常用方法update_transcription_notes(id, text)、replace_transcription_segments(id, segments)、get_transcription_segments(transcription_idid)。context.settings— Buzz 的Settings对象。context.log— 以插件命名的logging.Logger实际名为buzz.plugin.plugin_id见 manager.py。数据类型task是一个FileTranscriptionTask定义于 buzz/transcriber/transcriber.py包含file_path、original_file_path、uid、transcription_options等字段。segments是list[Segment]Segment(start, end, text, translation)transcriber.py中start/end的单位是毫秒。配置字段由 ConfigField 自动生成设置对话框插件的配置通过ConfigField声明ConfigFieldType决定输入控件类型base.py类型控件说明TEXT单行输入框普通文本配置TEXTAREA多行输入框长文本如提示词模板BOOL复选框布尔开关PASSWORD带显示/隐藏切换的掩码输入框存储在操作系统钥匙串keyring中绝不以明文写入普通设置每个字段包含key、label以及可选的type、default、description、placeholder。设置对话框由 Buzz 根据这些声明自动生成插件作者不需要写任何 UI 代码。配置的存取实现在 manager.py读取时get_configPASSWORD字段通过keyring_store.get_secret(_secret_name(plugin_id, field.key))从钥匙串取回密钥名格式为plugin:plugin_id:field_key见 manager.pyBOOL字段会经过_coerce_bool把字符串/整数规范化为布尔值其余字段从 QSettings 读取。写入时set_configPASSWORD字段写入钥匙串其余字段写入 QSettings 的plugins/config/plugin_id/field_key组下。删除插件时remove对应的钥匙串密钥与 QSettings 配置组会被一并清理manager.py。在 Hook 中通过context.config[field.key]读取配置值即可。依赖管理pip 依赖按需安装到共享缓存在metadata.pip_dependencies中列出 pip 需求即可。首次加载时PluginManager._install_deps_if_neededmanager.py会把依赖安装到共享的plugins_deps缓存目录并通过.installed.json标记文件记录「插件 id → 已安装依赖列表」避免重复安装。实践建议AGENTS.md 与源码一致强调优先复用 Buzz 已内置的包例如openai能声明空依赖就声明空依赖安装可能因离线环境失败因此插件应尽量零第三方依赖pip 的跨环境调用冻结构建、沙箱等细节见 buzz/pip_utils.py 与 pip_utils_test.py。本地化JSON 翻译文件与 plugin_gettext插件不能使用 Buzz 编译好的.mo目录。取而代之在plugin.py中调用plugin_gettext(__file__)获得翻译函数惯例命名为_用它包裹所有面向用户的字符串name、description、字段label、消息等。翻译文件放在plugin.py同级的locale/文件夹按区域命名locale/lv_LV.json并支持仅语言的回退文件locale/lv.json。每个文件把英文字符串映射为对应翻译{ My Plugin: Mans spraudnis, API key: API atslēga }翻译函数的实现见 base.py需要注意几个关键点JSON 的key 必须与传给_()的英文字符串完全一致包括标点、括号与空白因为匹配是逐字进行的Python 中通过隐式字符串拼接组装的多行字符串会折叠成一个键因此键中不含换行没有翻译的字符串或活动区域没有匹配文件时原样回退所以不带任何 locale 文件插件也能正常工作活动区域来自 Buzz 的UI_LOCALE设置_current_locale的实现见 base.py。关键警告永远不要用空字符串作为翻译值。翻译器把空值视为无翻译并回退到英文字符串但仅当值为 falsy 时才回退——若 locale 文件中写My Plugin: 会导致插件名在 UI 中显示为空白。要么提供真实翻译要么直接省略该 key。内置插件要为Buzz 支持的每个区域都提供翻译文件当前全集为所有内置插件必须保持同步ca_ES, da_DK, de_DE, es_ES, it_IT, ja_JP, lv_LV, nl, pl_PL, pt_BR, ru, uk_UA, zh_CN, zh_TW新增或修改内置插件的用户可见字符串时必须同步更新所有这些文件。完整的参考示例见 ai_summary/locale/ 或 enhanced_language_detection/locale/新建时直接复制现有插件的文件集合以保证区域列表一致。缓存说明内置插件在启动时被拷贝到~/.cache/Buzz/plugins/。copy_bundled_plugins()会逐个对比内置插件与其缓存副本比较文件集合与文件内容忽略__pycache__/*.pyc构建产物只要 bundle 有差异就刷新缓存——无论是修复了plugin.py还是新增/更新/删除了某个 locale 文件。因此修改内置插件的代码或语言文件后重新启动 Buzz 即可生效无需手动同步。用户安装的插件永远不会被触碰loader.pyloader_test.py 中test_leaves_unchanged_plugin_untouched、test_ignores_pycache_differences等测试验证了这一行为。打包与分发zip 结构与安装流程可分发插件的形态是一个.zip其内容就是插件文件夹。压缩包内plugin.py可以位于根目录也可以位于唯一的单层包装目录中GitHub 风格 zip 也适用。用户通过Help → Plugins → Add by URL安装。标准目录布局my_plugin/ plugin.py # 必填定义且仅定义一个 BuzzPlugin 子类 locale/ # 可选locale.json 翻译文件 lv_LV.json安装流程在 loader.download_and_extract 中实现值得注意的安全与健壮性设计下载超时为 60 秒文件先解压到临时目录解压经过_safe_extract的路径穿越防护zip-slip 防护../之类的成员会被拒绝loader.py_find_plugin_root自动识别扁平布局或单层包装目录布局loader.py在提交到插件目录之前先加载验证保证不会留下半安装状态随后以metadata.id为目录名安装进插件目录已存在同名插件会被整体替换。添加后插件会被自动启用并追加到执行顺序末尾manager.add_from_urlmanager.py。创建插件的完整清单按照 AGENTS.md 的 Checklist 与源码约束整理创建buzz/plugins/plugin_id/plugin.py其中只定义一个BuzzPlugin子类。设置metadata唯一的id与文件夹名一致、name、description以及所需的config_fields。只实现你需要的 Hook并严格遵守上文的线程规则worker 线程禁碰数据库与 Qt后台线程禁碰 Qt widget。用户可见字符串使用plugin_gettext(__file__)添加locale/*.json内置插件需提供 Localization 一节列出的全部区域且 key 必须与英文字符串逐字一致。pip_dependencies保持最小化优先复用内置包。若要随 Buzz 内置发布把插件 id 加入loader.BUNDLED_PLUGIN_IDS并在打包配置Buzz.spec的datas中添加对应条目例如(buzz/plugins/id, plugins/id)。在tests/plugins/下添加测试参考 plugin_system_test.py。参考实现ai_summary 插件深度剖析ai_summary/plugin.py 是随 Buzz 发布的参考插件它把转写后通过 OpenAI 兼容 API 生成摘要并写入 Notes 字段和/或文件完整落地示范了密码配置、本地化与复用内置openai客户端零额外依赖。值得学习的实现细节7 个配置字段覆盖了全部四种ConfigFieldTypeapi_urlTEXT默认https://api.openai.com/v1、api_keyPASSWORD、modelTEXT默认gpt-4o-mini、promptTEXTAREA内置默认摘要提示词、save_to_notesBOOL默认 True、save_to_fileBOOL默认 False、output_folderTEXT留空表示保存到源文件旁。on_complete的防御式写法缺少 API key 或转写文本为空时直接context.log.warning/info后返回不中断网络请求放在_summarize中 try/except失败仅记日志。副作用落地摘要通过context.transcription_service.update_transcription_notes(transcription_id, summary)写回数据库该调用会被自动编排到主线程文件模式则写到输出目录的stem.summary.txt。对应的测试 plugin_system_test.pytest_ai_summary_on_complete_writes_notes用 fake client 验证了on_complete会把摘要写入 Notes。测试与验证插件系统的行为由测试锁定tests/plugins/目录下的测试覆盖了插件系统的几乎所有关键行为是开发插件时的重要参考loader_test.py 验证了目录助手、内置插件拷贝的仅在变更时覆盖语义、插件目录发现、zip 安全解压、单层包装目录识别以及下载/解压/加载失败时的PluginLoadError。plugin_system_test.py 验证了有效/无效插件加载、管理器发现与排序、配置持久化与钥匙串密码存储test_config_persistence_and_password断言密码只进 keyring 不进 QSettings、启停与顺序调整、run_before_transcription/run_after_transcription的调度语义以及各内置插件ai_summary、transcript_resizer、export_docx、enhanced_language_detection、skip_already_transcribed、deep_filter_net的行为。图形界面侧的 plugins_dialog_test.py 与 plugin_settings_dialog_test.py 验证了插件管理对话框与设置对话框的生成逻辑。结语Buzz 插件系统的设计关键词是契约清晰、线程严格、容错优先BuzzPlugin子类 metadata声明身份与配置四个 Hook 分别在 worker/background 线程被调度任何异常只记录不中断数据库访问通过MainThreadServiceProxy透明编排回主线程。对于插件作者而言只需遵循一个插件一个子类、遵守线程规则、用plugin_gettext本地化、最小化依赖这几条约定就能以 zip 形式分发或在BUNDLED_PLUGIN_IDS中登记为内置插件。参考 ai_summary 的实现与 plugin_system_test.py 的测试范式即可快速上手开发自己的 Buzz 插件。【免费下载链接】buzzBuzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper.项目地址: https://gitcode.com/GitHub_Trending/buz/buzz创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →