mes-engine 0.0.1
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/CONTRIBUTING.md +199 -0
- package/README.md +86 -0
- package/dist/index.js +301 -0
- package/dist/index.js.map +1 -0
- package/dist/types/bandwidth.d.ts +8 -0
- package/dist/types/cache/ExternalCache.d.ts +11 -0
- package/dist/types/cache/LRU.d.ts +8 -0
- package/dist/types/cache/cacheStrategy.d.ts +12 -0
- package/dist/types/cache/internalCache.d.ts +13 -0
- package/dist/types/core/VideoEngine.d.ts +6 -0
- package/dist/types/core/events.d.ts +6 -0
- package/dist/types/core/types.d.ts +20 -0
- package/dist/types/engines/FFmpegEngine.d.ts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/processor.d.ts +19 -0
- package/dist/types/storage/FileSystemStorage.d.ts +6 -0
- package/dist/types/storage/StorageProvider.d.ts +5 -0
- package/dist/types/streaming/StreamManager.d.ts +10 -0
- package/docs/README.md +170 -0
- package/docs/engines.md +58 -0
- package/package.json +48 -0
- package/rollup.config.js +24 -0
- package/src/bandwidth.ts +30 -0
- package/src/cache/ExternalCache.ts +49 -0
- package/src/cache/LRU.ts +35 -0
- package/src/cache/cacheStrategy.ts +15 -0
- package/src/cache/internalCache.ts +60 -0
- package/src/core/VideoEngine.ts +16 -0
- package/src/core/events.ts +7 -0
- package/src/core/types.ts +26 -0
- package/src/engines/FFmpegEngine.ts +51 -0
- package/src/engines/GStreamerEngine.ts +45 -0
- package/src/index.ts +13 -0
- package/src/processor.ts +90 -0
- package/src/storage/FileSystemStorage.ts +19 -0
- package/src/storage/StorageProvider.ts +7 -0
- package/src/streaming/StreamManager.ts +26 -0
- package/tests/video-processor.test.ts +247 -0
- package/tsconfig.json +16 -0
- package/tsconfig.test.json +16 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CacheStrategy, CacheOptions } from './cacheStrategy';
|
|
2
|
+
import { StorageProvider } from '../storage/StorageProvider';
|
|
3
|
+
export declare class InternalCache extends CacheStrategy {
|
|
4
|
+
private cache;
|
|
5
|
+
private options;
|
|
6
|
+
private storage;
|
|
7
|
+
constructor(options: CacheOptions, storage: StorageProvider);
|
|
8
|
+
set(key: string, value: Buffer): Promise<void>;
|
|
9
|
+
get(key: string): Promise<Buffer | null>;
|
|
10
|
+
preload(key: string): Promise<void>;
|
|
11
|
+
clear(): Promise<void>;
|
|
12
|
+
private getNextChunkKey;
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { QualityLevel } from './types';
|
|
3
|
+
export declare abstract class VideoEngine extends EventEmitter {
|
|
4
|
+
abstract processChunk(inputPath: string, outputPath: string, startTime: number, quality: QualityLevel): Promise<void>;
|
|
5
|
+
abstract getDuration(inputPath: string): Promise<number>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface VideoConfig {
|
|
2
|
+
chunkSize: number;
|
|
3
|
+
cacheDir: string;
|
|
4
|
+
maxCacheSize: number;
|
|
5
|
+
defaultQualities: QualityLevel[];
|
|
6
|
+
}
|
|
7
|
+
export interface QualityLevel {
|
|
8
|
+
height: number;
|
|
9
|
+
bitrate: string;
|
|
10
|
+
}
|
|
11
|
+
export interface VideoChunk {
|
|
12
|
+
quality: number;
|
|
13
|
+
number: number;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
export interface VideoManifest {
|
|
17
|
+
videoId: string;
|
|
18
|
+
qualities: QualityLevel[];
|
|
19
|
+
chunks: VideoChunk[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { VideoEngine } from '../core/VideoEngine';
|
|
2
|
+
import { QualityLevel } from '../core/types';
|
|
3
|
+
export declare class FFmpegEngine extends VideoEngine {
|
|
4
|
+
processChunk(inputPath: string, outputPath: string, startTime: number, quality: QualityLevel): Promise<void>;
|
|
5
|
+
getDuration(inputPath: string): Promise<number>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './core/types';
|
|
2
|
+
export * from './core/events';
|
|
3
|
+
export * from './core/VideoEngine';
|
|
4
|
+
export * from './engines/FFmpegEngine';
|
|
5
|
+
export * from './storage/StorageProvider';
|
|
6
|
+
export * from './storage/FileSystemStorage';
|
|
7
|
+
export * from './cache/cacheStrategy';
|
|
8
|
+
export * from './cache/internalCache';
|
|
9
|
+
export * from './cache/ExternalCache';
|
|
10
|
+
export * from './processor';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VideoEngine } from './core/VideoEngine';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { StorageProvider } from './storage/StorageProvider';
|
|
4
|
+
import { VideoConfig, VideoManifest } from './core/types';
|
|
5
|
+
import { Readable } from 'stream';
|
|
6
|
+
export declare class VideoProcessor extends EventEmitter {
|
|
7
|
+
private engine;
|
|
8
|
+
private storage;
|
|
9
|
+
private streamManager;
|
|
10
|
+
private config;
|
|
11
|
+
constructor(engine: VideoEngine, storage: StorageProvider, config: VideoConfig);
|
|
12
|
+
processVideo(inputPath: string): Promise<VideoManifest>;
|
|
13
|
+
streamChunk(videoId: string, quality: number, chunkNumber: number, range?: {
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
}): Promise<Readable>;
|
|
17
|
+
private getChunkPath;
|
|
18
|
+
private generateVideoId;
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StorageProvider } from './StorageProvider';
|
|
2
|
+
export declare class FileSystemStorage extends StorageProvider {
|
|
3
|
+
saveChunk(chunkPath: string, data: Buffer): Promise<void>;
|
|
4
|
+
getChunk(chunkPath: string): Promise<Buffer>;
|
|
5
|
+
deleteChunk(chunkPath: string): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
import { StorageProvider } from '../storage/StorageProvider';
|
|
3
|
+
export declare class StreamManager {
|
|
4
|
+
private storage;
|
|
5
|
+
constructor(storage: StorageProvider);
|
|
6
|
+
createStream(chunkPath: string, range?: {
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
}): Promise<Readable>;
|
|
10
|
+
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Video Processing Framework Documentation
|
|
2
|
+
|
|
3
|
+
Comprehensive documentation for the mes-engine video processing framework.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Getting Started](#getting-started)
|
|
8
|
+
2. [Core Concepts](#core-concepts)
|
|
9
|
+
3. [Components](#components)
|
|
10
|
+
4. [Advanced Usage](#advanced-usage)
|
|
11
|
+
5. [API Reference](#api-reference)
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
```bash
|
|
17
|
+
npm install mes-engine
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Engine Requirements
|
|
21
|
+
|
|
22
|
+
#### FFmpeg Engine
|
|
23
|
+
```bash
|
|
24
|
+
npm install ffmpeg-static
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### GStreamer Engine
|
|
28
|
+
Requires GStreamer to be installed on your system:
|
|
29
|
+
- Ubuntu/Debian: `apt-get install gstreamer1.0-tools`
|
|
30
|
+
- MacOS: `brew install gstreamer`
|
|
31
|
+
- Windows: Download from GStreamer website
|
|
32
|
+
|
|
33
|
+
### Basic Configuration
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { VideoProcessor, FFmpegEngine, FileSystemStorage } from 'mes-engine';
|
|
37
|
+
|
|
38
|
+
const config = {
|
|
39
|
+
chunkSize: 10, // seconds
|
|
40
|
+
cacheDir: './cache',
|
|
41
|
+
maxCacheSize: 1000, // MB
|
|
42
|
+
defaultQualities: [
|
|
43
|
+
{ height: 1080, bitrate: '4000k' },
|
|
44
|
+
{ height: 720, bitrate: '2500k' },
|
|
45
|
+
{ height: 480, bitrate: '1000k' }
|
|
46
|
+
]
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const processor = new VideoProcessor({
|
|
50
|
+
engine: new FFmpegEngine(),
|
|
51
|
+
storage: new FileSystemStorage(),
|
|
52
|
+
config
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Core Concepts
|
|
57
|
+
|
|
58
|
+
### Video Processing Flow
|
|
59
|
+
|
|
60
|
+
1. **Input**: Raw video file
|
|
61
|
+
2. **Chunking**: Video split into segments
|
|
62
|
+
3. **Transcoding**: Multiple quality versions
|
|
63
|
+
4. **Storage**: Chunks saved to storage provider
|
|
64
|
+
5. **Streaming**: Adaptive delivery
|
|
65
|
+
|
|
66
|
+
### Events System
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
processor.on(VideoEvent.CHUNK_PROCESSED, (data) => {
|
|
70
|
+
console.log(`Chunk ${data.chunkNumber} processed at ${data.quality}p`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
processor.on(VideoEvent.PROCESSING_COMPLETE, (manifest) => {
|
|
74
|
+
console.log('Processing complete:', manifest);
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Components
|
|
79
|
+
|
|
80
|
+
### Processing Engines
|
|
81
|
+
|
|
82
|
+
- **FFmpegEngine**: Standard video processing
|
|
83
|
+
- **GStreamerEngine**: High-performance alternative
|
|
84
|
+
- **Custom Engines**: Extend `VideoEngine` class
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
class CustomEngine extends VideoEngine {
|
|
88
|
+
async processChunk(
|
|
89
|
+
inputPath: string,
|
|
90
|
+
outputPath: string,
|
|
91
|
+
startTime: number,
|
|
92
|
+
quality: QualityLevel
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
// Implementation
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getDuration(inputPath: string): Promise<number> {
|
|
98
|
+
// Implementation
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Storage Providers
|
|
104
|
+
|
|
105
|
+
- **FileSystemStorage**: Local file system storage
|
|
106
|
+
- **Custom Storage**: Implement `StorageProvider`
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
class CustomStorage extends StorageProvider {
|
|
110
|
+
async saveChunk(chunkPath: string, data: Buffer): Promise<void> {
|
|
111
|
+
// Implementation
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getChunk(chunkPath: string): Promise<Buffer> {
|
|
115
|
+
// Implementation
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async deleteChunk(chunkPath: string): Promise<void> {
|
|
119
|
+
// Implementation
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Caching Strategies
|
|
125
|
+
|
|
126
|
+
- **InternalCache**: Memory-based LRU cache
|
|
127
|
+
- **ExternalCache**: Remote cache service
|
|
128
|
+
- **Custom Cache**: Extend `CacheStrategy`
|
|
129
|
+
|
|
130
|
+
## Advanced Usage
|
|
131
|
+
|
|
132
|
+
### Custom Quality Levels
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const customQualities = [
|
|
136
|
+
{ height: 2160, bitrate: '8000k' }, // 4K
|
|
137
|
+
{ height: 1440, bitrate: '6000k' }, // 2K
|
|
138
|
+
{ height: 1080, bitrate: '4000k' }, // Full HD
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const manifest = await processor.processVideo('input.mp4', {
|
|
142
|
+
qualities: customQualities
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Bandwidth-Aware Streaming
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { BandwidthDetector } from 'mes-engine';
|
|
150
|
+
|
|
151
|
+
const detector = new BandwidthDetector();
|
|
152
|
+
detector.addSample(bytesTransferred, durationMs);
|
|
153
|
+
const estimatedBandwidth = detector.getEstimatedBandwidth();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## API Reference
|
|
157
|
+
|
|
158
|
+
See [API.md](./API.md) for detailed API documentation.
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm run test # Run all tests
|
|
164
|
+
npm run test:watch # Watch mode
|
|
165
|
+
npm run test:coverage # Coverage report
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Contributing
|
|
169
|
+
|
|
170
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md) for contribution guidelines.
|
package/docs/engines.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Engines Support
|
|
2
|
+
|
|
3
|
+
## FFmpegEngine
|
|
4
|
+
The `FFmpegEngine` uses `ffmpeg` for video processing. You will need to install an FFmpeg binary to use this engine.
|
|
5
|
+
|
|
6
|
+
### Installation of FFmpeg
|
|
7
|
+
To use the `FFmpegEngine`, you must install FFmpeg or use `ffmpeg-static` to include the FFmpeg binary. You can install `ffmpeg-static` by running:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install ffmpeg-static
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Alternatively, you can use any other FFmpeg binary that is compatible with your system.
|
|
14
|
+
|
|
15
|
+
### Example Usage:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { FFmpegEngine } from 'mes-engine';
|
|
19
|
+
|
|
20
|
+
// Create an FFmpeg engine instance
|
|
21
|
+
const engine = new FFmpegEngine();
|
|
22
|
+
|
|
23
|
+
// Use it to process video chunks
|
|
24
|
+
engine.processChunk('input.mp4', 'output.mp4', 0, { height: 720, bitrate: '2000k' })
|
|
25
|
+
.then(() => {
|
|
26
|
+
console.log('Chunk processed successfully');
|
|
27
|
+
})
|
|
28
|
+
.catch(error => {
|
|
29
|
+
console.error('Error processing chunk:', error);
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Custom Engines
|
|
34
|
+
You can implement your own video processing engine by extending the `VideoEngine` class. Here’s an example of how to create a custom engine:
|
|
35
|
+
|
|
36
|
+
### Example Custom Engine:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { VideoEngine } from 'mes-engine';
|
|
40
|
+
|
|
41
|
+
class CustomVideoEngine extends VideoEngine {
|
|
42
|
+
async processChunk(inputPath: string, outputPath: string, startTime: number, quality: any): Promise<void> {
|
|
43
|
+
// Implement custom video processing logic here
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getDuration(inputPath: string): Promise<number> {
|
|
47
|
+
// Implement custom duration calculation logic here
|
|
48
|
+
return 120; // Example
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For more details on implementing custom engines, see the [VideoEngine class](../src/core/VideoEngine.ts) in the codebase.
|
|
54
|
+
|
|
55
|
+
## Other Supported Engines
|
|
56
|
+
You can extend this framework with any other video processing engine that implements the `VideoEngine` interface.
|
|
57
|
+
|
|
58
|
+
For more details, check the [source code](https://github.com/Bum-Ho12/mes-engine) and adapt it as needed for your requirements.
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mes-engine",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "mes-engine is a video processing engine that handles video chunking, processing and other video high density and intensive processing and caching processes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mes",
|
|
7
|
+
"mes-engine",
|
|
8
|
+
"video",
|
|
9
|
+
"processing",
|
|
10
|
+
"video-framework",
|
|
11
|
+
"framework"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Bumho Nisubire",
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "mocha -r ts-node/register 'tests/**/*.test.ts' --timeout 10000",
|
|
22
|
+
"build": "rollup -c rollup.config.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@types/node-fetch": "^2.6.12",
|
|
26
|
+
"mes-engine": "file:",
|
|
27
|
+
"node-fetch": "^3.3.2",
|
|
28
|
+
"tslib": "^2.8.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@rollup/plugin-commonjs": "^28.0.2",
|
|
32
|
+
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
33
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
34
|
+
"@types/chai": "^4.3.11",
|
|
35
|
+
"@types/jest": "^29.5.14",
|
|
36
|
+
"@types/mocha": "^10.0.6",
|
|
37
|
+
"@types/node": "^22.10.5",
|
|
38
|
+
"chai": "^4.3.10",
|
|
39
|
+
"jest": "^29.7.0",
|
|
40
|
+
"mocha": "^10.8.2",
|
|
41
|
+
"rollup": "^4.30.1",
|
|
42
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
43
|
+
"ts-jest": "^29.2.5",
|
|
44
|
+
"ts-mocha": "^10.0.0",
|
|
45
|
+
"ts-node": "^10.9.2",
|
|
46
|
+
"typescript": "^5.3.3"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import typescript from '@rollup/plugin-typescript';
|
|
2
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
3
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
4
|
+
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
input: 'src/index.ts', // Entry point
|
|
8
|
+
output: [
|
|
9
|
+
{
|
|
10
|
+
file: 'dist/index.js', // Output file
|
|
11
|
+
format: 'es', // ES module format
|
|
12
|
+
sourcemap: true, // Enable source maps
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
plugins: [
|
|
16
|
+
peerDepsExternal(), // Externalize peer dependencies
|
|
17
|
+
nodeResolve(), // Resolve dependencies from node_modules
|
|
18
|
+
commonjs(), // Convert CommonJS to ES6
|
|
19
|
+
typescript({ tsconfig: './tsconfig.json' }), // TypeScript plugin
|
|
20
|
+
],
|
|
21
|
+
external: [
|
|
22
|
+
'node-fetch', // Prevent bundling node-fetch
|
|
23
|
+
],
|
|
24
|
+
};
|
package/src/bandwidth.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
// bandwidth.ts
|
|
3
|
+
class BandwidthDetector {
|
|
4
|
+
private samples: Array<[number, number]> = [];
|
|
5
|
+
private readonly windowSize = 10000; // 10 seconds in ms
|
|
6
|
+
|
|
7
|
+
addSample(bytesTransferred: number, durationMs: number): void {
|
|
8
|
+
const bandwidth = (bytesTransferred * 8) / (durationMs * 1000); // Mbps
|
|
9
|
+
const timestamp = Date.now();
|
|
10
|
+
this.samples.push([timestamp, bandwidth]);
|
|
11
|
+
this._cleanOldSamples();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getEstimatedBandwidth(): number {
|
|
15
|
+
if (this.samples.length === 0) return Infinity;
|
|
16
|
+
const bandwidths = this.samples.map(([, b]) => b);
|
|
17
|
+
return this._median(bandwidths);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private _cleanOldSamples(): void {
|
|
21
|
+
const cutoff = Date.now() - this.windowSize;
|
|
22
|
+
this.samples = this.samples.filter(([t]) => t > cutoff);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private _median(values: number[]): number {
|
|
26
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
27
|
+
const mid = Math.floor(sorted.length / 2);
|
|
28
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
// cache/ExternalCache.ts
|
|
3
|
+
import fetch from 'node-fetch';
|
|
4
|
+
import { CacheStrategy, CacheOptions } from './cacheStrategy';
|
|
5
|
+
|
|
6
|
+
export class ExternalCache extends CacheStrategy {
|
|
7
|
+
private baseUrl: string;
|
|
8
|
+
private options: CacheOptions;
|
|
9
|
+
|
|
10
|
+
constructor(options: CacheOptions) {
|
|
11
|
+
super();
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.baseUrl = options.externalCacheUrl!;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async set(key: string, value: Buffer): Promise<void> {
|
|
17
|
+
await fetch(`${this.baseUrl}/cache/${key}`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: value,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/octet-stream',
|
|
22
|
+
'TTL': this.options.ttl.toString()
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async get(key: string): Promise<Buffer | null> {
|
|
28
|
+
const response = await fetch(`${this.baseUrl}/cache/${key}`);
|
|
29
|
+
return response.ok ? Buffer.from(await response.arrayBuffer()) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async preload(key: string): Promise<void> {
|
|
33
|
+
if (this.options.preloadNextChunk) {
|
|
34
|
+
const nextChunkKey = this.getNextChunkKey(key);
|
|
35
|
+
await fetch(`${this.baseUrl}/preload/${nextChunkKey}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async clear(): Promise<void> {
|
|
40
|
+
await fetch(`${this.baseUrl}/cache`, { method: 'DELETE' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private getNextChunkKey(currentKey: string): string {
|
|
44
|
+
const parts = currentKey.split('_');
|
|
45
|
+
const currentChunk = parseInt(parts[parts.length - 1]);
|
|
46
|
+
parts[parts.length - 1] = (currentChunk + 1).toString();
|
|
47
|
+
return parts.join('_');
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/cache/LRU.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
// cache/LRU.ts
|
|
3
|
+
export class LRU<T> {
|
|
4
|
+
private maxSize: number;
|
|
5
|
+
private cache: Map<string, T>;
|
|
6
|
+
|
|
7
|
+
constructor(maxSize: number) {
|
|
8
|
+
this.maxSize = maxSize;
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set(key: string, value: T): void {
|
|
13
|
+
if (this.cache.has(key)) {
|
|
14
|
+
this.cache.delete(key);
|
|
15
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
16
|
+
const oldestKey = this.cache.keys().next().value;
|
|
17
|
+
if (oldestKey !== undefined) {
|
|
18
|
+
this.cache.delete(oldestKey);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
this.cache.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(key: string): T | undefined {
|
|
25
|
+
if (!this.cache.has(key)) return undefined;
|
|
26
|
+
const value = this.cache.get(key)!;
|
|
27
|
+
this.cache.delete(key);
|
|
28
|
+
this.cache.set(key, value);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clear(): void {
|
|
33
|
+
this.cache.clear();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
// cache/CacheStrategy.ts
|
|
3
|
+
export interface CacheOptions {
|
|
4
|
+
maxSize: number;
|
|
5
|
+
ttl: number;
|
|
6
|
+
preloadNextChunk: boolean;
|
|
7
|
+
externalCacheUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export abstract class CacheStrategy {
|
|
11
|
+
abstract set(key: string, value: Buffer): Promise<void>;
|
|
12
|
+
abstract get(key: string): Promise<Buffer | null>;
|
|
13
|
+
abstract preload(key: string): Promise<void>;
|
|
14
|
+
abstract clear(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
// cache/internalCache.ts
|
|
3
|
+
import { LRU } from './LRU';
|
|
4
|
+
import { CacheStrategy, CacheOptions } from './cacheStrategy';
|
|
5
|
+
import { StorageProvider } from '../storage/StorageProvider';
|
|
6
|
+
|
|
7
|
+
export class InternalCache extends CacheStrategy {
|
|
8
|
+
private cache: LRU<Buffer>;
|
|
9
|
+
private options: CacheOptions;
|
|
10
|
+
private storage: StorageProvider;
|
|
11
|
+
|
|
12
|
+
constructor(options: CacheOptions, storage: StorageProvider) {
|
|
13
|
+
super();
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.cache = new LRU(options.maxSize);
|
|
16
|
+
this.storage = storage;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async set(key: string, value: Buffer): Promise<void> {
|
|
20
|
+
this.cache.set(key, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async get(key: string): Promise<Buffer | null> {
|
|
24
|
+
const cached = this.cache.get(key);
|
|
25
|
+
if (cached) return cached;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const data = await this.storage.getChunk(key);
|
|
29
|
+
await this.set(key, data);
|
|
30
|
+
return data;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async preload(key: string): Promise<void> {
|
|
37
|
+
if (this.options.preloadNextChunk) {
|
|
38
|
+
const nextChunkKey = this.getNextChunkKey(key);
|
|
39
|
+
if (!this.cache.get(nextChunkKey)) {
|
|
40
|
+
try {
|
|
41
|
+
const data = await this.storage.getChunk(nextChunkKey);
|
|
42
|
+
await this.set(nextChunkKey, data);
|
|
43
|
+
} catch {
|
|
44
|
+
// Ignore preload failures
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async clear(): Promise<void> {
|
|
51
|
+
this.cache.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private getNextChunkKey(currentKey: string): string {
|
|
55
|
+
const parts = currentKey.split('_');
|
|
56
|
+
const currentChunk = parseInt(parts[parts.length - 1]);
|
|
57
|
+
parts[parts.length - 1] = (currentChunk + 1).toString();
|
|
58
|
+
return parts.join('_');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
// core/VideoEngine.ts
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { VideoConfig, QualityLevel, VideoManifest } from './types';
|
|
5
|
+
import { VideoEvent } from './events';
|
|
6
|
+
|
|
7
|
+
export abstract class VideoEngine extends EventEmitter {
|
|
8
|
+
abstract processChunk(
|
|
9
|
+
inputPath: string,
|
|
10
|
+
outputPath: string,
|
|
11
|
+
startTime: number,
|
|
12
|
+
quality: QualityLevel
|
|
13
|
+
): Promise<void>;
|
|
14
|
+
|
|
15
|
+
abstract getDuration(inputPath: string): Promise<number>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
// core/types.ts
|
|
3
|
+
|
|
4
|
+
export interface VideoConfig {
|
|
5
|
+
chunkSize: number;
|
|
6
|
+
cacheDir: string;
|
|
7
|
+
maxCacheSize: number;
|
|
8
|
+
defaultQualities: QualityLevel[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface QualityLevel {
|
|
12
|
+
height: number;
|
|
13
|
+
bitrate: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface VideoChunk {
|
|
17
|
+
quality: number;
|
|
18
|
+
number: number;
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VideoManifest {
|
|
23
|
+
videoId: string;
|
|
24
|
+
qualities: QualityLevel[];
|
|
25
|
+
chunks: VideoChunk[];
|
|
26
|
+
}
|