Spaces:
Paused
Paused
Update server.js
Browse files
server.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
| 1 |
-
|
| 2 |
const express = require('express');
|
| 3 |
const io = require('socket.io-client');
|
| 4 |
const axios = require('axios');
|
| 5 |
const NodeCache = require('node-cache');
|
| 6 |
-
const {
|
| 7 |
|
| 8 |
const app = express();
|
| 9 |
const port = 7860;
|
| 10 |
-
const jobCache = new NodeCache({ stdTTL: 3600 });
|
| 11 |
|
| 12 |
app.use(express.json());
|
| 13 |
app.use(express.urlencoded({ extended: true }));
|
|
@@ -28,7 +27,7 @@ const HEADERS = {
|
|
| 28 |
// --- LOGIC STABILIZER ---
|
| 29 |
async function runStabilize(jobId, videoUrl) {
|
| 30 |
try {
|
| 31 |
-
jobCache.set(jobId, { status: 'Initializing', progress:
|
| 32 |
|
| 33 |
const page = await axios.get(CONFIG.targetUrl, { headers: HEADERS });
|
| 34 |
const html = page.data;
|
|
@@ -38,7 +37,7 @@ async function runStabilize(jobId, videoUrl) {
|
|
| 38 |
setCookie.forEach(c => {
|
| 39 |
const [kv] = c.split(';');
|
| 40 |
const [k, v] = kv.split('=');
|
| 41 |
-
cookieMap[k.trim()] = v.trim();
|
| 42 |
});
|
| 43 |
|
| 44 |
const uid = cookieMap['uid'];
|
|
@@ -46,25 +45,25 @@ async function runStabilize(jobId, videoUrl) {
|
|
| 46 |
const g = html.match(/"g"\s*:\s*"([^"]+)"/)?.[1];
|
| 47 |
const f = html.match(/"f"\s*:\s*"([^"]+)"/)?.[1] || "44ec01b4c5d979004ecf7d57eed8ba65";
|
| 48 |
|
| 49 |
-
if (!uid || !i || !g) throw new Error("Gagal
|
| 50 |
|
| 51 |
const cookieStr = Object.entries(cookieMap).map(([k, v]) => `${k}=${v}`).join('; ');
|
| 52 |
|
| 53 |
-
jobCache.set(jobId, { status: 'Handshaking
|
| 54 |
const attachResponse = await axios.get('https://online-video-cutter.com/sso/attach/', {
|
| 55 |
params: { callback: 'jQuery' + Math.floor(Math.random() * 10e15), _: Date.now() },
|
| 56 |
headers: { ...HEADERS, 'cookie': cookieStr, 'x-requested-with': 'XMLHttpRequest' }
|
| 57 |
});
|
| 58 |
|
| 59 |
const verify = attachResponse.data.match(/"verify"\s*:\s*"([^"]+)"/)?.[1];
|
| 60 |
-
if (!verify) throw new Error("
|
| 61 |
|
| 62 |
await axios.post('https://online-video-cutter.com/sso/verify/',
|
| 63 |
new URLSearchParams({ verify }).toString(),
|
| 64 |
{ headers: { ...HEADERS, 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'cookie': cookieStr + `; sso_verify_onlinevideocuttercom=${verify}` } }
|
| 65 |
);
|
| 66 |
|
| 67 |
-
jobCache.set(jobId, { status: 'Connecting
|
| 68 |
const socket = io(CONFIG.workerUrl, {
|
| 69 |
path: '/socket.io/',
|
| 70 |
transports: ['websocket'],
|
|
@@ -83,7 +82,7 @@ async function runStabilize(jobId, videoUrl) {
|
|
| 83 |
|
| 84 |
socket.on('open_remote', (data) => {
|
| 85 |
if (data.message_type === 'final_result' && data.tmp_filename) {
|
| 86 |
-
jobCache.set(jobId, { status: '
|
| 87 |
socket.emit('encode', {
|
| 88 |
site_id: "vcutter", uid, operation_id: `${Date.now()}_enc`,
|
| 89 |
action_type: "encode", tmp_filename: data.tmp_filename, v2: true,
|
|
@@ -107,7 +106,7 @@ async function runStabilize(jobId, videoUrl) {
|
|
| 107 |
});
|
| 108 |
|
| 109 |
socket.on('connect_error', (err) => {
|
| 110 |
-
jobCache.set(jobId, { status: 'Error', error: '
|
| 111 |
});
|
| 112 |
|
| 113 |
} catch (e) {
|
|
@@ -126,48 +125,44 @@ app.get('/', (req, res) => {
|
|
| 126 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 127 |
<title>Video Stabilizer</title>
|
| 128 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 129 |
-
<style>
|
| 130 |
-
.loader { border-top-color: #3498db; animation: spin 1s linear infinite; }
|
| 131 |
-
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 132 |
-
</style>
|
| 133 |
</head>
|
| 134 |
-
<body class="bg-
|
| 135 |
-
<div class="w-full max-w-md bg-
|
| 136 |
-
<h1 class="text-
|
| 137 |
-
<p class="text-xs text-
|
| 138 |
|
| 139 |
<div class="space-y-4">
|
| 140 |
-
<input type="text" id="videoUrl" placeholder="
|
| 141 |
-
class="w-full p-
|
| 142 |
<button onclick="startJob()" id="btnStart"
|
| 143 |
-
class="w-full bg-blue-600 hover:bg-blue-
|
| 144 |
-
|
| 145 |
</button>
|
| 146 |
</div>
|
| 147 |
|
| 148 |
-
<div id="statusContainer" class="hidden mt-
|
| 149 |
-
<div class="flex
|
| 150 |
-
<span id="statusText" class="text-sm font-medium
|
| 151 |
-
<span id="progressPercent" class="text-sm font-
|
| 152 |
</div>
|
| 153 |
-
<div class="w-full bg-
|
| 154 |
-
<div id="progressBar" class="bg-blue-500 h-
|
| 155 |
</div>
|
| 156 |
-
|
|
|
|
| 157 |
<a id="downloadBtn" href="#" target="_blank"
|
| 158 |
-
class="
|
| 159 |
-
|
| 160 |
</a>
|
| 161 |
</div>
|
| 162 |
</div>
|
| 163 |
</div>
|
| 164 |
|
| 165 |
<script>
|
| 166 |
-
let
|
| 167 |
-
|
| 168 |
async function startJob() {
|
| 169 |
const url = document.getElementById('videoUrl').value;
|
| 170 |
-
if(!url) return alert('
|
| 171 |
|
| 172 |
document.getElementById('btnStart').disabled = true;
|
| 173 |
document.getElementById('statusContainer').classList.remove('hidden');
|
|
@@ -182,28 +177,30 @@ app.get('/', (req, res) => {
|
|
| 182 |
const data = await res.json();
|
| 183 |
if(data.jobId) pollStatus(data.jobId);
|
| 184 |
} catch (err) {
|
| 185 |
-
alert('Gagal
|
| 186 |
document.getElementById('btnStart').disabled = false;
|
| 187 |
}
|
| 188 |
}
|
| 189 |
|
| 190 |
function pollStatus(jobId) {
|
| 191 |
-
|
| 192 |
const res = await fetch('/api/status/' + jobId);
|
| 193 |
const data = await res.json();
|
| 194 |
|
| 195 |
document.getElementById('statusText').innerText = data.status;
|
| 196 |
-
|
| 197 |
-
document.getElementById('
|
|
|
|
| 198 |
|
| 199 |
if(data.status === 'Completed') {
|
| 200 |
-
clearInterval(
|
| 201 |
document.getElementById('btnStart').disabled = false;
|
| 202 |
document.getElementById('resultArea').classList.remove('hidden');
|
| 203 |
document.getElementById('downloadBtn').href = data.result;
|
|
|
|
| 204 |
} else if(data.status === 'Error') {
|
| 205 |
-
clearInterval(
|
| 206 |
-
alert('
|
| 207 |
document.getElementById('btnStart').disabled = false;
|
| 208 |
}
|
| 209 |
}, 2000);
|
|
@@ -216,7 +213,7 @@ app.get('/', (req, res) => {
|
|
| 216 |
|
| 217 |
app.post('/api/stabilize', (req, res) => {
|
| 218 |
const { url } = req.body;
|
| 219 |
-
const jobId =
|
| 220 |
runStabilize(jobId, url);
|
| 221 |
res.json({ jobId });
|
| 222 |
});
|
|
@@ -227,6 +224,6 @@ app.get('/api/status/:jobId', (req, res) => {
|
|
| 227 |
res.json(job);
|
| 228 |
});
|
| 229 |
|
| 230 |
-
app.listen(port, () => {
|
| 231 |
-
console.log(`Server
|
| 232 |
});
|
|
|
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const io = require('socket.io-client');
|
| 3 |
const axios = require('axios');
|
| 4 |
const NodeCache = require('node-cache');
|
| 5 |
+
const { randomUUID } = require('crypto'); // Gunakan modul bawaan Node.js
|
| 6 |
|
| 7 |
const app = express();
|
| 8 |
const port = 7860;
|
| 9 |
+
const jobCache = new NodeCache({ stdTTL: 3600 });
|
| 10 |
|
| 11 |
app.use(express.json());
|
| 12 |
app.use(express.urlencoded({ extended: true }));
|
|
|
|
| 27 |
// --- LOGIC STABILIZER ---
|
| 28 |
async function runStabilize(jobId, videoUrl) {
|
| 29 |
try {
|
| 30 |
+
jobCache.set(jobId, { status: 'Initializing', progress: 5 });
|
| 31 |
|
| 32 |
const page = await axios.get(CONFIG.targetUrl, { headers: HEADERS });
|
| 33 |
const html = page.data;
|
|
|
|
| 37 |
setCookie.forEach(c => {
|
| 38 |
const [kv] = c.split(';');
|
| 39 |
const [k, v] = kv.split('=');
|
| 40 |
+
if (k && v) cookieMap[k.trim()] = v.trim();
|
| 41 |
});
|
| 42 |
|
| 43 |
const uid = cookieMap['uid'];
|
|
|
|
| 45 |
const g = html.match(/"g"\s*:\s*"([^"]+)"/)?.[1];
|
| 46 |
const f = html.match(/"f"\s*:\s*"([^"]+)"/)?.[1] || "44ec01b4c5d979004ecf7d57eed8ba65";
|
| 47 |
|
| 48 |
+
if (!uid || !i || !g) throw new Error("Gagal mengambil token website.");
|
| 49 |
|
| 50 |
const cookieStr = Object.entries(cookieMap).map(([k, v]) => `${k}=${v}`).join('; ');
|
| 51 |
|
| 52 |
+
jobCache.set(jobId, { status: 'Handshaking', progress: 15 });
|
| 53 |
const attachResponse = await axios.get('https://online-video-cutter.com/sso/attach/', {
|
| 54 |
params: { callback: 'jQuery' + Math.floor(Math.random() * 10e15), _: Date.now() },
|
| 55 |
headers: { ...HEADERS, 'cookie': cookieStr, 'x-requested-with': 'XMLHttpRequest' }
|
| 56 |
});
|
| 57 |
|
| 58 |
const verify = attachResponse.data.match(/"verify"\s*:\s*"([^"]+)"/)?.[1];
|
| 59 |
+
if (!verify) throw new Error("SSO Verify Token tidak ditemukan.");
|
| 60 |
|
| 61 |
await axios.post('https://online-video-cutter.com/sso/verify/',
|
| 62 |
new URLSearchParams({ verify }).toString(),
|
| 63 |
{ headers: { ...HEADERS, 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'cookie': cookieStr + `; sso_verify_onlinevideocuttercom=${verify}` } }
|
| 64 |
);
|
| 65 |
|
| 66 |
+
jobCache.set(jobId, { status: 'Connecting', progress: 25 });
|
| 67 |
const socket = io(CONFIG.workerUrl, {
|
| 68 |
path: '/socket.io/',
|
| 69 |
transports: ['websocket'],
|
|
|
|
| 82 |
|
| 83 |
socket.on('open_remote', (data) => {
|
| 84 |
if (data.message_type === 'final_result' && data.tmp_filename) {
|
| 85 |
+
jobCache.set(jobId, { status: 'Stabilizing Video...', progress: 50 });
|
| 86 |
socket.emit('encode', {
|
| 87 |
site_id: "vcutter", uid, operation_id: `${Date.now()}_enc`,
|
| 88 |
action_type: "encode", tmp_filename: data.tmp_filename, v2: true,
|
|
|
|
| 106 |
});
|
| 107 |
|
| 108 |
socket.on('connect_error', (err) => {
|
| 109 |
+
jobCache.set(jobId, { status: 'Error', error: 'Connection to worker failed' });
|
| 110 |
});
|
| 111 |
|
| 112 |
} catch (e) {
|
|
|
|
| 125 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 126 |
<title>Video Stabilizer</title>
|
| 127 |
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
</head>
|
| 129 |
+
<body class="bg-slate-900 text-slate-100 min-h-screen flex flex-col items-center p-4">
|
| 130 |
+
<div class="w-full max-w-md bg-slate-800 p-6 rounded-3xl shadow-2xl mt-10">
|
| 131 |
+
<h1 class="text-3xl font-extrabold mb-1 text-center bg-gradient-to-r from-blue-400 to-emerald-400 bg-clip-text text-transparent">Stabilizer</h1>
|
| 132 |
+
<p class="text-xs text-slate-400 mb-8 text-center uppercase tracking-widest">Mobile Video Tools</p>
|
| 133 |
|
| 134 |
<div class="space-y-4">
|
| 135 |
+
<input type="text" id="videoUrl" placeholder="Tempel Link Video (.mp4)"
|
| 136 |
+
class="w-full p-4 rounded-2xl bg-slate-700 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all">
|
| 137 |
<button onclick="startJob()" id="btnStart"
|
| 138 |
+
class="w-full bg-blue-600 hover:bg-blue-500 p-4 rounded-2xl font-bold text-lg shadow-lg active:scale-95 transition-all">
|
| 139 |
+
Proses Video
|
| 140 |
</button>
|
| 141 |
</div>
|
| 142 |
|
| 143 |
+
<div id="statusContainer" class="hidden mt-10 p-4 bg-slate-900/50 rounded-2xl">
|
| 144 |
+
<div class="flex justify-between mb-3">
|
| 145 |
+
<span id="statusText" class="text-sm font-medium text-blue-300 tracking-wide">Menyiapkan...</span>
|
| 146 |
+
<span id="progressPercent" class="text-sm font-black text-blue-400">0%</span>
|
| 147 |
</div>
|
| 148 |
+
<div class="w-full bg-slate-700 rounded-full h-3">
|
| 149 |
+
<div id="progressBar" class="bg-blue-500 h-3 rounded-full transition-all duration-700 ease-out" style="width: 0%"></div>
|
| 150 |
</div>
|
| 151 |
+
|
| 152 |
+
<div id="resultArea" class="mt-8 hidden animate-bounce">
|
| 153 |
<a id="downloadBtn" href="#" target="_blank"
|
| 154 |
+
class="flex items-center justify-center gap-2 bg-emerald-600 hover:bg-emerald-500 text-white p-4 rounded-2xl font-bold shadow-xl">
|
| 155 |
+
<span>Simpan Video</span>
|
| 156 |
</a>
|
| 157 |
</div>
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
|
| 161 |
<script>
|
| 162 |
+
let polling;
|
|
|
|
| 163 |
async function startJob() {
|
| 164 |
const url = document.getElementById('videoUrl').value;
|
| 165 |
+
if(!url) return alert('Masukkan link video dulu!');
|
| 166 |
|
| 167 |
document.getElementById('btnStart').disabled = true;
|
| 168 |
document.getElementById('statusContainer').classList.remove('hidden');
|
|
|
|
| 177 |
const data = await res.json();
|
| 178 |
if(data.jobId) pollStatus(data.jobId);
|
| 179 |
} catch (err) {
|
| 180 |
+
alert('Gagal menghubungi server');
|
| 181 |
document.getElementById('btnStart').disabled = false;
|
| 182 |
}
|
| 183 |
}
|
| 184 |
|
| 185 |
function pollStatus(jobId) {
|
| 186 |
+
polling = setInterval(async () => {
|
| 187 |
const res = await fetch('/api/status/' + jobId);
|
| 188 |
const data = await res.json();
|
| 189 |
|
| 190 |
document.getElementById('statusText').innerText = data.status;
|
| 191 |
+
const prog = data.progress || 0;
|
| 192 |
+
document.getElementById('progressPercent').innerText = prog + '%';
|
| 193 |
+
document.getElementById('progressBar').style.width = prog + '%';
|
| 194 |
|
| 195 |
if(data.status === 'Completed') {
|
| 196 |
+
clearInterval(polling);
|
| 197 |
document.getElementById('btnStart').disabled = false;
|
| 198 |
document.getElementById('resultArea').classList.remove('hidden');
|
| 199 |
document.getElementById('downloadBtn').href = data.result;
|
| 200 |
+
document.getElementById('statusText').innerText = "Selesai!";
|
| 201 |
} else if(data.status === 'Error') {
|
| 202 |
+
clearInterval(polling);
|
| 203 |
+
alert('Gagal: ' + data.error);
|
| 204 |
document.getElementById('btnStart').disabled = false;
|
| 205 |
}
|
| 206 |
}, 2000);
|
|
|
|
| 213 |
|
| 214 |
app.post('/api/stabilize', (req, res) => {
|
| 215 |
const { url } = req.body;
|
| 216 |
+
const jobId = randomUUID(); // Menggunakan crypto bawaan
|
| 217 |
runStabilize(jobId, url);
|
| 218 |
res.json({ jobId });
|
| 219 |
});
|
|
|
|
| 224 |
res.json(job);
|
| 225 |
});
|
| 226 |
|
| 227 |
+
app.listen(port, '0.0.0.0', () => {
|
| 228 |
+
console.log(`Server aktif di port ${port}`);
|
| 229 |
});
|