HirCoir commited on
Commit
508067c
1 Parent(s): a762856

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -81
app.py CHANGED
@@ -1,99 +1,264 @@
1
- from flask import Flask, render_template, request, jsonify, after_this_request
2
- from io import BytesIO
3
- import base64
4
- import subprocess
5
  import os
 
6
  import random
7
  import string
8
- import re
9
- import shlex
 
 
 
 
 
 
 
10
 
11
  app = Flask(__name__)
12
 
13
- # Define the folder where files are saved
14
- file_folder = '/home/app/'
15
-
16
- # Models with specific character replacements
17
- models_replacements = {
18
- "Español México | Claude": {
19
- "model_path": "es_MX-claude-14947-epoch-high.onnx",
20
- "replacements": [('(', ','), (')', ','), ('?', ','), ('¿', ','), (':', ','), ('\n', ' ')]
21
- },
22
- "Español México | Cortana Infinnity": {
23
- "model_path": "es_MX-cortana-19669-epoch-high.onnx",
24
- "replacements": [('(', ','), (')', ','), ('?', ','), ('¿', ','), (':', ','), ('\n', ' ')]
25
- },
26
- "Español México | TheGevy": {
27
- "model_path": "es_MX-gevy-10196-epoch-high.onnx",
28
- "replacements": [('(', ','), (')', ','), ('?', ','), ('¿', ','), (':', ','), ('\n', ' ')]
29
- },
30
- "English US | Voice": {
31
- "model_path": "en_US-ljspeech-high.onnx",
32
- "replacements": [('(', ','), (')', ','), ('?', ','), ('¿', ','), (':', ','), ('\n', ' ')]
33
- },
34
- "English US | Jarvis (Marvel Cinematic Universe)": {
35
- "model_path": "en_us-jarvis_ucm-high.onnx",
36
- "replacements": [('(', ','), (')', ','), ('?', ','), ('¿', ','), (':', ','), ('\n', ' ')]
37
- }
38
  }
39
 
