bare-media 1.4.0 → 1.6.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
@@ -55,26 +55,29 @@ const data = await createPreview({ path, maxWidth, maxHeight })
55
55
 
56
56
  Create a preview from a media file
57
57
 
58
- | Property | Type | Description |
59
- | ----------- | ------ | ----------------------------------------------------------------- |
60
- | `path` | string | Path to the input file |
61
- | `mimetype` | string | Media type of the input file. If not provided it will be detected |
62
- | `maxWidth` | number | Max width for the generated preview |
63
- | `maxHeight` | number | Max height for the generated preview |
64
- | `maxFrames` | number | Max frames for the generated preview in case the file is animated |
65
- | `maxBytes` | number | Max bytes for the generated preview |
66
- | `format` | string | Media type for the generated preview. Default `image/webp` |
67
- | `encoding` | string | `base64` or nothing for buffer |
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 |
68
70
 
69
71
  ### decodeImage()
70
72
 
71
73
  Decode an image to RGBA
72
74
 
73
- | Property | Type | Description |
74
- | ---------- | ------ | ----------------------------------------------------------------- |
75
- | `path` | string | Path to the input file. Either this or `httpLink` is required |
76
- | `httpLink` | string | Http link to the input file |
77
- | `mimetype` | string | Media type of the input file. If not provided it will be detected |
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 |
78
81
 
79
82
  ## License
80
83
 
package/client.js CHANGED
@@ -42,6 +42,17 @@ export class WorkerClient extends ReadyResource {
42
42
  this.worker?.IPC.end()
43
43
  }
44
44
 
45
+ #reset() {
46
+ this.opening = null
47
+ this.closing = null
48
+
49
+ this.opened = false
50
+ this.closed = false
51
+
52
+ this.worker = null
53
+ this.rpc = null
54
+ }
55
+
45
56
  async #run() {
46
57
  const { filename, requireSource, args } = this.opts
47
58
  const source = requireSource?.()
@@ -50,7 +61,13 @@ export class WorkerClient extends ReadyResource {
50
61
  const ipc = this.worker.IPC
51
62
 
52
63
  ipc.on('end', () => ipc.end())
53
- ipc.on('close', () => this.onClose?.())
64
+ ipc.on('close', () => {
65
+ this.#reset()
66
+ this.onClose?.()
67
+ console.error(
68
+ '[bare-media] Worker has exited. IPC channel closed unexpectedly.'
69
+ )
70
+ })
54
71
 
55
72
  this.rpc = new HRPC(ipc)
56
73
  }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "bare-media",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build:rpc": "cd shared/spec && bare ./build.js",
8
8
  "format": "prettier --write .",
9
9
  "format:check": "prettier --check .",
10
- "test": "npm run format:check && brittle-bare test/index.js"
10
+ "lint": "lunte",
11
+ "test": "npm run lint && npm run format:check && brittle-bare test/index.js"
11
12
  },
12
13
  "keywords": [],
13
14
  "author": "Holepunch Inc",
@@ -17,6 +18,7 @@
17
18
  "b4a": "^1.6.7",
18
19
  "bare-fetch": "^2.4.1",
19
20
  "bare-fs": "^4.1.5",
21
+ "bare-gif": "^1.1.2",
20
22
  "bare-heif": "^1.0.5",
21
23
  "bare-image-resample": "^1.0.1",
22
24
  "bare-jpeg": "^1.0.1",
@@ -35,6 +37,7 @@
35
37
  "corestore": "^7.4.5",
36
38
  "hyperblobs": "^2.8.0",
37
39
  "hypercore-blob-server": "^1.11.0",
40
+ "lunte": "^1.3.0",
38
41
  "prettier": "^3.6.2",
39
42
  "prettier-config-holepunch": "^1.0.0",
40
43
  "test-tmp": "^1.4.0"
package/shared/codecs.js CHANGED
@@ -7,7 +7,8 @@ export const codecs = {
7
7
  'image/webp': () => import('bare-webp'),
8
8
  'image/png': () => import('bare-png'),
9
9
  'image/tif': () => import('bare-tiff'),
10
- 'image/tiff': () => import('bare-tiff')
10
+ 'image/tiff': () => import('bare-tiff'),
11
+ 'image/gif': () => import('bare-gif')
11
12
  }
12
13
 
