Skip to content

Commit

Permalink
feat: 添加语音插件和本地服务示例
Browse files Browse the repository at this point in the history
  • Loading branch information
xnmeet committed Jan 18, 2025
0 parents commit 0f14402
Show file tree
Hide file tree
Showing 19 changed files with 790 additions and 0 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Release Bob Plugin

on:
push:
branches:
- main
paths:
- 'bob-plugin/**'

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
sparse-checkout: |
.github
bob-plugin
release.py
appcast.json
sparse-checkout-cone-mode: false

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Get latest tag
id: get_latest_tag
run: |
git fetch --tags
latest_tag=$(git tag | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1)
if [ -z "$latest_tag" ]; then
echo "version=v0.0.1" >> $GITHUB_OUTPUT
else
current_version=${latest_tag#v}
IFS='.' read -r major minor patch <<< "$current_version"
new_version="v$major.$minor.$((patch + 1))"
echo "version=$new_version" >> $GITHUB_OUTPUT
fi
- name: Create Release Package
run: |
version="${{ steps.get_latest_tag.outputs.version }}"
version_number=${version#v}
python release.py $version_number
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.get_latest_tag.outputs.version }}
name: Release ${{ steps.get_latest_tag.outputs.version }}
files: |
voi-*.bobplugin
appcast.json
generate_release_notes: true
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.venv
.DS_Store
*.bobplugin
*.onnx
voices.json
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Bob Kokoro TTS Plugin

