@remotion/gif 4.0.0-webhook.27 → 4.1.0-alpha2
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/LICENSE.md +8 -8
- package/dist/cjs/Gif.d.ts +7 -0
- package/dist/{Gif.js → cjs/Gif.js} +11 -6
- package/dist/{GifForDevelopment.d.ts → cjs/GifForDevelopment.d.ts} +0 -0
- package/dist/{GifForDevelopment.js → cjs/GifForDevelopment.js} +10 -5
- package/dist/{GifForRendering.d.ts → cjs/GifForRendering.d.ts} +0 -0
- package/dist/{GifForRendering.js → cjs/GifForRendering.js} +14 -5
- package/dist/{canvas.d.ts → cjs/canvas.d.ts} +1 -1
- package/dist/{canvas.js → cjs/canvas.js} +3 -0
- package/dist/cjs/get-gif-duration-in-seconds.d.ts +5 -0
- package/dist/{get-gif-duration-in-seconds.js → cjs/get-gif-duration-in-seconds.js} +8 -3
- package/dist/cjs/gif-cache.d.ts +4 -0
- package/dist/cjs/gif-cache.js +6 -0
- package/dist/cjs/gifuct/deinterlace.d.ts +4 -0
- package/dist/cjs/gifuct/deinterlace.js +26 -0
- package/dist/cjs/gifuct/index.d.ts +4 -0
- package/dist/cjs/gifuct/index.js +54 -0
- package/dist/cjs/gifuct/lzw.d.ts +5 -0
- package/dist/cjs/gifuct/lzw.js +119 -0
- package/dist/cjs/gifuct/types.d.ts +96 -0
- package/dist/{props.js → cjs/gifuct/types.js} +0 -0
- package/dist/{index.d.ts → cjs/index.d.ts} +1 -0
- package/dist/{index.js → cjs/index.js} +3 -1
- package/dist/{is-cors-error.d.ts → cjs/is-cors-error.d.ts} +0 -0
- package/dist/{is-cors-error.js → cjs/is-cors-error.js} +0 -0
- package/dist/cjs/js-binary-schema-parser/gif.d.ts +51 -0
- package/dist/cjs/js-binary-schema-parser/gif.js +158 -0
- package/dist/cjs/js-binary-schema-parser/parser.d.ts +9 -0
- package/dist/cjs/js-binary-schema-parser/parser.js +54 -0
- package/dist/cjs/js-binary-schema-parser/uint8-parser.d.ts +17 -0
- package/dist/cjs/js-binary-schema-parser/uint8-parser.js +79 -0
- package/dist/cjs/lru/index.d.ts +101 -0
- package/dist/cjs/lru/index.js +258 -0
- package/dist/{parse-generate.d.ts → cjs/parse-generate.d.ts} +1 -1
- package/dist/{parse-generate.js → cjs/parse-generate.js} +27 -9
- package/dist/cjs/parser/decompress-frames.d.ts +2 -0
- package/dist/cjs/parser/decompress-frames.js +19 -0
- package/dist/cjs/preload-gif.d.ts +8 -0
- package/dist/cjs/preload-gif.js +41 -0
- package/dist/{props.d.ts → cjs/props.d.ts} +6 -3
- package/dist/cjs/props.js +2 -0
- package/dist/{react-tools.d.ts → cjs/react-tools.d.ts} +0 -0
- package/dist/{react-tools.js → cjs/react-tools.js} +1 -0
- package/dist/cjs/resolve-gif-source.d.ts +1 -0
- package/dist/cjs/resolve-gif-source.js +7 -0
- package/dist/{use-element-size.d.ts → cjs/use-element-size.d.ts} +1 -1
- package/dist/{use-element-size.js → cjs/use-element-size.js} +1 -1
- package/dist/cjs/useCurrentGifIndex.d.ts +2 -0
- package/dist/cjs/useCurrentGifIndex.js +35 -0
- package/dist/{worker → cjs/worker}/index.d.ts +0 -0
- package/dist/{worker → cjs/worker}/index.js +0 -0
- package/dist/cjs/worker/source.d.ts +1 -0
- package/dist/cjs/worker/source.js +7 -0
- package/dist/{worker → cjs/worker}/worker.d.ts +0 -0
- package/dist/{worker → cjs/worker}/worker.js +0 -0
- package/dist/esm/Gif.d.ts +7 -0
- package/dist/esm/GifForDevelopment.d.ts +3 -0
- package/dist/esm/GifForRendering.d.ts +3 -0
- package/dist/esm/canvas.d.ts +13 -0
- package/dist/esm/get-gif-duration-in-seconds.d.ts +5 -0
- package/dist/esm/gif-cache.d.ts +4 -0
- package/dist/esm/gifuct/deinterlace.d.ts +4 -0
- package/dist/esm/gifuct/index.d.ts +4 -0
- package/dist/esm/gifuct/lzw.d.ts +5 -0
- package/dist/esm/gifuct/types.d.ts +96 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.mjs +1281 -0
- package/dist/esm/is-cors-error.d.ts +1 -0
- package/dist/esm/js-binary-schema-parser/gif.d.ts +51 -0
- package/dist/esm/js-binary-schema-parser/parser.d.ts +9 -0
- package/dist/esm/js-binary-schema-parser/uint8-parser.d.ts +17 -0
- package/dist/esm/lru/index.d.ts +101 -0
- package/dist/esm/parse-generate.d.ts +18 -0
- package/dist/esm/parser/decompress-frames.d.ts +2 -0
- package/dist/esm/preload-gif.d.ts +8 -0
- package/dist/esm/props.d.ts +25 -0
- package/dist/esm/react-tools.d.ts +9 -0
- package/dist/esm/resolve-gif-source.d.ts +1 -0
- package/dist/esm/use-element-size.d.ts +6 -0
- package/dist/esm/useCurrentGifIndex.d.ts +2 -0
- package/dist/esm/worker/index.d.ts +1 -0
- package/dist/esm/worker/source.d.ts +1 -0
- package/dist/esm/worker/worker.d.ts +1 -0
- package/dist/tsconfig-esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +67 -57
- package/dist/Gif.d.ts +0 -3
- package/dist/get-gif-duration-in-seconds.d.ts +0 -1
- package/dist/gif-cache.d.ts +0 -3
- package/dist/gif-cache.js +0 -5
- package/dist/useCurrentGifIndex.d.ts +0 -1
- package/dist/useCurrentGifIndex.js +0 -28
- package/dist/worker/source.d.ts +0 -1
- package/dist/worker/source.js +0 -5
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
import { Internals, useCurrentFrame, useVideoConfig, delayRender, continueRender } from 'remotion';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useState, useMemo, useCallback, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';
|
|
4
|
+
|
|
5
|
+
class QuickLRU {
|
|
6
|
+
/**
|
|
7
|
+
Simple ["Least Recently Used" (LRU) cache](https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29).
|
|
8
|
+
|
|
9
|
+
The instance is an [`Iterable`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) of `[key, value]` pairs so you can use it directly in a [`for…of`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) loop.
|
|
10
|
+
|
|
11
|
+
@example
|
|
12
|
+
```
|
|
13
|
+
import { QuickLRU } from 'quick-lru-ts';
|
|
14
|
+
|
|
15
|
+
const lru = new QuickLRU({maxSize: 1000});
|
|
16
|
+
|
|
17
|
+
lru.set('🦄', '🌈');
|
|
18
|
+
|
|
19
|
+
lru.has('🦄');
|
|
20
|
+
//=> true
|
|
21
|
+
|
|
22
|
+
lru.get('🦄');
|
|
23
|
+
//=> '🌈'
|
|
24
|
+
```
|
|
25
|
+
*/
|
|
26
|
+
constructor(options) {
|
|
27
|
+
if (!(options.maxSize && options.maxSize > 0)) {
|
|
28
|
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
|
29
|
+
}
|
|
30
|
+
if (typeof options.maxAge === 'number' && options.maxAge === 0) {
|
|
31
|
+
throw new TypeError('`maxAge` must be a number greater than 0');
|
|
32
|
+
}
|
|
33
|
+
this.maxSize = options.maxSize;
|
|
34
|
+
this.maxAge = options.maxAge || Number.POSITIVE_INFINITY;
|
|
35
|
+
this.onEviction = options.onEviction;
|
|
36
|
+
this.cache = new Map();
|
|
37
|
+
this.oldCache = new Map();
|
|
38
|
+
this._size = 0;
|
|
39
|
+
}
|
|
40
|
+
_emitEvictions(cache) {
|
|
41
|
+
if (typeof this.onEviction !== 'function') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const [key, item] of cache) {
|
|
45
|
+
this.onEviction(key, item.value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
_deleteIfExpired(key, item) {
|
|
49
|
+
if (item === undefined)
|
|
50
|
+
return true;
|
|
51
|
+
if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
|
|
52
|
+
if (typeof this.onEviction === 'function') {
|
|
53
|
+
this.onEviction(key, item.value);
|
|
54
|
+
}
|
|
55
|
+
return this.delete(key);
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
_getOrDeleteIfExpired(key, item) {
|
|
60
|
+
const deleted = this._deleteIfExpired(key, item);
|
|
61
|
+
if (deleted === false) {
|
|
62
|
+
return item.value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
_getItemValue(key, item) {
|
|
66
|
+
if (item === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
|
|
69
|
+
}
|
|
70
|
+
_peek(key, cache) {
|
|
71
|
+
const item = cache.get(key);
|
|
72
|
+
return this._getItemValue(key, item);
|
|
73
|
+
}
|
|
74
|
+
_set(key, value) {
|
|
75
|
+
this.cache.set(key, value);
|
|
76
|
+
this._size++;
|
|
77
|
+
if (this._size >= this.maxSize) {
|
|
78
|
+
this._size = 0;
|
|
79
|
+
this._emitEvictions(this.oldCache);
|
|
80
|
+
this.oldCache = this.cache;
|
|
81
|
+
this.cache = new Map();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_moveToRecent(key, item) {
|
|
85
|
+
this.oldCache.delete(key);
|
|
86
|
+
this._set(key, item);
|
|
87
|
+
}
|
|
88
|
+
*_entriesAscending() {
|
|
89
|
+
for (const item of this.oldCache) {
|
|
90
|
+
const [key, value] = item;
|
|
91
|
+
if (!this.cache.has(key)) {
|
|
92
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
93
|
+
if (deleted === false) {
|
|
94
|
+
yield item;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const item of this.cache) {
|
|
99
|
+
const [key, value] = item;
|
|
100
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
101
|
+
if (deleted === false) {
|
|
102
|
+
yield item;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
Get an item.
|
|
108
|
+
|
|
109
|
+
@returns The stored item or `undefined`.
|
|
110
|
+
*/
|
|
111
|
+
get(key) {
|
|
112
|
+
if (this.cache.has(key)) {
|
|
113
|
+
const item = this.cache.get(key);
|
|
114
|
+
return this._getItemValue(key, item);
|
|
115
|
+
}
|
|
116
|
+
if (this.oldCache.has(key)) {
|
|
117
|
+
const item = this.oldCache.get(key);
|
|
118
|
+
if (this._deleteIfExpired(key, item) === false) {
|
|
119
|
+
this._moveToRecent(key, item);
|
|
120
|
+
return item.value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
set(key, value, { maxAge = this.maxAge } = {}) {
|
|
125
|
+
const expiry = typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY
|
|
126
|
+
? Date.now() + maxAge
|
|
127
|
+
: undefined;
|
|
128
|
+
if (this.cache.has(key)) {
|
|
129
|
+
this.cache.set(key, {
|
|
130
|
+
value,
|
|
131
|
+
expiry,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this._set(key, { value, expiry });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
has(key) {
|
|
139
|
+
if (this.cache.has(key)) {
|
|
140
|
+
return !this._deleteIfExpired(key, this.cache.get(key));
|
|
141
|
+
}
|
|
142
|
+
if (this.oldCache.has(key)) {
|
|
143
|
+
return !this._deleteIfExpired(key, this.oldCache.get(key));
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
peek(key) {
|
|
148
|
+
if (this.cache.has(key)) {
|
|
149
|
+
return this._peek(key, this.cache);
|
|
150
|
+
}
|
|
151
|
+
if (this.oldCache.has(key)) {
|
|
152
|
+
return this._peek(key, this.oldCache);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
delete(key) {
|
|
156
|
+
const deleted = this.cache.delete(key);
|
|
157
|
+
if (deleted) {
|
|
158
|
+
this._size--;
|
|
159
|
+
}
|
|
160
|
+
return this.oldCache.delete(key) || deleted;
|
|
161
|
+
}
|
|
162
|
+
clear() {
|
|
163
|
+
this.cache.clear();
|
|
164
|
+
this.oldCache.clear();
|
|
165
|
+
this._size = 0;
|
|
166
|
+
}
|
|
167
|
+
resize(maxSize) {
|
|
168
|
+
if (!(maxSize && maxSize > 0)) {
|
|
169
|
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
|
170
|
+
}
|
|
171
|
+
const items = [...this._entriesAscending()];
|
|
172
|
+
const removeCount = items.length - maxSize;
|
|
173
|
+
if (removeCount < 0) {
|
|
174
|
+
this.cache = new Map(items);
|
|
175
|
+
this.oldCache = new Map();
|
|
176
|
+
this._size = items.length;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
if (removeCount > 0) {
|
|
180
|
+
this._emitEvictions(items.slice(0, removeCount));
|
|
181
|
+
}
|
|
182
|
+
this.oldCache = new Map(items.slice(removeCount));
|
|
183
|
+
this.cache = new Map();
|
|
184
|
+
this._size = 0;
|
|
185
|
+
}
|
|
186
|
+
this.maxSize = maxSize;
|
|
187
|
+
}
|
|
188
|
+
*keys() {
|
|
189
|
+
for (const [key] of this) {
|
|
190
|
+
yield key;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
*values() {
|
|
194
|
+
for (const [, value] of this) {
|
|
195
|
+
yield value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
*[Symbol.iterator]() {
|
|
199
|
+
for (const item of this.cache) {
|
|
200
|
+
const [key, value] = item;
|
|
201
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
202
|
+
if (deleted === false) {
|
|
203
|
+
yield [key, value.value];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const item of this.oldCache) {
|
|
207
|
+
const [key, value] = item;
|
|
208
|
+
if (!this.cache.has(key)) {
|
|
209
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
210
|
+
if (deleted === false) {
|
|
211
|
+
yield [key, value.value];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
Iterable for all entries, starting with the newest (descending in recency).
|
|
218
|
+
*/
|
|
219
|
+
*entriesDescending() {
|
|
220
|
+
let items = [...this.cache];
|
|
221
|
+
for (let i = items.length - 1; i >= 0; --i) {
|
|
222
|
+
const item = items[i];
|
|
223
|
+
const [key, value] = item;
|
|
224
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
225
|
+
if (deleted === false) {
|
|
226
|
+
yield [key, value.value];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
items = [...this.oldCache];
|
|
230
|
+
for (let i = items.length - 1; i >= 0; --i) {
|
|
231
|
+
const item = items[i];
|
|
232
|
+
const [key, value] = item;
|
|
233
|
+
if (!this.cache.has(key)) {
|
|
234
|
+
const deleted = this._deleteIfExpired(key, value);
|
|
235
|
+
if (deleted === false) {
|
|
236
|
+
yield [key, value.value];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
*entriesAscending() {
|
|
242
|
+
for (const [key, value] of this._entriesAscending()) {
|
|
243
|
+
yield [key, value.value];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
get size() {
|
|
247
|
+
if (!this._size) {
|
|
248
|
+
return this.oldCache.size;
|
|
249
|
+
}
|
|
250
|
+
let oldCacheSize = 0;
|
|
251
|
+
for (const key of this.oldCache.keys()) {
|
|
252
|
+
if (!this.cache.has(key)) {
|
|
253
|
+
oldCacheSize++;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return Math.min(this._size + oldCacheSize, this.maxSize);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const volatileGifCache = new QuickLRU({ maxSize: 30 });
|
|
261
|
+
const manuallyManagedGifCache = new Map();
|
|
262
|
+
|
|
263
|
+
const parse$1 = (stream, schema, result = {}, parent = result) => {
|
|
264
|
+
if (Array.isArray(schema)) {
|
|
265
|
+
schema.forEach((partSchema) => {
|
|
266
|
+
return parse$1(stream, partSchema, result, parent);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else if (typeof schema === 'function') {
|
|
270
|
+
schema(stream, result, parent, parse$1);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// @ts-expect-error
|
|
274
|
+
const key = Object.keys(schema)[0];
|
|
275
|
+
// @ts-expect-error
|
|
276
|
+
if (Array.isArray(schema[key])) {
|
|
277
|
+
// @ts-expect-error
|
|
278
|
+
parent[key] = {};
|
|
279
|
+
// @ts-expect-error
|
|
280
|
+
parse$1(stream, schema[key], result, parent[key]);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// @ts-expect-error
|
|
284
|
+
parent[key] = schema[key](stream, result, parent, parse$1);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
};
|
|
289
|
+
const loop = (schema, continueFunc) => {
|
|
290
|
+
return function (stream, result, parent, _parse) {
|
|
291
|
+
const arr = [];
|
|
292
|
+
let lastStreamPos = stream.pos;
|
|
293
|
+
while (continueFunc(stream, result, parent)) {
|
|
294
|
+
const newParent = {};
|
|
295
|
+
_parse(stream, schema, result, newParent); // cases when whole file is parsed but no termination is there and stream position is not getting updated as well
|
|
296
|
+
// it falls into infinite recursion, null check to avoid the same
|
|
297
|
+
if (stream.pos === lastStreamPos) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
lastStreamPos = stream.pos;
|
|
301
|
+
arr.push(newParent);
|
|
302
|
+
}
|
|
303
|
+
return arr;
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
const conditional = (schema, conditionFunc) => (stream, result, parent, parseFn) => {
|
|
307
|
+
if (conditionFunc(stream, result, parent)) {
|
|
308
|
+
parseFn(stream, schema, result, parent);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/* eslint-disable no-bitwise */
|
|
313
|
+
// Default stream and parsers for Uint8TypedArray data type
|
|
314
|
+
const buildStream = (uint8Data) => ({
|
|
315
|
+
data: uint8Data,
|
|
316
|
+
pos: 0,
|
|
317
|
+
});
|
|
318
|
+
const readByte = () => (stream) => {
|
|
319
|
+
return stream.data[stream.pos++];
|
|
320
|
+
};
|
|
321
|
+
const peekByte = (offset = 0) => (stream) => {
|
|
322
|
+
return stream.data[stream.pos + offset];
|
|
323
|
+
};
|
|
324
|
+
const readBytes = (length) => (stream) => {
|
|
325
|
+
// eslint-disable-next-line no-return-assign
|
|
326
|
+
return stream.data.subarray(stream.pos, (stream.pos += length));
|
|
327
|
+
};
|
|
328
|
+
const peekBytes = (length) => (stream) => {
|
|
329
|
+
return stream.data.subarray(stream.pos, stream.pos + length);
|
|
330
|
+
};
|
|
331
|
+
const readString = (length) => (stream) => {
|
|
332
|
+
return Array.from(readBytes(length)(stream))
|
|
333
|
+
.map((value) => String.fromCharCode(value))
|
|
334
|
+
.join('');
|
|
335
|
+
};
|
|
336
|
+
const readUnsigned = (littleEndian) => (stream) => {
|
|
337
|
+
const bytes = readBytes(2)(stream);
|
|
338
|
+
return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1];
|
|
339
|
+
};
|
|
340
|
+
const readArray = (byteSize, totalOrFunc) => (stream, result, parent) => {
|
|
341
|
+
const total = typeof totalOrFunc === 'function'
|
|
342
|
+
? totalOrFunc(stream, result, parent)
|
|
343
|
+
: totalOrFunc;
|
|
344
|
+
const parser = readBytes(byteSize);
|
|
345
|
+
const arr = new Array(total);
|
|
346
|
+
for (let i = 0; i < total; i++) {
|
|
347
|
+
arr[i] = parser(stream);
|
|
348
|
+
}
|
|
349
|
+
return arr;
|
|
350
|
+
};
|
|
351
|
+
const subBitsTotal = (bits, startIndex, length) => {
|
|
352
|
+
let result = 0;
|
|
353
|
+
for (let i = 0; i < length; i++) {
|
|
354
|
+
result += Number(bits[startIndex + i] && 2 ** (length - i - 1));
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
};
|
|
358
|
+
const readBits = (schema) => (stream) => {
|
|
359
|
+
const byte = readByte()(stream);
|
|
360
|
+
// convert the byte to bit array
|
|
361
|
+
const bits = new Array(8);
|
|
362
|
+
for (let i = 0; i < 8; i++) {
|
|
363
|
+
bits[7 - i] = Boolean(byte & (1 << i));
|
|
364
|
+
}
|
|
365
|
+
// convert the bit array to values based on the schema
|
|
366
|
+
// @ts-expect-error
|
|
367
|
+
return Object.keys(schema).reduce((res, key) => {
|
|
368
|
+
// @ts-expect-error
|
|
369
|
+
const def = schema[key];
|
|
370
|
+
if (def.length) {
|
|
371
|
+
res[key] = subBitsTotal(bits, def.index, def.length);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
res[key] = bits[def.index];
|
|
375
|
+
}
|
|
376
|
+
return res;
|
|
377
|
+
}, {});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// a set of 0x00 terminated subblocks
|
|
381
|
+
const subBlocksSchema = {
|
|
382
|
+
blocks: (stream) => {
|
|
383
|
+
const terminator = 0x00;
|
|
384
|
+
const chunks = [];
|
|
385
|
+
const streamSize = stream.data.length;
|
|
386
|
+
let total = 0;
|
|
387
|
+
for (let size = readByte()(stream); size !== terminator; size = readByte()(stream)) {
|
|
388
|
+
// size becomes undefined for some case when file is corrupted and terminator is not proper
|
|
389
|
+
// null check to avoid recursion
|
|
390
|
+
if (!size)
|
|
391
|
+
break;
|
|
392
|
+
// catch corrupted files with no terminator
|
|
393
|
+
if (stream.pos + size >= streamSize) {
|
|
394
|
+
const availableSize = streamSize - stream.pos;
|
|
395
|
+
chunks.push(readBytes(availableSize)(stream));
|
|
396
|
+
total += availableSize;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
chunks.push(readBytes(size)(stream));
|
|
400
|
+
total += size;
|
|
401
|
+
}
|
|
402
|
+
const result = new Uint8Array(total);
|
|
403
|
+
let offset = 0;
|
|
404
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
405
|
+
result.set(chunks[i], offset);
|
|
406
|
+
offset += chunks[i].length;
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
// global control extension
|
|
412
|
+
const gceSchema = conditional({
|
|
413
|
+
gce: [
|
|
414
|
+
{ codes: readBytes(2) },
|
|
415
|
+
{ byteSize: readByte() },
|
|
416
|
+
{
|
|
417
|
+
extras: readBits({
|
|
418
|
+
future: { index: 0, length: 3 },
|
|
419
|
+
disposal: { index: 3, length: 3 },
|
|
420
|
+
userInput: { index: 6 },
|
|
421
|
+
transparentColorGiven: { index: 7 },
|
|
422
|
+
}),
|
|
423
|
+
},
|
|
424
|
+
{ delay: readUnsigned(true) },
|
|
425
|
+
{ transparentColorIndex: readByte() },
|
|
426
|
+
{ terminator: readByte() },
|
|
427
|
+
],
|
|
428
|
+
}, (stream) => {
|
|
429
|
+
const codes = peekBytes(2)(stream);
|
|
430
|
+
return codes[0] === 0x21 && codes[1] === 0xf9;
|
|
431
|
+
});
|
|
432
|
+
// image pipeline block
|
|
433
|
+
const imageSchema = conditional({
|
|
434
|
+
image: [
|
|
435
|
+
{ code: readByte() },
|
|
436
|
+
{
|
|
437
|
+
descriptor: [
|
|
438
|
+
{ left: readUnsigned(true) },
|
|
439
|
+
{ top: readUnsigned(true) },
|
|
440
|
+
{ width: readUnsigned(true) },
|
|
441
|
+
{ height: readUnsigned(true) },
|
|
442
|
+
{
|
|
443
|
+
lct: readBits({
|
|
444
|
+
exists: { index: 0 },
|
|
445
|
+
interlaced: { index: 1 },
|
|
446
|
+
sort: { index: 2 },
|
|
447
|
+
future: { index: 3, length: 2 },
|
|
448
|
+
size: { index: 5, length: 3 },
|
|
449
|
+
}),
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
},
|
|
453
|
+
conditional({
|
|
454
|
+
lct: readArray(3, (_stream, _result, parent) => {
|
|
455
|
+
return 2 ** (parent.descriptor.lct.size + 1);
|
|
456
|
+
}),
|
|
457
|
+
}, (_stream, _result, parent) => {
|
|
458
|
+
return parent.descriptor.lct.exists;
|
|
459
|
+
}),
|
|
460
|
+
{ data: [{ minCodeSize: readByte() }, subBlocksSchema] },
|
|
461
|
+
],
|
|
462
|
+
}, (stream) => {
|
|
463
|
+
return peekByte()(stream) === 0x2c;
|
|
464
|
+
});
|
|
465
|
+
// plain text block
|
|
466
|
+
const textSchema = conditional({
|
|
467
|
+
text: [
|
|
468
|
+
{ codes: readBytes(2) },
|
|
469
|
+
{ blockSize: readByte() },
|
|
470
|
+
{
|
|
471
|
+
preData: (stream, _result, parent) => readBytes(parent.text.blockSize)(stream),
|
|
472
|
+
},
|
|
473
|
+
subBlocksSchema,
|
|
474
|
+
],
|
|
475
|
+
}, (stream) => {
|
|
476
|
+
const codes = peekBytes(2)(stream);
|
|
477
|
+
return codes[0] === 0x21 && codes[1] === 0x01;
|
|
478
|
+
});
|
|
479
|
+
// application block
|
|
480
|
+
const applicationSchema = conditional({
|
|
481
|
+
application: [
|
|
482
|
+
{ codes: readBytes(2) },
|
|
483
|
+
{ blockSize: readByte() },
|
|
484
|
+
{
|
|
485
|
+
id: (stream, _result, parent) => readString(parent.blockSize)(stream),
|
|
486
|
+
},
|
|
487
|
+
subBlocksSchema,
|
|
488
|
+
],
|
|
489
|
+
}, (stream) => {
|
|
490
|
+
const codes = peekBytes(2)(stream);
|
|
491
|
+
return codes[0] === 0x21 && codes[1] === 0xff;
|
|
492
|
+
});
|
|
493
|
+
// comment block
|
|
494
|
+
const commentSchema = conditional({
|
|
495
|
+
comment: [{ codes: readBytes(2) }, subBlocksSchema],
|
|
496
|
+
}, (stream) => {
|
|
497
|
+
const codes = peekBytes(2)(stream);
|
|
498
|
+
return codes[0] === 0x21 && codes[1] === 0xfe;
|
|
499
|
+
});
|
|
500
|
+
const GIF = [
|
|
501
|
+
{ header: [{ signature: readString(3) }, { version: readString(3) }] },
|
|
502
|
+
{
|
|
503
|
+
lsd: [
|
|
504
|
+
{ width: readUnsigned(true) },
|
|
505
|
+
{ height: readUnsigned(true) },
|
|
506
|
+
{
|
|
507
|
+
gct: readBits({
|
|
508
|
+
exists: { index: 0 },
|
|
509
|
+
resolution: { index: 1, length: 3 },
|
|
510
|
+
sort: { index: 4 },
|
|
511
|
+
size: { index: 5, length: 3 },
|
|
512
|
+
}),
|
|
513
|
+
},
|
|
514
|
+
{ backgroundColorIndex: readByte() },
|
|
515
|
+
{ pixelAspectRatio: readByte() },
|
|
516
|
+
],
|
|
517
|
+
},
|
|
518
|
+
conditional({
|
|
519
|
+
gct: readArray(3, (_stream, result) => 2 ** (result.lsd.gct.size + 1)),
|
|
520
|
+
}, (_stream, result) => result.lsd.gct.exists),
|
|
521
|
+
// content frames
|
|
522
|
+
{
|
|
523
|
+
frames: loop([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], (stream) => {
|
|
524
|
+
const nextCode = peekByte()(stream);
|
|
525
|
+
// rather than check for a terminator, we should check for the existence
|
|
526
|
+
// of an ext or image block to avoid infinite loops
|
|
527
|
+
// var terminator = 0x3B;
|
|
528
|
+
// return nextCode !== terminator;
|
|
529
|
+
return nextCode === 0x21 || nextCode === 0x2c;
|
|
530
|
+
}),
|
|
531
|
+
},
|
|
532
|
+
];
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Deinterlace function from https://github.com/shachaf/jsgif
|
|
536
|
+
*/
|
|
537
|
+
const deinterlace = (pixels, width) => {
|
|
538
|
+
const newPixels = new Array(pixels.length);
|
|
539
|
+
const rows = pixels.length / width;
|
|
540
|
+
const cpRow = function (toRow, _fromRow) {
|
|
541
|
+
const fromPixels = pixels.slice(_fromRow * width, (_fromRow + 1) * width);
|
|
542
|
+
newPixels.splice(...[toRow * width, width].concat(fromPixels));
|
|
543
|
+
};
|
|
544
|
+
// See appendix E.
|
|
545
|
+
const offsets = [0, 4, 2, 1];
|
|
546
|
+
const steps = [8, 8, 4, 2];
|
|
547
|
+
let fromRow = 0;
|
|
548
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
549
|
+
for (let toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
|
|
550
|
+
cpRow(toRow, fromRow);
|
|
551
|
+
fromRow++;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return newPixels;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/* eslint-disable no-multi-assign */
|
|
558
|
+
/* eslint-disable no-redeclare */
|
|
559
|
+
/* eslint-disable no-bitwise */
|
|
560
|
+
/* eslint-disable no-var */
|
|
561
|
+
/**
|
|
562
|
+
* javascript port of java LZW decompression
|
|
563
|
+
* Original java author url: https://gist.github.com/devunwired/4479231
|
|
564
|
+
*/
|
|
565
|
+
const lzw = (minCodeSize, data, pixelCount) => {
|
|
566
|
+
const MAX_STACK_SIZE = 4096;
|
|
567
|
+
const nullCode = -1;
|
|
568
|
+
const npix = pixelCount;
|
|
569
|
+
let available;
|
|
570
|
+
let code_mask;
|
|
571
|
+
let code_size;
|
|
572
|
+
let in_code;
|
|
573
|
+
let old_code;
|
|
574
|
+
var bits;
|
|
575
|
+
let code;
|
|
576
|
+
let i;
|
|
577
|
+
var datum;
|
|
578
|
+
var first;
|
|
579
|
+
var top;
|
|
580
|
+
var bi;
|
|
581
|
+
var pi;
|
|
582
|
+
const dstPixels = new Array(pixelCount);
|
|
583
|
+
const prefix = new Array(MAX_STACK_SIZE);
|
|
584
|
+
const suffix = new Array(MAX_STACK_SIZE);
|
|
585
|
+
const pixelStack = new Array(MAX_STACK_SIZE + 1);
|
|
586
|
+
// Initialize GIF data stream decoder.
|
|
587
|
+
const data_size = minCodeSize;
|
|
588
|
+
const clear = 1 << data_size;
|
|
589
|
+
const end_of_information = clear + 1;
|
|
590
|
+
available = clear + 2;
|
|
591
|
+
old_code = nullCode;
|
|
592
|
+
code_size = data_size + 1;
|
|
593
|
+
code_mask = (1 << code_size) - 1;
|
|
594
|
+
for (code = 0; code < clear; code++) {
|
|
595
|
+
prefix[code] = 0;
|
|
596
|
+
suffix[code] = code;
|
|
597
|
+
}
|
|
598
|
+
// Decode GIF pixel stream.
|
|
599
|
+
var datum;
|
|
600
|
+
var bits;
|
|
601
|
+
var first;
|
|
602
|
+
var top;
|
|
603
|
+
var pi;
|
|
604
|
+
var bi;
|
|
605
|
+
datum = bits = first = top = pi = bi = 0;
|
|
606
|
+
for (i = 0; i < npix;) {
|
|
607
|
+
if (top === 0) {
|
|
608
|
+
if (bits < code_size) {
|
|
609
|
+
// get the next byte
|
|
610
|
+
datum += data[bi] << bits;
|
|
611
|
+
bits += 8;
|
|
612
|
+
bi++;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
// Get the next code.
|
|
616
|
+
code = datum & code_mask;
|
|
617
|
+
datum >>= code_size;
|
|
618
|
+
bits -= code_size;
|
|
619
|
+
// Interpret the code
|
|
620
|
+
if (code > available || code === end_of_information) {
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
if (code === clear) {
|
|
624
|
+
// Reset decoder.
|
|
625
|
+
code_size = data_size + 1;
|
|
626
|
+
code_mask = (1 << code_size) - 1;
|
|
627
|
+
available = clear + 2;
|
|
628
|
+
old_code = nullCode;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (old_code === nullCode) {
|
|
632
|
+
pixelStack[top++] = suffix[code];
|
|
633
|
+
old_code = code;
|
|
634
|
+
first = code;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
in_code = code;
|
|
638
|
+
if (code === available) {
|
|
639
|
+
pixelStack[top++] = first;
|
|
640
|
+
code = old_code;
|
|
641
|
+
}
|
|
642
|
+
while (code > clear) {
|
|
643
|
+
pixelStack[top++] = suffix[code];
|
|
644
|
+
code = prefix[code];
|
|
645
|
+
}
|
|
646
|
+
first = suffix[code] & 0xff;
|
|
647
|
+
pixelStack[top++] = first;
|
|
648
|
+
// add a new string to the table, but only if space is available
|
|
649
|
+
// if not, just continue with current table until a clear code is found
|
|
650
|
+
// (deferred clear code implementation as per GIF spec)
|
|
651
|
+
if (available < MAX_STACK_SIZE) {
|
|
652
|
+
prefix[available] = old_code;
|
|
653
|
+
suffix[available] = first;
|
|
654
|
+
available++;
|
|
655
|
+
if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) {
|
|
656
|
+
code_size++;
|
|
657
|
+
code_mask += available;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
old_code = in_code;
|
|
661
|
+
}
|
|
662
|
+
// Pop a pixel off the pixel stack.
|
|
663
|
+
top--;
|
|
664
|
+
dstPixels[pi++] = pixelStack[top];
|
|
665
|
+
i++;
|
|
666
|
+
}
|
|
667
|
+
for (i = pi; i < npix; i++) {
|
|
668
|
+
dstPixels[i] = 0; // clear missing pixels
|
|
669
|
+
}
|
|
670
|
+
return dstPixels;
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
const parseGIF = (arrayBuffer) => {
|
|
674
|
+
const byteData = new Uint8Array(arrayBuffer);
|
|
675
|
+
return parse$1(buildStream(byteData), GIF);
|
|
676
|
+
};
|
|
677
|
+
const decompressFrame = (frame, gct) => {
|
|
678
|
+
var _a, _b;
|
|
679
|
+
if (!frame.image) {
|
|
680
|
+
console.warn('gif frame does not have associated image.');
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const { image } = frame;
|
|
684
|
+
// get the number of pixels
|
|
685
|
+
const totalPixels = image.descriptor.width * image.descriptor.height;
|
|
686
|
+
// do lzw decompression
|
|
687
|
+
let pixels = lzw(image.data.minCodeSize, image.data.blocks, totalPixels);
|
|
688
|
+
// deal with interlacing if necessary
|
|
689
|
+
if ((_a = image.descriptor.lct) === null || _a === void 0 ? void 0 : _a.interlaced) {
|
|
690
|
+
pixels = deinterlace(pixels, image.descriptor.width);
|
|
691
|
+
}
|
|
692
|
+
const resultImage = {
|
|
693
|
+
pixels,
|
|
694
|
+
dims: {
|
|
695
|
+
top: frame.image.descriptor.top,
|
|
696
|
+
left: frame.image.descriptor.left,
|
|
697
|
+
width: frame.image.descriptor.width,
|
|
698
|
+
height: frame.image.descriptor.height,
|
|
699
|
+
},
|
|
700
|
+
colorTable: ((_b = image.descriptor.lct) === null || _b === void 0 ? void 0 : _b.exists)
|
|
701
|
+
? image.lct
|
|
702
|
+
: gct,
|
|
703
|
+
delay: (frame.gce.delay || 10) * 10,
|
|
704
|
+
disposalType: frame.gce.extras.disposal,
|
|
705
|
+
transparentIndex: frame.gce.extras.transparentColorGiven
|
|
706
|
+
? frame.gce.transparentColorIndex
|
|
707
|
+
: -1,
|
|
708
|
+
};
|
|
709
|
+
return resultImage;
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const decompressFrames = (parsedGif) => {
|
|
713
|
+
return parsedGif.frames
|
|
714
|
+
.filter((f) => {
|
|
715
|
+
return !('application' in f);
|
|
716
|
+
})
|
|
717
|
+
.map((f) => {
|
|
718
|
+
const fr = f.image
|
|
719
|
+
? decompressFrame(f, parsedGif.gct)
|
|
720
|
+
: null;
|
|
721
|
+
return fr;
|
|
722
|
+
})
|
|
723
|
+
.filter(Boolean)
|
|
724
|
+
.map((f) => f);
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const validateAndFix = (gif) => {
|
|
728
|
+
let currentGce = null;
|
|
729
|
+
for (const frame of gif.frames) {
|
|
730
|
+
currentGce = frame.gce ? frame.gce : currentGce;
|
|
731
|
+
// fix loosing graphic control extension for same frames
|
|
732
|
+
if ('image' in frame && !('gce' in frame) && currentGce !== null) {
|
|
733
|
+
frame.gce = currentGce;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const parse = (src, { signal, }) => fetch(src, { signal })
|
|
738
|
+
.then((resp) => {
|
|
739
|
+
var _a;
|
|
740
|
+
if (!((_a = resp.headers.get('Content-Type')) === null || _a === void 0 ? void 0 : _a.includes('image/gif')))
|
|
741
|
+
throw Error(`Wrong content type: "${resp.headers.get('Content-Type')}"`);
|
|
742
|
+
return resp.arrayBuffer();
|
|
743
|
+
})
|
|
744
|
+
.then((buffer) => parseGIF(buffer))
|
|
745
|
+
.then((gif) => {
|
|
746
|
+
validateAndFix(gif);
|
|
747
|
+
return gif;
|
|
748
|
+
})
|
|
749
|
+
.then((gif) => Promise.all([
|
|
750
|
+
decompressFrames(gif),
|
|
751
|
+
{ width: gif.lsd.width, height: gif.lsd.height },
|
|
752
|
+
]))
|
|
753
|
+
.then(([frames, options]) => {
|
|
754
|
+
const readyFrames = [];
|
|
755
|
+
const size = options.width * options.height * 4;
|
|
756
|
+
let canvas = new Uint8ClampedArray(size);
|
|
757
|
+
for (let i = 0; i < frames.length; ++i) {
|
|
758
|
+
const frame = frames[i];
|
|
759
|
+
// Read about different disposal types
|
|
760
|
+
// https://giflib.sourceforge.net/whatsinagif/animation_and_transparency.html
|
|
761
|
+
const prevCanvas = frames[i].disposalType === 3 ? canvas.slice() : null;
|
|
762
|
+
readyFrames.push(putPixels(canvas, frame, options));
|
|
763
|
+
// Disposal type 2: The canvas should be restored to the background color
|
|
764
|
+
if (frames[i].disposalType === 2) {
|
|
765
|
+
canvas = new Uint8ClampedArray(size);
|
|
766
|
+
}
|
|
767
|
+
// Disposal type 3: The decoder should restore the canvas to its previous state before the current image was drawn
|
|
768
|
+
else if (frames[i].disposalType === 3) {
|
|
769
|
+
if (!prevCanvas) {
|
|
770
|
+
throw Error('Disposal type 3 without previous frame');
|
|
771
|
+
}
|
|
772
|
+
canvas = prevCanvas;
|
|
773
|
+
}
|
|
774
|
+
// Disposal type 1: Draw the next image on top of it
|
|
775
|
+
else {
|
|
776
|
+
canvas = readyFrames[i].slice();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
...options,
|
|
781
|
+
loaded: true,
|
|
782
|
+
delays: frames.map((frame) => frame.delay),
|
|
783
|
+
frames: readyFrames,
|
|
784
|
+
};
|
|
785
|
+
});
|
|
786
|
+
const putPixels = (typedArray, frame, gifSize) => {
|
|
787
|
+
const { width, height, top: dy, left: dx } = frame.dims;
|
|
788
|
+
const offset = dy * gifSize.width + dx;
|
|
789
|
+
for (let y = 0; y < height; y++) {
|
|
790
|
+
for (let x = 0; x < width; x++) {
|
|
791
|
+
const pPos = y * width + x;
|
|
792
|
+
const colorIndex = frame.pixels[pPos];
|
|
793
|
+
if (colorIndex !== frame.transparentIndex) {
|
|
794
|
+
const taPos = offset + y * gifSize.width + x;
|
|
795
|
+
const color = frame.colorTable[colorIndex];
|
|
796
|
+
typedArray[taPos * 4] = color[0];
|
|
797
|
+
typedArray[taPos * 4 + 1] = color[1];
|
|
798
|
+
typedArray[taPos * 4 + 2] = color[2];
|
|
799
|
+
typedArray[taPos * 4 + 3] =
|
|
800
|
+
colorIndex === frame.transparentIndex ? 0 : 255;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return typedArray;
|
|
805
|
+
};
|
|
806
|
+
const generate = (info) => {
|
|
807
|
+
return {
|
|
808
|
+
...info,
|
|
809
|
+
frames: info.frames.map((buffer) => {
|
|
810
|
+
const image = new ImageData(info.width, info.height);
|
|
811
|
+
image.data.set(new Uint8ClampedArray(buffer));
|
|
812
|
+
return image;
|
|
813
|
+
}),
|
|
814
|
+
};
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// Auto-generated by build.mjs
|
|
818
|
+
const src =
|
|
819
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
820
|
+
'"use strict";(()=>{var P=(t,r,e={},n=e)=>{if(Array.isArray(r))r.forEach(o=>P(t,o,e,n));else if(typeof r=="function")r(t,e,n,P);else{let o=Object.keys(r)[0];Array.isArray(r[o])?(n[o]={},P(t,r[o],e,n[o])):n[o]=r[o](t,e,n,P)}return e},M=(t,r)=>function(e,n,o,c){let s=[],a=e.pos;for(;r(e,n,o);){let i={};if(c(e,t,n,i),e.pos===a)break;a=e.pos,s.push(i)}return s},g=(t,r)=>(e,n,o,c)=>{r(e,n,o)&&c(e,t,n,o)};var W=t=>({data:t,pos:0}),m=()=>t=>t.data[t.pos++],U=(t=0)=>r=>r.data[r.pos+t],f=t=>r=>r.data.subarray(r.pos,r.pos+=t),k=t=>r=>r.data.subarray(r.pos,r.pos+t),v=t=>r=>Array.from(f(t)(r)).map(e=>String.fromCharCode(e)).join(""),b=t=>r=>{let e=f(2)(r);return t?(e[1]<<8)+e[0]:(e[0]<<8)+e[1]},E=(t,r)=>(e,n,o)=>{let c=typeof r=="function"?r(e,n,o):r,s=f(t),a=new Array(c);for(let i=0;i<c;i++)a[i]=s(e);return a},$=(t,r,e)=>{let n=0;for(let o=0;o<e;o++)n+=Number(t[r+o]&&2**(e-o-1));return n},I=t=>r=>{let e=m()(r),n=new Array(8);for(let o=0;o<8;o++)n[7-o]=Boolean(e&1<<o);return Object.keys(t).reduce((o,c)=>{let s=t[c];return s.length?o[c]=$(n,s.index,s.length):o[c]=n[s.index],o},{})};var z={blocks:t=>{let e=[],n=t.data.length,o=0;for(let a=m()(t);a!==0&&a;a=m()(t)){if(t.pos+a>=n){let i=n-t.pos;e.push(f(i)(t)),o+=i;break}e.push(f(a)(t)),o+=a}let c=new Uint8Array(o),s=0;for(let a=0;a<e.length;a++)c.set(e[a],s),s+=e[a].length;return c}},q=g({gce:[{codes:f(2)},{byteSize:m()},{extras:I({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:b(!0)},{transparentColorIndex:m()},{terminator:m()}]},t=>{let r=k(2)(t);return r[0]===33&&r[1]===249}),H=g({image:[{code:m()},{descriptor:[{left:b(!0)},{top:b(!0)},{width:b(!0)},{height:b(!0)},{lct:I({exists:{index:0},interlaced:{index:1},sort:{index:2},future:{index:3,length:2},size:{index:5,length:3}})}]},g({lct:E(3,(t,r,e)=>2**(e.descriptor.lct.size+1))},(t,r,e)=>e.descriptor.lct.exists),{data:[{minCodeSize:m()},z]}]},t=>U()(t)===44),J=g({text:[{codes:f(2)},{blockSize:m()},{preData:(t,r,e)=>f(e.text.blockSize)(t)},z]},t=>{let r=k(2)(t);return r[0]===33&&r[1]===1}),Q=g({application:[{codes:f(2)},{blockSize:m()},{id:(t,r,e)=>v(e.blockSize)(t)},z]},t=>{let r=k(2)(t);return r[0]===33&&r[1]===255}),V=g({comment:[{codes:f(2)},z]},t=>{let r=k(2)(t);return r[0]===33&&r[1]===254}),K=[{header:[{signature:v(3)},{version:v(3)}]},{lsd:[{width:b(!0)},{height:b(!0)},{gct:I({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:m()},{pixelAspectRatio:m()}]},g({gct:E(3,(t,r)=>2**(r.lsd.gct.size+1))},(t,r)=>r.lsd.gct.exists),{frames:M([q,Q,V,H,J],t=>{let r=U()(t);return r===33||r===44})}];var X=(t,r)=>{let e=new Array(t.length),n=t.length/r,o=function(i,d){let u=t.slice(d*r,(d+1)*r);e.splice(...[i*r,r].concat(u))},c=[0,4,2,1],s=[8,8,4,2],a=0;for(let i=0;i<4;i++)for(let d=c[i];d<n;d+=s[i])o(d,a),a++;return e};var Z=(t,r,e)=>{let c=e,s,a,i,d,u;var w;let l,p;var C,S,h,_,G;let x=new Array(e),B=new Array(4096),T=new Array(4096),F=new Array(4096+1),R=t,y=1<<R,O=y+1;for(s=y+2,u=-1,i=R+1,a=(1<<i)-1,l=0;l<y;l++)B[l]=0,T[l]=l;var C,w,S,h,G,_;for(C=w=S=h=G=_=0,p=0;p<c;){if(h===0){if(w<i){C+=r[_]<<w,w+=8,_++;continue}if(l=C&a,C>>=i,w-=i,l>s||l===O)break;if(l===y){i=R+1,a=(1<<i)-1,s=y+2,u=-1;continue}if(u===-1){F[h++]=T[l],u=l,S=l;continue}for(d=l,l===s&&(F[h++]=S,l=u);l>y;)F[h++]=T[l],l=B[l];S=T[l]&255,F[h++]=S,s<4096&&(B[s]=u,T[s]=S,s++,!(s&a)&&s<4096&&(i++,a+=s)),u=d}h--,x[G++]=F[h],p++}for(p=G;p<c;p++)x[p]=0;return x};var j=t=>{let r=new Uint8Array(t);return P(W(r),K)},D=(t,r)=>{var s,a;if(!t.image){console.warn("gif frame does not have associated image.");return}let{image:e}=t,n=e.descriptor.width*e.descriptor.height,o=Z(e.data.minCodeSize,e.data.blocks,n);return(s=e.descriptor.lct)!=null&&s.interlaced&&(o=X(o,e.descriptor.width)),{pixels:o,dims:{top:t.image.descriptor.top,left:t.image.descriptor.left,width:t.image.descriptor.width,height:t.image.descriptor.height},colorTable:(a=e.descriptor.lct)!=null&&a.exists?e.lct:r,delay:(t.gce.delay||10)*10,disposalType:t.gce.extras.disposal,transparentIndex:t.gce.extras.transparentColorGiven?t.gce.transparentColorIndex:-1}};var L=t=>t.frames.filter(r=>!("application"in r)).map(r=>r.image?D(r,t.gct):null).filter(Boolean).map(r=>r);var Y=t=>{let r=null;for(let e of t.frames)r=e.gce?e.gce:r,"image"in e&&!("gce"in e)&&r!==null&&(e.gce=r)},N=(t,{signal:r})=>fetch(t,{signal:r}).then(e=>{var n;if(!((n=e.headers.get("Content-Type"))!=null&&n.includes("image/gif")))throw Error(`Wrong content type: "${e.headers.get("Content-Type")}"`);return e.arrayBuffer()}).then(e=>j(e)).then(e=>(Y(e),e)).then(e=>Promise.all([L(e),{width:e.lsd.width,height:e.lsd.height}])).then(([e,n])=>{let o=[],c=n.width*n.height*4,s=new Uint8ClampedArray(c);for(let a=0;a<e.length;++a){let i=e[a],d=e[a].disposalType===3?s.slice():null;if(o.push(ee(s,i,n)),e[a].disposalType===2)s=new Uint8ClampedArray(c);else if(e[a].disposalType===3){if(!d)throw Error("Disposal type 3 without previous frame");s=d}else s=o[a].slice()}return{...n,loaded:!0,delays:e.map(a=>a.delay),frames:o}}),ee=(t,r,e)=>{let{width:n,height:o,top:c,left:s}=r.dims,a=c*e.width+s;for(let i=0;i<o;i++)for(let d=0;d<n;d++){let u=i*n+d,l=r.pixels[u];if(l!==r.transparentIndex){let p=a+i*e.width+d,x=r.colorTable[l];t[p*4]=x[0],t[p*4+1]=x[1],t[p*4+2]=x[2],t[p*4+3]=l===r.transparentIndex?0:255}}return t};var A=new Map;self.addEventListener("message",t=>{let{type:r,src:e}=t.data||t;switch(r){case"parse":{if(!A.has(e)){let n=new AbortController,o={signal:n.signal};A.set(e,n),N(e,o).then(c=>{self.postMessage(Object.assign(c,{src:e}),c.frames.map(s=>s.buffer))}).catch(c=>{self.postMessage({src:e,error:c,loaded:!0})}).finally(()=>{A.delete(e)})}break}case"cancel":{A.has(e)&&(A.get(e).abort(),A.delete(e));break}default:break}});})();\n';
|
|
821
|
+
|
|
822
|
+
const makeWorker = () => {
|
|
823
|
+
const blob = new Blob([src], { type: 'application/javascript' });
|
|
824
|
+
const url = URL.createObjectURL(blob);
|
|
825
|
+
const worker = new Worker(url);
|
|
826
|
+
URL.revokeObjectURL(url);
|
|
827
|
+
return worker;
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const parseGif = async ({ src, controller, }) => {
|
|
831
|
+
const raw = await parse(src, { signal: controller.signal });
|
|
832
|
+
return generate(raw);
|
|
833
|
+
};
|
|
834
|
+
const parseWithWorker = (src) => {
|
|
835
|
+
const worker = makeWorker();
|
|
836
|
+
let handler = null;
|
|
837
|
+
const prom = new Promise((resolve, reject) => {
|
|
838
|
+
handler = (e) => {
|
|
839
|
+
const message = e.data || e;
|
|
840
|
+
if (message.src === src) {
|
|
841
|
+
if (message.error) {
|
|
842
|
+
reject(new Error(message.error));
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
const data = message.error ? message : generate(message);
|
|
846
|
+
resolve(data);
|
|
847
|
+
worker.terminate();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
worker.addEventListener('message', handler);
|
|
852
|
+
worker.postMessage({ src, type: 'parse' });
|
|
853
|
+
});
|
|
854
|
+
return {
|
|
855
|
+
prom,
|
|
856
|
+
cancel: () => {
|
|
857
|
+
worker.postMessage({ src, type: 'cancel' });
|
|
858
|
+
worker.removeEventListener('message', handler);
|
|
859
|
+
worker.terminate();
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const calcDuration = (parsed) => {
|
|
865
|
+
return (parsed.delays.reduce((sum, delay) => sum + delay, 0) / 1000);
|
|
866
|
+
};
|
|
867
|
+
/**
|
|
868
|
+
* @description Gets the duration in seconds of a GIF
|
|
869
|
+
* @see [Documentation](https://www.remotion.dev/docs/gif/get-gif-duration-in-seconds)
|
|
870
|
+
*/
|
|
871
|
+
const getGifDurationInSeconds = async (src) => {
|
|
872
|
+
var _a;
|
|
873
|
+
const resolvedSrc = new URL(src, window.location.origin).href;
|
|
874
|
+
const inCache = (_a = volatileGifCache.get(resolvedSrc)) !== null && _a !== void 0 ? _a : manuallyManagedGifCache.get(resolvedSrc);
|
|
875
|
+
if (inCache) {
|
|
876
|
+
return calcDuration(inCache);
|
|
877
|
+
}
|
|
878
|
+
if (Internals.getRemotionEnvironment() === 'rendering') {
|
|
879
|
+
const renderingParsed = parseWithWorker(resolvedSrc);
|
|
880
|
+
const resolved = await renderingParsed.prom;
|
|
881
|
+
volatileGifCache.set(resolvedSrc, resolved);
|
|
882
|
+
return calcDuration(resolved);
|
|
883
|
+
}
|
|
884
|
+
const parsed = await parseGif({
|
|
885
|
+
src: resolvedSrc,
|
|
886
|
+
controller: new AbortController(),
|
|
887
|
+
});
|
|
888
|
+
volatileGifCache.set(resolvedSrc, parsed);
|
|
889
|
+
return calcDuration(parsed);
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
let elementSizeHooks = [];
|
|
893
|
+
const useElementSize = (ref) => {
|
|
894
|
+
const [size, setSize] = useState(null);
|
|
895
|
+
const observer = useMemo(() => {
|
|
896
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
return new ResizeObserver((entries) => {
|
|
900
|
+
// The contentRect returns the width without any `scale()`'s being applied. The height is wrong
|
|
901
|
+
const { contentRect } = entries[0];
|
|
902
|
+
// The clientRect returns the size with `scale()` being applied.
|
|
903
|
+
const newSize = entries[0].target.getClientRects();
|
|
904
|
+
if (!(newSize === null || newSize === void 0 ? void 0 : newSize[0])) {
|
|
905
|
+
setSize(null);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const probableCssParentScale = newSize[0].width / contentRect.width;
|
|
909
|
+
const width = newSize[0].width * (1 / probableCssParentScale);
|
|
910
|
+
const height = newSize[0].height * (1 / probableCssParentScale);
|
|
911
|
+
setSize({
|
|
912
|
+
width,
|
|
913
|
+
height,
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
}, []);
|
|
917
|
+
const updateSize = useCallback(() => {
|
|
918
|
+
if (!ref.current) {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
const rect = ref.current.getClientRects();
|
|
922
|
+
if (!rect[0]) {
|
|
923
|
+
setSize(null);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
setSize({
|
|
927
|
+
width: rect[0].width,
|
|
928
|
+
height: rect[0].height,
|
|
929
|
+
});
|
|
930
|
+
}, [ref]);
|
|
931
|
+
useEffect(() => {
|
|
932
|
+
if (!observer) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
updateSize();
|
|
936
|
+
const { current } = ref;
|
|
937
|
+
if (ref.current) {
|
|
938
|
+
observer.observe(ref.current);
|
|
939
|
+
}
|
|
940
|
+
return () => {
|
|
941
|
+
if (current) {
|
|
942
|
+
observer.unobserve(current);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
}, [observer, ref, updateSize]);
|
|
946
|
+
useEffect(() => {
|
|
947
|
+
elementSizeHooks.push(updateSize);
|
|
948
|
+
return () => {
|
|
949
|
+
elementSizeHooks = elementSizeHooks.filter((e) => e !== updateSize);
|
|
950
|
+
};
|
|
951
|
+
}, [updateSize]);
|
|
952
|
+
return size;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
const calcArgs = (fit, frameSize, canvasSize) => {
|
|
956
|
+
switch (fit) {
|
|
957
|
+
case 'fill': {
|
|
958
|
+
return [
|
|
959
|
+
0,
|
|
960
|
+
0,
|
|
961
|
+
frameSize.width,
|
|
962
|
+
frameSize.height,
|
|
963
|
+
0,
|
|
964
|
+
0,
|
|
965
|
+
canvasSize.width,
|
|
966
|
+
canvasSize.height,
|
|
967
|
+
];
|
|
968
|
+
}
|
|
969
|
+
case 'contain': {
|
|
970
|
+
const ratio = Math.min(canvasSize.width / frameSize.width, canvasSize.height / frameSize.height);
|
|
971
|
+
const centerX = (canvasSize.width - frameSize.width * ratio) / 2;
|
|
972
|
+
const centerY = (canvasSize.height - frameSize.height * ratio) / 2;
|
|
973
|
+
return [
|
|
974
|
+
0,
|
|
975
|
+
0,
|
|
976
|
+
frameSize.width,
|
|
977
|
+
frameSize.height,
|
|
978
|
+
centerX,
|
|
979
|
+
centerY,
|
|
980
|
+
frameSize.width * ratio,
|
|
981
|
+
frameSize.height * ratio,
|
|
982
|
+
];
|
|
983
|
+
}
|
|
984
|
+
case 'cover': {
|
|
985
|
+
const ratio = Math.max(canvasSize.width / frameSize.width, canvasSize.height / frameSize.height);
|
|
986
|
+
const centerX = (canvasSize.width - frameSize.width * ratio) / 2;
|
|
987
|
+
const centerY = (canvasSize.height - frameSize.height * ratio) / 2;
|
|
988
|
+
return [
|
|
989
|
+
0,
|
|
990
|
+
0,
|
|
991
|
+
frameSize.width,
|
|
992
|
+
frameSize.height,
|
|
993
|
+
centerX,
|
|
994
|
+
centerY,
|
|
995
|
+
frameSize.width * ratio,
|
|
996
|
+
frameSize.height * ratio,
|
|
997
|
+
];
|
|
998
|
+
}
|
|
999
|
+
default:
|
|
1000
|
+
throw new Error('Unknown fit: ' + fit);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
const makeCanvas = () => {
|
|
1004
|
+
if (typeof document === 'undefined') {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
const canvas = document.createElement('canvas');
|
|
1008
|
+
const ctx = canvas.getContext('2d');
|
|
1009
|
+
canvas.width = 0;
|
|
1010
|
+
canvas.height = 0;
|
|
1011
|
+
return ctx;
|
|
1012
|
+
};
|
|
1013
|
+
const Canvas = forwardRef(({ index, frames, width, height, fit, className, style }, ref) => {
|
|
1014
|
+
const canvasRef = useRef(null);
|
|
1015
|
+
const [tempCtx] = useState(() => {
|
|
1016
|
+
return makeCanvas();
|
|
1017
|
+
});
|
|
1018
|
+
const size = useElementSize(canvasRef);
|
|
1019
|
+
useImperativeHandle(ref, () => {
|
|
1020
|
+
return canvasRef.current;
|
|
1021
|
+
}, []);
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
var _a;
|
|
1024
|
+
if (!size) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const imageData = frames[index];
|
|
1028
|
+
const ctx = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getContext('2d');
|
|
1029
|
+
if (imageData && tempCtx && ctx) {
|
|
1030
|
+
if (tempCtx.canvas.width < imageData.width ||
|
|
1031
|
+
tempCtx.canvas.height < imageData.height) {
|
|
1032
|
+
tempCtx.canvas.width = imageData.width;
|
|
1033
|
+
tempCtx.canvas.height = imageData.height;
|
|
1034
|
+
}
|
|
1035
|
+
if (size.width > 0 && size.height > 0) {
|
|
1036
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
1037
|
+
tempCtx.clearRect(0, 0, tempCtx.canvas.width, tempCtx.canvas.height);
|
|
1038
|
+
}
|
|
1039
|
+
tempCtx.putImageData(imageData, 0, 0);
|
|
1040
|
+
ctx.drawImage(tempCtx.canvas, ...calcArgs(fit, imageData, { width: size.width, height: size.height }));
|
|
1041
|
+
}
|
|
1042
|
+
}, [index, frames, fit, tempCtx, size]);
|
|
1043
|
+
return (jsx("canvas", { ref: canvasRef, className: className, style: style, width: width !== null && width !== void 0 ? width : size === null || size === void 0 ? void 0 : size.width, height: height !== null && height !== void 0 ? height : size === null || size === void 0 ? void 0 : size.height }));
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
const isCorsError = (error) => {
|
|
1047
|
+
return (
|
|
1048
|
+
// Chrome
|
|
1049
|
+
error.message.includes('Failed to fetch') ||
|
|
1050
|
+
// Safari
|
|
1051
|
+
error.message.includes('Load failed') ||
|
|
1052
|
+
// Firefox
|
|
1053
|
+
error.message.includes('NetworkError when attempting to fetch resource'));
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
const resolveGifSource = (src) => {
|
|
1057
|
+
return new URL(src, typeof window === 'undefined' ? undefined : window.location.origin).href;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
function useCurrentGifIndex(delays, loopBehavior) {
|
|
1061
|
+
const currentFrame = useCurrentFrame();
|
|
1062
|
+
const videoConfig = useVideoConfig();
|
|
1063
|
+
const duration = useMemo(() => {
|
|
1064
|
+
if (delays.length !== 0) {
|
|
1065
|
+
return delays.reduce((sum, delay) => sum + (delay !== null && delay !== void 0 ? delay : 0), 0);
|
|
1066
|
+
}
|
|
1067
|
+
return 1;
|
|
1068
|
+
}, [delays]);
|
|
1069
|
+
if (delays.length === 0) {
|
|
1070
|
+
return 0;
|
|
1071
|
+
}
|
|
1072
|
+
const time = (currentFrame / videoConfig.fps) * 1000;
|
|
1073
|
+
if (loopBehavior === 'pause-after-finish' && time >= duration) {
|
|
1074
|
+
return delays.length - 1;
|
|
1075
|
+
}
|
|
1076
|
+
if (loopBehavior === 'unmount-after-finish' && time >= duration) {
|
|
1077
|
+
return -1;
|
|
1078
|
+
}
|
|
1079
|
+
let currentTime = time % duration;
|
|
1080
|
+
for (let i = 0; i < delays.length; i++) {
|
|
1081
|
+
const delay = delays[i];
|
|
1082
|
+
if (currentTime < delay) {
|
|
1083
|
+
return i;
|
|
1084
|
+
}
|
|
1085
|
+
currentTime -= delay;
|
|
1086
|
+
}
|
|
1087
|
+
return 0;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const GifForDevelopment = forwardRef(({ src, width, height, onError, loopBehavior = 'loop', onLoad, fit = 'fill', ...props }, ref) => {
|
|
1091
|
+
const resolvedSrc = resolveGifSource(src);
|
|
1092
|
+
const [state, update] = useState(() => {
|
|
1093
|
+
var _a;
|
|
1094
|
+
const parsedGif = (_a = volatileGifCache.get(resolvedSrc)) !== null && _a !== void 0 ? _a : manuallyManagedGifCache.get(resolvedSrc);
|
|
1095
|
+
if (parsedGif === undefined) {
|
|
1096
|
+
return {
|
|
1097
|
+
delays: [],
|
|
1098
|
+
frames: [],
|
|
1099
|
+
width: 0,
|
|
1100
|
+
height: 0,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
return parsedGif;
|
|
1104
|
+
});
|
|
1105
|
+
const [error, setError] = useState(null);
|
|
1106
|
+
const [id] = useState(() => delayRender(`Rendering <Gif/> with src="${resolvedSrc}"`));
|
|
1107
|
+
const currentOnLoad = useRef(onLoad);
|
|
1108
|
+
const currentOnError = useRef(onError);
|
|
1109
|
+
currentOnLoad.current = onLoad;
|
|
1110
|
+
currentOnError.current = onError;
|
|
1111
|
+
useEffect(() => {
|
|
1112
|
+
let done = false;
|
|
1113
|
+
let aborted = false;
|
|
1114
|
+
const { prom, cancel } = parseWithWorker(resolvedSrc);
|
|
1115
|
+
const newHandle = delayRender('Loading <Gif /> with src=' + resolvedSrc);
|
|
1116
|
+
prom
|
|
1117
|
+
.then((parsed) => {
|
|
1118
|
+
var _a;
|
|
1119
|
+
(_a = currentOnLoad.current) === null || _a === void 0 ? void 0 : _a.call(currentOnLoad, parsed);
|
|
1120
|
+
update(parsed);
|
|
1121
|
+
volatileGifCache.set(resolvedSrc, parsed);
|
|
1122
|
+
done = true;
|
|
1123
|
+
continueRender(newHandle);
|
|
1124
|
+
continueRender(id);
|
|
1125
|
+
})
|
|
1126
|
+
.catch((err) => {
|
|
1127
|
+
if (aborted) {
|
|
1128
|
+
continueRender(newHandle);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (currentOnError.current) {
|
|
1132
|
+
currentOnError.current(err);
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
setError(err);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
return () => {
|
|
1139
|
+
if (!done) {
|
|
1140
|
+
aborted = true;
|
|
1141
|
+
cancel();
|
|
1142
|
+
}
|
|
1143
|
+
continueRender(newHandle);
|
|
1144
|
+
};
|
|
1145
|
+
}, [id, resolvedSrc]);
|
|
1146
|
+
if (error) {
|
|
1147
|
+
console.error(error.stack);
|
|
1148
|
+
if (isCorsError(error)) {
|
|
1149
|
+
throw new Error(`Failed to render GIF with source ${src}: "${error.message}". You must enable CORS for this URL.`);
|
|
1150
|
+
}
|
|
1151
|
+
throw new Error(`Failed to render GIF with source ${src}: "${error.message}".`);
|
|
1152
|
+
}
|
|
1153
|
+
const index = useCurrentGifIndex(state.delays, loopBehavior);
|
|
1154
|
+
if (index === -1) {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
return (jsx(Canvas, { fit: fit, index: index, frames: state.frames, width: width, height: height, ...props, ref: ref }));
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
const GifForRendering = forwardRef(({ src, width, height, onLoad, onError, loopBehavior = 'loop', fit = 'fill', ...props }, ref) => {
|
|
1161
|
+
const resolvedSrc = resolveGifSource(src);
|
|
1162
|
+
const [state, update] = useState(() => {
|
|
1163
|
+
const parsedGif = volatileGifCache.get(resolvedSrc);
|
|
1164
|
+
if (parsedGif === undefined) {
|
|
1165
|
+
return {
|
|
1166
|
+
delays: [],
|
|
1167
|
+
frames: [],
|
|
1168
|
+
width: 0,
|
|
1169
|
+
height: 0,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
return parsedGif;
|
|
1173
|
+
});
|
|
1174
|
+
const [error, setError] = useState(null);
|
|
1175
|
+
const [id] = useState(() => delayRender(`Rendering <Gif/> with src="${resolvedSrc}"`));
|
|
1176
|
+
useEffect(() => {
|
|
1177
|
+
return () => {
|
|
1178
|
+
continueRender(id);
|
|
1179
|
+
};
|
|
1180
|
+
}, [id]);
|
|
1181
|
+
const index = useCurrentGifIndex(state.delays, loopBehavior);
|
|
1182
|
+
const currentOnLoad = useRef(onLoad);
|
|
1183
|
+
const currentOnError = useRef(onError);
|
|
1184
|
+
currentOnLoad.current = onLoad;
|
|
1185
|
+
currentOnError.current = onError;
|
|
1186
|
+
useEffect(() => {
|
|
1187
|
+
const controller = new AbortController();
|
|
1188
|
+
let done = false;
|
|
1189
|
+
let aborted = false;
|
|
1190
|
+
const newHandle = delayRender('Loading <Gif /> with src=' + resolvedSrc);
|
|
1191
|
+
parseGif({ controller, src: resolvedSrc })
|
|
1192
|
+
.then((parsed) => {
|
|
1193
|
+
var _a;
|
|
1194
|
+
(_a = currentOnLoad.current) === null || _a === void 0 ? void 0 : _a.call(currentOnLoad, parsed);
|
|
1195
|
+
update(parsed);
|
|
1196
|
+
volatileGifCache.set(resolvedSrc, parsed);
|
|
1197
|
+
done = true;
|
|
1198
|
+
continueRender(newHandle);
|
|
1199
|
+
continueRender(id);
|
|
1200
|
+
})
|
|
1201
|
+
.catch((err) => {
|
|
1202
|
+
if (aborted) {
|
|
1203
|
+
continueRender(newHandle);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (currentOnError.current) {
|
|
1207
|
+
currentOnError.current(err);
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
setError(err);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
return () => {
|
|
1214
|
+
if (!done) {
|
|
1215
|
+
aborted = true;
|
|
1216
|
+
controller.abort();
|
|
1217
|
+
}
|
|
1218
|
+
continueRender(newHandle);
|
|
1219
|
+
};
|
|
1220
|
+
}, [id, resolvedSrc]);
|
|
1221
|
+
if (error) {
|
|
1222
|
+
console.error(error.stack);
|
|
1223
|
+
if (isCorsError(error)) {
|
|
1224
|
+
throw new Error(`Failed to render GIF with source ${src}: "${error.message}". You must enable CORS for this URL.`);
|
|
1225
|
+
}
|
|
1226
|
+
throw new Error(`Failed to render GIF with source ${src}: "${error.message}". Render with --log=verbose to see the full stack.`);
|
|
1227
|
+
}
|
|
1228
|
+
if (index === -1) {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
return (jsx(Canvas, { fit: fit, index: index, frames: state.frames, width: width, height: height, ...props, ref: ref }));
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* @description Displays a GIF that synchronizes with Remotions useCurrentFrame().
|
|
1236
|
+
* @see [Documentation](https://www.remotion.dev/docs/gif/gif)
|
|
1237
|
+
*/
|
|
1238
|
+
const Gif = forwardRef((props, ref) => {
|
|
1239
|
+
const env = Internals.useRemotionEnvironment();
|
|
1240
|
+
if (env === 'rendering') {
|
|
1241
|
+
return jsx(GifForRendering, { ...props, ref: ref });
|
|
1242
|
+
}
|
|
1243
|
+
return jsx(GifForDevelopment, { ...props, ref: ref });
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* @description Returns an object with two entries: waitUntilDone() that returns a Promise which can be awaited and free() which will cancel preloading or free up the memory if the GIF is not being used anymore.
|
|
1248
|
+
* @see [Documentation](https://www.remotion.dev/docs/gif/preload-gif)
|
|
1249
|
+
*/
|
|
1250
|
+
const preloadGif = (src) => {
|
|
1251
|
+
const resolvedSrc = resolveGifSource(src);
|
|
1252
|
+
if (volatileGifCache.has(resolvedSrc)) {
|
|
1253
|
+
return {
|
|
1254
|
+
waitUntilDone: () => Promise.resolve(),
|
|
1255
|
+
free: () => volatileGifCache.delete(resolvedSrc),
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
if (manuallyManagedGifCache.has(resolvedSrc)) {
|
|
1259
|
+
return {
|
|
1260
|
+
waitUntilDone: () => Promise.resolve(),
|
|
1261
|
+
free: () => manuallyManagedGifCache.delete(resolvedSrc),
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
const { prom, cancel } = parseWithWorker(resolvedSrc);
|
|
1265
|
+
let deleted = false;
|
|
1266
|
+
prom.then((p) => {
|
|
1267
|
+
if (!deleted) {
|
|
1268
|
+
manuallyManagedGifCache.set(resolvedSrc, p);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
return {
|
|
1272
|
+
waitUntilDone: () => prom.then(() => undefined),
|
|
1273
|
+
free: () => {
|
|
1274
|
+
cancel();
|
|
1275
|
+
deleted = true;
|
|
1276
|
+
manuallyManagedGifCache.delete(resolvedSrc);
|
|
1277
|
+
},
|
|
1278
|
+
};
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
export { Gif, getGifDurationInSeconds, preloadGif };
|