bare-media 1.2.1 → 1.4.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 -9
- package/client.js +12 -11
- package/package.json +7 -12
- package/shared/codecs.js +6 -2
- package/shared/spec/hrpc/hrpc.json +1 -1
- package/shared/spec/hrpc/index.js +51 -19
- package/shared/spec/hrpc/messages.js +75 -58
- package/shared/spec/schema/index.js +75 -58
- package/shared/spec/schema/schema.json +11 -1
- package/shared/spec/schema.js +111 -89
- package/worker/media.js +131 -22
- package/worker/util.js +1 -1
package/worker/media.js
CHANGED
|
@@ -3,37 +3,118 @@ 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'
|
|
10
10
|
|
|
11
11
|
const animatableMimetypes = ['image/webp']
|
|
12
12
|
|
|
13
|
-
export async function createPreview
|
|
13
|
+
export async function createPreview({
|
|
14
|
+
path,
|
|
15
|
+
mimetype,
|
|
16
|
+
maxWidth,
|
|
17
|
+
maxHeight,
|
|
18
|
+
maxFrames,
|
|
19
|
+
maxBytes,
|
|
20
|
+
format,
|
|
21
|
+
encoding
|
|
22
|
+
}) {
|
|
14
23
|
mimetype = mimetype || getMimeType(path)
|
|
15
24
|
format = format || DEFAULT_PREVIEW_FORMAT
|
|
16
25
|
|
|
17
26
|
const buffer = fs.readFileSync(path)
|
|
18
|
-
const rgba = await decodeImageToRGBA(buffer, mimetype)
|
|
27
|
+
const rgba = await decodeImageToRGBA(buffer, mimetype, maxFrames)
|
|
19
28
|
const { width, height } = rgba
|
|
20
29
|
|
|
21
|
-
const
|
|
30
|
+
const maybeResizedRGBA = await resizeRGBA(rgba, maxWidth, maxHeight)
|
|
31
|
+
|
|
32
|
+
let preview = await encodeImageFromRGBA(maybeResizedRGBA, format)
|
|
33
|
+
|
|
34
|
+
// quality reduction
|
|
35
|
+
|
|
36
|
+
if (maxBytes && preview.byteLength > maxBytes && supportsQuality(format)) {
|
|
37
|
+
const MIN_QUALITY = 50
|
|
38
|
+
for (let quality = 80; quality >= MIN_QUALITY; quality -= 15) {
|
|
39
|
+
preview = await encodeImageFromRGBA(maybeResizedRGBA, format, { quality })
|
|
40
|
+
if (preview.byteLength <= maxBytes) {
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// fps reduction
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
maxBytes &&
|
|
50
|
+
preview.byteLength > maxBytes &&
|
|
51
|
+
maybeResizedRGBA.frames?.length > 1
|
|
52
|
+
) {
|
|
53
|
+
const quality = 75
|
|
54
|
+
|
|
55
|
+
// drop every n frame
|
|
56
|
+
|
|
57
|
+
for (const dropEvery of [4, 3, 2]) {
|
|
58
|
+
const frames = maybeResizedRGBA.frames.filter(
|
|
59
|
+
(frame, index) => index % dropEvery !== 0
|
|
60
|
+
)
|
|
61
|
+
const filtered = { ...maybeResizedRGBA, frames }
|
|
62
|
+
preview = await encodeImageFromRGBA(filtered, format, { quality })
|
|
63
|
+
if (!maxBytes || preview.byteLength <= maxBytes) {
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// cap to 25 frames
|
|
69
|
+
|
|
70
|
+
if (preview.byteLength > maxBytes) {
|
|
71
|
+
const frames = maybeResizedRGBA.frames
|
|
72
|
+
.slice(0, 50)
|
|
73
|
+
.filter((frame, index) => index % 2 === 0)
|
|
74
|
+
const capped = { ...maybeResizedRGBA, frames }
|
|
75
|
+
preview = await encodeImageFromRGBA(capped, format, { quality })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// take only one frame
|
|
22
79
|
|
|
23
|
-
|
|
80
|
+
if (preview.byteLength > maxBytes) {
|
|
81
|
+
const oneFrame = {
|
|
82
|
+
...maybeResizedRGBA,
|
|
83
|
+
frames: maybeResizedRGBA.frames.slice(0, 1)
|
|
84
|
+
}
|
|
85
|
+
preview = await encodeImageFromRGBA(oneFrame, format)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (maxBytes && preview.byteLength > maxBytes) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Could not create preview under maxBytes, reached ${preview.byteLength} bytes`
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const encoded =
|
|
96
|
+
encoding === 'base64'
|
|
97
|
+
? { inlined: b4a.toString(preview, 'base64') }
|
|
98
|
+
: { buffer: preview }
|
|
24
99
|
|
|
25
100
|
return {
|
|
26
101
|
metadata: {
|
|
27
102
|
dimensions: { width, height }
|
|
28
103
|
},
|
|
29
104
|
preview: {
|
|
30
|
-
metadata: {
|
|
105
|
+
metadata: {
|
|
106
|
+
mimetype: format,
|
|
107
|
+
dimensions: {
|
|
108
|
+
width: maybeResizedRGBA.width,
|
|
109
|
+
height: maybeResizedRGBA.height
|
|
110
|
+
}
|
|
111
|
+
},
|
|
31
112
|
...encoded
|
|
32
113
|
}
|
|
33
114
|
}
|
|
34
115
|
}
|
|
35
116
|
|
|
36
|
-
export async function decodeImage
|
|
117
|
+
export async function decodeImage({ path, httpLink, mimetype }) {
|
|
37
118
|
let buffer
|
|
38
119
|
|
|
39
120
|
if (path) {
|
|
@@ -54,15 +135,19 @@ export async function decodeImage ({ path, httpLink, mimetype }) {
|
|
|
54
135
|
}
|
|
55
136
|
}
|
|
56
137
|
|
|
57
|
-
async function decodeImageToRGBA
|
|
138
|
+
async function decodeImageToRGBA(buffer, mimetype, maxFrames) {
|
|
58
139
|
let rgba
|
|
59
140
|
|
|
60
141
|
const codec = await importCodec(mimetype)
|
|
61
142
|
|
|
62
143
|
if (animatableMimetypes.includes(mimetype)) {
|
|
63
|
-
const {
|
|
64
|
-
const
|
|
65
|
-
|
|
144
|
+
const { width, height, loops, frames } = codec.decodeAnimated(buffer)
|
|
145
|
+
const data = []
|
|
146
|
+
for (const frame of frames) {
|
|
147
|
+
if (maxFrames > 0 && data.length >= maxFrames) break
|
|
148
|
+
data.push(frame)
|
|
149
|
+
}
|
|
150
|
+
rgba = { width, height, loops, frames: data }
|
|
66
151
|
} else {
|
|
67
152
|
rgba = codec.decode(buffer)
|
|
68
153
|
}
|
|
@@ -70,26 +155,50 @@ async function decodeImageToRGBA (buffer, mimetype) {
|
|
|
70
155
|
return rgba
|
|
71
156
|
}
|
|
72
157
|
|
|
73
|
-
async function encodeImageFromRGBA
|
|
158
|
+
async function encodeImageFromRGBA(rgba, format, opts) {
|
|
74
159
|
const codec = await importCodec(format)
|
|
75
|
-
const encoded = codec.encode(rgba)
|
|
76
160
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
161
|
+
let encoded
|
|
162
|
+
if (Array.isArray(rgba.frames)) {
|
|
163
|
+
encoded = codec.encodeAnimated(rgba, opts)
|
|
164
|
+
} else {
|
|
165
|
+
encoded = codec.encode(rgba, opts)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return encoded
|
|
80
169
|
}
|
|
81
170
|
|
|
82
|
-
async function resizeRGBA
|
|
83
|
-
|
|
171
|
+
async function resizeRGBA(rgba, maxWidth, maxHeight) {
|
|
172
|
+
const { width, height } = rgba
|
|
173
|
+
|
|
174
|
+
let maybeResizedRGBA
|
|
84
175
|
|
|
85
176
|
if (maxWidth && maxHeight && (width > maxWidth || height > maxHeight)) {
|
|
86
177
|
const { resize } = await import('bare-image-resample')
|
|
87
|
-
dimensions = calculateFitDimensions(
|
|
88
|
-
|
|
178
|
+
const dimensions = calculateFitDimensions(
|
|
179
|
+
width,
|
|
180
|
+
height,
|
|
181
|
+
maxWidth,
|
|
182
|
+
maxHeight
|
|
183
|
+
)
|
|
184
|
+
if (Array.isArray(rgba.frames)) {
|
|
185
|
+
const frames = []
|
|
186
|
+
for (const frame of rgba.frames) {
|
|
187
|
+
const resized = resize(frame, dimensions.width, dimensions.height)
|
|
188
|
+
frames.push({ ...resized, timestamp: frame.timestamp })
|
|
189
|
+
}
|
|
190
|
+
maybeResizedRGBA = {
|
|
191
|
+
width: frames[0].width,
|
|
192
|
+
height: frames[0].height,
|
|
193
|
+
loops: rgba.loops,
|
|
194
|
+
frames
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
maybeResizedRGBA = resize(rgba, dimensions.width, dimensions.height)
|
|
198
|
+
}
|
|
89
199
|
} else {
|
|
90
|
-
dimensions = { width, height }
|
|
91
200
|
maybeResizedRGBA = rgba
|
|
92
201
|
}
|
|
93
202
|
|
|
94
|
-
return
|
|
203
|
+
return maybeResizedRGBA
|
|
95
204
|
}
|
package/worker/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const log = (...args) => console.log('[bare-media]', ...args)
|
|
2
2
|
|
|
3
|
-
export function calculateFitDimensions
|
|
3
|
+
export function calculateFitDimensions(width, height, maxWidth, maxHeight) {
|
|
4
4
|
if (width <= maxWidth && height <= maxHeight) {
|
|
5
5
|
return { width, height }
|
|
6
6
|
}
|