const express = require('express'); const WebSocket = require('ws'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const ffmpeg = require('fluent-ffmpeg'); const app = express(); const PORT = 7860; // Konfigurasi TTS const TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4"; const GEC_VERSION = "1-143.0.3650.75"; const WSS_URL = "wss://speech.text-to-speech.online/consumer/speech/synthesize/readaloud/edge/v1"; // Pastikan folder temp ada const tempDir = path.join(__dirname, 'temp'); if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir); /** * RE Logics */ function generateMSHash() { const WIN_EPOCH = 11644473600n; const S_TO_NS = 1000000000n; let ticks = BigInt(Math.floor(Date.now() / 1000)); ticks += WIN_EPOCH; ticks -= (ticks % 300n); ticks *= (S_TO_NS / 100n); const strToHash = `${ticks}${TRUSTED_CLIENT_TOKEN}`; return crypto.createHash('sha256').update(strToHash).digest('hex').toUpperCase(); } function createGuid() { return crypto.randomBytes(16).toString('hex').toUpperCase(); } /** * Core TTS Synthesis */ function synthesize(text, voice, speed) { return new Promise((resolve, reject) => { const requestId = createGuid(); const connectionId = createGuid(); const gec = generateMSHash(); const fullUrl = `${WSS_URL}?TrustedClientToken=${TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${gec}&Sec-MS-GEC-Version=${GEC_VERSION}&Authorization=bearer%20undefined&ConnectionId=${connectionId}`; const ws = new WebSocket(fullUrl, { origin: 'https://www.text-to-speech.online', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36' } }); let audioChunks = []; const ttsRate = speed >= 1 ? `+${(speed - 1) * 100}%` : `-${(1 - speed) * 100}%`; ws.on('open', () => { ws.send(`Path: speech.config\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/json\r\n\r\n{"context":{"system":{"name":"SpeechSDK","version":"1.19.0","build":"JavaScript","lang":"JavaScript"},"os":{"platform":"Browser/Win32","name":"Chrome","version":"120.0.0.0"}}}`); ws.send(`Path: synthesis.context\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/json\r\n\r\n{"synthesis":{"audio":{"metadataOptions":{"bookmarkEnabled":false,"sentenceBoundaryEnabled":false,"visemeEnabled":false,"wordBoundaryEnabled":false},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"},"language":{"autoDetection":false}}}`); ws.send(`Path: ssml\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/ssml+xml\r\n\r\n${text}`); }); ws.on('message', (data, isBinary) => { if (isBinary) { const separator = Buffer.from('Path:audio\r\n'); const index = data.indexOf(separator); if (index !== -1) audioChunks.push(data.slice(index + separator.length)); } else if (data.toString().includes('turn.end')) { ws.close(); resolve(Buffer.concat(audioChunks)); } }); ws.on('error', reject); }); } /** * Express Routes */ // UI Sederhana app.get('/', (req, res) => { res.send(` Edge TTS Mixer

Edge TTS Web UI



Kecepatan:

Pakai Backsound:

Volume Backsound (0.1 - 1.0):

`); }); // Endpoint Generate app.get('/generate', async (req, res) => { const text = req.query.text || "Halo selamat datang"; const speed = parseFloat(req.query.speed) || 1.0; const useBg = req.query.use_bg === 'true'; const bgVol = parseFloat(req.query.bg_vol) || 0.3; const voice = "id-ID-ArdiNeural"; const jobId = Date.now(); const vocalPath = path.join(tempDir, `vocal_${jobId}.mp3`); const finalPath = path.join(tempDir, `final_${jobId}.mp3`); const backsoundPath = path.join(__dirname, 'backsound.mp3'); try { // 1. Ambil Vokal dari TTS const audioBuffer = await synthesize(text, voice, speed); fs.writeFileSync(vocalPath, audioBuffer); if (useBg && fs.existsSync(backsoundPath)) { // 2. Mix dengan Backsound ffmpeg() .input(vocalPath) .input(backsoundPath) .complexFilter([ { filter: 'volume', options: { volume: bgVol }, inputs: '1:a', outputs: 'bg' }, { filter: 'amix', options: { inputs: 2, duration: 'first' }, inputs: ['0:a', 'bg'], outputs: 'out' } ]) .map('out') .audioCodec('libmp3lame') .on('error', (err) => res.status(500).send("FFmpeg Error: " + err.message)) .on('end', () => { res.download(finalPath, "audio_result.mp3", () => { // Cleanup if (fs.existsSync(vocalPath)) fs.unlinkSync(vocalPath); if (fs.existsSync(finalPath)) fs.unlinkSync(finalPath); }); }) .save(finalPath); } else { // 3. Tanpa Backsound res.download(vocalPath, "audio_vocal.mp3", () => { if (fs.existsSync(vocalPath)) fs.unlinkSync(vocalPath); }); } } catch (err) { res.status(500).send("Error: " + err.message); } }); app.listen(PORT, () => { console.log(`Server berjalan di http://localhost:${PORT}`); });