@jrmc/adonis-attachment 4.0.1 → 5.0.0-beta.1

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 (79) hide show
  1. package/README.md +2 -2
  2. package/build/index.d.ts +1 -0
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +1 -0
  5. package/build/providers/attachment_provider.d.ts +7 -0
  6. package/build/providers/attachment_provider.d.ts.map +1 -1
  7. package/build/providers/attachment_provider.js +7 -0
  8. package/build/src/adapters/blurhash.d.ts +6 -0
  9. package/build/src/adapters/blurhash.d.ts.map +1 -1
  10. package/build/src/adapters/blurhash.js +6 -0
  11. package/build/src/adapters/exif.d.ts +1 -1
  12. package/build/src/adapters/exif.d.ts.map +1 -1
  13. package/build/src/adapters/exif.js +41 -11
  14. package/build/src/adapters/ffmpeg.d.ts +19 -0
  15. package/build/src/adapters/ffmpeg.d.ts.map +1 -0
  16. package/build/src/adapters/ffmpeg.js +115 -0
  17. package/build/src/adapters/meta.d.ts +6 -0
  18. package/build/src/adapters/meta.d.ts.map +1 -1
  19. package/build/src/adapters/meta.js +6 -0
  20. package/build/src/adapters/poppler.d.ts +20 -0
  21. package/build/src/adapters/poppler.d.ts.map +1 -0
  22. package/build/src/adapters/poppler.js +116 -0
  23. package/build/src/adapters/soffice.d.ts +14 -0
  24. package/build/src/adapters/soffice.d.ts.map +1 -0
  25. package/build/src/adapters/soffice.js +64 -0
  26. package/build/src/attachment_manager.d.ts +1 -0
  27. package/build/src/attachment_manager.d.ts.map +1 -1
  28. package/build/src/attachment_manager.js +3 -0
  29. package/build/src/attachments/attachment.d.ts.map +1 -1
  30. package/build/src/attachments/attachment.js +1 -0
  31. package/build/src/attachments/attachment_base.d.ts +2 -0
  32. package/build/src/attachments/attachment_base.d.ts.map +1 -1
  33. package/build/src/attachments/attachment_base.js +9 -0
  34. package/build/src/controllers/attachments_controller.d.ts +5 -0
  35. package/build/src/controllers/attachments_controller.d.ts.map +1 -0
  36. package/build/src/controllers/attachments_controller.js +87 -0
  37. package/build/src/converter_manager.d.ts +7 -1
  38. package/build/src/converter_manager.d.ts.map +1 -1
  39. package/build/src/converter_manager.js +7 -5
  40. package/build/src/converters/autodetect_converter.d.ts.map +1 -1
  41. package/build/src/converters/autodetect_converter.js +47 -2
  42. package/build/src/converters/document_thumbnail_converter.d.ts +1 -1
  43. package/build/src/converters/document_thumbnail_converter.d.ts.map +1 -1
  44. package/build/src/converters/document_thumbnail_converter.js +15 -33
  45. package/build/src/converters/pdf_thumbnail_converter.d.ts +1 -1
  46. package/build/src/converters/pdf_thumbnail_converter.d.ts.map +1 -1
  47. package/build/src/converters/pdf_thumbnail_converter.js +17 -24
  48. package/build/src/converters/video_thumbnail_converter.d.ts +1 -1
  49. package/build/src/converters/video_thumbnail_converter.d.ts.map +1 -1
  50. package/build/src/converters/video_thumbnail_converter.js +11 -26
  51. package/build/src/decorators/attachment.d.ts.map +1 -1
  52. package/build/src/define_config.d.ts +1 -0
  53. package/build/src/define_config.d.ts.map +1 -1
  54. package/build/src/define_config.js +6 -1
  55. package/build/src/services/record_with_attachment.d.ts +1 -0
  56. package/build/src/services/record_with_attachment.d.ts.map +1 -1
  57. package/build/src/services/record_with_attachment.js +25 -0
  58. package/build/src/types/attachment.d.ts +2 -0
  59. package/build/src/types/attachment.d.ts.map +1 -1
  60. package/build/src/types/config.d.ts +7 -4
  61. package/build/src/types/config.d.ts.map +1 -1
  62. package/build/src/types/converter.d.ts +2 -0
  63. package/build/src/types/converter.d.ts.map +1 -1
  64. package/build/src/types/index.d.ts +1 -0
  65. package/build/src/types/index.d.ts.map +1 -1
  66. package/build/src/types/index.js +1 -0
  67. package/build/src/types/input.d.ts +5 -0
  68. package/build/src/types/input.d.ts.map +1 -1
  69. package/build/src/types/metadata.d.ts +18 -0
  70. package/build/src/types/metadata.d.ts.map +1 -0
  71. package/build/src/types/metadata.js +1 -0
  72. package/build/src/utils/default_values.js +1 -1
  73. package/build/src/utils/helpers.d.ts +1 -0
  74. package/build/src/utils/helpers.d.ts.map +1 -1
  75. package/build/src/utils/helpers.js +6 -0
  76. package/build/src/utils/hooks.d.ts.map +1 -1
  77. package/build/src/utils/hooks.js +1 -0
  78. package/build/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +11 -3
