bare-media 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,13 +49,36 @@ const data = await createPreview({ path, maxWidth, maxHeight })
49
49
 
50
50
  ## API
51
51
 
52
- | Method | Parameters | Return Value | Description |
53
- | --------------- | ------------------------------------------------------- | ------------------- | ---------------------------------- |
54
- | `createPreview` | `path, mimetype, maxWidth, maxHeight, format, encoding` | `metadata, preview` | Create a preview from a media file |
55
- | `decodeImage` | `path`, `httpLink, mimetype` | `metadata, data` | Decode an image to RGBA |
56
-
57
52
  > See [schema.js](shared/spec/schema.js) for the complete reference of parameters
58
53
 
54
+ ### createPreview()
55
+
56
+ Create a preview from a media file
57
+
58
+ | Property | Type | Description |
59
+ | ----------- | ------ | ------------------------------------------------------------------------- |
60
+ | `path` | string | Path to the input file. Either `path`, `httpLink` or `buffer` is required |
61
+ | `httpLink` | string | Http link to the input file |
62
+ | `buffer` | object | Bytes of the input file |
63
+ | `mimetype` | string | Media type of the input file. If not provided it will be detected |
64
+ | `maxWidth` | number | Max width for the generated preview |
65
+ | `maxHeight` | number | Max height for the generated preview |
66
+ | `maxFrames` | number | Max frames for the generated preview in case the file is animated |
67
+ | `maxBytes` | number | Max bytes for the generated preview |
68
+ | `format` | string | Media type for the generated preview. Default `image/webp` |
69
+ | `encoding` | string | `base64` or nothing for buffer |
70
+
71
+ ### decodeImage()
72
+
73
+ Decode an image to RGBA
74
+
75
+ | Property | Type | Description |
76
+ | ---------- | ------ | ------------------------------------------------------------------------- |
77
+ | `path` | string | Path to the input file. Either `path`, `httpLink` or `buffer` is required |
78
+ | `httpLink` | string | Http link to the input file |
79
+ | `buffer` | object | Bytes of the input file |
80
+ | `mimetype` | string | Media type of the input file. If not provided it will be detected |
81
+
59
82
  ## License
60
83
 
61
84
  Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-media",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
package/shared/codecs.js CHANGED
@@ -19,3 +19,7 @@ export async function importCodec(mimetype) {
19
19
  if (!codecImport) throw new Error(`No codec for ${mimetype}`)
20
20
  return await codecImport()
21
21
  }
22
+
23
+ export function supportsQuality(mimetype) {
24
+ return { 'image/webp': true, 'image/jpeg': true }[mimetype] || false
25
+ }
@@ -1,5 +1,6 @@
1
1
  // This file is autogenerated by the hrpc compiler
2
2
  /* eslint-disable camelcase */
3
+ /* eslint-disable space-before-function-paren */
3
4
 
4
5
  import { c, RPC, RPCStream, RPCRequestStream } from 'hrpc/runtime'
5
6
  import { getEncoding } from './messages.js'
@@ -140,8 +141,11 @@ class HRPC {
140
141
  return [].includes(command)
141
142
  }
142
143
 
144
+ // prettier-ignore-start
143
145
  _requestIsSend(command) {
144
- return [].includes(command)
146
+ return [
147
+ // prettier-ignore
148
+ ].includes(command)
145
149
  }
146
150
  }
147
151
 
