HuuDatLego commited on
Commit
fdd0daa
·
verified ·
1 Parent(s): e07aeaf

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -1,35 +1,7 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.pth filter=lfs diff=lfs merge=lfs -text
2
+ *.onnx filter=lfs diff=lfs merge=lfs -text
3
+ *.pt filter=lfs diff=lfs merge=lfs -text
4
+ *.bin filter=lfs diff=lfs merge=lfs -text
5
+ *.wav filter=lfs diff=lfs merge=lfs -text
6
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
7
+ mahiru/mahiru.index filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ output_audio
2
+ *.pyc
3
+ audio
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Cài đặt ffmpeg cho các thư viện xử lý âm thanh (ví dụ: pydub)
4
+ RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
5
+
6
+ # Tạo user không phải root (Yêu cầu bắt buộc của Hugging Face Spaces)
7
+ RUN useradd -m -u 1000 user
8
+
9
+ # Thiết lập thư mục làm việc
10
+ WORKDIR /app
11
+
12
+ # Copy và cài đặt thư viện
13
+ COPY --chown=user:user requirements.txt .
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy toàn bộ mã nguồn vào container
17
+ COPY --chown=user:user . .
18
+
19
+ # Đổi sang user vừa tạo
20
+ USER user
21
+
22
+ # Thiết lập biến môi trường PORT thành 7860 (Port mặc định của Hugging Face)
23
+ ENV PORT=7860
24
+
25
+ EXPOSE 7860
26
+
27
+ # Khởi chạy ứng dụng
28
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,11 +1,98 @@
1
- ---
2
- title: Tts App
3
- emoji: 🏢
4
- colorFrom: red
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flask Text-to-Speech (TTS) Web App with Piper
2
+
3
+ Một ứng dụng web đơn giản, chạy trên máy tính cá nhân (local) để chuyển đổi văn bản thành giọng nói chất lượng cao bằng Python, Flask và công cụ mã nguồn mở Piper TTS.
4
+
5
+ <p align="center">
6
+ <img src="img/Capture.PNG" alt="Banner"/>
7
+ </p>
8
+
9
+ ## ✨ Giới thiệu
10
+
11
+ Dự án này được tạo ra nhằm cung cấp một giải pháp Text-to-Speech miễn phí, không giới hạn và hoàn toàn offline (sau khi đã tải model giọng đọc). Thay vì phụ thuộc vào các dịch vụ API đám mây có giới hạn sử dụng, ứng dụng này cho phép người dùng tạo ra các file audio mẫu một cách nhanh chóng ngay trên máy của mình, phục vụ cho các mục đích học tập hoặc tạo nội dung.
12
+
13
+ ## 🚀 Các tính năng chính
14
+
15
+ - **Giao diện Web đơn giản:** Cung cấp một trang web trực quan để nhập văn bản và chọn giọng đọc.
16
+ - **Giọng đọc chất lượng cao:** Sử dụng **Piper TTS** cho ra giọng nói tự nhiên và rõ ràng.
17
+ - **Hỗ trợ đa giọng đọc:** Dễ dàng cấu hình để thêm nhiều giọng đọc khác nhau. Phiên bản hiện tại hỗ trợ:
18
+ - Tiếng Anh (Mỹ) - Nam & Nữ
19
+ - Tiếng Anh (Anh) - Nam
20
+
21
+ - **Tự động tải Model:** Script sẽ tự động kiểm tra và tải về các "model giọng đọc" cần thiết từ Hugging Face trong lần chạy đầu tiên.
22
+ - **Tạo Audio nhanh chóng:** Nhận văn bản từ người dùng, xử lý ở backend và trả về file audio để nghe trực tiếp trên trình duyệt.
23
+
24
+ ## 🛠️ Công nghệ sử dụng
25
+
26
+ - **Backend:** Python 3, Flask
27
+ - **TTS Engine:** Piper TTS
28
+ - **Frontend:** HTML5, CSS3, JavaScript (Vanilla)
29
+ - **Thư viện Python:** `requests` (để tải model)
30
+
31
+ ---
32
+
33
+ ## ⚙️ Hướng dẫn Cài đặt & Chạy dự án
34
+
35
+ ### Yêu cầu
36
+ - [Python 3.8+](https://www.python.org/downloads/) đã được cài đặt.
37
+ - *Lưu ý quan trọng: Trong quá trình cài đặt Python, hãy chắc chắn rằng bạn đã tick vào ô **"Add Python to PATH"**.*
38
+
39
+ ### Các bước cài đặt
40
+
41
+ 1. **Clone repository này về máy:**
42
+ ```bash
43
+ git clone https://github.com/TranHuuDat2004/tts-flask-app.git
44
+ cd tts-flask-app
45
+ ```
46
+
47
+ 2. **(Khuyến khích) Tạo và kích hoạt môi trường ảo:**
48
+ ```bash
49
+ # Tạo môi trường ảo
50
+ python -m venv venv
51
+
52
+ # Kích hoạt trên Windows
53
+ .\venv\Scripts\activate
54
+
55
+ # Kích hoạt trên macOS/Linux
56
+ source venv/bin/activate
57
+ ```
58
+
59
+ 3. **Cài đặt các thư viện cần thiết:**
60
+ ```bash
61
+ pip install Flask piper-tts requests
62
+ ```
63
+
64
+ 4. **Chạy ứng dụng:**
65
+ ```bash
66
+ python app.py
67
+ ```
68
+
69
+ ### Hướng dẫn sử dụng
70
+
71
+ 1. Sau khi chạy lệnh `python app.py`, server sẽ khởi động. Mở trình duyệt và truy cập vào địa chỉ: [http://127.0.0.1:5000](http://127.0.0.1:5000)
72
+
73
+ 2. **Chờ tải model:** Trong lần chạy đầu tiên, server sẽ mất vài phút để tự động tải về các model giọng đọc cần thiết. Hãy theo dõi tiến trình trong cửa sổ dòng lệnh (CMD/Terminal).
74
+
75
+ 3. **Sử dụng giao diện:**
76
+ - Chọn giọng đọc bạn muốn từ menu dropdown.
77
+ - Nhập hoặc dán đoạn văn bản vào ô `textarea`.
78
+ - Nhấn nút **"Generate Audio"**.
79
+ - Chờ một vài giây và trình phát audio sẽ xuất hiện để bạn nghe kết quả.
80
+
81
+ ---
82
+
83
+ ## 📁 Cấu trúc thư mục
84
+
85
+ ```
86
+ tts-flask-app/
87
+ ├── app.py # File server Flask chính
88
+ ├── piper_voices/ # (Tự động tạo) Chứa các model giọng đọc
89
+ ├── output_audio/ # (Tự động tạo) Chứa các file audio đã tạo
90
+ ├── static/
91
+ │ └── style.css # File CSS trang trí
92
+ └── templates/
93
+ └── index.html # Giao diện người dùng
94
+ ```
95
+
96
+ ## 📝 Tác giả
97
+
98
+ Dự án được phát triển bởi **Trần Hữu Đạt**.
app.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_from_directory, render_template
2
+ import edge_tts
3
+ import asyncio
4
+ import os
5
+ import uuid
6
+
7
+ app = Flask(__name__)
8
+
9
+ # Config
10
+ AUDIO_DIR = os.path.join(os.path.dirname(__file__), 'audio')
11
+
12
+ if not os.path.exists(AUDIO_DIR):
13
+ os.makedirs(AUDIO_DIR)
14
+
15
+ # Dictionaries of available free voices
16
+ VOICES = {
17
+ # Vietnamese
18
+ 'nam_minh_northern_male': 'vi-VN-NamMinhNeural',
19
+ 'hoai_my_southern_female': 'vi-VN-HoaiMyNeural',
20
+ # English
21
+ 'jenny_us_female': 'en-US-JennyNeural',
22
+ 'guy_us_male': 'en-US-GuyNeural',
23
+ # Japanese
24
+ 'nanami_jp_female': 'ja-JP-NanamiNeural',
25
+ 'keita_jp_male': 'ja-JP-KeitaNeural'
26
+ }
27
+
28
+ @app.route('/')
29
+ def index_vi():
30
+ return render_template('index.html')
31
+
32
+ @app.route('/en')
33
+ def index_en():
34
+ return render_template('en.html')
35
+
36
+ @app.route('/jp')
37
+ def index_jp():
38
+ return render_template('jp.html')
39
+
40
+ @app.route('/api/synthesize', methods=['POST'])
41
+ def synthesize():
42
+ data = request.json
43
+ if not data or 'text' not in data or 'voice' not in data:
44
+ return jsonify({"error": "Missing text or voice in request"}), 400
45
+
46
+ text = data['text']
47
+ voice_key = data['voice']
48
+
49
+ # Check if voice_key exists
50
+ if voice_key not in VOICES:
51
+ return jsonify({"error": "Invalid voice selected"}), 400
52
+
53
+ voice_id = VOICES[voice_key]
54
+
55
+ # Generate a unique filename
56
+ filename = f"{uuid.uuid4()}.mp3"
57
+ filepath = os.path.join(AUDIO_DIR, filename)
58
+
59
+ # We use asyncio to run the edge-tts generation
60
+ async def _generate_audio():
61
+ communicate = edge_tts.Communicate(text, voice_id)
62
+ await communicate.save(filepath)
63
+
64
+ try:
65
+ # Generate base TTS
66
+ asyncio.run(_generate_audio())
67
+
68
+ return jsonify({
69
+ "success": True,
70
+ "audio_url": f"/audio/{filename}"
71
+ })
72
+ except Exception as e:
73
+ return jsonify({"error": str(e)}), 500
74
+
75
+ @app.route('/audio/<filename>')
76
+ def serve_audio(filename):
77
+ return send_from_directory(AUDIO_DIR, filename)
78
+
79
+ if __name__ == '__main__':
80
+ port = int(os.environ.get("PORT", 5000))
81
+ app.run(host='0.0.0.0', port=port)
draft.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_from_directory, render_template
2
+ import edge_tts
3
+ import asyncio
4
+ import os
5
+ import uuid
6
+ from rvc_service import convert_voice, init_rvc
7
+
8
+ app = Flask(__name__)
9
+
10
+ # Initialize RVC locally if possible
11
+ try:
12
+ init_rvc()
13
+ except Exception as e:
14
+ print(f"RVC intialization failed: {e}")
15
+
16
+ # Directory to store generated audio files
17
+ AUDIO_DIR = os.path.join(os.path.dirname(__file__), 'audio')
18
+ if not os.path.exists(AUDIO_DIR):
19
+ os.makedirs(AUDIO_DIR)
20
+
21
+ # Dictionary of available free voices
22
+ VOICES = {
23
+ # Vietnamese
24
+ 'nam_minh_northern_male': 'vi-VN-NamMinhNeural',
25
+ 'hoai_my_southern_female': 'vi-VN-HoaiMyNeural',
26
+ # English
27
+ 'jenny_us_female': 'en-US-JennyNeural',
28
+ 'guy_us_male': 'en-US-GuyNeural'
29
+ }
30
+
31
+ @app.route('/')
32
+ def index():
33
+ return render_template('index.html')
34
+
35
+ @app.route('/en')
36
+ def english_index():
37
+ return render_template('en.html')
38
+
39
+ @app.route('/api/synthesize', methods=['POST'])
40
+ def synthesize():
41
+ data = request.json
42
+ if not data or 'text' not in data or 'voice' not in data:
43
+ return jsonify({"error": "Missing text or voice in request"}), 400
44
+
45
+ text = data['text']
46
+ voice_key = data['voice']
47
+ anime_voice = data.get('anime_voice', 'none') # Can be 'none' or 'mahiru'
48
+
49
+ if voice_key not in VOICES:
50
+ return jsonify({"error": "Invalid base voice selected"}), 400
51
+
52
+ voice_id = VOICES[voice_key]
53
+
54
+ # Generate a unique filename
55
+ base_filename = f"{uuid.uuid4()}_base.mp3"
56
+ final_filename = f"{uuid.uuid4()}_final.mp3"
57
+
58
+ base_filepath = os.path.join(AUDIO_DIR, base_filename)
59
+ final_filepath = os.path.join(AUDIO_DIR, final_filename)
60
+
61
+ # We use asyncio to run the edge-tts generation
62
+ async def _generate_audio():
63
+ communicate = edge_tts.Communicate(text, voice_id)
64
+ await communicate.save(base_filepath)
65
+
66
+ try:
67
+ # Step 1: Generate Base TTS Audio
68
+ asyncio.run(_generate_audio())
69
+
70
+ # Step 2: Apply RVC if requested
71
+ if anime_voice == 'mahiru':
72
+ convert_voice(base_filepath, final_filepath)
73
+ # We serve the final (cloned) file
74
+ serve_filename = final_filename
75
+ # Cleanup base if you want, but leaving it is fine for debugging.
76
+ else:
77
+ # If no anime voice, serve the base TTS directly
78
+ serve_filename = base_filename
79
+
80
+ return jsonify({
81
+ "success": True,
82
+ "audio_url": f"/audio/{serve_filename}"
83
+ })
84
+ except Exception as e:
85
+ return jsonify({"error": str(e)}), 500
86
+
87
+ @app.route('/audio/<filename>')
88
+ def serve_audio(filename):
89
+ return send_from_directory(AUDIO_DIR, filename)
90
+
91
+ if __name__ == '__main__':
92
+ app.run(debug=True, port=5000)
img/Capture.PNG ADDED
mahiru/MahiruShiina.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d2fd429a500e929c9ce923f7a28b9794056ad36d72e88f6ccaa5b078982a31c
3
+ size 57589524
mahiru/mahiru.index ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c99e311924d4ecad7478584a244c6f19d23247c2fecac670a2cd5043d3d075a
3
+ size 3264939
piper_voices/en_GB-alan-medium.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a309668932205e762801f1efc2736cd4b0120329622adf62be09e56339d3330
3
+ size 63201294
piper_voices/en_GB-alan-medium.onnx.json ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio": {
3
+ "sample_rate": 22050,
4
+ "quality": "medium"
5
+ },
6
+ "espeak": {
7
+ "voice": "en-gb-x-rp"
8
+ },
9
+ "inference": {
10
+ "noise_scale": 0.667,
11
+ "length_scale": 1,
12
+ "noise_w": 0.8
13
+ },
14
+ "phoneme_type": "espeak",
15
+ "phoneme_map": {},
16
+ "phoneme_id_map": {
17
+ "_": [
18
+ 0
19
+ ],
20
+ "^": [
21
+ 1
22
+ ],
23
+ "$": [
24
+ 2
25
+ ],
26
+ " ": [
27
+ 3
28
+ ],
29
+ "!": [
30
+ 4
31
+ ],
32
+ "'": [
33
+ 5
34
+ ],
35
+ "(": [
36
+ 6
37
+ ],
38
+ ")": [
39
+ 7
40
+ ],
41
+ ",": [
42
+ 8
43
+ ],
44
+ "-": [
45
+ 9
46
+ ],
47
+ ".": [
48
+ 10
49
+ ],
50
+ ":": [
51
+ 11
52
+ ],
53
+ ";": [
54
+ 12
55
+ ],
56
+ "?": [
57
+ 13
58
+ ],
59
+ "a": [
60
+ 14
61
+ ],
62
+ "b": [
63
+ 15
64
+ ],
65
+ "c": [
66
+ 16
67
+ ],
68
+ "d": [
69
+ 17
70
+ ],
71
+ "e": [
72
+ 18
73
+ ],
74
+ "f": [
75
+ 19
76
+ ],
77
+ "h": [
78
+ 20
79
+ ],
80
+ "i": [
81
+ 21
82
+ ],
83
+ "j": [
84
+ 22
85
+ ],
86
+ "k": [
87
+ 23
88
+ ],
89
+ "l": [
90
+ 24
91
+ ],
92
+ "m": [
93
+ 25
94
+ ],
95
+ "n": [
96
+ 26
97
+ ],
98
+ "o": [
99
+ 27
100
+ ],
101
+ "p": [
102
+ 28
103
+ ],
104
+ "q": [
105
+ 29
106
+ ],
107
+ "r": [
108
+ 30
109
+ ],
110
+ "s": [
111
+ 31
112
+ ],
113
+ "t": [
114
+ 32
115
+ ],
116
+ "u": [
117
+ 33
118
+ ],
119
+ "v": [
120
+ 34
121
+ ],
122
+ "w": [
123
+ 35
124
+ ],
125
+ "x": [
126
+ 36
127
+ ],
128
+ "y": [
129
+ 37
130
+ ],
131
+ "z": [
132
+ 38
133
+ ],
134
+ "æ": [
135
+ 39
136
+ ],
137
+ "ç": [
138
+ 40
139
+ ],
140
+ "ð": [
141
+ 41
142
+ ],
143
+ "ø": [
144
+ 42
145
+ ],
146
+ "ħ": [
147
+ 43
148
+ ],
149
+ "ŋ": [
150
+ 44
151
+ ],
152
+ "œ": [
153
+ 45
154
+ ],
155
+ "ǀ": [
156
+ 46
157
+ ],
158
+ "ǁ": [
159
+ 47
160
+ ],
161
+ "ǂ": [
162
+ 48
163
+ ],
164
+ "ǃ": [
165
+ 49
166
+ ],
167
+ "ɐ": [
168
+ 50
169
+ ],
170
+ "ɑ": [
171
+ 51
172
+ ],
173
+ "ɒ": [
174
+ 52
175
+ ],
176
+ "ɓ": [
177
+ 53
178
+ ],
179
+ "ɔ": [
180
+ 54
181
+ ],
182
+ "ɕ": [
183
+ 55
184
+ ],
185
+ "ɖ": [
186
+ 56
187
+ ],
188
+ "ɗ": [
189
+ 57
190
+ ],
191
+ "ɘ": [
192
+ 58
193
+ ],
194
+ "ə": [
195
+ 59
196
+ ],
197
+ "ɚ": [
198
+ 60
199
+ ],
200
+ "ɛ": [
201
+ 61
202
+ ],
203
+ "ɜ": [
204
+ 62
205
+ ],
206
+ "ɞ": [
207
+ 63
208
+ ],
209
+ "ɟ": [
210
+ 64
211
+ ],
212
+ "ɠ": [
213
+ 65
214
+ ],
215
+ "ɡ": [
216
+ 66
217
+ ],
218
+ "ɢ": [
219
+ 67
220
+ ],
221
+ "ɣ": [
222
+ 68
223
+ ],
224
+ "ɤ": [
225
+ 69
226
+ ],
227
+ "ɥ": [
228
+ 70
229
+ ],
230
+ "ɦ": [
231
+ 71
232
+ ],
233
+ "ɧ": [
234
+ 72
235
+ ],
236
+ "ɨ": [
237
+ 73
238
+ ],
239
+ "ɪ": [
240
+ 74
241
+ ],
242
+ "ɫ": [
243
+ 75
244
+ ],
245
+ "ɬ": [
246
+ 76
247
+ ],
248
+ "ɭ": [
249
+ 77
250
+ ],
251
+ "ɮ": [
252
+ 78
253
+ ],
254
+ "ɯ": [
255
+ 79
256
+ ],
257
+ "ɰ": [
258
+ 80
259
+ ],
260
+ "ɱ": [
261
+ 81
262
+ ],
263
+ "ɲ": [
264
+ 82
265
+ ],
266
+ "ɳ": [
267
+ 83
268
+ ],
269
+ "ɴ": [
270
+ 84
271
+ ],
272
+ "ɵ": [
273
+ 85
274
+ ],
275
+ "ɶ": [
276
+ 86
277
+ ],
278
+ "ɸ": [
279
+ 87
280
+ ],
281
+ "ɹ": [
282
+ 88
283
+ ],
284
+ "ɺ": [
285
+ 89
286
+ ],
287
+ "ɻ": [
288
+ 90
289
+ ],
290
+ "ɽ": [
291
+ 91
292
+ ],
293
+ "ɾ": [
294
+ 92
295
+ ],
296
+ "ʀ": [
297
+ 93
298
+ ],
299
+ "ʁ": [
300
+ 94
301
+ ],
302
+ "ʂ": [
303
+ 95
304
+ ],
305
+ "ʃ": [
306
+ 96
307
+ ],
308
+ "ʄ": [
309
+ 97
310
+ ],
311
+ "ʈ": [
312
+ 98
313
+ ],
314
+ "ʉ": [
315
+ 99
316
+ ],
317
+ "ʊ": [
318
+ 100
319
+ ],
320
+ "ʋ": [
321
+ 101
322
+ ],
323
+ "ʌ": [
324
+ 102
325
+ ],
326
+ "ʍ": [
327
+ 103
328
+ ],
329
+ "ʎ": [
330
+ 104
331
+ ],
332
+ "ʏ": [
333
+ 105
334
+ ],
335
+ "ʐ": [
336
+ 106
337
+ ],
338
+ "ʑ": [
339
+ 107
340
+ ],
341
+ "ʒ": [
342
+ 108
343
+ ],
344
+ "ʔ": [
345
+ 109
346
+ ],
347
+ "ʕ": [
348
+ 110
349
+ ],
350
+ "ʘ": [
351
+ 111
352
+ ],
353
+ "ʙ": [
354
+ 112
355
+ ],
356
+ "ʛ": [
357
+ 113
358
+ ],
359
+ "ʜ": [
360
+ 114
361
+ ],
362
+ "ʝ": [
363
+ 115
364
+ ],
365
+ "ʟ": [
366
+ 116
367
+ ],
368
+ "ʡ": [
369
+ 117
370
+ ],
371
+ "ʢ": [
372
+ 118
373
+ ],
374
+ "ʲ": [
375
+ 119
376
+ ],
377
+ "ˈ": [
378
+ 120
379
+ ],
380
+ "ˌ": [
381
+ 121
382
+ ],
383
+ "ː": [
384
+ 122
385
+ ],
386
+ "ˑ": [
387
+ 123
388
+ ],
389
+ "˞": [
390
+ 124
391
+ ],
392
+ "β": [
393
+ 125
394
+ ],
395
+ "θ": [
396
+ 126
397
+ ],
398
+ "χ": [
399
+ 127
400
+ ],
401
+ "ᵻ": [
402
+ 128
403
+ ],
404
+ "ⱱ": [
405
+ 129
406
+ ],
407
+ "0": [
408
+ 130
409
+ ],
410
+ "1": [
411
+ 131
412
+ ],
413
+ "2": [
414
+ 132
415
+ ],
416
+ "3": [
417
+ 133
418
+ ],
419
+ "4": [
420
+ 134
421
+ ],
422
+ "5": [
423
+ 135
424
+ ],
425
+ "6": [
426
+ 136
427
+ ],
428
+ "7": [
429
+ 137
430
+ ],
431
+ "8": [
432
+ 138
433
+ ],
434
+ "9": [
435
+ 139
436
+ ],
437
+ "̧": [
438
+ 140
439
+ ],
440
+ "̃": [
441
+ 141
442
+ ],
443
+ "̪": [
444
+ 142
445
+ ],
446
+ "̯": [
447
+ 143
448
+ ],
449
+ "̩": [
450
+ 144
451
+ ],
452
+ "ʰ": [
453
+ 145
454
+ ],
455
+ "ˤ": [
456
+ 146
457
+ ],
458
+ "ε": [
459
+ 147
460
+ ],
461
+ "↓": [
462
+ 148
463
+ ],
464
+ "#": [
465
+ 149
466
+ ],
467
+ "\"": [
468
+ 150
469
+ ],
470
+ "↑": [
471
+ 151
472
+ ],
473
+ "̺": [
474
+ 152
475
+ ],
476
+ "̻": [
477
+ 153
478
+ ]
479
+ },
480
+ "num_symbols": 256,
481
+ "num_speakers": 1,
482
+ "speaker_id_map": {},
483
+ "piper_version": "1.0.0",
484
+ "language": {
485
+ "code": "en_GB",
486
+ "family": "en",
487
+ "region": "GB",
488
+ "name_native": "English",
489
+ "name_english": "English",
490
+ "country_english": "Great Britain"
491
+ },
492
+ "dataset": "alan"
493
+ }
piper_voices/en_US-kathleen-low.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:87adf17f5326bc0782282147a8b9788406236245f0f9b0e68dacb651bc1de8b6
3
+ size 63104526
piper_voices/en_US-kathleen-low.onnx.json ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio": {
3
+ "sample_rate": 16000,
4
+ "quality": "low"
5
+ },
6
+ "espeak": {
7
+ "voice": "en-us"
8
+ },
9
+ "inference": {
10
+ "noise_scale": 0.667,
11
+ "length_scale": 1,
12
+ "noise_w": 0.8
13
+ },
14
+ "phoneme_map": {},
15
+ "phoneme_id_map": {
16
+ "_": [
17
+ 0
18
+ ],
19
+ "^": [
20
+ 1
21
+ ],
22
+ "$": [
23
+ 2
24
+ ],
25
+ " ": [
26
+ 3
27
+ ],
28
+ "!": [
29
+ 4
30
+ ],
31
+ "'": [
32
+ 5
33
+ ],
34
+ "(": [
35
+ 6
36
+ ],
37
+ ")": [
38
+ 7
39
+ ],
40
+ ",": [
41
+ 8
42
+ ],
43
+ "-": [
44
+ 9
45
+ ],
46
+ ".": [
47
+ 10
48
+ ],
49
+ ":": [
50
+ 11
51
+ ],
52
+ ";": [
53
+ 12
54
+ ],
55
+ "?": [
56
+ 13
57
+ ],
58
+ "a": [
59
+ 14
60
+ ],
61
+ "b": [
62
+ 15
63
+ ],
64
+ "c": [
65
+ 16
66
+ ],
67
+ "d": [
68
+ 17
69
+ ],
70
+ "e": [
71
+ 18
72
+ ],
73
+ "f": [
74
+ 19
75
+ ],
76
+ "h": [
77
+ 20
78
+ ],
79
+ "i": [
80
+ 21
81
+ ],
82
+ "j": [
83
+ 22
84
+ ],
85
+ "k": [
86
+ 23
87
+ ],
88
+ "l": [
89
+ 24
90
+ ],
91
+ "m": [
92
+ 25
93
+ ],
94
+ "n": [
95
+ 26
96
+ ],
97
+ "o": [
98
+ 27
99
+ ],
100
+ "p": [
101
+ 28
102
+ ],
103
+ "q": [
104
+ 29
105
+ ],
106
+ "r": [
107
+ 30
108
+ ],
109
+ "s": [
110
+ 31
111
+ ],
112
+ "t": [
113
+ 32
114
+ ],
115
+ "u": [
116
+ 33
117
+ ],
118
+ "v": [
119
+ 34
120
+ ],
121
+ "w": [
122
+ 35
123
+ ],
124
+ "x": [
125
+ 36
126
+ ],
127
+ "y": [
128
+ 37
129
+ ],
130
+ "z": [
131
+ 38
132
+ ],
133
+ "æ": [
134
+ 39
135
+ ],
136
+ "ç": [
137
+ 40
138
+ ],
139
+ "ð": [
140
+ 41
141
+ ],
142
+ "ø": [
143
+ 42
144
+ ],
145
+ "ħ": [
146
+ 43
147
+ ],
148
+ "ŋ": [
149
+ 44
150
+ ],
151
+ "œ": [
152
+ 45
153
+ ],
154
+ "ǀ": [
155
+ 46
156
+ ],
157
+ "ǁ": [
158
+ 47
159
+ ],
160
+ "ǂ": [
161
+ 48
162
+ ],
163
+ "ǃ": [
164
+ 49
165
+ ],
166
+ "ɐ": [
167
+ 50
168
+ ],
169
+ "ɑ": [
170
+ 51
171
+ ],
172
+ "ɒ": [
173
+ 52
174
+ ],
175
+ "ɓ": [
176
+ 53
177
+ ],
178
+ "ɔ": [
179
+ 54
180
+ ],
181
+ "ɕ": [
182
+ 55
183
+ ],
184
+ "ɖ": [
185
+ 56
186
+ ],
187
+ "ɗ": [
188
+ 57
189
+ ],
190
+ "ɘ": [
191
+ 58
192
+ ],
193
+ "ə": [
194
+ 59
195
+ ],
196
+ "ɚ": [
197
+ 60
198
+ ],
199
+ "ɛ": [
200
+ 61
201
+ ],
202
+ "ɜ": [
203
+ 62
204
+ ],
205
+ "ɞ": [
206
+ 63
207
+ ],
208
+ "ɟ": [
209
+ 64
210
+ ],
211
+ "ɠ": [
212
+ 65
213
+ ],
214
+ "ɡ": [
215
+ 66
216
+ ],
217
+ "ɢ": [
218
+ 67
219
+ ],
220
+ "ɣ": [
221
+ 68
222
+ ],
223
+ "ɤ": [
224
+ 69
225
+ ],
226
+ "ɥ": [
227
+ 70
228
+ ],
229
+ "ɦ": [
230
+ 71
231
+ ],
232
+ "ɧ": [
233
+ 72
234
+ ],
235
+ "ɨ": [
236
+ 73
237
+ ],
238
+ "ɪ": [
239
+ 74
240
+ ],
241
+ "ɫ": [
242
+ 75
243
+ ],
244
+ "ɬ": [
245
+ 76
246
+ ],
247
+ "ɭ": [
248
+ 77
249
+ ],
250
+ "ɮ": [
251
+ 78
252
+ ],
253
+ "ɯ": [
254
+ 79
255
+ ],
256
+ "ɰ": [
257
+ 80
258
+ ],
259
+ "ɱ": [
260
+ 81
261
+ ],
262
+ "ɲ": [
263
+ 82
264
+ ],
265
+ "ɳ": [
266
+ 83
267
+ ],
268
+ "ɴ": [
269
+ 84
270
+ ],
271
+ "ɵ": [
272
+ 85
273
+ ],
274
+ "ɶ": [
275
+ 86
276
+ ],
277
+ "ɸ": [
278
+ 87
279
+ ],
280
+ "ɹ": [
281
+ 88
282
+ ],
283
+ "ɺ": [
284
+ 89
285
+ ],
286
+ "ɻ": [
287
+ 90
288
+ ],
289
+ "ɽ": [
290
+ 91
291
+ ],
292
+ "ɾ": [
293
+ 92
294
+ ],
295
+ "ʀ": [
296
+ 93
297
+ ],
298
+ "ʁ": [
299
+ 94
300
+ ],
301
+ "ʂ": [
302
+ 95
303
+ ],
304
+ "ʃ": [
305
+ 96
306
+ ],
307
+ "ʄ": [
308
+ 97
309
+ ],
310
+ "ʈ": [
311
+ 98
312
+ ],
313
+ "ʉ": [
314
+ 99
315
+ ],
316
+ "ʊ": [
317
+ 100
318
+ ],
319
+ "ʋ": [
320
+ 101
321
+ ],
322
+ "ʌ": [
323
+ 102
324
+ ],
325
+ "ʍ": [
326
+ 103
327
+ ],
328
+ "ʎ": [
329
+ 104
330
+ ],
331
+ "ʏ": [
332
+ 105
333
+ ],
334
+ "ʐ": [
335
+ 106
336
+ ],
337
+ "ʑ": [
338
+ 107
339
+ ],
340
+ "ʒ": [
341
+ 108
342
+ ],
343
+ "ʔ": [
344
+ 109
345
+ ],
346
+ "ʕ": [
347
+ 110
348
+ ],
349
+ "ʘ": [
350
+ 111
351
+ ],
352
+ "ʙ": [
353
+ 112
354
+ ],
355
+ "ʛ": [
356
+ 113
357
+ ],
358
+ "ʜ": [
359
+ 114
360
+ ],
361
+ "ʝ": [
362
+ 115
363
+ ],
364
+ "ʟ": [
365
+ 116
366
+ ],
367
+ "ʡ": [
368
+ 117
369
+ ],
370
+ "ʢ": [
371
+ 118
372
+ ],
373
+ "ʲ": [
374
+ 119
375
+ ],
376
+ "ˈ": [
377
+ 120
378
+ ],
379
+ "ˌ": [
380
+ 121
381
+ ],
382
+ "ː": [
383
+ 122
384
+ ],
385
+ "ˑ": [
386
+ 123
387
+ ],
388
+ "˞": [
389
+ 124
390
+ ],
391
+ "β": [
392
+ 125
393
+ ],
394
+ "θ": [
395
+ 126
396
+ ],
397
+ "χ": [
398
+ 127
399
+ ],
400
+ "ᵻ": [
401
+ 128
402
+ ],
403
+ "ⱱ": [
404
+ 129
405
+ ]
406
+ },
407
+ "num_symbols": 130,
408
+ "num_speakers": 1,
409
+ "speaker_id_map": {},
410
+ "piper_version": "0.2.0",
411
+ "language": {
412
+ "code": "en_US",
413
+ "family": "en",
414
+ "region": "US",
415
+ "name_native": "English",
416
+ "name_english": "English",
417
+ "country_english": "United States"
418
+ },
419
+ "dataset": "kathleen"
420
+ }
piper_voices/en_US-lessac-medium.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5efe09e69902187827af646e1a6e9d269dee769f9877d17b16b1b46eeaaf019f
3
+ size 63201294
piper_voices/en_US-lessac-medium.onnx.json ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio": {
3
+ "sample_rate": 22050,
4
+ "quality": "medium"
5
+ },
6
+ "espeak": {
7
+ "voice": "en-us"
8
+ },
9
+ "inference": {
10
+ "noise_scale": 0.667,
11
+ "length_scale": 1,
12
+ "noise_w": 0.8
13
+ },
14
+ "phoneme_type": "espeak",
15
+ "phoneme_map": {},
16
+ "phoneme_id_map": {
17
+ "_": [
18
+ 0
19
+ ],
20
+ "^": [
21
+ 1
22
+ ],
23
+ "$": [
24
+ 2
25
+ ],
26
+ " ": [
27
+ 3
28
+ ],
29
+ "!": [
30
+ 4
31
+ ],
32
+ "'": [
33
+ 5
34
+ ],
35
+ "(": [
36
+ 6
37
+ ],
38
+ ")": [
39
+ 7
40
+ ],
41
+ ",": [
42
+ 8
43
+ ],
44
+ "-": [
45
+ 9
46
+ ],
47
+ ".": [
48
+ 10
49
+ ],
50
+ ":": [
51
+ 11
52
+ ],
53
+ ";": [
54
+ 12
55
+ ],
56
+ "?": [
57
+ 13
58
+ ],
59
+ "a": [
60
+ 14
61
+ ],
62
+ "b": [
63
+ 15
64
+ ],
65
+ "c": [
66
+ 16
67
+ ],
68
+ "d": [
69
+ 17
70
+ ],
71
+ "e": [
72
+ 18
73
+ ],
74
+ "f": [
75
+ 19
76
+ ],
77
+ "h": [
78
+ 20
79
+ ],
80
+ "i": [
81
+ 21
82
+ ],
83
+ "j": [
84
+ 22
85
+ ],
86
+ "k": [
87
+ 23
88
+ ],
89
+ "l": [
90
+ 24
91
+ ],
92
+ "m": [
93
+ 25
94
+ ],
95
+ "n": [
96
+ 26
97
+ ],
98
+ "o": [
99
+ 27
100
+ ],
101
+ "p": [
102
+ 28
103
+ ],
104
+ "q": [
105
+ 29
106
+ ],
107
+ "r": [
108
+ 30
109
+ ],
110
+ "s": [
111
+ 31
112
+ ],
113
+ "t": [
114
+ 32
115
+ ],
116
+ "u": [
117
+ 33
118
+ ],
119
+ "v": [
120
+ 34
121
+ ],
122
+ "w": [
123
+ 35
124
+ ],
125
+ "x": [
126
+ 36
127
+ ],
128
+ "y": [
129
+ 37
130
+ ],
131
+ "z": [
132
+ 38
133
+ ],
134
+ "æ": [
135
+ 39
136
+ ],
137
+ "ç": [
138
+ 40
139
+ ],
140
+ "ð": [
141
+ 41
142
+ ],
143
+ "ø": [
144
+ 42
145
+ ],
146
+ "ħ": [
147
+ 43
148
+ ],
149
+ "ŋ": [
150
+ 44
151
+ ],
152
+ "œ": [
153
+ 45
154
+ ],
155
+ "ǀ": [
156
+ 46
157
+ ],
158
+ "ǁ": [
159
+ 47
160
+ ],
161
+ "ǂ": [
162
+ 48
163
+ ],
164
+ "ǃ": [
165
+ 49
166
+ ],
167
+ "ɐ": [
168
+ 50
169
+ ],
170
+ "ɑ": [
171
+ 51
172
+ ],
173
+ "ɒ": [
174
+ 52
175
+ ],
176
+ "ɓ": [
177
+ 53
178
+ ],
179
+ "ɔ": [
180
+ 54
181
+ ],
182
+ "ɕ": [
183
+ 55
184
+ ],
185
+ "ɖ": [
186
+ 56
187
+ ],
188
+ "ɗ": [
189
+ 57
190
+ ],
191
+ "ɘ": [
192
+ 58
193
+ ],
194
+ "ə": [
195
+ 59
196
+ ],
197
+ "ɚ": [
198
+ 60
199
+ ],
200
+ "ɛ": [
201
+ 61
202
+ ],
203
+ "ɜ": [
204
+ 62
205
+ ],
206
+ "ɞ": [
207
+ 63
208
+ ],
209
+ "ɟ": [
210
+ 64
211
+ ],
212
+ "ɠ": [
213
+ 65
214
+ ],
215
+ "ɡ": [
216
+ 66
217
+ ],
218
+ "ɢ": [
219
+ 67
220
+ ],
221
+ "ɣ": [
222
+ 68
223
+ ],
224
+ "ɤ": [
225
+ 69
226
+ ],
227
+ "ɥ": [
228
+ 70
229
+ ],
230
+ "ɦ": [
231
+ 71
232
+ ],
233
+ "ɧ": [
234
+ 72
235
+ ],
236
+ "ɨ": [
237
+ 73
238
+ ],
239
+ "ɪ": [
240
+ 74
241
+ ],
242
+ "ɫ": [
243
+ 75
244
+ ],
245
+ "ɬ": [
246
+ 76
247
+ ],
248
+ "ɭ": [
249
+ 77
250
+ ],
251
+ "ɮ": [
252
+ 78
253
+ ],
254
+ "ɯ": [
255
+ 79
256
+ ],
257
+ "ɰ": [
258
+ 80
259
+ ],
260
+ "ɱ": [
261
+ 81
262
+ ],
263
+ "ɲ": [
264
+ 82
265
+ ],
266
+ "ɳ": [
267
+ 83
268
+ ],
269
+ "ɴ": [
270
+ 84
271
+ ],
272
+ "ɵ": [
273
+ 85
274
+ ],
275
+ "ɶ": [
276
+ 86
277
+ ],
278
+ "ɸ": [
279
+ 87
280
+ ],
281
+ "ɹ": [
282
+ 88
283
+ ],
284
+ "ɺ": [
285
+ 89
286
+ ],
287
+ "ɻ": [
288
+ 90
289
+ ],
290
+ "ɽ": [
291
+ 91
292
+ ],
293
+ "ɾ": [
294
+ 92
295
+ ],
296
+ "ʀ": [
297
+ 93
298
+ ],
299
+ "ʁ": [
300
+ 94
301
+ ],
302
+ "ʂ": [
303
+ 95
304
+ ],
305
+ "ʃ": [
306
+ 96
307
+ ],
308
+ "ʄ": [
309
+ 97
310
+ ],
311
+ "ʈ": [
312
+ 98
313
+ ],
314
+ "ʉ": [
315
+ 99
316
+ ],
317
+ "ʊ": [
318
+ 100
319
+ ],
320
+ "ʋ": [
321
+ 101
322
+ ],
323
+ "ʌ": [
324
+ 102
325
+ ],
326
+ "ʍ": [
327
+ 103
328
+ ],
329
+ "ʎ": [
330
+ 104
331
+ ],
332
+ "ʏ": [
333
+ 105
334
+ ],
335
+ "ʐ": [
336
+ 106
337
+ ],
338
+ "ʑ": [
339
+ 107
340
+ ],
341
+ "ʒ": [
342
+ 108
343
+ ],
344
+ "ʔ": [
345
+ 109
346
+ ],
347
+ "ʕ": [
348
+ 110
349
+ ],
350
+ "ʘ": [
351
+ 111
352
+ ],
353
+ "ʙ": [
354
+ 112
355
+ ],
356
+ "ʛ": [
357
+ 113
358
+ ],
359
+ "ʜ": [
360
+ 114
361
+ ],
362
+ "ʝ": [
363
+ 115
364
+ ],
365
+ "ʟ": [
366
+ 116
367
+ ],
368
+ "ʡ": [
369
+ 117
370
+ ],
371
+ "ʢ": [
372
+ 118
373
+ ],
374
+ "ʲ": [
375
+ 119
376
+ ],
377
+ "ˈ": [
378
+ 120
379
+ ],
380
+ "ˌ": [
381
+ 121
382
+ ],
383
+ "ː": [
384
+ 122
385
+ ],
386
+ "ˑ": [
387
+ 123
388
+ ],
389
+ "˞": [
390
+ 124
391
+ ],
392
+ "β": [
393
+ 125
394
+ ],
395
+ "θ": [
396
+ 126
397
+ ],
398
+ "χ": [
399
+ 127
400
+ ],
401
+ "ᵻ": [
402
+ 128
403
+ ],
404
+ "ⱱ": [
405
+ 129
406
+ ],
407
+ "0": [
408
+ 130
409
+ ],
410
+ "1": [
411
+ 131
412
+ ],
413
+ "2": [
414
+ 132
415
+ ],
416
+ "3": [
417
+ 133
418
+ ],
419
+ "4": [
420
+ 134
421
+ ],
422
+ "5": [
423
+ 135
424
+ ],
425
+ "6": [
426
+ 136
427
+ ],
428
+ "7": [
429
+ 137
430
+ ],
431
+ "8": [
432
+ 138
433
+ ],
434
+ "9": [
435
+ 139
436
+ ],
437
+ "̧": [
438
+ 140
439
+ ],
440
+ "̃": [
441
+ 141
442
+ ],
443
+ "̪": [
444
+ 142
445
+ ],
446
+ "̯": [
447
+ 143
448
+ ],
449
+ "̩": [
450
+ 144
451
+ ],
452
+ "ʰ": [
453
+ 145
454
+ ],
455
+ "ˤ": [
456
+ 146
457
+ ],
458
+ "ε": [
459
+ 147
460
+ ],
461
+ "↓": [
462
+ 148
463
+ ],
464
+ "#": [
465
+ 149
466
+ ],
467
+ "\"": [
468
+ 150
469
+ ],
470
+ "↑": [
471
+ 151
472
+ ],
473
+ "̺": [
474
+ 152
475
+ ],
476
+ "̻": [
477
+ 153
478
+ ]
479
+ },
480
+ "num_symbols": 256,
481
+ "num_speakers": 1,
482
+ "speaker_id_map": {},
483
+ "piper_version": "1.0.0",
484
+ "language": {
485
+ "code": "en_US",
486
+ "family": "en",
487
+ "region": "US",
488
+ "name_native": "English",
489
+ "name_english": "English",
490
+ "country_english": "United States"
491
+ },
492
+ "dataset": "lessac"
493
+ }
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # requirements.txt
2
+ Flask
3
+ requests
4
+ pydub
5
+ edge-tts
static/script.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const textInput = document.getElementById('text-input');
3
+ const charCurrent = document.getElementById('char-current');
4
+ const voiceSelect = document.getElementById('voice-select');
5
+ const generateBtn = document.getElementById('generate-btn');
6
+ const btnText = document.querySelector('.btn-text');
7
+ const btnLoader = document.querySelector('.btn-loader');
8
+ const errorMessage = document.getElementById('error-message');
9
+ const errorText = document.getElementById('error-text');
10
+ const audioContainer = document.getElementById('audio-container');
11
+ const audioPlayer = document.getElementById('audio-player');
12
+ const downloadBtn = document.getElementById('download-btn');
13
+
14
+ const MAX_CHARS = 1000;
15
+
16
+ // Character counter
17
+ textInput.addEventListener('input', () => {
18
+ const text = textInput.value;
19
+ const length = text.length;
20
+
21
+ if (length > MAX_CHARS) {
22
+ textInput.value = text.substring(0, MAX_CHARS);
23
+ charCurrent.textContent = MAX_CHARS;
24
+ charCurrent.style.color = 'var(--danger)';
25
+ } else {
26
+ charCurrent.textContent = length;
27
+ charCurrent.style.color = 'var(--text-muted)';
28
+ }
29
+ });
30
+
31
+ // Generate Button Click
32
+ generateBtn.addEventListener('click', async () => {
33
+ const text = textInput.value.trim();
34
+ const voice = voiceSelect.value;
35
+
36
+ if (!text) {
37
+ showError("Vui lòng nhập văn bản tiếng Việt.");
38
+ textInput.focus();
39
+ return;
40
+ }
41
+
42
+ // Hide old errors/audio
43
+ errorMessage.classList.add('hidden');
44
+ audioContainer.classList.add('hidden');
45
+
46
+ // Setup Loading State
47
+ btnLoader.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Processing...';
48
+ setLoading(true);
49
+
50
+ try {
51
+ const response = await fetch('/api/synthesize', {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json'
55
+ },
56
+ body: JSON.stringify({ text, voice })
57
+ });
58
+
59
+ const data = await response.json();
60
+
61
+ if (!response.ok) {
62
+ throw new Error(data.error || 'Failed to generate speech.');
63
+ }
64
+
65
+ // Success
66
+ const uniqueUrl = data.audio_url + '?t=' + new Date().getTime(); // Prevent caching
67
+ audioPlayer.src = uniqueUrl;
68
+ downloadBtn.href = uniqueUrl;
69
+ downloadBtn.download = `Vietnamese_TTS_${Date.now()}.mp3`;
70
+
71
+ audioContainer.classList.remove('hidden');
72
+ audioPlayer.play().catch(e => console.log("Auto-play blocked", e));
73
+
74
+ } catch (error) {
75
+ showError(error.message);
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ });
80
+
81
+ function setLoading(isLoading) {
82
+ if (isLoading) {
83
+ generateBtn.disabled = true;
84
+ btnText.classList.add('hidden');
85
+ btnLoader.classList.remove('hidden');
86
+ } else {
87
+ generateBtn.disabled = false;
88
+ btnText.classList.remove('hidden');
89
+ btnLoader.classList.add('hidden');
90
+ }
91
+ }
92
+
93
+ function showError(message) {
94
+ errorText.textContent = message;
95
+ errorMessage.classList.remove('hidden');
96
+ }
97
+ });
static/script_en.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const textInput = document.getElementById('text-input');
3
+ const charCurrent = document.getElementById('char-current');
4
+ const voiceSelect = document.getElementById('voice-select');
5
+ const generateBtn = document.getElementById('generate-btn');
6
+ const btnText = document.querySelector('.btn-text');
7
+ const btnLoader = document.querySelector('.btn-loader');
8
+ const errorMessage = document.getElementById('error-message');
9
+ const errorText = document.getElementById('error-text');
10
+ const audioContainer = document.getElementById('audio-container');
11
+ const audioPlayer = document.getElementById('audio-player');
12
+ const downloadBtn = document.getElementById('download-btn');
13
+
14
+ const MAX_CHARS = 1000;
15
+
16
+ // Character counter
17
+ textInput.addEventListener('input', () => {
18
+ const text = textInput.value;
19
+ const length = text.length;
20
+
21
+ if (length > MAX_CHARS) {
22
+ textInput.value = text.substring(0, MAX_CHARS);
23
+ charCurrent.textContent = MAX_CHARS;
24
+ charCurrent.style.color = 'var(--danger)';
25
+ } else {
26
+ charCurrent.textContent = length;
27
+ charCurrent.style.color = 'var(--text-muted)';
28
+ }
29
+ });
30
+
31
+ // Generate Button Click
32
+ generateBtn.addEventListener('click', async () => {
33
+ const text = textInput.value.trim();
34
+ const voice = voiceSelect.value;
35
+
36
+ if (!text) {
37
+ showError("Please enter some English text.");
38
+ textInput.focus();
39
+ return;
40
+ }
41
+
42
+ // Hide old errors/audio
43
+ errorMessage.classList.add('hidden');
44
+ audioContainer.classList.add('hidden');
45
+
46
+ // Setup Loading State
47
+ btnLoader.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Processing...';
48
+ setLoading(true);
49
+
50
+ try {
51
+ const response = await fetch('/api/synthesize', {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json'
55
+ },
56
+ body: JSON.stringify({ text, voice })
57
+ });
58
+
59
+ const data = await response.json();
60
+
61
+ if (!response.ok) {
62
+ throw new Error(data.error || 'Failed to generate speech.');
63
+ }
64
+
65
+ // Success
66
+ const uniqueUrl = data.audio_url + '?t=' + new Date().getTime(); // Prevent caching
67
+ audioPlayer.src = uniqueUrl;
68
+ downloadBtn.href = uniqueUrl;
69
+ downloadBtn.download = `English_TTS_${Date.now()}.mp3`;
70
+
71
+ audioContainer.classList.remove('hidden');
72
+ audioPlayer.play().catch(e => console.log("Auto-play blocked", e));
73
+
74
+ } catch (error) {
75
+ showError(error.message);
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ });
80
+
81
+ function setLoading(isLoading) {
82
+ if (isLoading) {
83
+ generateBtn.disabled = true;
84
+ btnText.classList.add('hidden');
85
+ btnLoader.classList.remove('hidden');
86
+ } else {
87
+ generateBtn.disabled = false;
88
+ btnText.classList.remove('hidden');
89
+ btnLoader.classList.add('hidden');
90
+ }
91
+ }
92
+
93
+ function showError(message) {
94
+ errorText.textContent = message;
95
+ errorMessage.classList.remove('hidden');
96
+ }
97
+ });
static/script_jp.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const textInput = document.getElementById('text-input');
3
+ const charCurrent = document.getElementById('char-current');
4
+ const voiceSelect = document.getElementById('voice-select');
5
+ const generateBtn = document.getElementById('generate-btn');
6
+ const btnText = document.querySelector('.btn-text');
7
+ const btnLoader = document.querySelector('.btn-loader');
8
+ const errorMessage = document.getElementById('error-message');
9
+ const errorText = document.getElementById('error-text');
10
+ const audioContainer = document.getElementById('audio-container');
11
+ const audioPlayer = document.getElementById('audio-player');
12
+ const downloadBtn = document.getElementById('download-btn');
13
+
14
+ const MAX_CHARS = 1000;
15
+
16
+ // Character counter
17
+ textInput.addEventListener('input', () => {
18
+ const text = textInput.value;
19
+ const length = text.length;
20
+
21
+ if (length > MAX_CHARS) {
22
+ textInput.value = text.substring(0, MAX_CHARS);
23
+ charCurrent.textContent = MAX_CHARS;
24
+ charCurrent.style.color = 'var(--danger)';
25
+ } else {
26
+ charCurrent.textContent = length;
27
+ charCurrent.style.color = 'var(--text-muted)';
28
+ }
29
+ });
30
+
31
+ // Generate Button Click
32
+ generateBtn.addEventListener('click', async () => {
33
+ const text = textInput.value.trim();
34
+ const voice = voiceSelect.value;
35
+
36
+ if (!text) {
37
+ showError("Please enter some Japanese text.");
38
+ textInput.focus();
39
+ return;
40
+ }
41
+
42
+ // Hide old errors/audio
43
+ errorMessage.classList.add('hidden');
44
+ audioContainer.classList.add('hidden');
45
+
46
+ // Setup Loading State
47
+ btnLoader.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Processing...';
48
+ setLoading(true);
49
+
50
+ try {
51
+ const response = await fetch('/api/synthesize', {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json'
55
+ },
56
+ body: JSON.stringify({ text, voice })
57
+ });
58
+
59
+ const data = await response.json();
60
+
61
+ if (!response.ok) {
62
+ throw new Error(data.error || 'Failed to generate speech.');
63
+ }
64
+
65
+ // Success
66
+ const uniqueUrl = data.audio_url + '?t=' + new Date().getTime(); // Prevent caching
67
+ audioPlayer.src = uniqueUrl;
68
+ downloadBtn.href = uniqueUrl;
69
+ downloadBtn.download = `Japanese_TTS_${Date.now()}.mp3`;
70
+
71
+ audioContainer.classList.remove('hidden');
72
+ audioPlayer.play().catch(e => console.log("Auto-play blocked", e));
73
+
74
+ } catch (error) {
75
+ showError(error.message);
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ });
80
+
81
+ function setLoading(isLoading) {
82
+ if (isLoading) {
83
+ generateBtn.disabled = true;
84
+ btnText.classList.add('hidden');
85
+ btnLoader.classList.remove('hidden');
86
+ } else {
87
+ generateBtn.disabled = false;
88
+ btnText.classList.remove('hidden');
89
+ btnLoader.classList.add('hidden');
90
+ }
91
+ }
92
+
93
+ function showError(message) {
94
+ errorText.textContent = message;
95
+ errorMessage.classList.remove('hidden');
96
+ }
97
+ });
static/style.css ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary: #6366f1;
3
+ --primary-hover: #4f46e5;
4
+ --bg-dark: #0f172a;
5
+ --glass-bg: rgba(30, 41, 59, 0.7);
6
+ --glass-border: rgba(255, 255, 255, 0.1);
7
+ --text-main: #f8fafc;
8
+ --text-muted: #94a3b8;
9
+ --danger: #ef4444;
10
+ --success: #10b981;
11
+ }
12
+
13
+ * {
14
+ box-sizing: border-box;
15
+ margin: 0;
16
+ padding: 0;
17
+ font-family: 'Be Vietnam Pro', sans-serif;
18
+ }
19
+
20
+ body {
21
+ background-color: var(--bg-dark);
22
+ color: var(--text-main);
23
+ min-height: 100vh;
24
+ display: flex;
25
+ justify-content: center;
26
+ align-items: center;
27
+ position: relative;
28
+ overflow-x: hidden;
29
+ }
30
+
31
+ /* Background Effects */
32
+ .background-effects {
33
+ position: fixed;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100vw;
37
+ height: 100vh;
38
+ z-index: -1;
39
+ pointer-events: none;
40
+ overflow: hidden;
41
+ }
42
+
43
+ .glow {
44
+ position: absolute;
45
+ border-radius: 50%;
46
+ filter: blur(80px);
47
+ opacity: 0.5;
48
+ }
49
+
50
+ .glow-1 {
51
+ top: -10%;
52
+ right: -5%;
53
+ width: 40vw;
54
+ height: 40vw;
55
+ background: radial-gradient(circle, rgba(99,102,241,0.6) 0%, rgba(99,102,241,0) 70%);
56
+ animation: float 15s ease-in-out infinite alternate;
57
+ }
58
+
59
+ .glow-2 {
60
+ bottom: -10%;
61
+ left: -10%;
62
+ width: 50vw;
63
+ height: 50vw;
64
+ background: radial-gradient(circle, rgba(236,72,153,0.5) 0%, rgba(236,72,153,0) 70%);
65
+ animation: float 20s ease-in-out infinite alternate-reverse;
66
+ }
67
+
68
+ .glow-3 {
69
+ top: 40%;
70
+ left: 30%;
71
+ width: 30vw;
72
+ height: 30vw;
73
+ background: radial-gradient(circle, rgba(16,185,129,0.3) 0%, rgba(16,185,129,0) 70%);
74
+ animation: pulse 10s ease-in-out infinite alternate;
75
+ }
76
+
77
+ @keyframes float {
78
+ 0% { transform: translate(0, 0) scale(1); }
79
+ 100% { transform: translate(-50px, 50px) scale(1.1); }
80
+ }
81
+
82
+ @keyframes pulse {
83
+ 0% { transform: scale(0.9); opacity: 0.3; }
84
+ 100% { transform: scale(1.2); opacity: 0.6; }
85
+ }
86
+
87
+ /* Container */
88
+ .container {
89
+ width: 100%;
90
+ max-width: 600px;
91
+ padding: 2rem;
92
+ z-index: 1;
93
+ }
94
+
95
+ .header {
96
+ text-align: center;
97
+ margin-bottom: 2rem;
98
+ }
99
+
100
+ .header h1 {
101
+ font-size: 2.5rem;
102
+ font-weight: 700;
103
+ margin-bottom: 0.5rem;
104
+ background: linear-gradient(135deg, #f8fafc, #94a3b8);
105
+ -webkit-background-clip: text;
106
+ -webkit-text-fill-color: transparent;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ gap: 0.75rem;
111
+ }
112
+
113
+ .header h1 i {
114
+ font-size: 2rem;
115
+ color: var(--primary);
116
+ -webkit-text-fill-color: initial;
117
+ }
118
+
119
+ .header p {
120
+ color: var(--text-muted);
121
+ font-size: 1.1rem;
122
+ }
123
+
124
+ /* Glass Panel */
125
+ .glass-panel {
126
+ background: var(--glass-bg);
127
+ backdrop-filter: blur(16px);
128
+ -webkit-backdrop-filter: blur(16px);
129
+ border: 1px solid var(--glass-border);
130
+ border-radius: 24px;
131
+ padding: 2.5rem;
132
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
133
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
134
+ }
135
+
136
+ .glass-panel:hover {
137
+ transform: translateY(-5px);
138
+ box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6);
139
+ }
140
+
141
+ /* Input Groups */
142
+ .input-group {
143
+ margin-bottom: 1.5rem;
144
+ }
145
+
146
+ .input-group label {
147
+ display: block;
148
+ font-weight: 500;
149
+ margin-bottom: 0.75rem;
150
+ font-size: 0.95rem;
151
+ color: var(--text-main);
152
+ }
153
+
154
+ /* Custom Select */
155
+ .custom-select-wrapper {
156
+ position: relative;
157
+ }
158
+
159
+ .custom-select {
160
+ appearance: none;
161
+ -webkit-appearance: none;
162
+ width: 100%;
163
+ background: rgba(15, 23, 42, 0.6);
164
+ border: 1px solid var(--glass-border);
165
+ border-radius: 12px;
166
+ padding: 1rem 1.25rem;
167
+ color: white;
168
+ font-size: 1rem;
169
+ font-family: 'Be Vietnam Pro', sans-serif;
170
+ cursor: pointer;
171
+ transition: all 0.3s ease;
172
+ }
173
+
174
+ .custom-select:focus {
175
+ outline: none;
176
+ border-color: var(--primary);
177
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
178
+ }
179
+
180
+ .custom-select option {
181
+ background-color: var(--bg-dark);
182
+ color: white;
183
+ padding: 10px;
184
+ }
185
+
186
+ .select-icon {
187
+ position: absolute;
188
+ right: 1.25rem;
189
+ top: 50%;
190
+ transform: translateY(-50%);
191
+ color: var(--text-muted);
192
+ pointer-events: none;
193
+ transition: transform 0.3s ease;
194
+ }
195
+
196
+ .custom-select:focus + .select-icon {
197
+ transform: translateY(-50%) rotate(180deg);
198
+ color: var(--primary);
199
+ }
200
+
201
+ /* Textarea */
202
+ textarea {
203
+ width: 100%;
204
+ background: rgba(15, 23, 42, 0.6);
205
+ border: 1px solid var(--glass-border);
206
+ border-radius: 12px;
207
+ padding: 1rem 1.25rem;
208
+ color: white;
209
+ font-size: 1rem;
210
+ font-family: inherit;
211
+ resize: vertical;
212
+ min-height: 120px;
213
+ transition: all 0.3s ease;
214
+ }
215
+
216
+ textarea:focus {
217
+ outline: none;
218
+ border-color: var(--primary);
219
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
220
+ }
221
+
222
+ textarea::placeholder {
223
+ color: rgba(148, 163, 184, 0.5);
224
+ }
225
+
226
+ .char-count {
227
+ text-align: right;
228
+ font-size: 0.8rem;
229
+ color: var(--text-muted);
230
+ margin-top: 0.5rem;
231
+ }
232
+
233
+ /* Buttons */
234
+ .primary-btn {
235
+ width: 100%;
236
+ background: linear-gradient(135deg, var(--primary), var(--primary-hover));
237
+ color: white;
238
+ border: none;
239
+ border-radius: 12px;
240
+ padding: 1.25rem;
241
+ font-size: 1.1rem;
242
+ font-weight: 600;
243
+ font-family: inherit;
244
+ cursor: pointer;
245
+ display: flex;
246
+ justify-content: center;
247
+ align-items: center;
248
+ gap: 0.75rem;
249
+ transition: all 0.3s ease;
250
+ position: relative;
251
+ overflow: hidden;
252
+ }
253
+
254
+ .primary-btn::after {
255
+ content: '';
256
+ position: absolute;
257
+ top: 0;
258
+ left: -100%;
259
+ width: 100%;
260
+ height: 100%;
261
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
262
+ transition: left 0.5s ease;
263
+ }
264
+
265
+ .primary-btn:hover {
266
+ transform: translateY(-2px);
267
+ box-shadow: 0 10px 20px -10px var(--primary);
268
+ }
269
+
270
+ .primary-btn:hover::after {
271
+ left: 100%;
272
+ }
273
+
274
+ .primary-btn:active {
275
+ transform: translateY(0);
276
+ }
277
+
278
+ .primary-btn:disabled {
279
+ opacity: 0.7;
280
+ cursor: not-allowed;
281
+ transform: none;
282
+ }
283
+
284
+ .secondary-btn {
285
+ display: inline-flex;
286
+ align-items: center;
287
+ gap: 0.5rem;
288
+ background: rgba(255, 255, 255, 0.1);
289
+ color: white;
290
+ text-decoration: none;
291
+ padding: 0.75rem 1.25rem;
292
+ border-radius: 8px;
293
+ font-size: 0.95rem;
294
+ font-weight: 500;
295
+ transition: all 0.3s ease;
296
+ border: 1px solid var(--glass-border);
297
+ }
298
+
299
+ .secondary-btn:hover {
300
+ background: rgba(255, 255, 255, 0.15);
301
+ border-color: rgba(255, 255, 255, 0.3);
302
+ }
303
+
304
+ /* Audio Panel */
305
+ .audio-panel {
306
+ margin-top: 2rem;
307
+ padding-top: 2rem;
308
+ border-top: 1px solid var(--glass-border);
309
+ animation: slideUp 0.4s ease forwards;
310
+ }
311
+
312
+ .audio-panel h3 {
313
+ font-size: 1.25rem;
314
+ margin-bottom: 1.25rem;
315
+ display: flex;
316
+ align-items: center;
317
+ gap: 0.5rem;
318
+ }
319
+
320
+ .audio-panel h3 i {
321
+ color: var(--success);
322
+ }
323
+
324
+ audio {
325
+ width: 100%;
326
+ height: 44px;
327
+ border-radius: 8px;
328
+ margin-bottom: 1.25rem;
329
+ }
330
+
331
+ audio::-webkit-media-controls-panel {
332
+ background-color: var(--text-main);
333
+ }
334
+
335
+ /* Alerts */
336
+ .alert {
337
+ margin-top: 1.5rem;
338
+ padding: 1rem 1.25rem;
339
+ border-radius: 12px;
340
+ font-size: 0.95rem;
341
+ display: flex;
342
+ align-items: flex-start;
343
+ gap: 0.75rem;
344
+ animation: slideUp 0.3s ease forwards;
345
+ }
346
+
347
+ .alert.error {
348
+ background-color: rgba(239, 68, 68, 0.1);
349
+ border: 1px solid rgba(239, 68, 68, 0.3);
350
+ color: #fca5a5;
351
+ }
352
+
353
+ .alert i {
354
+ margin-top: 0.2rem;
355
+ }
356
+
357
+ /* Utility */
358
+ .hidden {
359
+ display: none !important;
360
+ }
361
+
362
+ footer {
363
+ text-align: center;
364
+ margin-top: 2.5rem;
365
+ color: var(--text-muted);
366
+ font-size: 0.9rem;
367
+ }
368
+
369
+ footer i {
370
+ color: var(--danger);
371
+ margin: 0 0.25rem;
372
+ }
373
+
374
+ @keyframes slideUp {
375
+ from { opacity: 0; transform: translateY(10px); }
376
+ to { opacity: 1; transform: translateY(0); }
377
+ }
378
+
379
+ @media (max-width: 640px) {
380
+ .container { padding: 1rem; }
381
+ .glass-panel { padding: 1.5rem; }
382
+ .header h1 { font-size: 2rem; }
383
+ }
templates/en.html ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>English Text to Speech App</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap"
9
+ rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
12
+ </head>
13
+
14
+ <body>
15
+ <div class="background-effects">
16
+ <div class="glow glow-1"></div>
17
+ <div class="glow glow-2"></div>
18
+ <div class="glow glow-3"></div>
19
+ </div>
20
+
21
+ <div class="container">
22
+ <!-- Add a language switcher at the top -->
23
+ <div style="text-align: right; margin-bottom: 1rem;">
24
+ <a href="/" class="secondary-btn" style="text-decoration: none; padding: 0.5rem 1rem;">
25
+ 🇻🇳 Switch to Vietnamese
26
+ </a>
27
+ <a href="/jp" class="secondary-btn" style="text-decoration: none; padding: 0.5rem 1rem;">
28
+ 🇯🇵 Switch to Japanese
29
+ </a>
30
+ </div>
31
+
32
+ <div class="header">
33
+ <h1><i class="fa-solid fa-volume-high"></i> English TTS</h1>
34
+ <p>Convert your text into natural English speech</p>
35
+ </div>
36
+
37
+ <div class="glass-panel">
38
+ <div class="input-group">
39
+ <label for="voice-select">English TTS Voice</label>
40
+ <div class="custom-select-wrapper">
41
+ <select id="voice-select" class="custom-select">
42
+ <option value="jenny_us_female">👩 Jenny (US, Female)</option>
43
+ <option value="guy_us_male">👨 Guy (US, Male)</option>
44
+ </select>
45
+ <i class="fa-solid fa-chevron-down select-icon"></i>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="input-group">
50
+ <label for="text-input">Enter Text</label>
51
+ <textarea id="text-input"
52
+ placeholder="Type your English text here... (Example: Hello, how are you today?)"
53
+ rows="6"></textarea>
54
+ <div class="char-count"><span id="char-current">0</span> / 1000</div>
55
+ </div>
56
+
57
+ <button id="generate-btn" class="primary-btn">
58
+ <span class="btn-text"><i class="fa-solid fa-wand-magic-sparkles"></i> Generate Speech</span>
59
+ <span class="btn-loader hidden"><i class="fa-solid fa-spinner fa-spin"></i> Processing...</span>
60
+ </button>
61
+
62
+ <div id="error-message" class="alert error hidden">
63
+ <i class="fa-solid fa-circle-exclamation"></i>
64
+ <span id="error-text">An error occurred.</span>
65
+ </div>
66
+
67
+ <div id="audio-container" class="audio-panel hidden">
68
+ <h3><i class="fa-solid fa-headphones"></i> Your Audio is Ready</h3>
69
+ <audio id="audio-player" controls>
70
+ Your browser does not support the audio element.
71
+ </audio>
72
+ <a id="download-btn" class="secondary-btn" href="#" download>
73
+ <i class="fa-solid fa-download"></i> Download MP3
74
+ </a>
75
+ </div>
76
+ </div>
77
+
78
+ <footer>
79
+ <p>Built with <i class="fa-solid fa-heart"></i> using Flask and Microsoft Edge TTS</p>
80
+ </footer>
81
+ </div>
82
+
83
+ <script src="{{ url_for('static', filename='script_en.js') }}"></script>
84
+ </body>
85
+
86
+ </html>
templates/index.html ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Vietnamese Text to Speech App</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap"
9
+ rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
12
+ </head>
13
+
14
+ <body>
15
+ <div class="background-effects">
16
+ <div class="glow glow-1"></div>
17
+ <div class="glow glow-2"></div>
18
+ <div class="glow glow-3"></div>
19
+ </div>
20
+
21
+ <div class="container">
22
+ <!-- Add a language switcher at the top -->
23
+ <div style="text-align: right; margin-bottom: 1rem;">
24
+ <a href="/en" class="secondary-btn" style="text-decoration: none; padding: 0.5rem 1rem;">
25
+ 🇬🇧 Switch to English
26
+ </a>
27
+ <a href="/jp" class="secondary-btn" style="text-decoration: none; padding: 0.5rem 1rem;">
28
+ 🇯🇵 Switch to Japanese
29
+ </a>
30
+ </div>
31
+
32
+ <div class="header">
33
+ <h1><i class="fa-solid fa-volume-high"></i> Vietnamese TTS</h1>
34
+ <p>Convert your text into natural Vietnamese speech</p>
35
+ </div>
36
+
37
+ <div class="glass-panel">
38
+ <div class="input-group">
39
+ <label for="voice-select">Base TTS Voice</label>
40
+ <div class="custom-select-wrapper">
41
+ <select id="voice-select" class="custom-select">
42
+ <option value="hoai_my_southern_female">👩 Hoài My (Southern, Female)</option>
43
+ <option value="nam_minh_northern_male">👨 Nam Minh (Northern, Male)</option>
44
+ </select>
45
+ <i class="fa-solid fa-chevron-down select-icon"></i>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="glass-panel">
50
+
51
+ <div class="input-group">
52
+ <label for="text-input">Enter Text</label>
53
+ <textarea id="text-input"
54
+ placeholder="Nhập văn bản tiếng Việt vào đây... (Ví dụ: Xin chào, bạn có khỏe không?)"
55
+ rows="6"></textarea>
56
+ <div class="char-count"><span id="char-current">0</span> / 1000</div>
57
+ </div>
58
+
59
+ <button id="generate-btn" class="primary-btn">
60
+ <span class="btn-text"><i class="fa-solid fa-wand-magic-sparkles"></i> Generate Speech</span>
61
+ <span class="btn-loader hidden"><i class="fa-solid fa-spinner fa-spin"></i> Processing...</span>
62
+ </button>
63
+
64
+ <div id="error-message" class="alert error hidden">
65
+ <i class="fa-solid fa-circle-exclamation"></i>
66
+ <span id="error-text">An error occurred.</span>
67
+ </div>
68
+
69
+ <div id="audio-container" class="audio-panel hidden">
70
+ <h3><i class="fa-solid fa-headphones"></i> Your Audio is Ready</h3>
71
+ <audio id="audio-player" controls>
72
+ Your browser does not support the audio element.
73
+ </audio>
74
+ <a id="download-btn" class="secondary-btn" href="#" download>
75
+ <i class="fa-solid fa-download"></i> Download MP3
76
+ </a>
77
+ </div>
78
+ </div>
79
+
80
+ <footer>
81
+ <p>Built with <i class="fa-solid fa-heart"></i> using Flask and Microsoft Edge TTS</p>
82
+ </footer>
83
+ </div>
84
+
85
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
86
+ </body>
87
+
88
+ </html>
templates/jp.html ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Japanese Text to Speech App</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap"
9
+ rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
12
+ </head>
13
+
14
+ <body>
15
+ <div class="background-effects">
16
+ <div class="glow glow-1"></div>
17
+ <div class="glow glow-2"></div>
18
+ <div class="glow glow-3"></div>
19
+ </div>
20
+
21
+ <div class="container">
22
+ <!-- Add a language switcher at the top -->
23
+ <div style="text-align: right; margin-bottom: 1rem;">
24
+ <a href="/" class="secondary-btn"
25
+ style="text-decoration: none; padding: 0.5rem 1rem; margin-right: 0.5rem;">
26
+ 🇻🇳 Switch to Vietnamese
27
+ </a>
28
+ <a href="/en" class="secondary-btn" style="text-decoration: none; padding: 0.5rem 1rem;">
29
+ 🇺🇸 Switch to English
30
+ </a>
31
+ </div>
32
+
33
+ <div class="header">
34
+ <h1><i class="fa-solid fa-volume-high"></i> Japanese TTS</h1>
35
+ <p>Convert your text into natural Japanese speech</p>
36
+ </div>
37
+
38
+ <div class="glass-panel">
39
+ <div class="input-group">
40
+ <label for="voice-select">Japanese TTS Voice</label>
41
+ <div class="custom-select-wrapper">
42
+ <select id="voice-select" class="custom-select">
43
+ <option value="nanami_jp_female">👩 Nanami (Japan, Female)</option>
44
+ <option value="keita_jp_male">👨 Keita (Japan, Male)</option>
45
+ </select>
46
+ <i class="fa-solid fa-chevron-down select-icon"></i>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="input-group">
51
+ <label for="text-input">Enter Text</label>
52
+ <textarea id="text-input" placeholder="Type your Japanese text here... (Example: こんにちは、お元気ですか?)"
53
+ rows="6"></textarea>
54
+ <div class="char-count"><span id="char-current">0</span> / 1000</div>
55
+ </div>
56
+
57
+ <button id="generate-btn" class="primary-btn">
58
+ <span class="btn-text"><i class="fa-solid fa-wand-magic-sparkles"></i> Generate Speech</span>
59
+ <span class="btn-loader hidden"><i class="fa-solid fa-spinner fa-spin"></i> Processing...</span>
60
+ </button>
61
+
62
+ <div id="error-message" class="alert error hidden">
63
+ <i class="fa-solid fa-circle-exclamation"></i>
64
+ <span id="error-text">An error occurred.</span>
65
+ </div>
66
+
67
+ <div id="audio-container" class="audio-panel hidden">
68
+ <h3><i class="fa-solid fa-headphones"></i> Your Audio is Ready</h3>
69
+ <audio id="audio-player" controls>
70
+ Your browser does not support the audio element.
71
+ </audio>
72
+ <a id="download-btn" class="secondary-btn" href="#" download>
73
+ <i class="fa-solid fa-download"></i> Download MP3
74
+ </a>
75
+ </div>
76
+ </div>
77
+
78
+ <footer>
79
+ <p>Built with <i class="fa-solid fa-heart"></i> using Flask and Microsoft Edge TTS</p>
80
+ </footer>
81
+ </div>
82
+
83
+ <script src="{{ url_for('static', filename='script_jp.js') }}"></script>
84
+ </body>
85
+
86
+ </html>
test_voices.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import edge_tts
3
+
4
+ async def main():
5
+ voices = await edge_tts.list_voices()
6
+ for v in voices:
7
+ if 'vi-VN' in v['ShortName']:
8
+ print(f"Name: {v['ShortName']}, Gender: {v['Gender']}")
9
+
10
+ asyncio.run(main())