File size: 8,325 Bytes
e129c85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8d2af7a
e129c85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
197
198
199
200
import gradio as gr
import requests
import json
import os
import time
from collections import defaultdict

BASE_URL = "https://api.jigsawstack.com/v1"
headers = {
    "x-api-key": os.getenv("JIGSAWSTACK_API_KEY")
}


# Rate limiting configuration
request_times = defaultdict(list)
MAX_REQUESTS = 20  # Maximum requests per time window
TIME_WINDOW = 3600   # Time window in seconds (1 hour)

def get_real_ip(request: gr.Request):
    """Extract real IP address using x-forwarded-for header or fallback"""
    if not request:
        return "unknown"
    
    forwarded = request.headers.get("x-forwarded-for")
    if forwarded:
        ip = forwarded.split(",")[0].strip()  # First IP in the list is the client's
    else:
        ip = request.client.host  # fallback
    return ip

def check_rate_limit(request: gr.Request):
    """Check if the current request exceeds rate limits"""
    if not request:
        return True, "Rate limit check failed - no request info"
    
    ip = get_real_ip(request)
    now = time.time()

    # Clean up old timestamps outside the time window
    request_times[ip] = [t for t in request_times[ip] if now - t < TIME_WINDOW]
    

    # Check if rate limit exceeded
    if len(request_times[ip]) >= MAX_REQUESTS:
        time_remaining = int(TIME_WINDOW - (now - request_times[ip][0]))
        time_remaining_minutes = round(time_remaining / 60, 1)      
        time_window_minutes = round(TIME_WINDOW / 60, 1)
        
        return False, f"Rate limit exceeded. You can make {MAX_REQUESTS} requests per {time_window_minutes} minutes. Try again in {time_remaining_minutes} minutes."
    
    # Add current request timestamp
    request_times[ip].append(now)
    return True, ""


def text_to_speech(text, accent, voice_clone_id, request: gr.Request):
    rate_limit_ok, rate_limit_msg = check_rate_limit(request)
    if not rate_limit_ok:
        return None, rate_limit_msg
    
    if not text or not text.strip():
        return None, "Error: Text input is required."

    payload = {"text": text.strip()}

    if accent and accent.strip():
        payload["accent"] = accent.strip()

    if voice_clone_id and voice_clone_id.strip():
        payload["voice_clone_id"] = voice_clone_id.strip()

    # Validate text length
    max_len = 500 if "voice_clone_id" in payload else 1500
    if len(payload["text"]) > max_len:
        mode = "Voice Cloning" if "voice_clone_id" in payload else "Standard"
        return None, f"Error: Text for {mode} is limited to {max_len} characters."

    try:
        response = requests.post(f"{BASE_URL}/ai/tts", headers=headers, json=payload)
        response.raise_for_status()

        if response.headers.get("content-type", "").startswith("audio/"):
            import tempfile
            with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
                tmp_file.write(response.content)
                return tmp_file.name, "βœ… Speech generated successfully!"
        else:
            try:
                error_data = response.json()
                return None, f"Error: API returned an error - {error_data.get('message', 'Unknown error')}"
            except:
                return None, "Error: Received an unexpected response from the API."

    except requests.exceptions.RequestException as e:
        return None, f"Request failed: {str(e)}"
    except Exception as e:
        return None, f"An unexpected error occurred: {str(e)}"


# ----------------- Gradio UI ------------------

