Skip to content

Commit 8e12d6e

Browse files
committed
Wip openvoice
1 parent e49ea01 commit 8e12d6e

10 files changed

+344
-3
lines changed

Dockerfile

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ ARG TARGETARCH
1212
ARG TARGETVARIANT
1313

1414
ENV DEBIAN_FRONTEND=noninteractive
15-
ENV EXTERNAL_GRPC_BACKENDS="coqui:/build/backend/python/coqui/run.sh,huggingface-embeddings:/build/backend/python/sentencetransformers/run.sh,petals:/build/backend/python/petals/run.sh,transformers:/build/backend/python/transformers/run.sh,sentencetransformers:/build/backend/python/sentencetransformers/run.sh,rerankers:/build/backend/python/rerankers/run.sh,autogptq:/build/backend/python/autogptq/run.sh,bark:/build/backend/python/bark/run.sh,diffusers:/build/backend/python/diffusers/run.sh,exllama:/build/backend/python/exllama/run.sh,vall-e-x:/build/backend/python/vall-e-x/run.sh,vllm:/build/backend/python/vllm/run.sh,mamba:/build/backend/python/mamba/run.sh,exllama2:/build/backend/python/exllama2/run.sh,transformers-musicgen:/build/backend/python/transformers-musicgen/run.sh,parler-tts:/build/backend/python/parler-tts/run.sh"
15+
ENV EXTERNAL_GRPC_BACKENDS="coqui:/build/backend/python/coqui/run.sh,huggingface-embeddings:/build/backend/python/sentencetransformers/run.sh,petals:/build/backend/python/petals/run.sh,transformers:/build/backend/python/transformers/run.sh,sentencetransformers:/build/backend/python/sentencetransformers/run.sh,rerankers:/build/backend/python/rerankers/run.sh,autogptq:/build/backend/python/autogptq/run.sh,bark:/build/backend/python/bark/run.sh,diffusers:/build/backend/python/diffusers/run.sh,exllama:/build/backend/python/exllama/run.sh,openvoice:/build/backend/python/openvoice/run.sh,vall-e-x:/build/backend/python/vall-e-x/run.sh,vllm:/build/backend/python/vllm/run.sh,mamba:/build/backend/python/mamba/run.sh,exllama2:/build/backend/python/exllama2/run.sh,transformers-musicgen:/build/backend/python/transformers-musicgen/run.sh,parler-tts:/build/backend/python/parler-tts/run.sh"
1616

1717
ARG GO_TAGS="stablediffusion tinydream tts"
1818