@@ -100,43 +100,71 @@ const encoding2 = {
100
100
  // @media/create-preview-request
101
101
  const encoding3 = {
102
102
  preencode(state, m) {
103
- c.string.preencode(state, m.path)
104
- state.end++ // max flag is 16 so always one byte
103
+ const flags =
104
+ (m.path ? 1 : 0) |
105
+ (m.httpLink ? 2 : 0) |
106
+ (m.buffer ? 4 : 0) |
107
+ (m.mimetype ? 8 : 0) |
108
+ (m.maxWidth ? 16 : 0) |
109
+ (m.maxHeight ? 32 : 0) |
110
+ (m.maxFrames ? 64 : 0) |
111
+ (m.maxBytes ? 128 : 0) |
112
+ (m.format ? 256 : 0) |
113
+ (m.encoding ? 512 : 0)
114
+
115
+ c.uint.preencode(state, flags)
105
116
 
117
+ if (m.path) c.string.preencode(state, m.path)
118
+ if (m.httpLink) c.string.preencode(state, m.httpLink)
119
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
106
120
  if (m.mimetype) c.string.preencode(state, m.mimetype)
107
121
  if (m.maxWidth) c.uint.preencode(state, m.maxWidth)
108
122
  if (m.maxHeight) c.uint.preencode(state, m.maxHeight)
123
+ if (m.maxFrames) c.uint.preencode(state, m.maxFrames)
124
+ if (m.maxBytes) c.uint.preencode(state, m.maxBytes)
109
125
  if (m.format) c.string.preencode(state, m.format)
110
126
  if (m.encoding) c.string.preencode(state, m.encoding)
111
127
  },
112
128
  encode(state, m) {
113
129
  const flags =
114
- (m.mimetype ? 1 : 0) |
115
- (m.maxWidth ? 2 : 0) |
116
- (m.maxHeight ? 4 : 0) |
117
- (m.format ? 8 : 0) |
118
- (m.encoding ? 16 : 0)
130
+ (m.path ? 1 : 0) |
131
+ (m.httpLink ? 2 : 0) |
132
+ (m.buffer ? 4 : 0) |
133
+ (m.mimetype ? 8 : 0) |
134
+ (m.maxWidth ? 16 : 0) |
135
+ (m.maxHeight ? 32 : 0) |
136
+ (m.maxFrames ? 64 : 0) |
137
+ (m.maxBytes ? 128 : 0) |
138
+ (m.format ? 256 : 0) |
139
+ (m.encoding ? 512 : 0)
119
140
 
120
- c.string.encode(state, m.path)
121
141
  c.uint.encode(state, flags)
122
142
 
143
+ if (m.path) c.string.encode(state, m.path)
144
+ if (m.httpLink) c.string.encode(state, m.httpLink)
145
+ if (m.buffer) c.buffer.encode(state, m.buffer)
123
146
  if (m.mimetype) c.string.encode(state, m.mimetype)
124
147
  if (m.maxWidth) c.uint.encode(state, m.maxWidth)
125
148
  if (m.maxHeight) c.uint.encode(state, m.maxHeight)
149
+ if (m.maxFrames) c.uint.encode(state, m.maxFrames)
150
+ if (m.maxBytes) c.uint.encode(state, m.maxBytes)
126
151
  if (m.format) c.string.encode(state, m.format)
127
152
  if (m.encoding) c.string.encode(state, m.encoding)
128
153
  },
129
154
  decode(state) {
130
- const r0 = c.string.decode(state)
131
155
  const flags = c.uint.decode(state)
132
156
 
133
157
  return {
134
- path: r0,
135
- mimetype: (flags & 1) !== 0 ? c.string.decode(state) : null,
136
- maxWidth: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
137
- maxHeight: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
138
- format: (flags & 8) !== 0 ? c.string.decode(state) : null,
139
- encoding: (flags & 16) !== 0 ? c.string.decode(state) : null
158
+ path: (flags & 1) !== 0 ? c.string.decode(state) : null,
159
+ httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
160
+ buffer: (flags & 4) !== 0 ? c.buffer.decode(state) : null,
161
+ mimetype: (flags & 8) !== 0 ? c.string.decode(state) : null,
162
+ maxWidth: (flags & 16) !== 0 ? c.uint.decode(state) : 0,
163
+ maxHeight: (flags & 32) !== 0 ? c.uint.decode(state) : 0,
164
+ maxFrames: (flags & 64) !== 0 ? c.uint.decode(state) : 0,
165
+ maxBytes: (flags & 128) !== 0 ? c.uint.decode(state) : 0,
166
+ format: (flags & 256) !== 0 ? c.string.decode(state) : null,
167
+ encoding: (flags & 512) !== 0 ? c.string.decode(state) : null
140
168
  }
141
169
  }
142
170
  }
@@ -170,19 +198,25 @@ const encoding4 = {
170
198
  // @media/decode-image-request
171
199
  const encoding5 = {
172
200
  preencode(state, m) {
173
- state.end++ // max flag is 4 so always one byte
201
+ state.end++ // max flag is 8 so always one byte
174
202
 
175
203
  if (m.path) c.string.preencode(state, m.path)
176
204
  if (m.httpLink) c.string.preencode(state, m.httpLink)
205
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
177
206
  if (m.mimetype) c.string.preencode(state, m.mimetype)
178
207
  },
179
208
  encode(state, m) {
180
- const flags = (m.path ? 1 : 0) | (m.httpLink ? 2 : 0) | (m.mimetype ? 4 : 0)
209
+ const flags =
210
+ (m.path ? 1 : 0) |
211
+ (m.httpLink ? 2 : 0) |
212
+ (m.buffer ? 4 : 0) |
213
+ (m.mimetype ? 8 : 0)
181
214
 
182
215
  c.uint.encode(state, flags)
183
216
 
184
217
  if (m.path) c.string.encode(state, m.path)
185
218
  if (m.httpLink) c.string.encode(state, m.httpLink)
219
+ if (m.buffer) c.buffer.encode(state, m.buffer)
186
220
  if (m.mimetype) c.string.encode(state, m.mimetype)
187
221
  },
188
222
  decode(state) {
@@ -191,7 +225,8 @@ const encoding5 = {
191
225
  return {
192
226
  path: (flags & 1) !== 0 ? c.string.decode(state) : null,
193
227
  httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
194
- mimetype: (flags & 4) !== 0 ? c.string.decode(state) : null
228
+ buffer: (flags & 4) !== 0 ? c.buffer.decode(state) : null,
229
+ mimetype: (flags & 8) !== 0 ? c.string.decode(state) : null
195
230
  }
196
231
  }
197
232
  }
@@ -100,43 +100,71 @@ const encoding2 = {
100
100
  // @media/create-preview-request
101
101
  const encoding3 = {
102
102
  preencode(state, m) {
103
- c.string.preencode(state, m.path)
104
- state.end++ // max flag is 16 so always one byte
103
+ const flags =
104
+ (m.path ? 1 : 0) |
105
+ (m.httpLink ? 2 : 0) |
106
+ (m.buffer ? 4 : 0) |
107
+ (m.mimetype ? 8 : 0) |
108
+ (m.maxWidth ? 16 : 0) |
109
+ (m.maxHeight ? 32 : 0) |
110
+ (m.maxFrames ? 64 : 0) |
111
+ (m.maxBytes ? 128 : 0) |
112
+ (m.format ? 256 : 0) |
113
+ (m.encoding ? 512 : 0)
114
+
115
+ c.uint.preencode(state, flags)
105
116
 
117
+ if (m.path) c.string.preencode(state, m.path)
118
+ if (m.httpLink) c.string.preencode(state, m.httpLink)
119
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
106
120
  if (m.mimetype) c.string.preencode(state, m.mimetype)
107
121
  if (m.maxWidth) c.uint.preencode(state, m.maxWidth)
108
122
  if (m.maxHeight) c.uint.preencode(state, m.maxHeight)
123
+ if (m.maxFrames) c.uint.preencode(state, m.maxFrames)
124
+ if (m.maxBytes) c.uint.preencode(state, m.maxBytes)
109
125
  if (m.format) c.string.preencode(state, m.format)
110
126
  if (m.encoding) c.string.preencode(state, m.encoding)
111
127
  },
112
128
  encode(state, m) {
113
129
  const flags =
114
- (m.mimetype ? 1 : 0) |
115
- (m.maxWidth ? 2 : 0) |
116
- (m.maxHeight ? 4 : 0) |
117
- (m.format ? 8 : 0) |
118
- (m.encoding ? 16 : 0)
130
+ (m.path ? 1 : 0) |
131
+ (m.httpLink ? 2 : 0) |
132
+ (m.buffer ? 4 : 0) |
133
+ (m.mimetype ? 8 : 0) |
134
+ (m.maxWidth ? 16 : 0) |
135
+ (m.maxHeight ? 32 : 0) |
136
+ (m.maxFrames ? 64 : 0) |
137
+ (m.maxBytes ? 128 : 0) |
138
+ (m.format ? 256 : 0) |
139
+ (m.encoding ? 512 : 0)
119
140
 
120
- c.string.encode(state, m.path)
121
141
  c.uint.encode(state, flags)
122
142
 
143
+ if (m.path) c.string.encode(state, m.path)
144
+ if (m.httpLink) c.string.encode(state, m.httpLink)
145
+ if (m.buffer) c.buffer.encode(state, m.buffer)
123
146
  if (m.mimetype) c.string.encode(state, m.mimetype)
124
147
  if (m.maxWidth) c.uint.encode(state, m.maxWidth)
125
148
  if (m.maxHeight) c.uint.encode(state, m.maxHeight)
149
+ if (m.maxFrames) c.uint.encode(state, m.maxFrames)
150
+ if (m.maxBytes) c.uint.encode(state, m.maxBytes)
126
151
  if (m.format) c.string.encode(state, m.format)
127
152
  if (m.encoding) c.string.encode(state, m.encoding)
128
153
  },
129
154
  decode(state) {
130
- const r0 = c.string.decode(state)
131
155
  const flags = c.uint.decode(state)
132
156
 
133
157
  return {
134
- path: r0,
135
- mimetype: (flags & 1) !== 0 ? c.string.decode(state) : null,
136
- maxWidth: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
137
- maxHeight: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
138
- format: (flags & 8) !== 0 ? c.string.decode(state) : null,
139
- encoding: (flags & 16) !== 0 ? c.string.decode(state) : null
158
+ path: (flags & 1) !== 0 ? c.string.decode(state) : null,
159
+ httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
160
+ buffer: (flags & 4) !== 0 ? c.buffer.decode(state) : null,
161
+ mimetype: (flags & 8) !== 0 ? c.string.decode(state) : null,
162
+ maxWidth: (flags & 16) !== 0 ? c.uint.decode(state) : 0,
163
+ maxHeight: (flags & 32) !== 0 ? c.uint.decode(state) : 0,
164
+ maxFrames: (flags & 64) !== 0 ? c.uint.decode(state) : 0,
165
+ maxBytes: (flags & 128) !== 0 ? c.uint.decode(state) : 0,
166
+ format: (flags & 256) !== 0 ? c.string.decode(state) : null,
167
+ encoding: (flags & 512) !== 0 ? c.string.decode(state) : null
140
168
  }
141
169
  }
142
170
  }
@@ -170,19 +198,25 @@ const encoding4 = {
170
198
  // @media/decode-image-request
171
199
  const encoding5 = {
172
200
  preencode(state, m) {
173
- state.end++ // max flag is 4 so always one byte
201
+ state.end++ // max flag is 8 so always one byte
174
202
 
175
203
  if (m.path) c.string.preencode(state, m.path)
176
204
  if (m.httpLink) c.string.preencode(state, m.httpLink)
205
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
177
206
  if (m.mimetype) c.string.preencode(state, m.mimetype)
178
207
  },
179
208
  encode(state, m) {
180
- const flags = (m.path ? 1 : 0) | (m.httpLink ? 2 : 0) | (m.mimetype ? 4 : 0)
209
+ const flags =
210
+ (m.path ? 1 : 0) |
211
+ (m.httpLink ? 2 : 0) |
212
+ (m.buffer ? 4 : 0) |
213
+ (m.mimetype ? 8 : 0)
181
214
 
182
215
  c.uint.encode(state, flags)
183
216
 
184
217
  if (m.path) c.string.encode(state, m.path)
185
218
  if (m.httpLink) c.string.encode(state, m.httpLink)
219
+ if (m.buffer) c.buffer.encode(state, m.buffer)
186
220
  if (m.mimetype) c.string.encode(state, m.mimetype)
187
221
  },
188
222
  decode(state) {
@@ -191,7 +225,8 @@ const encoding5 = {
191
225
  return {
192
226
  path: (flags & 1) !== 0 ? c.string.decode(state) : null,
193
227
  httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
194
- mimetype: (flags & 4) !== 0 ? c.string.decode(state) : null
228
+ buffer: (flags & 4) !== 0 ? c.buffer.decode(state) : null,
229
+ mimetype: (flags & 8) !== 0 ? c.string.decode(state) : null
195
230
  }
196
231
  }
197
232
  }
@@ -71,14 +71,23 @@
71
71
  "name": "create-preview-request",
72
72
  "namespace": "media",
73
73
  "compact": false,
74
- "flagsPosition": 1,
74
+ "flagsPosition": 0,
75
75
  "fields": [
76
76
  {
77
77
  "name": "path",
78
- "required": true,
79
78
  "type": "string",
80
79
  "version": 1
81
80
  },
81
+ {
82
+ "name": "httpLink",
83
+ "type": "string",
84
+ "version": 1
85
+ },
86
+ {
87
+ "name": "buffer",
88
+ "type": "buffer",
89
+ "version": 1
90
+ },
82
91
  {
83
92
  "name": "mimetype",
84
93
  "type": "string",
@@ -94,6 +103,16 @@
94
103
  "type": "uint",
95
104
  "version": 1
96
105
  },
106
+ {
107
+ "name": "maxFrames",
108
+ "type": "uint",
109
+ "version": 1
110
+ },
111
+ {
112
+ "name": "maxBytes",
113
+ "type": "uint",
114
+ "version": 1
115
+ },
97
116
  {
98
117
  "name": "format",
99
118
  "type": "string",
@@ -142,6 +161,11 @@
142
161
  "type": "string",
143
162
  "version": 1
144
163
  },
164
+ {
165
+ "name": "buffer",
166
+ "type": "buffer",
167
+ "version": 1
168
+ },
145
169
  {
146
170
  "name": "mimetype",
147
171
  "type": "string",
@@ -62,8 +62,15 @@ media.register({
62
62
  fields: [
63
63
  {
64
64
  name: 'path',
65
- type: 'string',
66
- required: true
65
+ type: 'string'
66
+ },
67
+ {
68
+ name: 'httpLink',
69
+ type: 'string'
70
+ },
71
+ {
72
+ name: 'buffer',
73
+ type: 'buffer'
67
74
  },
68
75
  {
69
76
  name: 'mimetype',
@@ -77,6 +84,14 @@ media.register({
77
84
  name: 'maxHeight',
78
85
  type: 'uint'
79
86
  },
87
+ {
88
+ name: 'maxFrames',
89
+ type: 'uint'
90
+ },
91
+ {
92
+ name: 'maxBytes',
93
+ type: 'uint'
94
+ },
80
95
  {
81
96
  name: 'format',
82
97
  type: 'string'
@@ -115,6 +130,10 @@ media.register({
115
130
  name: 'httpLink',
116
131
  type: 'string'
117
132
  },
133
+ {
134
+ name: 'buffer',
135
+ type: 'buffer'
136
+ },
118
137
  {
119
138
  name: 'mimetype',
120
139
  type: 'string'
package/worker/media.js CHANGED
@@ -3,7 +3,7 @@ import fs from 'bare-fs'
3
3
  import fetch from 'bare-fetch'
4
4
  import getMimeType from 'get-mime-type'
5
5
 
6
- import { importCodec } from '../shared/codecs.js'
6
+ import { importCodec, supportsQuality } from '../shared/codecs.js'
7
7
  import { calculateFitDimensions } from './util'
8
8
 
9
9
  const DEFAULT_PREVIEW_FORMAT = 'image/webp'
@@ -12,22 +12,92 @@ const animatableMimetypes = ['image/webp']
12
12
 
13
13
  export async function createPreview({
14
14
  path,
15
+ httpLink,
16
+ buffer,
15
17
  mimetype,
16
18
  maxWidth,
17
19
  maxHeight,
20
+ maxFrames,
21
+ maxBytes,
18
22
  format,
19
23
  encoding
20
24
  }) {
21
25
  mimetype = mimetype || getMimeType(path)
22
26
  format = format || DEFAULT_PREVIEW_FORMAT
23
27
 
24
- const buffer = fs.readFileSync(path)
25
- const rgba = await decodeImageToRGBA(buffer, mimetype)
28
+ const buff = await getBuffer({ path, httpLink, buffer })
29
+ const rgba = await decodeImageToRGBA(buff, mimetype, maxFrames)
26
30
  const { width, height } = rgba
27
31
 
28
32
  const maybeResizedRGBA = await resizeRGBA(rgba, maxWidth, maxHeight)
29
33
 
30
- const encoded = await encodeImageFromRGBA(maybeResizedRGBA, format, encoding)
34
+ let preview = await encodeImageFromRGBA(maybeResizedRGBA, format)
35
+
36
+ // quality reduction
37
+
38
+ if (maxBytes && preview.byteLength > maxBytes && supportsQuality(format)) {
39
+ const MIN_QUALITY = 50
40
+ for (let quality = 80; quality >= MIN_QUALITY; quality -= 15) {
41
+ preview = await encodeImageFromRGBA(maybeResizedRGBA, format, { quality })
42
+ if (preview.byteLength <= maxBytes) {
43
+ break
44
+ }
45
+ }
46
+ }
47
+
48
+ // fps reduction
49
+
50
+ if (
51
+ maxBytes &&
52
+ preview.byteLength > maxBytes &&
53
+ maybeResizedRGBA.frames?.length > 1
54
+ ) {
55
+ const quality = 75
56
+
57
+ // drop every n frame
58
+
59
+ for (const dropEvery of [4, 3, 2]) {
60
+ const frames = maybeResizedRGBA.frames.filter(
61
+ (frame, index) => index % dropEvery !== 0
62
+ )
63
+ const filtered = { ...maybeResizedRGBA, frames }
64
+ preview = await encodeImageFromRGBA(filtered, format, { quality })
65
+ if (!maxBytes || preview.byteLength <= maxBytes) {
66
+ break
67
+ }
68
+ }
69
+
70
+ // cap to 25 frames
71
+
72
+ if (preview.byteLength > maxBytes) {
73
+ const frames = maybeResizedRGBA.frames
74
+ .slice(0, 50)
75
+ .filter((frame, index) => index % 2 === 0)
76
+ const capped = { ...maybeResizedRGBA, frames }
77
+ preview = await encodeImageFromRGBA(capped, format, { quality })
78
+ }
79
+
80
+ // take only one frame
81
+
82
+ if (preview.byteLength > maxBytes) {
83
+ const oneFrame = {
84
+ ...maybeResizedRGBA,
85
+ frames: maybeResizedRGBA.frames.slice(0, 1)
86
+ }
87
+ preview = await encodeImageFromRGBA(oneFrame, format)
88
+ }
89
+ }
90
+
91
+ if (maxBytes && preview.byteLength > maxBytes) {
92
+ throw new Error(
93
+ `Could not create preview under maxBytes, reached ${preview.byteLength} bytes`
94
+ )
95
+ }
96
+
97
+ const encoded =
98
+ encoding === 'base64'
99
+ ? { inlined: b4a.toString(preview, 'base64') }
100
+ : { buffer: preview }
31
101
 
32
102
  return {
33
103
  metadata: {
@@ -46,17 +116,10 @@ export async function createPreview({
46
116
  }
47
117
  }
48
118
 
49
- export async function decodeImage({ path, httpLink, mimetype }) {
50
- let buffer
51
-
52
- if (path) {
53
- buffer = fs.readFileSync(path)
54
- } else if (httpLink) {
55
- const response = await fetch(httpLink)
56
- buffer = await response.buffer()
57
- }
119
+ export async function decodeImage({ path, httpLink, buffer, mimetype }) {
120
+ const buff = await getBuffer({ path, httpLink, buffer })
58
121
 
59
- const rgba = await decodeImageToRGBA(buffer, mimetype)
122
+ const rgba = await decodeImageToRGBA(buff, mimetype)
60
123
  const { width, height, data } = rgba
61
124
 
62
125
  return {
@@ -67,7 +130,24 @@ export async function decodeImage({ path, httpLink, mimetype }) {
67
130
  }
68
131
  }
69
132
 
70
- async function decodeImageToRGBA(buffer, mimetype) {
133
+ async function getBuffer({ path, httpLink, buffer }) {
134
+ if (buffer) return buffer
135
+
136
+ if (path) {
137
+ return fs.readFileSync(path)
138
+ }
139
+
140
+ if (httpLink) {
141
+ const response = await fetch(httpLink)
142
+ return await response.buffer()
143
+ }
144
+
145
+ throw new Error(
146
+ 'At least one of "path", "httpLink" or "buffer" must be provided'
147
+ )
148
+ }
149
+
150
+ async function decodeImageToRGBA(buffer, mimetype, maxFrames) {
71
151
  let rgba
72
152
 
73
153
  const codec = await importCodec(mimetype)
@@ -76,6 +156,7 @@ async function decodeImageToRGBA(buffer, mimetype) {
76
156
  const { width, height, loops, frames } = codec.decodeAnimated(buffer)
77
157
  const data = []
78
158
  for (const frame of frames) {
159
+ if (maxFrames > 0 && data.length >= maxFrames) break
79
160
  data.push(frame)
80
161
  }
81
162
  rgba = { width, height, loops, frames: data }
@@ -86,19 +167,17 @@ async function decodeImageToRGBA(buffer, mimetype) {
86
167
  return rgba
87
168
  }
88
169
 
89
- async function encodeImageFromRGBA(rgba, format, encoding) {
170
+ async function encodeImageFromRGBA(rgba, format, opts) {
90
171
  const codec = await importCodec(format)
91
172
 
92
173
  let encoded
93
174
  if (Array.isArray(rgba.frames)) {
94
- encoded = codec.encodeAnimated(rgba)
175
+ encoded = codec.encodeAnimated(rgba, opts)
95
176
  } else {
96
- encoded = codec.encode(rgba)
177
+ encoded = codec.encode(rgba, opts)
97
178
  }
98
179
 
99
- return encoding === 'base64'
100
- ? { inlined: b4a.toString(encoded, 'base64') }
101
- : { buffer: encoded }
180
+ return encoded
102
181
  }
103
182
 
104
183
  async function resizeRGBA(rgba, maxWidth, maxHeight) {