mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
合并多个更新 (#68)
* 🐳 chore: 优化打包速度 使用国内镜像 * 🎈 perf: 优化打包速度 注释日志的时间信息 * 🐳 chore: 改用docker hub * 🐎 ci(ci): 自动打包docker * 🐳 chore: 更新文档 * 🎈 perf: 改用opuslib_next * 🌈 style: 统一log
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
name: Docker Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release Docker image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out the repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to the GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build_push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
ghcr.io/${{ github.repository }}:${{ env.VERSION }}
|
||||||
|
ghcr.io/${{ github.repository }}:latest
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
+6
-2
@@ -1,8 +1,11 @@
|
|||||||
# 第一阶段:前端构建
|
# 第一阶段:前端构建
|
||||||
FROM ccr.ccs.tencentyun.com/kalicyh/node:18-alpine AS frontend-builder
|
|
||||||
|
FROM kalicyh/node:v18-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /app/ZhiKongTaiWeb
|
WORKDIR /app/ZhiKongTaiWeb
|
||||||
|
|
||||||
|
# RUN corepack enable && yarn config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
COPY ZhiKongTaiWeb/package.json ZhiKongTaiWeb/yarn.lock ./
|
COPY ZhiKongTaiWeb/package.json ZhiKongTaiWeb/yarn.lock ./
|
||||||
|
|
||||||
RUN yarn install --frozen-lockfile
|
RUN yarn install --frozen-lockfile
|
||||||
@@ -11,7 +14,8 @@ COPY ZhiKongTaiWeb .
|
|||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
# 第二阶段:构建 Python 依赖
|
# 第二阶段:构建 Python 依赖
|
||||||
FROM ccr.ccs.tencentyun.com/kalicyh/poetry:v3.10_xiaozhi AS builder
|
|
||||||
|
FROM kalicyh/poetry:v3.10_xiaozhi AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import os
|
import os
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import opuslib
|
import opuslib_next
|
||||||
from pydub import AudioSegment
|
from pydub import AudioSegment
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class TTSProviderBase(ABC):
|
|||||||
raw_data = audio.raw_data
|
raw_data = audio.raw_data
|
||||||
|
|
||||||
# 初始化Opus编码器
|
# 初始化Opus编码器
|
||||||
encoder = opuslib.Encoder(16000, 1, opuslib.APPLICATION_AUDIO)
|
encoder = opuslib_next.Encoder(16000, 1, opuslib_next.APPLICATION_AUDIO)
|
||||||
|
|
||||||
# 编码参数
|
# 编码参数
|
||||||
frame_duration = 60 # 60ms per frame
|
frame_duration = 60 # 60ms per frame
|
||||||
|
|||||||
+29
-11
@@ -1,18 +1,36 @@
|
|||||||
import time
|
import time
|
||||||
import wave
|
import wave
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from typing import Optional, Tuple, List
|
from typing import Optional, Tuple, List
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import opuslib
|
import opuslib_next
|
||||||
from funasr import AutoModel
|
from funasr import AutoModel
|
||||||
from funasr.utils.postprocess_utils import rich_transcription_postprocess
|
from funasr.utils.postprocess_utils import rich_transcription_postprocess
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
# 捕获标准输出
|
||||||
|
class CaptureOutput:
|
||||||
|
def __enter__(self):
|
||||||
|
self._output = io.StringIO()
|
||||||
|
self._original_stdout = sys.stdout
|
||||||
|
sys.stdout = self._output
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
sys.stdout = self._original_stdout
|
||||||
|
self.output = self._output.getvalue()
|
||||||
|
self._output.close()
|
||||||
|
|
||||||
|
# 将捕获到的内容通过 logger 输出
|
||||||
|
if self.output:
|
||||||
|
logger.bind(tag=TAG).info(self.output.strip())
|
||||||
|
|
||||||
class ASR(ABC):
|
class ASR(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
||||||
@@ -33,28 +51,28 @@ class FunASR(ASR):
|
|||||||
|
|
||||||
# 确保输出目录存在
|
# 确保输出目录存在
|
||||||
os.makedirs(self.output_dir, exist_ok=True)
|
os.makedirs(self.output_dir, exist_ok=True)
|
||||||
|
with CaptureOutput():
|
||||||
self.model = AutoModel(
|
self.model = AutoModel(
|
||||||
model=self.model_dir,
|
model=self.model_dir,
|
||||||
vad_kwargs={"max_single_segment_time": 30000},
|
vad_kwargs={"max_single_segment_time": 30000},
|
||||||
disable_update=True,
|
disable_update=True,
|
||||||
hub="hf"
|
hub="hf"
|
||||||
# device="cuda:0", # 启用GPU加速
|
# device="cuda:0", # 启用GPU加速
|
||||||
)
|
)
|
||||||
|
|
||||||
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, opus_data: List[bytes], session_id: str) -> str:
|
||||||
"""将Opus音频数据解码并保存为WAV文件"""
|
"""将Opus音频数据解码并保存为WAV文件"""
|
||||||
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
file_name = f"asr_{session_id}_{uuid.uuid4()}.wav"
|
||||||
file_path = os.path.join(self.output_dir, file_name)
|
file_path = os.path.join(self.output_dir, file_name)
|
||||||
|
|
||||||
decoder = opuslib.Decoder(16000, 1) # 16kHz, 单声道
|
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
||||||
pcm_data = []
|
pcm_data = []
|
||||||
|
|
||||||
for opus_packet in opus_data:
|
for opus_packet in opus_data:
|
||||||
try:
|
try:
|
||||||
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
||||||
pcm_data.append(pcm_frame)
|
pcm_data.append(pcm_frame)
|
||||||
except opuslib.OpusError as e:
|
except opuslib_next.OpusError as e:
|
||||||
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
||||||
|
|
||||||
with wave.open(file_path, "wb") as wf:
|
with wave.open(file_path, "wb") as wf:
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import opuslib
|
import opuslib_next
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
@@ -24,7 +24,7 @@ class SileroVAD(VAD):
|
|||||||
force_reload=False)
|
force_reload=False)
|
||||||
(get_speech_timestamps, _, _, _, _) = self.utils
|
(get_speech_timestamps, _, _, _, _) = self.utils
|
||||||
|
|
||||||
self.decoder = opuslib.Decoder(16000, 1)
|
self.decoder = opuslib_next.Decoder(16000, 1)
|
||||||
self.vad_threshold = config.get("threshold")
|
self.vad_threshold = config.get("threshold")
|
||||||
self.silence_threshold_ms = config.get("min_silence_duration_ms")
|
self.silence_threshold_ms = config.get("min_silence_duration_ms")
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class SileroVAD(VAD):
|
|||||||
conn.client_have_voice_last_time = time.time() * 1000
|
conn.client_have_voice_last_time = time.time() * 1000
|
||||||
|
|
||||||
return client_have_voice
|
return client_have_voice
|
||||||
except opuslib.OpusError as e:
|
except opuslib_next.OpusError as e:
|
||||||
logger.bind(tag=TAG).info(f"解码错误: {e}")
|
logger.bind(tag=TAG).info(f"解码错误: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error processing audio packet: {e}")
|
logger.bind(tag=TAG).error(f"Error processing audio packet: {e}")
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
xiaozhi-esp32-server:
|
xiaozhi-esp32-server:
|
||||||
image: ccr.ccs.tencentyun.com/kalicyh/esp32-ai-server:latest
|
image: ghcr.io/kalicyh/xiaozhi-esp32-server:latest
|
||||||
container_name: xiaozhi-esp32-server
|
container_name: xiaozhi-esp32-server
|
||||||
restart: always
|
restart: always
|
||||||
#security_opt:
|
#security_opt:
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconf
|
|||||||
-p 8000:8000 \
|
-p 8000:8000 \
|
||||||
-p 8002:8002 \
|
-p 8002:8002 \
|
||||||
-v ./:/app \
|
-v ./:/app \
|
||||||
ccr.ccs.tencentyun.com/kalicyh/poetry:v3.10_latest
|
kalicyh/poetry:v3.10_latest
|
||||||
```
|
```
|
||||||
|
|
||||||
然后就和正常开发一样了
|
然后就和正常开发一样了
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# 本地源码运行
|
# 本地源码运行
|
||||||
|
|
||||||
## 1.安装基础环境
|
## 1.安装基础环境
|
||||||
|
|||||||
Generated
+5
-4
@@ -2202,14 +2202,15 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
|
|||||||
realtime = ["websockets (>=13,<15)"]
|
realtime = ["websockets (>=13,<15)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opuslib"
|
name = "opuslib-next"
|
||||||
version = "3.0.1"
|
version = "1.1.2"
|
||||||
description = "Python bindings to the libopus, IETF low-delay audio codec"
|
description = "Python bindings to the libopus, IETF low-delay audio codec"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "opuslib-3.0.1.tar.gz", hash = "sha256:2cb045e5b03e7fc50dfefe431e3404dddddbd8f5961c10c51e32dfb69a044c97"},
|
{file = "opuslib_next-1.1.2-py2.py3-none-any.whl", hash = "sha256:adc432290ed721febff19dc0deb3b0cdbe846e80cd79fec742a7d47d960f13ed"},
|
||||||
|
{file = "opuslib_next-1.1.2.tar.gz", hash = "sha256:d44a63c69783ab3ccaf349d46a1e37741eb620d6391461a18f95ac1b538e6796"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3731,4 +3732,4 @@ propcache = ">=0.2.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.10.16"
|
python-versions = "^3.10.16"
|
||||||
content-hash = "a4198be98d482e4393ef98f074b2c62c59786c8ef679adc2285473d1b9eb7d92"
|
content-hash = "9d724dd1cc50a7a6a42f0b5d8a81ad788332939ffc6a3ba12537106567f4ebe7"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,6 @@ pyyml = "0.0.2"
|
|||||||
torch = "2.2.2"
|
torch = "2.2.2"
|
||||||
silero-vad = "5.1.2"
|
silero-vad = "5.1.2"
|
||||||
websockets = "14.2"
|
websockets = "14.2"
|
||||||
opuslib = "3.0.1"
|
|
||||||
numpy = "1.26.4"
|
numpy = "1.26.4"
|
||||||
pydub = "0.25.1"
|
pydub = "0.25.1"
|
||||||
funasr = "1.2.3"
|
funasr = "1.2.3"
|
||||||
@@ -26,6 +25,7 @@ ormsgpack = "1.7.0"
|
|||||||
ruamel-yaml = "0.18.10"
|
ruamel-yaml = "0.18.10"
|
||||||
setuptools = "^75.8.0"
|
setuptools = "^75.8.0"
|
||||||
loguru = "^0.7.3"
|
loguru = "^0.7.3"
|
||||||
|
opuslib-next = "^1.1.2"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ pyyml==0.0.2
|
|||||||
torch==2.2.2
|
torch==2.2.2
|
||||||
silero_vad==5.1.2
|
silero_vad==5.1.2
|
||||||
websockets==14.2
|
websockets==14.2
|
||||||
opuslib==3.0.1
|
opuslib_next==1.1.2
|
||||||
numpy==1.26.4
|
numpy==1.26.4
|
||||||
pydub==0.25.1
|
pydub==0.25.1
|
||||||
funasr==1.2.3
|
funasr==1.2.3
|
||||||
|
|||||||
Reference in New Issue
Block a user