bare-media 1.0.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.
@@ -0,0 +1,196 @@
1
+ import Hyperschema from 'hyperschema'
2
+
3
+ import { SCHEMA_DIR } from './constants'
4
+
5
+ export const schema = Hyperschema.from(SCHEMA_DIR)
6
+ const media = schema.namespace('media')
7
+
8
+ media.register({
9
+ name: 'dimensions',
10
+ fields: [{
11
+ name: 'width',
12
+ type: 'uint',
13
+ required: true
14
+ },
15
+ {
16
+ name: 'height',
17
+ type: 'uint',
18
+ required: true
19
+ }]
20
+ })
21
+
22
+ media.register({
23
+ name: 'metadata',
24
+ fields: [{
25
+ name: 'mimetype',
26
+ type: 'string'
27
+ },
28
+ {
29
+ name: 'dimensions',
30
+ type: '@media/dimensions'
31
+ },
32
+ {
33
+ name: 'duration',
34
+ type: 'uint'
35
+ }]
36
+ })
37
+
38
+ media.register({
39
+ name: 'file',
40
+ fields: [{
41
+ name: 'metadata',
42
+ type: '@media/metadata'
43
+ },
44
+ {
45
+ name: 'inlined',
46
+ type: 'string'
47
+ },
48
+ {
49
+ name: 'buffer',
50
+ type: 'buffer'
51
+ }]
52
+ })
53
+
54
+ media.register({
55
+ name: 'preview-by-size',
56
+ fields: [{
57
+ name: 'small',
58
+ type: '@media/file'
59
+ },
60
+ {
61
+ name: 'medium',
62
+ type: '@media/file'
63
+ },
64
+ {
65
+ name: 'large',
66
+ type: '@media/file'
67
+ }]
68
+ })
69
+
70
+ media.register({
71
+ name: 'sizePreview',
72
+ fields: [{
73
+ name: 'small',
74
+ type: 'uint',
75
+ required: true
76
+ },
77
+ {
78
+ name: 'medium',
79
+ type: 'uint',
80
+ required: true
81
+ },
82
+ {
83
+ name: 'large',
84
+ type: 'uint',
85
+ required: true
86
+ }]
87
+ })
88
+
89
+ media.register({
90
+ name: 'create-preview-request',
91
+ fields: [{
92
+ name: 'path',
93
+ type: 'string',
94
+ required: true
95
+ },
96
+ {
97
+ name: 'mimetype',
98
+ type: 'string'
99
+ },
100
+ {
101
+ name: 'maxWidth',
102
+ type: 'uint'
103
+ },
104
+ {
105
+ name: 'maxHeight',
106
+ type: 'uint'
107
+ },
108
+ {
109
+ name: 'format',
110
+ type: 'string'
111
+ },
112
+ {
113
+ name: 'encoding',
114
+ type: 'string'
115
+ }]
116
+ })
117
+
118
+ media.register({
119
+ name: 'create-preview-response',
120
+ fields: [{
121
+ name: 'metadata',
122
+ type: '@media/metadata',
123
+ required: true
124
+ },
125
+ {
126
+ name: 'preview',
127
+ type: '@media/file',
128
+ required: true
129
+ }]
130
+ })
131
+
132
+ media.register({
133
+ name: 'create-preview-all-request',
134
+ fields: [{
135
+ name: 'path',
136
+ type: 'string',
137
+ required: true
138
+ },
139
+ {
140
+ name: 'mimetype',
141
+ type: 'string'
142
+ },
143
+ {
144
+ name: 'maxWidth',
145
+ type: '@media/sizePreview',
146
+ required: true
147
+ },
148
+ {
149
+ name: 'maxHeight',
150
+ type: '@media/sizePreview',
151
+ required: true
152
+ },
153
+ {
154
+ name: 'format',
155
+ type: 'string'
156
+ }]
157
+ })
158
+
159
+ media.register({
160
+ name: 'create-preview-all-response',
161
+ fields: [{
162
+ name: 'metadata',
163
+ type: '@media/metadata',
164
+ required: true
165
+ },
166
+ {
167
+ name: 'preview',
168
+ type: '@media/preview-by-size',
169
+ required: true
170
+ }]
171
+ })
172
+
173
+ media.register({
174
+ name: 'decode-image-request',
175
+ fields: [{
176
+ name: 'httpLink',
177
+ type: 'string',
178
+ required: true
179
+ },
180
+ {
181
+ name: 'mimetype',
182
+ type: 'string'
183
+ }]
184
+ })
185
+
186
+ media.register({
187
+ name: 'decode-image-response',
188
+ fields: [{
189
+ name: 'metadata',
190
+ type: '@media/metadata'
191
+ },
192
+ {
193
+ name: 'data',
194
+ type: 'buffer'
195
+ }]
196
+ })
@@ -0,0 +1,25 @@
1
+ import worker from 'cross-worker'
2
+ import uncaughts from 'uncaughts'
3
+
4
+ import HRPC from '../shared/spec/hrpc'
5
+
6
+ import * as media from './media'
7
+ import { log } from './util'
8
+
9
+ log('Worker started 🚀')
10
+
11
+ const stream = worker.stream()
12
+
13
+ stream.on('end', () => stream.end())
14
+ stream.on('error', (err) => console.error(err))
15
+ stream.on('close', () => Bare.exit(0))
16
+
17
+ const rpc = new HRPC(stream)
18
+
19
+ rpc.onCreatePreview(media.createPreview)
20
+ rpc.onCreatePreviewAll(media.createPreviewAll)
21
+ rpc.onDecodeImage(media.decodeImage)
22
+
23
+ uncaughts.on((err) => {
24
+ log('Uncaught error:', err)
25
+ })
@@ -0,0 +1,105 @@
1
+ import b4a from 'b4a'
2
+ import fs from 'bare-fs'
3
+ import fetch from 'bare-fetch'
4
+
5
+ import { importCodec } from '../shared/codecs.js'
6
+ import { calculateFitDimensions } from './util'
7
+
8
+ const DEFAULT_PREVIEW_FORMAT = 'image/webp'
9
+
10
+ const animatableMimetypes = ['image/webp']
11
+
12
+ export async function createPreview ({ path, mimetype, maxWidth, maxHeight, format, encoding }) {
13
+ format = format || DEFAULT_PREVIEW_FORMAT
14
+
15
+ const buffer = fs.readFileSync(path)
16
+ const rgba = await decodeImageToRGBA(buffer, mimetype)
17
+ const { width, height } = rgba
18
+
19
+ return {
20
+ metadata: {
21
+ dimensions: { width, height }
22
+ },
23
+ preview: await createPreviewFromRGBA(rgba, maxWidth, maxHeight, format, encoding)
24
+ }
25
+ }
26
+
27
+ export async function createPreviewAll ({ path, mimetype, maxWidth, maxHeight, format }) {
28
+ format = format || DEFAULT_PREVIEW_FORMAT
29
+
30
+ const buffer = fs.readFileSync(path)
31
+ const rgba = await decodeImageToRGBA(buffer, mimetype)
32
+ const { width, height } = rgba
33
+
34
+ const [small, medium, large] = await Promise.all([
35
+ createPreviewFromRGBA(rgba, maxWidth.small, maxHeight.small, format, 'base64'),
36
+ createPreviewFromRGBA(rgba, maxWidth.medium, maxHeight.medium, format, 'base64'),
37
+ createPreviewFromRGBA(rgba, maxWidth.large, maxHeight.large, format)
38
+ ])
39
+
40
+ return {
41
+ metadata: {
42
+ dimensions: { width, height }
43
+ },
44
+ preview: { small, medium, large }
45
+ }
46
+ }
47
+
48
+ export async function decodeImage ({ httpLink, mimetype }) {
49
+ const response = await fetch(httpLink)
50
+ const buffer = await response.buffer()
51
+
52
+ const rgba = await decodeImageToRGBA(buffer, mimetype)
53
+ const { width, height, data } = rgba
54
+
55
+ return {
56
+ metadata: {
57
+ dimensions: { width, height }
58
+ },
59
+ data
60
+ }
61
+ }
62
+
63
+ async function decodeImageToRGBA (buffer, mimetype) {
64
+ let rgba
65
+
66
+ const codec = await importCodec(mimetype)
67
+
68
+ if (animatableMimetypes.includes(mimetype)) {
69
+ const { frames, width, height } = codec.decodeAnimated(buffer)
70
+ const { data } = frames.next().value
71
+ rgba = { width, height, data }
72
+ } else {
73
+ rgba = codec.decode(buffer)
74
+ }
75
+
76
+ return rgba
77
+ }
78
+
79
+ async function createPreviewFromRGBA (rgba, maxWidth, maxHeight, format, encoding) {
80
+ format = format || DEFAULT_PREVIEW_FORMAT
81
+
82
+ const { width, height } = rgba
83
+ let maybeResized, dimensions
84
+
85
+ if (maxWidth && maxHeight && (width > maxWidth || height > maxHeight)) {
86
+ const { resize } = await import('bare-image-resample')
87
+ dimensions = calculateFitDimensions(width, height, maxWidth, maxHeight)
88
+ maybeResized = resize(rgba, dimensions.width, dimensions.height)
89
+ } else {
90
+ dimensions = { width, height }
91
+ maybeResized = rgba
92
+ }
93
+
94
+ const codec = await importCodec(format)
95
+ const encoded = codec.encode(maybeResized)
96
+
97
+ const result = encoding === 'base64'
98
+ ? { inlined: b4a.toString(encoded, 'base64') }
99
+ : { buffer: encoded }
100
+
101
+ return {
102
+ ...result,
103
+ metadata: { mimetype: format, dimensions }
104
+ }
105
+ }
package/worker/util.js ADDED
@@ -0,0 +1,16 @@
1
+ export const log = (...args) => console.log('[bare-media]', ...args)
2
+
3
+ export function calculateFitDimensions (width, height, maxWidth, maxHeight) {
4
+ if (width <= maxWidth && height <= maxHeight) {
5
+ return { width, height }
6
+ }
7
+
8
+ const widthRatio = maxWidth / width
9
+ const heightRatio = maxHeight / height
10
+ const ratio = Math.min(widthRatio, heightRatio)
11
+
12
+ return {
13
+ width: Math.round(width * ratio),
14
+ height: Math.round(height * ratio)
15
+ }
16
+ }