一个基于 [Bob](https://bobtranslate.com/) 的文本转语音插件,使用 Kokoro 本地部署模型作为语音合成服务。

## 项目结构

本项目包含两个主要部分:

1. **Bob 插件** (`bob-plugin/`): Bob 的文本转语音插件
2. **TTS 服务器** (`server/`): Kokoro TTS 本地服务器

## 快速开始

### 1. 部署 TTS 服务器

首先下载必需的模型文件:

```bash
cd server
# 下载 ONNX 模型文件
wget https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/kokoro-v0_19.onnx
# 下载声音配置文件
wget https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/voices.json
```

然后选择以下任一方式部署服务器:

```bash
# 方式一:直接运行
pip install -r requirements.txt
python server.py

# 方式二:Docker(推荐)
docker build -f Dockerfile.conda -t kokoro-tts-conda .
docker run -p 8000:8000 kokoro-tts-conda
```

详细说明请参考 [服务器文档](server/README.md)

### 2. 安装 Bob 插件

1. 下载最新版本的插件([Releases](https://github.com/xnmeet/voi/releases/latest)
2. 安装 `.bobplugin` 文件到 Bob 中
3. 在 Bob 的偏好设置中配置服务器地址(例如:`http://localhost:8000/text-to-speech`

详细说明请参考 [插件文档](bob-plugin/README.md)

## 问题反馈

如果您在使用过程中遇到任何问题,请通过以下方式反馈:

1. 在 GitHub 上提交 Issue
2. 发送邮件至 [email protected]

## 许可证

[MIT License](LICENSE)
12 changes: 12 additions & 0 deletions appcast.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"identifier": "com.meet.bob-plugin-voi",
"versions": [
{
"version": "0.0.1",
"desc": "auto",
"sha256": "19b53f798e02d37adf685ed5470813f88bfa270dcfdb0855c0d6002deb7ecab9",
"url": "https://raw.githubusercontent.com/xnmeet/voi/releases/download/v0.0.2/voi-0.0.2.bobplugin",
"minBobVersion": "0.5.0"
}
]
}
51 changes: 51 additions & 0 deletions bob-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Bob Kokoro TTS Plugin

Bob 的文本转语音插件,使用 Kokoro 本地部署模型作为语音合成服务。

## 功能特点

- 支持多种声音选项
- 可自定义服务器接口地址
- 支持 Base64 格式的音频输出
- 与 Bob 无缝集成

## 安装要求

- Bob 版本 ≥ 1.6.0
- 需要有运行中的 Kokoro TTS 服务器

## 插件配置

### 必需配置

- **服务器地址**: 设置 Kokoro TTS 服务器的完整地址(例如:`http://localhost:8000/synthesize`

### 可选配置

- **声音选择**: 可选择不同的声音模型
- af: 默认声音
- af_bella: Bella 声音
- af_nicole: Nicole 声音
- af_sarah: Sarah 声音
- af_sky: Sky 声音
- am_adam: Adam 声音
- am_michael: Michael 声音
- bf_emma: Emma 声音
- bf_isabella: Isabella 声音
- bm_george: George 声音
- bm_lewis: Lewis 声音

## 使用方法

1. 在 Bob 中选择文本
2. 选择 Kokoro TTS 作为语音合成服务
3. 点击播放按钮即可听到合成的语音

## 开发说明

本插件使用 GitHub Actions 进行自动化发布。当插件代码有更新时,会自动:

1. 创建新的版本号
2. 生成插件包
3. 更新 `appcast.json`
4. 发布新版本
3 changes: 3 additions & 0 deletions bob-plugin/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const supportedLanguages = [['en', 'en']];

exports.supportedLanguages = supportedLanguages;
Binary file added bob-plugin/src/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions bob-plugin/src/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"identifier": "com.meet.bob-plugin-voi",
"version": "0.0.1",
"category": "tts",
"name": "Kokoro 语音",
"summary": "使用 Kokoro 本地部署模型作为语音合成服务",
"icon": "icon.png",
"author": "xnmeet <[email protected]>",
"homepage": "https://github.com/xnmeet/voi",
"appcast": "https://raw.githubusercontent.com/xnmeet/voi/main/appcast.json",
"minBobVersion": "1.6.0",
"options": [
{
"identifier": "baseUrl",
"type": "text",
"title": "自定义接口完整地址"
},
{
"identifier": "voice",
"type": "menu",
"title": "声色",
"defaultValue": "af",
"menuValues": [
{ "title": "af", "value": "af" },
{ "title": "af_bella", "value": "af_bella" },
{ "title": "af_nicole", "value": "af_nicole" },
{ "title": "af_sarah", "value": "af_sarah" },
{ "title": "af_sky", "value": "af_sky" },
{ "title": "am_adam", "value": "am_adam" },
{ "title": "am_michael", "value": "am_michael" },
{ "title": "bf_emma", "value": "bf_emma" },
{ "title": "bf_isabella", "value": "bf_isabella" },
{ "title": "bm_george", "value": "bm_george" },
{ "title": "bm_lewis", "value": "bm_lewis" }
]
}
]
}
47 changes: 47 additions & 0 deletions bob-plugin/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var config = require('./config.js');
var utils = require('./utils.js');

function supportLanguages() {
return config.supportedLanguages.map(([standardLang]) => standardLang);
}

function tts(query, completion) {
const targetLanguage = utils.langMap.get(query.lang);
if (!targetLanguage) {
const err = new Error(`不支持 ${query.lang} 语种`);
throw err;
}
const originText = query.text;

try {
$http.request({
method: 'POST',
url: $option.baseUrl,
header: {
'Content-Type': 'application/json'
},
body: {
text: originText,
voice: $option.voice,
speed: 1,
stream: false,
format: 'base64'
},
handler: function (resp) {
var rawData = resp.data.audio_data;
completion({
result: {
type: 'base64',
value: rawData,
raw: rawData
}
});
}
});
} catch (e) {
$log.error(e);
}
}

exports.supportLanguages = supportLanguages;
exports.tts = tts;
7 changes: 7 additions & 0 deletions bob-plugin/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var config = require('./config.js');

const langMap = new Map(config.supportedLanguages);
const langMapReverse = new Map(config.supportedLanguages.map(([standardLang, lang]) => [lang, standardLang]));

exports.langMap = langMap;
exports.langMapReverse = langMapReverse;
80 changes: 80 additions & 0 deletions release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import os
import json
import hashlib
import shutil
from pathlib import Path
import argparse
from datetime import datetime

def calculate_sha256(filename):
sha256_hash = hashlib.sha256()
with open(filename, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()

def create_plugin_package(version):
# Create zip file from src directory
src_dir = "bob-plugin"
output_name = f"voi-{version}"

# Create zip archive
shutil.make_archive(output_name, 'zip', src_dir)

# Rename to .bobplugin
plugin_file = f"{output_name}.bobplugin"
os.rename(f"{output_name}.zip", plugin_file)

return plugin_file

def update_appcast(version, plugin_file):
appcast_file = "appcast.json"

# Calculate SHA256
sha256 = calculate_sha256(plugin_file)

# Read existing appcast.json
if os.path.exists(appcast_file):
with open(appcast_file, 'r') as f:
appcast = json.load(f)
else:
appcast = {
"identifier": "com.meet.bob-plugin-voi",
"versions": []
}

# Create new version entry
new_version = {
"version": version,
"desc": "auto",
"sha256": sha256,
"url": f"https://raw.githubusercontent.com/xnmeet/voi/releases/download/v{version}/{plugin_file}",
"minBobVersion": "0.5.0"
}

# Add or update version
versions = appcast["versions"]
for i, v in enumerate(versions):
if v["version"] == version:
versions[i] = new_version
break
else:
versions.insert(0, new_version)

# Write updated appcast.json
with open(appcast_file, 'w') as f:
json.dump(appcast, f, indent=2)

def main():
parser = argparse.ArgumentParser(description='Create Bob plugin package and update appcast.json')
parser.add_argument('version', help='Version number (e.g., 0.0.1)')
args = parser.parse_args()

plugin_file = create_plugin_package(args.version)
update_appcast(args.version, plugin_file)
print(f"Created plugin package: {plugin_file}")
print("Updated appcast.json")

if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.pytest_cache
.env
.venv
.DS_Store
Loading

0 comments on commit 0f14402

Please sign in to comment.