合并多个更新 (#68)

* 🐳 chore: 优化打包速度

使用国内镜像

* 🎈 perf: 优化打包速度

注释日志的时间信息

* 🐳 chore: 改用docker hub

* 🐎 ci(ci): 自动打包docker

* 🐳 chore: 更新文档

* 🎈 perf: 改用opuslib_next

* 🌈 style: 统一log
This commit is contained in:
kalicyh
2025-02-18 21:33:40 +08:00
committed by kalicyh
parent cf8f2f83a0
commit 55981ec764
11 changed files with 96 additions and 26 deletions
+46
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -1,3 +1,4 @@
# 本地源码运行 # 本地源码运行
## 1.安装基础环境 ## 1.安装基础环境
Generated
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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