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 +28 -5
- package/package.json +1 -1
- package/shared/codecs.js +4 -0
- package/shared/spec/hrpc/index.js +5 -1
- package/shared/spec/hrpc/messages.js +53 -18
- package/shared/spec/schema/index.js +53 -18
- package/shared/spec/schema/schema.json +26 -2
- package/shared/spec/schema.js +21 -2
- package/worker/media.js +100 -21
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
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 [
|
|
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
|
-
|
|
104
|
-
|
|
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.
|
|
115
|
-
(m.
|
|
116
|
-
(m.
|
|
117
|
-
(m.
|
|
118
|
-
(m.
|
|
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:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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.
|
|
115
|
-
(m.
|
|
116
|
-
(m.
|
|
117
|
-
(m.
|
|
118
|
-
(m.
|
|
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:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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":
|
|
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",
|
package/shared/spec/schema.js
CHANGED
|
@@ -62,8 +62,15 @@ media.register({
|
|
|
62
62
|
fields: [
|
|
63
63
|
{
|
|
64
64
|
name: 'path',
|
|
65
|
-
type: 'string'
|
|
66
|
-
|
|
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
|
|
25
|
-
const rgba = await decodeImageToRGBA(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
100
|
-
? { inlined: b4a.toString(encoded, 'base64') }
|
|
101
|
-
: { buffer: encoded }
|
|
180
|
+
return encoded
|
|
102
181
|
}
|
|
103
182
|
|
|
104
183
|
async function resizeRGBA(rgba, maxWidth, maxHeight) {
|