@hff-ai/media-processor-client 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/README.md +570 -0
- package/dist/MediaProcessor.d.ts +222 -0
- package/dist/MediaProcessor.d.ts.map +1 -0
- package/dist/MediaProcessor.js +731 -0
- package/dist/MediaProcessor.js.map +1 -0
- package/dist/__tests__/integration/mockMediaProcessor.d.ts +80 -0
- package/dist/__tests__/integration/mockMediaProcessor.d.ts.map +1 -0
- package/dist/__tests__/integration/mockMediaProcessor.js +248 -0
- package/dist/__tests__/integration/mockMediaProcessor.js.map +1 -0
- package/dist/__tests__/setup.d.ts +4 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +13 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/errors.d.ts +134 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +173 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +453 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +51 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# @hff-ai/media-processor-client
|
|
2
|
+
|
|
3
|
+
A TypeScript client library for communicating with the HFF Media Processor service. Abstracts away AMQP and REST API details, providing both async/await and event-based APIs.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Dual API**: Choose between async/await (`processS3*`) or event-based (`queueS3*`) styles
|
|
8
|
+
- **Clean separation**: Async requests don't emit events, preventing double-handling
|
|
9
|
+
- **Progress callbacks**: Track processing progress with optional callbacks
|
|
10
|
+
- **Auto-reconnect**: Automatic reconnection handling with configurable retry logic
|
|
11
|
+
- **Type-safe**: Full TypeScript support with comprehensive type definitions
|
|
12
|
+
- **Zero-config MIME detection**: Automatically detects MIME types from file extensions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @hff-ai/media-processor-client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or if using the package locally within the monorepo:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install ./packages/media-processor-client
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Async/Await (Recommended)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { media_processor, ProcessingError } from '@hff-ai/media-processor-client';
|
|
32
|
+
|
|
33
|
+
const processor = await media_processor.connect({
|
|
34
|
+
processorUrl: 'https://mp.hff.ai',
|
|
35
|
+
processorAmqp: 'amqp://user:pass@mp.hff.ai',
|
|
36
|
+
token: process.env.MEDIA_PROCESSOR_TOKEN!,
|
|
37
|
+
s3Details: {
|
|
38
|
+
provider: 'idrive',
|
|
39
|
+
endpoint: 'https://s3.us-east-1.idrivee2-27.com',
|
|
40
|
+
accessKey: process.env.S3_ACCESS_KEY!,
|
|
41
|
+
secretAccessKey: process.env.S3_SECRET_KEY!,
|
|
42
|
+
region: 'us-east-1',
|
|
43
|
+
bucketName: 'my-media-bucket',
|
|
44
|
+
},
|
|
45
|
+
system: 'my-application',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Process and await result
|
|
49
|
+
try {
|
|
50
|
+
const result = await processor.processS3Image('uploads/photo.jpg', {
|
|
51
|
+
timeout: 30000,
|
|
52
|
+
onProgress: (p) => console.log(`${p.progress}%`)
|
|
53
|
+
});
|
|
54
|
+
console.log('Done:', result.result.thumbnailS3Key);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof ProcessingError) {
|
|
57
|
+
console.error(`Failed: ${error.errorCode}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Event-Based (Fire-and-Forget)
|
|
63
|
+
|
|
64
|
+
**Important:** Set up handlers BEFORE connecting to capture queued messages.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { media_processor, MediaResultMessage } from '@hff-ai/media-processor-client';
|
|
68
|
+
|
|
69
|
+
// Create without connecting
|
|
70
|
+
const processor = media_processor.create(config);
|
|
71
|
+
|
|
72
|
+
// Set up handlers FIRST
|
|
73
|
+
processor.on('image_completed', (msg: MediaResultMessage) => {
|
|
74
|
+
console.log('Image processed:', msg.result.s3Key);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// NOW connect - handlers ready for queued messages
|
|
78
|
+
await processor.connect();
|
|
79
|
+
|
|
80
|
+
// Queue (returns immediately)
|
|
81
|
+
const mediaId = processor.queueS3Image('uploads/photo.jpg');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
### MediaProcessorConfig
|
|
87
|
+
|
|
88
|
+
| Property | Type | Required | Description |
|
|
89
|
+
|----------|------|----------|-------------|
|
|
90
|
+
| `processorUrl` | `string` | Yes | Base URL of the media processor REST API |
|
|
91
|
+
| `processorAmqp` | `string` | Yes | AMQP connection URL for RabbitMQ |
|
|
92
|
+
| `token` | `string` | Yes | JWT authentication token |
|
|
93
|
+
| `s3Details` | `S3Config` | Yes | S3 bucket configuration |
|
|
94
|
+
| `system` | `string` | Yes | System namespace for queue routing |
|
|
95
|
+
| `options` | `ProcessorOptions` | No | Optional processing defaults |
|
|
96
|
+
|
|
97
|
+
### S3Config
|
|
98
|
+
|
|
99
|
+
| Property | Type | Required | Description |
|
|
100
|
+
|----------|------|----------|-------------|
|
|
101
|
+
| `provider` | `string` | Yes | S3-compatible provider (e.g., 'idrive', 'aws', 'minio') |
|
|
102
|
+
| `endpoint` | `string` | Yes | S3 endpoint URL |
|
|
103
|
+
| `accessKey` | `string` | Yes | S3 access key ID |
|
|
104
|
+
| `secretAccessKey` | `string` | Yes | S3 secret access key |
|
|
105
|
+
| `region` | `string` | Yes | S3 region |
|
|
106
|
+
| `bucketName` | `string` | Yes | Bucket name |
|
|
107
|
+
|
|
108
|
+
### ProcessorOptions
|
|
109
|
+
|
|
110
|
+
| Property | Type | Default | Description |
|
|
111
|
+
|----------|------|---------|-------------|
|
|
112
|
+
| `maxConcurrentJobs` | `number` | `5` | Maximum concurrent processing jobs |
|
|
113
|
+
| `defaultUserId` | `string` | `'anonymous'` | Default user ID for requests |
|
|
114
|
+
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on connection loss |
|
|
115
|
+
| `reconnectDelay` | `number` | `5000` | Reconnection delay in milliseconds |
|
|
116
|
+
| `maxReconnectAttempts` | `number` | `10` | Maximum reconnection attempts (0 = infinite) |
|
|
117
|
+
| `requestTimeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
118
|
+
|
|
119
|
+
## API Reference
|
|
120
|
+
|
|
121
|
+
### Factory Methods
|
|
122
|
+
|
|
123
|
+
#### `media_processor.connect(config)`
|
|
124
|
+
|
|
125
|
+
Create and connect to a MediaProcessor instance.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const processor = await media_processor.connect(config);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### `media_processor.create(config)`
|
|
132
|
+
|
|
133
|
+
Create a MediaProcessor instance without connecting. Useful for setting up event handlers before connecting.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
const processor = media_processor.create(config);
|
|
137
|
+
processor.on('connected', () => console.log('Connected!'));
|
|
138
|
+
await processor.connect();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Async Processing Methods (Promise-based)
|
|
142
|
+
|
|
143
|
+
These methods return a Promise that resolves when processing completes.
|
|
144
|
+
|
|
145
|
+
**Note:** Results from async methods are delivered only via the Promise - no events are emitted. This allows safe mixing of both API styles in the same application.
|
|
146
|
+
|
|
147
|
+
#### `processor.processS3Image(s3Key, options?): Promise<MediaResultMessage>`
|
|
148
|
+
|
|
149
|
+
Process an image and await the result.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const result = await processor.processS3Image('uploads/photo.jpg', {
|
|
153
|
+
userId: 'user-123',
|
|
154
|
+
timeout: 30000,
|
|
155
|
+
onProgress: (p) => console.log(`${p.progress}%`),
|
|
156
|
+
});
|
|
157
|
+
console.log('Thumbnail:', result.result.thumbnailS3Key);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `processor.processS3Video(s3Key, options?): Promise<MediaResultMessage>`
|
|
161
|
+
|
|
162
|
+
Process a video and await the result.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const result = await processor.processS3Video('uploads/video.mp4', {
|
|
166
|
+
userId: 'user-123',
|
|
167
|
+
targetQualities: ['1080p', '720p', '480p'],
|
|
168
|
+
timeout: 300000, // 5 minutes
|
|
169
|
+
onProgress: (p) => updateProgressBar(p.progress),
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `processor.processS3Zip(s3Key, options?): Promise<MediaResultMessage>`
|
|
174
|
+
|
|
175
|
+
Process a zip archive and await the result.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const result = await processor.processS3Zip('uploads/photos.zip', {
|
|
179
|
+
userId: 'user-123',
|
|
180
|
+
processImages: true,
|
|
181
|
+
processVideos: true,
|
|
182
|
+
timeout: 120000,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Queue Methods (Fire-and-Forget)
|
|
187
|
+
|
|
188
|
+
These methods return immediately. Results are delivered exclusively via events (`*_completed`, `*_error`, `*_progress`).
|
|
189
|
+
|
|
190
|
+
#### `processor.queueS3Image(s3Key, options?): string`
|
|
191
|
+
|
|
192
|
+
Queue an image for processing.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const mediaId = processor.queueS3Image('uploads/photo.jpg', {
|
|
196
|
+
userId: 'user-123',
|
|
197
|
+
});
|
|
198
|
+
// Handle result via processor.on('image_completed', ...)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `processor.queueS3Video(s3Key, options?): string`
|
|
202
|
+
|
|
203
|
+
Queue a video for processing.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const mediaId = processor.queueS3Video('uploads/video.mp4', {
|
|
207
|
+
userId: 'user-123',
|
|
208
|
+
targetQualities: ['1080p', '720p'],
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `processor.queueS3Zip(s3Key, options?): string`
|
|
213
|
+
|
|
214
|
+
Queue a zip archive for processing.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const mediaId = processor.queueS3Zip('uploads/photos.zip', {
|
|
218
|
+
userId: 'user-123',
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Connection Methods
|
|
223
|
+
|
|
224
|
+
#### `processor.disconnect()`
|
|
225
|
+
|
|
226
|
+
Gracefully close all connections.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
await processor.disconnect();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### `processor.isConnected()`
|
|
233
|
+
|
|
234
|
+
Check connection status.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
if (processor.isConnected()) {
|
|
238
|
+
// Safe to submit requests
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### `processor.reconnect()`
|
|
243
|
+
|
|
244
|
+
Manually trigger reconnection.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
await processor.reconnect();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### `processor.updateToken(token)`
|
|
251
|
+
|
|
252
|
+
Update the authentication token (e.g., after token refresh).
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
processor.updateToken(newToken);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `processor.getSystemStatus()`
|
|
259
|
+
|
|
260
|
+
Get current system status and statistics.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const status = await processor.getSystemStatus();
|
|
264
|
+
console.log(`Pending jobs: ${status.pendingJobs}`);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Events
|
|
268
|
+
|
|
269
|
+
### Processing Events
|
|
270
|
+
|
|
271
|
+
| Event | Payload | Description |
|
|
272
|
+
|-------|---------|-------------|
|
|
273
|
+
| `image_progress` | `MediaProgressMessage` | Image processing progress |
|
|
274
|
+
| `image_completed` | `MediaResultMessage` | Image processing completed |
|
|
275
|
+
| `image_error` | `MediaErrorMessage` | Image processing failed |
|
|
276
|
+
| `video_progress` | `MediaProgressMessage` | Video processing progress |
|
|
277
|
+
| `video_completed` | `MediaResultMessage` | Video processing completed |
|
|
278
|
+
| `video_error` | `MediaErrorMessage` | Video processing failed |
|
|
279
|
+
| `zip_progress` | `MediaProgressMessage` | Zip extraction progress |
|
|
280
|
+
| `zip_completed` | `MediaResultMessage` | Zip processing completed |
|
|
281
|
+
| `zip_error` | `MediaErrorMessage` | Zip processing failed |
|
|
282
|
+
| `progress` | `MediaProgressMessage` | Any media type progress |
|
|
283
|
+
| `completed` | `MediaResultMessage` | Any media type completed |
|
|
284
|
+
| `processing_error` | `MediaErrorMessage` | Any media type failed |
|
|
285
|
+
|
|
286
|
+
### Connection Events
|
|
287
|
+
|
|
288
|
+
| Event | Payload | Description |
|
|
289
|
+
|-------|---------|-------------|
|
|
290
|
+
| `connected` | `void` | Successfully connected |
|
|
291
|
+
| `disconnected` | `{ reason: string }` | Connection lost |
|
|
292
|
+
| `reconnecting` | `{ attempt: number }` | Attempting to reconnect |
|
|
293
|
+
| `error` | `Error` | Connection-level error |
|
|
294
|
+
|
|
295
|
+
### Warning Events
|
|
296
|
+
|
|
297
|
+
| Event | Payload | Description |
|
|
298
|
+
|-------|---------|-------------|
|
|
299
|
+
| `unhandled_message` | `UnhandledMessagePayload` | Orphaned or unhandled message (will be dropped) |
|
|
300
|
+
|
|
301
|
+
The `unhandled_message` event fires in two scenarios:
|
|
302
|
+
|
|
303
|
+
1. **Orphaned async messages**: A `processS3*()` request from a previous process that died. These have a `correlationId` but no matching pending promise. They are NOT sent to regular event handlers.
|
|
304
|
+
|
|
305
|
+
2. **Queue messages with no listeners**: A `queueS3*()` result where no event handler is registered.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
processor.on('unhandled_message', (warning) => {
|
|
309
|
+
console.warn(`Dropped: ${warning.message}`);
|
|
310
|
+
// Optionally: save to dead-letter store, alert, etc.
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Error Handling
|
|
315
|
+
|
|
316
|
+
The library provides typed error classes for different scenarios:
|
|
317
|
+
|
|
318
|
+
### Error Classes
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import {
|
|
322
|
+
MediaProcessorError, // Base class
|
|
323
|
+
ConnectionError, // Connection-related errors
|
|
324
|
+
ConfigurationError, // Configuration errors
|
|
325
|
+
RequestError, // Queue request errors (sync)
|
|
326
|
+
ProcessingError, // Processing failures (async, from process* methods)
|
|
327
|
+
} from '@hff-ai/media-processor-client';
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Connection Errors
|
|
331
|
+
|
|
332
|
+
| Code | Description |
|
|
333
|
+
|------|-------------|
|
|
334
|
+
| `CONNECTION_FAILED` | Cannot reach the processor |
|
|
335
|
+
| `CONNECTION_LOST` | Connection dropped |
|
|
336
|
+
| `AUTH_FAILED` | Invalid or expired token |
|
|
337
|
+
| `AMQP_ERROR` | RabbitMQ error |
|
|
338
|
+
| `REST_API_ERROR` | REST API error |
|
|
339
|
+
| `RECONNECT_FAILED` | Exceeded reconnection attempts |
|
|
340
|
+
|
|
341
|
+
### Processing Errors
|
|
342
|
+
|
|
343
|
+
| Code | Description |
|
|
344
|
+
|------|-------------|
|
|
345
|
+
| `S3_ACCESS_DENIED` | Insufficient S3 permissions |
|
|
346
|
+
| `S3_NOT_FOUND` | Source file not found |
|
|
347
|
+
| `S3_UPLOAD_FAILED` | Failed to upload results |
|
|
348
|
+
| `INVALID_FORMAT` | Invalid file format |
|
|
349
|
+
| `UNSUPPORTED_CODEC` | Unsupported video codec |
|
|
350
|
+
| `FILE_TOO_LARGE` | File exceeds size limit |
|
|
351
|
+
| `FILE_CORRUPTED` | Damaged file |
|
|
352
|
+
| `PROCESSING_TIMEOUT` | Processing timed out |
|
|
353
|
+
| `TRANSCODING_FAILED` | Video transcoding failed |
|
|
354
|
+
| `ZIP_EXTRACTION_FAILED` | Zip extraction failed |
|
|
355
|
+
| `ZIP_PASSWORD_PROTECTED` | Password-protected zip |
|
|
356
|
+
|
|
357
|
+
### ProcessingError Codes (async methods)
|
|
358
|
+
|
|
359
|
+
| Code | Description |
|
|
360
|
+
|------|-------------|
|
|
361
|
+
| `PROCESSING_FAILED` | Server returned an error (check `errorCode` for details) |
|
|
362
|
+
| `PROCESSING_TIMEOUT` | Client-side timeout exceeded |
|
|
363
|
+
| `DISCONNECTED` | Connection lost during processing |
|
|
364
|
+
|
|
365
|
+
### Error Handling Example
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { ConnectionError, ProcessingError, MediaErrorMessage } from '@hff-ai/media-processor-client';
|
|
369
|
+
|
|
370
|
+
// Async error handling (try/catch)
|
|
371
|
+
try {
|
|
372
|
+
const result = await processor.processS3Image('uploads/photo.jpg', {
|
|
373
|
+
timeout: 30000
|
|
374
|
+
});
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (error instanceof ProcessingError) {
|
|
377
|
+
console.error(`Processing failed: ${error.errorCode} - ${error.message}`);
|
|
378
|
+
if (error.errorCode === 'S3_NOT_FOUND') {
|
|
379
|
+
// Handle missing file
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Event-based error handling (for queue methods)
|
|
385
|
+
processor.on('image_error', (msg: MediaErrorMessage) => {
|
|
386
|
+
switch (msg.errorCode) {
|
|
387
|
+
case 'S3_NOT_FOUND':
|
|
388
|
+
console.error('File not found:', msg.details);
|
|
389
|
+
break;
|
|
390
|
+
case 'INVALID_FORMAT':
|
|
391
|
+
notifyUser('Invalid image format');
|
|
392
|
+
break;
|
|
393
|
+
default:
|
|
394
|
+
logError(msg);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Connection errors
|
|
399
|
+
processor.on('error', (error: ConnectionError) => {
|
|
400
|
+
if (error.code === 'AUTH_FAILED') {
|
|
401
|
+
refreshToken().then(newToken => {
|
|
402
|
+
processor.updateToken(newToken);
|
|
403
|
+
processor.reconnect();
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Complete Example
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import {
|
|
413
|
+
media_processor,
|
|
414
|
+
MediaProcessorConfig,
|
|
415
|
+
MediaResultMessage,
|
|
416
|
+
MediaProgressMessage,
|
|
417
|
+
MediaErrorMessage,
|
|
418
|
+
ConnectionError,
|
|
419
|
+
} from '@hff-ai/media-processor-client';
|
|
420
|
+
|
|
421
|
+
const config: MediaProcessorConfig = {
|
|
422
|
+
processorUrl: process.env.MEDIA_PROCESSOR_URL!,
|
|
423
|
+
processorAmqp: process.env.MEDIA_PROCESSOR_AMQP!,
|
|
424
|
+
token: process.env.MEDIA_PROCESSOR_TOKEN!,
|
|
425
|
+
s3Details: {
|
|
426
|
+
provider: 'idrive',
|
|
427
|
+
endpoint: process.env.S3_ENDPOINT!,
|
|
428
|
+
accessKey: process.env.S3_ACCESS_KEY!,
|
|
429
|
+
secretAccessKey: process.env.S3_SECRET_KEY!,
|
|
430
|
+
region: process.env.S3_REGION || 'us-east-1',
|
|
431
|
+
bucketName: process.env.S3_BUCKET!,
|
|
432
|
+
},
|
|
433
|
+
system: 'my-application',
|
|
434
|
+
options: {
|
|
435
|
+
maxConcurrentJobs: 10,
|
|
436
|
+
autoReconnect: true,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
async function main() {
|
|
441
|
+
// Create processor with event handlers before connecting
|
|
442
|
+
const processor = media_processor.create(config);
|
|
443
|
+
|
|
444
|
+
// Connection events
|
|
445
|
+
processor.on('connected', () => {
|
|
446
|
+
console.log('Connected to media processor');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
processor.on('disconnected', ({ reason }) => {
|
|
450
|
+
console.warn('Disconnected:', reason);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
processor.on('reconnecting', ({ attempt }) => {
|
|
454
|
+
console.log(`Reconnecting (attempt ${attempt})...`);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
processor.on('error', (error: Error) => {
|
|
458
|
+
console.error('Connection error:', error.message);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Processing events
|
|
462
|
+
processor.on('image_progress', (msg: MediaProgressMessage) => {
|
|
463
|
+
console.log(`Image ${msg.mediaId}: ${msg.progress}% - ${msg.message}`);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
processor.on('image_completed', (msg: MediaResultMessage) => {
|
|
467
|
+
console.log(`Image completed: ${msg.mediaId}`);
|
|
468
|
+
// Save to database, update UI, etc.
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
processor.on('image_error', (msg: MediaErrorMessage) => {
|
|
472
|
+
console.error(`Image failed: ${msg.mediaId} - ${msg.error}`);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
processor.on('video_progress', (msg: MediaProgressMessage) => {
|
|
476
|
+
updateProgressBar(msg.mediaId, msg.progress);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
processor.on('video_completed', (msg: MediaResultMessage) => {
|
|
480
|
+
console.log(`Video completed: ${msg.mediaId}`);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Connect
|
|
484
|
+
try {
|
|
485
|
+
await processor.connect();
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (error instanceof ConnectionError) {
|
|
488
|
+
console.error(`Failed to connect: ${error.code} - ${error.message}`);
|
|
489
|
+
}
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Process some media
|
|
494
|
+
try {
|
|
495
|
+
const imageId = processor.processS3Image('uploads/photo.jpg', {
|
|
496
|
+
userId: 'user-123',
|
|
497
|
+
});
|
|
498
|
+
console.log(`Processing image: ${imageId}`);
|
|
499
|
+
|
|
500
|
+
const videoId = processor.processS3Video('uploads/video.mp4', {
|
|
501
|
+
userId: 'user-123',
|
|
502
|
+
targetQualities: ['1080p', '720p'],
|
|
503
|
+
});
|
|
504
|
+
console.log(`Processing video: ${videoId}`);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('Failed to submit request:', error);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Graceful shutdown
|
|
510
|
+
process.on('SIGINT', async () => {
|
|
511
|
+
console.log('Shutting down...');
|
|
512
|
+
await processor.disconnect();
|
|
513
|
+
process.exit(0);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
main();
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Type Guards
|
|
521
|
+
|
|
522
|
+
The library provides type guards for narrowing message types:
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import {
|
|
526
|
+
isProgressMessage,
|
|
527
|
+
isResultMessage,
|
|
528
|
+
isErrorMessage,
|
|
529
|
+
isImageResult,
|
|
530
|
+
isVideoResult,
|
|
531
|
+
isZipResult,
|
|
532
|
+
} from '@hff-ai/media-processor-client';
|
|
533
|
+
|
|
534
|
+
processor.on('completed', (msg) => {
|
|
535
|
+
if (isImageResult(msg.result)) {
|
|
536
|
+
console.log('Image versions:', msg.result.metadata?.imageVersions);
|
|
537
|
+
} else if (isVideoResult(msg.result)) {
|
|
538
|
+
console.log('Duration:', msg.result.duration);
|
|
539
|
+
} else if (isZipResult(msg.result)) {
|
|
540
|
+
console.log('Extracted files:', msg.result.extractedMedia.length);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Development
|
|
546
|
+
|
|
547
|
+
### Building
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
cd packages/media-processor-client
|
|
551
|
+
npm install
|
|
552
|
+
npm run build
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Testing
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
npm test
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Linting
|
|
562
|
+
|
|
563
|
+
```bash
|
|
564
|
+
npm run lint
|
|
565
|
+
npm run lint:fix
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## License
|
|
569
|
+
|
|
570
|
+
MIT
|