95 lines
2.9 KiB
JavaScript
95 lines
2.9 KiB
JavaScript
// video-stream.js
|
|
const express = require('express');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const readline = require('readline');
|
|
const mime = require('mime-types'); // npm install mime-types
|
|
|
|
const app = express();
|
|
const PORT = 4002;
|
|
|
|
// CORS headers for Watchparty / browsers
|
|
app.use((req, res, next) => {
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Range, Origin, X-Requested-With, Content-Type, Accept');
|
|
next();
|
|
});
|
|
|
|
// Ask user for video directory
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
|
|
rl.question('Enter full path to video directory: ', (videoDir) => {
|
|
videoDir = path.resolve(videoDir.trim());
|
|
if (!fs.existsSync(videoDir)) {
|
|
console.error('❌ Directory does not exist!');
|
|
process.exit(1);
|
|
}
|
|
|
|
const allowedExtensions = ['.mp4', '.mkv', '.webm', '.mov', '.avi', '.flv', '.ogg'];
|
|
const videos = fs.readdirSync(videoDir).filter(f =>
|
|
allowedExtensions.includes(path.extname(f).toLowerCase())
|
|
);
|
|
|
|
if (videos.length === 0) {
|
|
console.error('❌ No video files found!');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Generate random serial numbers
|
|
const serialMap = {};
|
|
videos.forEach(video => {
|
|
const serial = Math.random().toString(36).slice(2, 10).toUpperCase();
|
|
serialMap[serial] = video;
|
|
// VPS domain URL (change if needed)
|
|
console.log(`🎥 ${video} -> Serial URL: https://stream.arulbalaji.xyz/videos/serial/${serial}`);
|
|
});
|
|
|
|
// Serve video by serial
|
|
app.get('/videos/serial/:code', (req, res) => {
|
|
const code = req.params.code;
|
|
const filename = serialMap[code];
|
|
if (!filename) return res.status(404).send('Invalid or expired serial');
|
|
|
|
const filePath = path.join(videoDir, filename);
|
|
if (!fs.existsSync(filePath)) return res.status(404).send('File missing');
|
|
|
|
const stat = fs.statSync(filePath);
|
|
const fileSize = stat.size;
|
|
const range = req.headers.range;
|
|
const contentType = mime.lookup(path.extname(filename)) || 'application/octet-stream';
|
|
|
|
if (range) {
|
|
const parts = range.replace(/bytes=/, "").split("-");
|
|
const start = parseInt(parts[0], 10);
|
|
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
|
const chunkSize = (end - start) + 1;
|
|
const file = fs.createReadStream(filePath, { start, end });
|
|
res.writeHead(206, {
|
|
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Length': chunkSize,
|
|
'Content-Type': contentType
|
|
});
|
|
file.pipe(res);
|
|
} else {
|
|
res.writeHead(200, {
|
|
'Content-Length': fileSize,
|
|
'Content-Type': contentType,
|
|
'Accept-Ranges': 'bytes'
|
|
});
|
|
fs.createReadStream(filePath).pipe(res);
|
|
}
|
|
});
|
|
|
|
app.listen(PORT, '127.0.0.1', () => {
|
|
console.log(`✅ Serial video server running on 127.0.0.1:${PORT}`);
|
|
});
|
|
|
|
rl.close();
|
|
});
|
|
|