Spaces:
Sleeping
Sleeping
File size: 4,539 Bytes
bc18ad5 23d4307 a79e363 bc18ad5 23d4307 a79e363 bc18ad5 a79e363 bc18ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
/**
* 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}`);
});
|