Spaces:
Sleeping
Sleeping
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TTS Offline - Verba</title> | |
| <style> | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| margin: 20px; | |
| background-color: #f4f7f6; | |
| color: #333; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| background-color: #fff; | |
| padding: 30px; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| width: 100%; | |
| max-width: 600px; | |
| } | |
| h1 { | |
| color: #007bff; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| } | |
| textarea, select, input[type="file"] { | |
| width: 100%; | |
| padding: 10px; | |
| margin-bottom: 20px; | |
| border: 1px solid #ccc; | |
| border-radius: 6px; | |
| box-sizing: border-box; | |
| } | |
| textarea { | |
| resize: vertical; | |
| min-height: 150px; | |
| } | |
| .file-upload-container { | |
| border: 2px dashed #ccc; | |
| padding: 20px; | |
| text-align: center; | |
| border-radius: 6px; | |
| margin-bottom: 20px; | |
| } | |
| button { | |
| background-color: #28a745; | |
| color: white; | |
| padding: 12px 20px; | |
| border: none; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| transition: background-color 0.3s ease; | |
| width: 100%; | |
| font-weight: bold; | |
| } | |
| button:hover { | |
| background-color: #218838; | |
| } | |
| #status-message { | |
| margin-top: 20px; | |
| padding: 10px; | |
| border-radius: 6px; | |
| text-align: center; | |
| display: none; | |
| } | |
| .status-loading { background-color: #ffc107; color: #333; } | |
| .status-error { background-color: #dc3545; color: white; } | |
| .status-success { background-color: #28a745; color: white; } | |
| #audio-player { | |
| width: 100%; | |
| margin-top: 20px; | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🗣️ Lecteur à Voix Haute (Offline TTS)</h1> | |
| <form id="tts-form"> | |
| <label for="text_input">Texte Manuel :</label> | |
| <textarea id="text_input" name="text_input" placeholder="Entrez le texte à lire ici..."></textarea> | |
| <div class="file-upload-container"> | |
| <label for="file_input">Ou Télécharger un Fichier (.txt seulement) :</label> | |
| <input type="file" id="file_input" name="file_input" accept=".txt"> | |
| </div> | |
| <label for="language_select">Langue de Lecture :</label> | |
| <select id="language_select" name="language_select"> | |
| {% for name, id in languages.items() %} | |
| <option value="{{ id }}">{{ name }}</option> | |
| {% endfor %} | |
| </select> | |
| <button type="submit" id="run-button"> | |
| <span id="run-icon">▶️</span> | |
| <span id="run-text">Lire le Texte</span> | |
| </button> | |
| </form> | |
| <div id="status-message"></div> | |
| <audio id="audio-player" controls></audio> | |
| </div> | |
| <script> | |
| document.getElementById('tts-form').addEventListener('submit', async function(event) { | |
| event.preventDefault(); | |
| const form = event.target; | |
| const formData = new FormData(form); | |
| const runButton = document.getElementById('run-button'); | |
| const statusMessage = document.getElementById('status-message'); | |
| const audioPlayer = document.getElementById('audio-player'); | |
| // --- Logique de validation et de nettoyage --- | |
| const textInput = formData.get('text_input').trim(); | |
| const fileInput = document.getElementById('file_input').files[0]; | |
| if (!textInput && !fileInput) { | |
| displayStatus('Veuillez entrer du texte ou sélectionner un fichier.', 'error'); | |
| return; | |
| } | |
| if (textInput && fileInput) { | |
| displayStatus('Veuillez choisir entre le texte manuel ET le fichier, pas les deux.', 'error'); | |
| return; | |
| } | |
| // --- Affichage du statut de chargement --- | |
| runButton.disabled = true; | |
| document.getElementById('run-text').textContent = 'Génération de l\'Audio...'; | |
| document.getElementById('run-icon').textContent = '⏳'; | |
| displayStatus('Génération de l\'audio en cours. Veuillez patienter...', 'loading'); | |
| audioPlayer.style.display = 'none'; | |
| audioPlayer.removeAttribute('src'); | |
| try { | |
| // --- Appel Asynchrone (Fetch) --- | |
| const response = await fetch('/read', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (response.ok) { | |
| // La réponse est un fichier audio (audio/wav) | |
| const audioBlob = await response.blob(); | |
| const audioUrl = URL.createObjectURL(audioBlob); | |
| // Configuration et lecture | |
| audioPlayer.src = audioUrl; | |
| audioPlayer.style.display = 'block'; | |
| audioPlayer.play(); | |
| displayStatus('Lecture de l\'audio démarrée !', 'success'); | |
| } else { | |
| // Erreur du serveur (400, 500, etc.) | |
| const errorText = await response.text(); | |
| displayStatus('Erreur (' + response.status + '): ' + errorText, 'error'); | |
| } | |
| } catch (error) { | |
| // Erreur réseau ou autre | |
| console.error('Erreur Fetch:', error); | |
| displayStatus('Erreur de connexion ou du serveur.', 'error'); | |
| } finally { | |
| // --- Réinitialisation du bouton --- | |
| runButton.disabled = false; | |
| document.getElementById('run-text').textContent = 'Lire le Texte'; | |
| document.getElementById('run-icon').textContent = '▶️'; | |
| } | |
| }); | |
| function displayStatus(message, type) { | |
| const statusMessage = document.getElementById('status-message'); | |
| statusMessage.textContent = message; | |
| statusMessage.className = ''; // Réinitialiser les classes | |
| statusMessage.classList.add('status-' + type); | |
| statusMessage.style.display = 'block'; | |
| } | |
| </script> | |
| </body> | |
| </html> |