From 78951e9ffae3685cf171247326276239f443ed99 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 2 Dec 2024 18:32:36 +0700 Subject: [PATCH 1/7] update readme, pyproject and add voice clone api --- README.md | 5 +- README_VN.md | 3 - pyproject.toml | 8 +-- viettts/cli.py | 3 +- viettts/server.py | 144 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 139 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 601dd5f..6b107c7 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,8 @@ docker compose build # Run with docker-compose - will create server at: http://localhost:8298 docker compose up -d -# Run with docker run - will create server at: http://localhost:8298 +# Or run with docker run - will create server at: http://localhost:8298 docker run -itd --gpu=alls -p 8298:8298 -v ./pretrained-models:/app/pretrained-models -n viet-tts-service viet-tts:latest viettts server --host 0.0.0.0 --port 8298 - -# Show available voices -docker exec viet-tts-service viettts show-voices ``` ## 🚀 Usage diff --git a/README_VN.md b/README_VN.md index c907c31..f4f9f12 100644 --- a/README_VN.md +++ b/README_VN.md @@ -53,9 +53,6 @@ docker compose up -d # Chạy bằng docker run - tạo server tại: http://localhost:8298 docker run -itd --gpu=alls -p 8298:8298 -v ./pretrained-models:/app/pretrained-models -n viet-tts-service viet-tts:latest viettts server --host 0.0.0.0 --port 8298 - -# Hiển thị danh sách giọng nói sẵn có -docker exec viet-tts-service viettts show-voices ``` ## 🚀 Sử dụng diff --git a/pyproject.toml b/pyproject.toml index 3189f0a..d4b1b78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "viet-tts" +name = "viettts" version = "0.1.0" description = "VietTTS: An Open-Source Vietnamese Text to Speech" authors = ["dangvansam "] @@ -8,17 +8,14 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" conformer = "0.3.2" -deepspeed = "0.14.2" diffusers = "0.27.2" gradio = "4.32.2" hydra-core = "1.3.2" hyperpyyaml = "1.2.2" librosa = "0.10.2" -networkx = "3.1" omegaconf = "2.3.0" onnx = "1.16.0" onnxruntime-gpu = "1.16.0" -openai-whisper = "20231117" protobuf = "4.25" pydantic = "2.7.0" soundfile = "0.12.1" @@ -29,7 +26,6 @@ wget = "3.2" fastapi = "0.111.0" fastapi-cli = "0.0.4" loguru = "0.7.2" -natsort = "8.4.0" vinorm = "^2.0.7" huggingface-hub = "0.24.7" click = "^8.1.7" @@ -37,7 +33,7 @@ gunicorn = "^23.0.0" silero-vad = "^5.1.2" [tool.poetry.scripts] -viet-tts = "viettts.cli:cli" +viettts = "viettts.cli:cli" [build-system] requires = ["poetry-core"] diff --git a/viettts/cli.py b/viettts/cli.py index e9dc288..957469c 100644 --- a/viettts/cli.py +++ b/viettts/cli.py @@ -107,7 +107,8 @@ def cli(): """ VietTTS CLI v0.1.0 - Vietnamese Text To Speech and Voice Clone - License: Apache 2.0 - Author: + Vietnamese Text To Speech and Voice Clone + License: Apache 2.0 - Author: """ pass diff --git a/viettts/server.py b/viettts/server.py index 0fdca90..6d470bd 100644 --- a/viettts/server.py +++ b/viettts/server.py @@ -1,18 +1,22 @@ import io import os import queue +import random import subprocess import threading import wave +import tempfile +import shutil +import requests import numpy as np from loguru import logger from typing import Any, List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel from anyio import CapacityLimiter from anyio.lowlevel import RunVar -from fastapi import FastAPI, UploadFile, Form, File -from fastapi.responses import StreamingResponse, JSONResponse, PlainTextResponse +from fastapi import FastAPI, UploadFile, Form, File, HTTPException +from fastapi.responses import StreamingResponse, JSONResponse, PlainTextResponse, FileResponse from fastapi.middleware.cors import CORSMiddleware from viettts.tts import TTS @@ -27,8 +31,12 @@ app = FastAPI( - title="VietTTS", - description="VietTTS API" + title="VietTTS API", + description=""" + VietTTS API (https://github.comdangvansam/viet-tts) + Vietnamese Text To Speech and Voice Clone + License: Apache 2.0 - Author: + """ ) app.add_middleware( CORSMiddleware, @@ -47,13 +55,17 @@ def generate_data(model_output): yield audio -class GenerateSpeechRequest(BaseModel): +class OpenAITTSRequest(BaseModel): input: str model: str = "tts-1" - voice: str = list(VOICE_MAP)[1] + voice: str = random.choice(list(VOICE_MAP)) response_format: str = "wav" speed: float = 1.0 +class TTSRequest(BaseModel): + text: str + voice: str = random.choice(list(VOICE_MAP)) + speed: float = 1.0 def wav_chunk_header(sample_rate=22050, bit_depth=16, channels=1): buffer = io.BytesIO() @@ -69,7 +81,11 @@ def wav_chunk_header(sample_rate=22050, bit_depth=16, channels=1): @app.get("/", response_class=PlainTextResponse) async def root(): - return 'VietTTS API Server Is Runing' + return 'VietTTS API' + +@app.get("/health", response_class=PlainTextResponse) +async def health(): + return 'VietTTS API is running...' @app.get("/voices") @app.get("/v1/voices") @@ -78,8 +94,9 @@ async def show_voices(): @app.post("/audio/speech") @app.post("/v1/audio/speech") -async def tts(tts_request: GenerateSpeechRequest): - logger.info(f"Generate speech request: {tts_request.dict()}") +async def openai_api_tts(tts_request: OpenAITTSRequest): + logger.info(f"Received TTS request: {tts_request.dict()}") + if tts_request.voice.isdigit(): voice_file = list(VOICE_MAP.values())[int(tts_request.voice)] else: @@ -199,6 +216,113 @@ async def cleanup(): background=cleanup ) +@app.post("/tts") +@app.post("/v1/tts") +async def tts( + text: str = Form(...), + voice: str = Form("0"), + speed: float = Form(1.0), + audio_url: str = Form(None), + audio_file: UploadFile = File(None) +): + logger.info(f"Received TTS request: text={text}, voice={voice}, speed={speed}, audio_url={audio_url}") + voice_file = None + + # Case 1: Uploaded audio file + if audio_file: + temp_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") + try: + with open(temp_audio_file.name, "wb") as temp_file: + shutil.copyfileobj(audio_file.file, temp_file) + voice_file = temp_audio_file.name + logger.info(f"Using uploaded audio file as voice: {voice_file}") + finally: + audio_file.file.close() + + # Case 2: Audio URL + elif audio_url: + temp_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") + try: + response = requests.get(audio_url, stream=True) + if response.status_code != 200: + raise HTTPException(status_code=400, detail="Failed to fetch audio from URL") + with open(temp_audio_file.name, "wb") as temp_file: + shutil.copyfileobj(response.raw, temp_file) + voice_file = temp_audio_file.name + logger.info(f"Using audio URL as voice: {voice_file}") + finally: + response.close() + + # Case 3: Predefined voice + elif voice: + if voice.isdigit(): + voice_file = list(VOICE_MAP.values())[int(voice)] + else: + voice_file = VOICE_MAP.get(voice) + + if not voice_file: + logger.error(f"Voice {voice} not found") + raise HTTPException(status_code=404, detail="Voice not found") + + else: + voice_file = random.choice(list(VOICE_MAP.values())) + + # Error if no voice file is available + if not voice_file or not os.path.exists(voice_file): + raise HTTPException(status_code=400, detail="No valid voice file provided") + + # Load prompt speech + prompt_speech_16k = load_prompt_speech_from_file( + filepath=voice_file, + min_duration=3, + max_duration=5 + ) + + # Temporary file for audio output + temp_output_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") + + try: + # Generate TTS output + model_output = tts_obj.inference_tts( + tts_text=text, + prompt_speech_16k=prompt_speech_16k, + speed=speed, + stream=False + ) + + # Combine audio chunks and pass to ffmpeg for processing + raw_audio = b''.join(chunk['tts_speech'].numpy().tobytes() for chunk in model_output) + ffmpeg_args = [ + "ffmpeg", "-loglevel", "error", "-y", "-f", "f32le", "-ar", "24000", "-ac", "1", + "-i", "-", "-f", "mp3", "-c:a", "libmp3lame", "-ab", "64k", temp_output_file.name + ] + ffmpeg_proc = subprocess.run( + ffmpeg_args, + input=raw_audio, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + if ffmpeg_proc.returncode != 0: + logger.error(f"FFmpeg error: {ffmpeg_proc.stderr.decode()}") + raise HTTPException(status_code=500, detail="Error during audio processing") + + return FileResponse( + path=temp_output_file.name, + media_type="audio/mpeg", + filename="output.mp3" + ) + + finally: + # Clean up temporary files + print(temp_output_file.name) + + if os.path.exists(temp_output_file.name): + os.unlink(temp_output_file.name) + if audio_file or audio_url: + if os.path.exists(temp_audio_file.name): + os.unlink(temp_audio_file.name) + @app.on_event("startup") async def startup(): From 6ec5f8aa3fef08d86ecb3ab0e77d51b73c111a7a Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 14:58:59 +0700 Subject: [PATCH 2/7] update tts voice clone api and docs --- .gitignore | 3 +- README.md | 2 +- README_VN.md | 2 +- viettts/flow/flow.py | 60 ++++++++++++++++++++++++++----------- viettts/server.py | 33 +++++++++++--------- viettts/utils/file_utils.py | 60 +++++++++++++++++++++++++++++++++++-- web/.gitkeep | 0 7 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 web/.gitkeep diff --git a/.gitignore b/.gitignore index d1c4380..a4199c3 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,4 @@ node_modules pretrained-models/* *_pb2_grpc.py *_pb2.py -poetry.lock -web \ No newline at end of file +poetry.lock \ No newline at end of file diff --git a/README.md b/README.md index 6b107c7..32b0b0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- +