40
- def filter_text(text):
41
- # Escapa caracteres especiales
42
- escaped_text = shlex.quote(text)
43
- return escaped_text
44
-
45
- def convert_text_to_speech(parrafo, model):
46
- # Limit text to 500 characters
47
- parrafo = parrafo[:10000]
48
-
49
- model_info = models_replacements.get(model)
50
- if model_info:
51
- model_path = model_info.get("model_path")
52
- parrafo_filtrado = filter_text(parrafo)
53
- random_name = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + '.wav'
54
- output_file = os.path.join(file_folder, random_name)
55
- app.logger.info("Audio file created at: %s", output_file)
56
- piper_exe = os.path.join(file_folder, 'piper') # Adjusted the path for piper
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- if os.path.isfile(piper_exe):
59
- comando = f'echo {parrafo_filtrado} | "{piper_exe}" -m {model_path} -f {output_file}'
60
- subprocess.run(comando, shell=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  return output_file
62
- else:
63
- return "The piper.exe file was not found in the correct directory."
64
- else:
65
- return "Model not found."
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  @app.route('/')
68
  def index():
69
- model_options = list(models_replacements.keys())
70
- # Log the contents of the current folder
71
- app.logger.info("Contents of current folder: %s", os.listdir(file_folder))
72
- return render_template('index.html', model_options=model_options)
73
-
74
- @app.route('/convert', methods=['POST'])
75
- def convert_text():
76
- text = request.form['text']
77
- model = request.form['model']
78
- output_file = convert_text_to_speech(text, model)
79
-
80
- @after_this_request
81
- def remove_file(response):
82
- try:
83
- os.remove(output_file)
84
- app.logger.info("Audio file deleted: %s", output_file)
85
- except Exception as error:
86
- app.logger.error("Error deleting file: %s", error)
87
- return response
88
-
89
- with open(output_file, 'rb') as audio_file:
90
- audio_content = audio_file.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- audio_base64 = base64.b64encode(audio_content).decode('utf-8')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- response = jsonify({'audio_base64': audio_base64})
 
95
 
96
- return response
 
 
 
 
 
 
 
 
97
 
98
  if __name__ == '__main__':
99
- app.run(host='0.0.0.0', port=7860, debug=False)
 
 
 
 
 
1
  import os
2
+ import json
3
  import random
4
  import string
5
+ import subprocess
6
+ import requests
7
+ from datetime import datetime
8
+ from flask import Flask, render_template, request, jsonify, send_file, Response, stream_with_context
9
+ from bs4 import BeautifulSoup
10
+ import markdown
11
+ import threading
12
+ from queue import Queue
13
+ import time
14
 
15
  app = Flask(__name__)
16
 
17
+ # Define directories
18
+ file_folder = os.path.dirname(os.path.abspath(__file__))
19
+ temp_audio_folder = os.path.join(file_folder, 'temp_audio')
20
+ chats_folder = os.path.join(file_folder, 'chats')
21
+ model_folder = None
22
+ piper_binary_path = os.path.join(file_folder, 'piper', 'piper')
23
+
24
+ # Create necessary directories
25
+ os.makedirs(temp_audio_folder, exist_ok=True)
26
+ os.makedirs(chats_folder, exist_ok=True)
27
+
28
+ # Check default user folder
29
+ default_user_folder = "./"
30
+ if os.path.exists(default_user_folder) and any(f.endswith('.onnx') for f in os.listdir(default_user_folder)):
31
+ model_folder = default_user_folder
32
+
33
+ # Global settings
34
+ DEFAULT_BASE_HOST = "http://localhost:11434"
35
+ SETTINGS = {
36
+ 'speaker': 0,
37
+ 'noise_scale': 0.667,
38
+ 'length_scale': 1.0,
39
+ 'noise_w': 0.8,
40
+ 'sentence_silence': 0.2
 
41
  }
42
 
43
+ def get_available_models():
44
+ if not model_folder:
45
+ return []
46
+ return [os.path.splitext(model)[0] for model in os.listdir(model_folder) if model.endswith('.onnx')]
47
+
48
+ def get_ollama_models(base_host=DEFAULT_BASE_HOST):
49
+ try:
50
+ response = requests.get(f"{base_host}/api/tags")
51
+ if response.status_code == 200:
52
+ return [model['name'] for model in response.json().get('models', [])]
53
+ return []
54
+ except:
55
+ return []
56
+
57
+ def load_chat_history(chat_file):
58
+ try:
59
+ with open(os.path.join(chats_folder, chat_file), 'r', encoding='utf-8') as f:
60
+ return json.load(f).get('messages', [])
61
+ except:
62
+ return []
63
+
64
+ def save_chat(messages, chat_file=None):
65
+ if not messages:
66
+ return None
67
+
68
+ if not chat_file:
69
+ timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
70
+ chat_file = f'chat-{timestamp}.json'
71
+
72
+ filepath = os.path.join(chats_folder, chat_file)
73
+ with open(filepath, 'w', encoding='utf-8') as f:
74
+ json.dump({
75
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
76
+ 'messages': messages
77
+ }, f, ensure_ascii=False, indent=2)
78
+
79
+ return chat_file
80
+
81
+ def remove_markdown(text):
82
+ html_content = markdown.markdown(text)
83
+ soup = BeautifulSoup(html_content, 'html.parser')
84
+ return soup.get_text().strip()
85
+
86
+ def convert_to_speech(text, model_name, remove_md=False):
87
+ if model_name not in get_available_models():
88
+ return None
89
 
90
+ if remove_md:
91
+ text = remove_markdown(text)
92
+
93
+ random_name = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + '.wav'
94
+ output_file = os.path.join(temp_audio_folder, random_name)
95
+
96
+ # Clean old audio files
97
+ for file in os.listdir(temp_audio_folder):
98
+ if file.endswith('.wav'):
99
+ os.remove(os.path.join(temp_audio_folder, file))
100
+
101
+ model_path = os.path.join(model_folder, model_name + '.onnx')
102
+
103
+ # Create temporary text file
104
+ temp_txt_path = os.path.join(os.getenv('TEMP'), "temp_text.txt")
105
+ with open(temp_txt_path, "w", encoding="utf-8") as f:
106
+ f.write(text)
107
+
108
+ try:
109
+ command = (f'type "{temp_txt_path}" | "{piper_binary_path}" -m "{model_path}" -f "{output_file}" '
110
+ f'--speaker {SETTINGS["speaker"]} --noise_scale {SETTINGS["noise_scale"]} '
111
+ f'--length_scale {SETTINGS["length_scale"]} --noise_w {SETTINGS["noise_w"]} '
112
+ f'--sentence_silence {SETTINGS["sentence_silence"]}')
113
+
114
+ subprocess.run(command, shell=True, check=True)
115
+ os.remove(temp_txt_path)
116
+
117
+ if os.path.exists(output_file):
118
  return output_file
119
+ except:
120
+ pass
121
+ finally:
122
+ if os.path.exists(temp_txt_path):
123
+ os.remove(temp_txt_path)
124
+
125
+ return None
126
+
127
+ def set_default_models():
128
+ tts_models = get_available_models()
129
+ ollama_models = get_ollama_models()
130
+
131
+ default_tts_model = "RecomendacionesConMiau" if "RecomendacionesConMiau" in tts_models else None
132
+ default_ollama_model = "llama3.2:1b" if "llama3.2:1b" in ollama_models else None
133
+
134
+ return default_tts_model, default_ollama_model
135
 
136
  @app.route('/')
137
  def index():
138
+ tts_models = get_available_models()
139
+ chat_files = sorted([f for f in os.listdir(chats_folder) if f.endswith('.json')], reverse=True)
140
+ default_tts_model, default_ollama_model = set_default_models()
141
+ return render_template('index.html', tts_models=tts_models, chat_files=chat_files, default_tts_model=default_tts_model, default_ollama_model=default_ollama_model)
142
+
143
+ @app.route('/api/list_ollama_models')
144
+ def list_ollama_models():
145
+ base_host = request.args.get('base_host', DEFAULT_BASE_HOST)
146
+ return jsonify(models=get_ollama_models(base_host))
147
+
148
+ @app.route('/api/load_chat/<chat_file>')
149
+ def load_chat(chat_file):
150
+ messages = load_chat_history(chat_file)
151
+ return jsonify(messages=messages)
152
+
153
+ @app.route('/api/update_message', methods=['POST'])
154
+ def update_message():
155
+ data = request.json
156
+ chat_file = data.get('chat_file')
157
+ message_index = data.get('message_index')
158
+ new_content = data.get('content')
159
+ is_user = data.get('is_user', False)
160
+
161
+ if not chat_file or message_index is None or not new_content:
162
+ return jsonify(error="Missing required parameters"), 400
163
+
164
+ messages = load_chat_history(chat_file)
165
+ if message_index >= len(messages):
166
+ return jsonify(error="Invalid message index"), 400
167
+
168
+ # Update the message content
169
+ messages[message_index]['content'] = new_content
170
+
171
+ # If it's a user message, regenerate all subsequent responses
172
+ if is_user:
173
+ # Keep messages up to and including the edited message
174
+ messages = messages[:message_index + 1]
175
+
176
+ # Save the updated chat
177
+ save_chat(messages, chat_file)
178
+
179
+ return jsonify(success=True, messages=messages)
180
+
181
+ @app.route('/api/chat', methods=['POST'])
182
+ def chat():
183
+ data = request.json
184
+ base_host = data.get('base_host', DEFAULT_BASE_HOST)
185
+ model = data.get('model')
186
+ messages = data.get('messages', [])
187
+ chat_file = data.get('chat_file')
188
+
189
+ def generate():
190
+ queue = Queue()
191
+ thread = threading.Thread(
192
+ target=stream_ollama_response,
193
+ args=(base_host, model, messages, queue)
194
+ )
195
+ thread.start()
196
 
197
+ complete_response = ""
198
+ while True:
199
+ msg_type, content = queue.get()
200
+ if msg_type == "error":
201
+ yield f"data: {json.dumps({'error': content})}\n\n"
202
+ break
203
+ elif msg_type == "chunk":
204
+ complete_response = content
205
+ yield f"data: {json.dumps({'chunk': content})}\n\n"
206
+ elif msg_type == "done":
207
+ # Save chat history
208
+ messages.append({"role": "assistant", "content": complete_response})
209
+ save_chat(messages, chat_file)
210
+ yield f"data: {json.dumps({'done': complete_response})}\n\n"
211
+ break
212
+
213
+ return Response(stream_with_context(generate()), mimetype='text/event-stream')
214
+
215
+ def stream_ollama_response(base_host, model, messages, queue):
216
+ url = f"{base_host}/api/chat"
217
+ data = {
218
+ "model": model,
219
+ "messages": messages,
220
+ "stream": True
221
+ }
222
+
223
+ try:
224
+ with requests.post(url, json=data, stream=True) as response:
225
+ if response.status_code == 200:
226
+ complete_response = ""
227
+ for line in response.iter_lines():
228
+ if line:
229
+ try:
230
+ json_response = json.loads(line)
231
+ chunk = json_response.get("message", {}).get("content", "")
232
+ if chunk:
233
+ complete_response += chunk
234
+ queue.put(("chunk", complete_response))
235
+ except json.JSONDecodeError:
236
+ continue
237
+ queue.put(("done", complete_response))
238
+ else:
239
+ queue.put(("error", f"Error: {response.status_code}"))
240
+ except Exception as e:
241
+ queue.put(("error", f"Error: {str(e)}"))
242
+
243
+ @app.route('/api/tts', methods=['POST'])
244
+ def text_to_speech():
245
+ data = request.json
246
+ text = data.get('text', '')
247
+ model = data.get('model')
248
+ remove_md = data.get('remove_markdown', False)
249
 
250
+ if not text or not model:
251
+ return jsonify(error="Missing text or model"), 400
252
 
253
+ audio_file = convert_to_speech(text, model, remove_md)
254
+ if not audio_file:
255
+ return jsonify(error="Failed to convert text to speech"), 500
256
+
257
+ return jsonify(audio_file=os.path.basename(audio_file))
258
+
259
+ @app.route('/audio/<filename>')
260
+ def serve_audio(filename):
261
+ return send_file(os.path.join(temp_audio_folder, filename))
262
 
263
  if __name__ == '__main__':
264
+ app.run(debug=True, port=7860, host='0.0.0.0')