@quatrain/worker 1.2.11 → 1.2.14-pr25.8334
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/dist/FileSystem.js +46 -60
- package/dist/FileSystem.test.js +8 -2
- package/dist/Worker.d.ts +2 -2
- package/dist/Worker.js +8 -9
- package/dist/Worker.test.js +6 -9
- package/package.json +2 -2
- package/src/FileSystem.test.ts +9 -2
- package/src/FileSystem.ts +52 -69
- package/src/Worker.test.ts +6 -10
- package/src/Worker.ts +9 -10
package/dist/FileSystem.js
CHANGED
|
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.FileSystem = void 0;
|
|
40
|
-
const node_fs_1 =
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
41
|
const node_path_1 = __importDefault(require("node:path"));
|
|
42
42
|
const axios_1 = __importDefault(require("axios"));
|
|
43
43
|
const Worker_1 = require("./Worker");
|
|
@@ -137,80 +137,66 @@ class FileSystem {
|
|
|
137
137
|
*/
|
|
138
138
|
static async uploadFile(filename, meta, mime = 'video/mp4') {
|
|
139
139
|
// try to get more file metadata
|
|
140
|
-
|
|
140
|
+
let info = {};
|
|
141
|
+
try {
|
|
142
|
+
info = await FileSystem.getInfo(filename);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
Worker_1.Worker.warn(`Could not probe media info for ${filename}: ${err?.message || err}`);
|
|
146
|
+
}
|
|
141
147
|
meta = { ...meta, ...info };
|
|
142
148
|
Worker_1.Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`);
|
|
143
149
|
const { size } = node_fs_1.default.statSync(filename);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
'Content-length': String(size),
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
.then(() => resolve({ ...meta, size, uploadUrl: undefined }))
|
|
157
|
-
.catch((err) => {
|
|
158
|
-
Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
|
|
159
|
-
reject(err);
|
|
160
|
-
});
|
|
150
|
+
try {
|
|
151
|
+
Worker_1.Worker.info(`Uploading file ${filename} with size ${size} bytes`);
|
|
152
|
+
const fileBuffer = node_fs_1.default.readFileSync(filename);
|
|
153
|
+
const res = await (0, node_fetch_native_1.default)(meta.uploadUrl, {
|
|
154
|
+
method: 'PUT',
|
|
155
|
+
body: fileBuffer,
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': meta.contentType || mime,
|
|
158
|
+
},
|
|
161
159
|
});
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
let done = 0; // total bytes uploaded
|
|
166
|
-
let prev = 0; // latest value of done stored when % done was displayed
|
|
167
|
-
const { size } = node_fs_1.default.statSync(filename);
|
|
168
|
-
const readStream = node_fs_1.default.createReadStream(filename);
|
|
169
|
-
const sizeMB = (size / (1024 * 1024)).toFixed(2);
|
|
170
|
-
readStream.on('data', (data) => {
|
|
171
|
-
done += data.length;
|
|
172
|
-
if (done - prev >= size / 20) {
|
|
173
|
-
// only display message when 5% more has been uploaded
|
|
174
|
-
Worker_1.Worker.info(`${((done / size) * 100).toFixed(2)}% - ${(done /
|
|
175
|
-
(1024 * 1024)).toFixed(2)}MB / ${sizeMB}MB`);
|
|
176
|
-
prev = done;
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
Worker_1.Worker.info(`Uploading file ${filename} with size ${size} bytes`);
|
|
180
|
-
(0, node_fetch_native_1.default)(meta.uploadUrl, {
|
|
181
|
-
method: 'PUT',
|
|
182
|
-
mode: 'cors',
|
|
183
|
-
duplex: 'half',
|
|
184
|
-
body: readStream,
|
|
185
|
-
headers: {
|
|
186
|
-
'Content-Type': meta.contentType || mime,
|
|
187
|
-
'Content-length': String(size),
|
|
188
|
-
},
|
|
189
|
-
})
|
|
190
|
-
.then(() => resolve({ ...meta, size, uploadUrl: undefined }))
|
|
191
|
-
.catch((err) => {
|
|
192
|
-
Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
|
|
193
|
-
reject(err);
|
|
194
|
-
});
|
|
160
|
+
if (res && res.ok === false) {
|
|
161
|
+
const errorText = typeof res.text === 'function' ? await res.text().catch(() => '') : '';
|
|
162
|
+
throw new Error(`HTTP upload failed with status ${res.status || 'unknown'}: ${errorText}`);
|
|
195
163
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
164
|
+
return { ...meta, size, uploadUrl: undefined };
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
201
170
|
}
|
|
202
171
|
/**
|
|
203
172
|
* Return meta data on given file
|
|
204
173
|
*/
|
|
205
174
|
static getInfo = (file) => {
|
|
206
175
|
if (file.endsWith('.mp4') || file.endsWith('.insv')) {
|
|
176
|
+
try {
|
|
177
|
+
const ffprobeStatic = require('ffprobe-static');
|
|
178
|
+
if (ffprobeStatic && ffprobeStatic.path) {
|
|
179
|
+
ffmpeg.setFfprobePath(ffprobeStatic.path);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// ffprobe-static not present, fallback to system PATH
|
|
184
|
+
}
|
|
207
185
|
return new Promise((resolve, reject) => ffmpeg.ffprobe(file, (err, metadata) => {
|
|
208
186
|
if (err) {
|
|
209
187
|
Worker_1.Worker.error(err);
|
|
210
|
-
reject(err);
|
|
188
|
+
return reject(err);
|
|
189
|
+
}
|
|
190
|
+
if (!metadata || !metadata.streams || !metadata.streams.length) {
|
|
191
|
+
Worker_1.Worker.warn(`No streams found in ffprobe metadata for ${file}`);
|
|
192
|
+
return resolve({});
|
|
193
|
+
}
|
|
194
|
+
const videoStream = metadata.streams.find((s) => s.codec_type === 'video') ||
|
|
195
|
+
metadata.streams[0];
|
|
196
|
+
if (!videoStream) {
|
|
197
|
+
return resolve({});
|
|
211
198
|
}
|
|
212
|
-
|
|
213
|
-
const { width, height, duration, bit_rate: bitrate, nb_frames: nbFramees, } = metadata.streams[0];
|
|
199
|
+
const { width, height, duration, bit_rate: bitrate, nb_frames: nbFramees, } = videoStream;
|
|
214
200
|
const nb_frames = Number.parseFloat(nbFramees);
|
|
215
201
|
const framerate = nb_frames / Number.parseFloat(duration);
|
|
216
202
|
resolve({
|
package/dist/FileSystem.test.js
CHANGED
|
@@ -94,6 +94,12 @@ describe('FileSystem Utilities (Worker)', () => {
|
|
|
94
94
|
jest.spyOn(node_fs_1.default, 'createReadStream').mockImplementation(() => {
|
|
95
95
|
return node_stream_1.Readable.from(Buffer.alloc(35 * 1024));
|
|
96
96
|
});
|
|
97
|
+
jest.spyOn(node_fs_1.default, 'readFileSync').mockImplementation((p) => {
|
|
98
|
+
if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
|
|
99
|
+
return Buffer.alloc(35 * 1024);
|
|
100
|
+
}
|
|
101
|
+
return (jest.requireActual('node:fs').readFileSync)(p);
|
|
102
|
+
});
|
|
97
103
|
jest.spyOn(node_fs_1.default, 'existsSync').mockImplementation((p) => {
|
|
98
104
|
if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
|
|
99
105
|
return true;
|
|
@@ -330,8 +336,8 @@ describe('FileSystem Utilities (Worker)', () => {
|
|
|
330
336
|
ffmpeg.ffprobe.mockImplementation((file, callback) => {
|
|
331
337
|
callback(null, mockMetadata);
|
|
332
338
|
});
|
|
333
|
-
// Make
|
|
334
|
-
jest.spyOn(node_fs_1.default, '
|
|
339
|
+
// Make readFileSync throw a synchronous error
|
|
340
|
+
jest.spyOn(node_fs_1.default, 'readFileSync').mockImplementationOnce(() => {
|
|
335
341
|
throw new Error('Sync stream error');
|
|
336
342
|
});
|
|
337
343
|
const meta = {
|
package/dist/Worker.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare class Worker extends Core {
|
|
|
25
25
|
* @param ts timestamp
|
|
26
26
|
* @returns boolean
|
|
27
27
|
*/
|
|
28
|
-
static pushEvent(event: string, data?: {}, ts?: number):
|
|
28
|
+
static pushEvent(event: string, data?: {}, ts?: number): void;
|
|
29
29
|
/**
|
|
30
30
|
* Async Push an event to the backend endpoint, if available
|
|
31
31
|
* @param event string
|
|
@@ -33,7 +33,7 @@ export declare class Worker extends Core {
|
|
|
33
33
|
* @param ts timestamp
|
|
34
34
|
* @returns boolean
|
|
35
35
|
*/
|
|
36
|
-
static pushEventAsync(event: string, data?: {}, ts?: number): Promise<
|
|
36
|
+
static pushEventAsync(event: string, data?: {}, ts?: number): Promise<true | undefined>;
|
|
37
37
|
/**
|
|
38
38
|
* Global handling function to process received messages
|
|
39
39
|
* @param messageHandler function
|
package/dist/Worker.js
CHANGED
|
@@ -59,8 +59,8 @@ class Worker extends core_1.Core {
|
|
|
59
59
|
*/
|
|
60
60
|
static pushEvent(event, data = {}, ts = 0) {
|
|
61
61
|
if (!this.endpoint) {
|
|
62
|
-
Worker.
|
|
63
|
-
|
|
62
|
+
Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`);
|
|
63
|
+
throw new Error(`Events endpoint is mandatory but missing`);
|
|
64
64
|
}
|
|
65
65
|
ts = ts === Date.now() ? Date.now() + 1 : Date.now();
|
|
66
66
|
const payload = {
|
|
@@ -79,8 +79,7 @@ class Worker extends core_1.Core {
|
|
|
79
79
|
return true;
|
|
80
80
|
})
|
|
81
81
|
.catch((err) => {
|
|
82
|
-
Worker.error(`Failed to push event to backend: ${err}`);
|
|
83
|
-
return false;
|
|
82
|
+
Worker.error(`Failed to push event to backend: ${err.message}`);
|
|
84
83
|
});
|
|
85
84
|
}
|
|
86
85
|
/**
|
|
@@ -92,8 +91,8 @@ class Worker extends core_1.Core {
|
|
|
92
91
|
*/
|
|
93
92
|
static async pushEventAsync(event, data = {}, ts = 0) {
|
|
94
93
|
if (!this.endpoint) {
|
|
95
|
-
Worker.
|
|
96
|
-
|
|
94
|
+
Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`);
|
|
95
|
+
throw new Error(`Events endpoint is mandatory but missing`);
|
|
97
96
|
}
|
|
98
97
|
try {
|
|
99
98
|
ts = ts === Date.now() ? Date.now() + 1 : Date.now();
|
|
@@ -105,14 +104,14 @@ class Worker extends core_1.Core {
|
|
|
105
104
|
ts,
|
|
106
105
|
};
|
|
107
106
|
const res = await axios_1.default.patch(Worker.endpoint, payload);
|
|
108
|
-
if (res.statusText === 'OK') {
|
|
107
|
+
if (res.statusText === 'OK' || res.status === 200) {
|
|
109
108
|
Worker.info(`Event pushed to backend: ${res.statusText}`);
|
|
110
109
|
return true;
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
catch (err) {
|
|
114
|
-
Worker.error(`Failed to push event to backend: ${err}`);
|
|
115
|
-
|
|
113
|
+
Worker.error(`Failed to push event to backend: ${err.message}`);
|
|
114
|
+
throw new Error(`Failed to push event to backend: ${err.message}`);
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
/**
|
package/dist/Worker.test.js
CHANGED
|
@@ -40,9 +40,8 @@ describe('Worker', () => {
|
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
describe('pushEvent', () => {
|
|
43
|
-
it('should
|
|
44
|
-
|
|
45
|
-
expect(result).toBe(false);
|
|
43
|
+
it('should throw error when endpoint is not set', () => {
|
|
44
|
+
expect(() => Worker_1.Worker.pushEvent('test-event')).toThrow('Events endpoint is mandatory but missing');
|
|
46
45
|
expect(mockedAxios.patch).not.toHaveBeenCalled();
|
|
47
46
|
});
|
|
48
47
|
it('should send event when endpoint is set', () => {
|
|
@@ -110,9 +109,8 @@ describe('Worker', () => {
|
|
|
110
109
|
});
|
|
111
110
|
});
|
|
112
111
|
describe('pushEventAsync', () => {
|
|
113
|
-
it('should
|
|
114
|
-
|
|
115
|
-
expect(result).toBe(false);
|
|
112
|
+
it('should throw error when endpoint is not set', async () => {
|
|
113
|
+
await expect(Worker_1.Worker.pushEventAsync('test-event')).rejects.toThrow('Events endpoint is mandatory but missing');
|
|
116
114
|
expect(mockedAxios.patch).not.toHaveBeenCalled();
|
|
117
115
|
});
|
|
118
116
|
it('should send event and return true on success', async () => {
|
|
@@ -130,11 +128,10 @@ describe('Worker', () => {
|
|
|
130
128
|
foo: 'bar',
|
|
131
129
|
}));
|
|
132
130
|
});
|
|
133
|
-
it('should
|
|
131
|
+
it('should throw error on axios error', async () => {
|
|
134
132
|
Worker_1.Worker.endpoint = 'https://api.example.com/events';
|
|
135
133
|
mockedAxios.patch.mockRejectedValue(new Error('Network error'));
|
|
136
|
-
|
|
137
|
-
expect(result).toBe(false);
|
|
134
|
+
await expect(Worker_1.Worker.pushEventAsync('test-event')).rejects.toThrow('Failed to push event to backend: Network error');
|
|
138
135
|
});
|
|
139
136
|
it('should handle non-OK status response', async () => {
|
|
140
137
|
Worker_1.Worker.endpoint = 'https://api.example.com/events';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/worker",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.14-pr25.8334",
|
|
4
4
|
"description": "Container Worker helpers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typescript": "^5.1.5"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@quatrain/core": "^1.2.
|
|
37
|
+
"@quatrain/core": "^1.2.18",
|
|
38
38
|
"@quatrain/queue": "^1.2.3",
|
|
39
39
|
"@quatrain/storage": "^1.2.13",
|
|
40
40
|
"axios": "^1.7.7",
|
package/src/FileSystem.test.ts
CHANGED
|
@@ -63,6 +63,13 @@ describe('FileSystem Utilities (Worker)', () => {
|
|
|
63
63
|
return Readable.from(Buffer.alloc(35 * 1024)) as any
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
jest.spyOn(fs, 'readFileSync').mockImplementation((p) => {
|
|
67
|
+
if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
|
|
68
|
+
return Buffer.alloc(35 * 1024)
|
|
69
|
+
}
|
|
70
|
+
return (jest.requireActual('node:fs').readFileSync)(p)
|
|
71
|
+
})
|
|
72
|
+
|
|
66
73
|
jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
|
|
67
74
|
if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
|
|
68
75
|
return true
|
|
@@ -346,8 +353,8 @@ describe('FileSystem Utilities (Worker)', () => {
|
|
|
346
353
|
callback(null, mockMetadata)
|
|
347
354
|
});
|
|
348
355
|
|
|
349
|
-
// Make
|
|
350
|
-
jest.spyOn(fs, '
|
|
356
|
+
// Make readFileSync throw a synchronous error
|
|
357
|
+
jest.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
|
|
351
358
|
throw new Error('Sync stream error')
|
|
352
359
|
})
|
|
353
360
|
|
package/src/FileSystem.ts
CHANGED
|
@@ -111,79 +111,38 @@ export class FileSystem {
|
|
|
111
111
|
mime = 'video/mp4'
|
|
112
112
|
): Promise<FileType> {
|
|
113
113
|
// try to get more file metadata
|
|
114
|
-
|
|
114
|
+
let info = {}
|
|
115
|
+
try {
|
|
116
|
+
info = await FileSystem.getInfo(filename)
|
|
117
|
+
} catch (err: any) {
|
|
118
|
+
Worker.warn(`Could not probe media info for ${filename}: ${(err as Error)?.message || err}`)
|
|
119
|
+
}
|
|
115
120
|
meta = { ...meta, ...info }
|
|
116
121
|
Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`)
|
|
117
122
|
const { size } = fs.statSync(filename)
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
} as any)
|
|
131
|
-
.then(() => resolve({ ...meta, size, uploadUrl: undefined }))
|
|
132
|
-
.catch((err) => {
|
|
133
|
-
Worker.error(
|
|
134
|
-
`An error occured while uploading ${filename}: ${err.message}`
|
|
135
|
-
)
|
|
136
|
-
reject(err)
|
|
137
|
-
})
|
|
124
|
+
try {
|
|
125
|
+
Worker.info(`Uploading file ${filename} with size ${size} bytes`)
|
|
126
|
+
const fileBuffer = fs.readFileSync(filename)
|
|
127
|
+
|
|
128
|
+
const res = await fetch(meta.uploadUrl, {
|
|
129
|
+
method: 'PUT',
|
|
130
|
+
body: fileBuffer,
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': meta.contentType || mime,
|
|
133
|
+
},
|
|
138
134
|
})
|
|
139
|
-
}
|
|
140
135
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
let prev = 0 // latest value of done stored when % done was displayed
|
|
145
|
-
const { size } = fs.statSync(filename)
|
|
146
|
-
const readStream = fs.createReadStream(filename)
|
|
147
|
-
|
|
148
|
-
const sizeMB = (size / (1024 * 1024)).toFixed(2)
|
|
149
|
-
|
|
150
|
-
readStream.on('data', (data) => {
|
|
151
|
-
done += data.length
|
|
152
|
-
if (done - prev >= size / 20) {
|
|
153
|
-
// only display message when 5% more has been uploaded
|
|
154
|
-
Worker.info(
|
|
155
|
-
`${((done / size) * 100).toFixed(2)}% - ${(
|
|
156
|
-
done /
|
|
157
|
-
(1024 * 1024)
|
|
158
|
-
).toFixed(2)}MB / ${sizeMB}MB`
|
|
159
|
-
)
|
|
160
|
-
prev = done
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
Worker.info(`Uploading file ${filename} with size ${size} bytes`)
|
|
165
|
-
fetch(meta.uploadUrl, {
|
|
166
|
-
method: 'PUT',
|
|
167
|
-
mode: 'cors',
|
|
168
|
-
duplex: 'half',
|
|
169
|
-
body: readStream,
|
|
170
|
-
headers: {
|
|
171
|
-
'Content-Type': meta.contentType || mime,
|
|
172
|
-
'Content-length': String(size),
|
|
173
|
-
},
|
|
174
|
-
} as any)
|
|
175
|
-
.then(() => resolve({ ...meta, size, uploadUrl: undefined }))
|
|
176
|
-
.catch((err) => {
|
|
177
|
-
Worker.error(
|
|
178
|
-
`An error occured while uploading ${filename}: ${err.message}`
|
|
179
|
-
)
|
|
180
|
-
reject(err)
|
|
181
|
-
})
|
|
182
|
-
} catch (err) {
|
|
183
|
-
Worker.error(err)
|
|
184
|
-
reject(err)
|
|
136
|
+
if (res && res.ok === false) {
|
|
137
|
+
const errorText = typeof res.text === 'function' ? await res.text().catch(() => '') : ''
|
|
138
|
+
throw new Error(`HTTP upload failed with status ${res.status || 'unknown'}: ${errorText}`)
|
|
185
139
|
}
|
|
186
|
-
|
|
140
|
+
|
|
141
|
+
return { ...meta, size, uploadUrl: undefined }
|
|
142
|
+
} catch (err: any) {
|
|
143
|
+
Worker.error(`An error occured while uploading ${filename}: ${err.message}`)
|
|
144
|
+
throw err
|
|
145
|
+
}
|
|
187
146
|
}
|
|
188
147
|
|
|
189
148
|
/**
|
|
@@ -191,22 +150,46 @@ export class FileSystem {
|
|
|
191
150
|
*/
|
|
192
151
|
static getInfo = (file: string): Promise<any> => {
|
|
193
152
|
if (file.endsWith('.mp4') || file.endsWith('.insv')) {
|
|
153
|
+
try {
|
|
154
|
+
const ffprobeStatic = require('ffprobe-static')
|
|
155
|
+
if (ffprobeStatic && ffprobeStatic.path) {
|
|
156
|
+
ffmpeg.setFfprobePath(ffprobeStatic.path)
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// ffprobe-static not present, fallback to system PATH
|
|
160
|
+
}
|
|
161
|
+
|
|
194
162
|
return new Promise((resolve, reject) =>
|
|
195
163
|
ffmpeg.ffprobe(file, (err: any, metadata: any) => {
|
|
196
164
|
if (err) {
|
|
197
165
|
Worker.error(err)
|
|
198
|
-
reject(err)
|
|
166
|
+
return reject(err)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!metadata || !metadata.streams || !metadata.streams.length) {
|
|
170
|
+
Worker.warn(`No streams found in ffprobe metadata for ${file}`)
|
|
171
|
+
return resolve({})
|
|
199
172
|
}
|
|
200
|
-
|
|
173
|
+
|
|
174
|
+
const videoStream =
|
|
175
|
+
metadata.streams.find((s: any) => s.codec_type === 'video') ||
|
|
176
|
+
metadata.streams[0]
|
|
177
|
+
|
|
178
|
+
if (!videoStream) {
|
|
179
|
+
return resolve({})
|
|
180
|
+
}
|
|
181
|
+
|
|
201
182
|
const {
|
|
202
183
|
width,
|
|
203
184
|
height,
|
|
204
185
|
duration,
|
|
205
186
|
bit_rate: bitrate,
|
|
206
187
|
nb_frames: nbFramees,
|
|
207
|
-
} =
|
|
188
|
+
} = videoStream
|
|
189
|
+
|
|
208
190
|
const nb_frames: number = Number.parseFloat(nbFramees as string)
|
|
209
191
|
const framerate = nb_frames / Number.parseFloat(duration as string)
|
|
192
|
+
|
|
210
193
|
resolve({
|
|
211
194
|
width,
|
|
212
195
|
height,
|
package/src/Worker.test.ts
CHANGED
|
@@ -42,9 +42,8 @@ describe('Worker', () => {
|
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
describe('pushEvent', () => {
|
|
45
|
-
it('should
|
|
46
|
-
|
|
47
|
-
expect(result).toBe(false)
|
|
45
|
+
it('should throw error when endpoint is not set', () => {
|
|
46
|
+
expect(() => Worker.pushEvent('test-event')).toThrow('Events endpoint is mandatory but missing')
|
|
48
47
|
expect(mockedAxios.patch).not.toHaveBeenCalled()
|
|
49
48
|
})
|
|
50
49
|
|
|
@@ -145,9 +144,8 @@ describe('Worker', () => {
|
|
|
145
144
|
})
|
|
146
145
|
|
|
147
146
|
describe('pushEventAsync', () => {
|
|
148
|
-
it('should
|
|
149
|
-
|
|
150
|
-
expect(result).toBe(false)
|
|
147
|
+
it('should throw error when endpoint is not set', async () => {
|
|
148
|
+
await expect(Worker.pushEventAsync('test-event')).rejects.toThrow('Events endpoint is mandatory but missing')
|
|
151
149
|
expect(mockedAxios.patch).not.toHaveBeenCalled()
|
|
152
150
|
})
|
|
153
151
|
|
|
@@ -173,14 +171,12 @@ describe('Worker', () => {
|
|
|
173
171
|
)
|
|
174
172
|
})
|
|
175
173
|
|
|
176
|
-
it('should
|
|
174
|
+
it('should throw error on axios error', async () => {
|
|
177
175
|
Worker.endpoint = 'https://api.example.com/events'
|
|
178
176
|
|
|
179
177
|
mockedAxios.patch.mockRejectedValue(new Error('Network error'))
|
|
180
178
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
expect(result).toBe(false)
|
|
179
|
+
await expect(Worker.pushEventAsync('test-event')).rejects.toThrow('Failed to push event to backend: Network error')
|
|
184
180
|
})
|
|
185
181
|
|
|
186
182
|
it('should handle non-OK status response', async () => {
|
package/src/Worker.ts
CHANGED
|
@@ -66,8 +66,8 @@ export class Worker extends Core {
|
|
|
66
66
|
*/
|
|
67
67
|
static pushEvent(event: string, data = {}, ts = 0) {
|
|
68
68
|
if (!this.endpoint) {
|
|
69
|
-
Worker.
|
|
70
|
-
|
|
69
|
+
Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`)
|
|
70
|
+
throw new Error(`Events endpoint is mandatory but missing`)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
ts = ts === Date.now() ? Date.now() + 1 : Date.now()
|
|
@@ -89,8 +89,7 @@ export class Worker extends Core {
|
|
|
89
89
|
return true
|
|
90
90
|
})
|
|
91
91
|
.catch((err) => {
|
|
92
|
-
Worker.error(`Failed to push event to backend: ${err}`)
|
|
93
|
-
return false
|
|
92
|
+
Worker.error(`Failed to push event to backend: ${err.message}`)
|
|
94
93
|
})
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -103,8 +102,8 @@ export class Worker extends Core {
|
|
|
103
102
|
*/
|
|
104
103
|
static async pushEventAsync(event: string, data = {}, ts = 0) {
|
|
105
104
|
if (!this.endpoint) {
|
|
106
|
-
Worker.
|
|
107
|
-
|
|
105
|
+
Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`)
|
|
106
|
+
throw new Error(`Events endpoint is mandatory but missing`)
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
try {
|
|
@@ -119,13 +118,13 @@ export class Worker extends Core {
|
|
|
119
118
|
|
|
120
119
|
const res = await axios.patch(Worker.endpoint, payload)
|
|
121
120
|
|
|
122
|
-
if (res.statusText === 'OK') {
|
|
121
|
+
if (res.statusText === 'OK' || res.status === 200) {
|
|
123
122
|
Worker.info(`Event pushed to backend: ${res.statusText}`)
|
|
124
123
|
return true
|
|
125
124
|
}
|
|
126
|
-
} catch (err) {
|
|
127
|
-
Worker.error(`Failed to push event to backend: ${err}`)
|
|
128
|
-
|
|
125
|
+
} catch (err: any) {
|
|
126
|
+
Worker.error(`Failed to push event to backend: ${err.message}`)
|
|
127
|
+
throw new Error(`Failed to push event to backend: ${err.message}`)
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
130
|
|