@sproux/media-sdk 0.1.0 → 0.1.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.
- package/README.md +106 -114
- package/dist/index.cjs +110 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -94
- package/dist/index.d.ts +78 -94
- package/dist/index.js +103 -117
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @sproux/media-sdk
|
|
2
2
|
|
|
3
|
-
Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from
|
|
3
|
+
Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from object keys using a singleton pattern.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -18,138 +18,145 @@ yarn add @sproux/media-sdk
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
|
-
import {
|
|
22
|
-
buildImageVariantUrl,
|
|
23
|
-
buildVideoHlsUrl,
|
|
24
|
-
buildVideoThumbnailUrl,
|
|
25
|
-
parseMediaUrl,
|
|
26
|
-
} from '@sproux/media-sdk';
|
|
21
|
+
import { SprouxMedia, IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';
|
|
27
22
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
// Initialize once (e.g. at app startup)
|
|
24
|
+
const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
25
|
+
|
|
26
|
+
// Build an image variant URL using enums
|
|
27
|
+
const imageUrl = media.getImageUrl('avatar/image/usr-1/abc', {
|
|
28
|
+
extension: IMAGE_FORMAT.WEBP,
|
|
29
|
+
width: 200,
|
|
30
|
+
height: 200,
|
|
31
|
+
resizeType: IMAGE_RESIZE_TYPE.FIT,
|
|
32
|
+
quality: 80,
|
|
33
|
+
});
|
|
34
|
+
// → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
34
35
|
|
|
35
36
|
// Build a video HLS playlist URL
|
|
36
|
-
const hlsUrl =
|
|
37
|
-
'https://cdn.example.com/gallery/video/usr-1/intro.mp4'
|
|
38
|
-
);
|
|
37
|
+
const hlsUrl = media.getVideoHlsUrl('gallery/video/usr-1/intro');
|
|
39
38
|
// → "https://cdn.example.com/gallery/video/usr-1/intro/playlist.m3u8"
|
|
40
39
|
|
|
41
40
|
// Build a video thumbnail URL
|
|
42
|
-
const
|
|
43
|
-
'https://cdn.example.com/gallery/video/usr-1/intro.mp4'
|
|
44
|
-
);
|
|
41
|
+
const thumbUrl = media.getVideoThumbnailUrl('gallery/video/usr-1/intro');
|
|
45
42
|
// → "https://cdn.example.com/gallery/video/usr-1/intro/thumbnail.webp"
|
|
46
43
|
```
|
|
47
44
|
|
|
48
|
-
##
|
|
45
|
+
## Singleton Usage
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
```typescript
|
|
48
|
+
import { SprouxMedia, IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
// Initialize once at startup
|
|
51
|
+
SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
// Access from anywhere via getInstance()
|
|
54
|
+
const media = SprouxMedia.getInstance();
|
|
55
|
+
const url = media.getImageUrl('avatar/image/usr-1/photo', {
|
|
56
|
+
extension: IMAGE_FORMAT.WEBP,
|
|
57
|
+
width: 400,
|
|
58
|
+
height: 300,
|
|
59
|
+
resizeType: IMAGE_RESIZE_TYPE.FILL,
|
|
60
|
+
});
|
|
61
|
+
```
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
import { parseMediaUrl } from '@sproux/media-sdk';
|
|
63
|
+
> Calling `getInstance()` before `init()` will throw an error.
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// name: "abc",
|
|
68
|
-
// extension: "jpg"
|
|
69
|
-
// }
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
### `SprouxMedia.init(config: SprouxMediaConfig): SprouxMedia`
|
|
68
|
+
|
|
69
|
+
Initialize the singleton with CDN configuration. Returns the singleton instance.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
70
73
|
```
|
|
71
74
|
|
|
72
|
-
### `
|
|
75
|
+
### `SprouxMedia.getInstance(): SprouxMedia`
|
|
76
|
+
|
|
77
|
+
Returns the existing singleton instance. Throws if `init()` has not been called.
|
|
78
|
+
|
|
79
|
+
### `media.getImageUrl(objectKey: string, options: ImageUrlOptions): string`
|
|
73
80
|
|
|
74
81
|
Build a CDN URL for an image variant with specific dimensions, resize type, and quality.
|
|
75
82
|
|
|
83
|
+
- `objectKey` — Object key **without** extension (e.g. `"avatar/image/usr-1/abc"`)
|
|
84
|
+
|
|
76
85
|
```typescript
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
quality: 85, // optional
|
|
87
|
-
}
|
|
88
|
-
);
|
|
86
|
+
import { IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';
|
|
87
|
+
|
|
88
|
+
media.getImageUrl('avatar/image/usr-1/photo', {
|
|
89
|
+
extension: IMAGE_FORMAT.WEBP,
|
|
90
|
+
width: 400,
|
|
91
|
+
height: 300,
|
|
92
|
+
resizeType: IMAGE_RESIZE_TYPE.FILL,
|
|
93
|
+
quality: 85,
|
|
94
|
+
});
|
|
89
95
|
// → "https://cdn.example.com/avatar/image/usr-1/photo-400x300-fill-q85.webp"
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
####
|
|
98
|
+
#### ImageUrlOptions
|
|
93
99
|
|
|
94
|
-
| Property | Type | Required | Description
|
|
95
|
-
| ------------ | ----------------- | -------- |
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `quality` | `number` | No | Quality 1
|
|
100
|
+
| Property | Type | Required | Description |
|
|
101
|
+
| ------------ | ----------------- | -------- | -------------------------------------- |
|
|
102
|
+
| `extension` | `ImageFormat` | Yes | Output format (`webp`, `avif`, `jpeg`, `png`, `gif`, `ico`, `svg`, `jpg`) |
|
|
103
|
+
| `width` | `number` | Yes | Target width in pixels (1–4096) |
|
|
104
|
+
| `height` | `number` | Yes | Target height in pixels (1–4096) |
|
|
105
|
+
| `resizeType` | `ImageResizeType` | Yes | Resize strategy: `fit`, `fill`, `force`, `fill-down`, `auto` |
|
|
106
|
+
| `quality` | `number` | No | Quality 1–100 |
|
|
101
107
|
|
|
102
|
-
### `
|
|
108
|
+
### `media.getVideoHlsUrl(objectKey: string): string`
|
|
103
109
|
|
|
104
|
-
Build an HLS playlist URL from an
|
|
110
|
+
Build an HLS playlist URL from an object key (without extension).
|
|
105
111
|
|
|
106
112
|
```typescript
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const hlsUrl = buildVideoHlsUrl(
|
|
110
|
-
'https://cdn.example.com/gallery/video/usr-1/video.mp4'
|
|
111
|
-
);
|
|
113
|
+
media.getVideoHlsUrl('gallery/video/usr-1/video');
|
|
112
114
|
// → "https://cdn.example.com/gallery/video/usr-1/video/playlist.m3u8"
|
|
113
115
|
```
|
|
114
116
|
|
|
115
|
-
### `
|
|
117
|
+
### `media.getVideoThumbnailUrl(objectKey: string): string`
|
|
116
118
|
|
|
117
|
-
Build a thumbnail URL from an
|
|
119
|
+
Build a thumbnail URL from an object key (without extension).
|
|
118
120
|
|
|
119
121
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const thumbnailUrl = buildVideoThumbnailUrl(
|
|
123
|
-
'https://cdn.example.com/gallery/video/usr-1/video.mp4'
|
|
124
|
-
);
|
|
122
|
+
media.getVideoThumbnailUrl('gallery/video/usr-1/video');
|
|
125
123
|
// → "https://cdn.example.com/gallery/video/usr-1/video/thumbnail.webp"
|
|
126
124
|
```
|
|
127
125
|
|
|
128
|
-
|
|
126
|
+
## Enums
|
|
129
127
|
|
|
130
|
-
|
|
128
|
+
Use the provided enums for type-safe image options:
|
|
131
129
|
|
|
132
130
|
```typescript
|
|
133
|
-
import {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
//
|
|
131
|
+
import { IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';
|
|
132
|
+
|
|
133
|
+
// IMAGE_FORMAT
|
|
134
|
+
IMAGE_FORMAT.WEBP // 'webp'
|
|
135
|
+
IMAGE_FORMAT.AVIF // 'avif'
|
|
136
|
+
IMAGE_FORMAT.JPEG // 'jpeg'
|
|
137
|
+
IMAGE_FORMAT.PNG // 'png'
|
|
138
|
+
IMAGE_FORMAT.GIF // 'gif'
|
|
139
|
+
IMAGE_FORMAT.ICO // 'ico'
|
|
140
|
+
IMAGE_FORMAT.SVG // 'svg'
|
|
141
|
+
IMAGE_FORMAT.JPG // 'jpg'
|
|
142
|
+
|
|
143
|
+
// IMAGE_RESIZE_TYPE
|
|
144
|
+
IMAGE_RESIZE_TYPE.FIT // 'fit'
|
|
145
|
+
IMAGE_RESIZE_TYPE.FILL // 'fill'
|
|
146
|
+
IMAGE_RESIZE_TYPE.FORCE // 'force'
|
|
147
|
+
IMAGE_RESIZE_TYPE.FILL_DOWN // 'fill-down'
|
|
148
|
+
IMAGE_RESIZE_TYPE.AUTO // 'auto'
|
|
140
149
|
```
|
|
141
150
|
|
|
142
151
|
## Constants
|
|
143
152
|
|
|
144
|
-
The SDK exports constants for type-safe usage:
|
|
145
|
-
|
|
146
153
|
```typescript
|
|
147
154
|
import {
|
|
148
|
-
MEDIA_TYPE,
|
|
149
|
-
MEDIA_PURPOSE,
|
|
150
|
-
MEDIA_STATUS,
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
MEDIA_TYPE, // { IMAGE: 'image', VIDEO: 'video' }
|
|
156
|
+
MEDIA_PURPOSE, // { AVATAR: 'avatar', HERO: 'hero', GALLERY: 'gallery' }
|
|
157
|
+
MEDIA_STATUS, // { PENDING, UPLOADED, PROCESSING, READY, ERROR }
|
|
158
|
+
R2_PATH, // { HLS_PLAYLIST: 'playlist.m3u8', THUMBNAIL: 'thumbnail.webp' }
|
|
159
|
+
MEDIA_DEFAULTS, // { IMAGE_FORMAT: 'webp' }
|
|
153
160
|
} from '@sproux/media-sdk';
|
|
154
161
|
```
|
|
155
162
|
|
|
@@ -157,50 +164,35 @@ import {
|
|
|
157
164
|
|
|
158
165
|
```typescript
|
|
159
166
|
import type {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
ImageResizeType, // 'fit' | 'fill' | 'auto'
|
|
165
|
-
ParsedMediaUrl,
|
|
166
|
-
ImageVariantOptions,
|
|
167
|
+
ImageFormat, // 'webp' | 'avif' | 'jpeg' | 'png' | 'gif' | 'ico' | 'svg' | 'jpg'
|
|
168
|
+
ImageResizeType, // 'fit' | 'fill' | 'force' | 'fill-down' | 'auto'
|
|
169
|
+
SprouxMediaConfig, // { cdnUrl: string }
|
|
170
|
+
ImageUrlOptions, // { extension, width, height, resizeType, quality? }
|
|
167
171
|
} from '@sproux/media-sdk';
|
|
168
172
|
```
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
This SDK expects media URLs to follow this structure:
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
{cdnBase}/{purpose}/{type}/{userId}/{filename}
|
|
176
|
-
```
|
|
174
|
+
> `ImageFormat` and `ImageResizeType` are string union types derived from their respective enums. You can use either the enum values (`IMAGE_FORMAT.WEBP`) or raw strings (`'webp'`).
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
| ---------- | ----------------------------------------- | --------------------------------- |
|
|
180
|
-
| `cdnBase` | CDN origin including protocol | Any valid URL origin |
|
|
181
|
-
| `purpose` | Media use case | `avatar`, `hero`, `gallery` |
|
|
182
|
-
| `type` | Media type | `image`, `video` |
|
|
183
|
-
| `userId` | User identifier | Any string |
|
|
184
|
-
| `filename` | File name with extension | `{name}.{extension}` |
|
|
176
|
+
## URL Structure
|
|
185
177
|
|
|
186
|
-
### Image Variant URL
|
|
178
|
+
### Image Variant URL
|
|
187
179
|
|
|
188
180
|
```
|
|
189
|
-
{
|
|
181
|
+
{cdnUrl}/{objectKey}-{width}x{height}-{resizeType}[-q{quality}].{extension}
|
|
190
182
|
```
|
|
191
183
|
|
|
192
184
|
Example: `https://cdn.example.com/avatar/image/usr-1/photo-200x200-fit-q80.webp`
|
|
193
185
|
|
|
194
|
-
### Video HLS URL
|
|
186
|
+
### Video HLS URL
|
|
195
187
|
|
|
196
188
|
```
|
|
197
|
-
{
|
|
189
|
+
{cdnUrl}/{objectKey}/playlist.m3u8
|
|
198
190
|
```
|
|
199
191
|
|
|
200
|
-
### Video Thumbnail URL
|
|
192
|
+
### Video Thumbnail URL
|
|
201
193
|
|
|
202
194
|
```
|
|
203
|
-
{
|
|
195
|
+
{cdnUrl}/{objectKey}/thumbnail.webp
|
|
204
196
|
```
|
|
205
197
|
|
|
206
198
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -27,11 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
MEDIA_STATUS: () => MEDIA_STATUS,
|
|
28
28
|
MEDIA_TYPE: () => MEDIA_TYPE,
|
|
29
29
|
R2_PATH: () => R2_PATH,
|
|
30
|
-
|
|
31
|
-
buildVariantString: () => buildVariantString,
|
|
32
|
-
buildVideoHlsUrl: () => buildVideoHlsUrl,
|
|
33
|
-
buildVideoThumbnailUrl: () => buildVideoThumbnailUrl,
|
|
34
|
-
parseMediaUrl: () => parseMediaUrl
|
|
30
|
+
SprouxMedia: () => SprouxMedia
|
|
35
31
|
});
|
|
36
32
|
module.exports = __toCommonJS(index_exports);
|
|
37
33
|
|
|
@@ -52,136 +48,126 @@ var MEDIA_STATUS = {
|
|
|
52
48
|
READY: "ready",
|
|
53
49
|
ERROR: "error"
|
|
54
50
|
};
|
|
55
|
-
var IMAGE_FORMAT = {
|
|
56
|
-
WEBP
|
|
57
|
-
AVIF
|
|
58
|
-
JPEG
|
|
59
|
-
PNG
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
var IMAGE_FORMAT = /* @__PURE__ */ ((IMAGE_FORMAT2) => {
|
|
52
|
+
IMAGE_FORMAT2["WEBP"] = "webp";
|
|
53
|
+
IMAGE_FORMAT2["AVIF"] = "avif";
|
|
54
|
+
IMAGE_FORMAT2["JPEG"] = "jpeg";
|
|
55
|
+
IMAGE_FORMAT2["PNG"] = "png";
|
|
56
|
+
IMAGE_FORMAT2["GIF"] = "gif";
|
|
57
|
+
IMAGE_FORMAT2["ICO"] = "ico";
|
|
58
|
+
IMAGE_FORMAT2["SVG"] = "svg";
|
|
59
|
+
IMAGE_FORMAT2["JPG"] = "jpg";
|
|
60
|
+
return IMAGE_FORMAT2;
|
|
61
|
+
})(IMAGE_FORMAT || {});
|
|
62
|
+
var IMAGE_RESIZE_TYPE = /* @__PURE__ */ ((IMAGE_RESIZE_TYPE2) => {
|
|
63
|
+
IMAGE_RESIZE_TYPE2["FIT"] = "fit";
|
|
64
|
+
IMAGE_RESIZE_TYPE2["FILL"] = "fill";
|
|
65
|
+
IMAGE_RESIZE_TYPE2["FORCE"] = "force";
|
|
66
|
+
IMAGE_RESIZE_TYPE2["FILL_DOWN"] = "fill-down";
|
|
67
|
+
IMAGE_RESIZE_TYPE2["AUTO"] = "auto";
|
|
68
|
+
return IMAGE_RESIZE_TYPE2;
|
|
69
|
+
})(IMAGE_RESIZE_TYPE || {});
|
|
67
70
|
var R2_PATH = {
|
|
68
71
|
HLS_PLAYLIST: "playlist.m3u8",
|
|
69
72
|
THUMBNAIL: "thumbnail.webp"
|
|
70
73
|
};
|
|
71
74
|
var MEDIA_DEFAULTS = {
|
|
72
|
-
IMAGE_FORMAT:
|
|
75
|
+
IMAGE_FORMAT: "webp" /* WEBP */
|
|
73
76
|
};
|
|
74
77
|
|
|
75
|
-
// src/
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
78
|
+
// src/sproux-media.ts
|
|
79
|
+
var SprouxMedia = class _SprouxMedia {
|
|
80
|
+
static instance = null;
|
|
81
|
+
cdnUrl;
|
|
82
|
+
constructor(config) {
|
|
83
|
+
this.cdnUrl = config.cdnUrl.replace(/\/+$/, "");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Initialize the singleton with CDN configuration.
|
|
87
|
+
* Returns the singleton instance.
|
|
88
|
+
*/
|
|
89
|
+
static init(config) {
|
|
90
|
+
if (_SprouxMedia.instance) {
|
|
91
|
+
return _SprouxMedia.instance;
|
|
92
|
+
}
|
|
93
|
+
return _SprouxMedia.instance = new _SprouxMedia(config);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns the existing singleton instance.
|
|
97
|
+
* Throws if `init()` has not been called.
|
|
98
|
+
*/
|
|
99
|
+
static getInstance() {
|
|
100
|
+
if (!_SprouxMedia.instance) {
|
|
101
|
+
throw new Error("SprouxMedia has not been initialized. Call SprouxMedia.init() first.");
|
|
102
|
+
}
|
|
103
|
+
return _SprouxMedia.instance;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a CDN URL for an image variant.
|
|
107
|
+
*
|
|
108
|
+
* @param objectKey - Object key without extension (e.g. "avatar/image/usr-1/abc")
|
|
109
|
+
* @param options - Image variant options including extension, dimensions, resize type, and optional quality
|
|
110
|
+
* @returns Full CDN URL for the image variant
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* media.getImageUrl('avatar/image/usr-1/abc', {
|
|
114
|
+
* extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,
|
|
115
|
+
* });
|
|
116
|
+
* // → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
117
|
+
*/
|
|
118
|
+
getImageUrl(objectKey, options) {
|
|
119
|
+
this.validateImageOptions(options);
|
|
120
|
+
const variant = this.buildVariantString(options);
|
|
121
|
+
return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Build a CDN URL for a video HLS playlist.
|
|
125
|
+
*
|
|
126
|
+
* @param objectKey - Object key without extension (e.g. "gallery/video/usr-1/xyz")
|
|
127
|
+
* @returns Full CDN URL for the HLS playlist
|
|
128
|
+
*/
|
|
129
|
+
getVideoHlsUrl(objectKey) {
|
|
130
|
+
return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Build a CDN URL for a video thumbnail.
|
|
134
|
+
*
|
|
135
|
+
* @param objectKey - Object key without extension (e.g. "gallery/video/usr-1/xyz")
|
|
136
|
+
* @returns Full CDN URL for the thumbnail
|
|
137
|
+
*/
|
|
138
|
+
getVideoThumbnailUrl(objectKey) {
|
|
139
|
+
return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build a deterministic variant string from image options.
|
|
143
|
+
*
|
|
144
|
+
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
145
|
+
*/
|
|
146
|
+
buildVariantString(options) {
|
|
147
|
+
const parts = [`${options.width}x${options.height}`, options.resizeType];
|
|
148
|
+
if (options.quality != null) {
|
|
149
|
+
parts.push(`q${options.quality}`);
|
|
150
|
+
}
|
|
151
|
+
return parts.join("-");
|
|
147
152
|
}
|
|
148
|
-
|
|
149
|
-
if (!Number.isInteger(options.
|
|
153
|
+
validateImageOptions(options) {
|
|
154
|
+
if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
|
|
155
|
+
throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
|
|
156
|
+
}
|
|
157
|
+
if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
|
|
150
158
|
throw new Error(
|
|
151
|
-
`Invalid
|
|
159
|
+
`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`
|
|
152
160
|
);
|
|
153
161
|
}
|
|
162
|
+
if (options.quality != null) {
|
|
163
|
+
if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
154
169
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// src/build-image-variant-url.ts
|
|
158
|
-
function buildImageVariantUrl(originalUrl, options) {
|
|
159
|
-
const parsed = parseMediaUrl(originalUrl);
|
|
160
|
-
if (parsed.type !== "image") {
|
|
161
|
-
throw new Error(`Expected image URL, got type "${parsed.type}" in "${originalUrl}"`);
|
|
162
|
-
}
|
|
163
|
-
const variant = buildVariantString(options);
|
|
164
|
-
const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;
|
|
165
|
-
return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// src/build-video-hls-url.ts
|
|
169
|
-
function buildVideoHlsUrl(originalUrl) {
|
|
170
|
-
const parsed = parseMediaUrl(originalUrl);
|
|
171
|
-
if (parsed.type !== "video") {
|
|
172
|
-
throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
|
|
173
|
-
}
|
|
174
|
-
return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// src/build-video-thumbnail-url.ts
|
|
178
|
-
function buildVideoThumbnailUrl(originalUrl) {
|
|
179
|
-
const parsed = parseMediaUrl(originalUrl);
|
|
180
|
-
if (parsed.type !== "video") {
|
|
181
|
-
throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
|
|
182
|
-
}
|
|
183
|
-
return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;
|
|
184
|
-
}
|
|
170
|
+
};
|
|
185
171
|
// Annotate the CommonJS export names for ESM import in node:
|
|
186
172
|
0 && (module.exports = {
|
|
187
173
|
IMAGE_FORMAT,
|
|
@@ -191,10 +177,6 @@ function buildVideoThumbnailUrl(originalUrl) {
|
|
|
191
177
|
MEDIA_STATUS,
|
|
192
178
|
MEDIA_TYPE,
|
|
193
179
|
R2_PATH,
|
|
194
|
-
|
|
195
|
-
buildVariantString,
|
|
196
|
-
buildVideoHlsUrl,
|
|
197
|
-
buildVideoThumbnailUrl,
|
|
198
|
-
parseMediaUrl
|
|
180
|
+
SprouxMedia
|
|
199
181
|
});
|
|
200
182
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/parse-media-url.ts","../src/build-variant-string.ts","../src/build-image-variant-url.ts","../src/build-video-hls-url.ts","../src/build-video-thumbnail-url.ts"],"sourcesContent":["// Constants\r\nexport {\r\n MEDIA_TYPE,\r\n MEDIA_PURPOSE,\r\n MEDIA_STATUS,\r\n IMAGE_FORMAT,\r\n IMAGE_RESIZE_TYPE,\r\n R2_PATH,\r\n MEDIA_DEFAULTS,\r\n} from './constants';\r\n\r\n// Types\r\nexport type {\r\n MediaType,\r\n MediaPurpose,\r\n MediaStatus,\r\n ImageFormat,\r\n ImageResizeType,\r\n ParsedMediaUrl,\r\n ImageVariantOptions,\r\n} from './types';\r\n\r\n// Functions\r\nexport { parseMediaUrl } from './parse-media-url';\r\nexport { buildVariantString } from './build-variant-string';\r\nexport { buildImageVariantUrl } from './build-image-variant-url';\r\nexport { buildVideoHlsUrl } from './build-video-hls-url';\r\nexport { buildVideoThumbnailUrl } from './build-video-thumbnail-url';\r\n","// ============================================================================\r\n// MEDIA TYPE\r\n// ============================================================================\r\n\r\nexport const MEDIA_TYPE = {\r\n IMAGE: 'image',\r\n VIDEO: 'video',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA PURPOSE\r\n// ============================================================================\r\n\r\nexport const MEDIA_PURPOSE = {\r\n AVATAR: 'avatar',\r\n HERO: 'hero',\r\n GALLERY: 'gallery',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA STATUS\r\n// ============================================================================\r\n\r\nexport const MEDIA_STATUS = {\r\n PENDING: 'pending',\r\n UPLOADED: 'uploaded',\r\n PROCESSING: 'processing',\r\n READY: 'ready',\r\n ERROR: 'error',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE FORMAT\r\n// ============================================================================\r\n\r\nexport const IMAGE_FORMAT = {\r\n WEBP: 'webp',\r\n AVIF: 'avif',\r\n JPEG: 'jpeg',\r\n PNG: 'png',\r\n JPG: 'jpg',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport const IMAGE_RESIZE_TYPE = {\r\n FIT: 'fit',\r\n FILL: 'fill',\r\n AUTO: 'auto',\r\n} as const;\r\n\r\n// ============================================================================\r\n// R2/CDN PATH CONSTANTS\r\n// ============================================================================\r\n\r\nexport const R2_PATH = {\r\n HLS_PLAYLIST: 'playlist.m3u8',\r\n THUMBNAIL: 'thumbnail.webp',\r\n} as const;\r\n\r\n// ============================================================================\r\n// DEFAULTS\r\n// ============================================================================\r\n\r\nexport const MEDIA_DEFAULTS = {\r\n IMAGE_FORMAT: IMAGE_FORMAT.WEBP,\r\n} as const;\r\n","import { MEDIA_PURPOSE, MEDIA_TYPE } from './constants';\r\nimport type { ParsedMediaUrl, MediaPurpose, MediaType } from './types';\r\n\r\nconst VALID_PURPOSES = new Set<string>(Object.values(MEDIA_PURPOSE));\r\nconst VALID_TYPES = new Set<string>(Object.values(MEDIA_TYPE));\r\n\r\n/**\r\n * Parse a full media URL into structured components.\r\n *\r\n * Expected URL pattern:\r\n * {cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}\r\n *\r\n * Example:\r\n * https://cdn.example.com/avatar/image/usr-1/abc.jpg\r\n * → { cdnBase: \"https://cdn.example.com\", purpose: \"avatar\", type: \"image\", userId: \"usr-1\", name: \"abc\", extension: \"jpg\" }\r\n */\r\nexport function parseMediaUrl(originalUrl: string): ParsedMediaUrl {\r\n let url: URL;\r\n try {\r\n url = new URL(originalUrl);\r\n } catch {\r\n throw new Error(`Invalid media URL: \"${originalUrl}\"`);\r\n }\r\n\r\n // Remove leading slash and split into segments\r\n const pathname = url.pathname.startsWith('/') ? url.pathname.slice(1) : url.pathname;\r\n const segments = pathname.split('/');\r\n\r\n if (segments.length < 4) {\r\n throw new Error(\r\n `Invalid media URL path: expected at least 4 segments (purpose/type/userId/filename), got ${segments.length} in \"${pathname}\"`,\r\n );\r\n }\r\n\r\n const [purpose, type, userId, filename] = segments as [string, string, string, string];\r\n\r\n if (!VALID_PURPOSES.has(purpose)) {\r\n throw new Error(\r\n `Invalid media purpose: \"${purpose}\". Expected one of: ${[...VALID_PURPOSES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!VALID_TYPES.has(type)) {\r\n throw new Error(\r\n `Invalid media type: \"${type}\". Expected one of: ${[...VALID_TYPES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!userId) {\r\n throw new Error('Missing userId segment in media URL');\r\n }\r\n\r\n if (!filename) {\r\n throw new Error('Missing filename segment in media URL');\r\n }\r\n\r\n const dotIndex = filename.lastIndexOf('.');\r\n if (dotIndex <= 0) {\r\n throw new Error(`Invalid filename: \"${filename}\". Expected format: \"name.extension\"`);\r\n }\r\n\r\n const name = filename.slice(0, dotIndex);\r\n const extension = filename.slice(dotIndex + 1);\r\n\r\n if (!extension) {\r\n throw new Error(`Missing file extension in filename: \"${filename}\"`);\r\n }\r\n\r\n const cdnBase = `${url.protocol}//${url.host}`;\r\n const objectKey = pathname;\r\n\r\n return {\r\n originalUrl,\r\n cdnBase,\r\n objectKey,\r\n purpose: purpose as MediaPurpose,\r\n type: type as MediaType,\r\n userId,\r\n name,\r\n extension,\r\n };\r\n}\r\n","import type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a deterministic variant string from image variant options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n *\r\n * Examples:\r\n * { width: 200, height: 200, resizeType: 'fit' } → \"200x200-fit\"\r\n * { width: 200, height: 200, resizeType: 'fit', quality: 80 } → \"200x200-fit-q80\"\r\n */\r\nexport function buildVariantString(options: ImageVariantOptions): string {\r\n validateVariantOptions(options);\r\n\r\n const parts: string[] = [`${options.width}x${options.height}`, options.resizeType];\r\n\r\n if (options.quality != null) {\r\n parts.push(`q${options.quality}`);\r\n }\r\n\r\n return parts.join('-');\r\n}\r\n\r\nfunction validateVariantOptions(options: ImageVariantOptions): void {\r\n if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {\r\n throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (options.quality != null) {\r\n if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {\r\n throw new Error(\r\n `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`,\r\n );\r\n }\r\n }\r\n}\r\n","import { MEDIA_DEFAULTS } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\nimport { buildVariantString } from './build-variant-string';\r\nimport type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a full CDN URL for an image variant.\r\n *\r\n * Takes an original image URL and variant options, returns the CDN URL\r\n * for that specific variant.\r\n *\r\n * Example:\r\n * buildImageVariantUrl(\"https://cdn.example.com/avatar/image/usr-1/abc.jpg\", {\r\n * width: 200, height: 200, resizeType: 'fit', quality: 80\r\n * })\r\n * → \"https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp\"\r\n */\r\nexport function buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'image') {\r\n throw new Error(`Expected image URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n const variant = buildVariantString(options);\r\n const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build HLS playlist URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoHlsUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/playlist.m3u8\"\r\n */\r\nexport function buildVideoHlsUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build thumbnail URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoThumbnailUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/thumbnail.webp\"\r\n */\r\nexport function buildVideoThumbnailUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/sproux-media.ts"],"sourcesContent":["// Constants\r\nexport {\r\n MEDIA_TYPE,\r\n MEDIA_PURPOSE,\r\n MEDIA_STATUS,\r\n IMAGE_FORMAT,\r\n IMAGE_RESIZE_TYPE,\r\n R2_PATH,\r\n MEDIA_DEFAULTS,\r\n} from './constants';\r\n\r\n// Types\r\nexport type { ImageFormat, ImageResizeType, SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\n// Singleton class\r\nexport { SprouxMedia } from './sproux-media';\r\n","// ============================================================================\r\n// MEDIA TYPE\r\n// ============================================================================\r\n\r\nexport const MEDIA_TYPE = {\r\n IMAGE: 'image',\r\n VIDEO: 'video',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA PURPOSE\r\n// ============================================================================\r\n\r\nexport const MEDIA_PURPOSE = {\r\n AVATAR: 'avatar',\r\n HERO: 'hero',\r\n GALLERY: 'gallery',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA STATUS\r\n// ============================================================================\r\n\r\nexport const MEDIA_STATUS = {\r\n PENDING: 'pending',\r\n UPLOADED: 'uploaded',\r\n PROCESSING: 'processing',\r\n READY: 'ready',\r\n ERROR: 'error',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE FORMAT\r\n// ============================================================================\r\n\r\nexport enum IMAGE_FORMAT {\r\n WEBP = 'webp',\r\n AVIF = 'avif',\r\n JPEG = 'jpeg',\r\n PNG = 'png',\r\n GIF = 'gif',\r\n ICO = 'ico',\r\n SVG = 'svg',\r\n JPG = 'jpg',\r\n}\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport enum IMAGE_RESIZE_TYPE {\r\n FIT = 'fit',\r\n FILL = 'fill',\r\n FORCE = 'force',\r\n FILL_DOWN = 'fill-down',\r\n AUTO = 'auto',\r\n}\r\n\r\n// ============================================================================\r\n// R2/CDN PATH CONSTANTS\r\n// ============================================================================\r\n\r\nexport const R2_PATH = {\r\n HLS_PLAYLIST: 'playlist.m3u8',\r\n THUMBNAIL: 'thumbnail.webp',\r\n} as const;\r\n\r\n// ============================================================================\r\n// DEFAULTS\r\n// ============================================================================\r\n\r\nexport const MEDIA_DEFAULTS = {\r\n IMAGE_FORMAT: IMAGE_FORMAT.WEBP,\r\n} as const;\r\n","import { R2_PATH } from './constants';\r\nimport type { SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\nexport class SprouxMedia {\r\n private static instance: SprouxMedia | null = null;\r\n\r\n private readonly cdnUrl: string;\r\n\r\n private constructor(config: SprouxMediaConfig) {\r\n this.cdnUrl = config.cdnUrl.replace(/\\/+$/, '');\r\n }\r\n\r\n /**\r\n * Initialize the singleton with CDN configuration.\r\n * Returns the singleton instance.\r\n */\r\n static init(config: SprouxMediaConfig): SprouxMedia {\r\n if (SprouxMedia.instance) {\r\n return SprouxMedia.instance;\r\n }\r\n \r\n return (SprouxMedia.instance = new SprouxMedia(config));\r\n }\r\n\r\n /**\r\n * Returns the existing singleton instance.\r\n * Throws if `init()` has not been called.\r\n */\r\n static getInstance(): SprouxMedia {\r\n if (!SprouxMedia.instance) {\r\n throw new Error('SprouxMedia has not been initialized. Call SprouxMedia.init() first.');\r\n }\r\n return SprouxMedia.instance;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for an image variant.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"avatar/image/usr-1/abc\")\r\n * @param options - Image variant options including extension, dimensions, resize type, and optional quality\r\n * @returns Full CDN URL for the image variant\r\n *\r\n * @example\r\n * media.getImageUrl('avatar/image/usr-1/abc', {\r\n * extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,\r\n * });\r\n * // → \"https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp\"\r\n */\r\n getImageUrl(objectKey: string, options: ImageUrlOptions): string {\r\n this.validateImageOptions(options);\r\n const variant = this.buildVariantString(options);\r\n return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video HLS playlist.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the HLS playlist\r\n */\r\n getVideoHlsUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video thumbnail.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the thumbnail\r\n */\r\n getVideoThumbnailUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;\r\n }\r\n\r\n /**\r\n * Build a deterministic variant string from image options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n */\r\n private buildVariantString(options: ImageUrlOptions): string {\r\n const parts: string[] = [`${options.width}x${options.height}`, options.resizeType];\r\n\r\n if (options.quality != null) {\r\n parts.push(`q${options.quality}`);\r\n }\r\n\r\n return parts.join('-');\r\n }\r\n\r\n private validateImageOptions(options: ImageUrlOptions): void {\r\n if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {\r\n throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(\r\n `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`,\r\n );\r\n }\r\n\r\n if (options.quality != null) {\r\n if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {\r\n throw new Error(\r\n `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`,\r\n );\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
|