VietTTS: An Open-Source Vietnamese Text to Speech

diff --git a/README_VN.md b/README_VN.md index f4f9f12..6e34fb3 100644 --- a/README_VN.md +++ b/README_VN.md @@ -1,5 +1,5 @@

+  

VietTTS: Công cụ chuyển văn bản thành giọng nói tiếng Việt mã nguồn mở

diff --git a/viettts/flow/flow.py b/viettts/flow/flow.py index 9408037..44375a2 100644 --- a/viettts/flow/flow.py +++ b/viettts/flow/flow.py @@ -10,23 +10,49 @@ class MaskedDiffWithXvec(torch.nn.Module): def __init__(self, - input_size: int = 512, - output_size: int = 80, - spk_embed_dim: int = 192, - output_type: str = "mel", - vocab_size: int = 4096, - input_frame_rate: int = 50, - only_mask_loss: bool = True, - encoder: torch.nn.Module = None, - length_regulator: torch.nn.Module = None, - decoder: torch.nn.Module = None, - decoder_conf: Dict = {'in_channels': 240, 'out_channel': 80, 'spk_emb_dim': 80, 'n_spks': 1, - 'cfm_params': DictConfig({'sigma_min': 1e-06, 'solver': 'euler', 't_scheduler': 'cosine', - 'training_cfg_rate': 0.2, 'inference_cfg_rate': 0.7, 'reg_loss_type': 'l1'}), - 'decoder_params': {'channels': [256, 256], 'dropout': 0.0, 'attention_head_dim': 64, - 'n_blocks': 4, 'num_mid_blocks': 12, 'num_heads': 8, 'act_fn': 'gelu'}}, - mel_feat_conf: Dict = {'n_fft': 1024, 'num_mels': 80, 'sampling_rate': 22050, - 'hop_size': 256, 'win_size': 1024, 'fmin': 0, 'fmax': 8000}): + input_size: int = 512, + output_size: int = 80, + spk_embed_dim: int = 192, + output_type: str = "mel", + vocab_size: int = 4096, + input_frame_rate: int = 50, + only_mask_loss: bool = True, + encoder: torch.nn.Module = None, + length_regulator: torch.nn.Module = None, + decoder: torch.nn.Module = None, + decoder_conf: Dict = { + 'in_channels': 240, + 'out_channel': 80, + 'spk_emb_dim': 80, + 'n_spks': 1, + 'cfm_params': DictConfig({ + 'sigma_min': 1e-06, + 'solver': 'euler', + 't_scheduler': 'cosine', + 'training_cfg_rate': 0.2, + 'inference_cfg_rate': 0.7, + 'reg_loss_type': 'l1' + }), + 'decoder_params': { + 'channels': [256, 256], + 'dropout': 0.0, + 'attention_head_dim': 64, + 'n_blocks': 4, + 'num_mid_blocks': 12, + 'num_heads': 8, + 'act_fn': 'gelu' + } + }, + mel_feat_conf: Dict = { + 'n_fft': 1024, + 'num_mels': 80, + 'sampling_rate': 22050, + 'hop_size': 256, + 'win_size': 1024, + 'fmin': 0, + 'fmax': 8000 + } + ): super().__init__() self.input_size = input_size self.output_size = output_size diff --git a/viettts/server.py b/viettts/server.py index 6d470bd..6902b24 100644 --- a/viettts/server.py +++ b/viettts/server.py @@ -11,6 +11,7 @@ import requests import numpy as np from loguru import logger +from datetime import datetime from typing import Any, List, Optional from pydantic import BaseModel from anyio import CapacityLimiter @@ -33,7 +34,7 @@ app = FastAPI( title="VietTTS API", description=""" - VietTTS API (https://github.comdangvansam/viet-tts) + VietTTS API (https://github.com/dangvansam/viet-tts) Vietnamese Text To Speech and Voice Clone License: Apache 2.0 - Author: """ @@ -230,7 +231,10 @@ async def tts( # Case 1: Uploaded audio file if audio_file: - temp_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") + temp_audio_file = tempfile.NamedTemporaryFile( + delete=False, + suffix=f'.{audio_file.filename.split(".")[-1]}' + ) try: with open(temp_audio_file.name, "wb") as temp_file: shutil.copyfileobj(audio_file.file, temp_file) @@ -241,7 +245,10 @@ async def tts( # Case 2: Audio URL elif audio_url: - temp_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") + temp_audio_file = tempfile.NamedTemporaryFile( + delete=False, + suffix=f'.{audio_url.lower().split(".")[-1]}' + ) try: response = requests.get(audio_url, stream=True) if response.status_code != 200: @@ -271,18 +278,18 @@ async def tts( if not voice_file or not os.path.exists(voice_file): raise HTTPException(status_code=400, detail="No valid voice file provided") - # Load prompt speech prompt_speech_16k = load_prompt_speech_from_file( filepath=voice_file, min_duration=3, max_duration=5 ) - # Temporary file for audio output - temp_output_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") + temp_output_file = tempfile.NamedTemporaryFile( + delete=False, + suffix=f"_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp3" + ) try: - # Generate TTS output model_output = tts_obj.inference_tts( tts_text=text, prompt_speech_16k=prompt_speech_16k, @@ -290,7 +297,6 @@ async def tts( stream=False ) - # Combine audio chunks and pass to ffmpeg for processing raw_audio = b''.join(chunk['tts_speech'].numpy().tobytes() for chunk in model_output) ffmpeg_args = [ "ffmpeg", "-loglevel", "error", "-y", "-f", "f32le", "-ar", "24000", "-ac", "1", @@ -307,18 +313,17 @@ async def tts( logger.error(f"FFmpeg error: {ffmpeg_proc.stderr.decode()}") raise HTTPException(status_code=500, detail="Error during audio processing") + if not os.path.exists(temp_output_file.name): + logger.error(f"FFmpeg did not create the output file: {temp_output_file.name}") + raise HTTPException(status_code=500, detail="FFmpeg failed to produce the output file") + return FileResponse( path=temp_output_file.name, media_type="audio/mpeg", - filename="output.mp3" + filename=temp_output_file.name.split("/")[-1] ) finally: - # Clean up temporary files - print(temp_output_file.name) - - if os.path.exists(temp_output_file.name): - os.unlink(temp_output_file.name) if audio_file or audio_url: if os.path.exists(temp_audio_file.name): os.unlink(temp_audio_file.name) diff --git a/viettts/utils/file_utils.py b/viettts/utils/file_utils.py index b1d91f9..66d2a27 100644 --- a/viettts/utils/file_utils.py +++ b/viettts/utils/file_utils.py @@ -1,19 +1,75 @@ import os +import subprocess import torchaudio import soundfile import numpy as np from glob import glob +from loguru import logger from huggingface_hub import snapshot_download from viettts.utils.vad import get_speech +import torchaudio +import os +import subprocess +import tempfile + + +def convert_to_wav(input_filepath: str, target_sr: int) -> str: + """ + Convert an input audio file to WAV format with the desired sample rate using FFmpeg. + + Args: + input_filepath (str): Path to the input audio file. + target_sr (int): Target sample rate. + + Returns: + str: Path to the converted WAV file. + """ + temp_wav_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav") + temp_wav_filepath = temp_wav_file.name + temp_wav_file.close() + + ffmpeg_command = [ + "ffmpeg", "-y", + "-loglevel", "error", + "-i", input_filepath, + "-ar", str(target_sr), + "-ac", "1", + temp_wav_filepath + ] + + result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + os.unlink(temp_wav_filepath) + raise RuntimeError(f"FFmpeg conversion failed: {result.stderr.decode()}") + + return temp_wav_filepath + def load_wav(filepath: str, target_sr: int): + """ + Load an audio file in any supported format, convert it to WAV, and load as a tensor. + + Args: + filepath (str): Path to the audio file in any format. + target_sr (int): Target sample rate. + + Returns: + Tensor: Loaded audio tensor resampled to the target sample rate. + """ + # Check if the file is already in WAV format + if not filepath.lower().endswith(".wav"): + logger.info(f"Converting {filepath} to WAV format") + filepath = convert_to_wav(filepath, target_sr) + + # Load the WAV file speech, sample_rate = torchaudio.load(filepath) - speech = speech.mean(dim=0, keepdim=True) + speech = speech.mean(dim=0, keepdim=True) # Convert to mono if not already if sample_rate != target_sr: - assert sample_rate > target_sr, 'wav sample rate {} must be greater than {}'.format(sample_rate, target_sr) + assert sample_rate > target_sr, f'WAV sample rate {sample_rate} must be greater than {target_sr}' speech = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=target_sr)(speech) + return speech diff --git a/web/.gitkeep b/web/.gitkeep new file mode 100644 index 0000000..e69de29 From 3e9fd39b33808d32c0f3d0632a3a720e485d4615 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 16:25:49 +0700 Subject: [PATCH 3/7] update docs --- README.md | 11 +++++++---- README_VN.md | 11 +++++++---- viettts/cli.py | 19 ++++++++----------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 32b0b0a..bf5fa91 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ## 🛠️ Installation -VietTTS can be installed via either a Python installer or Docker. +VietTTS can be installed via a Python installer (Linux only, with Windows and macOS support coming soon) or Docker. ### Python Installer ```bash @@ -105,11 +105,14 @@ viettts --help # Start API Server viettts server --host 0.0.0.0 --port 8298 -# Synthesis speech from text -viettts synthesis --text "Xin chào" --voice 0 --output test.wav - # List all built-in voices viettts show-voices + +# Synthesize speech from text with built-in voices +viettts synthesis --text "Xin chào" --voice 0 --output test.wav + +# Clone voice from a local audio file +viettts synthesis --text "Xin chào" --voice Download/voice.wav --output cloned.wav ``` ### API Client diff --git a/README_VN.md b/README_VN.md index 6e34fb3..5c2f813 100644 --- a/README_VN.md +++ b/README_VN.md @@ -21,7 +21,7 @@ - **VC**: Chuyển đổi giọng nói (TODO) ## 🛠️ Cài đặt -VietTTS có thể cài đặt qua trình cài đặt Python hoặc Docker. +VietTTS có thể được cài đặt qua trình cài đặt Python (chỉ hỗ trợ Linux, Windows và macOS sẽ có trong tương lai) hoặc Docker. ### Trình cài đặt Python @@ -106,11 +106,14 @@ viettts --help # Khởi động API Server viettts server --host 0.0.0.0 --port 8298 -# Tổng hợp giọng nói từ văn bản +# Xem tất cả các giọng nói có sẵn +viettts show-voices + +# Tổng hợp giọng nói từ văn bản với giọng có sẵn viettts synthesis --text "Xin chào" --voice 0 --output test.wav -# Liệt kê tất cả các giọng nói có sẵn -viettts show-voices +# Sao chép giọng từ audio file bất kì +viettts synthesis --text "Xin chào" --voice Download/voice.wav --output cloned.wav ``` ### API Client diff --git a/viettts/cli.py b/viettts/cli.py index 957469c..7262d0a 100644 --- a/viettts/cli.py +++ b/viettts/cli.py @@ -14,9 +14,9 @@ MODEL_DIR = 'pretrained-models' @click.command('server') -@click.option('-h', '--host', type=str, default='0.0.0.0') -@click.option('-p', '--port', type=int, default=8298) -@click.option('-w', '--workers', type=int, default=1) +@click.option('-h', '--host', type=str, default='0.0.0.0', help="The host address to bind the server to. Default is '0.0.0.0'.") +@click.option('-p', '--port', type=int, default=8298, help="The port number to bind the server to. Default is 8298.") +@click.option('-w', '--workers', type=int, default=1, help="The number of worker processes to handle requests. Default is 1.") def start_server(host: str, port: int, workers: int): """Start API server (OpenAI TTS API compatible). @@ -37,17 +37,14 @@ def start_server(host: str, port: int, workers: int): @click.command('synthesis') -@click.option('-t', "--text", type=str, required=True) -@click.option('-v', "--voice", type=str, default='1') -@click.option('-s', "--speed", type=float, default=1) -@click.option('-o', "--output", type=str, default='output.wav') +@click.option('-t', "--text", type=str, required=True, help="The input text to synthesize into speech.") +@click.option('-v', "--voice", type=str, default='1', help="The voice ID or file path to clone the voice from. Default is '1'.") +@click.option('-s', "--speed", type=float, default=1, help="The speed multiplier for the speech. Default is 1 (normal speed).") +@click.option('-o', "--output", type=str, default='output.wav', help="The file path to save the synthesized audio. Default is 'output.wav'.") def synthesis(text: str, voice: str, speed: float, output: str): """Synthesis audio from text and save to file. - Usage: - viettts synthesis --text 'Xin chào VietTTS' --voice nu-nhe-nhang --output test_nu-nhe-nhang.wav - viettts synthesis --text 'Chào bạn đến với Hà Nội' --voice 8 --speed 1.2 --output test_voice_8_speed_1.2.wav - viettts synthesis --text 'Bạn có thể sao chép giọng sẵn có' --voice Downloads/audio.wav + Usage: viettts synthesis --text 'Xin chào VietTTS' --voice nu-nhe-nhang --voice 8 --speed 1.2 --output test_nu-nhe-nhang.wav """ logger.info("Starting synthesis") st = time.perf_counter() From 38fe8cc1a8ca1e96301bfa48df4d064b7c841900 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 16:36:56 +0700 Subject: [PATCH 4/7] update docs --- README.md | 24 +++++++++++++++++------- README_VN.md | 10 ++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index bf5fa91..867aa16 100644 --- a/README.md +++ b/README.md @@ -144,14 +144,24 @@ with client.audio.speech.with_streaming_response.create( #### CURL ```bash +# Get all built-in voices +curl --location http://0.0.0.0:8298/v1/voices + +# OpenAI format (bult-in voices) curl http://localhost:8298/v1/audio/speech \ - -H "Authorization: Bearer viet-tts" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "tts-1", - "input": "Xin chào Việt Nam.", - "voice": "son-tung-mtp" - }' \ +  -H "Authorization: Bearer viet-tts" \ +  -H "Content-Type: application/json" \ +  -d '{ +    "model": "tts-1", +    "input": "Xin chào Việt Nam.", +    "voice": "son-tung-mtp" +  }' \ +  --output speech.wav + +# API with voice from local file +curl --location http://0.0.0.0:8298/v1/tts \ + --form 'text="xin chào"' \ + --form 'audio_file=@"/home/viettts/Downloads/voice.mp4"' \ --output speech.wav ``` diff --git a/README_VN.md b/README_VN.md index 5c2f813..f012987 100644 --- a/README_VN.md +++ b/README_VN.md @@ -149,6 +149,10 @@ with client.audio.speech.with_streaming_response.create( #### CURL ```bash +# Lấy danh sách giọng có sẵn +curl --location http://0.0.0.0:8298/v1/voices + +# OpenAI API format curl http://localhost:8298/v1/audio/speech \   -H "Authorization: Bearer viet-tts" \   -H "Content-Type: application/json" \ @@ -158,6 +162,12 @@ curl http://localhost:8298/v1/audio/speech \     "voice": "son-tung-mtp"   }' \   --output speech.wav + +# API với giọng từ file local +curl --location http://0.0.0.0:8298/v1/tts \ + --form 'text="xin chào"' \ + --form 'audio_file=@"/home/viettts/Downloads/voice.mp4"' \ + --output speech.wav ``` #### Node From 9cac2b2ee31bde500f383e9d389ed0e5cc072ac9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 16:51:46 +0700 Subject: [PATCH 5/7] update requirements --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d4b1b78..fd4751a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ huggingface-hub = "0.24.7" click = "^8.1.7" gunicorn = "^23.0.0" silero-vad = "^5.1.2" +whisper = "^1.1.10" [tool.poetry.scripts] viettts = "viettts.cli:cli" From 90e8fe136935d91b75912f30694a99689bf2861d Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 16:54:41 +0700 Subject: [PATCH 6/7] add tiktoken --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index fd4751a..43235dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ click = "^8.1.7" gunicorn = "^23.0.0" silero-vad = "^5.1.2" whisper = "^1.1.10" +tiktoken = "^0.8.0" [tool.poetry.scripts] viettts = "viettts.cli:cli" From 8932564b1b2bafabb379f45fe02840d29273e17d Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 11 Dec 2024 17:02:34 +0700 Subject: [PATCH 7/7] add whisper --- README.md | 2 +- README_VN.md | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 867aa16..746b71e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ## ⭐ Key Features - **TTS**: Text-to-Speech generation with any voice via prompt audio -- **VC**: Voice Conversion (TODO) +- **OpenAI-API-compatible**: Compatible with OpenAI's Text-to-Speech API format ## 🛠️ Installation diff --git a/README_VN.md b/README_VN.md index f012987..1cbdf26 100644 --- a/README_VN.md +++ b/README_VN.md @@ -18,7 +18,7 @@ ## ⭐ Tính năng nổi bật - **TTS**: Tổng hợp giọng nói từ văn bản với bất kỳ giọng nào qua audio mẫu -- **VC**: Chuyển đổi giọng nói (TODO) +- **OpenAI-API-compatible**: Tương thích với API Text to Speech OpenAI ## 🛠️ Cài đặt VietTTS có thể được cài đặt qua trình cài đặt Python (chỉ hỗ trợ Linux, Windows và macOS sẽ có trong tương lai) hoặc Docker. diff --git a/pyproject.toml b/pyproject.toml index 43235dd..f850dcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,8 +31,8 @@ huggingface-hub = "0.24.7" click = "^8.1.7" gunicorn = "^23.0.0" silero-vad = "^5.1.2" -whisper = "^1.1.10" tiktoken = "^0.8.0" +openai-whisper = "^20240930" [tool.poetry.scripts] viettts = "viettts.cli:cli"