@twick/ffmpeg 0.14.21 → 0.15.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/dist/index.cjs +888 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +138 -0
- package/dist/index.d.ts +138 -6
- package/dist/index.js +840 -22
- package/dist/index.js.map +1 -0
- package/package.json +29 -11
- package/dist/ffmpeg-exporter-server.d.ts +0 -26
- package/dist/ffmpeg-exporter-server.d.ts.map +0 -1
- package/dist/ffmpeg-exporter-server.js +0 -90
- package/dist/generate-audio.d.ts +0 -13
- package/dist/generate-audio.d.ts.map +0 -1
- package/dist/generate-audio.js +0 -195
- package/dist/image-stream.d.ts +0 -8
- package/dist/image-stream.d.ts.map +0 -1
- package/dist/image-stream.js +0 -25
- package/dist/index.d.ts.map +0 -1
- package/dist/settings.d.ts +0 -22
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js +0 -56
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/utils.d.ts +0 -21
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -232
- package/dist/video-frame-extractor.d.ts +0 -58
- package/dist/video-frame-extractor.d.ts.map +0 -1
- package/dist/video-frame-extractor.js +0 -265
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
type VideoFrameExtractorState = 'processing' | 'done' | 'error';
|
|
2
|
-
/**
|
|
3
|
-
* Walks through a video file and extracts frames.
|
|
4
|
-
*/
|
|
5
|
-
export declare class VideoFrameExtractor {
|
|
6
|
-
private static readonly chunkLengthInSeconds;
|
|
7
|
-
private readonly ffmpegPath;
|
|
8
|
-
state: VideoFrameExtractorState;
|
|
9
|
-
filePath: string;
|
|
10
|
-
private downloadedFilePath;
|
|
11
|
-
private buffer;
|
|
12
|
-
private bufferOffset;
|
|
13
|
-
private imageBuffers;
|
|
14
|
-
private lastImage;
|
|
15
|
-
private startTime;
|
|
16
|
-
private startTimeOffset;
|
|
17
|
-
private duration;
|
|
18
|
-
private toTime;
|
|
19
|
-
private fps;
|
|
20
|
-
private framesProcessed;
|
|
21
|
-
private width;
|
|
22
|
-
private height;
|
|
23
|
-
private frameSize;
|
|
24
|
-
private codec;
|
|
25
|
-
private process;
|
|
26
|
-
private terminated;
|
|
27
|
-
static downloadedVideoMap: Map<string, {
|
|
28
|
-
localPath: string;
|
|
29
|
-
startTimeOffset: number;
|
|
30
|
-
}>;
|
|
31
|
-
constructor(filePath: string, startTime: number, fps: number, duration: number);
|
|
32
|
-
static downloadVideoChunk(url: string, startTime: number, endTime: number): Promise<unknown>;
|
|
33
|
-
getTime(): number;
|
|
34
|
-
getLastTime(): number;
|
|
35
|
-
getLastFrame(): Buffer | null;
|
|
36
|
-
getWidth(): number;
|
|
37
|
-
getHeight(): number;
|
|
38
|
-
private getEndTime;
|
|
39
|
-
private getArgs;
|
|
40
|
-
private createFfmpegProcess;
|
|
41
|
-
/**
|
|
42
|
-
* We call this in the case that the time requested is greater than the
|
|
43
|
-
* duration of the video. In this case, we want to display the first frame
|
|
44
|
-
* of the video.
|
|
45
|
-
*
|
|
46
|
-
* Note: This does NOT match the behavior of the old implementation
|
|
47
|
-
* inside of 2d/src/lib/components/Video.ts. In the old implementation, the
|
|
48
|
-
* last frame is shown instead of the first frame.
|
|
49
|
-
*/
|
|
50
|
-
private createFfmpegProcessToExtractFirstFrame;
|
|
51
|
-
private processData;
|
|
52
|
-
popImage(): Promise<Buffer | null>;
|
|
53
|
-
private handleClose;
|
|
54
|
-
private handleError;
|
|
55
|
-
destroy(): void;
|
|
56
|
-
}
|
|
57
|
-
export {};
|
|
58
|
-
//# sourceMappingURL=video-frame-extractor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video-frame-extractor.d.ts","sourceRoot":"","sources":["../src/video-frame-extractor.ts"],"names":[],"mappings":"AASA,KAAK,wBAAwB,GAAG,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhE;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAK;IAEjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAEtD,KAAK,EAAE,wBAAwB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,kBAAkB,CAAS;IAEnC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,eAAe,CAAa;IAEpC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,UAAU,CAAkB;IAEpC,OAAc,kBAAkB,EAAE,GAAG,CACnC,MAAM,EACN;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAC,CAC7C,CAAa;gBAGZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM;WAwCJ,kBAAkB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM;IA0CV,OAAO;IAIP,WAAW;IAIX,YAAY;IAIZ,QAAQ;IAIR,SAAS;IAIhB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,OAAO;IAkCf,OAAO,CAAC,mBAAmB;IAsC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,sCAAsC;IAmC9C,OAAO,CAAC,WAAW;IAwBN,QAAQ;IAoDrB,OAAO,CAAC,WAAW;YAIL,WAAW;IA+BlB,OAAO;CAIf"}
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VideoFrameExtractor = void 0;
|
|
4
|
-
const telemetry_1 = require("@twick/telemetry");
|
|
5
|
-
const ffmpeg = require("fluent-ffmpeg");
|
|
6
|
-
const fs = require("fs");
|
|
7
|
-
const os = require("os");
|
|
8
|
-
const path = require("path");
|
|
9
|
-
const uuid_1 = require("uuid");
|
|
10
|
-
const settings_1 = require("./settings");
|
|
11
|
-
const utils_1 = require("./utils");
|
|
12
|
-
/**
|
|
13
|
-
* Walks through a video file and extracts frames.
|
|
14
|
-
*/
|
|
15
|
-
class VideoFrameExtractor {
|
|
16
|
-
constructor(filePath, startTime, fps, duration) {
|
|
17
|
-
this.ffmpegPath = settings_1.ffmpegSettings.getFfmpegPath();
|
|
18
|
-
this.buffer = Buffer.alloc(0);
|
|
19
|
-
this.bufferOffset = 0;
|
|
20
|
-
// Images are buffered in memory until they are requested.
|
|
21
|
-
this.imageBuffers = [];
|
|
22
|
-
this.lastImage = null;
|
|
23
|
-
this.framesProcessed = 0;
|
|
24
|
-
this.width = 0;
|
|
25
|
-
this.height = 0;
|
|
26
|
-
this.frameSize = 0;
|
|
27
|
-
this.codec = null;
|
|
28
|
-
this.process = null;
|
|
29
|
-
this.terminated = false;
|
|
30
|
-
this.state = 'processing';
|
|
31
|
-
this.filePath = filePath;
|
|
32
|
-
this.downloadedFilePath = VideoFrameExtractor.downloadedVideoMap.get(filePath)?.localPath;
|
|
33
|
-
this.startTimeOffset = VideoFrameExtractor.downloadedVideoMap.get(filePath)
|
|
34
|
-
?.startTimeOffset;
|
|
35
|
-
this.startTime = startTime;
|
|
36
|
-
this.duration = duration;
|
|
37
|
-
this.toTime = this.getEndTime(this.startTime);
|
|
38
|
-
this.fps = fps;
|
|
39
|
-
(0, utils_1.getVideoMetadata)(this.downloadedFilePath).then(metadata => {
|
|
40
|
-
this.width = metadata.width;
|
|
41
|
-
this.height = metadata.height;
|
|
42
|
-
this.frameSize = this.width * this.height * 4;
|
|
43
|
-
this.buffer = Buffer.alloc(this.frameSize);
|
|
44
|
-
this.codec = metadata.codec;
|
|
45
|
-
if (this.startTime >= this.duration) {
|
|
46
|
-
this.process = this.createFfmpegProcessToExtractFirstFrame(this.downloadedFilePath, this.codec);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
this.process = this.createFfmpegProcess(this.startTime - this.startTimeOffset, this.toTime, this.downloadedFilePath, this.fps, this.codec);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
static downloadVideoChunk(url, startTime, endTime) {
|
|
53
|
-
const outputDir = path.join(os.tmpdir(), `twick-decoder-chunks`);
|
|
54
|
-
if (!fs.existsSync(outputDir)) {
|
|
55
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
56
|
-
}
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
ffmpeg.ffprobe(url, (err, metadata) => {
|
|
59
|
-
if (err) {
|
|
60
|
-
reject(err);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const format = metadata.format.format_name?.split(',')[-1] || 'mp4';
|
|
64
|
-
const outputFileName = `chunk_${(0, uuid_1.v4)()}.${format}`;
|
|
65
|
-
const outputPath = path.join(outputDir, outputFileName);
|
|
66
|
-
const toleranceInSeconds = 0.5;
|
|
67
|
-
const adjustedStartTime = Math.max(startTime - toleranceInSeconds, 0);
|
|
68
|
-
ffmpeg(url)
|
|
69
|
-
.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath())
|
|
70
|
-
.inputOptions([
|
|
71
|
-
`-ss ${adjustedStartTime}`,
|
|
72
|
-
`-to ${endTime + toleranceInSeconds}`,
|
|
73
|
-
])
|
|
74
|
-
.outputOptions(['-c copy'])
|
|
75
|
-
.output(outputPath)
|
|
76
|
-
.on('end', () => {
|
|
77
|
-
this.downloadedVideoMap.set(url, {
|
|
78
|
-
localPath: outputPath,
|
|
79
|
-
startTimeOffset: adjustedStartTime,
|
|
80
|
-
});
|
|
81
|
-
resolve(outputPath);
|
|
82
|
-
})
|
|
83
|
-
.on('error', err => reject(err))
|
|
84
|
-
.run();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
getTime() {
|
|
89
|
-
return this.startTime + this.framesProcessed / this.fps;
|
|
90
|
-
}
|
|
91
|
-
getLastTime() {
|
|
92
|
-
return this.startTime + (this.framesProcessed - 1) / this.fps;
|
|
93
|
-
}
|
|
94
|
-
getLastFrame() {
|
|
95
|
-
return this.lastImage;
|
|
96
|
-
}
|
|
97
|
-
getWidth() {
|
|
98
|
-
return this.width;
|
|
99
|
-
}
|
|
100
|
-
getHeight() {
|
|
101
|
-
return this.height;
|
|
102
|
-
}
|
|
103
|
-
getEndTime(startTime) {
|
|
104
|
-
return Math.min(startTime + VideoFrameExtractor.chunkLengthInSeconds, this.duration);
|
|
105
|
-
}
|
|
106
|
-
getArgs(codec, range, fps) {
|
|
107
|
-
const inputOptions = [];
|
|
108
|
-
const outputOptions = [];
|
|
109
|
-
inputOptions.push('-loglevel', settings_1.ffmpegSettings.getLogLevel());
|
|
110
|
-
if (range) {
|
|
111
|
-
inputOptions.push(...['-ss', range[0].toFixed(2), '-to', range[1].toFixed(2)]);
|
|
112
|
-
}
|
|
113
|
-
if (codec === 'vp9') {
|
|
114
|
-
inputOptions.push('-vcodec', 'libvpx-vp9');
|
|
115
|
-
}
|
|
116
|
-
if (fps) {
|
|
117
|
-
outputOptions.push('-vf', `fps=fps=${fps}`);
|
|
118
|
-
}
|
|
119
|
-
if (!range) {
|
|
120
|
-
outputOptions.push('-vframes', '1');
|
|
121
|
-
}
|
|
122
|
-
outputOptions.push('-f', 'rawvideo');
|
|
123
|
-
outputOptions.push('-pix_fmt', 'rgba');
|
|
124
|
-
return { inputOptions, outputOptions };
|
|
125
|
-
}
|
|
126
|
-
createFfmpegProcess(startTime, toTime, filePath, fps, codec) {
|
|
127
|
-
const { inputOptions, outputOptions } = this.getArgs(codec, [startTime, toTime], fps);
|
|
128
|
-
const process = ffmpeg(filePath)
|
|
129
|
-
.setFfmpegPath(this.ffmpegPath)
|
|
130
|
-
.inputOptions(inputOptions)
|
|
131
|
-
.outputOptions(outputOptions)
|
|
132
|
-
.on('end', () => {
|
|
133
|
-
this.handleClose(0);
|
|
134
|
-
})
|
|
135
|
-
.on('error', err => {
|
|
136
|
-
this.handleError(err);
|
|
137
|
-
})
|
|
138
|
-
.on('stderr', stderrLine => {
|
|
139
|
-
console.log(stderrLine);
|
|
140
|
-
})
|
|
141
|
-
.on('stdout', stderrLine => {
|
|
142
|
-
console.log(stderrLine);
|
|
143
|
-
});
|
|
144
|
-
const ffstream = process.pipe();
|
|
145
|
-
ffstream.on('data', (data) => {
|
|
146
|
-
this.processData(data);
|
|
147
|
-
});
|
|
148
|
-
return process;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* We call this in the case that the time requested is greater than the
|
|
152
|
-
* duration of the video. In this case, we want to display the first frame
|
|
153
|
-
* of the video.
|
|
154
|
-
*
|
|
155
|
-
* Note: This does NOT match the behavior of the old implementation
|
|
156
|
-
* inside of 2d/src/lib/components/Video.ts. In the old implementation, the
|
|
157
|
-
* last frame is shown instead of the first frame.
|
|
158
|
-
*/
|
|
159
|
-
createFfmpegProcessToExtractFirstFrame(filePath, codec) {
|
|
160
|
-
const { inputOptions, outputOptions } = this.getArgs(codec, undefined, undefined);
|
|
161
|
-
const process = ffmpeg(filePath)
|
|
162
|
-
.setFfmpegPath(this.ffmpegPath)
|
|
163
|
-
.inputOptions(inputOptions)
|
|
164
|
-
.outputOptions(outputOptions)
|
|
165
|
-
.on('end', () => {
|
|
166
|
-
this.handleClose(0);
|
|
167
|
-
})
|
|
168
|
-
.on('error', err => {
|
|
169
|
-
this.handleError(err);
|
|
170
|
-
})
|
|
171
|
-
.on('stderr', stderrLine => {
|
|
172
|
-
console.log(stderrLine);
|
|
173
|
-
})
|
|
174
|
-
.on('stdout', stderrLine => {
|
|
175
|
-
console.log(stderrLine);
|
|
176
|
-
});
|
|
177
|
-
const ffstream = process.pipe();
|
|
178
|
-
ffstream.on('data', (data) => {
|
|
179
|
-
this.processData(data);
|
|
180
|
-
});
|
|
181
|
-
return process;
|
|
182
|
-
}
|
|
183
|
-
processData(data) {
|
|
184
|
-
let dataOffset = 0;
|
|
185
|
-
while (dataOffset < data.length) {
|
|
186
|
-
const remainingSpace = this.frameSize - this.bufferOffset;
|
|
187
|
-
const chunkSize = Math.min(remainingSpace, data.length - dataOffset);
|
|
188
|
-
data.copy(this.buffer, this.bufferOffset, dataOffset, dataOffset + chunkSize);
|
|
189
|
-
this.bufferOffset += chunkSize;
|
|
190
|
-
dataOffset += chunkSize;
|
|
191
|
-
// We have a complete frame
|
|
192
|
-
if (this.bufferOffset === this.frameSize) {
|
|
193
|
-
this.imageBuffers.push(Buffer.from(this.buffer)); // Create a copy
|
|
194
|
-
this.bufferOffset = 0; // Reset buffer for next frame
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
async popImage() {
|
|
199
|
-
if (this.imageBuffers.length) {
|
|
200
|
-
const image = this.imageBuffers.shift();
|
|
201
|
-
this.framesProcessed++;
|
|
202
|
-
this.lastImage = image;
|
|
203
|
-
return image;
|
|
204
|
-
}
|
|
205
|
-
if (this.state === 'error') {
|
|
206
|
-
throw new Error('An error occurred while extracting the video frames.');
|
|
207
|
-
}
|
|
208
|
-
// If the video is done and there are no more frames to extract, return the last frame.
|
|
209
|
-
if (this.state === 'done' && this.toTime >= this.duration) {
|
|
210
|
-
return this.lastImage;
|
|
211
|
-
}
|
|
212
|
-
// If there are more frames to extract, request the next chunk.
|
|
213
|
-
if (this.state === 'done') {
|
|
214
|
-
this.startTime = this.toTime;
|
|
215
|
-
this.toTime = Math.min(this.startTime + VideoFrameExtractor.chunkLengthInSeconds, this.duration);
|
|
216
|
-
if (!this.codec) {
|
|
217
|
-
throw new Error("Can't extract frames without a codec. This error should never happen.");
|
|
218
|
-
}
|
|
219
|
-
this.process = this.createFfmpegProcess(this.startTime, this.toTime, this.downloadedFilePath, this.fps, this.codec);
|
|
220
|
-
this.state = 'processing';
|
|
221
|
-
}
|
|
222
|
-
while (this.imageBuffers.length < 1) {
|
|
223
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
224
|
-
}
|
|
225
|
-
const image = this.imageBuffers.shift();
|
|
226
|
-
this.framesProcessed++;
|
|
227
|
-
this.lastImage = image;
|
|
228
|
-
return image;
|
|
229
|
-
}
|
|
230
|
-
handleClose(code) {
|
|
231
|
-
this.state = code === 0 ? 'done' : 'error';
|
|
232
|
-
}
|
|
233
|
-
async handleError(err) {
|
|
234
|
-
const code = err.code;
|
|
235
|
-
if (this.terminated) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (code === 'ENOENT') {
|
|
239
|
-
(0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, { error: 'ffmpeg-not-found' });
|
|
240
|
-
throw new Error('Error: ffmpeg not found. Make sure ffmpeg is installed on your system.');
|
|
241
|
-
}
|
|
242
|
-
else if (err.message.includes('SIGSEGV')) {
|
|
243
|
-
(0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, {
|
|
244
|
-
error: 'ffmpeg-sigsegv',
|
|
245
|
-
message: err.message,
|
|
246
|
-
});
|
|
247
|
-
throw new Error(`Error: Segmentation fault when running ffmpeg. This is a common issue on Linux, you might be able to fix it by installing nscd ('sudo apt-get install nscd'). For more information, see https://docs.re.video/common-issues/ffmpeg/`);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
await (0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, {
|
|
251
|
-
error: 'ffmpeg-error',
|
|
252
|
-
message: err.message,
|
|
253
|
-
});
|
|
254
|
-
throw new Error(`An ffmpeg error occurred while fetching frames from source video ${this.filePath}: ${err}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
destroy() {
|
|
258
|
-
this.terminated = true;
|
|
259
|
-
this.process?.kill('SIGTERM');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
exports.VideoFrameExtractor = VideoFrameExtractor;
|
|
263
|
-
VideoFrameExtractor.chunkLengthInSeconds = 5;
|
|
264
|
-
VideoFrameExtractor.downloadedVideoMap = new Map();
|
|
265
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlkZW8tZnJhbWUtZXh0cmFjdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3ZpZGVvLWZyYW1lLWV4dHJhY3Rvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxnREFBc0Q7QUFDdEQsd0NBQXdDO0FBQ3hDLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtCQUFrQztBQUNsQyx5Q0FBMEM7QUFDMUMsbUNBQXlDO0FBSXpDOztHQUVHO0FBQ0gsTUFBYSxtQkFBbUI7SUFtQzlCLFlBQ0UsUUFBZ0IsRUFDaEIsU0FBaUIsRUFDakIsR0FBVyxFQUNYLFFBQWdCO1FBcENELGVBQVUsR0FBRyx5QkFBYyxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBTXJELFdBQU0sR0FBVyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2pDLGlCQUFZLEdBQVcsQ0FBQyxDQUFDO1FBRWpDLDBEQUEwRDtRQUNsRCxpQkFBWSxHQUFhLEVBQUUsQ0FBQztRQUM1QixjQUFTLEdBQWtCLElBQUksQ0FBQztRQU9oQyxvQkFBZSxHQUFXLENBQUMsQ0FBQztRQUU1QixVQUFLLEdBQVcsQ0FBQyxDQUFDO1FBQ2xCLFdBQU0sR0FBVyxDQUFDLENBQUM7UUFDbkIsY0FBUyxHQUFXLENBQUMsQ0FBQztRQUN0QixVQUFLLEdBQWtCLElBQUksQ0FBQztRQUM1QixZQUFPLEdBQWdDLElBQUksQ0FBQztRQUM1QyxlQUFVLEdBQVksS0FBSyxDQUFDO1FBYWxDLElBQUksQ0FBQyxLQUFLLEdBQUcsWUFBWSxDQUFDO1FBQzFCLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxtQkFBbUIsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLENBQ2xFLFFBQVEsQ0FDVCxFQUFFLFNBQW1CLENBQUM7UUFDdkIsSUFBSSxDQUFDLGVBQWUsR0FBRyxtQkFBbUIsQ0FBQyxrQkFBa0IsQ0FB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
|