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