@visulima/storage 1.0.0-alpha.17 → 1.0.0-alpha.19

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 (185) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE.md +3 -0
  3. package/dist/adapter/nuxt/module.d.ts +3 -3
  4. package/dist/adapter/nuxt/module.js +1 -93
  5. package/dist/handler/http/fetch/index.d.ts +6 -6
  6. package/dist/handler/http/fetch/index.js +1 -4
  7. package/dist/handler/http/hono/index.d.ts +3 -3
  8. package/dist/handler/http/hono/index.js +1 -65
  9. package/dist/handler/http/nextjs/index.d.ts +4 -4
  10. package/dist/handler/http/nextjs/index.js +1 -37
  11. package/dist/handler/http/node/index.d.ts +6 -6
  12. package/dist/handler/http/node/index.js +1 -8
  13. package/dist/handler/http/solid-start/index.d.ts +4 -4
  14. package/dist/handler/http/solid-start/index.js +1 -44
  15. package/dist/index.js +1 -13
  16. package/dist/openapi/index.js +1 -5
  17. package/dist/packem_shared/AwsLightFile-BTLiRxXj.js +1 -0
  18. package/dist/packem_shared/AwsLightMetaStorage-DHz1nf_H.js +1 -0
  19. package/dist/packem_shared/AwsLightStorage-CA9cdfJ6.js +1 -0
  20. package/dist/packem_shared/AzureFile-BKHru1zB.js +1 -0
  21. package/dist/packem_shared/AzureSMetaStorage-BqA4jUDJ.js +1 -0
  22. package/dist/packem_shared/AzureStorage-BKUjCymW.js +1 -0
  23. package/dist/packem_shared/BaseTransformer-D68eLyz1.js +1 -0
  24. package/dist/packem_shared/DiskStorage-DNjpDMX9.js +1 -0
  25. package/dist/packem_shared/DiskStorageWithChecksum-Bb6JX_tC.js +1 -0
  26. package/dist/packem_shared/ERRORS-B9jkvKwx.js +1 -0
  27. package/dist/packem_shared/File-alcSNpLq.js +1 -0
  28. package/dist/packem_shared/GCSConfig-BQ0OpVhe.js +1 -0
  29. package/dist/packem_shared/GCSFile-CHjX_fJk.js +1 -0
  30. package/dist/packem_shared/GCSMetaStorage-CpxQ8wM9.js +1 -0
  31. package/dist/packem_shared/GCStorage-DAg1V55T.js +1 -0
  32. package/dist/packem_shared/LocalMetaStorage-BWuFvWn4.js +1 -0
  33. package/dist/packem_shared/MediaTransformer-2C8MJeAg.js +1 -0
  34. package/dist/packem_shared/MetaStorage-Y4m1dWgd.js +1 -0
  35. package/dist/packem_shared/Metadata-B6Ir0KO7.js +1 -0
  36. package/dist/packem_shared/Multipart-Bd9YFFn0.js +1 -0
  37. package/dist/packem_shared/Multipart-D5bcEvga.js +1 -0
  38. package/dist/packem_shared/NetlifyBlobFile-CpzkMKng.js +1 -0
  39. package/dist/packem_shared/NetlifyBlobMetaStorage-dD0oBLom.js +1 -0
  40. package/dist/packem_shared/NetlifyBlobStorage-ByTwCN-h.js +1 -0
  41. package/dist/packem_shared/NoOpMetrics-Dky06sf1.js +1 -0
  42. package/dist/packem_shared/OpenTelemetryMetrics-9uxQMSlg.js +1 -0
  43. package/dist/packem_shared/Rest-CYEBbtCD.js +1 -0
  44. package/dist/packem_shared/Rest-DoonVU7B.js +1 -0
  45. package/dist/packem_shared/S3File-Du6wP0Sz.js +1 -0
  46. package/dist/packem_shared/S3MetaStorage-vcriiosn.js +1 -0
  47. package/dist/packem_shared/S3Storage-Fx6-OSc-.js +1 -0
  48. package/dist/packem_shared/TUS_RESUMABLE-B7KtPVeC.js +1 -0
  49. package/dist/packem_shared/Tus-Bd_2Ki2G.js +1 -0
  50. package/dist/packem_shared/Tus-CUBCkuPE.js +1 -0
  51. package/dist/packem_shared/ValidationError-D8KKJiKA.js +1 -0
  52. package/dist/packem_shared/VercelBlobFile-CL9MjKcY.js +1 -0
  53. package/dist/packem_shared/VercelBlobMetaStorage-BsOTx-Su.js +1 -0
  54. package/dist/packem_shared/VercelBlobStorage-kNkk5Cn4.js +1 -0
  55. package/dist/packem_shared/_commonjsHelpers-D6W6KoPK.js +1 -0
  56. package/dist/packem_shared/aws-light-meta-storage-TPyQwLvk.js +4 -0
  57. package/dist/packem_shared/backblaze-DEflv87m.js +1 -0
  58. package/dist/packem_shared/base-handler-core-DaggCVsq.js +1 -0
  59. package/dist/packem_shared/base-handler-fetch-DnarP64X.js +1 -0
  60. package/dist/packem_shared/base-handler-node-BTes6sql.js +1 -0
  61. package/dist/packem_shared/cache-qV_5Umeq.js +1 -0
  62. package/dist/packem_shared/cloudflare-DLAeQvgD.js +1 -0
  63. package/dist/packem_shared/defaultCloudStorageFileNameValidation-yiM9GxHr.js +1 -0
  64. package/dist/packem_shared/digitalOcean-nhZUds87.js +1 -0
  65. package/dist/packem_shared/disk-storage-BvdK4bqF.js +5 -0
  66. package/dist/packem_shared/gcs-meta-storage-CvGkpzVE.js +1 -0
  67. package/dist/packem_shared/has-content-Bx2p8Okx.js +1 -0
  68. package/dist/packem_shared/headers-DC2MEOrK.js +27 -0
  69. package/dist/packem_shared/is-expired-1JuZjDtb.js +1 -0
  70. package/dist/packem_shared/isRetryableError-DQNSZiBc.js +1 -0
  71. package/dist/packem_shared/isValidMediaType-B-2UKjZx.js +1 -0
  72. package/dist/packem_shared/local-meta-storage-Bkwqi_gi.js +1 -0
  73. package/dist/packem_shared/{media-transformer.d-ChmjTvjc.d.ts → media-transformer.d-CY9CsEOe.d.ts} +1 -1
  74. package/dist/packem_shared/minio-BWwgGXeE.js +1 -0
  75. package/dist/packem_shared/multipart-base-C6OjBwMS.js +1 -0
  76. package/dist/packem_shared/part-match-DqD5U7An.js +1 -0
  77. package/dist/packem_shared/path-D7rMc9nq-B718mbm6.js +1 -0
  78. package/dist/packem_shared/response-builder-Wdo_fvcF.js +1 -0
  79. package/dist/packem_shared/rest-base-B2Dv7esY.js +1 -0
  80. package/dist/packem_shared/restOpenApiSpec-PaIVbESh.js +1 -0
  81. package/dist/packem_shared/s3-base-storage-xHZNtsGe.js +1 -0
  82. package/dist/packem_shared/sharedGet-Ce7WGAsc.js +1 -0
  83. package/dist/packem_shared/storage-m_Cxgiih.js +1 -0
  84. package/dist/packem_shared/tigris-DZoyrMNd.js +1 -0
  85. package/dist/packem_shared/transformOpenApiSpec-C0vEbDTO.js +1 -0
  86. package/dist/packem_shared/{tus-base.d-CpLzyd-S.d.ts → tus-base.d-DNZRXR9X.d.ts} +2 -2
  87. package/dist/packem_shared/tusOpenApiSpec-DF0-hlWV.js +1 -0
  88. package/dist/packem_shared/{types.d-DBjmJvIT.d.ts → types.d-BCN94sB_.d.ts} +1 -1
  89. package/dist/packem_shared/{types.d-DviSTLOg.d.ts → types.d-D4caL2Fp.d.ts} +1 -1
  90. package/dist/packem_shared/update-size-DANV7aa2.js +1 -0
  91. package/dist/packem_shared/validator-DK41xp8N.js +1 -0
  92. package/dist/packem_shared/waitForStorage-ChSBLhLc.js +1 -0
  93. package/dist/packem_shared/wasabi-Dmce4x9s.js +1 -0
  94. package/dist/packem_shared/xhrOpenApiSpec-nyoboz83.js +1 -0
  95. package/dist/storage/aws/clients/index.js +1 -6
  96. package/dist/storage/aws/index.js +1 -3
  97. package/dist/storage/aws-light/index.js +1 -3
  98. package/dist/storage/azure/index.js +1 -3
  99. package/dist/storage/gcs/index.js +1 -4
  100. package/dist/storage/local/index.js +1 -3
  101. package/dist/storage/netlify-blob/index.js +1 -3
  102. package/dist/storage/vercel-blob/index.js +1 -3
  103. package/dist/transformer/audio-transformer.d.ts +1 -1
  104. package/dist/transformer/audio-transformer.js +1 -278
  105. package/dist/transformer/image-transformer.d.ts +1 -1
  106. package/dist/transformer/image-transformer.js +1 -1100
  107. package/dist/transformer/index.d.ts +2 -2
  108. package/dist/transformer/index.js +1 -4
  109. package/dist/transformer/video-transformer.d.ts +1 -1
  110. package/dist/transformer/video-transformer.js +1 -310
  111. package/package.json +18 -18
  112. package/dist/packem_shared/AwsLightFile-C-Nd5rtl.js +0 -11
  113. package/dist/packem_shared/AwsLightMetaStorage-BIDb5EPg.js +0 -4
  114. package/dist/packem_shared/AwsLightStorage-CMuKw4bI.js +0 -133
  115. package/dist/packem_shared/AzureFile-C0kug4w4.js +0 -8
  116. package/dist/packem_shared/AzureSMetaStorage-BEbXX9Uk.js +0 -89
  117. package/dist/packem_shared/AzureStorage-98-LRZ1e.js +0 -359
  118. package/dist/packem_shared/BaseTransformer-9965D4pb.js +0 -83
  119. package/dist/packem_shared/DiskStorage-COMe_G42.js +0 -12
  120. package/dist/packem_shared/DiskStorageWithChecksum-BVaaZiyS.js +0 -249
  121. package/dist/packem_shared/ERRORS-D0apMqnc.js +0 -97
  122. package/dist/packem_shared/File-CPUhj3DI.js +0 -69
  123. package/dist/packem_shared/GCSConfig-vPP22kN6.js +0 -11
  124. package/dist/packem_shared/GCSFile-DaDiCKE2.js +0 -8
  125. package/dist/packem_shared/GCSMetaStorage-BygVH0Ee.js +0 -7
  126. package/dist/packem_shared/GCStorage-4hSx_bN1.js +0 -410
  127. package/dist/packem_shared/LocalMetaStorage-DvnZqf61.js +0 -7
  128. package/dist/packem_shared/MediaTransformer-BRRqYA0E.js +0 -1202
  129. package/dist/packem_shared/MetaStorage-pECeFOad.js +0 -46
  130. package/dist/packem_shared/Metadata-DRLXeJ0F.js +0 -89
  131. package/dist/packem_shared/Multipart-BzzvNxt4.js +0 -128
  132. package/dist/packem_shared/Multipart-DLtFLQ8s.js +0 -163
  133. package/dist/packem_shared/NetlifyBlobFile-CfyJtGWN.js +0 -14
  134. package/dist/packem_shared/NetlifyBlobMetaStorage-eJT6RFy9.js +0 -9
  135. package/dist/packem_shared/NetlifyBlobStorage-nPv7iIAG.js +0 -376
  136. package/dist/packem_shared/NoOpMetrics-DhAk5rXc.js +0 -10
  137. package/dist/packem_shared/OpenTelemetryMetrics-BnxhqIaH.js +0 -67
  138. package/dist/packem_shared/Rest-DEa7IA9R.js +0 -267
  139. package/dist/packem_shared/Rest-qhMbCn5a.js +0 -228
  140. package/dist/packem_shared/S3File-BXCuJaQZ.js +0 -11
  141. package/dist/packem_shared/S3MetaStorage-yT4CznlV.js +0 -77
  142. package/dist/packem_shared/S3Storage-wi5XT-yu.js +0 -317
  143. package/dist/packem_shared/TUS_RESUMABLE-Dtfsffnj.js +0 -434
  144. package/dist/packem_shared/Tus-Ck0Hdle5.js +0 -195
  145. package/dist/packem_shared/Tus-_0vCdHyB.js +0 -200
  146. package/dist/packem_shared/ValidationError-BfF1aE4h.js +0 -26
  147. package/dist/packem_shared/VercelBlobFile-DkFIIb9Q.js +0 -18
  148. package/dist/packem_shared/VercelBlobMetaStorage-DjFmi4vp.js +0 -9
  149. package/dist/packem_shared/VercelBlobStorage-CmMgiDgr.js +0 -261
  150. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  151. package/dist/packem_shared/aws-light-meta-storage-BGZEiY3y.js +0 -447
  152. package/dist/packem_shared/backblaze-BlMnIcBC.js +0 -20
  153. package/dist/packem_shared/base-handler-core-lVB-D59L.js +0 -306
  154. package/dist/packem_shared/base-handler-fetch-CitgN-Aw.js +0 -291
  155. package/dist/packem_shared/base-handler-node-BiO2DF87.js +0 -731
  156. package/dist/packem_shared/cache-B88MXQ_2.js +0 -18
  157. package/dist/packem_shared/cloudflare-Bi1q8wXE.js +0 -21
  158. package/dist/packem_shared/defaultCloudStorageFileNameValidation-Brw40ZEP.js +0 -11
  159. package/dist/packem_shared/digitalOcean-CWQRJM3L.js +0 -21
  160. package/dist/packem_shared/disk-storage-D9RfoKjW.js +0 -832
  161. package/dist/packem_shared/gcs-meta-storage-8FovEDbW.js +0 -159
  162. package/dist/packem_shared/has-content-CY66ehMK.js +0 -3
  163. package/dist/packem_shared/headers-YLRp1dLo.js +0 -1318
  164. package/dist/packem_shared/is-expired-CTThU1q5.js +0 -8
  165. package/dist/packem_shared/isRetryableError-Dycp7127.js +0 -82
  166. package/dist/packem_shared/isValidMediaType-BeDgiObq.js +0 -35
  167. package/dist/packem_shared/local-meta-storage-D-pPc3FI.js +0 -658
  168. package/dist/packem_shared/minio-C2YBZQOw.js +0 -22
  169. package/dist/packem_shared/multipart-base-C994naBi.js +0 -102
  170. package/dist/packem_shared/part-match-Dt738Ddb.js +0 -96
  171. package/dist/packem_shared/path-aV9lMdcn-DMU5H1-h.js +0 -162
  172. package/dist/packem_shared/response-builder-BtnRiBUI.js +0 -45
  173. package/dist/packem_shared/rest-base-k1ory8uB.js +0 -457
  174. package/dist/packem_shared/restOpenApiSpec-BBJ_SiPE.js +0 -800
  175. package/dist/packem_shared/s3-base-storage-zUO8u8Cp.js +0 -510
  176. package/dist/packem_shared/sharedGet-CkSLSnMd.js +0 -1022
  177. package/dist/packem_shared/storage-5XW1E7XU.js +0 -1188
  178. package/dist/packem_shared/tigris-Hac8TKnX.js +0 -21
  179. package/dist/packem_shared/transformOpenApiSpec-BodFhFls.js +0 -975
  180. package/dist/packem_shared/tusOpenApiSpec-DzppyJTW.js +0 -740
  181. package/dist/packem_shared/update-size-CCGm6i1J.js +0 -8
  182. package/dist/packem_shared/validator-DuO17wFF.js +0 -79
  183. package/dist/packem_shared/waitForStorage-Cscw85sx.js +0 -18
  184. package/dist/packem_shared/wasabi-DIyflHSd.js +0 -21
  185. package/dist/packem_shared/xhrOpenApiSpec-CXz7BC5J.js +0 -264