package/README.md CHANGED
@@ -8,8 +8,6 @@ This package is currently development and will replace [attachment-advanced](htt
8
8
 
9
9
  [ChangeLog](https://adonis-attachment.jrmc.dev/changelog.html)
10
10
 
11
- [Discord](https://discord.gg/89eMn2vB)
12
-
13
11
  Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-kit)
14
12
 
15
13
  ## Roadmap
@@ -19,6 +17,7 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
19
17
  - [x] attachment file by path
20
18
  - [x] attachment file by url
21
19
  - [x] attachment file by stream
20
+ - [x] attachment file by Base64
22
21
  - [x] attachment files
23
22
  - [x] save meta data
24
23
  - [x] variantes
@@ -31,6 +30,7 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
31
30
  - [x] adonis-drive/flydrive
32
31
  - [x] jobs queue
33
32
  - [x] serialize
33
+ - [x] attachments route
34
34
 
35
35
 
36
36
  ## Setup
package/build/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import attachmentManager from './services/main.js';
2
2
  import RegenerateService from './services/regenerate_service.js';
3
3
  export { configure } from './configure.js';
4
+ export { ConverterManager } from './src/converter_manager.js';
4
5
  export { Attachment } from './src/attachments/attachment.js';
5
6
  export { attachment } from './src/decorators/attachment.js';
6
7
  export { attachments } from './src/decorators/attachment.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,oBAAoB,CAAA;AAClD,OAAO,iBAAiB,MAAM,kCAAkC,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,oBAAoB,CAAA;AAClD,OAAO,iBAAiB,MAAM,kCAAkC,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
package/build/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import attachmentManager from './services/main.js';
2
2
  import RegenerateService from './services/regenerate_service.js';
3
3
  export { configure } from './configure.js';
4
+ export { ConverterManager } from './src/converter_manager.js';
4
5
  export { Attachment } from './src/attachments/attachment.js';
5
6
  export { attachment } from './src/decorators/attachment.js';
6
7
  export { attachments } from './src/decorators/attachment.js';
@@ -5,16 +5,23 @@
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
7
  import type { ApplicationService } from '@adonisjs/core/types';
8
+ import type { Route } from '@adonisjs/core/http';
8
9
  import type { AttachmentService } from '../src/types/config.js';
9
10
  declare module '@adonisjs/core/types' {
10
11
  interface ContainerBindings {
11
12
  'jrmc.attachment': AttachmentService;
12
13
  }
13
14
  }
15
+ declare module '@adonisjs/core/http' {
16
+ interface Router {
17
+ attachments: (pattern?: string) => Route;
18
+ }
19
+ }
14
20
  export default class AttachmentProvider {
15
21
  #private;
16
22
  protected app: ApplicationService;
17
23
  constructor(app: ApplicationService);
18
24
  register(): void;
25
+ boot(): Promise<void>;
19
26
  }
20
27
  //# sourceMappingURL=attachment_provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment_provider.d.ts","sourceRoot":"","sources":["../../providers/attachment_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAI9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAiB,iBAAiB;QAChC,iBAAiB,EAAE,iBAAiB,CAAA;KACrC;CACF;AAED,MAAM,CAAC,OAAO,OAAO,kBAAkB;;IAGzB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;CAmBT"}
1
+ {"version":3,"file":"attachment_provider.d.ts","sourceRoot":"","sources":["../../providers/attachment_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAIhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAiB,iBAAiB;QAChC,iBAAiB,EAAE,iBAAiB,CAAA;KACrC;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,KAAK,CAAA;KACzC;CACF;AAED,MAAM,CAAC,OAAO,OAAO,kBAAkB;;IAGzB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;IAoBF,IAAI;CAQX"}
@@ -25,4 +25,11 @@ export default class AttachmentProvider {
25
25
  return this.#manager;
26
26
  });
27
27
  }
28
+ async boot() {
29
+ const router = await this.app.container.make('router');
30
+ const AttachmentsController = () => import('@jrmc/adonis-attachment/controllers/attachments_controller');
31
+ router.attachments = (pattern = '/attachments/:key/:name?') => {
32
+ return router.get(pattern, [AttachmentsController]).as('attachments');
33
+ };
34
+ }
28
35
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
1
7
  declare const _default: {
2
8
  encode(pixels: Uint8ClampedArray, width: number, height: number, componentX: number, componentY: number): Promise<string>;
3
9
  };
@@ -1 +1 @@
1
- {"version":3,"file":"blurhash.d.ts","sourceRoot":"","sources":["../../../src/adapters/blurhash.ts"],"names":[],"mappings":";mBAIY,iBAAiB,SAClB,MAAM,UACL,MAAM,cACF,MAAM,cACN,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;;AAPpB,wBAUC"}
1
+ {"version":3,"file":"blurhash.d.ts","sourceRoot":"","sources":["../../../src/adapters/blurhash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;mBAMS,iBAAiB,SAClB,MAAM,UACL,MAAM,cACF,MAAM,cACN,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;;AAPpB,wBAUC"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
1
7
  import { encode } from 'blurhash';
2
8
  export default {
3
9
  async encode(pixels, width, height, componentX, componentY) {
@@ -5,8 +5,8 @@
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
7
  import type { Exif, Input } from '../types/input.js';
8
+ import type { Converter } from '../types/converter.js';
8
9
  import { ResolvedAttachmentConfig } from '../define_config.js';
9
- import { Converter } from '../types/converter.js';
10
10
  type KnownConverters = Record<string, Converter>;
11
11
  declare const _default: {
12
12
  exif(input: Input, config: ResolvedAttachmentConfig<KnownConverters>): Promise<Exif | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"exif.d.ts","sourceRoot":"","sources":["../../../src/adapters/exif.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAOpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;;gBAIrC,KAAK,UACJ,wBAAwB,CAAC,eAAe,CAAC,GAChD,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;;AAJ9B,wBAOC"}
1
+ {"version":3,"file":"exif.d.ts","sourceRoot":"","sources":["../../../src/adapters/exif.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAE9D,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;;gBAIrC,KAAK,UACJ,wBAAwB,CAAC,eAAe,CAAC,GAChD,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;;AAJ9B,wBAOC"}
@@ -6,9 +6,8 @@
6
6
  */
7
7
  import fs from 'node:fs/promises';
8
8
  import ExifReader from 'exifreader';
9
- import logger from '@adonisjs/core/services/logger';
10
9
  import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
11
- import { bufferToTempFile, cleanObject, use } from '../utils/helpers.js';
10
+ import { bufferToTempFile, cleanObject } from '../utils/helpers.js';
12
11
  export default {
13
12
  async exif(input, config) {
14
13
  return exif(input, config);
@@ -32,6 +31,9 @@ const exif = async (input, config) => {
32
31
  if (fileType?.mime.includes('video')) {
33
32
  return videoExif(input, config);
34
33
  }
34
+ if (fileType?.mime.includes('pdf')) {
35
+ return pdfExif(input, config);
36
+ }
35
37
  if (buffer && fileType?.mime.includes('image')) {
36
38
  return imageExif(buffer);
37
39
  }
@@ -108,28 +110,56 @@ async function imageExif(buffer) {
108
110
  return cleanObject(data);
109
111
  }
110
112
  async function videoExif(input, config) {
113
+ const { default: FFmpeg } = await import('./ffmpeg.js');
111
114
  return new Promise(async (resolve) => {
112
- const ffmpeg = await use('fluent-ffmpeg');
113
115
  let file = input;
114
116
  if (Buffer.isBuffer(input)) {
115
117
  file = await bufferToTempFile(input);
116
118
  }
117
- const ff = ffmpeg(file);
119
+ const ffmpeg = new FFmpeg(file);
118
120
  if (config.bin) {
119
121
  if (config.bin.ffprobePath) {
120
- ff.setFfprobePath(config.bin.ffprobePath);
122
+ ffmpeg.setFfprobePath(config.bin.ffprobePath);
121
123
  }
122
124
  }
123
- ff.ffprobe(0, (err, data) => {
124
- if (err) {
125
- logger.error({ err });
125
+ const info = await ffmpeg.exif();
126
+ if (info.width && info.height && info.duration) {
127
+ resolve({
128
+ dimension: {
129
+ width: info.width,
130
+ height: info.height,
131
+ },
132
+ duration: info.duration,
133
+ videoCodec: info?.videoCodec,
134
+ audioCodec: info?.audioCodec,
135
+ });
136
+ }
137
+ });
138
+ }
139
+ async function pdfExif(input, config) {
140
+ const { default: Poppler } = await import('./poppler.js');
141
+ return new Promise(async (resolve) => {
142
+ let file = input;
143
+ if (Buffer.isBuffer(input)) {
144
+ file = await bufferToTempFile(input);
145
+ }
146
+ const poppler = new Poppler(file);
147
+ if (config.bin) {
148
+ if (config.bin.pdfinfoPath) {
149
+ poppler.setPdfInfoPath(config.bin.pdfinfoPath);
126
150
  }
151
+ }
152
+ const info = await poppler.pdfInfo();
153
+ if (info.width && info.height && info.pages) {
127
154
  resolve({
128
155
  dimension: {
129
- width: data.streams[0].width,
130
- height: data.streams[0].height,
156
+ width: info.width,
157
+ height: info.height,
131
158
  },
159
+ version: info?.version,
160
+ pages: info?.pages,
161
+ date: info?.creationDate,
132
162
  });
133
- });
163
+ }
134
164
  });
135
165
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import { FfmpegMetadata } from '../types/metadata.js';
8
+ export default class FFmpeg {
9
+ #private;
10
+ private input;
11
+ constructor(input: string);
12
+ screenshots(options: {
13
+ time: number;
14
+ }): Promise<string | undefined>;
15
+ exif(): Promise<FfmpegMetadata>;
16
+ setFfmpegPath(ffmpegPath: string): Promise<void>;
17
+ setFfprobePath(ffprobePath: string): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=ffmpeg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../../src/adapters/ffmpeg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,CAAC,OAAO,OAAO,MAAM;;IAMb,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAuB3B,WAAW,CAAC,OAAO,EAAE;QACzB,IAAI,EAAE,MAAM,CAAA;KACb;IA2CK,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC;IA8B/B,aAAa,CAAC,UAAU,EAAE,MAAM;IAIhC,cAAc,CAAC,WAAW,EAAE,MAAM;CAGzC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { $, ExecaError } from 'execa';
10
+ import { cuid } from '@adonisjs/core/helpers';
11
+ import logger from '@adonisjs/core/services/logger';
12
+ import { attachmentManager } from '@jrmc/adonis-attachment';
13
+ import { secondsToTimeFormat } from '../utils/helpers.js';
14
+ export default class FFmpeg {
15
+ input;
16
+ #ffmpegPath;
17
+ #ffprobePath;
18
+ #timeout;
19
+ #TIMEOUT;
20
+ constructor(input) {
21
+ this.input = input;
22
+ this.#ffmpegPath = 'ffmpeg';
23
+ this.#ffprobePath = 'ffprobe';
24
+ this.#timeout = null;
25
+ this.#TIMEOUT = attachmentManager.getConfig().timeout || 30_000;
26
+ }
27
+ #createAbortController() {
28
+ this.#cleanup();
29
+ const controller = new AbortController();
30
+ this.#timeout = setTimeout(() => {
31
+ controller.abort();
32
+ }, this.#TIMEOUT);
33
+ return controller;
34
+ }
35
+ #cleanup() {
36
+ if (this.#timeout) {
37
+ clearTimeout(this.#timeout);
38
+ }
39
+ }
40
+ async screenshots(options) {
41
+ const folder = os.tmpdir();
42
+ const filename = `${cuid()}.jpg`;
43
+ const { time } = options;
44
+ const output = path.join(folder, filename);
45
+ const timestamp = secondsToTimeFormat(time);
46
+ try {
47
+ const { stderr } = await $({
48
+ cancelSignal: this.#createAbortController().signal,
49
+ gracefulCancel: true,
50
+ timeout: this.#TIMEOUT
51
+ }) `${this.#ffmpegPath} -y -i ${this.input} -ss ${timestamp} -vframes 1 -q:v 2 ${output}`;
52
+ if (stderr.includes('Output file is empty, nothing was encoded')) {
53
+ const durationMatch = stderr.match(/Duration: (\d{2}:\d{2}:\d{2}\.\d{2})/);
54
+ if (durationMatch) {
55
+ const videoDuration = durationMatch[1];
56
+ logger.error(`Video is not long enough. Duration: ${videoDuration}, Requested timestamp: ${timestamp}`);
57
+ throw new Error(`Video is not long enough. Duration: ${videoDuration}, Requested timestamp: ${timestamp}`);
58
+ }
59
+ }
60
+ return output;
61
+ }
62
+ catch (error) {
63
+ if (error instanceof ExecaError) {
64
+ if (error.failed) {
65
+ const stderr = error.stderr;
66
+ if (stderr) {
67
+ logger.error(stderr.split('\n').pop());
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+ else {
73
+ logger.error(error);
74
+ throw error;
75
+ }
76
+ }
77
+ finally {
78
+ this.#cleanup();
79
+ }
80
+ }
81
+ async exif() {
82
+ try {
83
+ const { stdout } = await $({
84
+ cancelSignal: this.#createAbortController().signal,
85
+ gracefulCancel: true,
86
+ timeout: this.#TIMEOUT
87
+ }) `${this.#ffprobePath} -v quiet -print_format json -show_format -show_streams ${this.input}`;
88
+ const metadata = JSON.parse(stdout);
89
+ const videoStream = metadata.streams.find((stream) => stream.codec_type === 'video');
90
+ const audioStream = metadata.streams.find((stream) => stream.codec_type === 'audio');
91
+ return {
92
+ types: metadata.streams.map((stream) => stream.codec_type),
93
+ width: videoStream?.width,
94
+ height: videoStream?.height,
95
+ videoCodec: videoStream?.codec_name,
96
+ audioCodec: audioStream?.codec_name,
97
+ duration: +metadata.format.duration,
98
+ size: +metadata.format.size
99
+ };
100
+ }
101
+ catch (error) {
102
+ logger.error(error);
103
+ throw error;
104
+ }
105
+ finally {
106
+ this.#cleanup();
107
+ }
108
+ }
109
+ async setFfmpegPath(ffmpegPath) {
110
+ this.#ffmpegPath = ffmpegPath;
111
+ }
112
+ async setFfprobePath(ffprobePath) {
113
+ this.#ffprobePath = ffprobePath;
114
+ }
115
+ }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
1
7
  import { Meta } from '../types/input.js';
2
8
  export declare function metaFormBuffer(input: Buffer): Promise<Meta>;
3
9
  export declare function metaFormFile(input: string, filename: string): Promise<Meta>;
@@ -1 +1 @@
1
- {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/adapters/meta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAuBxC,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjF"}
1
+ {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/adapters/meta.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAuBxC,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjF"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
1
7
  import path from 'node:path';
2
8
  import fs from 'node:fs/promises';
3
9
  import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import { PopplerMetadata } from '../types/metadata.js';
8
+ export default class Poppler {
9
+ #private;
10
+ private input;
11
+ constructor(input: string);
12
+ pdfToPpm(options: {
13
+ page: number;
14
+ dpi: number;
15
+ }): Promise<string>;
16
+ pdfInfo(): Promise<PopplerMetadata>;
17
+ setPdfToPpmPath(pdftoppm: string): Promise<void>;
18
+ setPdfInfoPath(pdfinfo: string): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=poppler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poppler.d.ts","sourceRoot":"","sources":["../../../src/adapters/poppler.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAQH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAGtD,MAAM,CAAC,OAAO,OAAO,OAAO;;IAMd,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAuB3B,QAAQ,CAAC,OAAO,EAAE;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;KACb;IAsBK,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC;IAyDnC,eAAe,CAAC,QAAQ,EAAE,MAAM;IAIhC,cAAc,CAAC,OAAO,EAAE,MAAM;CAGrC"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { $ } from 'execa';
10
+ import { cuid } from '@adonisjs/core/helpers';
11
+ import logger from '@adonisjs/core/services/logger';
12
+ import { attachmentManager } from '@jrmc/adonis-attachment';
13
+ import { DateTime } from 'luxon';
14
+ export default class Poppler {
15
+ input;
16
+ #pdfToPpmPath;
17
+ #pdfInfoPath;
18
+ #timeout;
19
+ #TIMEOUT;
20
+ constructor(input) {
21
+ this.input = input;
22
+ this.#pdfToPpmPath = 'pdftoppm';
23
+ this.#pdfInfoPath = 'pdfinfo';
24
+ this.#timeout = null;
25
+ this.#TIMEOUT = attachmentManager.getConfig().timeout || 30_000;
26
+ }
27
+ #createAbortController() {
28
+ this.#cleanup();
29
+ const controller = new AbortController();
30
+ this.#timeout = setTimeout(() => {
31
+ controller.abort();
32
+ }, this.#TIMEOUT);
33
+ return controller;
34
+ }
35
+ #cleanup() {
36
+ if (this.#timeout) {
37
+ clearTimeout(this.#timeout);
38
+ }
39
+ }
40
+ async pdfToPpm(options) {
41
+ const output = path.join(os.tmpdir(), cuid());
42
+ try {
43
+ await $({
44
+ cancelSignal: this.#createAbortController().signal,
45
+ gracefulCancel: true,
46
+ timeout: this.#TIMEOUT
47
+ }) `${this.#pdfToPpmPath} -f ${options.page.toString()} -l ${options.page.toString()} -r ${options.dpi.toString()} -jpeg ${this.input} ${output}`;
48
+ const pdfInfo = await this.pdfInfo();
49
+ const pageNumberFormat = '0'.repeat(String(pdfInfo.pages).length - String(options.page).length);
50
+ return `${output}-${pageNumberFormat}${options.page}.jpg`;
51
+ }
52
+ catch (error) {
53
+ logger.error('Error while converting PDF to PPM:', error);
54
+ throw error;
55
+ }
56
+ finally {
57
+ this.#cleanup();
58
+ }
59
+ }
60
+ async pdfInfo() {
61
+ try {
62
+ const { stdout } = await $({
63
+ cancelSignal: this.#createAbortController().signal,
64
+ gracefulCancel: true,
65
+ timeout: this.#TIMEOUT
66
+ }) `${this.#pdfInfoPath} ${this.input}`;
67
+ const metadata = {};
68
+ stdout.split('\n').forEach((line) => {
69
+ const colonIndex = line.indexOf(':');
70
+ if (colonIndex > 0) {
71
+ const key = line.substring(0, colonIndex).trim();
72
+ const value = line.substring(colonIndex + 1).trim();
73
+ if (key && value) {
74
+ metadata[key.toLowerCase()] = value;
75
+ }
76
+ }
77
+ });
78
+ const pageSizeMatch = metadata['page size']?.match(/(\d+)\s*x\s*(\d+)/);
79
+ const [width, height] = pageSizeMatch ? [parseInt(pageSizeMatch[1]), parseInt(pageSizeMatch[2])] : [0, 0];
80
+ const fileSizeMatch = metadata['file size']?.match(/(\d+)/);
81
+ const size = fileSizeMatch ? parseInt(fileSizeMatch[1]) : 0;
82
+ const version = metadata['pdf version'] || '';
83
+ const pages = parseInt(metadata['pages'] || '0');
84
+ let creationDate = '';
85
+ if (metadata['creationdate']) {
86
+ const dateStr = metadata['creationdate'];
87
+ const date = DateTime.fromFormat(dateStr, 'EEE MMM dd HH:mm:ss yyyy z', { zone: 'UTC' });
88
+ console.log('Parsed date:', date.toISO());
89
+ if (date.isValid) {
90
+ creationDate = date.toFormat('yyyy-MM-dd\'T\'HH:mm:ss');
91
+ }
92
+ }
93
+ return {
94
+ size,
95
+ version,
96
+ width,
97
+ height,
98
+ pages,
99
+ creationDate
100
+ };
101
+ }
102
+ catch (error) {
103
+ logger.error('Error while retrieving metadata:', error);
104
+ throw error;
105
+ }
106
+ finally {
107
+ this.#cleanup();
108
+ }
109
+ }
110
+ async setPdfToPpmPath(pdftoppm) {
111
+ this.#pdfToPpmPath = pdftoppm;
112
+ }
113
+ async setPdfInfoPath(pdfinfo) {
114
+ this.#pdfInfoPath = pdfinfo;
115
+ }
116
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ export default class Soffice {
8
+ #private;
9
+ private input;
10
+ constructor(input: string);
11
+ convert(): Promise<string>;
12
+ setSofficePath(sofficePath: string): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=soffice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soffice.d.ts","sourceRoot":"","sources":["../../../src/adapters/soffice.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,CAAC,OAAO,OAAO,OAAO;;IAKd,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAsB3B,OAAO;IA4BP,cAAc,CAAC,WAAW,EAAE,MAAM;CAGzC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { $ } from 'execa';
10
+ import logger from '@adonisjs/core/services/logger';
11
+ import { attachmentManager } from '@jrmc/adonis-attachment';
12
+ export default class Soffice {
13
+ input;
14
+ #sofficePath;
15
+ #timeout;
16
+ #TIMEOUT;
17
+ constructor(input) {
18
+ this.input = input;
19
+ this.#sofficePath = 'soffice';
20
+ this.#timeout = null;
21
+ this.#TIMEOUT = attachmentManager.getConfig().timeout || 30_000;
22
+ }
23
+ #createAbortController() {
24
+ this.#cleanup();
25
+ const controller = new AbortController();
26
+ this.#timeout = setTimeout(() => {
27
+ controller.abort();
28
+ }, this.#TIMEOUT);
29
+ return controller;
30
+ }
31
+ #cleanup() {
32
+ if (this.#timeout) {
33
+ clearTimeout(this.#timeout);
34
+ }
35
+ }
36
+ async convert() {
37
+ const output = os.tmpdir();
38
+ try {
39
+ const { stderr } = await $({
40
+ cancelSignal: this.#createAbortController().signal,
41
+ gracefulCancel: true,
42
+ timeout: this.#TIMEOUT
43
+ }) `${this.#sofficePath} --headless --writer --convert-to jpg ${this.input} --outdir ${output}`;
44
+ if (stderr) {
45
+ logger.error('Error while converting Document to Image:', stderr);
46
+ throw stderr;
47
+ }
48
+ const ext = path.extname(this.input);
49
+ const baseName = path.basename(this.input, ext);
50
+ const imagePath = path.join(output, baseName + '.jpg');
51
+ return imagePath;
52
+ }
53
+ catch (error) {
54
+ logger.error('Error while converting Document to Image:', error);
55
+ throw error;
56
+ }
57
+ finally {
58
+ this.#cleanup();
59
+ }
60
+ }
61
+ async setSofficePath(sofficePath) {
62
+ this.#sofficePath = sofficePath;
63
+ }
64
+ }
@@ -35,5 +35,6 @@ export declare class AttachmentManager<KnownConverters extends Record<string, Co
35
35
  preComputeUrl(attachment: AttachmentType): Promise<void>;
36
36
  write(attachment: AttachmentBase): Promise<void>;
37
37
  remove(attachment: AttachmentBase): Promise<void>;
38
+ getConfig(): ResolvedAttachmentConfig<KnownConverters>;
38
39
  }
39
40
  //# sourceMappingURL=attachment_manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment_manager.d.ts","sourceRoot":"","sources":["../../src/attachment_manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,IAAI,cAAc,EAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,SAAS,MAAM,2BAA2B,CAAA;AAQjD,qBAAa,iBAAiB,CAAC,eAAe,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;IAC9E,KAAK,aAAA;gBAIO,MAAM,EAAE,wBAAwB,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,YAAY;IASlF,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBvC,cAAc,CAAC,KAAK,EAAE,aAAa;IAgBnC,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE;;;;;;;;;IAIvC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAgB7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAW7C,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM;IAMvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM;IAM7D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAMpD,UAAU,CACd,UAAU,EAAE,cAAc,GAAG,cAAc,EAC3C,gBAAgB,CAAC,EAAE,gBAAgB;IAY/B,aAAa,CAAC,UAAU,EAAE,cAAc;IAgBxC,KAAK,CAAC,UAAU,EAAE,cAAc;IAgBhC,MAAM,CAAC,UAAU,EAAE,cAAc;CAwCxC"}
1
+ {"version":3,"file":"attachment_manager.d.ts","sourceRoot":"","sources":["../../src/attachment_manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,IAAI,cAAc,EAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,SAAS,MAAM,2BAA2B,CAAA;AAQjD,qBAAa,iBAAiB,CAAC,eAAe,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;IAC9E,KAAK,aAAA;gBAIO,MAAM,EAAE,wBAAwB,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,YAAY;IASlF,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBvC,cAAc,CAAC,KAAK,EAAE,aAAa;IAgBnC,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE;;;;;;;;;IAIvC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAgB7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAW7C,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM;IAMvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM;IAM7D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAMpD,UAAU,CACd,UAAU,EAAE,cAAc,GAAG,cAAc,EAC3C,gBAAgB,CAAC,EAAE,gBAAgB;IAY/B,aAAa,CAAC,UAAU,EAAE,cAAc;IAgBxC,KAAK,CAAC,UAAU,EAAE,cAAc;IAgBhC,MAAM,CAAC,UAAU,EAAE,cAAc;IAuBvC,SAAS;CAqBV"}
@@ -156,6 +156,9 @@ export class AttachmentManager {
156
156
  await attachment.getDisk().delete(attachment.originalPath);
157
157
  }
158
158
  }
159
+ getConfig() {
160
+ return this.#config;
161
+ }
159
162
  // private methods
160
163
  #configureAttachment(attachment) {
161
164
  if (this.#config.meta !== undefined) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../../src/attachments/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,IAAI,mBAAmB,EACjC,YAAY,EACb,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAGjD,qBAAa,UAAW,SAAQ,cAAe,YAAW,mBAAmB;IAC3E,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;gBAER,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,KAAK;IAehF;;OAEG;IAEH,IAAI,IAAI,WAMP;IAED;;OAEG;IAEG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBhE,UAAU,CAAC,WAAW,EAAE,MAAM;IAIxB,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM;IAe3B,YAAY,CAChB,oBAAoB,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAChD,gBAAgB,CAAC,EAAE,gBAAgB;IAyBrC,UAAU,CAAC,OAAO,EAAE,YAAY;IAkBhC;;OAEG;IAEH,QAAQ,IAAI,oBAAoB;IAUhC,MAAM,IAAI,MAAM;CAqCjB"}
1
+ {"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../../src/attachments/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,IAAI,mBAAmB,EACjC,YAAY,EACb,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAGjD,qBAAa,UAAW,SAAQ,cAAe,YAAW,mBAAmB;IAC3E,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;gBAER,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,KAAK;IAehF;;OAEG;IAEH,IAAI,IAAI,WAMP;IAED;;OAEG;IAEG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBhE,UAAU,CAAC,WAAW,EAAE,MAAM;IAIxB,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM;IAe3B,YAAY,CAChB,oBAAoB,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAChD,gBAAgB,CAAC,EAAE,gBAAgB;IAyBrC,UAAU,CAAC,OAAO,EAAE,YAAY;IAkBhC;;OAEG;IAEH,QAAQ,IAAI,oBAAoB;IAUhC,MAAM,IAAI,MAAM;CAsCjB"}