Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ttsfm</title> | |
| <link rel="stylesheet" href="styles.css"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <div class="content-wrapper"> | |
| <!-- Header Section --> | |
| <header class="main-header"> | |
| <div class="header-top"> | |
| <h1>ttsfm</h1> | |
| <a href="https://github.com/dbccccccc/ttsfm" target="_blank" class="github-link"> | |
| <i class="fab fa-github"></i> | |
| <span>GitHub</span> | |
| </a> | |
| </div> | |
| <p class="subtitle">Text-to-Speech API with Multiple Voice Options</p> | |
| <div class="header-bottom"> | |
| <div class="version-badge"> | |
| <span>Version: <strong id="version">1.3.0</strong></span> | |
| </div> | |
| <div class="language-selector"> | |
| <button class="lang-btn active" data-lang="en">English</button> | |
| <a href="index_zh.html" class="lang-btn">中文</a> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Disclaimer Section --> | |
| <section class="content-section disclaimer-notice"> | |
| <div class="disclaimer-container"> | |
| <div class="disclaimer-icon"> | |
| <i class="fas fa-info-circle"></i> | |
| </div> | |
| <div class="disclaimer-content"> | |
| <h2>Disclaimer</h2> | |
| <p>This project is for learning & testing purposes only. For production use, please use the official OpenAI TTS service at <a href="https://platform.openai.com/docs/guides/audio" target="_blank">https://platform.openai.com/docs/guides/audio</a>.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Status Section --> | |
| <section class="content-section status-section"> | |
| <h2>Service Status</h2> | |
| <div class="status-container"> | |
| <div class="status-card"> | |
| <div class="status-header"> | |
| <h3>Queue Status</h3> | |
| <div class="status-indicator" id="status-indicator"></div> | |
| </div> | |
| <div class="queue-stats"> | |
| <div class="stat-item"> | |
| <span class="stat-label">Active Requests:</span> | |
| <span class="stat-value" id="queue-size">0</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span class="stat-label">Maximum Capacity:</span> | |
| <span class="stat-value" id="max-queue-size">-</span> | |
| </div> | |
| </div> | |
| <div class="queue-progress-container"> | |
| <div class="queue-progress-bar" id="queue-progress-bar"></div> | |
| </div> | |
| <div class="queue-load-text" id="queue-load-text">No Load</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Playground Section --> | |
| <section class="content-section playground-section"> | |
| <h2>Try It Out</h2> | |
| <div class="playground-container"> | |
| <div class="playground-form"> | |
| <div class="form-group"> | |
| <label for="playground-text">Text to Convert</label> | |
| <textarea id="playground-text" rows="4" placeholder="Enter the text you want to convert to speech..."></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label for="playground-instructions">Instructions</label> | |
| <textarea id="playground-instructions" rows="2" placeholder="e.g., Speak in a cheerful and upbeat tone"></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label for="playground-voice">Voice</label> | |
| <select id="playground-voice"> | |
| <option value="alloy">Alloy</option> | |
| <option value="ash">Ash</option> | |
| <option value="ballad">Ballad</option> | |
| <option value="coral">Coral</option> | |
| <option value="echo">Echo</option> | |
| <option value="fable">Fable</option> | |
| <option value="onyx">Onyx</option> | |
| <option value="nova">Nova</option> | |
| <option value="sage">Sage</option> | |
| <option value="shimmer">Shimmer</option> | |
| <option value="verse">Verse</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="playground-format">Response Format</label> | |
| <select id="playground-format"> | |
| <option value="mp3">MP3</option> | |
| <option value="opus">Opus</option> | |
| <option value="aac">AAC</option> | |
| <option value="flac">FLAC</option> | |
| <option value="wav">WAV</option> | |
| <option value="pcm">PCM</option> | |
| </select> | |
| </div> | |
| <button id="playground-submit" class="playground-button"> | |
| <i class="fas fa-play"></i> Generate Speech | |
| </button> | |
| </div> | |
| <div class="playground-output"> | |
| <div class="audio-section"> | |
| <h3>Voice Preview</h3> | |
| <div id="preview-audio" class="audio-player"></div> | |
| </div> | |
| <div class="audio-section"> | |
| <h3>Generated Result</h3> | |
| <div id="playground-status" class="playground-status"></div> | |
| <div id="playground-audio" class="audio-player"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Main Content --> | |
| <main class="main-content"> | |
| <!-- Quick Start --> | |
| <section class="content-section"> | |
| <h2>Quick Start</h2> | |
| <p>Choose your preferred programming language to get started with the API:</p> | |
| <!-- Python Example --> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="code-language">Python</span> | |
| <button class="copy-button" onclick="copyCode(this)"> | |
| <i class="fas fa-copy"></i> | |
| </button> | |
| </div> | |
| <pre><code class="language-python"> | |
| import requests | |
| import os | |
| def generate_speech(text, voice="alloy", format="mp3", instructions=None): | |
| url = "https://ttsapi.site/v1/audio/speech" | |
| headers = { | |
| "Content-Type": "application/json" | |
| } | |
| data = { | |
| "input": text, | |
| "voice": voice, | |
| "response_format": format | |
| } | |
| # Add instructions if provided | |
| if instructions: | |
| data["instructions"] = instructions | |
| response = requests.post(url, json=data, headers=headers) | |
| if response.status_code == 200: | |
| # Get the appropriate file extension based on format | |
| ext = format.lower() | |
| filename = f"output.{ext}" | |
| # Save the audio file | |
| with open(filename, "wb") as f: | |
| f.write(response.content) | |
| print(f"Audio saved as {filename}") | |
| return filename | |
| else: | |
| error = response.json() | |
| print(f"Error: {response.status_code}, {error}") | |
| return None | |
| # Example usage | |
| text = "Hello, this is a test." | |
| voice = "alloy" | |
| format = "mp3" # Supported formats: mp3, opus, aac, flac, wav, pcm | |
| instructions = "Speak in a cheerful and upbeat tone." | |
| # Generate speech with default format (MP3) | |
| generate_speech(text, voice, instructions=instructions) | |
| # Generate speech in WAV format | |
| generate_speech(text, voice, format="wav", instructions=instructions)</code></pre> | |
| </div> | |
| <!-- JavaScript Example --> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="code-language">JavaScript</span> | |
| <button class="copy-button" onclick="copyCode(this)"> | |
| <i class="fas fa-copy"></i> | |
| </button> | |
| </div> | |
| <pre><code class="language-javascript">async function generateSpeech(text, voice = 'alloy', format = 'mp3', instructions = null) { | |
| const response = await fetch('https://ttsapi.site/v1/audio/speech', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| input: text, | |
| voice: voice, | |
| response_format: format, | |
| ...(instructions && { instructions }) | |
| }) | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| // Create audio element for playback | |
| const audio = new Audio(URL.createObjectURL(blob)); | |
| audio.play(); | |
| // Optional: Download the file | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `output.${format}`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| return blob; | |
| } else { | |
| const error = await response.json(); | |
| console.error('Error:', error); | |
| throw error; | |
| } | |
| } | |
| // Example usage | |
| const text = 'Hello, this is a test.'; | |
| const voice = 'alloy'; | |
| const format = 'mp3'; // Supported formats: mp3, opus, aac, flac, wav, pcm | |
| const instructions = 'Speak in a cheerful and upbeat tone.'; | |
| // Generate speech with default format (MP3) | |
| generateSpeech(text, voice, undefined, instructions); | |
| // Generate speech in WAV format | |
| generateSpeech(text, voice, 'wav', instructions);</code></pre> | |
| </div> | |
| </section> | |
| <!-- Available Voices --> | |
| <section class="content-section"> | |
| <h2>Available Voices</h2> | |
| <div class="voice-list"> | |
| <span class="voice-name">alloy</span> | |
| <span class="voice-name">ash</span> | |
| <span class="voice-name">ballad</span> | |
| <span class="voice-name">coral</span> | |
| <span class="voice-name">echo</span> | |
| <span class="voice-name">fable</span> | |
| <span class="voice-name">onyx</span> | |
| <span class="voice-name">nova</span> | |
| <span class="voice-name">sage</span> | |
| <span class="voice-name">shimmer</span> | |
| <span class="voice-name">verse</span> | |
| </div> | |
| </section> | |
| <!-- API Reference --> | |
| <section class="content-section"> | |
| <h2>API Reference</h2> | |
| <div class="api-endpoint"> | |
| <h3>Generate Speech (OpenAI Compatible)</h3> | |
| <pre><code class="language-http">POST /v1/audio/speech</code></pre> | |
| <div class="request-body"> | |
| <h4>Request Parameters</h4> | |
| <table class="params-table"> | |
| <tr> | |
| <th>Parameter</th> | |
| <th>Type</th> | |
| <th>Required</th> | |
| <th>Description</th> | |
| </tr> | |
| <tr> | |
| <td>input</td> | |
| <td>string</td> | |
| <td>Yes</td> | |
| <td>The text to convert to speech</td> | |
| </tr> | |
| <tr> | |
| <td>voice</td> | |
| <td>string</td> | |
| <td>Yes</td> | |
| <td>The voice to use (see Available Voices)</td> | |
| </tr> | |
| <tr> | |
| <td class="partial-param">instructions</td> | |
| <td>string</td> | |
| <td>No</td> | |
| <td><em>Mapped to "prompt" parameter when sent to the backend service. Can be used to guide voice emotion or style.</em></td> | |
| </tr> | |
| <tr> | |
| <td class="partial-param">response_format</td> | |
| <td>string</td> | |
| <td>No</td> | |
| <td>The format of the audio response. Supported formats: mp3, opus, aac, flac, wav, pcm. Defaults to mp3.</td> | |
| </tr> | |
| <tr> | |
| <td class="compat-param">model</td> | |
| <td>string</td> | |
| <td>No</td> | |
| <td><em>OpenAI compatibility only - completely ignored.</em></td> | |
| </tr> | |
| <tr> | |
| <td class="compat-param">speed</td> | |
| <td>number</td> | |
| <td>No</td> | |
| <td><em>OpenAI compatibility only - completely ignored.</em></td> | |
| </tr> | |
| </table> | |
| <div class="compatibility-notice"> | |
| <p><strong>Note:</strong> Parameters in <span class="compat-inline">gray</span> are completely ignored by the service or may cause misleading behavior. Only <code>input</code>, <code>voice</code>, <code>response_format</code> and <code>instructions</code> affect the actual TTS output.</p> | |
| </div> | |
| <!-- Instructions Parameter Details --> | |
| <div class="parameter-details"> | |
| <h4>How the Instructions Parameter Works</h4> | |
| <p>The <code>instructions</code> parameter is mapped to a <code>prompt</code> parameter when sent to the backend service. It can be used to guide the voice emotion, tone, or style. Some examples of effective instructions:</p> | |
| <ul class="examples-list"> | |
| <li><strong>Emotional guidance:</strong> "Speak in a happy and excited tone."</li> | |
| <li><strong>Character impersonation:</strong> "Speak like a wise old wizard."</li> | |
| <li><strong>Contextual hints:</strong> "This is being read to a child, speak gently."</li> | |
| <li><strong>Reading style:</strong> "Read this as a news broadcast."</li> | |
| </ul> | |
| <div class="tip-box"> | |
| <p><strong>Tip:</strong> Keep instructions clear and concise. Overly complex instructions may not be interpreted correctly.</p> | |
| </div> | |
| </div> | |
| <h4>Response Format</h4> | |
| <p>The API returns audio in the requested format with the following headers:</p> | |
| <ul> | |
| <li><code>Content-Type</code>: Based on the requested format (e.g., "audio/mpeg" for MP3)</li> | |
| <li><code>Access-Control-Allow-Origin</code>: "*" (CORS enabled)</li> | |
| </ul> | |
| <h4>Error Responses</h4> | |
| <table class="error-table"> | |
| <tr> | |
| <th>Status Code</th> | |
| <th>Description</th> | |
| </tr> | |
| <tr> | |
| <td>400</td> | |
| <td>Missing required parameters (input or voice)</td> | |
| </tr> | |
| <tr> | |
| <td>429</td> | |
| <td>Rate limit exceeded or queue is full. Includes Retry-After header when rate limited.</td> | |
| </tr> | |
| <tr> | |
| <td>500</td> | |
| <td>Internal server error</td> | |
| </tr> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Queue System --> | |
| <div class="api-endpoint"> | |
| <h3>Queue System</h3> | |
| <p>The API uses a queue system to handle multiple requests efficiently:</p> | |
| <ul> | |
| <li>Maximum queue size: Configurable via <code>MAX_QUEUE_SIZE</code> environment variable (default: 100 requests)</li> | |
| <li>Requests are processed in FIFO (First In, First Out) order</li> | |
| <li>Rate limiting: Configurable via <code>RATE_LIMIT_REQUESTS</code> and <code>RATE_LIMIT_WINDOW</code> environment variables (default: 30 requests per 60 seconds per IP address)</li> | |
| <li>Queue status can be monitored via the <code>/api/queue-size</code> endpoint</li> | |
| <li>Queue status updates every 2 seconds in the web interface</li> | |
| <li>Visual indicators show queue load (Low/Medium/High) based on utilization</li> | |
| </ul> | |
| <h4>Queue Status Endpoint</h4> | |
| <pre><code class="language-http">GET /api/queue-size</code></pre> | |
| <p>Returns JSON with queue information:</p> | |
| <pre><code class="language-json">{ | |
| "queue_size": 0, // Current number of requests in queue | |
| "max_queue_size": 100 // Maximum queue capacity | |
| }</code></pre> | |
| <div class="response-codes"> | |
| <h4>Response Status Codes</h4> | |
| <ul> | |
| <li><code>200</code> - Success</li> | |
| <li><code>429</code> - Queue is full or rate limit exceeded</li> | |
| <li><code>500</code> - Server error</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| </div> | |
| <script src="script.js"></script> | |
| </body> | |
| </html> |