orator-conversion 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.
@@ -0,0 +1,504 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ const libSharp = require('sharp');
4
+ const libChildProcess = require('child_process');
5
+ const libFS = require('fs');
6
+ const libPath = require('path');
7
+ const libOS = require('os');
8
+
9
+ const libEndpointImageJpgToPng = require('./endpoints/Endpoint-Image-JpgToPng.js');
10
+ const libEndpointImagePngToJpg = require('./endpoints/Endpoint-Image-PngToJpg.js');
11
+ const libEndpointPdfPageToPng = require('./endpoints/Endpoint-Pdf-PageToPng.js');
12
+ const libEndpointPdfPageToJpg = require('./endpoints/Endpoint-Pdf-PageToJpg.js');
13
+ const libEndpointPdfPageToPngSized = require('./endpoints/Endpoint-Pdf-PageToPng-Sized.js');
14
+ const libEndpointPdfPageToJpgSized = require('./endpoints/Endpoint-Pdf-PageToJpg-Sized.js');
15
+
16
+ const _DEFAULT_ROUTE_PREFIX = '/conversion';
17
+ const _DEFAULT_VERSION = '1.0';
18
+ const _DEFAULT_LOG_LEVEL = 0;
19
+ const _DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
20
+ const _DEFAULT_PDFTK_PATH = 'pdftk';
21
+ const _DEFAULT_PDFTOPPM_PATH = 'pdftoppm';
22
+
23
+ /**
24
+ * Fable service that provides file format conversion endpoints for an Orator web server.
25
+ * Includes built-in image converters (JPG to PNG, PNG to JPG), PDF page extraction
26
+ * (PDF to PNG/JPG via pdftk + pdftoppm + sharp), and supports custom converters.
27
+ *
28
+ * All routes are versioned: {RoutePrefix}/{Version}/{converterPath}
29
+ */
30
+ class OratorFileTranslation extends libFableServiceProviderBase
31
+ {
32
+ /**
33
+ * Construct a service instance.
34
+ *
35
+ * @param {object} pFable The fable instance for the application.
36
+ * @param {object} pOptions Custom settings for this service instance.
37
+ * @param {string} pServiceHash The hash for this service instance.
38
+ *
39
+ * @return an OratorFileTranslation instance.
40
+ */
41
+ constructor(pFable, pOptions, pServiceHash)
42
+ {
43
+ super(pFable, pOptions, pServiceHash);
44
+
45
+ this.serviceType = 'OratorFileTranslation';
46
+
47
+ // Configuration: options > fable.settings > defaults
48
+ this.RoutePrefix = (`RoutePrefix` in this.options) ? this.options.RoutePrefix
49
+ : `OratorFileTranslationRoutePrefix` in this.fable.settings ? this.fable.settings.OratorFileTranslationRoutePrefix
50
+ : _DEFAULT_ROUTE_PREFIX;
51
+
52
+ this.Version = (`Version` in this.options) ? this.options.Version
53
+ : `OratorFileTranslationVersion` in this.fable.settings ? this.fable.settings.OratorFileTranslationVersion
54
+ : _DEFAULT_VERSION;
55
+
56
+ this.LogLevel = (`LogLevel` in this.options) ? this.options.LogLevel
57
+ : `OratorFileTranslationLogLevel` in this.fable.settings ? this.fable.settings.OratorFileTranslationLogLevel
58
+ : _DEFAULT_LOG_LEVEL;
59
+
60
+ this.MaxFileSize = (`MaxFileSize` in this.options) ? this.options.MaxFileSize
61
+ : `OratorFileTranslationMaxFileSize` in this.fable.settings ? this.fable.settings.OratorFileTranslationMaxFileSize
62
+ : _DEFAULT_MAX_FILE_SIZE;
63
+
64
+ this.PdftkPath = (`PdftkPath` in this.options) ? this.options.PdftkPath
65
+ : `OratorFileTranslationPdftkPath` in this.fable.settings ? this.fable.settings.OratorFileTranslationPdftkPath
66
+ : _DEFAULT_PDFTK_PATH;
67
+
68
+ this.PdftoppmPath = (`PdftoppmPath` in this.options) ? this.options.PdftoppmPath
69
+ : `OratorFileTranslationPdftoppmPath` in this.fable.settings ? this.fable.settings.OratorFileTranslationPdftoppmPath
70
+ : _DEFAULT_PDFTOPPM_PATH;
71
+
72
+ // Registry of available converters (extensible)
73
+ this.converters = {};
74
+
75
+ // Array of instantiated endpoint services
76
+ this._endpointServices = [];
77
+
78
+ // Initialize the built-in converters
79
+ this.initializeDefaultConverters();
80
+ }
81
+
82
+ /**
83
+ * Register the default image format converters and PDF page extraction converters.
84
+ *
85
+ * Each converter is implemented as a separate Fable service in the endpoints/ folder.
86
+ */
87
+ initializeDefaultConverters()
88
+ {
89
+ // Register endpoint service types with fable
90
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-ImageJpgToPng', libEndpointImageJpgToPng);
91
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-ImagePngToJpg', libEndpointImagePngToJpg);
92
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-PdfPageToPng', libEndpointPdfPageToPng);
93
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-PdfPageToJpg', libEndpointPdfPageToJpg);
94
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-PdfPageToPngSized', libEndpointPdfPageToPngSized);
95
+ this.fable.addServiceTypeIfNotExists('OratorFileTranslationEndpoint-PdfPageToJpgSized', libEndpointPdfPageToJpgSized);
96
+
97
+ let tmpEndpointOptions = { FileTranslation: this };
98
+
99
+ let tmpEndpointTypes =
100
+ [
101
+ 'OratorFileTranslationEndpoint-ImageJpgToPng',
102
+ 'OratorFileTranslationEndpoint-ImagePngToJpg',
103
+ 'OratorFileTranslationEndpoint-PdfPageToPng',
104
+ 'OratorFileTranslationEndpoint-PdfPageToJpg',
105
+ 'OratorFileTranslationEndpoint-PdfPageToPngSized',
106
+ 'OratorFileTranslationEndpoint-PdfPageToJpgSized'
107
+ ];
108
+
109
+ for (let i = 0; i < tmpEndpointTypes.length; i++)
110
+ {
111
+ let tmpEndpoint = this.fable.instantiateServiceProviderWithoutRegistration(tmpEndpointTypes[i], tmpEndpointOptions);
112
+ this._endpointServices.push(tmpEndpoint);
113
+ this.addConverter(tmpEndpoint.converterPath, tmpEndpoint.convert.bind(tmpEndpoint));
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Extract a single page from a PDF buffer using pdftk, returning the single-page PDF as a buffer.
119
+ *
120
+ * @param {Buffer} pPdfBuffer The input PDF buffer.
121
+ * @param {number} pPageNumber The 1-based page number to extract.
122
+ * @param {Function} fCallback Called with (pError, pSinglePagePdfBuffer).
123
+ */
124
+ extractPdfPage(pPdfBuffer, pPageNumber, fCallback)
125
+ {
126
+ let tmpTempDir = libOS.tmpdir();
127
+ let tmpInputPath = libPath.join(tmpTempDir, `orator_ft_input_${Date.now()}_${Math.random().toString(36).slice(2)}.pdf`);
128
+ let tmpOutputPath = libPath.join(tmpTempDir, `orator_ft_output_${Date.now()}_${Math.random().toString(36).slice(2)}.pdf`);
129
+
130
+ let tmpCleanup = () =>
131
+ {
132
+ try { libFS.unlinkSync(tmpInputPath); } catch (pIgnore) { /* file may not exist */ }
133
+ try { libFS.unlinkSync(tmpOutputPath); } catch (pIgnore) { /* file may not exist */ }
134
+ };
135
+
136
+ try
137
+ {
138
+ libFS.writeFileSync(tmpInputPath, pPdfBuffer);
139
+ }
140
+ catch (pWriteError)
141
+ {
142
+ tmpCleanup();
143
+ return fCallback(new Error(`Failed to write temporary PDF file: ${pWriteError.message}`));
144
+ }
145
+
146
+ let tmpCommand = `${this.PdftkPath} "${tmpInputPath}" cat ${pPageNumber} output "${tmpOutputPath}"`;
147
+
148
+ if (this.LogLevel > 1)
149
+ {
150
+ this.log.info(`OratorFileTranslation: executing pdftk command: ${tmpCommand}`);
151
+ }
152
+
153
+ libChildProcess.exec(tmpCommand,
154
+ {
155
+ timeout: 30000,
156
+ maxBuffer: this.MaxFileSize
157
+ },
158
+ (pExecError, pStdout, pStderr) =>
159
+ {
160
+ if (pExecError)
161
+ {
162
+ tmpCleanup();
163
+ let tmpMessage = pStderr ? pStderr.toString().trim() : pExecError.message;
164
+ return fCallback(new Error(`pdftk failed: ${tmpMessage}`));
165
+ }
166
+
167
+ try
168
+ {
169
+ let tmpOutputBuffer = libFS.readFileSync(tmpOutputPath);
170
+ tmpCleanup();
171
+ return fCallback(null, tmpOutputBuffer);
172
+ }
173
+ catch (pReadError)
174
+ {
175
+ tmpCleanup();
176
+ return fCallback(new Error(`Failed to read pdftk output: ${pReadError.message}`));
177
+ }
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Render a specific page of a PDF to an image buffer.
183
+ *
184
+ * Uses pdftk to extract the single page and pdftoppm to rasterize it.
185
+ * The output format is determined by the pFormat parameter ('png' or 'jpeg').
186
+ *
187
+ * When pOptions.LongSidePixels is provided, the image is rendered at 300 DPI
188
+ * and then resized so the longest dimension fits within the specified pixel count.
189
+ *
190
+ * @param {Buffer} pPdfBuffer The input PDF buffer.
191
+ * @param {number} pPageNumber The 1-based page number to render.
192
+ * @param {string} pFormat The output format: 'png' or 'jpeg'.
193
+ * @param {Function} fCallback Called with (pError, pImageBuffer).
194
+ * @param {object} [pOptions] Optional rendering options.
195
+ * @param {number} [pOptions.LongSidePixels] Target size for the longest side of the output image.
196
+ */
197
+ renderPdfPageToImage(pPdfBuffer, pPageNumber, pFormat, fCallback, pOptions)
198
+ {
199
+ let tmpTempDir = libOS.tmpdir();
200
+ let tmpUniqueId = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
201
+ let tmpInputPath = libPath.join(tmpTempDir, `orator_ft_render_input_${tmpUniqueId}.pdf`);
202
+ let tmpOutputPrefix = libPath.join(tmpTempDir, `orator_ft_render_output_${tmpUniqueId}`);
203
+
204
+ let tmpLongSidePixels = (pOptions && pOptions.LongSidePixels) ? pOptions.LongSidePixels : 0;
205
+
206
+ // When resizing is requested, render at 300 DPI for higher quality source material
207
+ let tmpRenderDpi = (tmpLongSidePixels > 0) ? 300 : 150;
208
+
209
+ // pdftoppm appends -NNNNNN.png (or .jpg) to the output prefix
210
+ // When rendering a single page, it will be -01.png or similar
211
+ let tmpExpectedSuffix = (pFormat === 'jpeg') ? '.jpg' : '.png';
212
+
213
+ let tmpCleanup = () =>
214
+ {
215
+ try { libFS.unlinkSync(tmpInputPath); } catch (pIgnore) { /* file may not exist */ }
216
+ // Clean up any pdftoppm output files matching the prefix
217
+ try
218
+ {
219
+ let tmpDirContents = libFS.readdirSync(tmpTempDir);
220
+ let tmpPrefix = libPath.basename(tmpOutputPrefix);
221
+ for (let i = 0; i < tmpDirContents.length; i++)
222
+ {
223
+ if (tmpDirContents[i].startsWith(tmpPrefix))
224
+ {
225
+ try { libFS.unlinkSync(libPath.join(tmpTempDir, tmpDirContents[i])); } catch (pIgnore) { /* ignore */ }
226
+ }
227
+ }
228
+ }
229
+ catch (pIgnore) { /* ignore readdir errors */ }
230
+ };
231
+
232
+ try
233
+ {
234
+ libFS.writeFileSync(tmpInputPath, pPdfBuffer);
235
+ }
236
+ catch (pWriteError)
237
+ {
238
+ tmpCleanup();
239
+ return fCallback(new Error(`Failed to write temporary PDF file: ${pWriteError.message}`));
240
+ }
241
+
242
+ // Build the pdftoppm command to render the specific page
243
+ let tmpFormatFlag = (pFormat === 'jpeg') ? '-jpeg' : '-png';
244
+ let tmpCommand = `${this.PdftoppmPath} ${tmpFormatFlag} -f ${pPageNumber} -l ${pPageNumber} -r ${tmpRenderDpi} "${tmpInputPath}" "${tmpOutputPrefix}"`;
245
+
246
+ if (this.LogLevel > 1)
247
+ {
248
+ this.log.info(`OratorFileTranslation: executing pdftoppm command: ${tmpCommand}`);
249
+ }
250
+
251
+ libChildProcess.exec(tmpCommand,
252
+ {
253
+ timeout: 30000,
254
+ maxBuffer: this.MaxFileSize
255
+ },
256
+ (pExecError, pStdout, pStderr) =>
257
+ {
258
+ if (pExecError)
259
+ {
260
+ tmpCleanup();
261
+ let tmpMessage = pStderr ? pStderr.toString().trim() : pExecError.message;
262
+ return fCallback(new Error(`pdftoppm failed: ${tmpMessage}`));
263
+ }
264
+
265
+ // Find the output file — pdftoppm names it with a page number suffix
266
+ try
267
+ {
268
+ let tmpDirContents = libFS.readdirSync(tmpTempDir);
269
+ let tmpPrefix = libPath.basename(tmpOutputPrefix);
270
+ let tmpOutputFile = null;
271
+
272
+ for (let i = 0; i < tmpDirContents.length; i++)
273
+ {
274
+ if (tmpDirContents[i].startsWith(tmpPrefix) && tmpDirContents[i].endsWith(tmpExpectedSuffix))
275
+ {
276
+ tmpOutputFile = libPath.join(tmpTempDir, tmpDirContents[i]);
277
+ break;
278
+ }
279
+ }
280
+
281
+ if (!tmpOutputFile)
282
+ {
283
+ tmpCleanup();
284
+ return fCallback(new Error('pdftoppm produced no output file.'));
285
+ }
286
+
287
+ let tmpImageBuffer = libFS.readFileSync(tmpOutputFile);
288
+ tmpCleanup();
289
+
290
+ // If LongSidePixels is specified, resize with sharp so the longest dimension
291
+ // fits within the target pixel count, preserving aspect ratio.
292
+ if (tmpLongSidePixels > 0)
293
+ {
294
+ let tmpSharpInstance = libSharp(tmpImageBuffer);
295
+
296
+ tmpSharpInstance.metadata()
297
+ .then(
298
+ (pMetadata) =>
299
+ {
300
+ let tmpResizeOptions = {};
301
+ if (pMetadata.width >= pMetadata.height)
302
+ {
303
+ tmpResizeOptions.width = tmpLongSidePixels;
304
+ }
305
+ else
306
+ {
307
+ tmpResizeOptions.height = tmpLongSidePixels;
308
+ }
309
+
310
+ let tmpResizer = tmpSharpInstance.resize(tmpResizeOptions);
311
+
312
+ if (pFormat === 'jpeg')
313
+ {
314
+ tmpResizer = tmpResizer.jpeg();
315
+ }
316
+ else
317
+ {
318
+ tmpResizer = tmpResizer.png();
319
+ }
320
+
321
+ return tmpResizer.toBuffer();
322
+ })
323
+ .then(
324
+ (pResizedBuffer) =>
325
+ {
326
+ return fCallback(null, pResizedBuffer);
327
+ })
328
+ .catch(
329
+ (pResizeError) =>
330
+ {
331
+ return fCallback(new Error(`Image resize failed: ${pResizeError.message}`));
332
+ });
333
+ }
334
+ else
335
+ {
336
+ return fCallback(null, tmpImageBuffer);
337
+ }
338
+ }
339
+ catch (pReadError)
340
+ {
341
+ tmpCleanup();
342
+ return fCallback(new Error(`Failed to read pdftoppm output: ${pReadError.message}`));
343
+ }
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Register a converter function for a given path segment.
349
+ *
350
+ * The path may include route parameters using :paramName syntax (e.g. 'pdf-to-page-png/:Page').
351
+ * The converter function receives the input buffer, the request object (for accessing params),
352
+ * and a callback.
353
+ *
354
+ * @param {string} pPath The path segment (e.g. 'image/jpg-to-png' or 'pdf-to-page-png/:Page').
355
+ * @param {Function} fConverter A function(pInputBuffer, pRequest, fCallback) where fCallback is (pError, pOutputBuffer, pContentType).
356
+ */
357
+ addConverter(pPath, fConverter)
358
+ {
359
+ this.converters[pPath] = fConverter;
360
+ if (this.LogLevel > 0)
361
+ {
362
+ this.log.info(`OratorFileTranslation: registered converter [${pPath}]`);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Collect the raw request body from the stream, enforcing the max file size limit.
368
+ *
369
+ * @param {object} pRequest The incoming HTTP request.
370
+ * @param {Function} fCallback Called with (pError, pBuffer).
371
+ */
372
+ collectRequestBody(pRequest, fCallback)
373
+ {
374
+ // If the body parser has already provided the body as a Buffer, use it directly
375
+ if (pRequest.body && Buffer.isBuffer(pRequest.body))
376
+ {
377
+ if (pRequest.body.length > this.MaxFileSize)
378
+ {
379
+ return fCallback(new Error(`File size exceeds maximum allowed size of ${this.MaxFileSize} bytes.`));
380
+ }
381
+ return fCallback(null, pRequest.body);
382
+ }
383
+
384
+ let tmpChunks = [];
385
+ let tmpTotalLength = 0;
386
+ let tmpErrored = false;
387
+
388
+ pRequest.on('data',
389
+ (pChunk) =>
390
+ {
391
+ if (tmpErrored)
392
+ {
393
+ return;
394
+ }
395
+ tmpTotalLength += pChunk.length;
396
+ if (tmpTotalLength > this.MaxFileSize)
397
+ {
398
+ tmpErrored = true;
399
+ pRequest.destroy();
400
+ return fCallback(new Error(`File size exceeds maximum allowed size of ${this.MaxFileSize} bytes.`));
401
+ }
402
+ tmpChunks.push(pChunk);
403
+ });
404
+
405
+ pRequest.on('end',
406
+ () =>
407
+ {
408
+ if (tmpErrored)
409
+ {
410
+ return;
411
+ }
412
+ let tmpBuffer = Buffer.concat(tmpChunks);
413
+ return fCallback(null, tmpBuffer);
414
+ });
415
+
416
+ pRequest.on('error',
417
+ (pError) =>
418
+ {
419
+ if (tmpErrored)
420
+ {
421
+ return;
422
+ }
423
+ tmpErrored = true;
424
+ return fCallback(pError);
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Register POST routes on the Orator service server for each registered converter.
430
+ *
431
+ * Routes are versioned: {RoutePrefix}/{Version}/{converterPath}
432
+ *
433
+ * @return {boolean} True if routes were registered, false if Orator is not available.
434
+ */
435
+ connectRoutes()
436
+ {
437
+ if (!this.fable.Orator)
438
+ {
439
+ this.log.error('OratorFileTranslation: Orator must be initialized before connecting routes.');
440
+ return false;
441
+ }
442
+
443
+ let tmpConverterPaths = Object.keys(this.converters);
444
+
445
+ for (let i = 0; i < tmpConverterPaths.length; i++)
446
+ {
447
+ let tmpPath = tmpConverterPaths[i];
448
+ let tmpRoute = `${this.RoutePrefix}/${this.Version}/${tmpPath}`;
449
+ let tmpConverter = this.converters[tmpPath];
450
+
451
+ if (this.LogLevel > 0)
452
+ {
453
+ this.log.info(`OratorFileTranslation: registering POST route [${tmpRoute}]`);
454
+ }
455
+
456
+ this.fable.Orator.serviceServer.post(tmpRoute,
457
+ (pRequest, pResponse, fNext) =>
458
+ {
459
+ this.collectRequestBody(pRequest,
460
+ (pCollectError, pInputBuffer) =>
461
+ {
462
+ if (pCollectError)
463
+ {
464
+ this.log.error(`OratorFileTranslation: Error collecting request body for [${tmpRoute}]: ${pCollectError.message}`);
465
+ pResponse.writeHead(413, { 'Content-Type': 'application/json' });
466
+ pResponse.end(JSON.stringify({ error: pCollectError.message }));
467
+ return fNext();
468
+ }
469
+
470
+ if (!pInputBuffer || pInputBuffer.length === 0)
471
+ {
472
+ pResponse.writeHead(400, { 'Content-Type': 'application/json' });
473
+ pResponse.end(JSON.stringify({ error: 'No file data provided in request body.' }));
474
+ return fNext();
475
+ }
476
+
477
+ tmpConverter(pInputBuffer, pRequest,
478
+ (pConvertError, pOutputBuffer, pContentType) =>
479
+ {
480
+ if (pConvertError)
481
+ {
482
+ this.log.error(`OratorFileTranslation: Conversion error for [${tmpRoute}]: ${pConvertError.message}`);
483
+ pResponse.writeHead(500, { 'Content-Type': 'application/json' });
484
+ pResponse.end(JSON.stringify({ error: `Conversion failed: ${pConvertError.message}` }));
485
+ return fNext();
486
+ }
487
+
488
+ pResponse.writeHead(200,
489
+ {
490
+ 'Content-Type': pContentType,
491
+ 'Content-Length': pOutputBuffer.length
492
+ });
493
+ pResponse.end(pOutputBuffer);
494
+ return fNext();
495
+ });
496
+ });
497
+ });
498
+ }
499
+
500
+ return true;
501
+ }
502
+ }
503
+
504
+ module.exports = OratorFileTranslation;
@@ -0,0 +1,34 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ const libSharp = require('sharp');
4
+
5
+ class EndpointImageJpgToPng extends libFableServiceProviderBase
6
+ {
7
+ constructor(pFable, pOptions, pServiceHash)
8
+ {
9
+ super(pFable, pOptions, pServiceHash);
10
+
11
+ this.serviceType = 'OratorFileTranslationEndpoint-ImageJpgToPng';
12
+
13
+ this.converterPath = 'image/jpg-to-png';
14
+ }
15
+
16
+ convert(pInputBuffer, pRequest, fCallback)
17
+ {
18
+ libSharp(pInputBuffer)
19
+ .png()
20
+ .toBuffer()
21
+ .then(
22
+ (pOutputBuffer) =>
23
+ {
24
+ return fCallback(null, pOutputBuffer, 'image/png');
25
+ })
26
+ .catch(
27
+ (pError) =>
28
+ {
29
+ return fCallback(pError);
30
+ });
31
+ }
32
+ }
33
+
34
+ module.exports = EndpointImageJpgToPng;
@@ -0,0 +1,34 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ const libSharp = require('sharp');
4
+
5
+ class EndpointImagePngToJpg extends libFableServiceProviderBase
6
+ {
7
+ constructor(pFable, pOptions, pServiceHash)
8
+ {
9
+ super(pFable, pOptions, pServiceHash);
10
+
11
+ this.serviceType = 'OratorFileTranslationEndpoint-ImagePngToJpg';
12
+
13
+ this.converterPath = 'image/png-to-jpg';
14
+ }
15
+
16
+ convert(pInputBuffer, pRequest, fCallback)
17
+ {
18
+ libSharp(pInputBuffer)
19
+ .jpeg()
20
+ .toBuffer()
21
+ .then(
22
+ (pOutputBuffer) =>
23
+ {
24
+ return fCallback(null, pOutputBuffer, 'image/jpeg');
25
+ })
26
+ .catch(
27
+ (pError) =>
28
+ {
29
+ return fCallback(pError);
30
+ });
31
+ }
32
+ }
33
+
34
+ module.exports = EndpointImagePngToJpg;
@@ -0,0 +1,43 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ class EndpointPdfPageToJpgSized extends libFableServiceProviderBase
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+
9
+ this.serviceType = 'OratorFileTranslationEndpoint-PdfPageToJpgSized';
10
+
11
+ this._FileTranslation = (pOptions && pOptions.FileTranslation) ? pOptions.FileTranslation : null;
12
+
13
+ this.converterPath = 'pdf-to-page-jpg/:Page/:LongSidePixels';
14
+ }
15
+
16
+ convert(pInputBuffer, pRequest, fCallback)
17
+ {
18
+ let tmpPage = parseInt(pRequest.params.Page, 10);
19
+ if (isNaN(tmpPage) || tmpPage < 1)
20
+ {
21
+ return fCallback(new Error('Invalid page number. Must be a positive integer.'));
22
+ }
23
+
24
+ let tmpLongSidePixels = parseInt(pRequest.params.LongSidePixels, 10);
25
+ if (isNaN(tmpLongSidePixels) || tmpLongSidePixels < 1)
26
+ {
27
+ return fCallback(new Error('Invalid LongSidePixels value. Must be a positive integer.'));
28
+ }
29
+
30
+ this._FileTranslation.renderPdfPageToImage(pInputBuffer, tmpPage, 'jpeg',
31
+ (pRenderError, pImageBuffer) =>
32
+ {
33
+ if (pRenderError)
34
+ {
35
+ return fCallback(pRenderError);
36
+ }
37
+ return fCallback(null, pImageBuffer, 'image/jpeg');
38
+ },
39
+ { LongSidePixels: tmpLongSidePixels });
40
+ }
41
+ }
42
+
43
+ module.exports = EndpointPdfPageToJpgSized;
@@ -0,0 +1,36 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ class EndpointPdfPageToJpg extends libFableServiceProviderBase
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+
9
+ this.serviceType = 'OratorFileTranslationEndpoint-PdfPageToJpg';
10
+
11
+ this._FileTranslation = (pOptions && pOptions.FileTranslation) ? pOptions.FileTranslation : null;
12
+
13
+ this.converterPath = 'pdf-to-page-jpg/:Page';
14
+ }
15
+
16
+ convert(pInputBuffer, pRequest, fCallback)
17
+ {
18
+ let tmpPage = parseInt(pRequest.params.Page, 10);
19
+ if (isNaN(tmpPage) || tmpPage < 1)
20
+ {
21
+ return fCallback(new Error('Invalid page number. Must be a positive integer.'));
22
+ }
23
+
24
+ this._FileTranslation.renderPdfPageToImage(pInputBuffer, tmpPage, 'jpeg',
25
+ (pRenderError, pImageBuffer) =>
26
+ {
27
+ if (pRenderError)
28
+ {
29
+ return fCallback(pRenderError);
30
+ }
31
+ return fCallback(null, pImageBuffer, 'image/jpeg');
32
+ });
33
+ }
34
+ }
35
+
36
+ module.exports = EndpointPdfPageToJpg;
@@ -0,0 +1,43 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ class EndpointPdfPageToPngSized extends libFableServiceProviderBase
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+
9
+ this.serviceType = 'OratorFileTranslationEndpoint-PdfPageToPngSized';
10
+
11
+ this._FileTranslation = (pOptions && pOptions.FileTranslation) ? pOptions.FileTranslation : null;
12
+
13
+ this.converterPath = 'pdf-to-page-png/:Page/:LongSidePixels';
14
+ }
15
+
16
+ convert(pInputBuffer, pRequest, fCallback)
17
+ {
18
+ let tmpPage = parseInt(pRequest.params.Page, 10);
19
+ if (isNaN(tmpPage) || tmpPage < 1)
20
+ {
21
+ return fCallback(new Error('Invalid page number. Must be a positive integer.'));
22
+ }
23
+
24
+ let tmpLongSidePixels = parseInt(pRequest.params.LongSidePixels, 10);
25
+ if (isNaN(tmpLongSidePixels) || tmpLongSidePixels < 1)
26
+ {
27
+ return fCallback(new Error('Invalid LongSidePixels value. Must be a positive integer.'));
28
+ }
29
+
30
+ this._FileTranslation.renderPdfPageToImage(pInputBuffer, tmpPage, 'png',
31
+ (pRenderError, pImageBuffer) =>
32
+ {
33
+ if (pRenderError)
34
+ {
35
+ return fCallback(pRenderError);
36
+ }
37
+ return fCallback(null, pImageBuffer, 'image/png');
38
+ },
39
+ { LongSidePixels: tmpLongSidePixels });
40
+ }
41
+ }
42
+
43
+ module.exports = EndpointPdfPageToPngSized;