Spaces:
Sleeping
Sleeping
| /** | |
| * VEditor Render Server | |
| * | |
| * Standalone Express API for video rendering using Remotion. | |
| * Designed for deployment on Hugging Face Spaces (port 7860). | |
| */ | |
| import express from 'express'; | |
| import cors from 'cors'; | |
| import path from 'path'; | |
| import fs from 'fs'; | |
| import { v4 as uuidv4 } from 'uuid'; | |
| import { renderVideo } from './render'; | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| const VIDEOS_DIR = path.join(process.cwd(), 'videos'); | |
| const JOBS_DIR = path.join(process.cwd(), 'jobs'); | |
| // Ensure directories exist | |
| [VIDEOS_DIR, JOBS_DIR].forEach(dir => { | |
| if (!fs.existsSync(dir)) { | |
| fs.mkdirSync(dir, { recursive: true }); | |
| } | |
| }); | |
| // Middleware | |
| app.use(cors()); | |
| app.use(express.json({ limit: '50mb' })); | |
| app.use('/videos', express.static(VIDEOS_DIR)); | |
| // Health check | |
| app.get('/', (req, res) => { | |
| res.json({ | |
| status: 'ok', | |
| service: 'VEditor Render Server', | |
| version: '1.0.0' | |
| }); | |
| }); | |
| // Start render job | |
| app.post('/render', async (req, res) => { | |
| try { | |
| const { design, options } = req.body; | |
| if (!design) { | |
| return res.status(400).json({ error: 'Design is required' }); | |
| } | |
| const jobId = `job_${Date.now()}_${uuidv4().substring(0, 8)}`; | |
| const jobFile = path.join(JOBS_DIR, `${jobId}.json`); | |
| const statusFile = path.join(JOBS_DIR, `${jobId}.status.json`); | |
| // Save job data | |
| fs.writeFileSync(jobFile, JSON.stringify({ design, options }, null, 2)); | |
| fs.writeFileSync(statusFile, JSON.stringify({ | |
| status: 'pending', | |
| progress: 0 | |
| })); | |
| console.log(`[Render] Starting job: ${jobId}`); | |
| // Start render in background | |
| renderVideo(jobId, design, options || {}) | |
| .then((firebaseUrl) => { | |
| // Recalculate path to ensure correct file is updated | |
| const completedStatusFile = path.join(JOBS_DIR, `${jobId}.status.json`); | |
| const completedData = JSON.stringify({ | |
| status: 'completed', | |
| progress: 100, | |
| outputUrl: firebaseUrl | |
| }); | |
| try { | |
| fs.writeFileSync(completedStatusFile, completedData); | |
| console.log(`[Render] Status file updated: ${completedStatusFile}`); | |
| console.log(`[Render] Status data: ${completedData}`); | |
| console.log(`[Render] Completed: ${jobId} -> ${firebaseUrl}`); | |
| } catch (writeError) { | |
| console.error(`[Render] Failed to write status file:`, writeError); | |
| } | |
| }) | |
| .catch((error) => { | |
| console.error(`[Render] Failed: ${jobId}`, error); | |
| const failedStatusFile = path.join(JOBS_DIR, `${jobId}.status.json`); | |
| try { | |
| fs.writeFileSync(failedStatusFile, JSON.stringify({ | |
| status: 'failed', | |
| progress: 0, | |
| error: error.message || 'Unknown error' | |
| })); | |
| } catch (writeError) { | |
| console.error(`[Render] Failed to write error status:`, writeError); | |
| } | |
| }); | |
| res.status(202).json({ | |
| jobId, | |
| status: 'pending', | |
| statusUrl: `/render/${jobId}` | |
| }); | |
| } catch (error) { | |
| console.error('[Render] Error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Check render status | |
| app.get('/render/:jobId', (req, res) => { | |
| const { jobId } = req.params; | |
| const statusFile = path.join(JOBS_DIR, `${jobId}.status.json`); | |
| if (!fs.existsSync(statusFile)) { | |
| return res.status(404).json({ error: 'Job not found' }); | |
| } | |
| const status = JSON.parse(fs.readFileSync(statusFile, 'utf-8')); | |
| res.json({ jobId, ...status }); | |
| }); | |
| // List all jobs | |
| app.get('/jobs', (req, res) => { | |
| const files = fs.readdirSync(JOBS_DIR).filter(f => f.endsWith('.status.json')); | |
| const jobs = files.map(f => { | |
| const jobId = f.replace('.status.json', ''); | |
| const status = JSON.parse(fs.readFileSync(path.join(JOBS_DIR, f), 'utf-8')); | |
| return { jobId, ...status }; | |
| }); | |
| res.json(jobs); | |
| }); | |
| // Start server | |
| app.listen(PORT, () => { | |
| console.log(`🎬 VEditor Render Server running on port ${PORT}`); | |
| console.log(`📁 Videos directory: ${VIDEOS_DIR}`); | |
| console.log(`📋 Jobs directory: ${JOBS_DIR}`); | |
| }); | |