@hazeljs/pdf-to-audio 0.2.0-beta.27

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +18 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.test.d.ts +2 -0
  8. package/dist/index.test.d.ts.map +1 -0
  9. package/dist/index.test.js +49 -0
  10. package/dist/index.test.js.map +1 -0
  11. package/dist/pdf-extractor.d.ts +5 -0
  12. package/dist/pdf-extractor.d.ts.map +1 -0
  13. package/dist/pdf-extractor.js +15 -0
  14. package/dist/pdf-extractor.js.map +1 -0
  15. package/dist/pdf-extractor.test.d.ts +2 -0
  16. package/dist/pdf-extractor.test.d.ts.map +1 -0
  17. package/dist/pdf-extractor.test.js +40 -0
  18. package/dist/pdf-extractor.test.js.map +1 -0
  19. package/dist/pdf-to-audio-queue.worker.d.ts +16 -0
  20. package/dist/pdf-to-audio-queue.worker.d.ts.map +1 -0
  21. package/dist/pdf-to-audio-queue.worker.js +103 -0
  22. package/dist/pdf-to-audio-queue.worker.js.map +1 -0
  23. package/dist/pdf-to-audio-queue.worker.test.d.ts +2 -0
  24. package/dist/pdf-to-audio-queue.worker.test.d.ts.map +1 -0
  25. package/dist/pdf-to-audio-queue.worker.test.js +24 -0
  26. package/dist/pdf-to-audio-queue.worker.test.js.map +1 -0
  27. package/dist/pdf-to-audio.controller.d.ts +10 -0
  28. package/dist/pdf-to-audio.controller.d.ts.map +1 -0
  29. package/dist/pdf-to-audio.controller.js +210 -0
  30. package/dist/pdf-to-audio.controller.js.map +1 -0
  31. package/dist/pdf-to-audio.controller.test.d.ts +2 -0
  32. package/dist/pdf-to-audio.controller.test.d.ts.map +1 -0
  33. package/dist/pdf-to-audio.controller.test.js +252 -0
  34. package/dist/pdf-to-audio.controller.test.js.map +1 -0
  35. package/dist/pdf-to-audio.module.d.ts +30 -0
  36. package/dist/pdf-to-audio.module.d.ts.map +1 -0
  37. package/dist/pdf-to-audio.module.js +66 -0
  38. package/dist/pdf-to-audio.module.js.map +1 -0
  39. package/dist/pdf-to-audio.module.test.d.ts +2 -0
  40. package/dist/pdf-to-audio.module.test.d.ts.map +1 -0
  41. package/dist/pdf-to-audio.module.test.js +50 -0
  42. package/dist/pdf-to-audio.module.test.js.map +1 -0
  43. package/dist/pdf-to-audio.service.d.ts +10 -0
  44. package/dist/pdf-to-audio.service.d.ts.map +1 -0
  45. package/dist/pdf-to-audio.service.js +132 -0
  46. package/dist/pdf-to-audio.service.js.map +1 -0
  47. package/dist/pdf-to-audio.service.test.d.ts +2 -0
  48. package/dist/pdf-to-audio.service.test.d.ts.map +1 -0
  49. package/dist/pdf-to-audio.service.test.js +146 -0
  50. package/dist/pdf-to-audio.service.test.js.map +1 -0
  51. package/dist/types.d.ts +16 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 HazelJS Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @hazeljs/pdf-to-audio
