Compare commits
No commits in common. "7bd4cfceeff047571d8bee0a254ea740b91265be" and "f49e6b6a37d3dd4b1c11de602bf2b1dc44b1b66a" have entirely different histories.
7bd4cfceef
...
f49e6b6a37
13 changed files with 29 additions and 516 deletions
|
|
@ -1,47 +0,0 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
# Set non-interactive frontend
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install Python and other dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3.10 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
libsndfile1 \
|
||||
ffmpeg \
|
||||
portaudio19-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user and set up directories
|
||||
RUN useradd -m -u 1001 appuser && \
|
||||
mkdir -p /app/outputs /app && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency files
|
||||
COPY --chown=appuser:appuser requirements.txt ./requirements.txt
|
||||
|
||||
# Create and activate virtual environment
|
||||
RUN python3 -m venv /app/venv
|
||||
ENV PATH="/app/venv/bin:$PATH"
|
||||
|
||||
# Install CPU-only PyTorch and other dependencies
|
||||
RUN pip3 install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu && \
|
||||
pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy project files
|
||||
COPY --chown=appuser:appuser . .
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONPATH=/app \
|
||||
USE_GPU=false
|
||||
|
||||
# Expose the port
|
||||
EXPOSE 5005
|
||||
|
||||
# Run FastAPI server with uvicorn
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5005", "--workers", "1"]
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
FROM rocm/dev-ubuntu-22.04:latest
|
||||
|
||||
# Set non-interactive frontend
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install Python and other dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3.10 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
libsndfile1 \
|
||||
ffmpeg \
|
||||
portaudio19-dev \
|
||||
libjpeg-dev \
|
||||
python3-dev \
|
||||
&& apt-get install python3-wheel python3-setuptools \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user and set up directories
|
||||
RUN useradd -m -u 1001 appuser && \
|
||||
mkdir -p /app/outputs /app && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency files
|
||||
COPY --chown=appuser:appuser requirements.txt ./requirements.txt
|
||||
|
||||
# Create and activate virtual environment
|
||||
RUN python3 -m venv /app/venv
|
||||
ENV PATH="/app/venv/bin:$PATH"
|
||||
|
||||
# Install PyTorch with ROCm support and other dependencies
|
||||
RUN pip3 install --no-cache-dir --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm6.4/ && \
|
||||
pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy project files
|
||||
COPY --chown=appuser:appuser . .
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONPATH=/app \
|
||||
USE_GPU=true
|
||||
|
||||
# Expose the port
|
||||
EXPOSE 5005
|
||||
|
||||
# Run FastAPI server with uvicorn
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5005", "--workers", "1"]
|
||||
96
README.md
96
README.md
|
|
@ -4,34 +4,10 @@
|
|||
|
||||
[](https://github.com/Lex-au/Orpheus-FastAPI/blob/main/LICENSE.txt)
|
||||
|
||||
High-performance Text-to-Speech server with OpenAI-compatible API, multilingual support with 24 voices, emotion tags, and modern web UI. Optimized for RTX GPUs.
|
||||
High-performance Text-to-Speech server with OpenAI-compatible API, 8 voices, emotion tags, and modern web UI. Optimized for RTX GPUs.
|
||||
|
||||
## Changelog
|
||||
|
||||
**v1.3.1** (2025-07-05)
|
||||
- 🐳 ROCm Docker implementation contributed by [@wizardeur](https://github.com/wizardeur) – many thanks for your contribution ❤️
|
||||
|
||||
**v1.3.0** (2025-04-18)
|
||||
- 🌐 Added comprehensive multilingual support with 16 new voice actors across 7 languages
|
||||
- 🗣️ New voice actors include:
|
||||
- French: pierre, amelie, marie
|
||||
- German: jana, thomas, max
|
||||
- Korean: 유나, 준서
|
||||
- Hindi: ऋतिका
|
||||
- Mandarin: 长乐, 白芷
|
||||
- Spanish: javi, sergio, maria
|
||||
- Italian: pietro, giulia, carlo
|
||||
- 🔄 Enhanced UI with dynamic language selection and voice filtering
|
||||
- 🚀 Released language-specific optimized models:
|
||||
- [Italian & Spanish Model](https://huggingface.co/lex-au/Orpheus-3b-Italian_Spanish-FT-Q8_0.gguf)
|
||||
- [Korean Model](https://huggingface.co/lex-au/Orpheus-3b-Korean-FT-Q8_0.gguf)
|
||||
- [French Model](https://huggingface.co/lex-au/Orpheus-3b-French-FT-Q8_0.gguf)
|
||||
- [Hindi Model](https://huggingface.co/lex-au/Orpheus-3b-Hindi-FT-Q8_0.gguf)
|
||||
- [Mandarin Model](https://huggingface.co/lex-au/Orpheus-3b-Chinese-FT-Q8_0.gguf)
|
||||
- [German Model](https://huggingface.co/lex-au/Orpheus-3b-German-FT-Q8_0.gguf)
|
||||
- 🐳 Docker Compose users: To use a language-specific model, edit the `.env` file before installation and change `ORPHEUS_MODEL_NAME` to match the desired model repo ID (e.g., `Orpheus-3b-French-FT-Q8_0.gguf`)
|
||||
- An additional Docker Compose installation path is now available, specifically for CPU-bound scenarios. This contribution comes from [@alexjyong](https://github.com/alexjyong) - thank you!
|
||||
|
||||
**v1.2.0** (2025-04-12)
|
||||
- ❤️ Added optional Docker Compose support with GPU-enabled `llama.cpp` server and Orpheus-FastAPI integration
|
||||
- 🐳 Docker implementation contributed by [@richardr1126](https://github.com/richardr1126) – huge thanks for the clean setup and orchestration work!
|
||||
|
|
@ -73,7 +49,7 @@ Listen to sample outputs with different voices and emotions:
|
|||
- **OpenAI API Compatible**: Drop-in replacement for OpenAI's `/v1/audio/speech` endpoint
|
||||
- **Modern Web Interface**: Clean, responsive UI with waveform visualization
|
||||
- **High Performance**: Optimized for RTX GPUs with parallel processing
|
||||
- **Multilingual Support**: 24 different voices across 8 languages (English, French, German, Korean, Hindi, Mandarin, Spanish, Italian)
|
||||
- **Multiple Voices**: 8 different voice options with different characteristics
|
||||
- **Emotion Tags**: Support for laughter, sighs, and other emotional expressions
|
||||
- **Unlimited Audio Length**: Generate audio of any length through intelligent batching
|
||||
- **Smooth Transitions**: Crossfaded audio segments for seamless listening experience
|
||||
|
|
@ -104,45 +80,22 @@ Orpheus-FastAPI/
|
|||
### Prerequisites
|
||||
|
||||
- Python 3.8-3.11 (Python 3.12 is not supported due to removal of pkgutil.ImpImporter)
|
||||
- CUDA-compatible or ROCm-compatible GPU (recommended: RTX series for best performance)
|
||||
- CUDA-compatible GPU (recommended: RTX series for best performance)
|
||||
- Using docker compose or separate LLM inference server running the Orpheus model (e.g., LM Studio or llama.cpp server)
|
||||
- For Docker GPU Support, ensure you're using an Nvidia GPU on either Linux or Windows with CUDA 12.4 or greater and NVIDIA Container Toolkit installed
|
||||
|
||||
### 🐳 Docker compose
|
||||
|
||||
The docker compose file orchestrates the Orpheus-FastAPI for audio and a llama.cpp inference server for the base model token generation. The GGUF model is downloaded with the model-init service.
|
||||
There are three versions, two for machines that have access to GPU support `docker-compose-gpu.yaml`, `docker-compose-gpu-rocm.yml` and one for CPU support only: `docker-compose-cpu.yaml`
|
||||
|
||||
```bash
|
||||
cp .env.example .env # Create your .env file from the example
|
||||
cp .env.example .env # Nothing needs to be changed, but the file is required
|
||||
copy .env.example .env # For Windows CMD
|
||||
```
|
||||
|
||||
For multilingual models, edit the `.env` file and change the model name:
|
||||
```
|
||||
# Change this line in .env to use a language-specific model
|
||||
ORPHEUS_MODEL_NAME=Orpheus-3b-French-FT-Q8_0.gguf # Example for French
|
||||
```
|
||||
|
||||
Then start the services:
|
||||
|
||||
For CUDA GPU support run
|
||||
```bash
|
||||
docker compose -f docker-compose-gpu.yml up
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
For ROCm GPU support run
|
||||
```bash
|
||||
docker compose -f docker-compose-gpu-rocm.yml up
|
||||
```
|
||||
|
||||
For CPU support run:
|
||||
```bash
|
||||
docker compose -f docker-compose-cpu.yml up
|
||||
```
|
||||
|
||||
The system will automatically download the specified model from Hugging Face before starting the service.
|
||||
|
||||
### FastAPI Service Native Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
|
@ -166,11 +119,6 @@ conda activate orpheus-tts
|
|||
```bash
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
|
||||
```
|
||||
or
|
||||
Install PyTorch with ROCm support:
|
||||
```bash
|
||||
pip3 install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm6.4/
|
||||
```
|
||||
|
||||
4. Install other dependencies:
|
||||
```bash
|
||||
|
|
@ -246,7 +194,6 @@ curl -X POST http://localhost:5005/speak \
|
|||
|
||||
### Available Voices
|
||||
|
||||
#### English
|
||||
- `tara`: Female, conversational, clear
|
||||
- `leah`: Female, warm, gentle
|
||||
- `jess`: Female, energetic, youthful
|
||||
|
|
@ -256,37 +203,6 @@ curl -X POST http://localhost:5005/speak \
|
|||
- `zac`: Male, enthusiastic, dynamic
|
||||
- `zoe`: Female, calm, soothing
|
||||
|
||||
#### French
|
||||
- `pierre`: Male, sophisticated
|
||||
- `amelie`: Female, elegant
|
||||
- `marie`: Female, spirited
|
||||
|
||||
#### German
|
||||
- `jana`: Female, clear
|
||||
- `thomas`: Male, authoritative
|
||||
- `max`: Male, energetic
|
||||
|
||||
#### Korean
|
||||
- `유나`: Female, melodic
|
||||
- `준서`: Male, confident
|
||||
|
||||
#### Hindi
|
||||
- `ऋतिका`: Female, expressive
|
||||
|
||||
#### Mandarin
|
||||
- `长乐`: Female, gentle
|
||||
- `白芷`: Female, clear
|
||||
|
||||
#### Spanish
|
||||
- `javi`: Male, warm
|
||||
- `sergio`: Male, professional
|
||||
- `maria`: Female, friendly
|
||||
|
||||
#### Italian
|
||||
- `pietro`: Male, passionate
|
||||
- `giulia`: Female, expressive
|
||||
- `carlo`: Male, refined
|
||||
|
||||
### Emotion Tags
|
||||
|
||||
You can insert emotion tags into your text to add expressiveness:
|
||||
|
|
@ -370,7 +286,7 @@ You can easily integrate this TTS solution with [OpenWebUI](https://github.com/o
|
|||
3. Change TTS from Web API to OpenAI
|
||||
4. Set APIBASE URL to your server address (e.g., `http://localhost:5005/v1`)
|
||||
5. API Key can be set to "not-needed"
|
||||
6. Set TTS Voice to any of the available voices (e.g., `tara`, `pierre`, `jana`, `유나`, etc.)
|
||||
6. Set TTS Voice to one of the available voices: `tara`, `leah`, `jess`, `leo`, `dan`, `mia`, `zac`, or `zoe`
|
||||
7. Set TTS Model to `tts-1`
|
||||
|
||||
### External Inference Server
|
||||
|
|
|
|||
25
app.py
25
app.py
|
|
@ -51,7 +51,7 @@ from fastapi.templating import Jinja2Templates
|
|||
from pydantic import BaseModel
|
||||
import json
|
||||
|
||||
from tts_engine import generate_speech_from_api, AVAILABLE_VOICES, DEFAULT_VOICE, VOICE_TO_LANGUAGE, AVAILABLE_LANGUAGES
|
||||
from tts_engine import generate_speech_from_api, AVAILABLE_VOICES, DEFAULT_VOICE
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
|
|
@ -189,12 +189,7 @@ async def root(request: Request):
|
|||
"""Redirect to web UI"""
|
||||
return templates.TemplateResponse(
|
||||
"tts.html",
|
||||
{
|
||||
"request": request,
|
||||
"voices": AVAILABLE_VOICES,
|
||||
"VOICE_TO_LANGUAGE": VOICE_TO_LANGUAGE,
|
||||
"AVAILABLE_LANGUAGES": AVAILABLE_LANGUAGES
|
||||
}
|
||||
{"request": request, "voices": AVAILABLE_VOICES}
|
||||
)
|
||||
|
||||
@app.get("/web/", response_class=HTMLResponse)
|
||||
|
|
@ -204,13 +199,7 @@ async def web_ui(request: Request):
|
|||
config = get_current_config()
|
||||
return templates.TemplateResponse(
|
||||
"tts.html",
|
||||
{
|
||||
"request": request,
|
||||
"voices": AVAILABLE_VOICES,
|
||||
"config": config,
|
||||
"VOICE_TO_LANGUAGE": VOICE_TO_LANGUAGE,
|
||||
"AVAILABLE_LANGUAGES": AVAILABLE_LANGUAGES
|
||||
}
|
||||
{"request": request, "voices": AVAILABLE_VOICES, "config": config}
|
||||
)
|
||||
|
||||
@app.get("/get_config")
|
||||
|
|
@ -312,9 +301,7 @@ async def generate_from_web(
|
|||
{
|
||||
"request": request,
|
||||
"error": "Please enter some text.",
|
||||
"voices": AVAILABLE_VOICES,
|
||||
"VOICE_TO_LANGUAGE": VOICE_TO_LANGUAGE,
|
||||
"AVAILABLE_LANGUAGES": AVAILABLE_LANGUAGES
|
||||
"voices": AVAILABLE_VOICES
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -347,9 +334,7 @@ async def generate_from_web(
|
|||
"voice": voice,
|
||||
"output_file": output_path,
|
||||
"generation_time": generation_time,
|
||||
"voices": AVAILABLE_VOICES,
|
||||
"VOICE_TO_LANGUAGE": VOICE_TO_LANGUAGE,
|
||||
"AVAILABLE_LANGUAGES": AVAILABLE_LANGUAGES
|
||||
"voices": AVAILABLE_VOICES
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
services:
|
||||
orpheus-fastapi:
|
||||
container_name: orpheus-fastapi
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.cpu
|
||||
ports:
|
||||
- "5005:5005"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- ORPHEUS_API_URL=http://llama-cpp-server:5006/v1/completions
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
llama-cpp-server:
|
||||
condition: service_started
|
||||
|
||||
llama-cpp-server:
|
||||
image: ghcr.io/ggml-org/llama.cpp:server
|
||||
ports:
|
||||
- "5006:5006"
|
||||
volumes:
|
||||
- ./models:/models
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
model-init:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
-m /models/${ORPHEUS_MODEL_NAME}
|
||||
--host 0.0.0.0
|
||||
--port 5006
|
||||
--ctx-size ${ORPHEUS_MAX_TOKENS}
|
||||
--n-predict ${ORPHEUS_MAX_TOKENS}
|
||||
--threads ${LLAMA_CPU_THREADS:-6}
|
||||
--threads-batch ${LLAMA_CPU_THREADS:-6}
|
||||
--rope-scaling linear
|
||||
--no-mmap
|
||||
--no-slots
|
||||
--no-webui
|
||||
model-init:
|
||||
image: curlimages/curl:latest
|
||||
user: ${UID}:${GID}
|
||||
volumes:
|
||||
- ./models:/app/models
|
||||
working_dir: /app
|
||||
command: >
|
||||
sh -c '
|
||||
if [ ! -f /app/models/${ORPHEUS_MODEL_NAME} ]; then
|
||||
echo "Downloading model file..."
|
||||
wget -P /app/models https://huggingface.co/lex-au/${ORPHEUS_MODEL_NAME}/resolve/main/${ORPHEUS_MODEL_NAME}
|
||||
else
|
||||
echo "Model file already exists"
|
||||
fi'
|
||||
restart: "no"
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
services:
|
||||
orpheus-fastapi:
|
||||
container_name: orpheus-fastapi
|
||||
image: orpheus-tts-fastapi-server-orpheus-fastapi:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.gpu-rocm
|
||||
ports:
|
||||
- "5005:5005"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- ORPHEUS_API_URL=http://llama-cpp-server:5006/v1/completions
|
||||
ipc: host
|
||||
privileged: true
|
||||
security_opt:
|
||||
- seccomp=unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
- CAP_SYS_ADMIN
|
||||
devices:
|
||||
- /dev/kfd
|
||||
- /dev/dri
|
||||
- /dev/mem
|
||||
group_add:
|
||||
- video
|
||||
- render
|
||||
# - 993 # Add numeric render/video group id(s) if your system has different group id(s) than the image - 44(video),109(render)
|
||||
shm_size: 8g
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
llama-cpp-server:
|
||||
condition: service_started
|
||||
|
||||
llama-cpp-server:
|
||||
image: ghcr.io/ggml-org/llama.cpp:server-vulkan
|
||||
ports:
|
||||
- "5006:5006"
|
||||
volumes:
|
||||
- ./models:/models
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
model-init:
|
||||
condition: service_completed_successfully
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
- CAP_SYS_ADMIN
|
||||
security_opt:
|
||||
- seccomp=unconfined
|
||||
privileged: true
|
||||
devices:
|
||||
- /dev/kfd
|
||||
- /dev/dri
|
||||
- /dev/mem
|
||||
group_add:
|
||||
- video
|
||||
- 993
|
||||
ipc: host
|
||||
shm_size: 8g
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
-m /models/${ORPHEUS_MODEL_NAME}
|
||||
--port 5006
|
||||
--host 0.0.0.0
|
||||
--n-gpu-layers 29
|
||||
--ctx-size ${ORPHEUS_MAX_TOKENS}
|
||||
--n-predict ${ORPHEUS_MAX_TOKENS}
|
||||
--rope-scaling linear
|
||||
|
||||
model-init:
|
||||
image: curlimages/curl:latest
|
||||
user: ${UID}:${GID}
|
||||
volumes:
|
||||
- ./models:/app/models
|
||||
working_dir: /app
|
||||
command: >
|
||||
sh -c '
|
||||
if [ ! -f /app/models/${ORPHEUS_MODEL_NAME} ]; then
|
||||
echo "Downloading model file..."
|
||||
wget -P /app/models https://huggingface.co/lex-au/${ORPHEUS_MODEL_NAME}/resolve/main/${ORPHEUS_MODEL_NAME}
|
||||
else
|
||||
echo "Model file already exists"
|
||||
fi'
|
||||
restart: "no"
|
||||
|
||||
Binary file not shown.
BIN
docs/WebUI.png
BIN
docs/WebUI.png
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 168 KiB |
|
|
@ -64,10 +64,6 @@
|
|||
<td>Default Test (MP3)</td>
|
||||
<td><a href="DefaultTest.mp3" target="_blank">DefaultTest.mp3</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default Test (MP3)</td>
|
||||
<td><a href="TaraLongGeneration.mp3" target="_blank">TaraLongGeneration.mp3</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebUI Screenshot (PNG)</td>
|
||||
<td>
|
||||
|
|
|
|||
|
|
@ -155,72 +155,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Language selection -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-white mb-2">Language</label>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{% for language in AVAILABLE_LANGUAGES %}
|
||||
<div class="language-option {% if language == 'english' %}active{% endif %}" data-language="{{ language }}">
|
||||
<input type="radio" name="language" value="{{ language }}" class="hidden" {% if language == 'english' %}checked{% endif %}>
|
||||
<span class="px-3 py-1 rounded-full bg-dark-700 hover:bg-dark-600 text-white text-sm cursor-pointer">{{ language|capitalize }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Voice selection -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-white mb-2">Voice</label>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{% for voice_option in voices %}
|
||||
<div class="voice-card {% if voice_option == DEFAULT_VOICE %}active{% endif %}"
|
||||
data-voice="{{ voice_option }}"
|
||||
data-language="{{ VOICE_TO_LANGUAGE[voice_option] }}"
|
||||
style="display: {% if VOICE_TO_LANGUAGE[voice_option] != 'english' %}none{% endif %}">
|
||||
<div class="voice-card {% if voice_option == DEFAULT_VOICE %}active{% endif %}" data-voice="{{ voice_option }}">
|
||||
<input type="radio" name="voice" value="{{ voice_option }}" class="hidden" {% if voice_option == DEFAULT_VOICE %}checked{% endif %}>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="font-medium text-white">{{ voice_option|capitalize }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-dark-300">
|
||||
{% if voice_option == "tara" %}Female, English, conversational, clear
|
||||
{% elif voice_option == "leah" %}Female, English, warm, gentle
|
||||
{% elif voice_option == "jess" %}Female, English, energetic, youthful
|
||||
{% elif voice_option == "leo" %}Male, English, authoritative, deep
|
||||
{% elif voice_option == "dan" %}Male, English, friendly, casual
|
||||
{% elif voice_option == "mia" %}Female, English, professional, articulate
|
||||
{% elif voice_option == "zac" %}Male, English, enthusiastic, dynamic
|
||||
{% elif voice_option == "zoe" %}Female, English, calm, soothing
|
||||
|
||||
<!-- French voices -->
|
||||
{% elif voice_option == "pierre" %}Male, French, sophisticated
|
||||
{% elif voice_option == "amelie" %}Female, French, elegant
|
||||
{% elif voice_option == "marie" %}Female, French, spirited
|
||||
|
||||
<!-- German voices -->
|
||||
{% elif voice_option == "jana" %}Female, German, clear
|
||||
{% elif voice_option == "thomas" %}Male, German, authoritative
|
||||
{% elif voice_option == "max" %}Male, German, energetic
|
||||
|
||||
<!-- Korean voices -->
|
||||
{% elif voice_option == "유나" %}Female, Korean, melodic
|
||||
{% elif voice_option == "준서" %}Male, Korean, confident
|
||||
|
||||
<!-- Hindi voice -->
|
||||
{% elif voice_option == "ऋतिका" %}Female, Hindi, expressive
|
||||
|
||||
<!-- Mandarin voices -->
|
||||
{% elif voice_option == "长乐" %}Female, Mandarin, gentle
|
||||
{% elif voice_option == "白芷" %}Female, Mandarin, clear
|
||||
|
||||
<!-- Spanish voices -->
|
||||
{% elif voice_option == "javi" %}Male, Spanish, warm
|
||||
{% elif voice_option == "sergio" %}Male, Spanish, professional
|
||||
{% elif voice_option == "maria" %}Female, Spanish, friendly
|
||||
|
||||
<!-- Italian voices -->
|
||||
{% elif voice_option == "pietro" %}Male, Italian, passionate
|
||||
{% elif voice_option == "giulia" %}Female, Italian, expressive
|
||||
{% elif voice_option == "carlo" %}Male, Italian, refined
|
||||
<div class="text-xs text-dark-300">
|
||||
{% if voice_option == "tara" %}Female, conversational, clear
|
||||
{% elif voice_option == "leah" %}Female, warm, gentle
|
||||
{% elif voice_option == "jess" %}Female, energetic, youthful
|
||||
{% elif voice_option == "leo" %}Male, authoritative, deep
|
||||
{% elif voice_option == "dan" %}Male, friendly, casual
|
||||
{% elif voice_option == "mia" %}Female, professional, articulate
|
||||
{% elif voice_option == "zac" %}Male, enthusiastic, dynamic
|
||||
{% elif voice_option == "zoe" %}Female, calm, soothing
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -451,83 +404,20 @@
|
|||
// Initialize char count
|
||||
charCount.textContent = textArea.value.length;
|
||||
|
||||
// Language selection
|
||||
const languageOptions = document.querySelectorAll('.language-option');
|
||||
// Voice selection
|
||||
const voiceCards = document.querySelectorAll('.voice-card');
|
||||
|
||||
languageOptions.forEach(option => {
|
||||
option.addEventListener('click', function() {
|
||||
// Unselect all language options
|
||||
languageOptions.forEach(o => o.classList.remove('active'));
|
||||
voiceCards.forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
// Unselect all cards
|
||||
voiceCards.forEach(c => c.classList.remove('active'));
|
||||
|
||||
// Select this language option
|
||||
// Select this card
|
||||
this.classList.add('active');
|
||||
|
||||
// Check the radio button
|
||||
const radio = this.querySelector('input[type="radio"]');
|
||||
radio.checked = true;
|
||||
|
||||
// Get the selected language
|
||||
const selectedLanguage = this.getAttribute('data-language');
|
||||
|
||||
// Show/hide voice cards based on language
|
||||
let firstVisibleCard = null;
|
||||
|
||||
voiceCards.forEach(card => {
|
||||
const cardLanguage = card.getAttribute('data-language');
|
||||
if (cardLanguage === selectedLanguage) {
|
||||
card.style.display = '';
|
||||
if (!firstVisibleCard) {
|
||||
firstVisibleCard = card;
|
||||
}
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
// If this hidden card was selected, unselect it
|
||||
if (card.classList.contains('active')) {
|
||||
card.classList.remove('active');
|
||||
const cardRadio = card.querySelector('input[type="radio"]');
|
||||
if (cardRadio) {
|
||||
cardRadio.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no voice card is selected for this language, select the first one
|
||||
if (firstVisibleCard && document.querySelectorAll(`.voice-card[data-language="${selectedLanguage}"].active:not([style*="display: none"])`).length === 0) {
|
||||
firstVisibleCard.classList.add('active');
|
||||
const firstVisibleRadio = firstVisibleCard.querySelector('input[type="radio"]');
|
||||
if (firstVisibleRadio) {
|
||||
firstVisibleRadio.checked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Voice card selection
|
||||
voiceCards.forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
// Only allow selection of cards that are visible (not display:none)
|
||||
if (this.style.display !== 'none') {
|
||||
// Unselect all cards with the same language
|
||||
const cardLanguage = this.getAttribute('data-language');
|
||||
document.querySelectorAll(`.voice-card[data-language="${cardLanguage}"]`).forEach(c => {
|
||||
c.classList.remove('active');
|
||||
const radio = c.querySelector('input[type="radio"]');
|
||||
if (radio) {
|
||||
radio.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Select this card
|
||||
this.classList.add('active');
|
||||
|
||||
// Check the radio button
|
||||
const radio = this.querySelector('input[type="radio"]');
|
||||
if (radio) {
|
||||
radio.checked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,5 @@ from .inference import (
|
|||
generate_speech_from_api,
|
||||
AVAILABLE_VOICES,
|
||||
DEFAULT_VOICE,
|
||||
VOICE_TO_LANGUAGE,
|
||||
AVAILABLE_LANGUAGES,
|
||||
list_available_voices
|
||||
)
|
||||
|
|
|
|||
|
|
@ -136,43 +136,10 @@ if not IS_RELOADER:
|
|||
# Parallel processing settings
|
||||
NUM_WORKERS = 4 if HIGH_END_GPU else 2
|
||||
|
||||
# Define voices by language
|
||||
ENGLISH_VOICES = ["tara", "leah", "jess", "leo", "dan", "mia", "zac", "zoe"]
|
||||
FRENCH_VOICES = ["pierre", "amelie", "marie"]
|
||||
GERMAN_VOICES = ["jana", "thomas", "max"]
|
||||
KOREAN_VOICES = ["유나", "준서"]
|
||||
HINDI_VOICES = ["ऋतिका"]
|
||||
MANDARIN_VOICES = ["长乐", "白芷"]
|
||||
SPANISH_VOICES = ["javi", "sergio", "maria"]
|
||||
ITALIAN_VOICES = ["pietro", "giulia", "carlo"]
|
||||
|
||||
# Combined list for API compatibility
|
||||
AVAILABLE_VOICES = (
|
||||
ENGLISH_VOICES +
|
||||
FRENCH_VOICES +
|
||||
GERMAN_VOICES +
|
||||
KOREAN_VOICES +
|
||||
HINDI_VOICES +
|
||||
MANDARIN_VOICES +
|
||||
SPANISH_VOICES +
|
||||
ITALIAN_VOICES
|
||||
)
|
||||
# Available voices based on the Orpheus-TTS repository
|
||||
AVAILABLE_VOICES = ["tara", "leah", "jess", "leo", "dan", "mia", "zac", "zoe"]
|
||||
DEFAULT_VOICE = "tara" # Best voice according to documentation
|
||||
|
||||
# Map voices to languages for the UI
|
||||
VOICE_TO_LANGUAGE = {}
|
||||
VOICE_TO_LANGUAGE.update({voice: "english" for voice in ENGLISH_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "french" for voice in FRENCH_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "german" for voice in GERMAN_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "korean" for voice in KOREAN_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "hindi" for voice in HINDI_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "mandarin" for voice in MANDARIN_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "spanish" for voice in SPANISH_VOICES})
|
||||
VOICE_TO_LANGUAGE.update({voice: "italian" for voice in ITALIAN_VOICES})
|
||||
|
||||
# Languages list for the UI
|
||||
AVAILABLE_LANGUAGES = ["english", "french", "german", "korean", "hindi", "mandarin", "spanish", "italian"]
|
||||
|
||||
# Import the unified token handling from speechpipe
|
||||
from .speechpipe import turn_token_into_id, CUSTOM_TOKEN_PREFIX
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue