@tensorflow/tfjs-data
Version:
TensorFlow Data API in JavaScript
192 lines • 25.9 kB
JavaScript
/**
* @license
* Copyright 2018 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
import { browser, cast, env, expandDims, image, reshape, tensor1d, tensor2d, tidy, util } from '@tensorflow/tfjs-core';
import { LazyIterator } from './lazy_iterator';
/**
* Provide a stream of image tensors from webcam video stream. Only works in
* browser environment.
*/
export class WebcamIterator extends LazyIterator {
constructor(webcamVideoElement, webcamConfig) {
super();
this.webcamVideoElement = webcamVideoElement;
this.webcamConfig = webcamConfig;
this.isClosed = true;
this.resize = false;
if (this.needToResize()) {
this.resize = true;
this.cropSize =
[this.webcamConfig.resizeHeight, this.webcamConfig.resizeWidth];
this.cropBoxInd = tensor1d([0], 'int32');
if (this.webcamConfig.centerCrop) {
// Calculate the box based on resizing shape.
const widthCroppingRatio = this.webcamConfig.resizeWidth * 1.0 / this.webcamVideoElement.width;
const heightCroppingRatio = this.webcamConfig.resizeHeight * 1.0 /
this.webcamVideoElement.height;
const widthCropStart = (1 - widthCroppingRatio) / 2;
const heightCropStart = (1 - heightCroppingRatio) / 2;
const widthCropEnd = widthCropStart + widthCroppingRatio;
const heightCropEnd = heightCroppingRatio + heightCropStart;
this.cropBox = tensor2d([heightCropStart, widthCropStart, heightCropEnd, widthCropEnd], [1, 4]);
}
else {
this.cropBox = tensor2d([0, 0, 1, 1], [1, 4]);
}
}
}
summary() {
return `webcam`;
}
// Construct a WebcamIterator and start it's video stream.
static async create(webcamVideoElement, webcamConfig = {}) {
if (!env().get('IS_BROWSER')) {
throw new Error('tf.data.webcam is only supported in browser environment.');
}
if (!webcamVideoElement) {
// If webcam video element is not provided, create a hidden video element
// with provided width and height.
webcamVideoElement = document.createElement('video');
if (!webcamConfig.resizeWidth || !webcamConfig.resizeHeight) {
throw new Error('Please provide webcam video element, or resizeWidth and ' +
'resizeHeight to create a hidden video element.');
}
webcamVideoElement.width = webcamConfig.resizeWidth;
webcamVideoElement.height = webcamConfig.resizeHeight;
}
const webcamIterator = new WebcamIterator(webcamVideoElement, webcamConfig);
// Call async function to initialize the video stream.
await webcamIterator.start();
return webcamIterator;
}
// Async function to start video stream.
async start() {
if (this.webcamConfig.facingMode) {
util.assert((this.webcamConfig.facingMode === 'user') ||
(this.webcamConfig.facingMode === 'environment'), () => `Invalid webcam facing mode: ${this.webcamConfig.facingMode}. ` +
`Please provide 'user' or 'environment'`);
}
try {
this.stream = await navigator.mediaDevices.getUserMedia({
video: {
deviceId: this.webcamConfig.deviceId,
facingMode: this.webcamConfig.facingMode ?
this.webcamConfig.facingMode :
'user',
width: this.webcamVideoElement.width,
height: this.webcamVideoElement.height
}
});
}
catch (e) {
// Modify the error message but leave the stack trace intact
e.message = `Error thrown while initializing video stream: ${e.message}`;
throw e;
}
if (!this.stream) {
throw new Error('Could not obtain video from webcam.');
}
// Older browsers may not have srcObject
try {
this.webcamVideoElement.srcObject = this.stream;
}
catch (error) {
console.log(error);
this.webcamVideoElement.src = window.URL.createObjectURL(this.stream);
}
// Start the webcam video stream
this.webcamVideoElement.play();
this.isClosed = false;
return new Promise(resolve => {
// Add event listener to make sure the webcam has been fully initialized.
this.webcamVideoElement.onloadedmetadata = () => {
resolve();
};
});
}
async next() {
if (this.isClosed) {
return { value: null, done: true };
}
let img;
try {
img = browser.fromPixels(this.webcamVideoElement);
}
catch (e) {
throw new Error(`Error thrown converting video to pixels: ${JSON.stringify(e)}`);
}
if (this.resize) {
try {
return { value: this.cropAndResizeFrame(img), done: false };
}
catch (e) {
throw new Error(`Error thrown cropping the video: ${e.message}`);
}
finally {
img.dispose();
}
}
else {
return { value: img, done: false };
}
}
needToResize() {
// If resizeWidth and resizeHeight are provided, and different from the
// width and height of original HTMLVideoElement, then resizing and cropping
// is required.
if (this.webcamConfig.resizeWidth && this.webcamConfig.resizeHeight &&
(this.webcamVideoElement.width !== this.webcamConfig.resizeWidth ||
this.webcamVideoElement.height !== this.webcamConfig.resizeHeight)) {
return true;
}
return false;
}
// Cropping and resizing each frame based on config
cropAndResizeFrame(img) {
return tidy(() => {
const expandedImage = expandDims(cast(img, 'float32'), (0));
let resizedImage;
resizedImage = image.cropAndResize(expandedImage, this.cropBox, this.cropBoxInd, this.cropSize, 'bilinear');
// Extract image from batch cropping.
const shape = resizedImage.shape;
return reshape(resizedImage, shape.slice(1));
});
}
// Capture one frame from the video stream, and extract the value from
// iterator.next() result.
async capture() {
return (await this.next()).value;
}
// Stop the video stream and pause webcam iterator.
stop() {
const tracks = this.stream.getTracks();
tracks.forEach(track => track.stop());
try {
this.webcamVideoElement.srcObject = null;
}
catch (error) {
console.log(error);
this.webcamVideoElement.src = null;
}
this.isClosed = true;
}
// Override toArray() function to prevent collecting.
toArray() {
throw new Error('Can not convert infinite video stream to array.');
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2ViY2FtX2l0ZXJhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vdGZqcy1kYXRhL3NyYy9pdGVyYXRvcnMvd2ViY2FtX2l0ZXJhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBRUgsT0FBTyxFQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBWSxRQUFRLEVBQWdDLElBQUksRUFBRSxJQUFJLEVBQUMsTUFBTSx1QkFBdUIsQ0FBQztBQUU3SixPQUFPLEVBQUMsWUFBWSxFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFFN0M7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLGNBQWUsU0FBUSxZQUFzQjtJQVF4RCxZQUN1QixrQkFBb0MsRUFDcEMsWUFBMEI7UUFDL0MsS0FBSyxFQUFFLENBQUM7UUFGYSx1QkFBa0IsR0FBbEIsa0JBQWtCLENBQWtCO1FBQ3BDLGlCQUFZLEdBQVosWUFBWSxDQUFjO1FBVHpDLGFBQVEsR0FBRyxJQUFJLENBQUM7UUFFaEIsV0FBTSxHQUFHLEtBQUssQ0FBQztRQVNyQixJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUUsRUFBRTtZQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztZQUNuQixJQUFJLENBQUMsUUFBUTtnQkFDVCxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQyxZQUFZLENBQUMsV0FBVyxDQUFDLENBQUM7WUFDcEUsSUFBSSxDQUFDLFVBQVUsR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQztZQUN6QyxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsVUFBVSxFQUFFO2dCQUNoQyw2Q0FBNkM7Z0JBQzdDLE1BQU0sa0JBQWtCLEdBQ3BCLElBQUksQ0FBQyxZQUFZLENB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Q0FBQyxDQUFDO1lBQ25CLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDO1NBQ3BDO1FBQ0QsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7SUFDdkIsQ0FBQztJQUVELHFEQUFxRDtJQUM1QyxPQUFPO1FBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDE4IEdvb2dsZSBMTEMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgXCJMaWNlbnNlXCIpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gXCJBUyBJU1wiIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbmltcG9ydCB7YnJvd3NlciwgY2FzdCwgZW52LCBleHBhbmREaW1zLCBpbWFnZSwgcmVzaGFwZSwgdGVuc29yMWQsIFRlbnNvcjFELCB0ZW5zb3IyZCwgVGVuc29yMkQsIFRlbnNvcjNELCBUZW5zb3I0RCwgdGlkeSwgdXRpbH0gZnJvbSAnQHRlbnNvcmZsb3cvdGZqcy1jb3JlJztcbmltcG9ydCB7V2ViY2FtQ29uZmlnfSBmcm9tICcuLi90eXBlcyc7XG5pbXBvcnQge0xhenlJdGVyYXRvcn0gZnJvbSAnLi9sYXp5X2l0ZXJhdG9yJztcblxuLyoqXG4gKiBQcm92aWRlIGEgc3RyZWFtIG9mIGltYWdlIHRlbnNvcnMgZnJvbSB3ZWJjYW0gdmlkZW8gc3RyZWFtLiBPbmx5IHdvcmtzIGluXG4gKiBicm93c2VyIGVudmlyb25tZW50LlxuICovXG5leHBvcnQgY2xhc3MgV2ViY2FtSXRlcmF0b3IgZXh0ZW5kcyBMYXp5SXRlcmF0b3I8VGVuc29yM0Q+IHtcbiAgcHJpdmF0ZSBpc0Nsb3NlZCA9IHRydWU7XG4gIHByaXZhdGUgc3RyZWFtOiBNZWRpYVN0cmVhbTtcbiAgcHJpdmF0ZSByZXNpemUgPSBmYWxzZTtcbiAgcHJpdmF0ZSBjcm9wU2l6ZTogW251bWJlciwgbnVtYmVyXTtcbiAgcHJpdmF0ZSBjcm9wQm94OiBUZW5zb3IyRDtcbiAgcHJpdmF0ZSBjcm9wQm94SW5kOiBUZW5zb3IxRDtcblxuICBwcml2YXRlIGNvbnN0cnVjdG9yKFxuICAgICAgcHJvdGVjdGVkIHJlYWRvbmx5IHdlYmNhbVZpZGVvRWxlbWVudDogSFRNTFZpZGVvRWxlbWVudCxcbiAgICAgIHByb3RlY3RlZCByZWFkb25seSB3ZWJjYW1Db25maWc6IFdlYmNhbUNvbmZpZykge1xuICAgIHN1cGVyKCk7XG4gICAgaWYgKHRoaXMubmVlZFRvUmVzaXplKCkpIHtcbiAgICAgIHRoaXMucmVzaXplID0gdHJ1ZTtcbiAgICAgIHRoaXMuY3JvcFNpemUgPVxuICAgICAgICAgIFt0aGlzLndlYmNhbUNvbmZpZy5yZXNpemVIZWlnaHQsIHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZVdpZHRoXTtcbiAgICAgIHRoaXMuY3JvcEJveEluZCA9IHRlbnNvcjFkKFswXSwgJ2ludDMyJyk7XG4gICAgICBpZiAodGhpcy53ZWJjYW1Db25maWcuY2VudGVyQ3JvcCkge1xuICAgICAgICAvLyBDYWxjdWxhdGUgdGhlIGJveCBiYXNlZCBvbiByZXNpemluZyBzaGFwZS5cbiAgICAgICAgY29uc3Qgd2lkdGhDcm9wcGluZ1JhdGlvID1cbiAgICAgICAgICAgIHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZVdpZHRoICogMS4wIC8gdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQud2lkdGg7XG4gICAgICAgIGNvbnN0IGhlaWdodENyb3BwaW5nUmF0aW8gPSB0aGlzLndlYmNhbUNvbmZpZy5yZXNpemVIZWlnaHQgKiAxLjAgL1xuICAgICAgICAgICAgdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQuaGVpZ2h0O1xuICAgICAgICBjb25zdCB3aWR0aENyb3BTdGFydCA9ICgxIC0gd2lkdGhDcm9wcGluZ1JhdGlvKSAvIDI7XG4gICAgICAgIGNvbnN0IGhlaWdodENyb3BTdGFydCA9ICgxIC0gaGVpZ2h0Q3JvcHBpbmdSYXRpbykgLyAyO1xuICAgICAgICBjb25zdCB3aWR0aENyb3BFbmQgPSB3aWR0aENyb3BTdGFydCArIHdpZHRoQ3JvcHBpbmdSYXRpbztcbiAgICAgICAgY29uc3QgaGVpZ2h0Q3JvcEVuZCA9IGhlaWdodENyb3BwaW5nUmF0aW8gKyBoZWlnaHRDcm9wU3RhcnQ7XG4gICAgICAgIHRoaXMuY3JvcEJveCA9IHRlbnNvcjJkKFxuICAgICAgICAgICAgW2hlaWdodENyb3BTdGFydCwgd2lkdGhDcm9wU3RhcnQsIGhlaWdodENyb3BFbmQsIHdpZHRoQ3JvcEVuZF0sXG4gICAgICAgICAgICBbMSwgNF0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5jcm9wQm94ID0gdGVuc29yMmQoWzAsIDAsIDEsIDFdLCBbMSwgNF0pO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHN1bW1hcnkoKSB7XG4gICAgcmV0dXJuIGB3ZWJjYW1gO1xuICB9XG5cbiAgLy8gQ29uc3RydWN0IGEgV2ViY2FtSXRlcmF0b3IgYW5kIHN0YXJ0IGl0J3MgdmlkZW8gc3RyZWFtLlxuICBzdGF0aWMgYXN5bmMgY3JlYXRlKFxuICAgICAgd2ViY2FtVmlkZW9FbGVtZW50PzogSFRNTFZpZGVvRWxlbWVudCwgd2ViY2FtQ29uZmlnOiBXZWJjYW1Db25maWcgPSB7fSkge1xuICAgIGlmICghZW52KCkuZ2V0KCdJU19CUk9XU0VSJykpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAndGYuZGF0YS53ZWJjYW0gaXMgb25seSBzdXBwb3J0ZWQgaW4gYnJvd3NlciBlbnZpcm9ubWVudC4nKTtcbiAgICB9XG5cbiAgICBpZiAoIXdlYmNhbVZpZGVvRWxlbWVudCkge1xuICAgICAgLy8gSWYgd2ViY2FtIHZpZGVvIGVsZW1lbnQgaXMgbm90IHByb3ZpZGVkLCBjcmVhdGUgYSBoaWRkZW4gdmlkZW8gZWxlbWVudFxuICAgICAgLy8gd2l0aCBwcm92aWRlZCB3aWR0aCBhbmQgaGVpZ2h0LlxuICAgICAgd2ViY2FtVmlkZW9FbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndmlkZW8nKTtcbiAgICAgIGlmICghd2ViY2FtQ29uZmlnLnJlc2l6ZVdpZHRoIHx8ICF3ZWJjYW1Db25maWcucmVzaXplSGVpZ2h0KSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICdQbGVhc2UgcHJvdmlkZSB3ZWJjYW0gdmlkZW8gZWxlbWVudCwgb3IgcmVzaXplV2lkdGggYW5kICcgK1xuICAgICAgICAgICAgJ3Jlc2l6ZUhlaWdodCB0byBjcmVhdGUgYSBoaWRkZW4gdmlkZW8gZWxlbWVudC4nKTtcbiAgICAgIH1cbiAgICAgIHdlYmNhbVZpZGVvRWxlbWVudC53aWR0aCA9IHdlYmNhbUNvbmZpZy5yZXNpemVXaWR0aDtcbiAgICAgIHdlYmNhbVZpZGVvRWxlbWVudC5oZWlnaHQgPSB3ZWJjYW1Db25maWcucmVzaXplSGVpZ2h0O1xuICAgIH1cbiAgICBjb25zdCB3ZWJjYW1JdGVyYXRvciA9IG5ldyBXZWJjYW1JdGVyYXRvcih3ZWJjYW1WaWRlb0VsZW1lbnQsIHdlYmNhbUNvbmZpZyk7XG5cbiAgICAvLyBDYWxsIGFzeW5jIGZ1bmN0aW9uIHRvIGluaXRpYWxpemUgdGhlIHZpZGVvIHN0cmVhbS5cbiAgICBhd2FpdCB3ZWJjYW1JdGVyYXRvci5zdGFydCgpO1xuXG4gICAgcmV0dXJuIHdlYmNhbUl0ZXJhdG9yO1xuICB9XG5cbiAgLy8gQXN5bmMgZnVuY3Rpb24gdG8gc3RhcnQgdmlkZW8gc3RyZWFtLlxuICBhc3luYyBzdGFydCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy53ZWJjYW1Db25maWcuZmFjaW5nTW9kZSkge1xuICAgICAgdXRpbC5hc3NlcnQoXG4gICAgICAgICAgKHRoaXMud2ViY2FtQ29uZmlnLmZhY2luZ01vZGUgPT09ICd1c2VyJykgfHxcbiAgICAgICAgICAgICAgKHRoaXMud2ViY2FtQ29uZmlnLmZhY2luZ01vZGUgPT09ICdlbnZpcm9ubWVudCcpLFxuICAgICAgICAgICgpID0+XG4gICAgICAgICAgICAgIGBJbnZhbGlkIHdlYmNhbSBmYWNpbmcgbW9kZTogJHt0aGlzLndlYmNhbUNvbmZpZy5mYWNpbmdNb2RlfS4gYCArXG4gICAgICAgICAgICAgIGBQbGVhc2UgcHJvdmlkZSAndXNlcicgb3IgJ2Vudmlyb25tZW50J2ApO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICB0aGlzLnN0cmVhbSA9IGF3YWl0IG5hdmlnYXRvci5tZWRpYURldmljZXMuZ2V0VXNlck1lZGlhKHtcbiAgICAgICAgdmlkZW86IHtcbiAgICAgICAgICBkZXZpY2VJZDogdGhpcy53ZWJjYW1Db25maWcuZGV2aWNlSWQsXG4gICAgICAgICAgZmFjaW5nTW9kZTogdGhpcy53ZWJjYW1Db25maWcuZmFjaW5nTW9kZSA/XG4gICAgICAgICAgICAgIHRoaXMud2ViY2FtQ29uZmlnLmZhY2luZ01vZGUgOlxuICAgICAgICAgICAgICAndXNlcicsXG4gICAgICAgICAgd2lkdGg6IHRoaXMud2ViY2FtVmlkZW9FbGVtZW50LndpZHRoLFxuICAgICAgICAgIGhlaWdodDogdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQuaGVpZ2h0XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIC8vIE1vZGlmeSB0aGUgZXJyb3IgbWVzc2FnZSBidXQgbGVhdmUgdGhlIHN0YWNrIHRyYWNlIGludGFjdFxuICAgICAgZS5tZXNzYWdlID0gYEVycm9yIHRocm93biB3aGlsZSBpbml0aWFsaXppbmcgdmlkZW8gc3RyZWFtOiAke2UubWVzc2FnZX1gO1xuICAgICAgdGhyb3cgZTtcbiAgICB9XG5cbiAgICBpZiAoIXRoaXMuc3RyZWFtKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBvYnRhaW4gdmlkZW8gZnJvbSB3ZWJjYW0uJyk7XG4gICAgfVxuXG4gICAgLy8gT2xkZXIgYnJvd3NlcnMgbWF5IG5vdCBoYXZlIHNyY09iamVjdFxuICAgIHRyeSB7XG4gICAgICB0aGlzLndlYmNhbVZpZGVvRWxlbWVudC5zcmNPYmplY3QgPSB0aGlzLnN0cmVhbTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc29sZS5sb2coZXJyb3IpO1xuICAgICAgdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQuc3JjID0gd2luZG93LlVSTC5jcmVhdGVPYmplY3RVUkwoXG4gICAgICAgIHRoaXMuc3RyZWFtIGFzIHVua25vd24gYXMgTWVkaWFTb3VyY2UpO1xuICAgIH1cbiAgICAvLyBTdGFydCB0aGUgd2ViY2FtIHZpZGVvIHN0cmVhbVxuICAgIHRoaXMud2ViY2FtVmlkZW9FbGVtZW50LnBsYXkoKTtcblxuICAgIHRoaXMuaXNDbG9zZWQgPSBmYWxzZTtcblxuICAgIHJldHVybiBuZXcgUHJvbWlzZTx2b2lkPihyZXNvbHZlID0+IHtcbiAgICAgIC8vIEFkZCBldmVudCBsaXN0ZW5lciB0byBtYWtlIHN1cmUgdGhlIHdlYmNhbSBoYXMgYmVlbiBmdWxseSBpbml0aWFsaXplZC5cbiAgICAgIHRoaXMud2ViY2FtVmlkZW9FbGVtZW50Lm9ubG9hZGVkbWV0YWRhdGEgPSAoKSA9PiB7XG4gICAgICAgIHJlc29sdmUoKTtcbiAgICAgIH07XG4gICAgfSk7XG4gIH1cblxuICBhc3luYyBuZXh0KCk6IFByb21pc2U8SXRlcmF0b3JSZXN1bHQ8VGVuc29yM0Q+PiB7XG4gICAgaWYgKHRoaXMuaXNDbG9zZWQpIHtcbiAgICAgIHJldHVybiB7dmFsdWU6IG51bGwsIGRvbmU6IHRydWV9O1xuICAgIH1cblxuICAgIGxldCBpbWc7XG4gICAgdHJ5IHtcbiAgICAgIGltZyA9IGJyb3dzZXIuZnJvbVBpeGVscyh0aGlzLndlYmNhbVZpZGVvRWxlbWVudCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgIGBFcnJvciB0aHJvd24gY29udmVydGluZyB2aWRlbyB0byBwaXhlbHM6ICR7SlNPTi5zdHJpbmdpZnkoZSl9YCk7XG4gICAgfVxuICAgIGlmICh0aGlzLnJlc2l6ZSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIHt2YWx1ZTogdGhpcy5jcm9wQW5kUmVzaXplRnJhbWUoaW1nKSwgZG9uZTogZmFsc2V9O1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEVycm9yIHRocm93biBjcm9wcGluZyB0aGUgdmlkZW86ICR7ZS5tZXNzYWdlfWApO1xuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgaW1nLmRpc3Bvc2UoKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHt2YWx1ZTogaW1nLCBkb25lOiBmYWxzZX07XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBuZWVkVG9SZXNpemUoKSB7XG4gICAgLy8gSWYgcmVzaXplV2lkdGggYW5kIHJlc2l6ZUhlaWdodCBhcmUgcHJvdmlkZWQsIGFuZCBkaWZmZXJlbnQgZnJvbSB0aGVcbiAgICAvLyB3aWR0aCBhbmQgaGVpZ2h0IG9mIG9yaWdpbmFsIEhUTUxWaWRlb0VsZW1lbnQsIHRoZW4gcmVzaXppbmcgYW5kIGNyb3BwaW5nXG4gICAgLy8gaXMgcmVxdWlyZWQuXG4gICAgaWYgKHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZVdpZHRoICYmIHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZUhlaWdodCAmJlxuICAgICAgICAodGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQud2lkdGggIT09IHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZVdpZHRoIHx8XG4gICAgICAgICB0aGlzLndlYmNhbVZpZGVvRWxlbWVudC5oZWlnaHQgIT09IHRoaXMud2ViY2FtQ29uZmlnLnJlc2l6ZUhlaWdodCkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvLyBDcm9wcGluZyBhbmQgcmVzaXppbmcgZWFjaCBmcmFtZSBiYXNlZCBvbiBjb25maWdcbiAgY3JvcEFuZFJlc2l6ZUZyYW1lKGltZzogVGVuc29yM0QpOiBUZW5zb3IzRCB7XG4gICAgcmV0dXJuIHRpZHkoKCkgPT4ge1xuICAgICAgY29uc3QgZXhwYW5kZWRJbWFnZTogVGVuc29yNEQgPSBleHBhbmREaW1zKGNhc3QoaW1nLCAnZmxvYXQzMicpLCAoMCkpO1xuICAgICAgbGV0IHJlc2l6ZWRJbWFnZTtcbiAgICAgIHJlc2l6ZWRJbWFnZSA9IGltYWdlLmNyb3BBbmRSZXNpemUoXG4gICAgICAgICAgZXhwYW5kZWRJbWFnZSwgdGhpcy5jcm9wQm94LCB0aGlzLmNyb3BCb3hJbmQsIHRoaXMuY3JvcFNpemUsXG4gICAgICAgICAgJ2JpbGluZWFyJyk7XG4gICAgICAvLyBFeHRyYWN0IGltYWdlIGZyb20gYmF0Y2ggY3JvcHBpbmcuXG4gICAgICBjb25zdCBzaGFwZSA9IHJlc2l6ZWRJbWFnZS5zaGFwZTtcbiAgICAgIHJldHVybiByZXNoYXBlKHJlc2l6ZWRJbWFnZSwgc2hhcGUuc2xpY2UoMSkgYXMgW251bWJlciwgbnVtYmVyLCBudW1iZXJdKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8vIENhcHR1cmUgb25lIGZyYW1lIGZyb20gdGhlIHZpZGVvIHN0cmVhbSwgYW5kIGV4dHJhY3QgdGhlIHZhbHVlIGZyb21cbiAgLy8gaXRlcmF0b3IubmV4dCgpIHJlc3VsdC5cbiAgYXN5bmMgY2FwdHVyZSgpOiBQcm9taXNlPFRlbnNvcjNEPiB7XG4gICAgcmV0dXJuIChhd2FpdCB0aGlzLm5leHQoKSkudmFsdWU7XG4gIH1cblxuICAvLyBTdG9wIHRoZSB2aWRlbyBzdHJlYW0gYW5kIHBhdXNlIHdlYmNhbSBpdGVyYXRvci5cbiAgc3RvcCgpOiB2b2lkIHtcbiAgICBjb25zdCB0cmFja3MgPSB0aGlzLnN0cmVhbS5nZXRUcmFja3MoKTtcblxuICAgIHRyYWNrcy5mb3JFYWNoKHRyYWNrID0+IHRyYWNrLnN0b3AoKSk7XG5cbiAgICB0cnkge1xuICAgICAgdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQuc3JjT2JqZWN0ID0gbnVsbDtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc29sZS5sb2coZXJyb3IpO1xuICAgICAgdGhpcy53ZWJjYW1WaWRlb0VsZW1lbnQuc3JjID0gbnVsbDtcbiAgICB9XG4gICAgdGhpcy5pc0Nsb3NlZCA9IHRydWU7XG4gIH1cblxuICAvLyBPdmVycmlkZSB0b0FycmF5KCkgZnVuY3Rpb24gdG8gcHJldmVudCBjb2xsZWN0aW5nLlxuICBvdmVycmlkZSB0b0FycmF5KCk6IFByb21pc2U8VGVuc29yM0RbXT4ge1xuICAgIHRocm93IG5ldyBFcnJvcignQ2FuIG5vdCBjb252ZXJ0IGluZmluaXRlIHZpZGVvIHN0cmVhbSB0byBhcnJheS4nKTtcbiAgfVxufVxuIl19