realeditz / src /server.js
andevs's picture
Update src/server.js
0a0cb70 verified
Raw
History Blame Contribute Delete
19.8 kB
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const dotenv = require('dotenv');
const path = require('path');
const fs = require('fs').promises;
// Load environment variables
dotenv.config();
class TikVibeServer {
constructor() {
this.app = express();
this.httpServer = createServer(this.app);
// Setup Socket.IO for real-time features
this.io = new Server(this.httpServer, {
cors: {
origin: '*',
credentials: true,
methods: ['GET', 'POST']
},
maxHttpBufferSize: 1e8 // 100MB for video streaming
});
this.PORT = process.env.PORT || 7860;
this.connectedUsers = new Map();
this.projectRooms = new Map();
this.activeRenders = new Map();
}
async initialize() {
console.log('πŸš€ Initializing TikVibe Backend Server...');
// Create required directories
await this.createDirectories();
this.setupMiddleware();
this.setupRoutes();
this.setupWebSocket();
this.setupErrorHandling();
this.setupCleanupJobs();
console.log('βœ… Server initialization complete');
}
async createDirectories() {
const dirs = ['uploads', 'renders', 'temp'];
for (const dir of dirs) {
const dirPath = path.join(__dirname, '..', dir);
await fs.mkdir(dirPath, { recursive: true });
console.log(`βœ… Directory ready: ${dir}`);
}
}
setupMiddleware() {
// Security middleware
this.app.use(helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
crossOriginResourcePolicy: { policy: "cross-origin" }
}));
// CORS configuration
this.app.use(cors({
origin: '*',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}));
// Compression for faster responses
this.app.use(compression({
level: 6,
threshold: 1024,
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
}
}));
// Body parsing with increased limits for video
this.app.use(express.json({ limit: '500mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '500mb' }));
// Static file serving
this.app.use('/uploads', express.static('uploads'));
this.app.use('/renders', express.static('renders'));
// Request logging with timing
this.app.use((req, res, next) => {
req.startTime = Date.now();
console.log(`πŸ“₯ ${new Date().toISOString()} - ${req.method} ${req.path}`);
res.on('finish', () => {
const duration = Date.now() - req.startTime;
console.log(`πŸ“€ ${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`);
});
next();
});
}
setupRoutes() {
// Health check with system info
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV || 'production',
server: 'TikVibe Backend',
version: '2.0.0',
system: {
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
node: process.version,
platform: process.platform
}
});
});
// Root endpoint with full API documentation
this.app.get('/', (req, res) => {
res.json({
name: 'πŸŽ₯ TikVibe Creator Pro API',
version: '2.0.0',
description: 'Advanced AI-Powered Video Editing Platform',
documentation: '/api/docs',
endpoints: {
health: {
path: '/health',
method: 'GET',
description: 'System health check'
},
test: {
path: '/api/test',
method: 'GET',
description: 'Test API connection'
},
ai: {
velocity: {
path: '/api/ai/velocity-edit',
method: 'POST',
description: 'Create velocity/fancam style edit with beat sync'
},
beatMontage: {
path: '/api/ai/beat-montage',
method: 'POST',
description: 'Create beat-synced montage with perfect timing'
},
aesthetic: {
path: '/api/ai/aesthetic-edit',
method: 'POST',
description: 'Apply aesthetic filters and color grading'
},
smooth: {
path: '/api/ai/smooth-transitions',
method: 'POST',
description: 'Create smooth transitions between clips'
},
vlog: {
path: '/api/ai/vlog-edit',
method: 'POST',
description: 'Create vlog-style edit with text overlays'
},
imitate: {
path: '/api/ai/imitate-style',
method: 'POST',
description: 'Imitate editing style from inspiration video'
}
},
analytics: {
trends: '/api/trends',
predict: '/api/ai/predict-trends'
},
auth: {
login: '/api/auth/login',
register: '/api/auth/register'
}
}
});
});
// API Documentation
this.app.get('/api/docs', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>TikVibe API Documentation</title>
<style>
body { font-family: Arial; background: #0a0a0a; color: white; padding: 20px; }
h1 { color: #ff0050; }
h2 { color: #00f2ea; }
.endpoint { background: #1a1a1a; padding: 15px; margin: 10px 0; border-radius: 5px; }
.method { color: #00ff00; font-weight: bold; }
.path { color: #ff0050; }
pre { background: #2a2a2a; padding: 10px; border-radius: 3px; }
</style>
</head>
<body>
<h1>πŸŽ₯ TikVibe Creator Pro API</h1>
<p>Base URL: https://andevs-realeditz.hf.space</p>
<h2>AI Editing Endpoints</h2>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/velocity-edit</span>
<p>Create velocity/fancam style edit with beat sync</p>
<pre>FormData: {
clips: File[] (video files),
audio: File (music file),
intensity: "low" | "medium" | "high"
}</pre>
</div>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/beat-montage</span>
<p>Create beat-synced montage</p>
<pre>FormData: {
clips: File[] (video files),
audio: File (music file),
montageType: "fancam" | "cinematic"
}</pre>
</div>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/aesthetic-edit</span>
<p>Apply aesthetic filters</p>
<pre>FormData: {
clips: File[] (video files),
audio: File (optional),
aesthetic: "vintage" | "cinematic" | "vibrant" | "moody" | "dreamy"
}</pre>
</div>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/smooth-transitions</span>
<p>Create smooth transitions</p>
<pre>FormData: {
clips: File[] (video files),
audio: File (optional)
}</pre>
</div>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/vlog-edit</span>
<p>Create vlog-style edit</p>
<pre>FormData: {
clips: File[] (video files),
audio: File (optional)
}</pre>
</div>
<div class="endpoint">
<span class="method">POST</span> <span class="path">/api/ai/imitate-style</span>
<p>Imitate style from inspiration video</p>
<pre>FormData: {
inspoVideo: File (inspiration video),
clips: File[] (your clips),
audio: File (optional)
}</pre>
</div>
</body>
</html>
`);
});
// Test endpoint
this.app.get('/api/test', (req, res) => {
res.json({
success: true,
message: 'βœ… TikVibe API is working correctly!',
timestamp: new Date().toISOString(),
features: [
'velocity-edit',
'beat-montage',
'aesthetic-edit',
'smooth-transitions',
'vlog-edit',
'imitate-style'
]
});
});
// Auth endpoints (simplified)
this.app.post('/api/auth/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
success: false,
message: 'Email and password required'
});
}
res.json({
success: true,
message: 'Login successful',
token: 'mock-jwt-token-' + Date.now(),
user: {
id: 1,
email: email,
name: email.split('@')[0]
}
});
});
this.app.post('/api/auth/register', (req, res) => {
const { email, password, name } = req.body;
if (!email || !password) {
return res.status(400).json({
success: false,
message: 'Email and password required'
});
}
res.json({
success: true,
message: 'Registration successful',
user: {
id: Date.now(),
email: email,
name: name || email.split('@')[0]
}
});
});
// Trends endpoint with mock data
this.app.get('/api/trends', (req, res) => {
const { category = 'all', timeframe = '24h' } = req.query;
const trends = {
all: [
{ id: 1, name: '#dancechallenge', category: 'hashtag', growth: 25, views: 1000000 },
{ id: 2, name: 'Summer Vibes', category: 'sound', growth: 15, views: 500000 },
{ id: 3, name: 'Glitch Effect', category: 'effect', growth: -5, views: 250000 }
]
};
res.json({
success: true,
timeframe,
category,
trends: trends[category] || trends.all
});
});
// AI Routes
const aiRouter = require('./routes/ai');
this.app.use('/api/ai', aiRouter);
// ============= VIDEO DOWNLOAD ENDPOINT =============
this.app.get('/api/video/download/:filename', async (req, res) => {
try {
const filename = req.params.filename;
// Sanitize filename to prevent path traversal
const safeFilename = path.basename(filename);
const filepath = path.join(__dirname, '../renders', safeFilename);
console.log('πŸ“₯ Download requested:', safeFilename);
console.log('Full path:', filepath);
// Check if file exists
try {
await fs.access(filepath);
} catch (error) {
console.error('File not found:', filepath);
return res.status(404).json({
error: 'File not found',
message: 'The video file may have been deleted or expired'
});
}
// Get file stats
const stats = await fs.stat(filepath);
console.log(`File size: ${stats.size} bytes`);
// Set headers
res.setHeader('Content-Disposition', `attachment; filename="${safeFilename}"`);
res.setHeader('Content-Type', 'video/mp4');
res.setHeader('Content-Length', stats.size);
// Send file
res.sendFile(filepath, (err) => {
if (err) {
console.error('Error sending file:', err);
if (!res.headersSent) {
res.status(500).json({ error: 'Failed to send file' });
}
} else {
console.log('βœ… File sent successfully:', safeFilename);
}
});
} catch (error) {
console.error('Download error:', error);
res.status(500).json({
error: 'Download failed',
message: error.message
});
}
});
// Progress tracking for renders
this.app.get('/api/render/progress/:renderId', (req, res) => {
const { renderId } = req.params;
const progress = this.activeRenders.get(renderId) || 0;
res.json({ renderId, progress });
});
// Catch-all for undefined routes
this.app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'Endpoint not found',
path: req.originalUrl,
availableEndpoints: '/api/docs'
});
});
}
setupWebSocket() {
this.io.on('connection', (socket) => {
console.log('πŸ”Œ Client connected:', socket.id);
socket.emit('connected', {
id: socket.id,
message: 'Connected to TikVibe server',
timestamp: new Date().toISOString()
});
this.connectedUsers.set(socket.id, {
id: socket.id,
joinedAt: new Date().toISOString(),
lastActivity: new Date().toISOString()
});
socket.on('project:join', (data) => {
const { projectId, userId, userName } = data;
socket.join(`project:${projectId}`);
if (!this.projectRooms.has(projectId)) {
this.projectRooms.set(projectId, new Set());
}
this.projectRooms.get(projectId).add(socket.id);
socket.emit('project:joined', {
projectId,
users: Array.from(this.projectRooms.get(projectId)).length
});
socket.to(`project:${projectId}`).emit('user:joined', {
userId: userId || socket.id,
userName: userName || 'Anonymous',
timestamp: new Date().toISOString()
});
console.log(`πŸ‘€ User ${userName || socket.id} joined project ${projectId}`);
});
socket.on('project:message', (data) => {
const { projectId, message, userId, userName } = data;
const messageData = {
id: Date.now(),
userId: userId || socket.id,
userName: userName || 'Anonymous',
message: message,
timestamp: new Date().toISOString()
};
socket.emit('message:sent', messageData);
socket.to(`project:${projectId}`).emit('project:message', messageData);
});
socket.on('disconnect', () => {
console.log('πŸ”Œ Client disconnected:', socket.id);
this.connectedUsers.delete(socket.id);
this.projectRooms.forEach((users, projectId) => {
if (users.has(socket.id)) {
users.delete(socket.id);
socket.to(`project:${projectId}`).emit('user:left', {
userId: socket.id,
timestamp: new Date().toISOString()
});
if (users.size === 0) {
this.projectRooms.delete(projectId);
}
}
});
});
});
}
setupErrorHandling() {
this.app.use((err, req, res, next) => {
console.error('❌ Global error:', err);
const status = err.status || 500;
const message = err.message || 'Internal server error';
console.error({
timestamp: new Date().toISOString(),
path: req.path,
method: req.method,
status,
message,
stack: err.stack
});
res.status(status).json({
success: false,
error: message,
code: err.code,
timestamp: new Date().toISOString()
});
});
}
setupCleanupJobs() {
// Clean up old files every hour
setInterval(async () => {
try {
const now = Date.now();
const dirs = ['uploads', 'renders', 'temp'];
for (const dir of dirs) {
const dirPath = path.join(__dirname, '..', dir);
const files = await fs.readdir(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const stats = await fs.stat(filePath);
// Delete files older than 1 hour (uploads/temp) or 24 hours (renders)
const maxAge = dir === 'renders' ? 86400000 : 3600000; // 24 hours or 1 hour
if (now - stats.mtimeMs > maxAge) {
await fs.unlink(filePath);
console.log(`🧹 Cleaned up old file: ${dir}/${file}`);
}
}
}
} catch (error) {
console.error('Cleanup error:', error);
}
}, 3600000); // Every hour
}
async start() {
await this.initialize();
this.httpServer.listen(this.PORT, '0.0.0.0', () => {
console.log(`
╔══════════════════════════════════════════════════════════╗
β•‘ β•‘
β•‘ πŸŽ₯ TikVibe Creator Pro Backend β•‘
β•‘ ================================================== β•‘
β•‘ β•‘
β•‘ πŸš€ Server: https://andevs-realeditz.hf.space β•‘
β•‘ πŸ”Œ WebSocket: wss://andevs-realeditz.hf.space β•‘
β•‘ 🌐 Environment: ${process.env.NODE_ENV || 'production'} β•‘
β•‘ πŸ“Š Status: ONLINE β•‘
β•‘ β•‘
β•‘ πŸ“‘ AI Endpoints: β•‘
β•‘ β€’ ⚑ /api/ai/velocity-edit β•‘
β•‘ β€’ 🎡 /api/ai/beat-montage β•‘
β•‘ β€’ 🎨 /api/ai/aesthetic-edit β•‘
β•‘ β€’ πŸ”„ /api/ai/smooth-transitions β•‘
β•‘ β€’ πŸŽ₯ /api/ai/vlog-edit β•‘
β•‘ β€’ 🎭 /api/ai/imitate-style β•‘
β•‘ β•‘
β•‘ πŸ“₯ Download: /api/video/download/:filename β•‘
β•‘ πŸ“š Documentation: /api/docs β•‘
β•‘ πŸ’š Health Check: /health β•‘
β•‘ β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
`);
});
}
async shutdown() {
console.log('\nπŸ›‘ Shutting down gracefully...');
this.io.emit('server:shutdown', {
message: 'Server is shutting down',
timestamp: new Date().toISOString()
});
this.io.close(() => {
console.log('βœ… Socket.IO closed');
});
this.httpServer.close(() => {
console.log('βœ… HTTP server closed');
process.exit(0);
});
setTimeout(() => {
console.error('❌ Force shutdown after timeout');
process.exit(1);
}, 10000);
}
}
const server = new TikVibeServer();
process.on('SIGTERM', () => server.shutdown());
process.on('SIGINT', () => server.shutdown());
process.on('uncaughtException', (err) => {
console.error('❌ Uncaught Exception:', err);
server.shutdown();
});
process.on('unhandledRejection', (err) => {
console.error('❌ Unhandled Rejection:', err);
server.shutdown();
});
server.start().catch(err => {
console.error('❌ Failed to start server:', err);
process.exit(1);
});