13
14
  export function isCodecSupported(mimetype) {
@@ -100,9 +100,23 @@ 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 64 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)
@@ -113,17 +127,22 @@ const encoding3 = {
113
127
  },
114
128
  encode(state, m) {
115
129
  const flags =
116
- (m.mimetype ? 1 : 0) |
117
- (m.maxWidth ? 2 : 0) |
118
- (m.maxHeight ? 4 : 0) |
119
- (m.maxFrames ? 8 : 0) |
120
- (m.maxBytes ? 16 : 0) |
121
- (m.format ? 32 : 0) |
122
- (m.encoding ? 64 : 0)
123
-
124
- c.string.encode(state, m.path)
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)
140
+
125
141
  c.uint.encode(state, flags)
126
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)
127
146
  if (m.mimetype) c.string.encode(state, m.mimetype)
128
147
  if (m.maxWidth) c.uint.encode(state, m.maxWidth)
129
148
  if (m.maxHeight) c.uint.encode(state, m.maxHeight)
@@ -133,18 +152,19 @@ const encoding3 = {
133
152
  if (m.encoding) c.string.encode(state, m.encoding)
134
153
  },
135
154
  decode(state) {
136
- const r0 = c.string.decode(state)
137
155
  const flags = c.uint.decode(state)
138
156
 
139
157
  return {
140
- path: r0,
141
- mimetype: (flags & 1) !== 0 ? c.string.decode(state) : null,
142
- maxWidth: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
143
- maxHeight: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
144
- maxFrames: (flags & 8) !== 0 ? c.uint.decode(state) : 0,
145
- maxBytes: (flags & 16) !== 0 ? c.uint.decode(state) : 0,
146
- format: (flags & 32) !== 0 ? c.string.decode(state) : null,
147
- encoding: (flags & 64) !== 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
148
168
  }
149
169
  }
150
170
  }
@@ -178,19 +198,25 @@ const encoding4 = {
178
198
  // @media/decode-image-request
179
199
  const encoding5 = {
180
200
  preencode(state, m) {
181
- state.end++ // max flag is 4 so always one byte
201
+ state.end++ // max flag is 8 so always one byte
182
202
 
183
203
  if (m.path) c.string.preencode(state, m.path)
184
204
  if (m.httpLink) c.string.preencode(state, m.httpLink)
205
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
185
206
  if (m.mimetype) c.string.preencode(state, m.mimetype)
186
207
  },
187
208
  encode(state, m) {
188
- 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)
189
214
 
190
215
  c.uint.encode(state, flags)
191
216
 
192
217
  if (m.path) c.string.encode(state, m.path)
193
218
  if (m.httpLink) c.string.encode(state, m.httpLink)
219
+ if (m.buffer) c.buffer.encode(state, m.buffer)
194
220
  if (m.mimetype) c.string.encode(state, m.mimetype)
195
221
  },
196
222
  decode(state) {
@@ -199,7 +225,8 @@ const encoding5 = {
199
225
  return {
200
226
  path: (flags & 1) !== 0 ? c.string.decode(state) : null,
201
227
  httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
202
- 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
203
230
  }
204
231
  }
205
232
  }
@@ -100,9 +100,23 @@ 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 64 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)
@@ -113,17 +127,22 @@ const encoding3 = {
113
127
  },
114
128
  encode(state, m) {
115
129
  const flags =
116
- (m.mimetype ? 1 : 0) |
117
- (m.maxWidth ? 2 : 0) |
118
- (m.maxHeight ? 4 : 0) |
119
- (m.maxFrames ? 8 : 0) |
120
- (m.maxBytes ? 16 : 0) |
121
- (m.format ? 32 : 0) |
122
- (m.encoding ? 64 : 0)
123
-
124
- c.string.encode(state, m.path)
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)
140
+
125
141
  c.uint.encode(state, flags)
126
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)
127
146
  if (m.mimetype) c.string.encode(state, m.mimetype)
128
147
  if (m.maxWidth) c.uint.encode(state, m.maxWidth)
129
148
  if (m.maxHeight) c.uint.encode(state, m.maxHeight)
@@ -133,18 +152,19 @@ const encoding3 = {
133
152
  if (m.encoding) c.string.encode(state, m.encoding)
134
153
  },