@@ -1,1202 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import mime from 'mime';
3
- import { N as NoOpCache } from './cache-B88MXQ_2.js';
4
- import { isKnownContentType } from './isValidMediaType-BeDgiObq.js';
5
- import ValidationError from './ValidationError-BfF1aE4h.js';
6
-
7
- class MediaTransformer {
8
- /**
9
- * Creates a new MediaTransformer instance.
10
- * @param storage The storage backend for retrieving and storing media files.
11
- * @param config Configuration options for the media transformer including transformer classes and settings.
12
- * @throws Error if no transformer classes are provided in the configuration.
13
- */
14
- constructor(storage, config = {}) {
15
- this.storage = storage;
16
- this.logger = config.logger || this.storage.logger;
17
- this.config = {
18
- audioBitrate: 128e3,
19
- // 128 kbps
20
- audioCodec: "aac",
21
- cache: config.cache ?? new NoOpCache(),
22
- cacheTtl: 3600,
23
- // 1 hour
24
- maxAudioSize: 100 * 1024 * 1024,
25
- // 100MB
26
- maxImageSize: 10 * 1024 * 1024,
27
- // 10MB
28
- maxVideoSize: 500 * 1024 * 1024,
29
- // 500MB
30
- saveTransformedFiles: false,
31
- // Save transformed files to storage
32
- supportedAudioFormats: [],
33
- supportedImageFormats: [],
34
- supportedVideoFormats: [],
35
- videoBitrate: 2e6,
36
- // 2 Mbps
37
- videoCodec: "avc",
38
- ...config
39
- };
40
- if (config.ImageTransformer) {
41
- if (!Array.isArray(this.config.supportedImageFormats) || this.config.supportedImageFormats.length === 0) {
42
- this.config.supportedImageFormats = ["jpeg", "png", "webp", "avif", "tiff", "gif", "svg"];
43
- }
44
- this.imageTransformer = new config.ImageTransformer(this.storage, {
45
- cache: this.config.cache,
46
- cacheTtl: this.config.cacheTtl,
47
- logger: this.logger,
48
- maxImageSize: this.config.maxImageSize,
49
- supportedFormats: this.config.supportedImageFormats
50
- });
51
- }
52
- if (config.VideoTransformer) {
53
- if (!Array.isArray(this.config.supportedVideoFormats) || this.config.supportedVideoFormats.length === 0) {
54
- this.config.supportedVideoFormats = ["mp4", "webm", "mkv", "avi", "mov", "flv", "wmv"];
55
- }
56
- this.videoTransformer = new config.VideoTransformer(this.storage, {
57
- cache: this.config.cache,
58
- cacheTtl: this.config.cacheTtl,
59
- defaultBitrate: this.config.videoBitrate,
60
- defaultCodec: this.config.videoCodec,
61
- logger: this.logger,
62
- maxVideoSize: this.config.maxVideoSize,
63
- supportedFormats: this.config.supportedVideoFormats
64
- });
65
- }
66
- if (config.AudioTransformer) {
67
- if (!Array.isArray(this.config.supportedAudioFormats) || this.config.supportedAudioFormats.length === 0) {
68
- this.config.supportedAudioFormats = ["mp3", "wav", "ogg", "aac", "flac", "m4a", "wma", "aiff"];
69
- }
70
- this.audioTransformer = new config.AudioTransformer(this.storage, {
71
- cache: this.config.cache,
72
- cacheTtl: this.config.cacheTtl,
73
- defaultBitrate: this.config.audioBitrate,
74
- defaultCodec: this.config.audioCodec,
75
- logger: this.logger,
76
- maxAudioSize: this.config.maxAudioSize,
77
- supportedFormats: this.config.supportedAudioFormats
78
- });
79
- }
80
- if (config.AudioTransformer === void 0 && config.ImageTransformer === void 0 && config.VideoTransformer === void 0) {
81
- throw new Error(
82
- "MediaTransformer initialization failed: No transformers are configured. At least one transformer (AudioTransformer, ImageTransformer, or VideoTransformer) must be provided in the configuration to enable media processing functionality."
83
- );
84
- }
85
- }
86
- storage;
87
- imageTransformer;
88
- videoTransformer;
89
- audioTransformer;
90
- config;
91
- logger;
92
- supportedFormats() {
93
- return [...this.config.supportedAudioFormats, ...this.config.supportedImageFormats, ...this.config.supportedVideoFormats];
94
- }
95
- /**
96
- * Handles media transformation based on query parameters.
97
- * @param fileId File identifier.
98
- * @param query Query parameters for transformation.
99
- * @returns Unified transformation result.
100
- */
101
- async handle(fileId, query) {
102
- const parsedQuery = this.parseQuery(query);
103
- const fileQuery = { id: fileId };
104
- const file = await this.storage.get(fileQuery);
105
- const mediaType = this.detectMediaType(file.contentType);
106
- this.validateQueryParameters(parsedQuery, mediaType);
107
- if (this.config.saveTransformedFiles && this.hasTransformations(parsedQuery)) {
108
- const transformedFileId = this.generateTransformedFileId(fileId, parsedQuery, mediaType);
109
- try {
110
- const transformedFile = await this.storage.get({ id: transformedFileId });
111
- this.logger?.debug?.(`Serving cached transformed file: ${transformedFileId}`);
112
- return this.createMediaTransformResult(transformedFile, mediaType, file);
113
- } catch {
114
- this.logger?.debug?.(`Transformed file not found, processing: ${transformedFileId}`);
115
- }
116
- }
117
- let result;
118
- switch (mediaType) {
119
- case "audio": {
120
- if (!this.audioTransformer) {
121
- throw new Error(
122
- "Audio transformer not available. Please install audio transformation dependencies or provide an audio transformer instance."
123
- );
124
- }
125
- result = await this.handleAudioTransformation(fileId, parsedQuery);
126
- break;
127
- }
128
- case "image": {
129
- if (!this.imageTransformer) {
130
- throw new Error(
131
- "Image transformer not available. Please install image transformation dependencies or provide an image transformer instance."
132
- );
133
- }
134
- result = await this.handleImageTransformation(fileId, parsedQuery);
135
- break;
136
- }
137
- case "video": {
138
- if (!this.videoTransformer) {
139
- throw new Error(
140
- "Video transformer not available. Please install video transformation dependencies or provide a video transformer instance."
141
- );
142
- }
143
- result = await this.handleVideoTransformation(fileId, parsedQuery);
144
- break;
145
- }
146
- default: {
147
- throw new Error(`Unsupported media type: ${file.contentType}`);
148
- }
149
- }
150
- if (this.config.saveTransformedFiles && this.hasTransformations(parsedQuery)) {
151
- await this.saveTransformedFile(result, fileId, parsedQuery, mediaType);
152
- }
153
- return result;
154
- }
155
- /**
156
- * Fetches media transformation with URL query string support.
157
- * @param fileId File identifier.
158
- * @param queryString URL query string (e.g., "width=800&height=600&format=webp").
159
- * @returns Unified transformation result.
160
- */
161
- async fetch(fileId, queryString) {
162
- const query = new URLSearchParams(queryString);
163
- return this.handle(fileId, query);
164
- }
165
- /**
166
- * Clears cache for a specific file across all transformers.
167
- * @param fileId Optional file identifier to clear cache for. If omitted, clears all cache.
168
- */
169
- clearCache(fileId) {
170
- this.imageTransformer?.clearCache(fileId);
171
- this.videoTransformer?.clearCache(fileId);
172
- this.audioTransformer?.clearCache(fileId);
173
- }
174
- /**
175
- * Clears all saved transformed files from storage.
176
- * @remarks This is a maintenance operation and may take time for large numbers of files.
177
- */
178
- async clearSavedTransformedFiles() {
179
- if (!this.config.saveTransformedFiles) {
180
- return;
181
- }
182
- try {
183
- this.logger?.warn?.("clearSavedTransformedFiles requires custom storage implementation to find and delete transformed files");
184
- } catch (error) {
185
- this.logger?.error?.(`Failed to clear saved transformed files: ${error}`);
186
- }
187
- }
188
- /**
189
- * Clears saved transformed files for a specific original file.
190
- * @param originalFileId Original file identifier to clear transformed files for.
191
- */
192
- async clearSavedTransformedFilesForFile(originalFileId) {
193
- if (!this.config.saveTransformedFiles) {
194
- return;
195
- }
196
- try {
197
- this.logger?.warn?.("clearSavedTransformedFilesForFile requires custom storage implementation");
198
- } catch (error) {
199
- this.logger?.error?.(`Failed to clear saved transformed files for ${originalFileId}: ${error}`);
200
- }
201
- }
202
- /**
203
- * Gets cache statistics (combined from all transformers).
204
- * @returns Cache statistics for audio, image, and video transformers.
205
- */
206
- getCacheStats() {
207
- const stats = {
208
- audio: this.audioTransformer?.getCacheStats(),
209
- image: this.imageTransformer?.getCacheStats(),
210
- video: this.videoTransformer?.getCacheStats()
211
- };
212
- if (stats.image === void 0) {
213
- delete stats.image;
214
- }
215
- if (stats.video === void 0) {
216
- delete stats.video;
217
- }
218
- if (stats.audio === void 0) {
219
- delete stats.audio;
220
- }
221
- return stats;
222
- }
223
- /**
224
- * Validates query parameters for the given media type.
225
- * @param query The query parameters to validate.
226
- * @param mediaType The media type being validated ('image', 'video', or 'audio').
227
- * @throws {ValidationError} When invalid parameters are provided.
228
- */
229
- validateQueryParameters(query, mediaType) {
230
- switch (mediaType) {
231
- case "audio": {
232
- this.validateAudioQueryParameters(query);
233
- break;
234
- }
235
- case "image": {
236
- this.validateImageQueryParameters(query);
237
- break;
238
- }
239
- case "video": {
240
- this.validateVideoQueryParameters(query);
241
- break;
242
- }
243
- }
244
- }
245
- /**
246
- * Validates query parameters for image transformations. Checks for invalid video/audio-only parameters, invalid fit values, invalid rotation angles, invalid formats, and incomplete crop parameter sets.
247
- * @param query Query parameters to validate.
248
- * @throws {ValidationError} When validation fails.
249
- */
250
- // eslint-disable-next-line class-methods-use-this
251
- validateImageQueryParameters(query) {
252
- const allowedParameters = /* @__PURE__ */ new Set([
253
- "alphaQuality",
254
- "angle",
255
- "background",
256
- "bandbool",
257
- "blur",
258
- "boolean",
259
- "brightness",
260
- "channel",
261
- "clahe",
262
- "colourspace",
263
- "compressionLevel",
264
- "convolve",
265
- "cropHeight",
266
- "cropWidth",
267
- "delay",
268
- "dilate",
269
- "effort",
270
- "erode",
271
- "extractChannel",
272
- "fastShrinkOnLoad",
273
- "fit",
274
- "flatten",
275
- "flip",
276
- "flop",
277
- "format",
278
- "frameRate",
279
- "gamma",
280
- "grayscale",
281
- "greyscale",
282
- "height",
283
- "hue",
284
- "joinChannel",
285
- "kernel",
286
- "left",
287
- "lightness",
288
- "linear",
289
- "loop",
290
- "maxSlope",
291
- "median",
292
- "modulate",
293
- "negate",
294
- "normalise",
295
- "normalize",
296
- "position",
297
- "quality",
298
- "recombine",
299
- "saturation",
300
- "sharpen",
301
- "threshold",
302
- "tint",
303
- "top",
304
- "unflatten",
305
- "width",
306
- "withoutEnlargement",
307
- "withoutReduction"
308
- ]);
309
- const videoAudioOnlyParameters = /* @__PURE__ */ new Set(["bitrate", "codec", "frameRate", "keyFrameInterval", "numberOfChannels", "sampleRate"]);
310
- const invalidParameters = [];
311
- for (const parameter of videoAudioOnlyParameters) {
312
- if (query[parameter] !== void 0) {
313
- invalidParameters.push(parameter);
314
- }
315
- }
316
- if (invalidParameters.length > 0) {
317
- const sortedInvalidParameters = [...invalidParameters].toSorted();
318
- throw new ValidationError(
319
- `Invalid query parameters for image transformation: ${sortedInvalidParameters.join(", ")}. Images support: ${[...allowedParameters].toSorted().join(", ")}. Video/audio parameters (${sortedInvalidParameters.join(", ")}) are not supported for images.`,
320
- "INVALID_PARAMS_FOR_IMAGE",
321
- "image",
322
- sortedInvalidParameters,
323
- [...allowedParameters].toSorted()
324
- );
325
- }
326
- if (query.fit && !["contain", "cover", "fill", "inside", "outside"].includes(query.fit)) {
327
- throw new ValidationError(
328
- `Invalid fit value: "${query.fit}". Supported values: "cover", "contain", "fill", "inside", "outside"`,
329
- "INVALID_FIT_VALUE",
330
- "image",
331
- ["fit"],
332
- ["cover", "contain", "fill", "inside", "outside"]
333
- );
334
- }
335
- if (query.format && !["avif", "gif", "jpeg", "png", "tiff", "webp"].includes(query.format)) {
336
- throw new ValidationError(
337
- `Invalid format for image: "${query.format}". Supported formats: jpeg, png, webp, avif, tiff, gif`,
338
- "INVALID_FORMAT",
339
- "image",
340
- ["format"],
341
- ["jpeg", "png", "webp", "avif", "tiff", "gif"]
342
- );
343
- }
344
- const cropParameters = ["left", "top", "cropWidth", "cropHeight"];
345
- const providedCropParameters = cropParameters.filter((parameter) => query[parameter] !== void 0);
346
- if (providedCropParameters.length > 0 && providedCropParameters.length !== cropParameters.length) {
347
- throw new ValidationError(
348
- `Incomplete crop parameters: ${providedCropParameters.join(", ")}. All crop parameters must be provided: left, top, cropWidth, cropHeight`,
349
- "INCOMPLETE_CROP_PARAMS",
350
- "image",
351
- providedCropParameters,
352
- cropParameters
353
- );
354
- }
355
- }
356
- /**
357
- * Validates query parameters for video transformations. Checks for invalid audio-only parameters, invalid fit values, invalid rotation angles, invalid codecs, invalid formats, incomplete crop parameter sets, and invalid numeric values.
358
- * @param query Query parameters to validate.
359
- * @throws {ValidationError} When validation fails.
360
- */
361
- // eslint-disable-next-line class-methods-use-this
362
- validateVideoQueryParameters(query) {
363
- const allowedParameters = /* @__PURE__ */ new Set([
364
- "angle",
365
- "background",
366
- "bitrate",
367
- "codec",
368
- "cropHeight",
369
- "cropWidth",
370
- "fit",
371
- "format",
372
- "frameRate",
373
- "height",
374
- "keyFrameInterval",
375
- "left",
376
- "position",
377
- "quality",
378
- "top",
379
- "width",
380
- "withoutEnlargement",
381
- "withoutReduction"
382
- ]);
383
- const audioOnlyParameters = /* @__PURE__ */ new Set(["numberOfChannels", "sampleRate"]);
384
- const invalidParameters = [];
385
- for (const parameter of audioOnlyParameters) {
386
- if (query[parameter] !== void 0) {
387
- invalidParameters.push(parameter);
388
- }
389
- }
390
- if (invalidParameters.length > 0) {
391
- const sortedInvalidParameters = [...invalidParameters].toSorted();
392
- throw new ValidationError(
393
- `Invalid query parameters for video transformation: ${sortedInvalidParameters.join(", ")}. Videos support: ${[...allowedParameters].toSorted().join(", ")}. Audio-only parameters (${sortedInvalidParameters.join(", ")}) are not supported for videos.`,
394
- "INVALID_PARAMS_FOR_VIDEO",
395
- "video",
396
- sortedInvalidParameters,
397
- [...allowedParameters].toSorted()
398
- );
399
- }
400
- if (query.fit && !["contain", "cover", "fill", "inside", "outside"].includes(query.fit)) {
401
- throw new ValidationError(
402
- `Invalid fit value: "${query.fit}". Supported values: "cover", "contain", "fill", "inside", "outside"`,
403
- "INVALID_FIT_VALUE",
404
- "video",
405
- ["fit"],
406
- ["cover", "contain", "fill", "inside", "outside"]
407
- );
408
- }
409
- if (query.codec && !["av1", "avc", "hevc", "vp8", "vp9"].includes(query.codec)) {
410
- throw new ValidationError(
411
- `Invalid codec for video: "${query.codec}". Supported codecs: avc, hevc, vp8, vp9, av1`,
412
- "INVALID_VIDEO_CODEC",
413
- "video",
414
- ["codec"],
415
- ["avc", "hevc", "vp8", "vp9", "av1"]
416
- );
417
- }
418
- if (query.format && !["mkv", "mp4", "ogg", "webm"].includes(query.format)) {
419
- throw new ValidationError(
420
- `Invalid format for video: "${query.format}". Supported formats: mp4, webm, mkv, ogg`,
421
- "INVALID_VIDEO_FORMAT",
422
- "video",
423
- ["format"],
424
- ["mp4", "webm", "mkv", "ogg"]
425
- );
426
- }
427
- const cropParameters = ["left", "top", "cropWidth", "cropHeight"];
428
- const providedCropParameters = cropParameters.filter((parameter) => query[parameter] !== void 0);
429
- if (providedCropParameters.length > 0 && providedCropParameters.length !== cropParameters.length) {
430
- throw new ValidationError(
431
- `Incomplete crop parameters: ${providedCropParameters.join(", ")}. All crop parameters must be provided: left, top, cropWidth, cropHeight`,
432
- "INCOMPLETE_CROP_PARAMS",
433
- "video",
434
- providedCropParameters,
435
- cropParameters
436
- );
437
- }
438
- if (query.width !== void 0 && (typeof query.width !== "number" || query.width <= 0)) {
439
- throw new ValidationError(`Invalid width: ${query.width}. Must be a positive number.`, "INVALID_WIDTH", "video", ["width"], []);
440
- }
441
- if (query.height !== void 0 && (typeof query.height !== "number" || query.height <= 0)) {
442
- throw new ValidationError(`Invalid height: ${query.height}. Must be a positive number.`, "INVALID_HEIGHT", "video", ["height"], []);
443
- }
444
- if (query.bitrate !== void 0 && (typeof query.bitrate !== "number" || query.bitrate <= 0)) {
445
- throw new ValidationError(`Invalid bitrate: ${query.bitrate}. Must be a positive number.`, "INVALID_BITRATE", "video", ["bitrate"], []);
446
- }
447
- if (query.frameRate !== void 0 && (typeof query.frameRate !== "number" || query.frameRate <= 0)) {
448
- throw new ValidationError(`Invalid frameRate: ${query.frameRate}. Must be a positive number.`, "INVALID_FRAME_RATE", "video", ["frameRate"], []);
449
- }
450
- if (query.keyFrameInterval !== void 0 && (typeof query.keyFrameInterval !== "number" || query.keyFrameInterval <= 0)) {
451
- throw new ValidationError(
452
- `Invalid keyFrameInterval: ${query.keyFrameInterval}. Must be a positive number.`,
453
- "INVALID_KEY_FRAME_INTERVAL",
454
- "video",
455
- ["keyFrameInterval"],
456
- []
457
- );
458
- }
459
- }
460
- /**
461
- * Generates a unique ID for transformed files based on original file and transformations.
462
- * @param originalFileId The original file identifier.
463
- * @param query The transformation query parameters.
464
- * @param mediaType The media type (image, video, audio).
465
- * @returns Unique deterministic identifier for the transformed file.
466
- * @private
467
- */
468
- // eslint-disable-next-line class-methods-use-this
469
- generateTransformedFileId(originalFileId, query, mediaType) {
470
- const transformParameters = Object.keys(query).filter((key) => query[key] !== void 0).toSorted().map((key) => `${key}:${query[key]}`).join("|");
471
- const hashInput = `${originalFileId}|${mediaType}|${transformParameters}`;
472
- const hash = createHash("sha256").update(hashInput).digest("hex").slice(0, 16);
473
- return `${originalFileId}_transformed_${hash}`;
474
- }
475
- /**
476
- * Checks if the query contains any transformations.
477
- * @param query The transformation query parameters.
478
- * @returns True if any transformations are requested.
479
- * @private
480
- */
481
- hasTransformations(query) {
482
- return this.hasImageTransformations(query) || this.hasVideoTransformations(query) || this.hasAudioTransformations(query);
483
- }
484
- /**
485
- * Creates a MediaTransformResult from a stored transformed file.
486
- * @param storedFile The stored transformed file.
487
- * @param mediaType The media type (image, video, audio).
488
- * @param originalFile The original file information.
489
- * @returns Media transformation result with metadata.
490
- * @private
491
- */
492
- createMediaTransformResult(storedFile, mediaType, originalFile) {
493
- const baseResult = {
494
- buffer: storedFile.content,
495
- format: this.getFormatFromContentType(storedFile.contentType || ""),
496
- mediaType,
497
- originalFile,
498
- size: storedFile.content.length
499
- };
500
- switch (mediaType) {
501
- case "audio": {
502
- return {
503
- ...baseResult,
504
- bitrate: storedFile.bitrate,
505
- duration: storedFile.duration,
506
- mediaType: "audio",
507
- numberOfChannels: storedFile.numberOfChannels,
508
- sampleRate: storedFile.sampleRate
509
- };
510
- }
511
- case "image": {
512
- return {
513
- ...baseResult,
514
- height: storedFile.height,
515
- mediaType: "image",
516
- width: storedFile.width
517
- };
518
- }
519
- case "video": {
520
- return {
521
- ...baseResult,
522
- bitrate: storedFile.bitrate,
523
- duration: storedFile.duration,
524
- height: storedFile.height,
525
- mediaType: "video",
526
- width: storedFile.width
527
- };
528
- }
529
- default: {
530
- return baseResult;
531
- }
532
- }
533
- }
534
- /**
535
- * Extracts format from content type.
536
- * @param contentType MIME content type string.
537
- * @returns Format string extracted from content type.
538
- * @private
539
- */
540
- // eslint-disable-next-line class-methods-use-this
541
- getFormatFromContentType(contentType) {
542
- if (!contentType) {
543
- return "";
544
- }
545
- const extension = mime.getExtension(contentType);
546
- return extension || "application/octet-stream";
547
- }
548
- /**
549
- * Saves transformed file to storage.
550
- * @param result The transformation result to save.
551
- * @param originalFileId The original file identifier.
552
- * @param query The transformation query parameters.
553
- * @param mediaType The media type (image, video, audio).
554
- * @returns Promise that resolves when file is saved.
555
- * @private
556
- */
557
- async saveTransformedFile(result, originalFileId, query, mediaType) {
558
- try {
559
- const transformedFileId = this.generateTransformedFileId(originalFileId, query, mediaType);
560
- const metadata = {
561
- format: result.format,
562
- mediaType,
563
- originalFileId,
564
- size: result.size,
565
- transformation: query,
566
- transformedAt: (/* @__PURE__ */ new Date()).toISOString()
567
- };
568
- if (mediaType === "image" && result.width && result.height) {
569
- metadata.width = result.width;
570
- metadata.height = result.height;
571
- } else if (mediaType === "video" && result.width && result.height) {
572
- metadata.width = result.width;
573
- metadata.height = result.height;
574
- metadata.duration = result.duration;
575
- metadata.bitrate = result.bitrate;
576
- } else if (mediaType === "audio") {
577
- metadata.duration = result.duration;
578
- metadata.numberOfChannels = result.numberOfChannels;
579
- metadata.sampleRate = result.sampleRate;
580
- metadata.bitrate = result.bitrate;
581
- }
582
- await this.storage.create({
583
- contentType: mime.getType(result.format) ?? void 0,
584
- metadata,
585
- originalName: `${transformedFileId}.${result.format}`,
586
- size: result.size
587
- });
588
- this.logger?.debug?.(`Saved transformed file: ${transformedFileId}`);
589
- } catch (error) {
590
- this.logger?.error?.(`Failed to save transformed file: ${error}`);
591
- }
592
- }
593
- /**
594
- * Validates query parameters for audio transformations. Checks for invalid video-only parameters, invalid codecs, invalid formats, and invalid numeric values.
595
- * @param query Query parameters to validate.
596
- * @throws {ValidationError} When validation fails.
597
- */
598
- // eslint-disable-next-line class-methods-use-this
599
- validateAudioQueryParameters(query) {
600
- const allowedParameters = /* @__PURE__ */ new Set(["bitrate", "codec", "format", "numberOfChannels", "quality", "sampleRate"]);
601
- const videoOnlyParameters = /* @__PURE__ */ new Set([
602
- "angle",
603
- "background",
604
- "cropHeight",
605
- "cropWidth",
606
- "fit",
607
- "frameRate",
608
- "height",
609
- "keyFrameInterval",
610
- "left",
611
- "position",
612
- "top",
613
- "width",
614
- "withoutEnlargement",
615
- "withoutReduction"
616
- ]);
617
- const invalidParameters = [];
618
- for (const parameter of videoOnlyParameters) {
619
- if (query[parameter] !== void 0) {
620
- invalidParameters.push(parameter);
621
- }
622
- }
623
- if (invalidParameters.length > 0) {
624
- const sortedInvalidParameters = [...invalidParameters].toSorted();
625
- throw new ValidationError(
626
- `Invalid query parameters for audio transformation: ${sortedInvalidParameters.join(", ")}. Audio supports: ${[...allowedParameters].toSorted().join(", ")}. Video-only parameters (${sortedInvalidParameters.join(", ")}) are not supported for audio.`,
627
- "INVALID_PARAMS_FOR_AUDIO",
628
- "audio",
629
- sortedInvalidParameters,
630
- [...allowedParameters].toSorted()
631
- );
632
- }
633
- if (query.codec && !["aac", "flac", "mp3", "opus", "vorbis"].includes(query.codec)) {
634
- throw new ValidationError(
635
- `Invalid codec for audio: "${query.codec}". Supported codecs: aac, opus, mp3, vorbis, flac`,
636
- "INVALID_AUDIO_CODEC",
637
- "audio",
638
- ["codec"],
639
- ["aac", "opus", "mp3", "vorbis", "flac"]
640
- );
641
- }
642
- if (query.format && !["aac", "flac", "mp3", "ogg", "wav"].includes(query.format)) {
643
- throw new ValidationError(
644
- `Invalid format for audio: "${query.format}". Supported formats: mp3, wav, ogg, aac, flac`,
645
- "INVALID_AUDIO_FORMAT",
646
- "audio",
647
- ["format"],
648
- ["mp3", "wav", "ogg", "aac", "flac"]
649
- );
650
- }
651
- if (query.bitrate !== void 0 && (typeof query.bitrate !== "number" || query.bitrate <= 0)) {
652
- throw new ValidationError(`Invalid bitrate: ${query.bitrate}. Must be a positive number.`, "INVALID_BITRATE", "audio", ["bitrate"], []);
653
- }
654
- if (query.numberOfChannels !== void 0 && (typeof query.numberOfChannels !== "number" || query.numberOfChannels <= 0 || query.numberOfChannels > 8)) {
655
- throw new ValidationError(
656
- `Invalid numberOfChannels: ${query.numberOfChannels}. Must be a number between 1 and 8.`,
657
- "INVALID_CHANNEL_COUNT",
658
- "audio",
659
- ["numberOfChannels"],
660
- ["1", "2", "3", "4", "5", "6", "7", "8"]
661
- );
662
- }
663
- if (query.sampleRate !== void 0) {
664
- const validSampleRates = [8e3, 11025, 16e3, 22050, 32e3, 44100, 48e3, 88200, 96e3, 192e3];
665
- if (typeof query.sampleRate !== "number" || !validSampleRates.includes(query.sampleRate)) {
666
- throw new ValidationError(
667
- `Invalid sampleRate: ${query.sampleRate}. Supported sample rates: ${validSampleRates.join(", ")}`,
668
- "INVALID_SAMPLE_RATE",
669
- "audio",
670
- ["sampleRate"],
671
- validSampleRates.map(String)
672
- );
673
- }
674
- }
675
- }
676
- /**
677
- * Handles image transformation.
678
- * @param fileId The file identifier.
679
- * @param query The transformation query parameters.
680
- * @returns Promise resolving to media transformation result.
681
- * @private
682
- */
683
- async handleImageTransformation(fileId, query) {
684
- const steps = [];
685
- if (this.hasImageTransformations(query)) {
686
- if (query.width || query.height || query.fit) {
687
- steps.push({
688
- options: {
689
- fit: query.fit,
690
- height: query.height,
691
- position: query.position,
692
- width: query.width,
693
- withoutEnlargement: query.withoutEnlargement,
694
- withoutReduction: query.withoutReduction
695
- },
696
- type: "resize"
697
- });
698
- }
699
- if (query.left !== void 0 && query.top !== void 0 && query.cropWidth && query.cropHeight) {
700
- steps.push({
701
- options: {
702
- height: query.cropHeight,
703
- left: query.left,
704
- top: query.top,
705
- width: query.cropWidth
706
- },
707
- type: "crop"
708
- });
709
- }
710
- if (query.angle) {
711
- steps.push({
712
- options: {
713
- angle: query.angle,
714
- background: query.background
715
- },
716
- type: "rotate"
717
- });
718
- }
719
- if (query.format) {
720
- steps.push({
721
- options: {
722
- format: query.format,
723
- quality: query.quality
724
- },
725
- type: "format"
726
- });
727
- } else if (query.quality) {
728
- steps.push({
729
- options: {
730
- quality: query.quality
731
- },
732
- type: "quality"
733
- });
734
- }
735
- }
736
- const result = steps.length > 0 ? await this.imageTransformer.transform(fileId, steps) : await this.imageTransformer.transform(fileId, []);
737
- return this.convertImageResult(result);
738
- }
739
- /**
740
- * Handles video transformation.
741
- * @param fileId The file identifier.
742
- * @param query The transformation query parameters.
743
- * @returns Promise resolving to media transformation result.
744
- * @private
745
- */
746
- async handleVideoTransformation(fileId, query) {
747
- const steps = [];
748
- if (this.hasVideoTransformations(query)) {
749
- if (query.width || query.height || query.fit) {
750
- steps.push({
751
- options: {
752
- fit: query.fit,
753
- height: query.height,
754
- position: query.position,
755
- width: query.width,
756
- withoutEnlargement: query.withoutEnlargement,
757
- withoutReduction: query.withoutReduction
758
- },
759
- type: "resize"
760
- });
761
- }
762
- if (query.left !== void 0 && query.top !== void 0 && query.cropWidth && query.cropHeight) {
763
- steps.push({
764
- options: {
765
- height: Number(query.cropHeight),
766
- left: query.left,
767
- top: query.top,
768
- width: query.cropWidth
769
- },
770
- type: "crop"
771
- });
772
- }
773
- if (query.angle) {
774
- steps.push({
775
- options: {
776
- angle: query.angle,
777
- background: query.background
778
- },
779
- type: "rotate"
780
- });
781
- }
782
- if (query.codec || query.bitrate) {
783
- steps.push({
784
- options: {
785
- bitrate: query.bitrate || query.quality,
786
- codec: query.codec,
787
- frameRate: query.frameRate,
788
- keyFrameInterval: query.keyFrameInterval
789
- },
790
- type: "codec"
791
- });
792
- }
793
- if (query.format) {
794
- steps.push({
795
- options: {
796
- format: query.format
797
- },
798
- type: "format"
799
- });
800
- }
801
- }
802
- const result = steps.length > 0 ? await this.videoTransformer.transform(fileId, steps) : await this.videoTransformer.transform(fileId, []);
803
- return this.convertVideoResult(result);
804
- }
805
- /**
806
- * Handles audio transformation.
807
- * @param fileId The file identifier.
808
- * @param query The transformation query parameters.
809
- * @returns Promise resolving to media transformation result.
810
- * @private
811
- */
812
- async handleAudioTransformation(fileId, query) {
813
- const steps = [];
814
- if (this.hasAudioTransformations(query)) {
815
- if (query.sampleRate) {
816
- steps.push({
817
- options: {
818
- sampleRate: query.sampleRate
819
- },
820
- type: "resample"
821
- });
822
- }
823
- if (query.numberOfChannels) {
824
- steps.push({
825
- options: {
826
- numberOfChannels: query.numberOfChannels
827
- },
828
- type: "channels"
829
- });
830
- }
831
- if (query.codec || query.bitrate) {
832
- steps.push({
833
- options: {
834
- bitrate: query.bitrate || query.quality,
835
- codec: query.codec
836
- },
837
- type: "codec"
838
- });
839
- }
840
- if (query.format) {
841
- steps.push({
842
- options: {
843
- format: query.format
844
- },
845
- type: "format"
846
- });
847
- }
848
- }
849
- const result = steps.length > 0 ? await this.audioTransformer.transform(fileId, steps) : await this.audioTransformer.transform(fileId, []);
850
- return this.convertAudioResult(result);
851
- }
852
- /**
853
- * Parses query parameters from various input formats.
854
- * @param query Query parameters in various formats.
855
- * @returns Normalized MediaTransformQuery object.
856
- * @private
857
- */
858
- parseQuery(query) {
859
- if (typeof query === "string") {
860
- return this.parseURLSearchParams(new URLSearchParams(query));
861
- }
862
- if (query instanceof URLSearchParams) {
863
- return this.parseURLSearchParams(query);
864
- }
865
- const transformQuery = query;
866
- if (query.left) {
867
- transformQuery.left = Number.parseInt(query.left, 10);
868
- }
869
- if (query.top) {
870
- transformQuery.top = Number.parseInt(query.top, 10);
871
- }
872
- if (query.cropWidth) {
873
- transformQuery.cropWidth = Number.parseInt(query.cropWidth, 10);
874
- }
875
- if (query.cropHeight) {
876
- transformQuery.cropHeight = Number.parseInt(query.cropHeight, 10);
877
- }
878
- if (query.angle) {
879
- transformQuery.angle = Number.parseInt(query.angle, 10);
880
- }
881
- if (query.quality) {
882
- transformQuery.quality = Number.parseInt(query.quality, 10);
883
- }
884
- if (query.width) {
885
- transformQuery.width = Number.parseInt(query.width, 10);
886
- }
887
- if (query.height) {
888
- transformQuery.height = Number.parseInt(query.height, 10);
889
- }
890
- if (query.bitrate) {
891
- transformQuery.bitrate = Number.parseInt(query.bitrate, 10);
892
- }
893
- if (query.frameRate) {
894
- transformQuery.frameRate = Number.parseInt(query.frameRate, 10);
895
- }
896
- if (query.keyFrameInterval) {
897
- transformQuery.keyFrameInterval = Number.parseInt(query.keyFrameInterval, 10);
898
- }
899
- if (query.numberOfChannels) {
900
- transformQuery.numberOfChannels = Number.parseInt(query.numberOfChannels, 10);
901
- }
902
- if (query.sampleRate) {
903
- transformQuery.sampleRate = Number.parseInt(query.sampleRate, 10);
904
- }
905
- return transformQuery;
906
- }
907
- /**
908
- * Parses boolean parameter from string.
909
- * @param value String value to parse.
910
- * @returns Boolean value or undefined.
911
- * @private
912
- */
913
- // eslint-disable-next-line class-methods-use-this
914
- parseBooleanParameter(value) {
915
- if (value === null) {
916
- return void 0;
917
- }
918
- return value === "true" || value === "1";
919
- }
920
- /**
921
- * Parses URLSearchParams into MediaTransformQuery.
922
- * @param parameters URL search parameters object.
923
- * @returns MediaTransformQuery object with parsed parameters.
924
- * @private
925
- */
926
- parseURLSearchParams(parameters) {
927
- const query = {};
928
- if (parameters.has("format")) {
929
- query.format = parameters.get("format") ?? void 0;
930
- }
931
- if (parameters.has("quality")) {
932
- query.quality = parameters.get("quality") ? Number.parseInt(parameters.get("quality"), 10) : void 0;
933
- }
934
- if (parameters.has("width")) {
935
- query.width = parameters.get("width") ? Number.parseInt(parameters.get("width"), 10) : void 0;
936
- }
937
- if (parameters.has("height")) {
938
- query.height = parameters.get("height") ? Number.parseInt(parameters.get("height"), 10) : void 0;
939
- }
940
- if (parameters.has("fit")) {
941
- const fitValue = parameters.get("fit");
942
- if (fitValue && ["contain", "cover", "fill", "inside", "outside"].includes(fitValue)) {
943
- query.fit = fitValue;
944
- }
945
- }
946
- if (parameters.has("position")) {
947
- query.position = parameters.get("position") ?? void 0;
948
- }
949
- if (parameters.has("withoutEnlargement")) {
950
- const value = parameters.get("withoutEnlargement");
951
- query.withoutEnlargement = value === "true" ? true : void 0;
952
- }
953
- if (parameters.has("withoutReduction")) {
954
- const value = parameters.get("withoutReduction");
955
- query.withoutReduction = value === "true" ? true : void 0;
956
- }
957
- if (parameters.has("left")) {
958
- query.left = parameters.get("left") ? Number.parseInt(parameters.get("left"), 10) : void 0;
959
- }
960
- if (parameters.has("top")) {
961
- query.top = parameters.get("top") ? Number.parseInt(parameters.get("top"), 10) : void 0;
962
- }
963
- if (parameters.has("cropWidth")) {
964
- query.cropWidth = parameters.get("cropWidth") ? Number.parseInt(parameters.get("cropWidth"), 10) : void 0;
965
- }
966
- if (parameters.has("cropHeight")) {
967
- query.cropHeight = parameters.get("cropHeight") ? Number.parseInt(parameters.get("cropHeight"), 10) : void 0;
968
- }
969
- if (parameters.has("angle")) {
970
- const angleValue = parameters.get("angle") ? Number.parseInt(parameters.get("angle"), 10) : void 0;
971
- if (angleValue !== void 0) {
972
- query.angle = angleValue;
973
- }
974
- }
975
- if (parameters.has("background")) {
976
- query.background = parameters.get("background") ?? void 0;
977
- }
978
- if (parameters.has("blur")) {
979
- query.blur = this.parseBooleanParameter(parameters.get("blur"));
980
- }
981
- if (parameters.has("sharpen")) {
982
- query.sharpen = this.parseBooleanParameter(parameters.get("sharpen"));
983
- }
984
- if (parameters.has("median")) {
985
- query.median = parameters.get("median") ? Number.parseInt(parameters.get("median"), 10) : void 0;
986
- }
987
- if (parameters.has("clahe")) {
988
- query.clahe = this.parseBooleanParameter(parameters.get("clahe"));
989
- }
990
- if (parameters.has("threshold")) {
991
- query.threshold = parameters.get("threshold") ? Number.parseInt(parameters.get("threshold"), 10) : void 0;
992
- }
993
- if (parameters.has("gamma")) {
994
- query.gamma = this.parseBooleanParameter(parameters.get("gamma"));
995
- }
996
- if (parameters.has("negate")) {
997
- query.negate = this.parseBooleanParameter(parameters.get("negate"));
998
- }
999
- if (parameters.has("normalise") || parameters.has("normalize")) {
1000
- query.normalise = this.parseBooleanParameter(parameters.get("normalise")) || this.parseBooleanParameter(parameters.get("normalize"));
1001
- }
1002
- if (parameters.has("flatten")) {
1003
- query.flatten = this.parseBooleanParameter(parameters.get("flatten"));
1004
- }
1005
- if (parameters.has("unflatten")) {
1006
- query.unflatten = this.parseBooleanParameter(parameters.get("unflatten"));
1007
- }
1008
- if (parameters.has("flip")) {
1009
- query.flip = this.parseBooleanParameter(parameters.get("flip"));
1010
- }
1011
- if (parameters.has("flop")) {
1012
- query.flop = this.parseBooleanParameter(parameters.get("flop"));
1013
- }
1014
- if (parameters.has("affine")) {
1015
- query.affine = this.parseBooleanParameter(parameters.get("affine"));
1016
- }
1017
- if (parameters.has("dilate")) {
1018
- query.dilate = this.parseBooleanParameter(parameters.get("dilate"));
1019
- }
1020
- if (parameters.has("erode")) {
1021
- query.erode = this.parseBooleanParameter(parameters.get("erode"));
1022
- }
1023
- if (parameters.has("pipelineColourspace")) {
1024
- query.pipelineColourspace = this.parseBooleanParameter(parameters.get("pipelineColourspace"));
1025
- }
1026
- if (parameters.has("toColourspace")) {
1027
- query.toColourspace = this.parseBooleanParameter(parameters.get("toColourspace"));
1028
- }
1029
- if (parameters.has("removeAlpha")) {
1030
- query.removeAlpha = this.parseBooleanParameter(parameters.get("removeAlpha"));
1031
- }
1032
- if (parameters.has("ensureAlpha")) {
1033
- query.ensureAlpha = this.parseBooleanParameter(parameters.get("ensureAlpha"));
1034
- }
1035
- if (parameters.has("greyscale") || parameters.has("grayscale")) {
1036
- query.greyscale = this.parseBooleanParameter(parameters.get("greyscale")) || this.parseBooleanParameter(parameters.get("grayscale"));
1037
- }
1038
- if (parameters.has("modulate")) {
1039
- query.modulate = this.parseBooleanParameter(parameters.get("modulate"));
1040
- }
1041
- if (parameters.has("tint")) {
1042
- query.tint = this.parseBooleanParameter(parameters.get("tint"));
1043
- }
1044
- if (parameters.has("brightness")) {
1045
- query.brightness = parameters.get("brightness") ? Number.parseFloat(parameters.get("brightness")) : void 0;
1046
- }
1047
- if (parameters.has("saturation")) {
1048
- query.saturation = parameters.get("saturation") ? Number.parseFloat(parameters.get("saturation")) : void 0;
1049
- }
1050
- if (parameters.has("hue")) {
1051
- query.hue = parameters.get("hue") ? Number.parseInt(parameters.get("hue"), 10) : void 0;
1052
- }
1053
- if (parameters.has("lightness")) {
1054
- query.lightness = parameters.get("lightness") ? Number.parseInt(parameters.get("lightness"), 10) : void 0;
1055
- }
1056
- if (parameters.has("kernel")) {
1057
- query.kernel = parameters.get("kernel") ?? void 0;
1058
- }
1059
- if (parameters.has("fastShrinkOnLoad")) {
1060
- query.fastShrinkOnLoad = parameters.get("fastShrinkOnLoad") === "true";
1061
- }
1062
- if (parameters.has("codec")) {
1063
- const codecValue = parameters.get("codec");
1064
- const validCodecs = ["avc", "hevc", "vp8", "vp9", "av1", "aac", "opus", "mp3", "vorbis", "flac"];
1065
- if (codecValue && validCodecs.includes(codecValue)) {
1066
- query.codec = codecValue;
1067
- }
1068
- }
1069
- if (parameters.has("bitrate")) {
1070
- query.bitrate = parameters.get("bitrate") ? Number.parseInt(parameters.get("bitrate"), 10) : void 0;
1071
- }
1072
- if (parameters.has("frameRate")) {
1073
- query.frameRate = parameters.get("frameRate") ? Number.parseInt(parameters.get("frameRate"), 10) : void 0;
1074
- }
1075
- if (parameters.has("keyFrameInterval")) {
1076
- query.keyFrameInterval = parameters.get("keyFrameInterval") ? Number.parseInt(parameters.get("keyFrameInterval"), 10) : void 0;
1077
- }
1078
- if (parameters.has("numberOfChannels")) {
1079
- query.numberOfChannels = parameters.get("numberOfChannels") ? Number.parseInt(parameters.get("numberOfChannels"), 10) : void 0;
1080
- }
1081
- if (parameters.has("sampleRate")) {
1082
- query.sampleRate = parameters.get("sampleRate") ? Number.parseInt(parameters.get("sampleRate"), 10) : void 0;
1083
- }
1084
- return query;
1085
- }
1086
- /**
1087
- * Detects media type from MIME type.
1088
- * @param contentType MIME content type string.
1089
- * @returns Detected media type.
1090
- * @throws Error if content type is missing or unsupported.
1091
- * @private
1092
- */
1093
- // eslint-disable-next-line class-methods-use-this
1094
- detectMediaType(contentType) {
1095
- if (!contentType) {
1096
- throw new Error("Cannot detect media type: no content type provided");
1097
- }
1098
- if (!isKnownContentType(contentType)) {
1099
- throw new Error(`Unknown or invalid content type: ${contentType}`);
1100
- }
1101
- if (contentType.startsWith("image/")) {
1102
- return "image";
1103
- }
1104
- if (contentType.startsWith("video/")) {
1105
- return "video";
1106
- }
1107
- if (contentType.startsWith("audio/")) {
1108
- return "audio";
1109
- }
1110
- throw new Error(`Unsupported media type for content type: ${contentType}`);
1111
- }
1112
- /**
1113
- * Checks if query has image transformations.
1114
- * @param query The transformation query parameters.
1115
- * @returns True if image transformations are requested.
1116
- * @private
1117
- */
1118
- // eslint-disable-next-line class-methods-use-this
1119
- hasImageTransformations(query) {
1120
- return !!(query.width || query.height || query.fit || query.position || query.withoutEnlargement || query.withoutReduction || query.left !== void 0 || query.top !== void 0 || query.cropWidth || query.cropHeight || query.angle !== void 0 || query.background || query.blur || query.sharpen || query.median || query.clahe || query.threshold !== void 0 || query.gamma || query.negate || query.normalise || query.flatten || query.unflatten || query.flip || query.flop || query.greyscale || query.modulate || query.tint || query.brightness !== void 0 || query.saturation !== void 0 || query.hue !== void 0 || query.lightness !== void 0 || query.kernel || query.fastShrinkOnLoad || query.affine || query.dilate || query.erode || query.pipelineColourspace || query.toColourspace || query.removeAlpha || query.ensureAlpha || query.format || query.quality);
1121
- }
1122
- /**
1123
- * Checks if query has video transformations.
1124
- * @param query The transformation query parameters.
1125
- * @returns True if video transformations are requested.
1126
- * @private
1127
- */
1128
- // eslint-disable-next-line class-methods-use-this
1129
- hasVideoTransformations(query) {
1130
- return !!(query.width || query.height || query.fit || query.position || query.withoutEnlargement || query.withoutReduction || query.left !== void 0 || query.top !== void 0 || query.cropWidth || query.cropHeight || query.angle || query.background || query.codec || query.bitrate || query.frameRate || query.keyFrameInterval || query.format || query.quality);
1131
- }
1132
- /**
1133
- * Checks if query has audio transformations.
1134
- * @param query The transformation query parameters.
1135
- * @returns True if audio transformations are requested.
1136
- * @private
1137
- */
1138
- // eslint-disable-next-line class-methods-use-this
1139
- hasAudioTransformations(query) {
1140
- return !!(query.sampleRate || query.numberOfChannels || query.codec || query.bitrate || query.format || query.quality);
1141
- }
1142
- /**
1143
- * Converts ImageTransformer result to unified format.
1144
- * @param result The image transformation result.
1145
- * @returns Unified media transformation result.
1146
- * @private
1147
- */
1148
- // eslint-disable-next-line class-methods-use-this
1149
- convertImageResult(result) {
1150
- return {
1151
- buffer: result.buffer,
1152
- format: result.format,
1153
- height: result.height,
1154
- mediaType: "image",
1155
- originalFile: result.originalFile,
1156
- size: result.size,
1157
- width: result.width
1158
- };
1159
- }
1160
- /**
1161
- * Converts VideoTransformer result to unified format.
1162
- * @param result The video transformation result.
1163
- * @returns Unified media transformation result.
1164
- * @private
1165
- */
1166
- // eslint-disable-next-line class-methods-use-this
1167
- convertVideoResult(result) {
1168
- return {
1169
- bitrate: result.bitrate,
1170
- buffer: result.buffer,
1171
- duration: result.duration,
1172
- format: result.format,
1173
- height: result.height,
1174
- mediaType: "video",
1175
- originalFile: result.originalFile,
1176
- size: result.size,
1177
- width: result.width
1178
- };
1179
- }
1180
- /**
1181
- * Converts AudioTransformer result to unified format.
1182
- * @param result The audio transformation result.
1183
- * @returns Unified media transformation result.
1184
- * @private
1185
- */
1186
- // eslint-disable-next-line class-methods-use-this
1187
- convertAudioResult(result) {
1188
- return {
1189
- bitrate: result.bitrate,
1190
- buffer: result.buffer,
1191
- duration: result.duration,
1192
- format: result.format,
1193
- mediaType: "audio",
1194
- numberOfChannels: result.numberOfChannels,
1195
- originalFile: result.originalFile,
1196
- sampleRate: result.sampleRate,
1197
- size: result.size
1198
- };
1199
- }
1200
- }
1201
-
1202
- export { MediaTransformer as default };