toolnet-tts 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ prune toolnet/voices
2
+ global-exclude *.onnx *.ckpt *.wav *.zip
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolnet-tts
3
+ Version: 0.2.0
4
+ Summary: Lightweight client for ToolNet Vietnamese text-to-speech
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Provides-Extra: server
8
+ Requires-Dist: fastapi<1,>=0.115; extra == "server"
9
+ Requires-Dist: uvicorn<1,>=0.30; extra == "server"
10
+ Requires-Dist: vieneu==3.8.4; extra == "server"
11
+ Requires-Dist: onnxruntime<1.23,>=1.20; extra == "server"
12
+
13
+ # toolnet-tts
14
+
15
+ Client Python và Node.js gửi văn bản đến máy chủ ToolNet TTS để tạo WAV bằng giọng Thiện Minh của VieNeu v3 Turbo. Client không tải model; máy chủ cần cài model và các phụ thuộc riêng. Cần có URL máy chủ để sử dụng.
16
+
17
+ ## Python
18
+
19
+ ```bash
20
+ pip install toolnet-tts
21
+ export TOOLNET_TTS_SERVER="https://your-server.example"
22
+ # Nếu máy chủ yêu cầu khóa:
23
+ export TOOLNET_TTS_API_KEY="your-api-key"
24
+ toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
25
+ ```
26
+
27
+ ```python
28
+ import asyncio
29
+ from toolnet_tts import Communicate
30
+
31
+ asyncio.run(Communicate("Xin chào các bạn").save("hello.wav"))
32
+ # Hoặc đồng bộ: Communicate("Xin chào").save_sync("hello.wav")
33
+ ```
34
+
35
+ Có thể đặt URL bằng `server="https://your-server.example"`. Chỉ hỗ trợ WAV; hiện không hỗ trợ MP3, word boundaries hoặc phát trực tiếp.
36
+
37
+ ## Node.js
38
+
39
+ ```bash
40
+ npm install toolnet-tts
41
+ export TOOLNET_TTS_SERVER="https://your-server.example"
42
+ npx toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
43
+ ```
44
+
45
+ ```js
46
+ import { Communicate } from 'toolnet-tts';
47
+ await new Communicate('Xin chào các bạn').save('hello.wav');
48
+ ```
49
+
50
+ ## Chạy máy chủ riêng
51
+
52
+ ```bash
53
+ pip install 'toolnet-tts[server]'
54
+ export TOOLNET_TTS_API_KEY="replace-with-a-long-random-key"
55
+ uvicorn toolnet_tts.server:app --host 127.0.0.1 --port 8000
56
+ ```
57
+
58
+ Đặt `TOOLNET_TTS_DEVICE=cpu` để chạy CPU. Lần đầu cần tải model VieNeu; nếu dùng GPU cần thêm các phụ thuộc CUDA tương thích. `/health` báo tiến trình và trạng thái nạp model; `POST /v1/speech` nhận JSON `{"text":"...","voice":"Thiện Minh"}` và trả WAV. Đặt `TOOLNET_TTS_API_KEY` ở cả client và server để xác thực Bearer. Nếu mở máy chủ cho client ở xa, cần cấu hình HTTPS và mạng phù hợp; lệnh trên chỉ lắng nghe tại localhost.
59
+
60
+ Giọng và model: [VieNeu-TTS v3 Turbo](https://huggingface.co/pnnbao-ump/VieNeu-TTS-v3-Turbo), Apache-2.0. Dự án này sử dụng model gốc; không đưa checkpoint Piper thử nghiệm vào gói client.
61
+
62
+ ## Chạy cục bộ kiểu cũ
63
+
64
+ `from toolnet import TTS` vẫn có thể dùng sau khi cài `toolnet-tts[server]`. API này tải và chạy VieNeu trên chính máy gọi, không dùng máy chủ.
@@ -0,0 +1,52 @@
1
+ # toolnet-tts
2
+
3
+ Client Python và Node.js gửi văn bản đến máy chủ ToolNet TTS để tạo WAV bằng giọng Thiện Minh của VieNeu v3 Turbo. Client không tải model; máy chủ cần cài model và các phụ thuộc riêng. Cần có URL máy chủ để sử dụng.
4
+
5
+ ## Python
6
+
7
+ ```bash
8
+ pip install toolnet-tts
9
+ export TOOLNET_TTS_SERVER="https://your-server.example"
10
+ # Nếu máy chủ yêu cầu khóa:
11
+ export TOOLNET_TTS_API_KEY="your-api-key"
12
+ toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
13
+ ```
14
+
15
+ ```python
16
+ import asyncio
17
+ from toolnet_tts import Communicate
18
+
19
+ asyncio.run(Communicate("Xin chào các bạn").save("hello.wav"))
20
+ # Hoặc đồng bộ: Communicate("Xin chào").save_sync("hello.wav")
21
+ ```
22
+
23
+ Có thể đặt URL bằng `server="https://your-server.example"`. Chỉ hỗ trợ WAV; hiện không hỗ trợ MP3, word boundaries hoặc phát trực tiếp.
24
+
25
+ ## Node.js
26
+
27
+ ```bash
28
+ npm install toolnet-tts
29
+ export TOOLNET_TTS_SERVER="https://your-server.example"
30
+ npx toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
31
+ ```
32
+
33
+ ```js
34
+ import { Communicate } from 'toolnet-tts';
35
+ await new Communicate('Xin chào các bạn').save('hello.wav');
36
+ ```
37
+
38
+ ## Chạy máy chủ riêng
39
+
40
+ ```bash
41
+ pip install 'toolnet-tts[server]'
42
+ export TOOLNET_TTS_API_KEY="replace-with-a-long-random-key"
43
+ uvicorn toolnet_tts.server:app --host 127.0.0.1 --port 8000
44
+ ```
45
+
46
+ Đặt `TOOLNET_TTS_DEVICE=cpu` để chạy CPU. Lần đầu cần tải model VieNeu; nếu dùng GPU cần thêm các phụ thuộc CUDA tương thích. `/health` báo tiến trình và trạng thái nạp model; `POST /v1/speech` nhận JSON `{"text":"...","voice":"Thiện Minh"}` và trả WAV. Đặt `TOOLNET_TTS_API_KEY` ở cả client và server để xác thực Bearer. Nếu mở máy chủ cho client ở xa, cần cấu hình HTTPS và mạng phù hợp; lệnh trên chỉ lắng nghe tại localhost.
47
+
48
+ Giọng và model: [VieNeu-TTS v3 Turbo](https://huggingface.co/pnnbao-ump/VieNeu-TTS-v3-Turbo), Apache-2.0. Dự án này sử dụng model gốc; không đưa checkpoint Piper thử nghiệm vào gói client.
49
+
50
+ ## Chạy cục bộ kiểu cũ
51
+
52
+ `from toolnet import TTS` vẫn có thể dùng sau khi cài `toolnet-tts[server]`. API này tải và chạy VieNeu trên chính máy gọi, không dùng máy chủ.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "toolnet-tts"
7
+ version = "0.2.0"
8
+ description = "Lightweight client for ToolNet Vietnamese text-to-speech"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = []
12
+
13
+ [project.optional-dependencies]
14
+ server = ["fastapi>=0.115,<1", "uvicorn>=0.30,<1", "vieneu==3.8.4", "onnxruntime>=1.20,<1.23"]
15
+
16
+ [project.scripts]
17
+ toolnet-tts = "toolnet_tts.cli:main"
18
+
19
+ [tool.setuptools.packages.find]
20
+ include = ["toolnet", "toolnet_tts*"]
21
+
22
+ [tool.setuptools]
23
+ include-package-data = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ import asyncio
2
+ import io
3
+ import json
4
+ import os
5
+ import tempfile
6
+ import threading
7
+ import unittest
8
+ import wave
9
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
10
+ from pathlib import Path
11
+
12
+ from toolnet_tts import Communicate, ToolnetError
13
+
14
+
15
+ def make_wav():
16
+ buf = io.BytesIO()
17
+ with wave.open(buf, 'wb') as w:
18
+ w.setnchannels(1); w.setsampwidth(2); w.setframerate(48000); w.writeframes(b'\0\0' * 20)
19
+ return buf.getvalue()
20
+
21
+
22
+ class Handler(BaseHTTPRequestHandler):
23
+ def do_POST(self):
24
+ body = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
25
+ self.server.last_request = (self.path, body, self.headers.get('Authorization'))
26
+ if body['text'] == 'failure':
27
+ self.send_error(503)
28
+ return
29
+ audio = make_wav()
30
+ self.send_response(200); self.send_header('Content-Type', 'audio/wav'); self.end_headers()
31
+ self.wfile.write(audio)
32
+
33
+ def log_message(self, *args):
34
+ pass
35
+
36
+
37
+ class ClientTest(unittest.TestCase):
38
+ @classmethod
39
+ def setUpClass(cls):
40
+ cls.server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
41
+ cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True); cls.thread.start()
42
+ cls.url = 'http://127.0.0.1:%d' % cls.server.server_port
43
+
44
+ @classmethod
45
+ def tearDownClass(cls):
46
+ cls.server.shutdown(); cls.server.server_close(); cls.thread.join()
47
+
48
+ def test_sync_and_async_send_exact_text_and_voice(self):
49
+ with tempfile.TemporaryDirectory() as d:
50
+ path = Path(d)/'sample.wav'
51
+ c = Communicate('Xin chào!', server=self.url, api_key='secret')
52
+ self.assertEqual(c.save_sync(path), str(path))
53
+ with wave.open(str(path)) as wav: self.assertEqual(wav.getframerate(), 48000)
54
+ self.assertEqual(self.server.last_request, ('/v1/speech', {'text': 'Xin chào!', 'voice': 'Thiện Minh'}, 'Bearer secret'))
55
+ self.assertEqual(asyncio.run(c.save(path)), str(path))
56
+
57
+ def test_error_does_not_replace_existing_file(self):
58
+ with tempfile.TemporaryDirectory() as d:
59
+ p = Path(d)/'sample.wav'; p.write_bytes(b'keep')
60
+ with self.assertRaises(ToolnetError): Communicate('failure', server=self.url).save_sync(p)
61
+ self.assertEqual(p.read_bytes(), b'keep')
62
+
63
+ def test_invalid_inputs(self):
64
+ with self.assertRaises(ValueError): Communicate(' ', server=self.url)
65
+ with self.assertRaises(ValueError): Communicate('hello', server=self.url).save_sync('x.mp3')
66
+
67
+
68
+ if __name__ == '__main__': unittest.main()
@@ -0,0 +1,24 @@
1
+ import os
2
+ import unittest
3
+ from unittest.mock import patch
4
+
5
+ from fastapi.testclient import TestClient
6
+ from toolnet_tts.server import app
7
+
8
+
9
+ class ServerTest(unittest.TestCase):
10
+ def test_voice_auth_and_audio_response(self):
11
+ with patch.dict(os.environ, {'TOOLNET_TTS_API_KEY': 'test-key'}), patch('toolnet_tts.server._synthesize', return_value=b'wave') as synth:
12
+ client = TestClient(app)
13
+ self.assertEqual(client.post('/v1/speech', json={'text': 'Xin chào'}).status_code, 401)
14
+ headers = {'Authorization': 'Bearer test-key'}
15
+ self.assertEqual(client.post('/v1/speech', headers=headers, json={'text': 'Xin chào', 'voice': 'other'}).status_code, 400)
16
+ self.assertEqual(client.post('/v1/speech', headers=headers, json={'text': ' '}).status_code, 422)
17
+ result = client.post('/v1/speech', headers=headers, json={'text': 'Xin chào', 'voice': 'giong1'})
18
+ self.assertEqual(result.status_code, 200)
19
+ self.assertEqual(result.headers['content-type'], 'audio/wav')
20
+ self.assertEqual(result.content, b'wave')
21
+ synth.assert_called_once_with('Xin chào', 'Thiện Minh')
22
+
23
+
24
+ if __name__ == '__main__': unittest.main()
@@ -0,0 +1,3 @@
1
+ from .engine import TTS
2
+
3
+ __all__ = ["TTS"]
@@ -0,0 +1,11 @@
1
+ class TTS:
2
+ def __init__(self, voice="giong1", device="auto"):
3
+ from vieneu import Vieneu
4
+
5
+ self.voice = "Thiện Minh" if voice == "giong1" else voice
6
+ self.engine = Vieneu(mode="v3turbo", device=device)
7
+
8
+ def say(self, text, path="out.wav"):
9
+ audio = self.engine.infer(text, voice=self.voice)
10
+ self.engine.save(audio, str(path))
11
+ return path
@@ -0,0 +1,50 @@
1
+ import re
2
+
3
+ _D = ["không", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín"]
4
+ _ABBR = {"TP.HCM": "thành phố Hồ Chí Minh", "UBND": "uỷ ban nhân dân", "VND": "đồng"}
5
+
6
+
7
+ def _read_3(n, full=False):
8
+ h, t, u = n // 100, (n % 100) // 10, n % 10
9
+ out = []
10
+ if h or full:
11
+ out += [_D[h], "trăm"]
12
+ if t > 1:
13
+ out += [_D[t], "mươi"]
14
+ elif t == 1:
15
+ out.append("mười")
16
+ elif (h or full) and u:
17
+ out.append("lẻ")
18
+ if u:
19
+ if t > 1 and u == 1:
20
+ out.append("mốt")
21
+ elif t >= 1 and u == 5:
22
+ out.append("lăm")
23
+ elif t > 1 and u == 4:
24
+ out.append("tư")
25
+ else:
26
+ out.append(_D[u])
27
+ return " ".join(out)
28
+
29
+
30
+ def number_to_words(n):
31
+ if n == 0:
32
+ return "không"
33
+ parts, units = [], ["", "nghìn", "triệu", "tỷ"]
34
+ groups = []
35
+ while n:
36
+ groups.append(n % 1000)
37
+ n //= 1000
38
+ for i in range(len(groups) - 1, -1, -1):
39
+ g = groups[i]
40
+ if g:
41
+ parts.append(_read_3(g, full=(i < len(groups) - 1)))
42
+ parts.append(units[i])
43
+ return " ".join(p for p in parts if p)
44
+
45
+
46
+ def clean(text):
47
+ for k, v in _ABBR.items():
48
+ text = text.replace(k, v)
49
+ text = re.sub(r"\d+", lambda m: number_to_words(int(m.group())), text)
50
+ return re.sub(r"\s+", " ", text).strip().lower()
@@ -0,0 +1,3 @@
1
+ from .client import Communicate, ToolnetError
2
+
3
+ __all__ = ["Communicate", "ToolnetError"]
@@ -0,0 +1,27 @@
1
+ import argparse
2
+ from .client import Communicate, ToolnetError
3
+
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(description="Chuyển văn bản tiếng Việt thành giọng nói ToolNet.")
7
+ source = parser.add_mutually_exclusive_group(required=True)
8
+ source.add_argument("--text", help="Văn bản cần đọc")
9
+ source.add_argument("--file", help="File văn bản UTF-8")
10
+ parser.add_argument("--write-media", required=True, help="Đường dẫn file WAV")
11
+ parser.add_argument("--server", help="URL máy chủ (hoặc biến TOOLNET_TTS_SERVER)")
12
+ parser.add_argument("--voice", default="Thiện Minh")
13
+ args = parser.parse_args()
14
+ try:
15
+ if args.file:
16
+ with open(args.file, encoding="utf-8-sig") as file:
17
+ text = file.read()
18
+ else:
19
+ text = args.text
20
+ Communicate(text, args.voice, server=args.server).save_sync(args.write_media)
21
+ except (ToolnetError, ValueError, OSError) as exc:
22
+ parser.exit(1, f"toolnet-tts: {exc}\n")
23
+ print(f"Đã tạo {args.write_media}")
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
@@ -0,0 +1,51 @@
1
+ """A small HTTP client for a ToolNet TTS server."""
2
+ import asyncio
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from urllib.error import HTTPError, URLError
7
+ from urllib.request import Request, urlopen
8
+
9
+
10
+ class ToolnetError(RuntimeError):
11
+ """The speech service could not fulfil a request."""
12
+
13
+
14
+ class Communicate:
15
+ def __init__(self, text, voice="Thiện Minh", *, server=None, api_key=None, timeout=180):
16
+ if not isinstance(text, str) or not text.strip():
17
+ raise ValueError("text must be a non-empty string")
18
+ self.text = text
19
+ self.voice = voice
20
+ self.server = (server or os.getenv("TOOLNET_TTS_SERVER", "")).rstrip("/")
21
+ if not self.server.startswith(("https://", "http://")):
22
+ raise ValueError("Set server= or TOOLNET_TTS_SERVER to the speech server URL")
23
+ self.api_key = api_key if api_key is not None else os.getenv("TOOLNET_TTS_API_KEY")
24
+ self.timeout = timeout
25
+
26
+ def save_sync(self, path):
27
+ path = Path(path)
28
+ if path.suffix.lower() != ".wav":
29
+ raise ValueError("Output must have a .wav extension")
30
+ payload = json.dumps({"text": self.text, "voice": self.voice}, ensure_ascii=False).encode("utf-8")
31
+ headers = {"Content-Type": "application/json", "Accept": "audio/wav"}
32
+ if self.api_key:
33
+ headers["Authorization"] = f"Bearer {self.api_key}"
34
+ request = Request(f"{self.server}/v1/speech", data=payload, headers=headers, method="POST")
35
+ try:
36
+ with urlopen(request, timeout=self.timeout) as response:
37
+ content_type = response.headers.get_content_type()
38
+ if content_type not in ("audio/wav", "audio/x-wav"):
39
+ raise ToolnetError(f"Unexpected response type: {content_type}")
40
+ audio = response.read()
41
+ except HTTPError as exc:
42
+ raise ToolnetError(f"Speech server returned HTTP {exc.code}") from exc
43
+ except (URLError, TimeoutError, OSError) as exc:
44
+ raise ToolnetError(f"Cannot reach speech server: {exc}") from exc
45
+ if len(audio) < 44 or audio[:4] != b"RIFF" or audio[8:12] != b"WAVE":
46
+ raise ToolnetError("Speech server returned invalid WAV data")
47
+ path.write_bytes(audio)
48
+ return str(path)
49
+
50
+ async def save(self, path):
51
+ return await asyncio.to_thread(self.save_sync, path)
@@ -0,0 +1,51 @@
1
+ """Self-hosted VieNeu inference service. Install with toolnet-tts[server]."""
2
+ import hmac
3
+ import io
4
+ import os
5
+ import threading
6
+
7
+ from fastapi import FastAPI, Header, HTTPException
8
+ from fastapi.responses import Response
9
+ from pydantic import BaseModel, Field
10
+ from starlette.concurrency import run_in_threadpool
11
+
12
+ app = FastAPI(title="ToolNet TTS")
13
+ _engine = None
14
+ _lock = threading.Lock()
15
+
16
+
17
+ class SpeechRequest(BaseModel):
18
+ text: str = Field(min_length=1, max_length=10000)
19
+ voice: str = "Thiện Minh"
20
+
21
+
22
+ def _synthesize(text: str, voice: str) -> bytes:
23
+ global _engine
24
+ import soundfile as sf
25
+ from vieneu import Vieneu
26
+
27
+ with _lock:
28
+ if _engine is None:
29
+ _engine = Vieneu(mode="v3turbo", device=os.getenv("TOOLNET_TTS_DEVICE", "auto"))
30
+ audio = _engine.infer(text, voice=voice)
31
+ output = io.BytesIO()
32
+ sf.write(output, audio, _engine.sample_rate, format="WAV", subtype="PCM_16")
33
+ return output.getvalue()
34
+
35
+
36
+ @app.get("/health")
37
+ def health():
38
+ return {"status": "ok", "model_loaded": _engine is not None}
39
+
40
+
41
+ @app.post("/v1/speech")
42
+ async def speech(request: SpeechRequest, authorization: str = Header(default="")):
43
+ key = os.getenv("TOOLNET_TTS_API_KEY")
44
+ if key and not hmac.compare_digest(authorization, f"Bearer {key}"):
45
+ raise HTTPException(status_code=401, detail="Unauthorized")
46
+ if not request.text.strip():
47
+ raise HTTPException(status_code=422, detail="Text is empty")
48
+ if request.voice not in ("Thiện Minh", "giong1"):
49
+ raise HTTPException(status_code=400, detail="Voice is not supported")
50
+ audio = await run_in_threadpool(_synthesize, request.text, "Thiện Minh")
51
+ return Response(audio, media_type="audio/wav")
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolnet-tts
3
+ Version: 0.2.0
4
+ Summary: Lightweight client for ToolNet Vietnamese text-to-speech
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Provides-Extra: server
8
+ Requires-Dist: fastapi<1,>=0.115; extra == "server"
9
+ Requires-Dist: uvicorn<1,>=0.30; extra == "server"
10
+ Requires-Dist: vieneu==3.8.4; extra == "server"
11
+ Requires-Dist: onnxruntime<1.23,>=1.20; extra == "server"
12
+
13
+ # toolnet-tts
14
+
15
+ Client Python và Node.js gửi văn bản đến máy chủ ToolNet TTS để tạo WAV bằng giọng Thiện Minh của VieNeu v3 Turbo. Client không tải model; máy chủ cần cài model và các phụ thuộc riêng. Cần có URL máy chủ để sử dụng.
16
+
17
+ ## Python
18
+
19
+ ```bash
20
+ pip install toolnet-tts
21
+ export TOOLNET_TTS_SERVER="https://your-server.example"
22
+ # Nếu máy chủ yêu cầu khóa:
23
+ export TOOLNET_TTS_API_KEY="your-api-key"
24
+ toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
25
+ ```
26
+
27
+ ```python
28
+ import asyncio
29
+ from toolnet_tts import Communicate
30
+
31
+ asyncio.run(Communicate("Xin chào các bạn").save("hello.wav"))
32
+ # Hoặc đồng bộ: Communicate("Xin chào").save_sync("hello.wav")
33
+ ```
34
+
35
+ Có thể đặt URL bằng `server="https://your-server.example"`. Chỉ hỗ trợ WAV; hiện không hỗ trợ MP3, word boundaries hoặc phát trực tiếp.
36
+
37
+ ## Node.js
38
+
39
+ ```bash
40
+ npm install toolnet-tts
41
+ export TOOLNET_TTS_SERVER="https://your-server.example"
42
+ npx toolnet-tts --text "Xin chào các bạn" --write-media hello.wav
43
+ ```
44
+
45
+ ```js
46
+ import { Communicate } from 'toolnet-tts';
47
+ await new Communicate('Xin chào các bạn').save('hello.wav');
48
+ ```
49
+
50
+ ## Chạy máy chủ riêng
51
+
52
+ ```bash
53
+ pip install 'toolnet-tts[server]'
54
+ export TOOLNET_TTS_API_KEY="replace-with-a-long-random-key"
55
+ uvicorn toolnet_tts.server:app --host 127.0.0.1 --port 8000
56
+ ```
57
+
58
+ Đặt `TOOLNET_TTS_DEVICE=cpu` để chạy CPU. Lần đầu cần tải model VieNeu; nếu dùng GPU cần thêm các phụ thuộc CUDA tương thích. `/health` báo tiến trình và trạng thái nạp model; `POST /v1/speech` nhận JSON `{"text":"...","voice":"Thiện Minh"}` và trả WAV. Đặt `TOOLNET_TTS_API_KEY` ở cả client và server để xác thực Bearer. Nếu mở máy chủ cho client ở xa, cần cấu hình HTTPS và mạng phù hợp; lệnh trên chỉ lắng nghe tại localhost.
59
+
60
+ Giọng và model: [VieNeu-TTS v3 Turbo](https://huggingface.co/pnnbao-ump/VieNeu-TTS-v3-Turbo), Apache-2.0. Dự án này sử dụng model gốc; không đưa checkpoint Piper thử nghiệm vào gói client.
61
+
62
+ ## Chạy cục bộ kiểu cũ
63
+
64
+ `from toolnet import TTS` vẫn có thể dùng sau khi cài `toolnet-tts[server]`. API này tải và chạy VieNeu trên chính máy gọi, không dùng máy chủ.
@@ -0,0 +1,18 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_client.py
5
+ tests/test_server.py
6
+ toolnet/__init__.py
7
+ toolnet/engine.py
8
+ toolnet/text.py
9
+ toolnet_tts/__init__.py
10
+ toolnet_tts/cli.py
11
+ toolnet_tts/client.py
12
+ toolnet_tts/server.py
13
+ toolnet_tts.egg-info/PKG-INFO
14
+ toolnet_tts.egg-info/SOURCES.txt
15
+ toolnet_tts.egg-info/dependency_links.txt
16
+ toolnet_tts.egg-info/entry_points.txt
17
+ toolnet_tts.egg-info/requires.txt
18
+ toolnet_tts.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ toolnet-tts = toolnet_tts.cli:main
@@ -0,0 +1,6 @@
1
+
2
+ [server]
3
+ fastapi<1,>=0.115
4
+ uvicorn<1,>=0.30
5
+ vieneu==3.8.4
6
+ onnxruntime<1.23,>=1.20
@@ -0,0 +1,2 @@
1
+ toolnet
2
+ toolnet_tts