File size: 6,582 Bytes
6450a0b
0dd2770
54412ba
508067c
 
 
 
 
 
 
 
 
e23dd95
0dd2770
631c13e
 
508067c
 
 
 
edc72f5
508067c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6d1fe44
 
508067c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24fe88b
508067c
 
 
24fe88b
508067c
 
24fe88b
508067c
 
24fe88b
508067c
 
 
 
24fe88b
508067c
24fe88b
508067c
218666d
 
 
 
 
 
 
24fe88b
218666d
 
24fe88b
508067c
79df6e6
218666d
 
24fe88b
508067c
 
4cff669
 
 
 
 
 
 
 
 
631c13e
 
a2f2f5d
4cff669
 
508067c
 
 
 
 
 
 
 
 
 
 
 
24fe88b
508067c
 
 
 
 
 
 
24fe88b
508067c
 
 
 
 
 
 
 
 
 
 
 
24fe88b
508067c
 
 
 
 
 
 
 
 
24fe88b
508067c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24fe88b
508067c
 
24fe88b
508067c
 
 
24fe88b
508067c
 
 
 
 
51e2649
631c13e
24fe88b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import os
import random
import string
import subprocess
import requests
from datetime import datetime
from flask import Flask, render_template, request, jsonify, send_file, Response, stream_with_context
from bs4 import BeautifulSoup
import markdown
import threading
from queue import Queue
import time
import json

app = Flask(__name__)

# Define directories
file_folder = os.path.dirname(os.path.abspath(__file__))
temp_audio_folder = os.path.join(file_folder, 'temp_audio')
model_folder = None
piper_binary_path = os.path.join(file_folder, 'piper')

# Create necessary directories
os.makedirs(temp_audio_folder, exist_ok=True)

# Check default user folder
default_user_folder = "./"
if os.path.exists(default_user_folder) and any(f.endswith('.onnx') for f in os.listdir(default_user_folder)):
    model_folder = default_user_folder

# Global settings
DEFAULT_BASE_HOST = "http://localhost:11434"
SETTINGS = {
    'speaker': 0,
    'noise_scale': 0.667,
    'length_scale': 1.0,
    'noise_w': 0.8,
    'sentence_silence': 0.2
}

def get_available_models():
    if not model_folder:
        return []
    return [os.path.splitext(model)[0] for model in os.listdir(model_folder) if model.endswith('.onnx')]

def get_ollama_models(base_host=DEFAULT_BASE_HOST):
    try:
        response = requests.get(f"{base_host}/api/tags")
        if response.status_code == 200:
            return [model['name'] for model in response.json().get('models', [])]
        return []
    except:
        return []

def remove_markdown(text):
    html_content = markdown.markdown(text)
    soup = BeautifulSoup(html_content, 'html.parser')
    return soup.get_text().strip()

def convert_to_speech(text, model_name, remove_md=False):
    if model_name not in get_available_models():
        return None

    if remove_md:
        text = remove_markdown(text)

    random_name = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + '.wav'
    output_file = os.path.join(temp_audio_folder, random_name)

    # Clean old audio files
    for file in os.listdir(temp_audio_folder):
        if file.endswith('.wav'):
            os.remove(os.path.join(temp_audio_folder, file))

    model_path = os.path.join(model_folder, model_name + '.onnx')

    try:
        # Use the text directly in the command
        command = (
            f'"{piper_binary_path}" -m "{model_path}" -f "{output_file}" '
            f'--speaker {SETTINGS["speaker"]} --noise_scale {SETTINGS["noise_scale"]} '
            f'--length_scale {SETTINGS["length_scale"]} --noise_w {SETTINGS["noise_w"]} '
            f'--sentence_silence {SETTINGS["sentence_silence"]}'
        )

        # Pass the text as input to the command
        result = subprocess.run(command, input=text.encode('utf-8'), shell=True, check=True)

        if os.path.exists(output_file):
            return output_file
    except Exception as e:
        print(f"Error during text-to-speech conversion: {e}")

    return None

def set_default_models():
    tts_models = get_available_models()
    ollama_models = get_ollama_models()

    default_tts_model = "RecomendacionesConMiau" if "RecomendacionesConMiau" in tts_models else None
    default_ollama_model = "llama3.2:1b" if "llama3.2:1b" in ollama_models else None

    return default_tts_model, default_ollama_model

@app.route('/')
def index():
    tts_models = get_available_models()
    default_tts_model, default_ollama_model = set_default_models()
    return render_template('index.html', tts_models=tts_models, default_tts_model=default_tts_model, default_ollama_model=default_ollama_model)

@app.route('/api/list_ollama_models')
def list_ollama_models():
    base_host = request.args.get('base_host', DEFAULT_BASE_HOST)
    return jsonify(models=get_ollama_models(base_host))

@app.route('/api/chat', methods=['POST'])
def chat():
    data = request.json
    base_host = data.get('base_host', DEFAULT_BASE_HOST)
    model = data.get('model')
    messages = data.get('messages', [])

    def generate():
        queue = Queue()
        thread = threading.Thread(
            target=stream_ollama_response,
            args=(base_host, model, messages, queue)
        )
        thread.start()

        complete_response = ""
        while True:
            msg_type, content = queue.get()
            if msg_type == "error":
                yield f"data: {json.dumps({'error': content})}\n\n"
                break
            elif msg_type == "chunk":
                complete_response = content
                yield f"data: {json.dumps({'chunk': content})}\n\n"
            elif msg_type == "done":
                yield f"data: {json.dumps({'done': complete_response})}\n\n"
                break

    return Response(stream_with_context(generate()), mimetype='text/event-stream')

def stream_ollama_response(base_host, model, messages, queue):
    url = f"{base_host}/api/chat"
    data = {
        "model": model,
        "messages": messages,
        "stream": True
    }

    try:
        with requests.post(url, json=data, stream=True) as response:
            if response.status_code == 200:
                complete_response = ""
                for line in response.iter_lines():
                    if line:
                        try:
                            json_response = json.loads(line)
                            chunk = json_response.get("message", {}).get("content", "")
                            if chunk:
                                complete_response += chunk
                                queue.put(("chunk", complete_response))
                        except json.JSONDecodeError:
                            continue
                queue.put(("done", complete_response))
            else:
                queue.put(("error", f"Error: {response.status_code}"))
    except Exception as e:
        queue.put(("error", f"Error: {str(e)}"))

@app.route('/api/tts', methods=['POST'])
def text_to_speech():
    data = request.json
    text = data.get('text', '')
    model = data.get('model')
    remove_md = data.get('remove_markdown', False)

    if not text or not model:
        return jsonify(error="Missing text or model"), 400

    audio_file = convert_to_speech(text, model, remove_md)
    if not audio_file:
        return jsonify(error="Failed to convert text to speech"), 500

    return jsonify(audio_file=os.path.basename(audio_file))

@app.route('/audio/<filename>')
def serve_audio(filename):
    return send_file(os.path.join(temp_audio_folder, filename))

if __name__ == '__main__':
    app.run(debug=True, port=7860, host='0.0.0.0')