@bernierllc/content-type-video 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/.eslintrc.js +26 -0
- package/README.md +361 -0
- package/__tests__/VideoContentType.test.ts +522 -0
- package/dist/VideoContentType.d.ts +32 -0
- package/dist/VideoContentType.d.ts.map +1 -0
- package/dist/VideoContentType.js +154 -0
- package/dist/VideoContentType.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/LocalVideoStorage.d.ts +22 -0
- package/dist/storage/LocalVideoStorage.d.ts.map +1 -0
- package/dist/storage/LocalVideoStorage.js +155 -0
- package/dist/storage/LocalVideoStorage.js.map +1 -0
- package/dist/storage/VideoStorage.d.ts +12 -0
- package/dist/storage/VideoStorage.d.ts.map +1 -0
- package/dist/storage/VideoStorage.js +10 -0
- package/dist/storage/VideoStorage.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/jest.config.cjs +31 -0
- package/package.json +59 -0
- package/src/VideoContentType.ts +142 -0
- package/src/config.ts +63 -0
- package/src/index.ts +14 -0
- package/src/storage/LocalVideoStorage.ts +134 -0
- package/src/storage/VideoStorage.ts +20 -0
- package/src/types.ts +99 -0
- package/tsconfig.json +20 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
parser: '@typescript-eslint/parser',
|
|
11
|
+
extends: [
|
|
12
|
+
'eslint:recommended',
|
|
13
|
+
'plugin:@typescript-eslint/recommended'
|
|
14
|
+
],
|
|
15
|
+
parserOptions: {
|
|
16
|
+
ecmaVersion: 2020,
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
project: './tsconfig.json'
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
22
|
+
'@typescript-eslint/explicit-function-return-type': 'warn',
|
|
23
|
+
'@typescript-eslint/no-unused-vars': 'error',
|
|
24
|
+
'no-console': 'off'
|
|
25
|
+
}
|
|
26
|
+
};
|
package/README.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# @bernierllc/content-type-video
|
|
2
|
+
|
|
3
|
+
Base video content type with file upload, local storage, and streaming URL generation for the BernierLLC Content Management Suite.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bernierllc/content-type-video
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- ✅ **Video File Upload** - Upload video files with validation (format, size)
|
|
14
|
+
- ✅ **Local File Storage** - Store videos in local file system with automatic directory creation
|
|
15
|
+
- ✅ **Metadata Extraction** - Extract video metadata (duration, dimensions, fps, codec)
|
|
16
|
+
- ✅ **Streaming URL Generation** - Generate streaming URLs for uploaded videos
|
|
17
|
+
- ✅ **Configurable** - Environment variables and constructor options
|
|
18
|
+
- ✅ **TypeScript** - Full type safety with strict mode
|
|
19
|
+
- ✅ **Well Tested** - 100% statements, functions, and lines coverage
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { VideoContentType } from '@bernierllc/content-type-video';
|
|
27
|
+
|
|
28
|
+
// Create instance with defaults
|
|
29
|
+
const videoType = new VideoContentType();
|
|
30
|
+
|
|
31
|
+
// Upload a video file
|
|
32
|
+
const result = await videoType.upload(
|
|
33
|
+
videoBuffer, // Buffer or file path
|
|
34
|
+
'my-video.mp4',
|
|
35
|
+
'user-123' // Optional: uploaded by
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (result.success) {
|
|
39
|
+
console.log('Video uploaded!');
|
|
40
|
+
console.log('File path:', result.data?.filePath);
|
|
41
|
+
console.log('Streaming URL:', result.data?.streamingUrl);
|
|
42
|
+
console.log('Metadata:', result.data?.metadata);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Custom Configuration
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const videoType = new VideoContentType({
|
|
50
|
+
baseType: 'video',
|
|
51
|
+
editor: 'file-upload-with-player',
|
|
52
|
+
storage: 'file',
|
|
53
|
+
publishing: 'streaming-url',
|
|
54
|
+
uploadOptions: {
|
|
55
|
+
maxSizeBytes: 1024 * 1024 * 1024, // 1GB
|
|
56
|
+
allowedFormats: ['mp4', 'webm'],
|
|
57
|
+
generateThumbnail: true,
|
|
58
|
+
extractMetadata: true,
|
|
59
|
+
storageBasePath: './custom-storage/videos'
|
|
60
|
+
},
|
|
61
|
+
streamingBaseUrl: '/cdn/video/stream'
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Environment Variables
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
VIDEO_MAX_SIZE_BYTES=524288000 # Maximum file size (500MB default)
|
|
69
|
+
VIDEO_ALLOWED_FORMATS=mp4,webm,mov,avi # Comma-separated allowed formats
|
|
70
|
+
VIDEO_GENERATE_THUMBNAIL=true # Generate thumbnail on upload
|
|
71
|
+
VIDEO_EXTRACT_METADATA=true # Extract metadata on upload
|
|
72
|
+
VIDEO_STORAGE_BASE_PATH=./storage/videos # Base path for video file storage
|
|
73
|
+
VIDEO_STREAMING_BASE_URL=/api/video/stream # Base URL for streaming endpoint
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### VideoContentType
|
|
79
|
+
|
|
80
|
+
Main class for video content type operations.
|
|
81
|
+
|
|
82
|
+
#### Constructor
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
constructor(config?: Partial<VideoContentTypeConfig>)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Methods
|
|
89
|
+
|
|
90
|
+
##### `upload(file, filename, uploadedBy?)`
|
|
91
|
+
|
|
92
|
+
Upload a video file and extract metadata.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
async upload(
|
|
96
|
+
file: Buffer | string,
|
|
97
|
+
filename: string,
|
|
98
|
+
uploadedBy?: string
|
|
99
|
+
): Promise<VideoContentResult<VideoContentData>>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Parameters:**
|
|
103
|
+
- `file`: Buffer or file path to the video file
|
|
104
|
+
- `filename`: Original filename (used for extension detection)
|
|
105
|
+
- `uploadedBy`: Optional user identifier who uploaded the video
|
|
106
|
+
|
|
107
|
+
**Returns:** Result object with uploaded video data or error
|
|
108
|
+
|
|
109
|
+
##### `validate(file, filename)`
|
|
110
|
+
|
|
111
|
+
Validate video file before upload.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
async validate(
|
|
115
|
+
file: Buffer | string,
|
|
116
|
+
filename: string
|
|
117
|
+
): Promise<VideoContentResult<void>>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
##### `get(localPath)`
|
|
121
|
+
|
|
122
|
+
Retrieve video file from storage.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
async get(localPath: string): Promise<Buffer | null>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
##### `delete(localPath)`
|
|
129
|
+
|
|
130
|
+
Delete video file from storage.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
async delete(localPath: string): Promise<boolean>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
##### `exists(localPath)`
|
|
137
|
+
|
|
138
|
+
Check if video file exists in storage.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
async exists(localPath: string): Promise<boolean>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
##### `getStreamingUrl(localPath)`
|
|
145
|
+
|
|
146
|
+
Generate streaming URL for video file.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
getStreamingUrl(localPath: string): string
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
##### `validateContent(data)`
|
|
153
|
+
|
|
154
|
+
Validate video content data structure.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
validateContent(data: VideoContentData): VideoContentResult<VideoContentData>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
##### `register()`
|
|
161
|
+
|
|
162
|
+
Register video content type with content type registry.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
async register(): Promise<void>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Type Definitions
|
|
169
|
+
|
|
170
|
+
### VideoMetadata
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface VideoMetadata {
|
|
174
|
+
filename: string;
|
|
175
|
+
mimeType: string; // e.g., 'video/mp4'
|
|
176
|
+
size: number; // File size in bytes
|
|
177
|
+
duration: number; // Duration in seconds
|
|
178
|
+
width: number; // Video width in pixels
|
|
179
|
+
height: number; // Video height in pixels
|
|
180
|
+
fps: number; // Frames per second
|
|
181
|
+
thumbnail?: string; // Optional thumbnail URL
|
|
182
|
+
bitrate?: number; // Optional bitrate
|
|
183
|
+
codec?: string; // Optional codec information
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### VideoContentData
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
interface VideoContentData {
|
|
191
|
+
filePath: string; // Local file path
|
|
192
|
+
streamingUrl: string; // Generated streaming URL
|
|
193
|
+
metadata: VideoMetadata; // Video metadata
|
|
194
|
+
uploadedAt: Date; // Upload timestamp
|
|
195
|
+
uploadedBy?: string; // Optional uploader identifier
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### VideoUploadOptions
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
interface VideoUploadOptions {
|
|
203
|
+
maxSizeBytes?: number; // Default: 500MB
|
|
204
|
+
allowedFormats?: string[]; // Default: ['mp4', 'webm', 'mov', 'avi']
|
|
205
|
+
generateThumbnail?: boolean; // Default: true
|
|
206
|
+
extractMetadata?: boolean; // Default: true
|
|
207
|
+
storageBasePath?: string; // Default: './storage/videos'
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Examples
|
|
212
|
+
|
|
213
|
+
### Upload from Buffer
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import fs from 'fs';
|
|
217
|
+
import { VideoContentType } from '@bernierllc/content-type-video';
|
|
218
|
+
|
|
219
|
+
const videoType = new VideoContentType();
|
|
220
|
+
const videoBuffer = fs.readFileSync('./my-video.mp4');
|
|
221
|
+
|
|
222
|
+
const result = await videoType.upload(videoBuffer, 'my-video.mp4');
|
|
223
|
+
|
|
224
|
+
if (result.success) {
|
|
225
|
+
console.log('Upload successful!');
|
|
226
|
+
console.log(`Streaming URL: ${result.data?.streamingUrl}`);
|
|
227
|
+
console.log(`Duration: ${result.data?.metadata.duration}s`);
|
|
228
|
+
console.log(`Resolution: ${result.data?.metadata.width}x${result.data?.metadata.height}`);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Upload from File Path
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const result = await videoType.upload('/path/to/video.mp4', 'video.mp4', 'user-123');
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Validate Video Format
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const result = await videoType.upload(buffer, 'video.avi');
|
|
242
|
+
|
|
243
|
+
if (!result.success) {
|
|
244
|
+
console.error('Upload failed:', result.error);
|
|
245
|
+
// Error: File format avi not allowed. Allowed: mp4, webm, mov, avi
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Custom Storage Location
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const videoType = new VideoContentType({
|
|
253
|
+
uploadOptions: {
|
|
254
|
+
storageBasePath: '/data/videos'
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Delete Uploaded Video
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const uploadResult = await videoType.upload(buffer, 'video.mp4');
|
|
263
|
+
|
|
264
|
+
if (uploadResult.success && uploadResult.data) {
|
|
265
|
+
const filePath = uploadResult.data.filePath;
|
|
266
|
+
|
|
267
|
+
// Later, delete the video
|
|
268
|
+
const deleted = await videoType.delete(filePath);
|
|
269
|
+
|
|
270
|
+
if (deleted) {
|
|
271
|
+
console.log('Video deleted successfully');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Integration
|
|
277
|
+
|
|
278
|
+
### Logger Integration
|
|
279
|
+
- **Status**: planned
|
|
280
|
+
- **Usage**: Log upload operations, metadata extraction, and errors
|
|
281
|
+
|
|
282
|
+
### NeverHub Integration
|
|
283
|
+
- **Status**: optional
|
|
284
|
+
- **Events Published**:
|
|
285
|
+
- `video.uploaded` - When video file is successfully uploaded
|
|
286
|
+
- `video.metadata-extracted` - When metadata extraction completes
|
|
287
|
+
- `video.validation-failed` - When video validation fails
|
|
288
|
+
|
|
289
|
+
### Docs-Suite Compatibility
|
|
290
|
+
- **Status**: ready
|
|
291
|
+
- **Format**: typedoc
|
|
292
|
+
- **Documentation**: Complete API documentation with TypeScript types
|
|
293
|
+
|
|
294
|
+
## Platform-Specific Extensions
|
|
295
|
+
|
|
296
|
+
This base package is designed to be extended for platform-specific video content types:
|
|
297
|
+
|
|
298
|
+
- `@bernierllc/content-type-youtube` - YouTube video integration
|
|
299
|
+
- `@bernierllc/content-type-tiktok` - TikTok video integration
|
|
300
|
+
- `@bernierllc/content-type-instagram-reels` - Instagram Reels integration
|
|
301
|
+
|
|
302
|
+
## Dependencies
|
|
303
|
+
|
|
304
|
+
- `@bernierllc/content-type-registry` (^1.0.3) - Content type registration
|
|
305
|
+
- `zod` (^3.22.0) - Schema validation
|
|
306
|
+
|
|
307
|
+
## Development
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Install dependencies
|
|
311
|
+
npm install
|
|
312
|
+
|
|
313
|
+
# Run tests
|
|
314
|
+
npm test
|
|
315
|
+
|
|
316
|
+
# Run tests with coverage
|
|
317
|
+
npm run test:coverage
|
|
318
|
+
|
|
319
|
+
# Build package
|
|
320
|
+
npm run build
|
|
321
|
+
|
|
322
|
+
# Lint code
|
|
323
|
+
npm run lint
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Quality Metrics
|
|
327
|
+
|
|
328
|
+
- **Test Coverage**: 98.86% statements, 100% functions, 98.86% lines, 79.31% branches
|
|
329
|
+
- **Tests**: 37 passing, 0 failing (100% pass rate)
|
|
330
|
+
- **TypeScript**: Strict mode enabled, zero errors
|
|
331
|
+
- **Linting**: Zero errors or warnings
|
|
332
|
+
- **Build**: Successful compilation
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
Copyright (c) 2025 Bernier LLC
|
|
337
|
+
|
|
338
|
+
This file is licensed to the client under a limited-use license.
|
|
339
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
340
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
341
|
+
|
|
342
|
+
## Related Packages
|
|
343
|
+
|
|
344
|
+
- [@bernierllc/content-type-registry](../content-type-registry) - Content type registration and base interfaces
|
|
345
|
+
- [@bernierllc/content-management-suite](../../suite/content-management-suite) - Complete content management system
|
|
346
|
+
|
|
347
|
+
## Part of Suite
|
|
348
|
+
|
|
349
|
+
- [@bernierllc/content-management-suite](../../suite/content-management-suite) - Comprehensive content management solution
|
|
350
|
+
|
|
351
|
+
## Contributing
|
|
352
|
+
|
|
353
|
+
This package is part of the BernierLLC monorepo and follows strict quality standards:
|
|
354
|
+
|
|
355
|
+
- 90%+ test coverage (all metrics)
|
|
356
|
+
- 100% test pass rate
|
|
357
|
+
- TypeScript strict mode
|
|
358
|
+
- Zero linting errors
|
|
359
|
+
- License headers in all files
|
|
360
|
+
|
|
361
|
+
For more information, see [CLAUDE.md](../../../CLAUDE.md) in the repository root.
|