135
154
  decode(state) {
136
- const r0 = c.string.decode(state)
137
155
  const flags = c.uint.decode(state)
138
156
 
139
157
  return {
140
- path: r0,
141
- mimetype: (flags & 1) !== 0 ? c.string.decode(state) : null,
142
- maxWidth: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
143
- maxHeight: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
144
- maxFrames: (flags & 8) !== 0 ? c.uint.decode(state) : 0,
145
- maxBytes: (flags & 16) !== 0 ? c.uint.decode(state) : 0,
146
- format: (flags & 32) !== 0 ? c.string.decode(state) : null,
147
- encoding: (flags & 64) !== 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
148
168
  }
149
169
  }
150
170
  }
@@ -178,19 +198,25 @@ const encoding4 = {
178
198
  // @media/decode-image-request
179
199
  const encoding5 = {
180
200
  preencode(state, m) {
181
- state.end++ // max flag is 4 so always one byte
201
+ state.end++ // max flag is 8 so always one byte
182
202
 
183
203
  if (m.path) c.string.preencode(state, m.path)
184
204
  if (m.httpLink) c.string.preencode(state, m.httpLink)
205
+ if (m.buffer) c.buffer.preencode(state, m.buffer)
185
206
  if (m.mimetype) c.string.preencode(state, m.mimetype)
186
207
  },
187
208
  encode(state, m) {
188
- 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)
189
214
 
190
215
  c.uint.encode(state, flags)
191
216
 
192
217
  if (m.path) c.string.encode(state, m.path)
193
218
  if (m.httpLink) c.string.encode(state, m.httpLink)
219
+ if (m.buffer) c.buffer.encode(state, m.buffer)
194
220
  if (m.mimetype) c.string.encode(state, m.mimetype)
195
221
  },
196
222
  decode(state) {
@@ -199,7 +225,8 @@ const encoding5 = {
199
225
  return {
200
226
  path: (flags & 1) !== 0 ? c.string.decode(state) : null,
201
227
  httpLink: (flags & 2) !== 0 ? c.string.decode(state) : null,
202
- 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
203
230
  }
204
231
  }
205
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",
@@ -152,6 +161,11 @@
152
161
  "type": "string",
153
162
  "version": 1
154
163
  },
164
+ {
165
+ "name": "buffer",
166
+ "type": "buffer",
167
+ "version": 1
168
+ },
155
169
  {
156
170
  "name": "mimetype",
157
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',
@@ -123,6 +130,10 @@ media.register({
123
130
  name: 'httpLink',
124
131
  type: 'string'
125
132
  },
133
+ {
134
+ name: 'buffer',
135
+ type: 'buffer'
136
+ },
126
137
  {
127
138
  name: 'mimetype',
128
139
  type: 'string'
package/worker/media.js CHANGED
@@ -12,6 +12,8 @@ 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,
@@ -23,8 +25,8 @@ export async function createPreview({
23
25
  mimetype = mimetype || getMimeType(path)
24
26
  format = format || DEFAULT_PREVIEW_FORMAT
25
27
 
26
- const buffer = fs.readFileSync(path)
27
- const rgba = await decodeImageToRGBA(buffer, mimetype, maxFrames)
28
+ const buff = await getBuffer({ path, httpLink, buffer })
29
+ const rgba = await decodeImageToRGBA(buff, mimetype, maxFrames)
28
30
  const { width, height } = rgba
29
31
 
30
32
  const maybeResizedRGBA = await resizeRGBA(rgba, maxWidth, maxHeight)
@@ -114,17 +116,10 @@ export async function createPreview({
114
116
  }
115
117
  }
116
118
 
117
- export async function decodeImage({ path, httpLink, mimetype }) {
118
- let buffer
119
+ export async function decodeImage({ path, httpLink, buffer, mimetype }) {
120
+ const buff = await getBuffer({ path, httpLink, buffer })
119
121
 
120
- if (path) {
121
- buffer = fs.readFileSync(path)
122
- } else if (httpLink) {
123
- const response = await fetch(httpLink)
124
- buffer = await response.buffer()
125
- }
126
-
127
- const rgba = await decodeImageToRGBA(buffer, mimetype)
122
+ const rgba = await decodeImageToRGBA(buff, mimetype)
128
123
  const { width, height, data } = rgba
129
124
 
130
125
  return {
@@ -135,6 +130,23 @@ export async function decodeImage({ path, httpLink, mimetype }) {
135
130
  }
136
131
  }
137
132
 
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
+
138
150
  async function decodeImageToRGBA(buffer, mimetype, maxFrames) {
139
151
  let rgba
140
152