| 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; |
|
|
| |
| dotenv.config(); |
|
|
| class TikVibeServer { |
| constructor() { |
| this.app = express(); |
| this.httpServer = createServer(this.app); |
| |
| |
| this.io = new Server(this.httpServer, { |
| cors: { |
| origin: '*', |
| credentials: true, |
| methods: ['GET', 'POST'] |
| }, |
| maxHttpBufferSize: 1e8 |
| }); |
|
|
| 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...'); |
| |
| |
| 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() { |
| |
| this.app.use(helmet({ |
| contentSecurityPolicy: false, |
| crossOriginEmbedderPolicy: false, |
| crossOriginResourcePolicy: { policy: "cross-origin" } |
| })); |
| |
| |
| this.app.use(cors({ |
| origin: '*', |
| credentials: true, |
| methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], |
| allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] |
| })); |
| |
| |
| this.app.use(compression({ |
| level: 6, |
| threshold: 1024, |
| filter: (req, res) => { |
| if (req.headers['x-no-compression']) return false; |
| return compression.filter(req, res); |
| } |
| })); |
| |
| |
| this.app.use(express.json({ limit: '500mb' })); |
| this.app.use(express.urlencoded({ extended: true, limit: '500mb' })); |
| |
| |
| this.app.use('/uploads', express.static('uploads')); |
| this.app.use('/renders', express.static('renders')); |
| |
| |
| 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() { |
| |
| 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 |
| } |
| }); |
| }); |
|
|
| |
| 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' |
| } |
| } |
| }); |
| }); |
|
|
| |
| 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> |
| `); |
| }); |
|
|
| |
| 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' |
| ] |
| }); |
| }); |
|
|
| |
| 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] |
| } |
| }); |
| }); |
|
|
| |
| 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 |
| }); |
| }); |
|
|
| |
| const aiRouter = require('./routes/ai'); |
| this.app.use('/api/ai', aiRouter); |
|
|
| |
| this.app.get('/api/video/download/:filename', async (req, res) => { |
| try { |
| const filename = req.params.filename; |
| |
| const safeFilename = path.basename(filename); |
| const filepath = path.join(__dirname, '../renders', safeFilename); |
| |
| console.log('π₯ Download requested:', safeFilename); |
| console.log('Full path:', filepath); |
| |
| |
| 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' |
| }); |
| } |
| |
| |
| const stats = await fs.stat(filepath); |
| console.log(`File size: ${stats.size} bytes`); |
| |
| |
| res.setHeader('Content-Disposition', `attachment; filename="${safeFilename}"`); |
| res.setHeader('Content-Type', 'video/mp4'); |
| res.setHeader('Content-Length', stats.size); |
| |
| |
| 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 |
| }); |
| } |
| }); |
|
|
| |
| this.app.get('/api/render/progress/:renderId', (req, res) => { |
| const { renderId } = req.params; |
| const progress = this.activeRenders.get(renderId) || 0; |
| res.json({ renderId, progress }); |
| }); |
|
|
| |
| 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() { |
| |
| 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); |
| |
| |
| const maxAge = dir === 'renders' ? 86400000 : 3600000; |
| 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); |
| } |
|
|
| 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); |
| }); |