{"id":11859,"date":"2026-02-11T11:52:45","date_gmt":"2026-02-11T10:52:45","guid":{"rendered":"https:\/\/spgoo.org\/?page_id=11859"},"modified":"2026-02-11T14:57:37","modified_gmt":"2026-02-11T13:57:37","slug":"comparateur-melspectrogrammes","status":"publish","type":"page","link":"https:\/\/spgoo.org\/?page_id=11859","title":{"rendered":"Comparateur Melspectrogrammes"},"content":{"rendered":"    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n            box-sizing: border-box;\r\n        }\r\n\r\n\/*        body {\r\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\r\n            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);\r\n            min-height: 100vh;\r\n            padding: 20px;\r\n        }*\/\r\n\r\n        .containerComp {\r\n            max-width: 1800px;\r\n            margin: 0 auto;\r\n        }\r\n\r\n        h1 {\r\n            color: white;\r\n            text-align: center;\r\n            margin-bottom: 10px;\r\n            font-size: 2.8em;\r\n            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\r\n        }\r\n\r\n        .subtitle {\r\n            color: rgba(255,255,255,0.95);\r\n            text-align: center;\r\n            margin-bottom: 35px;\r\n            font-size: 1.2em;\r\n        }\r\n\r\n        .card {\r\n            background: white;\r\n            padding: 35px;\r\n            border-radius: 18px;\r\n            box-shadow: 0 10px 40px rgba(0,0,0,0.3);\r\n            margin-bottom: 25px;\r\n        }\r\n\r\n        .card h2 {\r\n            color: #2a5298;\r\n            margin-bottom: 25px;\r\n            font-size: 1.6em;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 12px;\r\n            padding-bottom: 15px;\r\n            border-bottom: 3px solid #f0f0f0;\r\n        }\r\n\r\n        .text-input {\r\n            width: 100%;\r\n            min-height: 120px;\r\n            padding: 18px;\r\n            border: 2px solid #e0e0e0;\r\n            border-radius: 12px;\r\n            font-size: 1.15em;\r\n            font-family: 'Georgia', serif;\r\n            line-height: 1.8;\r\n            resize: vertical;\r\n            margin-bottom: 20px;\r\n        }\r\n\r\n        .btn {\r\n            padding: 14px 32px;\r\n            border: none;\r\n            border-radius: 28px;\r\n            font-size: 1.05em;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n            box-shadow: 0 5px 18px rgba(0,0,0,0.2);\r\n            font-weight: 600;\r\n            margin-right: 12px;\r\n            margin-bottom: 12px;\r\n        }\r\n\r\n        .btn-primary {\r\n            background: linear-gradient(135deg, #2a5298, #7e22ce);\r\n            color: white;\r\n        }\r\n\r\n        .btn-primary:hover:not(:disabled) {\r\n            transform: translateY(-3px);\r\n        }\r\n\r\n        .btn-secondary {\r\n            background: white;\r\n            color: #2a5298;\r\n            border: 2px solid #2a5298;\r\n        }\r\n\r\n        .btn-danger {\r\n            background: linear-gradient(135deg, #e74c3c, #c0392b);\r\n            color: white;\r\n        }\r\n\r\n        .btn:disabled {\r\n            opacity: 0.6;\r\n            cursor: not-allowed;\r\n        }\r\n\r\n        .main-grid {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 25px;\r\n            margin-bottom: 25px;\r\n        }\r\n\r\n        .wavesurfer-container {\r\n            background: #000;\r\n            padding: 25px;\r\n            border-radius: 12px;\r\n            margin: 20px 0;\r\n        }\r\n\r\n        .playback-controls {\r\n            background: #f8f9fa;\r\n            padding: 18px;\r\n            border-radius: 10px;\r\n            margin-top: 15px;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 12px;\r\n            flex-wrap: wrap;\r\n        }\r\n\r\n        .playback-btn {\r\n            padding: 10px 20px;\r\n            border: none;\r\n            border-radius: 20px;\r\n            background: linear-gradient(135deg, #2a5298, #7e22ce);\r\n            color: white;\r\n            cursor: pointer;\r\n            font-weight: 600;\r\n        }\r\n\r\n        .alert {\r\n            padding: 18px 24px;\r\n            border-radius: 12px;\r\n            margin-bottom: 25px;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 15px;\r\n        }\r\n\r\n        .alert-info {\r\n            background: linear-gradient(135deg, #d1ecf1, #bee5eb);\r\n            color: #0c5460;\r\n            border-left: 5px solid #17a2b8;\r\n        }\r\n\r\n        .alert-success {\r\n            background: linear-gradient(135deg, #d4edda, #c3e6cb);\r\n            color: #155724;\r\n            border-left: 5px solid #28a745;\r\n        }\r\n\r\n        .alert-error {\r\n            background: linear-gradient(135deg, #f8d7da, #fcc);\r\n            color: #721c24;\r\n            border-left: 5px solid #dc3545;\r\n        }\r\n\r\n        .status-badge {\r\n            display: inline-block;\r\n            padding: 6px 14px;\r\n            border-radius: 15px;\r\n            font-size: 0.85em;\r\n            font-weight: 600;\r\n            margin-left: 10px;\r\n        }\r\n\r\n        .status-ready {\r\n            background: #d4edda;\r\n            color: #155724;\r\n        }\r\n\r\n        .status-processing {\r\n            background: #fff3cd;\r\n            color: #856404;\r\n        }\r\n\r\n        .recording-indicator {\r\n            display: inline-flex;\r\n            align-items: center;\r\n            gap: 10px;\r\n            padding: 10px 20px;\r\n            background: linear-gradient(135deg, #fee, #fcc);\r\n            border-radius: 25px;\r\n            color: #c0392b;\r\n            font-weight: 700;\r\n            margin-left: 12px;\r\n        }\r\n\r\n        .recording-dot {\r\n            width: 14px;\r\n            height: 14px;\r\n            background: #e74c3c;\r\n            border-radius: 50%;\r\n            animation: pulse 1.5s ease-in-out infinite;\r\n        }\r\n\r\n        @keyframes pulse {\r\n            0%, 100% { opacity: 1; }\r\n            50% { opacity: 0.6; }\r\n        }\r\n\r\n        .stats-grid {\r\n            display: grid;\r\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\r\n            gap: 18px;\r\n            margin: 25px 0;\r\n        }\r\n\r\n        .stat-box {\r\n            background: linear-gradient(135deg, #2a5298, #7e22ce);\r\n            color: white;\r\n            padding: 25px;\r\n            border-radius: 14px;\r\n            text-align: center;\r\n        }\r\n\r\n        .stat-number {\r\n            font-size: 2.5em;\r\n            font-weight: bold;\r\n            margin-bottom: 8px;\r\n        }\r\n\r\n        .settings-grid {\r\n            display: grid;\r\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\r\n            gap: 15px;\r\n            margin-bottom: 20px;\r\n        }\r\n\r\n        .setting-item label {\r\n            display: block;\r\n            margin-bottom: 8px;\r\n            color: #555;\r\n            font-weight: 600;\r\n        }\r\n\r\n        .setting-item select {\r\n            width: 100%;\r\n            padding: 10px;\r\n            border: 2px solid #e0e0e0;\r\n            border-radius: 8px;\r\n        }\r\n\r\n        @media (max-width: 1200px) {\r\n            .main-grid { grid-template-columns: 1fr; }\r\n        }\r\n    <\/style>\r\n    <div class=\"containerComp\">\r\n        <h1>\ud83c\udfb5 Comparateur TTS avec API FastAPI<\/h1>\r\n        <p class=\"subtitle\">G\u00e9n\u00e9ration TTS via API + Analyse Melspectrogramme<\/p>\r\n\r\n        <div class=\"card\">\r\n            <h2>\ud83d\udcdd Configuration<\/h2>\r\n            \r\n            <div class=\"alert alert-info\">\r\n                <span style=\"font-size: 1.5em;\">\u2139\ufe0f<\/span>\r\n                <div>\r\n                    <strong>Pr\u00e9requis :<\/strong> Le service FastAPI doit \u00eatre d\u00e9marr\u00e9.<br>\r\n                    Commande : <code style=\"background: #fff; padding: 3px 8px; border-radius: 4px;\">python tts_service.py<\/code>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <textarea id=\"textInput\" class=\"text-input\" placeholder=\"Entrez le texte...\">Les scientifiques ont r\u00e9cemment d\u00e9couvert une tourbi\u00e8re remarquable dans le Cher.<\/textarea>\r\n            \r\n            <div class=\"settings-grid\">\r\n                <div class=\"setting-item\">\r\n                    <label>\ud83c\udf0d Langue<\/label>\r\n                    <select id=\"langSelect\">\r\n                        <option value=\"fr\" selected>Fran\u00e7ais<\/option>\r\n                        <option value=\"en\">Anglais<\/option>\r\n                        <option value=\"es\">Espagnol<\/option>\r\n                        <option value=\"de\">Allemand<\/option>\r\n                    <\/select>\r\n                <\/div>\r\n                <div class=\"setting-item\">\r\n                    <label>\ud83d\udccd Accent<\/label>\r\n                    <select id=\"tldSelect\">\r\n                        <option value=\"fr\" selected>France<\/option>\r\n                        <option value=\"com\">International<\/option>\r\n                        <option value=\"ca\">Canada<\/option>\r\n                    <\/select>\r\n                <\/div>\r\n                <!-- <div class=\"setting-item\">\r\n                    <label>\ud83d\udd17 URL API<\/label>\r\n                    <input disabled type=\"text\" id=\"apiUrl\" value=\"\/personnalisation\/\" \r\n                           style=\"width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px;\">\r\n                <\/div> -->\t\t\t\t\r\n            <\/div>\r\n\r\n            <button class=\"btn btn-primary\" onclick=\"generateTTSFromAPI()\" id=\"generateBtn\">\r\n                \ud83d\udd0a G\u00e9n\u00e9rer TTS via API\r\n            <\/button>\r\n\r\n            <div id=\"alertContainer\"><\/div>\r\n        <\/div>\r\n\r\n        <div class=\"main-grid\">\r\n            <!-- Panneau TTS -->\r\n            <div class=\"card\">\r\n                <h2>\ud83e\udd16 TTS (via API)\r\n                    <span id=\"ttsStatus\" class=\"status-badge\" style=\"display: none;\"><\/span>\r\n                <\/h2>\r\n\r\n                <div id=\"ttsWavesurferContainer\" style=\"display: none;\">\r\n                    <div class=\"wavesurfer-container\">\r\n                        <h3 style=\"color: #fff; margin-bottom: 15px;\">Forme d&#8217;onde<\/h3>\r\n                        <div id=\"waveform-tts\"><\/div>\r\n                        \r\n                        <h3 style=\"color: #fff; margin: 20px 0 15px 0;\">Melspectrogramme<\/h3>\r\n                        <div id=\"spectrogram-tts\"><\/div>\r\n                    <\/div>\r\n                    \r\n                    <div class=\"playback-controls\">\r\n                        <button class=\"playback-btn\" onclick=\"playTTS()\">\u25b6\ufe0f<\/button>\r\n                        <button class=\"playback-btn\" onclick=\"pauseTTS()\">\u23f8\ufe0f<\/button>\r\n                        <button class=\"playback-btn\" onclick=\"stopTTS()\">\u23f9\ufe0f<\/button>\r\n                        <button class=\"playback-btn\" onclick=\"downloadTTS()\">\ud83d\udce5 T\u00e9l\u00e9charger<\/button>\r\n                    <\/div>\r\n                <\/div>\r\n            <\/div>\r\n\r\n\r\n            <!-- Panneau R\u00e9el -->\r\n            <div class=\"card\">\r\n                <h2>\ud83c\udf99\ufe0f Locuteur R\u00e9el\r\n                    <span id=\"realStatus\" class=\"status-badge\" style=\"display: none;\"><\/span>\r\n                <\/h2>\r\n                \r\n                <button class=\"btn btn-primary\" id=\"recordBtn\" onclick=\"toggleRecording()\">\r\n                    \ud83c\udf99\ufe0f Enregistrer\r\n                <\/button>\r\n                <button class=\"btn btn-secondary\" onclick=\"document.getElementById('audioUpload').click()\">\r\n                    \ud83d\udcc1 Charger fichier\r\n                <\/button>\r\n                <input type=\"file\" id=\"audioUpload\" accept=\"audio\/*\" style=\"display: none;\" onchange=\"handleAudioUpload(event)\">\r\n                \r\n                <span id=\"recordingIndicator\" style=\"display: none;\" class=\"recording-indicator\">\r\n                    <span class=\"recording-dot\"><\/span>\r\n                    Enregistrement&#8230;\r\n                <\/span>\r\n\r\n                <div id=\"realWavesurferContainer\" style=\"display: none;\">\r\n                    <div class=\"wavesurfer-container\">\r\n                        <h3 style=\"color: #fff; margin-bottom: 15px;\">Forme d&#8217;onde<\/h3>\r\n                        <div id=\"waveform-real\"><\/div>\r\n                        \r\n                        <h3 style=\"color: #fff; margin: 20px 0 15px 0;\">Melspectrogramme<\/h3>\r\n                        <div id=\"spectrogram-real\"><\/div>\r\n                    <\/div>\r\n                    \r\n                    <div class=\"playback-controls\">\r\n                        <button class=\"playback-btn\" onclick=\"playReal()\">\u25b6\ufe0f<\/button>\r\n                        <button class=\"playback-btn\" onclick=\"pauseReal()\">\u23f8\ufe0f<\/button>\r\n                        <button class=\"playback-btn\" onclick=\"stopReal()\">\u23f9\ufe0f<\/button>\r\n                    <\/div>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <!-- Comparaison -->\r\n        <div class=\"card\" id=\"comparisonCard\" style=\"display: none;\">\r\n            <h2>\ud83d\udcca Analyse Comparative<\/h2>\r\n            \r\n            <div class=\"alert alert-success\">\r\n                <span style=\"font-size: 1.5em;\">\u2705<\/span>\r\n                <div>Comparaison des melspectrogrammes TTS vs R\u00e9el<\/div>\r\n            <\/div>\r\n            \r\n            <div class=\"stats-grid\">\r\n                <div class=\"stat-box\">\r\n\r\n                    <div class=\"stat-number\" id=\"sim\">&#8212;<\/div>\r\n                    <div style=\"font-size: 0.95em;\">Similarit\u00e9<\/div>\r\n                <\/div>\r\n                <div class=\"stat-box\">\r\n                    <div class=\"stat-number\" id=\"diff\">&#8212;<\/div>\r\n                    <div style=\"font-size: 0.95em;\">\u00c9cart (s)<\/div>\r\n                <\/div>\r\n                <div class=\"stat-box\">\r\n                    <div class=\"stat-number\" id=\"dur1\">&#8212;<\/div>\r\n                    <div style=\"font-size: 0.95em;\">Dur\u00e9e TTS<\/div>\r\n                <\/div>\r\n                <div class=\"stat-box\">\r\n                    <div class=\"stat-number\" id=\"dur2\">&#8212;<\/div>\r\n                    <div style=\"font-size: 0.95em;\">Dur\u00e9e R\u00e9el<\/div>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script src=\"https:\/\/unpkg.com\/wavesurfer.js@7\"><\/script>\r\n    <script src=\"https:\/\/unpkg.com\/wavesurfer.js@7\/dist\/plugins\/spectrogram.min.js\"><\/script>\r\n\r\n    <script>\r\n        let wavesurferTTS = null;\r\n        let wavesurferReal = null;\r\n        let mediaRecorder, audioChunks = [], isRecording = false;\r\n        let currentTTSBlob = null;\r\n\r\n        \/\/ G\u00e9n\u00e9rer TTS via API\r\n        async function generateTTSFromAPI() {\r\n            const apiUrl = \"\/personnalisation\/\"; \/\/ document.getElementById('apiUrl').value.trim();\r\n            const text = document.getElementById('textInput').value.trim();\r\n            const lang = document.getElementById('langSelect').value;\r\n            const tld = document.getElementById('tldSelect').value;\r\n\r\n            if (!text) {\r\n                showAlert('Veuillez entrer un texte', 'error');\r\n                return;\r\n            }\r\n\r\n            const btn = document.getElementById('generateBtn');\r\n            btn.disabled = true;\r\n            btn.textContent = '\u23f3 G\u00e9n\u00e9ration...';\r\n            updateStatus('tts', 'processing', 'G\u00e9n\u00e9ration...');\r\n\r\n            try {\r\n                const response = await fetch(`${apiUrl}get_melspectrogrammenostream.php`, {\r\n                    method: 'POST',\r\n                    headers: { 'Content-Type': 'application\/json' },\r\n                    body: JSON.stringify({ text, lang, slow: false, tld })\r\n                })\r\n\t\t\t\t\/\/.then(r => r.blob())\r\n\t\t\t\t\/\/.then(blob => {\r\n                \t\r\n\t\t\t\tconst data=await response.json();\r\n\t\t\t\tconsole.log(JSON.parse(data.detail));\r\n\t\t\t\tconst filename=JSON.parse(data.detail).filename;\r\n\t\t\t\t\/*const audioBlob = await response.blob();\r\n\t\t\t\tconsole.log('\u2705 Taille:', audioBlob.size);\t\t\r\n                currentTTSBlob = audioBlob;\r\n                await loadTTS(audioBlob);*\/\r\n\t\t\t\tawait loadTTS(filename);\r\n                showAlert('\u2705 TTS g\u00e9n\u00e9r\u00e9 avec succ\u00e8s !', 'success');\r\n                updateStatus('tts', 'ready', '\u2713 Pr\u00eat');\r\n\t\t\t\t\t\r\n\t\t\t\t\/\/}\r\n                \/*if (!response.ok) {\r\n                    const error = await response.json();\r\n                    throw new Error(error.detail || 'Erreur API');\r\n                }*\/\r\n\t\t\t\t\/\/)\r\n            } catch (error) {\r\n                console.error('Erreur:', error);\r\n                showAlert(`\u274c Erreur: ${error.message}`, 'error');\r\n                updateStatus('tts', 'processing', 'Erreur');\r\n            } finally {\r\n                btn.disabled = false;\r\n                btn.textContent = '\ud83d\udd0a G\u00e9n\u00e9rer TTS via API';\r\n            }\r\n        }\r\n\r\n        async function loadTTS(audioBlob) {\r\n            const url = \"\/personnalisation\/TTS\/\"+ audioBlob;\r\n            \r\n            if (wavesurferTTS) wavesurferTTS.destroy();\r\n\r\n            wavesurferTTS = WaveSurfer.create({\r\n                container: '#waveform-tts',\r\n                waveColor: '#4facfe',\r\n                progressColor: '#00f2fe',\r\n                cursorColor: '#fff',\r\n                barWidth: 2,\r\n                height: 100,\r\n                normalize: true,\r\n                plugins: [\r\n                    WaveSurfer.Spectrogram.create({\r\n                        container: '#spectrogram-tts',\r\n                        labels: true,\r\n                        height: 300,\r\n                        colorMap: getColormap()\r\n                    })\r\n                ]\r\n            });\r\n            wavesurferTTS.on('ready', () => {\r\n                document.getElementById('ttsWavesurferContainer').style.display = 'block';\r\n                if (wavesurferReal) compareAudios();\r\n            });\r\n\r\n\t\t\tconst a = document.createElement('a');\r\n    \t\ta.href = url;\r\n    \t\ta.download = 'test.mp3';\r\n    \t\ta.click();\r\n\r\n            \/\/ await wavesurferTTS.load(url);\r\n\t\t\t\/\/await wavesurferTTS.load(url);\r\n\t\t\tawait wavesurferTTS.load(url);\r\n\t\t\t\r\n        }\r\n\r\n        \/\/ Enregistrement micro\r\n        async function toggleRecording() {\r\n            isRecording ? stopRecording() : startRecording();\r\n        }\r\n\r\n        async function startRecording() {\r\n            try {\r\n                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\r\n                mediaRecorder = new MediaRecorder(stream);\r\n                audioChunks = [];\r\n\r\n                mediaRecorder.ondataavailable = e => audioChunks.push(e.data);\r\n                mediaRecorder.onstop = async () => {\r\n                    await loadReal(new Blob(audioChunks, { type: 'audio\/webm' }));\r\n                    stream.getTracks().forEach(t => t.stop());\r\n                };\r\n\r\n                mediaRecorder.start();\r\n                isRecording = true;\r\n                document.getElementById('recordBtn').textContent = '\u23f9\ufe0f Arr\u00eater';\r\n                document.getElementById('recordBtn').classList.replace('btn-primary', 'btn-danger');\r\n                document.getElementById('recordingIndicator').style.display = 'inline-flex';\r\n            } catch (err) {\r\n                showAlert('Erreur microphone', 'error');\r\n            }\r\n        }\r\n\r\n        function stopRecording() {\r\n            if (mediaRecorder && mediaRecorder.state !== 'inactive') {\r\n                mediaRecorder.stop();\r\n                isRecording = false;\r\n                document.getElementById('recordBtn').textContent = '\ud83c\udf99\ufe0f Enregistrer';\r\n                document.getElementById('recordBtn').classList.replace('btn-danger', 'btn-primary');\r\n                document.getElementById('recordingIndicator').style.display = 'none';\r\n            }\r\n        }\r\n\r\n        async function handleAudioUpload(event) {\r\n            const file = event.target.files[0];\r\n            if (!file) return;\r\n            await loadReal(file);\r\n        }\r\n\r\n        async function loadReal(audioBlob) {\r\n            const url = URL.createObjectURL(audioBlob);\r\n            \r\n            if (wavesurferReal) wavesurferReal.destroy();\r\n\r\n            wavesurferReal = WaveSurfer.create({\r\n                container: '#waveform-real',\r\n                waveColor: '#f093fb',\r\n                progressColor: '#f5576c',\r\n                cursorColor: '#fff',\r\n                barWidth: 2,\r\n                height: 100,\r\n                normalize: true,\r\n                plugins: [\r\n                    WaveSurfer.Spectrogram.create({\r\n                        container: '#spectrogram-real',\r\n                        labels: true,\r\n                        height: 300,\r\n                        colorMap: getColormap()\r\n                    })\r\n                ]\r\n            });\r\n\r\n            wavesurferReal.on('ready', () => {\r\n                document.getElementById('realWavesurferContainer').style.display = 'block';\r\n                updateStatus('real', 'ready', '\u2713 Pr\u00eat');\r\n                if (wavesurferTTS) compareAudios();\r\n            });\r\n\r\n            await wavesurferReal.load(url);\r\n        }\r\n\r\n        function getColormap() {\r\n            const colors = [];\r\n            for (let i = 0; i < 256; i++) {\r\n                const n = i \/ 255;\r\n                let r, g, b;\r\n                if (n < 0.125) { r = 0; g = 0; b = Math.floor(128 + n * 8 * 127); }\r\n                else if (n < 0.375) { r = 0; g = Math.floor((n - 0.125) * 4 * 255); b = 255; }\r\n                else if (n < 0.625) { r = Math.floor((n - 0.375) * 4 * 255); g = 255; b = Math.floor(255 - (n - 0.375) * 4 * 255); }\r\n                else if (n < 0.875) { r = 255; g = Math.floor(255 - (n - 0.625) * 4 * 255); b = 0; }\r\n                else { r = Math.floor(255 - (n - 0.875) * 8 * 127); g = 0; b = 0; }\r\n                colors.push([r, g, b, 255]);\r\n            }\r\n            return colors;\r\n        }\r\n\r\n        function playTTS() { if (wavesurferTTS) wavesurferTTS.play(); }\r\n        function pauseTTS() { if (wavesurferTTS) wavesurferTTS.pause(); }\r\n        function stopTTS() { if (wavesurferTTS) wavesurferTTS.stop(); }\r\n        function playReal() { if (wavesurferReal) wavesurferReal.play(); }\r\n        function pauseReal() { if (wavesurferReal) wavesurferReal.pause(); }\r\n        function stopReal() { if (wavesurferReal) wavesurferReal.stop(); }\r\n\r\n        function downloadTTS() {\r\n            if (!currentTTSBlob) {\r\n                showAlert('Aucun audio TTS \u00e0 t\u00e9l\u00e9charger', 'error');\r\n                return;\r\n            }\r\n            const url = URL.createObjectURL(currentTTSBlob);\r\n            const a = document.createElement('a');\r\n            a.href = url;\r\n            a.download = 'tts_audio.mp3';\r\n            a.click();\r\n            URL.revokeObjectURL(url);\r\n        }\r\n\r\n        function compareAudios() {\r\n            const d1 = wavesurferTTS.getDuration();\r\n            const d2 = wavesurferReal.getDuration();\r\n            const diff = Math.abs(d1 - d2);\r\n            const sim = Math.max(0, 100 * (1 - diff \/ Math.max(d1, d2)));\r\n\r\n            document.getElementById('sim').textContent = sim.toFixed(1) + '%';\r\n            document.getElementById('diff').textContent = diff.toFixed(2);\r\n            document.getElementById('dur1').textContent = d1.toFixed(2) + 's';\r\n            document.getElementById('dur2').textContent = d2.toFixed(2) + 's';\r\n\r\n            document.getElementById('comparisonCard').style.display = 'block';\r\n            setTimeout(() => document.getElementById('comparisonCard').scrollIntoView({ behavior: 'smooth' }), 100);\r\n        }\r\n\r\n        function updateStatus(type, status, text) {\r\n            const badge = document.getElementById(type + 'Status');\r\n            badge.style.display = 'inline-block';\r\n            badge.className = 'status-badge status-' + status;\r\n            badge.textContent = text;\r\n        }\r\n\r\n        function showAlert(message, type) {\r\n            const container = document.getElementById('alertContainer');\r\n            const alertClass = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';\r\n            container.innerHTML = `<div class=\"alert ${alertClass}\">${message}<\/div>`;\r\n        }\r\n\r\n        \/\/ Test connexion au d\u00e9marrage\r\n        window.addEventListener('load', async () => {\r\n            const apiUrl = \"\/personnalisation\/\"; \/\/ document.getElementById('apiUrl').value.trim();\r\n            try {\r\n                const response = await fetch(`${apiUrl}get_health.php`);\r\n                if (response.ok) {\r\n                    showAlert('\u2705 Service TTS API connect\u00e9 !', 'success');\r\n                }\r\n            } catch (error) {\r\n                showAlert('\u26a0\ufe0f Service TTS non accessible. D\u00e9marrez-le avec : python tts_service.py', 'error');\r\n            }\r\n        });\r\n    <\/script>\r\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-11859","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/11859","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11859"}],"version-history":[{"count":1,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/11859\/revisions"}],"predecessor-version":[{"id":11860,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/11859\/revisions\/11860"}],"wp:attachment":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11859"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}