mes-engine 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.json +7 -0
- package/README.md +94 -85
- package/dist/index.js +243 -24
- package/dist/index.js.map +1 -1
- package/dist/{types → src}/core/VideoEngine.d.ts +2 -1
- package/dist/{types → src}/core/types.d.ts +12 -0
- package/dist/src/engines/FFmpegEngine.d.ts +7 -0
- package/dist/{types/engines/FFmpegEngine.d.ts → src/engines/GStreamerEngine.d.ts} +2 -1
- package/dist/src/index.d.ts +12 -0
- package/dist/{types → src}/processor.d.ts +5 -5
- package/dist/{types → src}/storage/FileSystemStorage.d.ts +1 -1
- package/dist/{types → src}/streaming/StreamManager.d.ts +1 -1
- package/dist/tests/video-processor.test.d.ts +1 -0
- package/docs/API.md +109 -0
- package/docs/HLS.md +54 -0
- package/docs/README.md +172 -169
- package/docs/caching.md +62 -0
- package/docs/engines.md +62 -58
- package/docs/storage.md +57 -0
- package/examples/full-demo/backend/.env +6 -0
- package/examples/full-demo/backend/package-lock.json +1783 -0
- package/examples/full-demo/backend/package.json +22 -0
- package/examples/full-demo/backend/src/routes/video.js +92 -0
- package/examples/full-demo/backend/src/server.js +43 -0
- package/examples/full-demo/backend/src/services/videoProcessor.js +85 -0
- package/examples/full-demo/frontend/index.html +13 -0
- package/examples/full-demo/frontend/package-lock.json +5791 -0
- package/examples/full-demo/frontend/package.json +32 -0
- package/examples/full-demo/frontend/postcss.config.js +6 -0
- package/examples/full-demo/frontend/src/App.jsx +113 -0
- package/examples/full-demo/frontend/src/components/ProcessingStatus.jsx +71 -0
- package/examples/full-demo/frontend/src/components/VideoPlayer.jsx +87 -0
- package/examples/full-demo/frontend/src/components/VideoUploader.jsx +62 -0
- package/examples/full-demo/frontend/src/index.css +3 -0
- package/examples/full-demo/frontend/src/main.jsx +10 -0
- package/examples/full-demo/frontend/src/services/api.js +30 -0
- package/examples/full-demo/frontend/tailwind.config.js +10 -0
- package/examples/full-demo/frontend/vite.config.js +16 -0
- package/examples/simple-usage/README.md +31 -0
- package/examples/simple-usage/index.ts +68 -0
- package/examples/simple-usage/package-lock.json +592 -0
- package/examples/simple-usage/package.json +15 -0
- package/package.json +69 -48
- package/rollup.config.js +3 -1
- package/src/bandwidth.ts +1 -1
- package/src/core/VideoEngine.ts +29 -4
- package/src/core/events.ts +9 -1
- package/src/core/types.ts +38 -3
- package/src/engines/FFmpegEngine.ts +173 -32
- package/src/engines/GStreamerEngine.ts +24 -1
- package/src/index.ts +13 -13
- package/src/processor.ts +119 -35
- package/src/storage/FileSystemStorage.ts +2 -4
- package/src/storage/StorageProvider.ts +7 -2
- package/src/streaming/StreamManager.ts +4 -5
- package/tests/video-processor.test.ts +32 -12
- package/tsconfig.json +19 -5
- package/tsconfig.test.json +17 -4
- package/dist/types/index.d.ts +0 -10
- package/dist/{types → src}/bandwidth.d.ts +0 -0
- package/dist/{types → src}/cache/ExternalCache.d.ts +0 -0
- package/dist/{types → src}/cache/LRU.d.ts +2 -2
- /package/dist/{types → src}/cache/cacheStrategy.d.ts +0 -0
- /package/dist/{types → src}/cache/internalCache.d.ts +0 -0
- /package/dist/{types → src}/core/events.d.ts +0 -0
- /package/dist/{types → src}/storage/StorageProvider.d.ts +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mes-engine-demo-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node src/server.js",
|
|
7
|
+
"dev": "nodemon src/server.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"express": "^4.18.2",
|
|
11
|
+
"mes-engine": "file:../../..",
|
|
12
|
+
"ffmpeg-static": "^5.2.0",
|
|
13
|
+
"ffprobe-static": "^3.1.0",
|
|
14
|
+
"multer": "^2.0.2",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"dotenv": "^16.3.1",
|
|
17
|
+
"socket.io": "^4.6.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"nodemon": "^3.0.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import multer from 'multer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import videoProcessorService from '../services/videoProcessor.js';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
|
|
11
|
+
// Configure multer for file upload
|
|
12
|
+
const storage = multer.diskStorage({
|
|
13
|
+
destination: (req, file, cb) => {
|
|
14
|
+
cb(null, path.join(__dirname, '../../uploads'));
|
|
15
|
+
},
|
|
16
|
+
filename: (req, file, cb) => {
|
|
17
|
+
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
18
|
+
cb(null, uniqueSuffix + path.extname(file.originalname));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const upload = multer({
|
|
23
|
+
storage,
|
|
24
|
+
limits: {
|
|
25
|
+
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 500 * 1024 * 1024
|
|
26
|
+
},
|
|
27
|
+
fileFilter: (req, file, cb) => {
|
|
28
|
+
const allowedTypes = /mp4|mov|avi|mkv|webm/;
|
|
29
|
+
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
|
|
30
|
+
const mimetype = allowedTypes.test(file.mimetype);
|
|
31
|
+
|
|
32
|
+
if (extname && mimetype) {
|
|
33
|
+
return cb(null, true);
|
|
34
|
+
}
|
|
35
|
+
cb(new Error('Only video files are allowed!'));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Upload and process video
|
|
40
|
+
router.post('/upload', upload.single('video'), async (req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
if (!req.file) {
|
|
43
|
+
return res.status(400).json({ error: 'No video file uploaded' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const socketId = req.body.socketId;
|
|
47
|
+
const io = req.app.get('io');
|
|
48
|
+
|
|
49
|
+
// Start processing asynchronously
|
|
50
|
+
videoProcessorService.processVideo(req.file.path, socketId, io)
|
|
51
|
+
.catch(error => {
|
|
52
|
+
console.error('Processing error:', error);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
res.json({
|
|
56
|
+
message: 'Video uploaded successfully. Processing started.',
|
|
57
|
+
filename: req.file.filename,
|
|
58
|
+
size: req.file.size
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
res.status(500).json({ error: error.message });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Get video manifest
|
|
66
|
+
router.get('/manifest/:videoId', async (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const manifest = await videoProcessorService.getManifest(req.params.videoId);
|
|
69
|
+
res.json(manifest);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
res.status(404).json({ error: 'Manifest not found' });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Stream video chunk
|
|
76
|
+
router.get('/stream/:videoId/:quality/:chunk', async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const { videoId, quality, chunk } = req.params;
|
|
79
|
+
const stream = await videoProcessorService.streamChunk(
|
|
80
|
+
videoId,
|
|
81
|
+
parseInt(quality),
|
|
82
|
+
parseInt(chunk)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
res.setHeader('Content-Type', 'video/mp4');
|
|
86
|
+
stream.pipe(res);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
res.status(404).json({ error: 'Chunk not found' });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default router;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
import { Server } from 'socket.io';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import videoRoutes from './routes/video.js';
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
const httpServer = createServer(app);
|
|
12
|
+
const io = new Server(httpServer, {
|
|
13
|
+
cors: {
|
|
14
|
+
origin: "http://localhost:5173",
|
|
15
|
+
methods: ["GET", "POST"]
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Middleware
|
|
20
|
+
app.use(cors());
|
|
21
|
+
app.use(express.json());
|
|
22
|
+
app.use(express.static('processed'));
|
|
23
|
+
|
|
24
|
+
// Make io available to routes
|
|
25
|
+
app.set('io', io);
|
|
26
|
+
|
|
27
|
+
// Routes
|
|
28
|
+
app.use('/api/video', videoRoutes);
|
|
29
|
+
|
|
30
|
+
// Socket.io connection
|
|
31
|
+
io.on('connection', (socket) => {
|
|
32
|
+
console.log('Client connected:', socket.id);
|
|
33
|
+
|
|
34
|
+
socket.on('disconnect', () => {
|
|
35
|
+
console.log('Client disconnected:', socket.id);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const PORT = process.env.PORT || 3001;
|
|
40
|
+
|
|
41
|
+
httpServer.listen(PORT, () => {
|
|
42
|
+
console.log(`Server running on port ${PORT}`);
|
|
43
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VideoProcessor,
|
|
3
|
+
FFmpegEngine,
|
|
4
|
+
FileSystemStorage,
|
|
5
|
+
InternalCache
|
|
6
|
+
} from 'mes-engine';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
export class VideoProcessorService {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.processor = new VideoProcessor(
|
|
15
|
+
new FFmpegEngine(),
|
|
16
|
+
new FileSystemStorage(path.join(__dirname, '../../processed')),
|
|
17
|
+
{
|
|
18
|
+
chunkSize: 10, // 10 seconds
|
|
19
|
+
cacheDir: path.join(__dirname, '../../processed'),
|
|
20
|
+
maxCacheSize: parseInt(process.env.CACHE_SIZE) || 100 * 1024 * 1024, // 100MB
|
|
21
|
+
defaultQualities: [
|
|
22
|
+
{ height: 1080, bitrate: '5000k' },
|
|
23
|
+
{ height: 720, bitrate: '2500k' },
|
|
24
|
+
{ height: 480, bitrate: '1000k' },
|
|
25
|
+
{ height: 360, bitrate: '500k' }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
this.setupEventListeners();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setupEventListeners() {
|
|
34
|
+
// The current VideoProcessor doesn't emit 'progress',
|
|
35
|
+
// it emits CHUNK_PROCESSED, QUALITY_PROCESSED, etc.
|
|
36
|
+
this.processor.on('chunkProcessed', (data) => {
|
|
37
|
+
console.log('Chunk processed:', data);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.processor.on('processingComplete', (data) => {
|
|
41
|
+
console.log('Processing complete:', data);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.processor.on('error', (error) => {
|
|
45
|
+
console.error('Processing error:', error);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async processVideo(filePath, socketId, io) {
|
|
50
|
+
try {
|
|
51
|
+
// Emit processing started
|
|
52
|
+
io.to(socketId).emit('processing:started', { filePath });
|
|
53
|
+
|
|
54
|
+
// Process video
|
|
55
|
+
// Note: VideoProcessor.processVideo(inputPath, options)
|
|
56
|
+
const manifest = await this.processor.processVideo(filePath, {
|
|
57
|
+
title: path.basename(filePath)
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Emit processing complete
|
|
61
|
+
io.to(socketId).emit('processing:complete', { manifest });
|
|
62
|
+
|
|
63
|
+
return manifest;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
io.to(socketId).emit('processing:error', { error: error.message });
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async streamChunk(videoId, quality, chunkNumber) {
|
|
71
|
+
return await this.processor.streamChunk(videoId, quality, chunkNumber);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getManifest(videoId) {
|
|
75
|
+
// In actual implementation, we'd read from disk or memory.
|
|
76
|
+
// For the demo, let's assume we can fetch it.
|
|
77
|
+
// The VideoProcessor doesn't have a getManifest method.
|
|
78
|
+
const manifestPath = path.join(__dirname, '../../processed', videoId, 'manifest.json');
|
|
79
|
+
const data = await fs.promises.readFile(manifestPath, 'utf8');
|
|
80
|
+
return JSON.parse(data);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
import fs from 'fs';
|
|
85
|
+
export default new VideoProcessorService();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>MES-Engine Demo</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|