@@ -305,6 +305,9 @@ RUN if [[ ( "${EXTRA_BACKENDS}" =~ "coqui" || -z "${EXTRA_BACKENDS}" ) && "$IMAG
305305
RUN if [[ ( "${EXTRA_BACKENDS}" =~ "vall-e-x" || -z "${EXTRA_BACKENDS}" ) && "$IMAGE_TYPE" == "extras" ]]; then \
306306
make -C backend/python/vall-e-x \
307307
; fi && \
308+
if [[ ( "${EXTRA_BACKENDS}" =~ "openvoice" || -z "${EXTRA_BACKENDS}" ) && "$IMAGE_TYPE" == "extras" ]]; then \
309+
make -C backend/python/openvoice \
310+
; fi && \
308311
if [[ ( "${EXTRA_BACKENDS}" =~ "petals" || -z "${EXTRA_BACKENDS}" ) && "$IMAGE_TYPE" == "extras" ]]; then \
309312
make -C backend/python/petals \
310313
; fi && \

Makefile

+11-2
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,10 @@ protogen-go-clean:
445445
$(RM) bin/*
446446

447447
.PHONY: protogen-python
448-
protogen-python: autogptq-protogen bark-protogen coqui-protogen diffusers-protogen exllama-protogen exllama2-protogen mamba-protogen petals-protogen rerankers-protogen sentencetransformers-protogen transformers-protogen parler-tts-protogen transformers-musicgen-protogen vall-e-x-protogen vllm-protogen
448+
protogen-python: autogptq-protogen bark-protogen coqui-protogen diffusers-protogen exllama-protogen exllama2-protogen mamba-protogen petals-protogen rerankers-protogen sentencetransformers-protogen transformers-protogen parler-tts-protogen transformers-musicgen-protogen vall-e-x-protogen vllm-protogen openvoice-protogen
449449

450450
.PHONY: protogen-python-clean
451-
protogen-python-clean: autogptq-protogen-clean bark-protogen-clean coqui-protogen-clean diffusers-protogen-clean exllama-protogen-clean exllama2-protogen-clean mamba-protogen-clean petals-protogen-clean sentencetransformers-protogen-clean rerankers-protogen-clean transformers-protogen-clean transformers-musicgen-protogen-clean parler-tts-protogen-clean vall-e-x-protogen-clean vllm-protogen-clean
451+
protogen-python-clean: autogptq-protogen-clean bark-protogen-clean coqui-protogen-clean diffusers-protogen-clean exllama-protogen-clean exllama2-protogen-clean mamba-protogen-clean petals-protogen-clean sentencetransformers-protogen-clean rerankers-protogen-clean transformers-protogen-clean transformers-musicgen-protogen-clean parler-tts-protogen-clean vall-e-x-protogen-clean vllm-protogen-clean openvoice-protogen-clean
452452

453453
.PHONY: autogptq-protogen
454454
autogptq-protogen:
@@ -562,6 +562,14 @@ vall-e-x-protogen:
562562
vall-e-x-protogen-clean:
563563
$(MAKE) -C backend/python/vall-e-x protogen-clean
564564

565+
.PHONY: openvoice-protogen
566+
openvoice-protogen:
567+
$(MAKE) -C backend/python/openvoice protogen
568+
569+
.PHONY: openvoice-protogen-clean
570+
openvoice-protogen-clean:
571+
$(MAKE) -C backend/python/openvoice protogen-clean
572+
565573
.PHONY: vllm-protogen
566574
vllm-protogen:
567575
$(MAKE) -C backend/python/vllm protogen
@@ -585,6 +593,7 @@ prepare-extra-conda-environments: protogen-python
585593
$(MAKE) -C backend/python/transformers-musicgen
586594
$(MAKE) -C backend/python/parler-tts
587595
$(MAKE) -C backend/python/vall-e-x
596+
$(MAKE) -C backend/python/openvoice
588597
$(MAKE) -C backend/python/exllama
589598
$(MAKE) -C backend/python/petals
590599
$(MAKE) -C backend/python/exllama2

backend/python/openvoice/Makefile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.DEFAULT_GOAL := install
2+
3+
.PHONY: install
4+
install: protogen
5+
bash install.sh
6+
7+
.PHONY: protogen
8+
protogen: backend_pb2_grpc.py backend_pb2.py
9+
10+
.PHONY: protogen-clean
11+
protogen-clean:
12+
$(RM) backend_pb2_grpc.py backend_pb2.py
13+
14+
backend_pb2_grpc.py backend_pb2.py:
15+
python3 -m grpc_tools.protoc -I../.. --python_out=. --grpc_python_out=. backend.proto
16+
17+
.PHONY: clean
18+
clean: protogen-clean
19+
rm -rf venv __pycache__

backend/python/openvoice/backend.py

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Extra gRPC server for OpenVoice models.
4+
"""
5+
from concurrent import futures
6+
7+
import argparse
8+
import signal
9+
import sys
10+
import os
11+
import torch
12+
from openvoice import se_extractor
13+
from openvoice.api import ToneColorConverter
14+
from melo.api import TTS
15+
16+
import time
17+
import backend_pb2
18+
import backend_pb2_grpc
19+
20+
import grpc
21+
22+
23+
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
24+
25+
# If MAX_WORKERS are specified in the environment use it, otherwise default to 1
26+
MAX_WORKERS = int(os.environ.get('PYTHON_GRPC_MAX_WORKERS', '1'))
27+
28+
# Implement the BackendServicer class with the service methods
29+
class BackendServicer(backend_pb2_grpc.BackendServicer):
30+
"""
31+
A gRPC servicer for the backend service.
32+
33+
This class implements the gRPC methods for the backend service, including Health, LoadModel, and Embedding.
34+
"""
35+
def Health(self, request, context):
36+
"""
37+
A gRPC method that returns the health status of the backend service.
38+
39+
Args:
40+
request: A HealthRequest object that contains the request parameters.
41+
context: A grpc.ServicerContext object that provides information about the RPC.
42+
43+
Returns:
44+
A Reply object that contains the health status of the backend service.
45+
"""
46+
return backend_pb2.Reply(message=bytes("OK", 'utf-8'))
47+
48+
def LoadModel(self, request, context):
49+
"""
50+
A gRPC method that loads a model into memory.
51+
52+
Args:
53+
request: A LoadModelRequest object that contains the request parameters.
54+
context: A grpc.ServicerContext object that provides information about the RPC.
55+
56+
Returns:
57+
A Result object that contains the result of the LoadModel operation.
58+
"""
59+
model_name = request.Model
60+
try:
61+
62+
self.clonedVoice = False
63+
# Assume directory from request.ModelFile.
64+
# Only if request.LoraAdapter it's not an absolute path
65+
if request.AudioPath and request.ModelFile != "" and not os.path.isabs(request.AudioPath):
66+
# get base path of modelFile
67+
modelFileBase = os.path.dirname(request.ModelFile)
68+
request.AudioPath = os.path.join(modelFileBase, request.AudioPath)
69+
if request.AudioPath != "":
70+
self.clonedVoice = True
71+
72+
self.modelpath = request.ModelFile
73+
self.speaker = request.Type
74+
self.ClonedVoicePath = request.AudioPath
75+
76+
ckpt_converter = request.Model+'/converter'
77+
device = "cuda:0" if torch.cuda.is_available() else "cpu"
78+
self.device = device
79+
self.tone_color_converter = None
80+
if self.clonedVoice:
81+
self.tone_color_converter = ToneColorConverter(f'{ckpt_converter}/config.json', device=device)
82+
self.tone_color_converter.load_ckpt(f'{ckpt_converter}/checkpoint.pth')
83+
84+
except Exception as err:
85+
return backend_pb2.Result(success=False, message=f"Unexpected {err=}, {type(err)=}")
86+
87+
return backend_pb2.Result(message="Model loaded successfully", success=True)
88+
89+
def TTS(self, request, context):
90+
model_name = request.model
91+
if model_name == "":
92+
return backend_pb2.Result(success=False, message="request.model is required")
93+
try:
94+
# Speed is adjustable
95+
speed = 1.0
96+
model = TTS(language=request.voice, device=self.device)
97+
speaker_ids = model.hps.data.spk2id
98+
speaker_key = self.speaker
99+
modelpath = self.modelpath
100+
for s in speaker_ids.keys():
101+
print(f"Speaker: {s} - ID: {speaker_ids[s]}")
102+
speaker_id = speaker_ids[speaker_key]
103+
speaker_key = speaker_key.lower().replace('_', '-')
104+
source_se = torch.load(f'{modelpath}/base_speakers/ses/{speaker_key}.pth', map_location=self.device)
105+
model.tts_to_file(request.text, speaker_id, request.dst, speed=speed)
106+
if self.clonedVoice:
107+
reference_speaker = self.ClonedVoicePath
108+
target_se, audio_name = se_extractor.get_se(reference_speaker, self.tone_color_converter, vad=False)
109+
# Run the tone color converter
110+
encode_message = "@MyShell"
111+
self.tone_color_converter.convert(
112+
audio_src_path=request.dst,
113+
src_se=source_se,
114+
tgt_se=target_se,
115+
output_path=request.dst,
116+
message=encode_message)
117+
118+
print("[OpenVoice] TTS generated!", file=sys.stderr)
119+
print("[OpenVoice] TTS saved to", request.dst, file=sys.stderr)
120+
print(request, file=sys.stderr)
121+
except Exception as err:
122+
return backend_pb2.Result(success=False, message=f"Unexpected {err=}, {type(err)=}")
123+
return backend_pb2.Result(success=True)
124+
125+
def serve(address):
126+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=MAX_WORKERS))
127+
backend_pb2_grpc.add_BackendServicer_to_server(BackendServicer(), server)
128+
server.add_insecure_port(address)
129+
server.start()
130+
print("[OpenVoice] Server started. Listening on: " + address, file=sys.stderr)
131+
132+
# Define the signal handler function
133+
def signal_handler(sig, frame):
134+
print("[OpenVoice] Received termination signal. Shutting down...")
135+
server.stop(0)
136+
sys.exit(0)
137+
138+
# Set the signal handlers for SIGINT and SIGTERM
139+
signal.signal(signal.SIGINT, signal_handler)
140+
signal.signal(signal.SIGTERM, signal_handler)
141+
142+
try:
143+
while True:
144+
time.sleep(_ONE_DAY_IN_SECONDS)
145+
except KeyboardInterrupt:
146+
server.stop(0)
147+
148+
if __name__ == "__main__":
149+
parser = argparse.ArgumentParser(description="Run the gRPC server.")
150+
parser.add_argument(
151+
"--addr", default="localhost:50051", help="The address to bind the server to."
152+
)
153+
args = parser.parse_args()
154+
print(f"[OpenVoice] startup: {args}", file=sys.stderr)
155+
serve(args.addr)

backend/python/openvoice/install.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source $(dirname $0)/../common/libbackend.sh
5+
6+
# This is here because the Intel pip index is broken and returns 200 status codes for every package name, it just doesn't return any package links.
7+
# This makes uv think that the package exists in the Intel pip index, and by default it stops looking at other pip indexes once it finds a match.
8+
# We need uv to continue falling through to the pypi default index to find optimum[openvino] in the pypi index
9+
# the --upgrade actually allows us to *downgrade* torch to the version provided in the Intel pip index
10+
if [ "x${BUILD_PROFILE}" == "xintel" ]; then
11+
EXTRA_PIP_INSTALL_FLAGS+=" --upgrade --index-strategy=unsafe-first-match"
12+
fi
13+
14+
installRequirements
15+
16+
python -m unidic download
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/
2+
intel-extension-for-pytorch
3+
torch
4+
optimum[openvino]
5+
grpcio==1.63.0
6+
protobuf
7+
librosa==0.9.1
8+
faster-whisper==0.9.0
9+
pydub==0.25.1
10+
wavmark==0.0.3
11+
numpy==1.22.0
12+
eng_to_ipa==0.0.2
13+
inflect==7.0.0
14+
unidecode==1.3.7
15+
whisper-timestamped==1.14.2
16+
openai
17+
python-dotenv
18+
pypinyin==0.50.0
19+
cn2an==0.5.22
20+
jieba==0.42.1
21+
gradio==3.48.0
22+
langid==1.1.6
23+
git+https://github.com/myshell-ai/MeloTTS.git
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
grpcio==1.63.0
2+
protobuf
3+
librosa==0.9.1
4+
faster-whisper==0.9.0
5+
pydub==0.25.1
6+
wavmark==0.0.3
7+
numpy==1.22.0
8+
eng_to_ipa==0.0.2
9+
inflect==7.0.0
10+
unidecode==1.3.7
11+
whisper-timestamped==1.14.2
12+
openai
13+
python-dotenv
14+
pypinyin==0.50.0
15+
cn2an==0.5.22
16+
jieba==0.42.1
17+
gradio==3.48.0
18+
langid==1.1.6
19+
git+https://github.com/myshell-ai/MeloTTS.git

backend/python/openvoice/run.sh

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
source $(dirname $0)/../common/libbackend.sh
3+
4+
startBackend $@

backend/python/openvoice/test.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
A test script to test the gRPC service
3+
"""
4+
import unittest
5+
import subprocess
6+
import time
7+
import backend_pb2
8+
import backend_pb2_grpc
9+
10+
import grpc
11+
12+
13+
class TestBackendServicer(unittest.TestCase):
14+
"""
15+
TestBackendServicer is the class that tests the gRPC service
16+
"""
17+
def setUp(self):
18+
"""
19+
This method sets up the gRPC service by starting the server
20+
"""
21+
self.service = subprocess.Popen(["python3", "backend.py", "--addr", "localhost:50051"])
22+
time.sleep(10)
23+
24+
def tearDown(self) -> None:
25+
"""
26+
This method tears down the gRPC service by terminating the server
27+
"""
28+
self.service.terminate()
29+
self.service.wait()
30+
31+
def test_server_startup(self):
32+
"""
33+
This method tests if the server starts up successfully
34+
"""
35+
try:
36+
self.setUp()
37+
with grpc.insecure_channel("localhost:50051") as channel:
38+
stub = backend_pb2_grpc.BackendStub(channel)
39+
response = stub.Health(backend_pb2.HealthMessage())
40+
self.assertEqual(response.message, b'OK')
41+
except Exception as err:
42+
print(err)
43+
self.fail("Server failed to start")
44+
finally:
45+
self.tearDown()
46+
47+
def test_load_model(self):
48+
"""
49+
This method tests if the model is loaded successfully
50+
"""
51+
try:
52+
self.setUp()
53+
with grpc.insecure_channel("localhost:50051") as channel:
54+
stub = backend_pb2_grpc.BackendStub(channel)
55+
response = stub.LoadModel(backend_pb2.ModelOptions(Model="checkpoints_v2"))
56+
self.assertTrue(response.success)
57+
self.assertEqual(response.message, "Model loaded successfully")
58+
except Exception as err:
59+
print(err)
60+
self.fail("LoadModel service failed")
61+
finally:
62+
self.tearDown()
63+
64+
def test_tts(self):
65+
"""
66+
This method tests if the embeddings are generated successfully
67+
"""
68+
try:
69+
self.setUp()
70+
with grpc.insecure_channel("localhost:50051") as channel:
71+
stub = backend_pb2_grpc.BackendStub(channel)
72+
response = stub.LoadModel(backend_pb2.ModelOptions(Model="dingzhen"))
73+
self.assertTrue(response.success)
74+
tts_request = backend_pb2.TTSRequest(text="80s TV news production music hit for tonight's biggest story")
75+
tts_response = stub.TTS(tts_request)
76+
self.assertIsNotNone(tts_response)
77+
except Exception as err:
78+
print(err)
79+
self.fail("TTS service failed")
80+
finally:
81+
self.tearDown()

backend/python/openvoice/test.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
set -e
3+
4+
source $(dirname $0)/../common/libbackend.sh
5+
6+
# Download checkpoints if not present
7+
if [ ! -d "checkpoints_v2" ]; then
8+
wget https://myshell-public-repo-hosting.s3.amazonaws.com/openvoice/checkpoints_v2_0417.zip -O checkpoints_v2.zip
9+
unzip checkpoints_v2.zip
10+
fi
11+
12+
runUnittests

0 commit comments

Comments
 (0)