Files changed (1) hide show
  1. server.js +151 -0
server.js ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const WebSocket = require('ws');
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const ffmpeg = require('fluent-ffmpeg');
7
+
8
+ const app = express();
9
+ const PORT = 7860;
10
+
11
+ // Konfigurasi TTS
12
+ const TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4";
13
+ const GEC_VERSION = "1-143.0.3650.75";
14
+ const WSS_URL = "wss://speech.text-to-speech.online/consumer/speech/synthesize/readaloud/edge/v1";
15
+
16
+ // Pastikan folder temp ada
17
+ const tempDir = path.join(__dirname, 'temp');
18
+ if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);
19
+
20
+ /**
21
+ * RE Logics
22
+ */
23
+ function generateMSHash() {
24
+ const WIN_EPOCH = 11644473600n;
25
+ const S_TO_NS = 1000000000n;
26
+ let ticks = BigInt(Math.floor(Date.now() / 1000));
27
+ ticks += WIN_EPOCH;
28
+ ticks -= (ticks % 300n);
29
+ ticks *= (S_TO_NS / 100n);
30
+ const strToHash = `${ticks}${TRUSTED_CLIENT_TOKEN}`;
31
+ return crypto.createHash('sha256').update(strToHash).digest('hex').toUpperCase();
32
+ }
33
+
34
+ function createGuid() {
35
+ return crypto.randomBytes(16).toString('hex').toUpperCase();
36
+ }
37
+
38
+ /**
39
+ * Core TTS Synthesis
40
+ */
41
+ function synthesize(text, voice, speed) {
42
+ return new Promise((resolve, reject) => {
43
+ const requestId = createGuid();
44
+ const connectionId = createGuid();
45
+ const gec = generateMSHash();
46
+ const fullUrl = `${WSS_URL}?TrustedClientToken=${TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${gec}&Sec-MS-GEC-Version=${GEC_VERSION}&Authorization=bearer%20undefined&ConnectionId=${connectionId}`;
47
+
48
+ const ws = new WebSocket(fullUrl, {
49
+ origin: 'https://www.text-to-speech.online',
50
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36' }
51
+ });
52
+
53
+ let audioChunks = [];
54
+ const ttsRate = speed >= 1 ? `+${(speed - 1) * 100}%` : `-${(1 - speed) * 100}%`;
55
+
56
+ ws.on('open', () => {
57
+ 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"}}}`);
58
+ 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}}}`);
59
+ ws.send(`Path: ssml\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${new Date().toISOString()}\r\nContent-Type: application/ssml+xml\r\n\r\n<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US"><voice name="${voice}"><prosody rate="${ttsRate}" pitch="0%">${text}</prosody></voice></speak>`);
60
+ });
61
+
62
+ ws.on('message', (data, isBinary) => {
63
+ if (isBinary) {
64
+ const separator = Buffer.from('Path:audio\r\n');
65
+ const index = data.indexOf(separator);
66
+ if (index !== -1) audioChunks.push(data.slice(index + separator.length));
67
+ } else if (data.toString().includes('turn.end')) {
68
+ ws.close();
69
+ resolve(Buffer.concat(audioChunks));
70
+ }
71
+ });
72
+ ws.on('error', reject);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Express Routes
78
+ */
79
+
80
+ // UI Sederhana
81
+ app.get('/', (req, res) => {
82
+ res.send(`
83
+ <html>
84
+ <head><title>Edge TTS Mixer</title></head>
85
+ <body style="font-family:sans-serif; max-width:500px; margin:50px auto;">
86
+ <h2>Edge TTS Web UI</h2>
87
+ <form action="/generate" method="GET">
88
+ <textarea name="text" style="width:100%" rows="4" placeholder="Masukkan teks..."></textarea><br><br>
89
+ Kecepatan: <input type="number" name="speed" value="1.0" step="0.1" min="0.5" max="2.0"><br><br>
90
+ Pakai Backsound: <input type="checkbox" name="use_bg" value="true" checked><br><br>
91
+ Volume Backsound (0.1 - 1.0): <input type="number" name="bg_vol" value="0.3" step="0.1"><br><br>
92
+ <button type="submit" style="padding:10px 20px">Generate & Download</button>
93
+ </form>
94
+ </body>
95
+ </html>
96
+ `);
97
+ });
98
+
99
+ // Endpoint Generate
100
+ app.get('/generate', async (req, res) => {
101
+ const text = req.query.text || "Halo selamat datang";
102
+ const speed = parseFloat(req.query.speed) || 1.0;
103
+ const useBg = req.query.use_bg === 'true';
104
+ const bgVol = parseFloat(req.query.bg_vol) || 0.3;
105
+ const voice = "id-ID-ArdiNeural";
106
+
107
+ const jobId = Date.now();
108
+ const vocalPath = path.join(tempDir, `vocal_${jobId}.mp3`);
109
+ const finalPath = path.join(tempDir, `final_${jobId}.mp3`);
110
+ const backsoundPath = path.join(__dirname, 'backsound.mp3');
111
+
112
+ try {
113
+ // 1. Ambil Vokal dari TTS
114
+ const audioBuffer = await synthesize(text, voice, speed);
115
+ fs.writeFileSync(vocalPath, audioBuffer);
116
+
117
+ if (useBg && fs.existsSync(backsoundPath)) {
118
+ // 2. Mix dengan Backsound
119
+ ffmpeg()
120
+ .input(vocalPath)
121
+ .input(backsoundPath)
122
+ .complexFilter([
123
+ { filter: 'volume', options: { volume: bgVol }, inputs: '1:a', outputs: 'bg' },
124
+ { filter: 'amix', options: { inputs: 2, duration: 'first' }, inputs: ['0:a', 'bg'], outputs: 'out' }
125
+ ])
126
+ .map('out')
127
+ .audioCodec('libmp3lame')
128
+ .on('error', (err) => res.status(500).send("FFmpeg Error: " + err.message))
129
+ .on('end', () => {
130
+ res.download(finalPath, "audio_result.mp3", () => {
131
+ // Cleanup
132
+ if (fs.existsSync(vocalPath)) fs.unlinkSync(vocalPath);
133
+ if (fs.existsSync(finalPath)) fs.unlinkSync(finalPath);
134
+ });
135
+ })
136
+ .save(finalPath);
137
+ } else {
138
+ // 3. Tanpa Backsound
139
+ res.download(vocalPath, "audio_vocal.mp3", () => {
140
+ if (fs.existsSync(vocalPath)) fs.unlinkSync(vocalPath);
141
+ });
142
+ }
143
+
144
+ } catch (err) {
145
+ res.status(500).send("Error: " + err.message);
146
+ }
147
+ });
148
+
149
+ app.listen(PORT, () => {
150
+ console.log(`Server berjalan di http://localhost:${PORT}`);
151
+ });