add elevenlabs voice
Browse files- README.md +7 -1
- app/static/index.html +1 -0
- gameState.js +1 -0
- requirements.txt +5 -0
- team8audio.py +221 -0
README.md
CHANGED
@@ -10,4 +10,10 @@ git remote add huggingface-test https://huggingface.co/spaces/The-Last-Message/s
|
|
10 |
docker build -t p5js-game .
|
11 |
|
12 |
# Run the container
|
13 |
-
docker run -p 8000:8000 p5js-game
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
docker build -t p5js-game .
|
11 |
|
12 |
# Run the container
|
13 |
+
docker run -p 8000:8000 p5js-game
|
14 |
+
|
15 |
+
# Build the image
|
16 |
+
docker build -t elevenlabs-api .
|
17 |
+
|
18 |
+
# Run the container
|
19 |
+
docker run -p 7860:7860 -e ELEVENLABS_API_KEY=your_api_key elevenlabs-api
|
app/static/index.html
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
hello
|
gameState.js
CHANGED
@@ -81,3 +81,4 @@ Remember:
|
|
81 |
|
82 |
// Export the class for use in other files
|
83 |
export default GameState;
|
|
|
|
81 |
|
82 |
// Export the class for use in other files
|
83 |
export default GameState;
|
84 |
+
|
requirements.txt
CHANGED
@@ -1,2 +1,7 @@
|
|
1 |
fastapi
|
2 |
uvicorn[standard]
|
|
|
|
|
|
|
|
|
|
|
|
1 |
fastapi
|
2 |
uvicorn[standard]
|
3 |
+
flask==2.0.1
|
4 |
+
requests==2.26.0
|
5 |
+
pydub==0.25.1
|
6 |
+
flask-cors==3.0.10
|
7 |
+
python-multipart==0.0.5
|
team8audio.py
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, request, send_file, jsonify, render_template
|
2 |
+
import os
|
3 |
+
import requests
|
4 |
+
from pydub import AudioSegment
|
5 |
+
import io
|
6 |
+
import uuid
|
7 |
+
import tempfile
|
8 |
+
from flask_cors import CORS
|
9 |
+
import sqlite3
|
10 |
+
from datetime import datetime
|
11 |
+
|
12 |
+
app = Flask(__name__, static_folder='static')
|
13 |
+
CORS(app)
|
14 |
+
|
15 |
+
# Configuration
|
16 |
+
class Config:
|
17 |
+
ELEVENLABS_API_KEY = os.getenv('ELEVENLABS_API_KEY', "<YOUR-API-KEY>")
|
18 |
+
VOICE_ID = "21m00Tcm4TlvDq8ikWAM" # Default voice (Rachel)
|
19 |
+
CHUNK_SIZE = 2000 # Characters per chunk
|
20 |
+
TEMP_DIR = "temp_audio"
|
21 |
+
DB_PATH = "data/audio_history.db"
|
22 |
+
|
23 |
+
@staticmethod
|
24 |
+
def create_temp_dir():
|
25 |
+
if not os.path.exists(Config.TEMP_DIR):
|
26 |
+
os.makedirs(Config.TEMP_DIR)
|
27 |
+
|
28 |
+
@staticmethod
|
29 |
+
def init_db():
|
30 |
+
conn = sqlite3.connect(Config.DB_PATH)
|
31 |
+
c = conn.cursor()
|
32 |
+
c.execute('''
|
33 |
+
CREATE TABLE IF NOT EXISTS audio_generations
|
34 |
+
(id TEXT PRIMARY KEY,
|
35 |
+
timestamp DATETIME,
|
36 |
+
text TEXT,
|
37 |
+
file_path TEXT)
|
38 |
+
''')
|
39 |
+
conn.commit()
|
40 |
+
conn.close()
|
41 |
+
|
42 |
+
# Database operations
|
43 |
+
class DatabaseManager:
|
44 |
+
@staticmethod
|
45 |
+
def add_generation(generation_id, text, file_path):
|
46 |
+
conn = sqlite3.connect(Config.DB_PATH)
|
47 |
+
c = conn.cursor()
|
48 |
+
c.execute(
|
49 |
+
"INSERT INTO audio_generations VALUES (?, ?, ?, ?)",
|
50 |
+
(generation_id, datetime.now(), text, file_path)
|
51 |
+
)
|
52 |
+
conn.commit()
|
53 |
+
conn.close()
|
54 |
+
|
55 |
+
@staticmethod
|
56 |
+
def get_generations():
|
57 |
+
conn = sqlite3.connect(Config.DB_PATH)
|
58 |
+
c = conn.cursor()
|
59 |
+
c.execute("SELECT * FROM audio_generations ORDER BY timestamp DESC")
|
60 |
+
generations = c.fetchall()
|
61 |
+
conn.close()
|
62 |
+
return generations
|
63 |
+
|
64 |
+
# Text processor class
|
65 |
+
class TextProcessor:
|
66 |
+
@staticmethod
|
67 |
+
def split_text(text, chunk_size):
|
68 |
+
words = text.split()
|
69 |
+
chunks = []
|
70 |
+
current_chunk = []
|
71 |
+
current_size = 0
|
72 |
+
|
73 |
+
for word in words:
|
74 |
+
if current_size + len(word) > chunk_size:
|
75 |
+
chunks.append(" ".join(current_chunk))
|
76 |
+
current_chunk = [word]
|
77 |
+
current_size = len(word)
|
78 |
+
else:
|
79 |
+
current_chunk.append(word)
|
80 |
+
current_size += len(word) + 1
|
81 |
+
|
82 |
+
if current_chunk:
|
83 |
+
chunks.append(" ".join(current_chunk))
|
84 |
+
return chunks
|
85 |
+
|
86 |
+
# Audio Generator class
|
87 |
+
class AudioGenerator:
|
88 |
+
def __init__(self):
|
89 |
+
self.api_key = Config.ELEVENLABS_API_KEY
|
90 |
+
self.voice_id = Config.VOICE_ID
|
91 |
+
|
92 |
+
def generate_audio_chunk(self, text, previous_text=None, next_text=None, previous_request_ids=None):
|
93 |
+
url = f"https://api.elevenlabs.io/v1/text-to-speech/{self.voice_id}/stream"
|
94 |
+
|
95 |
+
headers = {
|
96 |
+
"xi-api-key": self.api_key
|
97 |
+
}
|
98 |
+
|
99 |
+
payload = {
|
100 |
+
"text": text,
|
101 |
+
"model_id": "eleven_multilingual_v2"
|
102 |
+
}
|
103 |
+
|
104 |
+
if previous_text is not None:
|
105 |
+
payload["previous_text"] = previous_text
|
106 |
+
if next_text is not None:
|
107 |
+
payload["next_text"] = next_text
|
108 |
+
if previous_request_ids:
|
109 |
+
payload["previous_request_ids"] = previous_request_ids[-3:]
|
110 |
+
|
111 |
+
response = requests.post(url, json=payload, headers=headers)
|
112 |
+
|
113 |
+
if response.status_code != 200:
|
114 |
+
raise Exception(f"API Error: {response.status_code} - {response.text}")
|
115 |
+
|
116 |
+
return response.content, response.headers.get("request-id")
|
117 |
+
|
118 |
+
# Routes
|
119 |
+
@app.route('/')
|
120 |
+
def index():
|
121 |
+
return render_template('index.html')
|
122 |
+
|
123 |
+
@app.route('/generate-audio', methods=['POST'])
|
124 |
+
def generate_audio():
|
125 |
+
try:
|
126 |
+
data = request.get_json()
|
127 |
+
|
128 |
+
if not data or 'text' not in data:
|
129 |
+
return jsonify({'error': 'No text provided'}), 400
|
130 |
+
|
131 |
+
text = data['text']
|
132 |
+
context_before = data.get('context_before', '')
|
133 |
+
context_after = data.get('context_after', '')
|
134 |
+
|
135 |
+
processor = TextProcessor()
|
136 |
+
generator = AudioGenerator()
|
137 |
+
|
138 |
+
chunks = processor.split_text(text, Config.CHUNK_SIZE)
|
139 |
+
|
140 |
+
segments = []
|
141 |
+
previous_request_ids = []
|
142 |
+
|
143 |
+
for i, chunk in enumerate(chunks):
|
144 |
+
is_first = i == 0
|
145 |
+
is_last = i == len(chunks) - 1
|
146 |
+
|
147 |
+
prev_text = None if is_first else (context_before + " " + " ".join(chunks[:i])).strip()
|
148 |
+
next_text = None if is_last else (" ".join(chunks[i+1:]) + " " + context_after).strip()
|
149 |
+
|
150 |
+
audio_content, request_id = generator.generate_audio_chunk(
|
151 |
+
chunk,
|
152 |
+
previous_text=prev_text,
|
153 |
+
next_text=next_text,
|
154 |
+
previous_request_ids=previous_request_ids
|
155 |
+
)
|
156 |
+
|
157 |
+
segments.append(AudioSegment.from_mp3(io.BytesIO(audio_content)))
|
158 |
+
previous_request_ids.append(request_id)
|
159 |
+
|
160 |
+
final_audio = segments[0]
|
161 |
+
for segment in segments[1:]:
|
162 |
+
final_audio += segment
|
163 |
+
|
164 |
+
generation_id = str(uuid.uuid4())
|
165 |
+
output_path = os.path.join(Config.TEMP_DIR, f"{generation_id}.wav")
|
166 |
+
final_audio.export(output_path, format="wav")
|
167 |
+
|
168 |
+
# Save to database
|
169 |
+
DatabaseManager.add_generation(generation_id, text, output_path)
|
170 |
+
|
171 |
+
return send_file(
|
172 |
+
output_path,
|
173 |
+
mimetype="audio/wav",
|
174 |
+
as_attachment=True,
|
175 |
+
download_name="generated_audio.wav"
|
176 |
+
)
|
177 |
+
|
178 |
+
except Exception as e:
|
179 |
+
return jsonify({'error': str(e)}), 500
|
180 |
+
|
181 |
+
@app.route('/voices', methods=['GET'])
|
182 |
+
def get_voices():
|
183 |
+
try:
|
184 |
+
response = requests.get(
|
185 |
+
"https://api.elevenlabs.io/v1/voices",
|
186 |
+
headers={"xi-api-key": Config.ELEVENLABS_API_KEY}
|
187 |
+
)
|
188 |
+
return jsonify(response.json())
|
189 |
+
except Exception as e:
|
190 |
+
return jsonify({'error': str(e)}), 500
|
191 |
+
|
192 |
+
@app.route('/history', methods=['GET'])
|
193 |
+
def get_history():
|
194 |
+
try:
|
195 |
+
generations = DatabaseManager.get_generations()
|
196 |
+
return jsonify([{
|
197 |
+
'id': g[0],
|
198 |
+
'timestamp': g[1],
|
199 |
+
'text': g[2],
|
200 |
+
'file_path': g[3]
|
201 |
+
} for g in generations])
|
202 |
+
except Exception as e:
|
203 |
+
return jsonify({'error': str(e)}), 500
|
204 |
+
|
205 |
+
# Error handlers
|
206 |
+
@app.errorhandler(404)
|
207 |
+
def not_found(error):
|
208 |
+
return jsonify({'error': 'Not found'}), 404
|
209 |
+
|
210 |
+
@app.errorhandler(500)
|
211 |
+
def server_error(error):
|
212 |
+
return jsonify({'error': 'Server error'}), 500
|
213 |
+
|
214 |
+
if __name__ == '__main__':
|
215 |
+
# Initialize directories and database
|
216 |
+
Config.create_temp_dir()
|
217 |
+
Config.init_db()
|
218 |
+
|
219 |
+
# Run the Flask app
|
220 |
+
port = int(os.getenv('PORT', 7860))
|
221 |
+
app.run(host='0.0.0.0', port=port)
|