Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
AI test version super initial commit
Browse files- Dockerfile +14 -0
- app/__pycache__/config.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- app/__pycache__/voice_languag_detector.cpython-312.pyc +0 -0
- app/config.py +36 -0
- app/main.py +75 -0
- app/voice_languag_detector.py +45 -0
- classifier_model/voice_language_detector.py +41 -0
- lid.176.bin +3 -0
- requirements.txt +6 -0
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /code
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY app/ ./app
|
| 9 |
+
|
| 10 |
+
WORKDIR /code/app
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
|
| 14 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
app/__pycache__/config.cpython-312.pyc
ADDED
|
Binary file (866 Bytes). View file
|
|
|
app/__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (2.87 kB). View file
|
|
|
app/__pycache__/voice_languag_detector.cpython-312.pyc
ADDED
|
Binary file (2.11 kB). View file
|
|
|
app/config.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dataclasses import dataclass, field
|
| 3 |
+
from typing import Dict
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
ELEVENLABS_API_KEY = "ELEVENLABS_API_KEY"
|
| 9 |
+
|
| 10 |
+
ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
MODEL_ID = "eleven_v3"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
VOICE_IDS = {
|
| 17 |
+
"ig": "QLniWkGYsJa91mXrxl3c", # Igbo
|
| 18 |
+
"yo": "x86DtpnPPuq2BpEiKPRy", # Yoruba
|
| 19 |
+
"ha": "65blg8G9v7ZRWectRxK8", # Hausa
|
| 20 |
+
"en": "65blg8G9v7ZRWectRxK8", # English
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
DEFAULT_LANGUAGE = "en"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
CONFIDENCE_THRESHOLD = 0.5
|
| 27 |
+
|
| 28 |
+
FASTTEXT_REPO_ID = "facebook/fasttext-language-identification"
|
| 29 |
+
FASTTEXT_FILENAME = "model.bin"
|
| 30 |
+
|
| 31 |
+
LABEL_TO_VOICE = {
|
| 32 |
+
"ibo_Latn": "ig",
|
| 33 |
+
"yor_Latn": "yo",
|
| 34 |
+
"hau_Latn": "ha",
|
| 35 |
+
"eng_Latn": "en",
|
| 36 |
+
}
|
app/main.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
from fastapi import FastAPI, HTTPException
|
| 4 |
+
from fastapi.responses import Response
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
|
| 7 |
+
from app.config import (
|
| 8 |
+
ELEVENLABS_API_KEY,
|
| 9 |
+
ELEVENLABS_BASE_URL,
|
| 10 |
+
MODEL_ID,
|
| 11 |
+
VOICE_IDS,
|
| 12 |
+
DEFAULT_LANGUAGE,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from app.voice_languag_detector import detect_language
|
| 16 |
+
|
| 17 |
+
app = FastAPI(title="Africa TTS - Auto Language Voice Cloning")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TTSRequest(BaseModel):
|
| 21 |
+
text: str
|
| 22 |
+
stability: float = 0.5
|
| 23 |
+
similarity_boost: float = 0.75
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@app.get("/")
|
| 27 |
+
def root():
|
| 28 |
+
return {"status": "ok", "message": "Africa TTS API is running"}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@app.post("/tts")
|
| 32 |
+
def text_to_speech(request: TTSRequest):
|
| 33 |
+
if not request.text or not request.text.strip():
|
| 34 |
+
raise HTTPException(status_code=400, detail="Text cannot be empty")
|
| 35 |
+
|
| 36 |
+
if not ELEVENLABS_API_KEY:
|
| 37 |
+
raise HTTPException(status_code=500, detail="ELEVENLABS_API_KEY not set")
|
| 38 |
+
|
| 39 |
+
lang_code, confidence = detect_language(request.text)
|
| 40 |
+
voice_id = VOICE_IDS.get(lang_code, VOICE_IDS[DEFAULT_LANGUAGE])
|
| 41 |
+
|
| 42 |
+
url = f"{ELEVENLABS_BASE_URL}/text-to-speech/{voice_id}"
|
| 43 |
+
|
| 44 |
+
headers = {
|
| 45 |
+
"xi-api-key": ELEVENLABS_API_KEY,
|
| 46 |
+
"Content-Type": "application/json",
|
| 47 |
+
"Accept": "audio/mpeg",
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
payload = {
|
| 51 |
+
"text": request.text,
|
| 52 |
+
"model_id": MODEL_ID,
|
| 53 |
+
"voice_settings": {
|
| 54 |
+
"stability": request.stability,
|
| 55 |
+
"similarity_boost": request.similarity_boost,
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
response = requests.post(url, json=payload, headers=headers)
|
| 60 |
+
|
| 61 |
+
if response.status_code != 200:
|
| 62 |
+
raise HTTPException(
|
| 63 |
+
status_code=response.status_code,
|
| 64 |
+
detail=f"ElevenLabs API error: {response.text}",
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
return Response(
|
| 68 |
+
content = response.content,
|
| 69 |
+
media_type = "audio/mpeg",
|
| 70 |
+
headers = {
|
| 71 |
+
"X-Detected-Language": lang_code,
|
| 72 |
+
"X-Detection-confidence": str(round(confidence,4)),
|
| 73 |
+
"X-Voice-Id-Used": voice_id,
|
| 74 |
+
}
|
| 75 |
+
)
|
app/voice_languag_detector.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fasttext
|
| 2 |
+
from huggingface_hub import hf_hub_download
|
| 3 |
+
|
| 4 |
+
from app.config import (
|
| 5 |
+
FASTTEXT_REPO_ID,
|
| 6 |
+
FASTTEXT_FILENAME,
|
| 7 |
+
CONFIDENCE_THRESHOLD,
|
| 8 |
+
DEFAULT_LANGUAGE,
|
| 9 |
+
LABEL_TO_VOICE,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
fasttext.FastText.eprint = lambda x: None # silence warnings
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class LanguageDetector:
|
| 16 |
+
def __init__(self):
|
| 17 |
+
model_path = hf_hub_download(
|
| 18 |
+
repo_id=FASTTEXT_REPO_ID,
|
| 19 |
+
filename=FASTTEXT_FILENAME,
|
| 20 |
+
)
|
| 21 |
+
self.model = fasttext.load_model(model_path)
|
| 22 |
+
|
| 23 |
+
def detect(self, text: str):
|
| 24 |
+
clean_text = text.replace("\n", " ").strip()
|
| 25 |
+
if not clean_text:
|
| 26 |
+
return DEFAULT_LANGUAGE, 0.0
|
| 27 |
+
|
| 28 |
+
labels, confidences = self.model.predict(clean_text, k=1)
|
| 29 |
+
raw_label = labels[0].replace("__label__", "")
|
| 30 |
+
confidence = float(confidences[0])
|
| 31 |
+
|
| 32 |
+
lang_code = LABEL_TO_VOICE.get(raw_label)
|
| 33 |
+
|
| 34 |
+
if lang_code is not None and confidence >= CONFIDENCE_THRESHOLD:
|
| 35 |
+
return lang_code, confidence
|
| 36 |
+
|
| 37 |
+
return DEFAULT_LANGUAGE, confidence
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
detector = LanguageDetector()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def detect_language(text: str):
|
| 44 |
+
"""Returns (lang_code, confidence)"""
|
| 45 |
+
return detector.detect(text)
|
classifier_model/voice_language_detector.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import urllib.request
|
| 3 |
+
import fasttext
|
| 4 |
+
|
| 5 |
+
from app.config import config
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
fasttext.FastText.eprint = lambda x: None
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LanguageDetector:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
if not os.path.exists(FASTTEXT_MODEL_PATH):
|
| 15 |
+
print("Downloading fastText language ID model")
|
| 16 |
+
urllib.request.urlretrieve(FASTTEXT_MODEL_URL, FASTTEXT_MODEL_PATH)
|
| 17 |
+
self.model = fasttext.load_model(FASTTEXT_MODEL_PATH)
|
| 18 |
+
|
| 19 |
+
def detect(self, text: str):
|
| 20 |
+
clean_text = text.replace("\n", " ").strip()
|
| 21 |
+
if not clean_text:
|
| 22 |
+
return DEFAULT_LANGUAGE, 0.0
|
| 23 |
+
|
| 24 |
+
labels, confidences = self.model.predict(clean_text, k=1)
|
| 25 |
+
lang_code = labels[0].replace("__label__", "")
|
| 26 |
+
confidence = float(confidences[0])
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
if lang_code in VOICE_IDS and confidence >= CONFIDENCE_THRESHOLD:
|
| 30 |
+
return lang_code, confidence
|
| 31 |
+
|
| 32 |
+
return DEFAULT_LANGUAGE, confidence
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
detector = LanguageDetector()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def detect_language(text: str):
|
| 40 |
+
"""Returns (lang_code, confidence)"""
|
| 41 |
+
return detector.detect(text)
|
lid.176.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7e69ec5451bc261cc7844e49e4792a85d7f09c06789ec800fc4a44aec362764e
|
| 3 |
+
size 131266198
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn[standard]
|
| 3 |
+
requests
|
| 4 |
+
fasttext-wheel
|
| 5 |
+
pydantic
|
| 6 |
+
numpy<2
|