2
+
3
+ Convert PDF documents to audio using OpenAI TTS. Extracts text from PDFs, chunks it, generates speech per chunk, and merges the audio.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hazeljs/pdf-to-audio @hazeljs/core @hazeljs/ai @hazeljs/queue @hazeljs/rag ioredis
9
+ ```
10
+
11
+ **Note:** Requires Redis for the job queue. Start Redis before using PDF-to-audio.
12
+
13
+ ## Usage
14
+
15
+ ### Module (REST API)
16
+
17
+ ```ts
18
+ import { HazelApp } from '@hazeljs/core';
19
+ import { PdfToAudioModule } from '@hazeljs/pdf-to-audio';
20
+
21
+ const app = new HazelApp({
22
+ module: YourAppModule,
23
+ });
24
+
25
+ // In your app module (use forRoot with Redis connection):
26
+ import { HazelModule } from '@hazeljs/core';
27
+ import { PdfToAudioModule } from '@hazeljs/pdf-to-audio';
28
+
29
+ @HazelModule({
30
+ imports: [
31
+ PdfToAudioModule.forRoot({
32
+ connection: {
33
+ host: process.env.REDIS_HOST || 'localhost',
34
+ port: parseInt(process.env.REDIS_PORT || '6379', 10),
35
+ },
36
+ outputDir: './data/pdf-to-audio', // optional, default: ./data/pdf-to-audio
37
+ }),
38
+ ],
39
+ })
40
+ export class AppModule {}
41
+ ```
42
+
43
+ **Endpoints (async job-based):**
44
+
45
+ 1. `POST /api/pdf-to-audio/convert` — Submit PDF, returns `{ jobId }` (202)
46
+ - Content-Type: `multipart/form-data`
47
+ - Field: `file` (PDF file)
48
+ - Optional: `includeSummary` = `"false"`, `summaryOnly` = `"true"`, `voice` = `"alloy"` etc.
49
+
50
+ 2. `GET /api/pdf-to-audio/status/:jobId` — Check job status (`pending`, `processing`, `completed`, `failed`)
51
+
52
+ 3. `GET /api/pdf-to-audio/download/:jobId` — Download MP3 when job is completed (reads from disk; files stored in `outputDir`)
53
+
54
+ ### Service (programmatic)
55
+
56
+ ```ts
57
+ import { PdfToAudioService } from '@hazeljs/pdf-to-audio';
58
+ import { OpenAIProvider } from '@hazeljs/ai';
59
+
60
+ const provider = new OpenAIProvider();
61
+ const service = new PdfToAudioService(provider);
62
+ const audioBuffer = await service.convert(pdfBuffer, { voice: 'alloy', model: 'tts-1' });
63
+ ```
64
+
65
+ ### CLI
66
+
67
+ Requires a running API server. Uses async job flow: submit → poll status → download.
68
+
69
+ ```bash
70
+ # Submit job and wait for completion, then save to output
71
+ hazel pdf-to-audio convert document.pdf --api-url http://localhost:3000 --wait -o audio.mp3
72
+
73
+ # Submit only (returns job ID)
74
+ hazel pdf-to-audio convert document.pdf --api-url http://localhost:3000
75
+
76
+ # Check status and download when ready
77
+ hazel pdf-to-audio status <jobId> --api-url http://localhost:3000 -o audio.mp3
78
+ ```
79
+
80
+ ## Environment
81
+
82
+ - `OPENAI_API_KEY` — Required for TTS
83
+
84
+ ## Options
85
+
86
+ | Option | Description | Default |
87
+ |--------|-------------|---------|
88
+ | voice | TTS voice (alloy, echo, fable, onyx, nova, shimmer) | alloy |
89
+ | model | TTS model (tts-1, tts-1-hd) | tts-1 |
90
+ | format | Output format (mp3, opus) | mp3 |
91
+ | includeSummary | Include AI-generated document summary at the start of the audio | true |
92
+ | summaryOnly | Output only the summary—do not read the full document | false |
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @hazeljs/pdf-to-audio
3
+ * Convert PDF documents to audio using TTS
4
+ */
5
+ export { PdfToAudioModule } from './pdf-to-audio.module';
6
+ export { PdfToAudioService } from './pdf-to-audio.service';
7
+ export { PDF_TO_AUDIO_QUEUE } from './pdf-to-audio-queue.worker';
8
+ export { extractText } from './pdf-extractor';
9
+ export type { PdfToAudioOptions } from './types';
10
+ export { PDF_TO_AUDIO_OUTPUT_DIR } from './pdf-to-audio.module';
11
+ export type { PdfToAudioModuleOptions } from './pdf-to-audio.module';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * @hazeljs/pdf-to-audio
4
+ * Convert PDF documents to audio using TTS
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PDF_TO_AUDIO_OUTPUT_DIR = exports.extractText = exports.PDF_TO_AUDIO_QUEUE = exports.PdfToAudioService = exports.PdfToAudioModule = void 0;
8
+ var pdf_to_audio_module_1 = require("./pdf-to-audio.module");
9
+ Object.defineProperty(exports, "PdfToAudioModule", { enumerable: true, get: function () { return pdf_to_audio_module_1.PdfToAudioModule; } });
10
+ var pdf_to_audio_service_1 = require("./pdf-to-audio.service");
11
+ Object.defineProperty(exports, "PdfToAudioService", { enumerable: true, get: function () { return pdf_to_audio_service_1.PdfToAudioService; } });
12
+ var pdf_to_audio_queue_worker_1 = require("./pdf-to-audio-queue.worker");
13
+ Object.defineProperty(exports, "PDF_TO_AUDIO_QUEUE", { enumerable: true, get: function () { return pdf_to_audio_queue_worker_1.PDF_TO_AUDIO_QUEUE; } });
14
+ var pdf_extractor_1 = require("./pdf-extractor");
15
+ Object.defineProperty(exports, "extractText", { enumerable: true, get: function () { return pdf_extractor_1.extractText; } });
16
+ var pdf_to_audio_module_2 = require("./pdf-to-audio.module");
17
+ Object.defineProperty(exports, "PDF_TO_AUDIO_OUTPUT_DIR", { enumerable: true, get: function () { return pdf_to_audio_module_2.PDF_TO_AUDIO_OUTPUT_DIR; } });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6DAAyD;AAAhD,uHAAA,gBAAgB,OAAA;AACzB,+DAA2D;AAAlD,yHAAA,iBAAiB,OAAA;AAC1B,yEAAiE;AAAxD,+HAAA,kBAAkB,OAAA;AAC3B,iDAA8C;AAArC,4GAAA,WAAW,OAAA;AAEpB,6DAAgE;AAAvD,8HAAA,uBAAuB,OAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const pdfToAudio = __importStar(require("./index"));
37
+ describe('@hazeljs/pdf-to-audio exports', () => {
38
+ it('should export PdfToAudioModule', () => {
39
+ expect(pdfToAudio.PdfToAudioModule).toBeDefined();
40
+ });
41
+ it('should export PdfToAudioService', () => {
42
+ expect(pdfToAudio.PdfToAudioService).toBeDefined();
43
+ });
44
+ it('should export extractText', () => {
45
+ expect(pdfToAudio.extractText).toBeDefined();
46
+ expect(typeof pdfToAudio.extractText).toBe('function');
47
+ });
48
+ });
49
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AAEtC,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7C,MAAM,CAAC,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Extract raw text from a PDF buffer
3
+ */
4
+ export declare function extractText(buffer: Buffer): Promise<string>;
5
+ //# sourceMappingURL=pdf-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-extractor.d.ts","sourceRoot":"","sources":["../src/pdf-extractor.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGjE"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractText = extractText;
7
+ const pdf_parse_1 = __importDefault(require("pdf-parse"));
8
+ /**
9
+ * Extract raw text from a PDF buffer
10
+ */
11
+ async function extractText(buffer) {
12
+ const data = await (0, pdf_parse_1.default)(buffer);
13
+ return data.text;
14
+ }
15
+ //# sourceMappingURL=pdf-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-extractor.js","sourceRoot":"","sources":["../src/pdf-extractor.ts"],"names":[],"mappings":";;;;;AAKA,kCAGC;AARD,0DAA4B;AAE5B;;GAEG;AACI,KAAK,UAAU,WAAW,CAAC,MAAc;IAC9C,MAAM,IAAI,GAAG,MAAM,IAAA,mBAAG,EAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pdf-extractor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-extractor.test.d.ts","sourceRoot":"","sources":["../src/pdf-extractor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const pdf_extractor_1 = require("./pdf-extractor");
4
+ const mockPdf = jest.fn();
5
+ jest.mock('pdf-parse', () => ({
6
+ __esModule: true,
7
+ default: (buffer) => mockPdf(buffer),
8
+ }));
9
+ describe('extractText', () => {
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+ it('should extract text from PDF buffer', async () => {
14
+ const buffer = Buffer.from('fake-pdf-content');
15
+ mockPdf.mockResolvedValue({ text: 'Extracted document text' });
16
+ const result = await (0, pdf_extractor_1.extractText)(buffer);
17
+ expect(mockPdf).toHaveBeenCalledWith(buffer);
18
+ expect(result).toBe('Extracted document text');
19
+ });
20
+ it('should return empty string when PDF has no text', async () => {
21
+ mockPdf.mockResolvedValue({ text: '' });
22
+ const result = await (0, pdf_extractor_1.extractText)(Buffer.from('empty-pdf'));
23
+ expect(result).toBe('');
24
+ });
25
+ it('should propagate pdf-parse errors', async () => {
26
+ mockPdf.mockRejectedValue(new Error('Invalid PDF'));
27
+ await expect((0, pdf_extractor_1.extractText)(Buffer.from('invalid'))).rejects.toThrow('Invalid PDF');
28
+ });
29
+ it('should handle PDF with metadata', async () => {
30
+ mockPdf.mockResolvedValue({
31
+ text: 'Document content',
32
+ numpages: 5,
33
+ info: {},
34
+ metadata: {},
35
+ });
36
+ const result = await (0, pdf_extractor_1.extractText)(Buffer.from('pdf'));
37
+ expect(result).toBe('Document content');
38
+ });
39
+ });
40
+ //# sourceMappingURL=pdf-extractor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-extractor.test.js","sourceRoot":"","sources":["../src/pdf-extractor.test.ts"],"names":[],"mappings":";;AAAA,mDAA8C;AAE9C,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;AAE1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CAC7C,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC/C,OAAO,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAE/D,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAW,EAAC,MAAM,CAAC,CAAC;QAEzC,MAAM,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,OAAO,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAExC,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAW,EAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QAE3D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,OAAO,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;QAEpD,MAAM,MAAM,CAAC,IAAA,2BAAW,EAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,OAAO,CAAC,iBAAiB,CAAC;YACxB,IAAI,EAAE,kBAAkB;YACxB,QAAQ,EAAE,CAAC;YACX,IAAI,EAAE,EAAE;YACR,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAW,EAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAErD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { QueueService } from '@hazeljs/queue';
2
+ import type { PdfToAudioOptions } from './types';
3
+ export declare const PDF_TO_AUDIO_QUEUE = "pdf-to-audio";
4
+ export interface PdfToAudioJobData {
5
+ pdfBase64: string;
6
+ options: PdfToAudioOptions;
7
+ }
8
+ export declare class PdfToAudioQueueWorker {
9
+ private readonly queueService;
10
+ private readonly outputDir;
11
+ private worker;
12
+ constructor(queueService: QueueService, outputDir: string);
13
+ private start;
14
+ stop(): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=pdf-to-audio-queue.worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-to-audio-queue.worker.d.ts","sourceRoot":"","sources":["../src/pdf-to-audio-queue.worker.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,qBACa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,YAAY;IACI,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ7D,OAAO,CAAC,MAAM,CAAkD;gBAG7C,YAAY,EAAE,YAAY,EACO,SAAS,EAAE,MAAM;IAKrE,OAAO,CAAC,KAAK;IAwEP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAO5B"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.PdfToAudioQueueWorker = exports.PDF_TO_AUDIO_QUEUE = void 0;
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const core_1 = require("@hazeljs/core");
22
+ const bullmq_1 = require("bullmq");
23
+ const queue_1 = require("@hazeljs/queue");
24
+ const pdf_to_audio_service_1 = require("./pdf-to-audio.service");
25
+ const pdf_to_audio_module_1 = require("./pdf-to-audio.module");
26
+ exports.PDF_TO_AUDIO_QUEUE = 'pdf-to-audio';
27
+ let PdfToAudioQueueWorker = class PdfToAudioQueueWorker {
28
+ constructor(queueService, outputDir) {
29
+ this.queueService = queueService;
30
+ this.outputDir = outputDir;
31
+ this.worker = null;
32
+ this.start();
33
+ }
34
+ start() {
35
+ const connection = this.queueService.getConnection();
36
+ if (!connection) {
37
+ core_1.logger.warn('PdfToAudioQueueWorker: QueueService not configured (no Redis connection). PDF-to-audio jobs will not be processed.');
38
+ return;
39
+ }
40
+ // Ensure queue exists
41
+ this.queueService.getQueue(exports.PDF_TO_AUDIO_QUEUE);
42
+ this.worker = new bullmq_1.Worker(exports.PDF_TO_AUDIO_QUEUE, async (job) => {
43
+ core_1.logger.info(`PdfToAudio job ${job.id} started`);
44
+ const container = core_1.Container.getInstance();
45
+ const service = container.resolve(pdf_to_audio_service_1.PdfToAudioService);
46
+ if (!service) {
47
+ throw new Error('PdfToAudioService not found in container');
48
+ }
49
+ const { pdfBase64, options } = job.data;
50
+ const pdfBuffer = Buffer.from(pdfBase64, 'base64');
51
+ core_1.logger.info(`PdfToAudio job ${job.id}: PDF decoded (${(pdfBuffer.length / 1024).toFixed(1)} KB), starting conversion`);
52
+ const audioBuffer = await service.convert(pdfBuffer, options, (completed, total, phase) => {
53
+ void job.updateProgress({ completed, total, phase });
54
+ if (phase) {
55
+ core_1.logger.info(`PdfToAudio job ${job.id}: ${phase}`);
56
+ }
57
+ else {
58
+ const pct = total > 0 ? Math.round((completed / total) * 100) : 0;
59
+ const everyN = total > 20 ? 5 : total > 10 ? 3 : 1;
60
+ const shouldLog = completed === total || completed <= 3 || completed % everyN === 0;
61
+ if (shouldLog) {
62
+ core_1.logger.info(`PdfToAudio job ${job.id}: ${completed}/${total} chunks (${pct}%)`);
63
+ }
64
+ }
65
+ });
66
+ const jobId = String(job.id);
67
+ const defaultDir = path_1.default.resolve(process.cwd(), 'data/pdf-to-audio');
68
+ const rawDir = this.outputDir != null && this.outputDir !== '' ? String(this.outputDir).trim() : '';
69
+ const outputDir = rawDir || defaultDir;
70
+ const outPath = path_1.default.join(outputDir, `${jobId}.mp3`);
71
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
72
+ fs_1.default.writeFileSync(outPath, audioBuffer);
73
+ core_1.logger.info(`PdfToAudio job ${job.id} completed, saved to ${outPath}`);
74
+ return outPath;
75
+ }, {
76
+ connection: connection,
77
+ concurrency: 1,
78
+ lockDuration: 30 * 60 * 1000, // 30 min - job can take many minutes for large PDFs
79
+ lockRenewTime: 30 * 1000, // renew lock every 30 sec so job doesn't stall
80
+ });
81
+ this.worker.on('active', (job) => {
82
+ core_1.logger.info(`PdfToAudio job ${job.id} picked up by worker`);
83
+ });
84
+ this.worker.on('failed', (job, err) => {
85
+ core_1.logger.error(`PdfToAudio job ${job?.id} failed:`, err);
86
+ });
87
+ core_1.logger.info('PdfToAudioQueueWorker started');
88
+ }
89
+ async stop() {
90
+ if (this.worker) {
91
+ await this.worker.close();
92
+ this.worker = null;
93
+ core_1.logger.info('PdfToAudioQueueWorker stopped');
94
+ }
95
+ }
96
+ };
97
+ exports.PdfToAudioQueueWorker = PdfToAudioQueueWorker;
98
+ exports.PdfToAudioQueueWorker = PdfToAudioQueueWorker = __decorate([
99
+ (0, core_1.Injectable)(),
100
+ __param(1, (0, core_1.Inject)(pdf_to_audio_module_1.PDF_TO_AUDIO_OUTPUT_DIR)),
101
+ __metadata("design:paramtypes", [queue_1.QueueService, String])
102
+ ], PdfToAudioQueueWorker);
103
+ //# sourceMappingURL=pdf-to-audio-queue.worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-to-audio-queue.worker.js","sourceRoot":"","sources":["../src/pdf-to-audio-queue.worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pdf-to-audio-queue.worker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-to-audio-queue.worker.test.d.ts","sourceRoot":"","sources":["../src/pdf-to-audio-queue.worker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const pdf_to_audio_queue_worker_1 = require("./pdf-to-audio-queue.worker");
4
+ describe('PdfToAudioQueueWorker', () => {
5
+ it('should not create worker when QueueService has no connection', () => {
6
+ const mockQueueService = {
7
+ getConnection: jest.fn().mockReturnValue(null),
8
+ getQueue: jest.fn(),
9
+ };
10
+ const worker = new pdf_to_audio_queue_worker_1.PdfToAudioQueueWorker(mockQueueService, '/tmp/pdf-to-audio');
11
+ expect(mockQueueService.getConnection).toHaveBeenCalled();
12
+ expect(mockQueueService.getQueue).not.toHaveBeenCalled();
13
+ expect(worker).toBeDefined();
14
+ });
15
+ it('should call stop without error when worker was never started', async () => {
16
+ const mockQueueService = {
17
+ getConnection: jest.fn().mockReturnValue(null),
18
+ getQueue: jest.fn(),
19
+ };
20
+ const worker = new pdf_to_audio_queue_worker_1.PdfToAudioQueueWorker(mockQueueService, '/tmp/pdf-to-audio');
21
+ await expect(worker.stop()).resolves.not.toThrow();
22
+ });
23
+ });
24
+ //# sourceMappingURL=pdf-to-audio-queue.worker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-to-audio-queue.worker.test.js","sourceRoot":"","sources":["../src/pdf-to-audio-queue.worker.test.ts"],"names":[],"mappings":";;AAAA,2EAAoE;AAEpE,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,gBAAgB,GAAG;YACvB,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;YAC9C,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE;SACpB,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,iDAAqB,CAAC,gBAAyB,EAAE,mBAAmB,CAAC,CAAC;QACzF,MAAM,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAC1D,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,gBAAgB,GAAG;YACvB,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;YAC9C,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE;SACpB,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,iDAAqB,CAAC,gBAAyB,EAAE,mBAAmB,CAAC,CAAC;QACzF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Request, HazelResponse } from '@hazeljs/core';
2
+ import { QueueService } from '@hazeljs/queue';
3
+ export declare class PdfToAudioController {
4
+ private readonly queueService;
5
+ constructor(queueService: QueueService);
6
+ convert(req: Request, res: HazelResponse): Promise<void>;
7
+ status(jobId: string, res: HazelResponse): Promise<void>;
8
+ download(jobId: string, res: HazelResponse): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=pdf-to-audio.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-to-audio.controller.d.ts","sourceRoot":"","sources":["../src/pdf-to-audio.controller.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8B9C,qBACa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAAZ,YAAY,EAAE,YAAY;IAGjD,OAAO,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCtE,MAAM,CAAiB,KAAK,EAAE,MAAM,EAAS,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAsD/E,QAAQ,CAAiB,KAAK,EAAE,MAAM,EAAS,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BxF"}