Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitattributes +7 -35
- .gitignore +3 -0
- Dockerfile +28 -0
- README.md +98 -11
- app.py +81 -0
- draft.py +92 -0
- img/Capture.PNG +0 -0
- mahiru/MahiruShiina.pth +3 -0
- mahiru/mahiru.index +3 -0
- piper_voices/en_GB-alan-medium.onnx +3 -0
- piper_voices/en_GB-alan-medium.onnx.json +493 -0
- piper_voices/en_US-kathleen-low.onnx +3 -0
- piper_voices/en_US-kathleen-low.onnx.json +420 -0
- piper_voices/en_US-lessac-medium.onnx +3 -0
- piper_voices/en_US-lessac-medium.onnx.json +493 -0
- requirements.txt +5 -0
- static/script.js +97 -0
- static/script_en.js +97 -0
- static/script_jp.js +97 -0
- static/style.css +383 -0
- templates/en.html +86 -0
- templates/index.html +88 -0
- templates/jp.html +86 -0
- test_voices.py +10 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,7 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.
|
| 7 |
-
|
| 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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())
|