with gr.Blocks() as demo:
    gr.Markdown("""
    <div style='text-align: center; margin-bottom: 24px;'>
        <h1 style='font-size:2.2em; margin-bottom: 0.2em;'>🧩 Text to Speech</h1>
        <p style='font-size:1.2em; margin-top: 0;'>Transform text into natural-sounding human-like AI voices with low latency and exceptional quality.</p>
        <p style='font-size:1em; margin-top: 0.5em;'>For more details and API usage, see the <a href='https://jigsawstack.com/docs/api-reference/audio/tts/text-to-speech' target='_blank'>documentation</a>.</p>
    </div>
    """)

    with gr.Row():
            with gr.Column():
                gr.Markdown("#### Input")
                tts_text = gr.Textbox(
                    label="Text to Convert",
                    lines=5,
                    placeholder="Enter the text you want to convert to speech here."
                )
                
                gr.Markdown("#### Quick Examples")
                gr.Markdown("Click any example below to auto-fill the text:")
                
                with gr.Row():
                    tts_example_btn1 = gr.Button("πŸ‘‹ Greeting", size="sm")
                    tts_example_btn2 = gr.Button("πŸ“’ Announcement", size="sm")
                    tts_example_btn3 = gr.Button("πŸ“š Story", size="sm")
                
                with gr.Row():
                    tts_example_btn4 = gr.Button("🎡 Song Lyrics", size="sm")
                    tts_example_btn5 = gr.Button("πŸ“– Poem", size="sm")
                    tts_example_btn6 = gr.Button("πŸ’Ό Business", size="sm")
                
                with gr.Row():
                    tts_example_btn7 = gr.Button("🎭 Drama", size="sm")
                    tts_example_btn8 = gr.Button("πŸ”” Notification", size="sm")
                
                accent = gr.Textbox(
                    label="Voice Accent (Optional)",
                    placeholder="e.g., en-US-female-27, en-GB-male-2",
                    info="Default voice is used if left blank. Ignored if Voice Clone ID is provided."
                )
                
                voice_clone_id = gr.Textbox(
                    label="Voice Clone ID (Optional)",
                    placeholder="Enter a voice clone ID to use a custom voice.",
                    info="Overrides the accent if provided."
                )

            with gr.Column():
                gr.Markdown("#### Generated Audio")
                audio_output = gr.Audio(label="Speech Output")
                tts_status = gr.Textbox(label="Status", interactive=False, placeholder="Ready to generate speech...")

    tts_btn = gr.Button("Generate Speech", variant="primary")

    # Example functions to auto-fill text field
    def fill_tts_example_1():
        return "Hello! Welcome to our AI-powered text-to-speech system. This technology can convert any written text into natural-sounding speech."
    
    def fill_tts_example_2():
        return "Attention all passengers. Flight 1234 to New York is now boarding at gate 15. Please have your boarding pass ready."
    
    def fill_tts_example_3():
        return "Once upon a time, in a magical forest, there lived a wise old owl who loved to share stories with all the woodland creatures."
    
    def fill_tts_example_4():
        return "Twinkle twinkle little star, how I wonder what you are. Up above the world so high, like a diamond in the sky."
    
    def fill_tts_example_5():
        return "The road not taken by Robert Frost. Two roads diverged in a yellow wood, and sorry I could not travel both."
    
    def fill_tts_example_6():
        return "Thank you for your business. Your order has been confirmed and will be shipped within 2-3 business days."
    
    def fill_tts_example_7():
        return "To be or not to be, that is the question. Whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune."
    
    def fill_tts_example_8():
        return "You have a new message. Please check your inbox for important updates regarding your account."

    # Connect example buttons to auto-fill functions
    tts_example_btn1.click(fill_tts_example_1, outputs=[tts_text])
    tts_example_btn2.click(fill_tts_example_2, outputs=[tts_text])
    tts_example_btn3.click(fill_tts_example_3, outputs=[tts_text])
    tts_example_btn4.click(fill_tts_example_4, outputs=[tts_text])
    tts_example_btn5.click(fill_tts_example_5, outputs=[tts_text])
    tts_example_btn6.click(fill_tts_example_6, outputs=[tts_text])
    tts_example_btn7.click(fill_tts_example_7, outputs=[tts_text])
    tts_example_btn8.click(fill_tts_example_8, outputs=[tts_text])

    tts_btn.click(
        text_to_speech,
        inputs=[tts_text, accent, voice_clone_id],
        outputs=[audio_output, tts_status]
    )



demo.launch()