diff --git a/README.md b/README.md
index 367471a..35ebea9 100644
--- a/README.md
+++ b/README.md
@@ -84,6 +84,13 @@ with the AWS SDK for the Kinesis Video. This example provides examples for
* It has been tested not only for streams ingested by `PutMediaWorker` but also streams sent to Kinesis Video Streams using GStreamer Demo application (https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp)
## Release Notes
+
+### Release 1.0.4 (April 2018)
+* Add example for KinesisVideo Streams integration with Rekognition and draw Bounding Boxes for every sampled frame.
+* Fix for stream ending before reporting tags visited.
+* Same test data file for parsing and rendering example.
+* Known Issues: In `KinesisVideoRekognitionIntegrationExample`, the decode/renderer sample using JCodec may not be able to decode all mkv files.
+
### Release 1.0.3 (Februrary 2018)
* In OutputSegmentMerger, make sure that the lastClusterTimecode is updated for the first fragment.
If timecode is equal to that of a previous cluster, stop merging
diff --git a/pom.xml b/pom.xml
index cd3e100..447c695 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
amazon-kinesis-video-streams-parser-library
jar
Amazon Kinesis Video Streams Parser Library
- 1.0.3
+ 1.0.4
The Amazon Kinesis Video Streams Parser Library for Java enables Java developers to parse the streams
returned by GetMedia calls to Amazon Kinesis Video.
@@ -47,11 +47,27 @@
slf4j-api
1.7.10
+
+ com.amazonaws
+ aws-java-sdk
+ 1.11.289
+
+
+
+ com.amazonaws
+ aws-java-sdk-core
+ 1.11.289
+
com.amazonaws
aws-java-sdk-kinesisvideo
1.11.247
+
+ com.amazonaws
+ amazon-kinesis-client
+ 1.9.0
+
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/BoundingBoxImagePanel.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/BoundingBoxImagePanel.java
new file mode 100644
index 0000000..997aec5
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/BoundingBoxImagePanel.java
@@ -0,0 +1,112 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.examples;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.BoundingBox;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.FaceType;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.MatchedFace;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedOutput;
+
+/**
+ * Panel which is used for rendering frames and embedding bounding boxes on the frames.
+ */
+class BoundingBoxImagePanel extends ImagePanel {
+ private static final String DELIMITER = "-";
+ private RekognizedOutput rekognizedOutput;
+
+ @Override
+ public void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g;
+ super.paintComponent(g);
+
+ if (rekognizedOutput != null) {
+
+ // Draw bounding boxes for faces.
+ if (rekognizedOutput.getFaceSearchOutputs() != null) {
+ for (RekognizedOutput.FaceSearchOutput faceSearchOutput: rekognizedOutput.getFaceSearchOutputs()) {
+ FaceType detectedFaceType;
+ String title;
+ if (!faceSearchOutput.getMatchedFaceList().isEmpty()) {
+ // Taking First match as Rekognition returns set of matched faces sorted by confidence level
+ MatchedFace matchedFace = faceSearchOutput.getMatchedFaceList().get(0);
+ String externalImageId = matchedFace.getFace().getExternalImageId();
+ // Rekognition doesn't allow any extra attributes/tags to be associated with the 'Face'.
+ // External Image Id is used here to draw title on top of the bounding box and change color
+ // of the bounding box (based on the FaceType). External Image Id needs to be specified in
+ // below format in order get this working.
+ // Eg: PersonName1-Criminal, PersonName2-Trusted, PersonName3-Intruder etc.
+ if (externalImageId == null) {
+ // If the external image id is not specified, then draw confidence level as title.
+ title = matchedFace.getFace().getConfidence() + "";
+ detectedFaceType = FaceType.NOT_RECOGNIZED;
+ } else {
+ String[] imageIds = externalImageId.split(DELIMITER);
+ if (imageIds.length > 1) {
+ title = imageIds[0];
+ detectedFaceType = FaceType.valueOf(imageIds[1].toUpperCase());
+ } else {
+ title = "No prefix";
+ detectedFaceType = FaceType.NOT_RECOGNIZED;
+ }
+ }
+ } else {
+ detectedFaceType = FaceType.NOT_RECOGNIZED;
+ title = "Not recognized";
+ }
+ drawFaces(g2, faceSearchOutput.getDetectedFace().getBoundingBox(),
+ title, detectedFaceType.getColor());
+ }
+ }
+ }
+ }
+
+ private void drawFaces(Graphics2D g2, BoundingBox boundingBox, String personName, Color color) {
+ Color c = g2.getColor();
+
+ g2.setColor(color);
+ // Draw bounding box
+ drawBoundingBox(g2, boundingBox);
+
+ // Draw title
+ drawFaceTitle(g2, boundingBox, personName);
+ g2.setColor(c);
+ }
+
+ private void drawFaceTitle(Graphics2D g2, BoundingBox boundingBox, String personName) {
+ int left = (int) (boundingBox.getLeft() * image.getWidth());
+ int top = (int) (boundingBox.getTop() * image.getHeight());
+ g2.drawString(personName, left, top);
+ }
+
+ private void drawBoundingBox(Graphics2D g2, BoundingBox boundingBox) {
+ int left = (int) (boundingBox.getLeft() * image.getWidth());
+ int top = (int) (boundingBox.getTop() * image.getHeight());
+ int width = (int) (boundingBox.getWidth() * image.getWidth());
+ int height = (int) (boundingBox.getHeight() * image.getHeight());
+
+ // Draw bounding box
+ g2.drawRect(left, top, width, height);
+ }
+
+ public void setImage(BufferedImage bufferedImage, RekognizedOutput rekognizedOutput) {
+ this.image = bufferedImage;
+ this.rekognizedOutput = rekognizedOutput;
+ repaint();
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/ImagePanel.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/ImagePanel.java
new file mode 100644
index 0000000..5274cb1
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/ImagePanel.java
@@ -0,0 +1,43 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.examples;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JPanel;
+
+/**
+ * Panel for rendering buffered image.
+ */
+class ImagePanel extends JPanel {
+ protected BufferedImage image;
+
+ @Override
+ public void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g;
+ if (image != null) {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.clearRect(0, 0, image.getWidth(), image.getHeight());
+ g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
+ }
+ }
+
+ public void setImage(BufferedImage bufferedImage) {
+ image = bufferedImage;
+ repaint();
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoBoundingBoxFrameViewer.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoBoundingBoxFrameViewer.java
new file mode 100644
index 0000000..83d13ec
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoBoundingBoxFrameViewer.java
@@ -0,0 +1,32 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.examples;
+
+import java.awt.image.BufferedImage;
+
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedOutput;
+
+public class KinesisVideoBoundingBoxFrameViewer extends KinesisVideoFrameViewer {
+
+ public KinesisVideoBoundingBoxFrameViewer(int width, int height) {
+ super(width, height, "KinesisVideo Embedded Frame Viewer");
+ panel = new BoundingBoxImagePanel();
+ addImagePanel(panel);
+ }
+
+ public void update(BufferedImage image, RekognizedOutput rekognizedOutput) {
+ ((BoundingBoxImagePanel) panel).setImage(image, rekognizedOutput);
+ }
+}
+
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoFrameViewer.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoFrameViewer.java
index f55be90..eaf3c9c 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoFrameViewer.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoFrameViewer.java
@@ -13,31 +13,42 @@
*/
package com.amazonaws.kinesisvideo.parser.examples;
-import javax.swing.*;
-
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-
-import javax.swing.JPanel;
+import javax.swing.JFrame;
public class KinesisVideoFrameViewer extends JFrame {
- private final ImagePanel panel;
+ private final int width;
+ private final int height;
+ private final String title;
- public KinesisVideoFrameViewer(int width, int height) {
- this.setTitle("Kinesis Video Frame Viewer Sample");
+ protected ImagePanel panel;
+
+ protected KinesisVideoFrameViewer(int width, int height, String title) {
+ this.width = width;
+ this.height = height;
+ this.title = title;
+ this.setTitle(title);
this.setBackground(Color.BLACK);
+ }
+
+ public KinesisVideoFrameViewer(int width, int height) {
+ this(width, height, "Kinesis Video Frame Viewer ");
panel = new ImagePanel();
+ addImagePanel(panel);
+ }
+
+ protected void addImagePanel(final ImagePanel panel) {
panel.setPreferredSize(new Dimension(width, height));
this.add(panel);
this.pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
- System.out.println("Kinesis video viewer frame closed");
+ System.out.println(title + " closed");
System.exit(0);
}
});
@@ -48,21 +59,3 @@ public void update(BufferedImage image) {
}
}
-class ImagePanel extends JPanel {
- private BufferedImage image;
-
- @Override
- public void paintComponent(Graphics g) {
- Graphics2D g2 = (Graphics2D) g;
- if (image != null) {
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g2.clearRect(0, 0, image.getWidth(), image.getHeight());
- g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
- }
- }
-
- public void setImage(BufferedImage bufferedImage) {
- image = bufferedImage;
- repaint();
- }
-}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExample.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExample.java
new file mode 100644
index 0000000..be32654
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExample.java
@@ -0,0 +1,169 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.examples;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.kinesisvideo.parser.kinesis.KinesisDataStreamsWorker;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognitionInput;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedFragmentsIndex;
+import com.amazonaws.kinesisvideo.parser.rekognition.processor.RekognitionStreamProcessor;
+import com.amazonaws.kinesisvideo.parser.utilities.FrameVisitor;
+import com.amazonaws.kinesisvideo.parser.utilities.H264BoundingBoxFrameRenderer;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.kinesisvideo.model.StartSelector;
+import com.amazonaws.services.kinesisvideo.model.StartSelectorType;
+import lombok.Builder;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * This examples demonstrates how to integrate KVS with Rekognition and draw bounding boxes while
+ * rendering each frame in KinesisVideoFrameViewer.
+ */
+@Slf4j
+public class KinesisVideoRekognitionIntegrationExample extends KinesisVideoCommon {
+
+ private static final int DEFAULT_FRAME_WIDTH =640;
+ private static final int DEFAULT_FRAME_HEIGHT =480;
+ private static final int INITIAL_DELAY=10_000;
+ private final StreamOps streamOps;
+ private final InputStream inputStream;
+ private final ExecutorService executorService;
+ private RekognitionStreamProcessor rekognitionStreamProcessor;
+ private KinesisDataStreamsWorker kinesisDataStreamsWorker;
+ private GetMediaWorker getMediaWorker;
+ private String kdsStreamName;
+ private RekognitionInput rekognitionInput;
+
+ @Setter
+ private Integer rekognitionMaxTimeoutInMillis;
+ @Setter
+ private int width = DEFAULT_FRAME_WIDTH;
+ @Setter
+ private int height = DEFAULT_FRAME_HEIGHT;
+ private RekognizedFragmentsIndex rekognizedFragmentsIndex = new RekognizedFragmentsIndex();
+
+
+ @Builder
+ private KinesisVideoRekognitionIntegrationExample(Regions region,
+ InputStream inputStream,
+ String kvsStreamName, String kdsStreamName,
+ RekognitionInput rekognitionInput,
+ AWSCredentialsProvider credentialsProvider) {
+ super(region, credentialsProvider, kvsStreamName);
+ this.streamOps = new StreamOps(region, kvsStreamName, credentialsProvider);
+ this.inputStream = inputStream;
+ this.kdsStreamName = kdsStreamName;
+ this.rekognitionInput = rekognitionInput;
+ this.executorService = Executors.newFixedThreadPool(3);
+ }
+
+ /**
+ * This method executes the example.
+ * @param timeOutinSec Timeout in seconds
+ * @throws InterruptedException the thread is interrupted while waiting for the stream to enter the correct state.
+ * @throws IOException fails to read video from the input stream or write to the output stream.
+ */
+ public void execute(Long timeOutinSec) throws InterruptedException, IOException {
+
+ // Start the RekognitionStreamProcessor and the KinesisDataStreams worker to read and process rekognized
+ // face results. NOTE: Starting up KinesisClientLibrary can take some time, so start that first.
+ startRekognitionProcessor();
+ startKinesisDataStreamsWorker();
+ // Adding some initial delay to sync both KVS and KDS data
+ Thread.sleep(INITIAL_DELAY);
+
+ // Start a GetMedia worker to read and render KVS fragments.
+ startGetMediaWorker();
+
+ // Start a PutMedia worker to write data to a Kinesis Video Stream. NOTE: Video fragments can also be ingested
+ // using real-time producer like the Kinesis Video GStreamer sample app or AmazonKinesisVideoDemoApp
+ if (inputStream != null) {
+ startPutMediaWorker();
+ }
+
+ // Wait for the workers to finish.
+ waitForTermination(timeOutinSec);
+ cleanup();
+ }
+
+ private void startPutMediaWorker() {
+ PutMediaWorker putMediaWorker = PutMediaWorker.create(getRegion(),
+ getCredentialsProvider(),
+ getStreamName(),
+ inputStream,
+ streamOps.getAmazonKinesisVideo());
+ executorService.submit(putMediaWorker);
+ }
+
+ private void startGetMediaWorker() {
+ final KinesisVideoBoundingBoxFrameViewer kinesisVideoBoundingBoxFrameViewer =
+ new KinesisVideoBoundingBoxFrameViewer(width, height);
+ final H264BoundingBoxFrameRenderer h264BoundingBoxFrameRenderer = H264BoundingBoxFrameRenderer.create(
+ kinesisVideoBoundingBoxFrameViewer,
+ rekognizedFragmentsIndex);
+
+ if (rekognitionMaxTimeoutInMillis != null) {
+ // Change the below timeout value to if the frames need to be rendered with low latency when
+ // rekognition results are not present.
+ h264BoundingBoxFrameRenderer.setMaxTimeout(rekognitionMaxTimeoutInMillis);
+ }
+
+ final FrameVisitor frameVisitor = FrameVisitor.create(h264BoundingBoxFrameRenderer);
+ this.getMediaWorker = GetMediaWorker.create(getRegion(),
+ getCredentialsProvider(),
+ getStreamName(),
+ new StartSelector().withStartSelectorType(StartSelectorType.NOW),
+ streamOps.getAmazonKinesisVideo(),
+ frameVisitor);
+ executorService.submit(getMediaWorker);
+ }
+
+ private void startRekognitionProcessor() {
+ this.rekognitionStreamProcessor = RekognitionStreamProcessor.create(getRegion(),
+ getCredentialsProvider(),
+ rekognitionInput);
+ this.rekognitionStreamProcessor.process();
+ }
+
+ private void startKinesisDataStreamsWorker() {
+ this.kinesisDataStreamsWorker = KinesisDataStreamsWorker.create(getRegion(),
+ getCredentialsProvider(),
+ kdsStreamName,
+ rekognizedFragmentsIndex);
+ executorService.submit(kinesisDataStreamsWorker);
+ }
+
+ private void waitForTermination(final Long timeOutinSec) throws InterruptedException {
+ executorService.shutdown();
+ executorService.awaitTermination(timeOutinSec, TimeUnit.SECONDS);
+ }
+
+ private void cleanup() {
+ if (!executorService.isTerminated()) {
+ log.warn("Shutting down executor service by force");
+ executorService.shutdownNow();
+ } else {
+ log.info("Executor service is shutdown");
+ }
+ this.rekognitionStreamProcessor.stopStreamProcessor();
+ }
+}
+
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExample.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExample.java
index 7e7d256..d499bef 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExample.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExample.java
@@ -13,26 +13,23 @@
*/
package com.amazonaws.kinesisvideo.parser.examples;
-import com.amazonaws.auth.AWSCredentialsProvider;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.kinesisvideo.parser.utilities.FrameVisitor;
import com.amazonaws.kinesisvideo.parser.utilities.H264FrameRenderer;
import com.amazonaws.regions.Regions;
-
import com.amazonaws.services.kinesisvideo.model.StartSelector;
import com.amazonaws.services.kinesisvideo.model.StartSelectorType;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
/*
* Example for integrating with Kinesis Video.
* This example does:
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/StreamOps.java b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/StreamOps.java
index a53dca4..f95240d 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/examples/StreamOps.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/examples/StreamOps.java
@@ -13,20 +13,26 @@
*/
package com.amazonaws.kinesisvideo.parser.examples;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.kinesisvideo.AmazonKinesisVideo;
import com.amazonaws.services.kinesisvideo.AmazonKinesisVideoClientBuilder;
-import com.amazonaws.services.kinesisvideo.model.*;
+import com.amazonaws.services.kinesisvideo.model.CreateStreamRequest;
+import com.amazonaws.services.kinesisvideo.model.DeleteStreamRequest;
+import com.amazonaws.services.kinesisvideo.model.DescribeStreamRequest;
+import com.amazonaws.services.kinesisvideo.model.ResourceNotFoundException;
+import com.amazonaws.services.kinesisvideo.model.StreamInfo;
import lombok.Builder;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-
@Slf4j
+@Getter
public class StreamOps extends KinesisVideoCommon {
private static final long SLEEP_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(3);
private static final int DATA_RETENTION_IN_HOURS = 48;
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisDataStreamsWorker.java b/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisDataStreamsWorker.java
new file mode 100644
index 0000000..f66e3e5
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisDataStreamsWorker.java
@@ -0,0 +1,87 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.kinesis;
+
+import java.net.InetAddress;
+import java.util.UUID;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedFragmentsIndex;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorFactory;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Sample Amazon Kinesis Application.
+ */
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public final class KinesisDataStreamsWorker implements Runnable {
+
+ private static final String APPLICATION_NAME = "rekognition-kds-stream-application";
+
+ // Initial position in the stream when the application starts up for the first time.
+ // Position can be one of LATEST (most recent data) or TRIM_HORIZON (oldest available data)
+ private static final InitialPositionInStream SAMPLE_APPLICATION_INITIAL_POSITION_IN_STREAM =
+ InitialPositionInStream.LATEST;
+
+ private final Regions region;
+ private final AWSCredentialsProvider credentialsProvider;
+ private final String kdsStreamName;
+ private final RekognizedFragmentsIndex rekognizedFragmentsIndex;
+
+ public static KinesisDataStreamsWorker create(final Regions region, final AWSCredentialsProvider credentialsProvider,
+ final String kdsStreamName, final RekognizedFragmentsIndex rekognizedFragmentsIndex) {
+ return new KinesisDataStreamsWorker(region, credentialsProvider, kdsStreamName, rekognizedFragmentsIndex);
+ }
+
+ @Override
+ public void run() {
+
+ try {
+ String workerId = InetAddress.getLocalHost().getCanonicalHostName() + ":" + UUID.randomUUID();
+ KinesisClientLibConfiguration kinesisClientLibConfiguration =
+ new KinesisClientLibConfiguration(APPLICATION_NAME,
+ kdsStreamName,
+ credentialsProvider,
+ workerId);
+ kinesisClientLibConfiguration.withInitialPositionInStream(SAMPLE_APPLICATION_INITIAL_POSITION_IN_STREAM)
+ .withRegionName(region.getName());
+
+ final IRecordProcessorFactory recordProcessorFactory =
+ () -> new KinesisRecordProcessor(rekognizedFragmentsIndex, credentialsProvider);
+ final Worker worker = new Worker(recordProcessorFactory, kinesisClientLibConfiguration);
+
+ System.out.printf("Running %s to process stream %s as worker %s...",
+ APPLICATION_NAME,
+ kdsStreamName,
+ workerId);
+
+ int exitCode = 0;
+ try {
+ worker.run();
+ } catch (Throwable t) {
+ System.err.println("Caught throwable while processing data.");
+ t.printStackTrace();
+ exitCode = 1;
+ }
+ System.out.println("Exit code : " + exitCode);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisRecordProcessor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisRecordProcessor.java
new file mode 100644
index 0000000..9f373d1
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/kinesis/KinesisRecordProcessor.java
@@ -0,0 +1,257 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.kinesis;
+
+/*
+ * Copyright 2012-2018 Amazon.com, Inc. or its affiliates. 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.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.util.List;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.DetectedFace;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.FaceSearchResponse;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.MatchedFace;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognitionOutput;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedFragmentsIndex;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedOutput;
+import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException;
+import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
+import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingException;
+import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessor;
+import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorCheckpointer;
+import com.amazonaws.services.kinesis.clientlibrary.lib.worker.ShutdownReason;
+import com.amazonaws.services.kinesis.model.Record;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Processes records and checkpoints progress.
+ */
+public class KinesisRecordProcessor implements IRecordProcessor {
+
+ private static final Log LOG = LogFactory.getLog(KinesisRecordProcessor.class);
+ private String kinesisShardId;
+
+ // Backoff and retry settings
+ private static final long BACKOFF_TIME_IN_MILLIS = 3000L;
+ private static final int NUM_RETRIES = 10;
+
+ // Checkpoint about once a minute
+ private static final long CHECKPOINT_INTERVAL_MILLIS = 1000L;
+ private long nextCheckpointTimeInMillis;
+
+ private final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
+
+ private final RekognizedFragmentsIndex rekognizedFragmentsIndex;
+
+ public KinesisRecordProcessor(RekognizedFragmentsIndex rekognizedFragmentsIndex, AWSCredentialsProvider awsCredentialsProvider) {
+ this.rekognizedFragmentsIndex = rekognizedFragmentsIndex;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(String shardId) {
+ LOG.info("Initializing record processor for shard: " + shardId);
+ this.kinesisShardId = shardId;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void processRecords(List records, IRecordProcessorCheckpointer checkpointer) {
+ LOG.info("Processing " + records.size() + " records from " + kinesisShardId);
+
+ // Process records and perform all exception handling.
+ processRecordsWithRetries(records);
+
+ // Checkpoint once every checkpoint interval.
+ if (System.currentTimeMillis() > nextCheckpointTimeInMillis) {
+ checkpoint(checkpointer);
+ nextCheckpointTimeInMillis = System.currentTimeMillis() + CHECKPOINT_INTERVAL_MILLIS;
+ }
+ }
+
+ /**
+ * Process records performing retries as needed. Skip "poison pill" records.
+ *
+ * @param records Data records to be processed.
+ */
+ private void processRecordsWithRetries(List records) {
+ for (Record record : records) {
+ boolean processedSuccessfully = false;
+ for (int i = 0; i < NUM_RETRIES; i++) {
+ try {
+ processSingleRecord(record);
+ processedSuccessfully = true;
+ break;
+ } catch (Throwable t) {
+ LOG.warn("Caught throwable while processing record " + record, t);
+ }
+
+ // backoff if we encounter an exception.
+ try {
+ Thread.sleep(BACKOFF_TIME_IN_MILLIS);
+ } catch (InterruptedException e) {
+ LOG.debug("Interrupted sleep", e);
+ }
+ }
+
+ if (!processedSuccessfully) {
+ LOG.error("Couldn't process record " + record + ". Skipping the record.");
+ }
+ }
+ }
+
+ /**
+ * Process a single record.
+ *
+ * @param record The record to be processed.
+ */
+ private void processSingleRecord(Record record) {
+ // TODO Add your own record processing logic here
+
+ String data = null;
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ // For this app, we interpret the payload as UTF-8 chars.
+ ByteBuffer buffer = record.getData();
+ data = new String(buffer.array(), "UTF-8");
+ RekognitionOutput output = mapper.readValue(data, RekognitionOutput.class);
+
+ // Get the fragment number from Rekognition Output
+ final String fragmentNumber = output
+ .getInputInformation()
+ .getKinesisVideo()
+ .getFragmentNumber();
+ final Double frameOffsetInSeconds = output
+ .getInputInformation()
+ .getKinesisVideo()
+ .getFrameOffsetInSeconds();
+ final Double serverTimestamp = output
+ .getInputInformation()
+ .getKinesisVideo()
+ .getServerTimestamp();
+ final Double producerTimestamp = output
+ .getInputInformation()
+ .getKinesisVideo()
+ .getProducerTimestamp();
+ final double detectedTime = output.getInputInformation().getKinesisVideo().getServerTimestamp()
+ + output.getInputInformation().getKinesisVideo().getFrameOffsetInSeconds() * 1000L;
+ final RekognizedOutput rekognizedOutput = RekognizedOutput.builder()
+ .fragmentNumber(fragmentNumber)
+ .serverTimestamp(serverTimestamp)
+ .producerTimestamp(producerTimestamp)
+ .frameOffsetInSeconds(frameOffsetInSeconds)
+ .detectedTime(detectedTime)
+ .build();
+
+ // Add face search response
+ List responses = output.getFaceSearchResponse();
+
+ for (FaceSearchResponse response : responses) {
+ DetectedFace detectedFace = response.getDetectedFace();
+ List matchedFaces = response.getMatchedFaces();
+ RekognizedOutput.FaceSearchOutput faceSearchOutput = RekognizedOutput.FaceSearchOutput.builder()
+ .detectedFace(detectedFace)
+ .matchedFaceList(matchedFaces)
+ .build();
+ rekognizedOutput.addFaceSearchOutput(faceSearchOutput);
+ }
+
+ // Add it to the index
+ rekognizedFragmentsIndex.addToMap(fragmentNumber, rekognizedOutput);
+
+ } catch (NumberFormatException e) {
+ LOG.info("Record does not match sample record format. Ignoring record with data; " + data);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ } catch (JsonParseException e) {
+ e.printStackTrace();
+ } catch (JsonMappingException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void shutdown(IRecordProcessorCheckpointer checkpointer, ShutdownReason reason) {
+ LOG.info("Shutting down record processor for shard: " + kinesisShardId);
+ // Important to checkpoint after reaching end of shard, so we can start processing data from child shards.
+ if (reason == ShutdownReason.TERMINATE) {
+ checkpoint(checkpointer);
+ }
+ }
+
+ /** Checkpoint with retries.
+ * @param checkpointer
+ */
+ private void checkpoint(IRecordProcessorCheckpointer checkpointer) {
+ LOG.info("Checkpointing shard " + kinesisShardId);
+ for (int i = 0; i < NUM_RETRIES; i++) {
+ try {
+ checkpointer.checkpoint();
+ break;
+ } catch (ShutdownException se) {
+ // Ignore checkpoint if the processor instance has been shutdown (fail over).
+ LOG.info("Caught shutdown exception, skipping checkpoint.", se);
+ break;
+ } catch (ThrottlingException e) {
+ // Backoff and re-attempt checkpoint upon transient failures
+ if (i >= (NUM_RETRIES - 1)) {
+ LOG.error("Checkpoint failed after " + (i + 1) + "attempts.", e);
+ break;
+ } else {
+ LOG.info("Transient issue when checkpointing - attempt " + (i + 1) + " of "
+ + NUM_RETRIES, e);
+ }
+ } catch (InvalidStateException e) {
+ // This indicates an issue with the DynamoDB table (check for table, provisioned IOPS).
+ LOG.error("Cannot save checkpoint to the DynamoDB table used by the Amazon Kinesis Client Library.", e);
+ break;
+ }
+ try {
+ Thread.sleep(BACKOFF_TIME_IN_MILLIS);
+ } catch (InterruptedException e) {
+ LOG.debug("Interrupted sleep", e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/BoundingBox.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/BoundingBox.java
new file mode 100644
index 0000000..e7970bc
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/BoundingBox.java
@@ -0,0 +1,135 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BoundingBox implements Serializable
+{
+
+ @JsonProperty("Height")
+ private Double height;
+ @JsonProperty("Width")
+ private Double width;
+ @JsonProperty("Left")
+ private Double left;
+ @JsonProperty("Top")
+ private Double top;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = -3845089061670074615L;
+
+ @JsonProperty("Height")
+ public Double getHeight() {
+ return height;
+ }
+
+ @JsonProperty("Height")
+ public void setHeight(Double height) {
+ this.height = height;
+ }
+
+ @JsonProperty("Width")
+ public Double getWidth() {
+ return width;
+ }
+
+ @JsonProperty("Width")
+ public void setWidth(Double width) {
+ this.width = width;
+ }
+
+ @JsonProperty("Left")
+ public Double getLeft() {
+ return left;
+ }
+
+ @JsonProperty("Left")
+ public void setLeft(Double left) {
+ this.left = left;
+ }
+
+ @JsonProperty("Top")
+ public Double getTop() {
+ return top;
+ }
+
+ @JsonProperty("Top")
+ public void setTop(Double top) {
+ this.top = top;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("height", height)
+ .append("width", width)
+ .append("left", left)
+ .append("top", top)
+ .append("additionalProperties", additionalProperties)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(height)
+ .append(additionalProperties)
+ .append(width)
+ .append(left)
+ .append(top)
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof BoundingBox) == false) {
+ return false;
+ }
+ BoundingBox rhs = ((BoundingBox) other);
+ return new EqualsBuilder()
+ .append(height, rhs.height)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(width, rhs.width)
+ .append(left, rhs.left)
+ .append(top, rhs.top)
+ .isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/DetectedFace.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/DetectedFace.java
new file mode 100644
index 0000000..124d490
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/DetectedFace.java
@@ -0,0 +1,148 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class DetectedFace implements Serializable
+{
+
+ @JsonProperty("BoundingBox")
+ private BoundingBox boundingBox;
+ @JsonProperty("Confidence")
+ private Double confidence;
+ @JsonProperty("Landmarks")
+ private List landmarks = null;
+ @JsonProperty("Pose")
+ private Pose pose;
+ @JsonProperty("Quality")
+ private Quality quality;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 4389260550207592384L;
+
+ @JsonProperty("BoundingBox")
+ public BoundingBox getBoundingBox() {
+ return boundingBox;
+ }
+
+ @JsonProperty("BoundingBox")
+ public void setBoundingBox(BoundingBox boundingBox) {
+ this.boundingBox = boundingBox;
+ }
+
+ @JsonProperty("Confidence")
+ public Double getConfidence() {
+ return confidence;
+ }
+
+ @JsonProperty("Confidence")
+ public void setConfidence(Double confidence) {
+ this.confidence = confidence;
+ }
+
+ @JsonProperty("Landmarks")
+ public List getLandmarks() {
+ return landmarks;
+ }
+
+ @JsonProperty("Landmarks")
+ public void setLandmarks(List landmarks) {
+ this.landmarks = landmarks;
+ }
+
+ @JsonProperty("Pose")
+ public Pose getPose() {
+ return pose;
+ }
+
+ @JsonProperty("Pose")
+ public void setPose(Pose pose) {
+ this.pose = pose;
+ }
+
+ @JsonProperty("Quality")
+ public Quality getQuality() {
+ return quality;
+ }
+
+ @JsonProperty("Quality")
+ public void setQuality(Quality quality) {
+ this.quality = quality;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("boundingBox", boundingBox)
+ .append("confidence", confidence)
+ .append("landmarks", landmarks)
+ .append("pose", pose)
+ .append("quality", quality)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(pose)
+ .append(boundingBox)
+ .append(landmarks)
+ .append(additionalProperties)
+ .append(quality)
+ .append(confidence).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof DetectedFace) == false) {
+ return false;
+ }
+ DetectedFace rhs = ((DetectedFace) other);
+ return new EqualsBuilder()
+ .append(pose, rhs.pose)
+ .append(boundingBox, rhs.boundingBox)
+ .append(landmarks, rhs.landmarks)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(quality, rhs.quality)
+ .append(confidence, rhs.confidence).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Face.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Face.java
new file mode 100644
index 0000000..b5ecc64
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Face.java
@@ -0,0 +1,141 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Face implements Serializable
+{
+
+ @JsonProperty("BoundingBox")
+ private BoundingBox boundingBox;
+ @JsonProperty("FaceId")
+ private String faceId;
+ @JsonProperty("Confidence")
+ private Double confidence;
+ @JsonProperty("ImageId")
+ private String imageId;
+ @JsonProperty("ExternalImageId")
+ private String externalImageId;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 4320869723686571816L;
+
+ @JsonProperty("BoundingBox")
+ public BoundingBox getBoundingBox() {
+ return boundingBox;
+ }
+
+ @JsonProperty("BoundingBox")
+ public void setBoundingBox(BoundingBox boundingBox) {
+ this.boundingBox = boundingBox;
+ }
+
+ @JsonProperty("FaceId")
+ public String getFaceId() {
+ return faceId;
+ }
+
+ @JsonProperty("FaceId")
+ public void setFaceId(String faceId) {
+ this.faceId = faceId;
+ }
+
+ @JsonProperty("Confidence")
+ public Double getConfidence() {
+ return confidence;
+ }
+
+ @JsonProperty("Confidence")
+ public void setConfidence(Double confidence) {
+ this.confidence = confidence;
+ }
+
+ @JsonProperty("ImageId")
+ public String getImageId() {
+ return imageId;
+ }
+
+ @JsonProperty("ImageId")
+ public void setImageId(String imageId) {
+ this.imageId = imageId;
+ }
+
+ @JsonProperty("ExternalImageId")
+ public String getExternalImageId() {
+ return externalImageId;
+ }
+
+ @JsonProperty("ExternalImageId")
+ public void setExternalImageId(String externalImageId) {
+ this.externalImageId = externalImageId;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("boundingBox", boundingBox)
+ .append("faceId", faceId).append("confidence", confidence)
+ .append("imageId", imageId)
+ .append("externalImageId", externalImageId)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().append(boundingBox)
+ .append(imageId)
+ .append(externalImageId)
+ .append(faceId)
+ .append(additionalProperties).append(confidence).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof Face) == false) {
+ return false;
+ }
+ Face rhs = ((Face) other);
+ return new EqualsBuilder().append(boundingBox, rhs.boundingBox)
+ .append(imageId, rhs.imageId)
+ .append(externalImageId, rhs.externalImageId)
+ .append(faceId, rhs.faceId).append(additionalProperties, rhs.additionalProperties)
+ .append(confidence, rhs.confidence).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceSearchResponse.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceSearchResponse.java
new file mode 100644
index 0000000..427369e
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceSearchResponse.java
@@ -0,0 +1,103 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FaceSearchResponse implements Serializable
+{
+
+ @JsonProperty("DetectedFace")
+ private DetectedFace detectedFace;
+ @JsonProperty("MatchedFaces")
+ private List matchedFaces = null;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = -5645575235038800306L;
+
+ @JsonProperty("DetectedFace")
+ public DetectedFace getDetectedFace() {
+ return detectedFace;
+ }
+
+ @JsonProperty("DetectedFace")
+ public void setDetectedFace(DetectedFace detectedFace) {
+ this.detectedFace = detectedFace;
+ }
+
+ @JsonProperty("MatchedFaces")
+ public List getMatchedFaces() {
+ return matchedFaces;
+ }
+
+ @JsonProperty("MatchedFaces")
+ public void setMatchedFaces(List matchedFaces) {
+ this.matchedFaces = matchedFaces;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("detectedFace", detectedFace)
+ .append("matchedFaces", matchedFaces)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(matchedFaces)
+ .append(detectedFace)
+ .append(additionalProperties).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof FaceSearchResponse) == false) {
+ return false;
+ }
+ FaceSearchResponse rhs = ((FaceSearchResponse) other);
+ return new EqualsBuilder()
+ .append(matchedFaces, rhs.matchedFaces)
+ .append(detectedFace, rhs.detectedFace)
+ .append(additionalProperties, rhs.additionalProperties).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceType.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceType.java
new file mode 100644
index 0000000..efdf6f9
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/FaceType.java
@@ -0,0 +1,47 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.awt.Color;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Enum which lists down the sample types of the faces detected in given frame. This list can be expanded
+ * based on the face type given in external image id while creating face collection.
+ *
+ * For more information please refer
+ * https://docs.aws.amazon.com/rekognition/latest/dg/add-faces-to-collection-procedure.html
+ */
+@Getter
+@RequiredArgsConstructor
+public enum FaceType {
+ TRUSTED (Color.GREEN, "Trusted"),
+ CRIMINAL (Color.RED, "Criminal"),
+ UNKNOWN (Color.YELLOW, "Unknown"),
+ NOT_RECOGNIZED (Color.PINK, "NotRecognized"),
+ ALL (Color.BLACK, "All");
+
+ private final Color color;
+ private final String prefix;
+
+ public static FaceType fromString(String value) {
+ for (int i = 0; i < FaceType.values().length; i++) {
+ if(FaceType.values()[i].getPrefix().equals(value))
+ return FaceType.values()[i];
+ }
+ throw new UnsupportedOperationException("Not valid face type !");
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/InputInformation.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/InputInformation.java
new file mode 100644
index 0000000..e7c50d5
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/InputInformation.java
@@ -0,0 +1,87 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class InputInformation implements Serializable
+{
+
+ @JsonProperty("KinesisVideo")
+ private KinesisVideo kinesisVideo;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 4448679967188698414L;
+
+ @JsonProperty("KinesisVideo")
+ public KinesisVideo getKinesisVideo() {
+ return kinesisVideo;
+ }
+
+ @JsonProperty("KinesisVideo")
+ public void setKinesisVideo(KinesisVideo kinesisVideo) {
+ this.kinesisVideo = kinesisVideo;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("kinesisVideo", kinesisVideo)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(kinesisVideo)
+ .append(additionalProperties).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof InputInformation) == false) {
+ return false;
+ }
+ InputInformation rhs = ((InputInformation) other);
+ return new EqualsBuilder()
+ .append(kinesisVideo, rhs.kinesisVideo)
+ .append(additionalProperties, rhs.additionalProperties).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/KinesisVideo.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/KinesisVideo.java
new file mode 100644
index 0000000..814e0e1
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/KinesisVideo.java
@@ -0,0 +1,147 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class KinesisVideo implements Serializable
+{
+
+ @JsonProperty("StreamArn")
+ private String streamArn;
+ @JsonProperty("FragmentNumber")
+ private String fragmentNumber;
+ @JsonProperty("ServerTimestamp")
+ private Double serverTimestamp;
+ @JsonProperty("ProducerTimestamp")
+ private Double producerTimestamp;
+ @JsonProperty("FrameOffsetInSeconds")
+ private Double frameOffsetInSeconds;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 4018546116449531242L;
+
+ @JsonProperty("StreamArn")
+ public String getStreamArn() {
+ return streamArn;
+ }
+
+ @JsonProperty("StreamArn")
+ public void setStreamArn(String streamArn) {
+ this.streamArn = streamArn;
+ }
+
+ @JsonProperty("FragmentNumber")
+ public String getFragmentNumber() {
+ return fragmentNumber;
+ }
+
+ @JsonProperty("FragmentNumber")
+ public void setFragmentNumber(String fragmentNumber) {
+ this.fragmentNumber = fragmentNumber;
+ }
+
+ @JsonProperty("ServerTimestamp")
+ public Double getServerTimestamp() {
+ return serverTimestamp;
+ }
+
+ @JsonProperty("ServerTimestamp")
+ public void setServerTimestamp(Double serverTimestamp) {
+ this.serverTimestamp = serverTimestamp;
+ }
+
+ @JsonProperty("ProducerTimestamp")
+ public Double getProducerTimestamp() {
+ return producerTimestamp;
+ }
+
+ @JsonProperty("ProducerTimestamp")
+ public void setProducerTimestamp(Double producerTimestamp) {
+ this.producerTimestamp = producerTimestamp;
+ }
+
+ @JsonProperty("FrameOffsetInSeconds")
+ public Double getFrameOffsetInSeconds() {
+ return frameOffsetInSeconds;
+ }
+
+ @JsonProperty("FrameOffsetInSeconds")
+ public void setFrameOffsetInSeconds(Double frameOffsetInSeconds) {
+ this.frameOffsetInSeconds = frameOffsetInSeconds;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("streamArn", streamArn)
+ .append("fragmentNumber", fragmentNumber)
+ .append("serverTimestamp", serverTimestamp)
+ .append("producerTimestamp", producerTimestamp)
+ .append("frameOffsetInSeconds", frameOffsetInSeconds)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(frameOffsetInSeconds)
+ .append(fragmentNumber)
+ .append(streamArn)
+ .append(additionalProperties)
+ .append(producerTimestamp)
+ .append(serverTimestamp).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof KinesisVideo) == false) {
+ return false;
+ }
+ KinesisVideo rhs = ((KinesisVideo) other);
+ return new EqualsBuilder()
+ .append(frameOffsetInSeconds, rhs.frameOffsetInSeconds)
+ .append(fragmentNumber, rhs.fragmentNumber)
+ .append(streamArn, rhs.streamArn)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(producerTimestamp, rhs.producerTimestamp)
+ .append(serverTimestamp, rhs.serverTimestamp).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Landmark.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Landmark.java
new file mode 100644
index 0000000..2ccf349
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Landmark.java
@@ -0,0 +1,117 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Landmark implements Serializable
+{
+
+ @JsonProperty("X")
+ private Double x;
+ @JsonProperty("Y")
+ private Double y;
+ @JsonProperty("Type")
+ private String type;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 8108892948615651543L;
+
+ @JsonProperty("X")
+ public Double getX() {
+ return x;
+ }
+
+ @JsonProperty("X")
+ public void setX(Double x) {
+ this.x = x;
+ }
+
+ @JsonProperty("Y")
+ public Double getY() {
+ return y;
+ }
+
+ @JsonProperty("Y")
+ public void setY(Double y) {
+ this.y = y;
+ }
+
+ @JsonProperty("Type")
+ public String getType() {
+ return type;
+ }
+
+ @JsonProperty("Type")
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("x", x)
+ .append("y", y)
+ .append("type", type)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(additionalProperties)
+ .append(type)
+ .append(y)
+ .append(x).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof Landmark) == false) {
+ return false;
+ }
+ Landmark rhs = ((Landmark) other);
+ return new EqualsBuilder()
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(type, rhs.type)
+ .append(y, rhs.y)
+ .append(x, rhs.x).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/MatchedFace.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/MatchedFace.java
new file mode 100644
index 0000000..6513352
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/MatchedFace.java
@@ -0,0 +1,102 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class MatchedFace implements Serializable
+{
+
+ @JsonProperty("Similarity")
+ private Double similarity;
+ @JsonProperty("Face")
+ private Face face;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = -5269363379216197335L;
+
+ @JsonProperty("Similarity")
+ public Double getSimilarity() {
+ return similarity;
+ }
+
+ @JsonProperty("Similarity")
+ public void setSimilarity(Double similarity) {
+ this.similarity = similarity;
+ }
+
+ @JsonProperty("Face")
+ public Face getFace() {
+ return face;
+ }
+
+ @JsonProperty("Face")
+ public void setFace(Face face) {
+ this.face = face;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("similarity", similarity)
+ .append("face", face)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(face)
+ .append(additionalProperties)
+ .append(similarity).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof MatchedFace) == false) {
+ return false;
+ }
+ MatchedFace rhs = ((MatchedFace) other);
+ return new EqualsBuilder()
+ .append(face, rhs.face)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(similarity, rhs.similarity).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Pose.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Pose.java
new file mode 100644
index 0000000..4a54815
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Pose.java
@@ -0,0 +1,117 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Pose implements Serializable
+{
+
+ @JsonProperty("Pitch")
+ private Double pitch;
+ @JsonProperty("Roll")
+ private Double roll;
+ @JsonProperty("Yaw")
+ private Double yaw;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 5134659150043632590L;
+
+ @JsonProperty("Pitch")
+ public Double getPitch() {
+ return pitch;
+ }
+
+ @JsonProperty("Pitch")
+ public void setPitch(Double pitch) {
+ this.pitch = pitch;
+ }
+
+ @JsonProperty("Roll")
+ public Double getRoll() {
+ return roll;
+ }
+
+ @JsonProperty("Roll")
+ public void setRoll(Double roll) {
+ this.roll = roll;
+ }
+
+ @JsonProperty("Yaw")
+ public Double getYaw() {
+ return yaw;
+ }
+
+ @JsonProperty("Yaw")
+ public void setYaw(Double yaw) {
+ this.yaw = yaw;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("pitch", pitch)
+ .append("roll", roll)
+ .append("yaw", yaw)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(yaw)
+ .append(roll)
+ .append(additionalProperties)
+ .append(pitch).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof Pose) == false) {
+ return false;
+ }
+ Pose rhs = ((Pose) other);
+ return new EqualsBuilder()
+ .append(yaw, rhs.yaw)
+ .append(roll, rhs.roll)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(pitch, rhs.pitch).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Quality.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Quality.java
new file mode 100644
index 0000000..81a0efa
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/Quality.java
@@ -0,0 +1,102 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Quality implements Serializable
+{
+
+ @JsonProperty("Brightness")
+ private Double brightness;
+ @JsonProperty("Sharpness")
+ private Double sharpness;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = 2898836203617659983L;
+
+ @JsonProperty("Brightness")
+ public Double getBrightness() {
+ return brightness;
+ }
+
+ @JsonProperty("Brightness")
+ public void setBrightness(Double brightness) {
+ this.brightness = brightness;
+ }
+
+ @JsonProperty("Sharpness")
+ public Double getSharpness() {
+ return sharpness;
+ }
+
+ @JsonProperty("Sharpness")
+ public void setSharpness(Double sharpness) {
+ this.sharpness = sharpness;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("brightness", brightness)
+ .append("sharpness", sharpness)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(sharpness)
+ .append(brightness)
+ .append(additionalProperties).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof Quality) == false) {
+ return false;
+ }
+ Quality rhs = ((Quality) other);
+ return new EqualsBuilder()
+ .append(sharpness, rhs.sharpness)
+ .append(brightness, rhs.brightness)
+ .append(additionalProperties, rhs.additionalProperties).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionInput.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionInput.java
new file mode 100644
index 0000000..6c590a8
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionInput.java
@@ -0,0 +1,31 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import lombok.Builder;
+import lombok.Value;
+
+/**
+ * Input for Rekognition stream processor.
+ */
+@Value
+@Builder
+public class RekognitionInput {
+ private String kinesisVideoStreamArn;
+ private String kinesisDataStreamArn;
+ private String iamRoleArn;
+ private String faceCollectionId;
+ private String streamingProcessorName;
+ private Float matchThreshold;
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionOutput.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionOutput.java
new file mode 100644
index 0000000..899bc1d
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognitionOutput.java
@@ -0,0 +1,118 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RekognitionOutput implements Serializable
+{
+
+ @JsonProperty("InputInformation")
+ private InputInformation inputInformation;
+ @JsonProperty("StreamProcessorInformation")
+ private StreamProcessorInformation streamProcessorInformation;
+ @JsonProperty("FaceSearchResponse")
+ private List faceSearchResponse = null;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = -4243167512470204665L;
+
+ @JsonProperty("InputInformation")
+ public InputInformation getInputInformation() {
+ return inputInformation;
+ }
+
+ @JsonProperty("InputInformation")
+ public void setInputInformation(InputInformation inputInformation) {
+ this.inputInformation = inputInformation;
+ }
+
+ @JsonProperty("StreamProcessorInformation")
+ public StreamProcessorInformation getStreamProcessorInformation() {
+ return streamProcessorInformation;
+ }
+
+ @JsonProperty("StreamProcessorInformation")
+ public void setStreamProcessorInformation(StreamProcessorInformation streamProcessorInformation) {
+ this.streamProcessorInformation = streamProcessorInformation;
+ }
+
+ @JsonProperty("FaceSearchResponse")
+ public List getFaceSearchResponse() {
+ return faceSearchResponse;
+ }
+
+ @JsonProperty("FaceSearchResponse")
+ public void setFaceSearchResponse(List faceSearchResponse) {
+ this.faceSearchResponse = faceSearchResponse;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("inputInformation", inputInformation)
+ .append("streamProcessorInformation", streamProcessorInformation)
+ .append("faceSearchResponse", faceSearchResponse)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(inputInformation)
+ .append(additionalProperties)
+ .append(faceSearchResponse)
+ .append(streamProcessorInformation).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof RekognitionOutput) == false) {
+ return false;
+ }
+ RekognitionOutput rhs = ((RekognitionOutput) other);
+ return new EqualsBuilder()
+ .append(inputInformation, rhs.inputInformation)
+ .append(additionalProperties, rhs.additionalProperties)
+ .append(faceSearchResponse, rhs.faceSearchResponse)
+ .append(streamProcessorInformation, rhs.streamProcessorInformation).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedFragmentsIndex.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedFragmentsIndex.java
new file mode 100644
index 0000000..9b9dd2d
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedFragmentsIndex.java
@@ -0,0 +1,47 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * FragmentIndex which exposes abstract method to store and retrieve results from Rekognition output i.e Kinesis Data
+ * Streams. Internally it uses ConcurrentHashMap to store the result from Rekognition. This index can be extended with
+ * different data structure if required.
+ *
+ */
+public class RekognizedFragmentsIndex {
+
+ private final ConcurrentHashMap> rekognizedOutputMap = new ConcurrentHashMap<>();
+
+ public void addToMap(String fragmentNumber, RekognizedOutput rekognizedOutput) {
+ List rekognizedOutputs = rekognizedOutputMap.getOrDefault(fragmentNumber, new ArrayList<>());
+ rekognizedOutputs.add(rekognizedOutput);
+ rekognizedOutputMap.put(fragmentNumber, rekognizedOutputs);
+ }
+
+ public List getRekognizedOutputList(String fragmentNumber) {
+ return rekognizedOutputMap.get(fragmentNumber);
+ }
+
+ public void removeFromIndex(String fragmentNumber) {
+ rekognizedOutputMap.remove(fragmentNumber);
+ }
+
+ public boolean isEmpty() {
+ return rekognizedOutputMap.isEmpty();
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedOutput.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedOutput.java
new file mode 100644
index 0000000..5a38fdc
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/RekognizedOutput.java
@@ -0,0 +1,54 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+
+@Builder
+@Getter
+@ToString
+public class RekognizedOutput {
+
+ private String fragmentNumber;
+ private Double frameOffsetInSeconds;
+ private Double serverTimestamp;
+ private Double producerTimestamp;
+
+ @Setter
+ private String faceId;
+ private double detectedTime;
+ @Builder.Default
+ private List faceSearchOutputs = new ArrayList<>();
+
+ public void addFaceSearchOutput(FaceSearchOutput faceSearchOutput) {
+ this.faceSearchOutputs.add(faceSearchOutput);
+ }
+
+ @Getter
+ @Builder
+ @ToString
+ public static class FaceSearchOutput {
+
+ private DetectedFace detectedFace;
+ @Builder.Default
+ private List matchedFaceList = new ArrayList<>();
+ }
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/StreamProcessorInformation.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/StreamProcessorInformation.java
new file mode 100644
index 0000000..1de41f8
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/pojo/StreamProcessorInformation.java
@@ -0,0 +1,88 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.pojo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class StreamProcessorInformation implements Serializable
+{
+
+ @JsonProperty("Status")
+ private String status;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+ private final static long serialVersionUID = -4043725115310892727L;
+
+ @JsonProperty("Status")
+ public String getStatus() {
+ return status;
+ }
+
+ @JsonProperty("Status")
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("status", status)
+ .append("additionalProperties", additionalProperties).toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(status)
+ .append(additionalProperties).toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof StreamProcessorInformation) == false) {
+ return false;
+ }
+ StreamProcessorInformation rhs = ((StreamProcessorInformation) other);
+ return new EqualsBuilder()
+ .append(status, rhs.status)
+ .append(additionalProperties, rhs.additionalProperties).isEquals();
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/processor/RekognitionStreamProcessor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/processor/RekognitionStreamProcessor.java
new file mode 100644
index 0000000..50b03e0
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/rekognition/processor/RekognitionStreamProcessor.java
@@ -0,0 +1,173 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.rekognition.processor;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognitionInput;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.rekognition.AmazonRekognition;
+import com.amazonaws.services.rekognition.AmazonRekognitionClientBuilder;
+import com.amazonaws.services.rekognition.model.CreateStreamProcessorRequest;
+import com.amazonaws.services.rekognition.model.CreateStreamProcessorResult;
+import com.amazonaws.services.rekognition.model.DeleteStreamProcessorRequest;
+import com.amazonaws.services.rekognition.model.DeleteStreamProcessorResult;
+import com.amazonaws.services.rekognition.model.DescribeStreamProcessorRequest;
+import com.amazonaws.services.rekognition.model.DescribeStreamProcessorResult;
+import com.amazonaws.services.rekognition.model.FaceSearchSettings;
+import com.amazonaws.services.rekognition.model.KinesisDataStream;
+import com.amazonaws.services.rekognition.model.KinesisVideoStream;
+import com.amazonaws.services.rekognition.model.ListStreamProcessorsRequest;
+import com.amazonaws.services.rekognition.model.ListStreamProcessorsResult;
+import com.amazonaws.services.rekognition.model.ResourceNotFoundException;
+import com.amazonaws.services.rekognition.model.StartStreamProcessorRequest;
+import com.amazonaws.services.rekognition.model.StartStreamProcessorResult;
+import com.amazonaws.services.rekognition.model.StopStreamProcessorRequest;
+import com.amazonaws.services.rekognition.model.StopStreamProcessorResult;
+import com.amazonaws.services.rekognition.model.StreamProcessor;
+import com.amazonaws.services.rekognition.model.StreamProcessorInput;
+import com.amazonaws.services.rekognition.model.StreamProcessorOutput;
+import com.amazonaws.services.rekognition.model.StreamProcessorSettings;
+import com.amazonaws.services.rekognition.model.StreamProcessorStatus;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Rekognition Stream Processor client class which acts as a wrapper for invoking corresponding Rekognition APIs.
+ *
+ */
+@Slf4j
+public class RekognitionStreamProcessor {
+
+ private String streamProcessorName;
+ private String kinesisVideoStreamArn;
+ private String kinesisDataStreamArn;
+ private String roleArn;
+ private String collectionId;
+ private float matchThreshold;
+ private String region;
+ private AmazonRekognition rekognitionClient;
+
+ private RekognitionStreamProcessor(final Regions regions, final AWSCredentialsProvider provider,
+ final RekognitionInput rekognitionInput) {
+ this.streamProcessorName = rekognitionInput.getStreamingProcessorName();
+ this.kinesisVideoStreamArn = rekognitionInput.getKinesisVideoStreamArn();
+ this.kinesisDataStreamArn = rekognitionInput.getKinesisDataStreamArn();
+ this.roleArn = rekognitionInput.getIamRoleArn();
+ this.collectionId = rekognitionInput.getFaceCollectionId();
+ this.matchThreshold = rekognitionInput.getMatchThreshold();
+
+ rekognitionClient = AmazonRekognitionClientBuilder
+ .standard()
+ .withRegion(regions)
+ .withCredentials(provider)
+ .build();
+ }
+
+ public static RekognitionStreamProcessor create(final Regions regions, final AWSCredentialsProvider provider,
+ final RekognitionInput rekognitionInput) {
+ return new RekognitionStreamProcessor(regions, provider, rekognitionInput);
+ }
+
+ /**
+ * Creates a StreamProcess if it doesn't exist already. Once the stream processor is created, it's started and then
+ * described to know the result of the stream processor.
+ */
+ public void process() {
+ // Creates a stream processor if it doesn't already exist and start.
+ try {
+ DescribeStreamProcessorResult result = describeStreamProcessor();
+ if (!result.getStatus().equals(StreamProcessorStatus.RUNNING.toString())) {
+ startStreamProcessor();
+ }
+ } catch (ResourceNotFoundException e) {
+ log.info("StreamProcessor with name : {} doesnt exist. Creating...", streamProcessorName);
+ createStreamProcessor();
+ startStreamProcessor();
+ }
+
+ // Describe the Stream Processor results to log the status.
+ describeStreamProcessor();
+ }
+
+ public void createStreamProcessor() {
+ KinesisVideoStream kinesisVideoStream = new KinesisVideoStream()
+ .withArn(kinesisVideoStreamArn);
+ StreamProcessorInput streamProcessorInput = new StreamProcessorInput()
+ .withKinesisVideoStream(kinesisVideoStream);
+ KinesisDataStream kinesisDataStream = new KinesisDataStream()
+ .withArn(kinesisDataStreamArn);
+ StreamProcessorOutput streamProcessorOutput = new StreamProcessorOutput()
+ .withKinesisDataStream(kinesisDataStream);
+ FaceSearchSettings faceSearchSettings = new FaceSearchSettings()
+ .withCollectionId(collectionId)
+ .withFaceMatchThreshold(matchThreshold);
+ StreamProcessorSettings streamProcessorSettings = new StreamProcessorSettings()
+ .withFaceSearch(faceSearchSettings);
+
+ CreateStreamProcessorResult createStreamProcessorResult =
+ rekognitionClient.createStreamProcessor(new CreateStreamProcessorRequest()
+ .withInput(streamProcessorInput)
+ .withOutput(streamProcessorOutput)
+ .withSettings(streamProcessorSettings)
+ .withRoleArn(roleArn)
+ .withName(streamProcessorName));
+ log.info("StreamProcessorArn : {} ", createStreamProcessorResult.getStreamProcessorArn());
+ }
+
+ public void startStreamProcessor() {
+ StartStreamProcessorResult startStreamProcessorResult =
+ rekognitionClient.startStreamProcessor(new StartStreamProcessorRequest().withName(streamProcessorName));
+ log.info("SdkResponseMetadata : {} ", startStreamProcessorResult.getSdkResponseMetadata());
+
+ }
+
+ public void stopStreamProcessor() {
+ StopStreamProcessorResult stopStreamProcessorResult =
+ rekognitionClient.stopStreamProcessor(new StopStreamProcessorRequest().withName(streamProcessorName));
+ log.info("SdkResponseMetadata : {} ", stopStreamProcessorResult.getSdkResponseMetadata());
+ }
+
+ public void deleteStreamProcessor() {
+ DeleteStreamProcessorResult deleteStreamProcessorResult = rekognitionClient
+ .deleteStreamProcessor(new DeleteStreamProcessorRequest().withName(streamProcessorName));
+ log.info("SdkResponseMetadata : {} ", deleteStreamProcessorResult.getSdkResponseMetadata());
+ }
+
+ public DescribeStreamProcessorResult describeStreamProcessor() {
+ DescribeStreamProcessorResult describeStreamProcessorResult = rekognitionClient
+ .describeStreamProcessor(new DescribeStreamProcessorRequest().withName(streamProcessorName));
+ log.info("Arn : {}", describeStreamProcessorResult.getStreamProcessorArn());
+ log.info("Input kinesisVideo stream : {} ",
+ describeStreamProcessorResult.getInput().getKinesisVideoStream().getArn());
+ log.info("Output kinesisData stream {} ",
+ describeStreamProcessorResult.getOutput().getKinesisDataStream().getArn());
+ log.info("RoleArn {} ", describeStreamProcessorResult.getRoleArn());
+ log.info(
+ "CollectionId {} ", describeStreamProcessorResult.getSettings().getFaceSearch().getCollectionId());
+ log.info("Status {} ", describeStreamProcessorResult.getStatus());
+ log.info("Status message {} ", describeStreamProcessorResult.getStatusMessage());
+ log.info("Creation timestamp {} ", describeStreamProcessorResult.getCreationTimestamp());
+ log.info("Last update timestamp {} ", describeStreamProcessorResult.getLastUpdateTimestamp());
+ return describeStreamProcessorResult;
+ }
+
+ public void listStreamProcessor() {
+ ListStreamProcessorsResult listStreamProcessorsResult =
+ rekognitionClient.listStreamProcessors(new ListStreamProcessorsRequest().withMaxResults(100));
+ for (StreamProcessor streamProcessor : listStreamProcessorsResult.getStreamProcessors()) {
+ log.info("StreamProcessor name {} ", streamProcessor.getName());
+ log.info("Status {} ", streamProcessor.getStatus());
+ }
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
index c5e1629..c892195 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitor.java
@@ -149,6 +149,12 @@ public void visit(MkvEndMasterElement endMasterElement) throws MkvElementVisitEx
state = State.NEW;
}
break;
+ case PRE_CLUSTER:
+ if (MkvTypeInfos.SEGMENT.equals(endMasterElement.getElementMetaData().getTypeInfo())) {
+ log.warn("Segment end {} while in PRE_CLUSTER. Collecting cluster info", endMasterElement);
+ collectPreClusterInfo();
+ }
+ break;
default:
break;
}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameRendererVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameRendererVisitor.java
index 40a709b..9c71b55 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameRendererVisitor.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameRendererVisitor.java
@@ -13,6 +13,10 @@
*/
package com.amazonaws.kinesisvideo.parser.utilities;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.util.List;
+
import com.amazonaws.kinesisvideo.parser.examples.KinesisVideoFrameViewer;
import com.amazonaws.kinesisvideo.parser.mkv.Frame;
import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitor;
@@ -28,11 +32,6 @@
import org.jcodec.scale.Transform;
import org.jcodec.scale.Yuv420jToRgb;
-import java.awt.image.BufferedImage;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-
import static org.jcodec.codecs.h264.H264Utils.splitMOVPacket;
@Slf4j
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
index 13d3345..f47dcd0 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/FrameVisitor.java
@@ -13,6 +13,8 @@
*/
package com.amazonaws.kinesisvideo.parser.utilities;
+import java.util.Optional;
+
import com.amazonaws.kinesisvideo.parser.ebml.MkvTypeInfos;
import com.amazonaws.kinesisvideo.parser.mkv.Frame;
import com.amazonaws.kinesisvideo.parser.mkv.MkvElementVisitor;
@@ -41,7 +43,8 @@ public static FrameVisitor create(FrameProcessor frameProcessor) {
}
public interface FrameProcessor {
- default void process(Frame frame, MkvTrackMetadata trackMetadata) {
+ default void process(Frame frame, MkvTrackMetadata trackMetadata,
+ Optional fragmentMetadata) {
throw new NotImplementedException("Default FrameVisitor.FrameProcessor");
}
}
@@ -66,7 +69,8 @@ public void visit(com.amazonaws.kinesisvideo.parser.mkv.MkvDataElement dataEleme
Validate.notNull(frame);
MkvTrackMetadata trackMetadata =
fragmentMetadataVisitor.getMkvTrackMetadata(frame.getVal().getTrackNumber());
- frameProcessor.process(frame.getVal(), trackMetadata);
+ frameProcessor.process(frame.getVal(), trackMetadata,
+ fragmentMetadataVisitor.getCurrentFragmentMetadata());
}
}
}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264BoundingBoxFrameRenderer.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264BoundingBoxFrameRenderer.java
new file mode 100644
index 0000000..edab47b
--- /dev/null
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264BoundingBoxFrameRenderer.java
@@ -0,0 +1,152 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.utilities;
+
+import java.awt.image.BufferedImage;
+import java.util.List;
+import java.util.Optional;
+
+import com.amazonaws.kinesisvideo.parser.examples.KinesisVideoBoundingBoxFrameViewer;
+import com.amazonaws.kinesisvideo.parser.mkv.Frame;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedFragmentsIndex;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognizedOutput;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class H264BoundingBoxFrameRenderer extends H264FrameRenderer {
+
+ private static final int DEFAULT_MAX_TIMEOUT = 100;
+ private static final int WAIT_TIMEOUT = 3;
+ private static final int MILLIS_IN_SEC = 1000;
+ private static final int OFFSET_DELTA_THRESHOLD = 10;
+
+ private final KinesisVideoBoundingBoxFrameViewer kinesisVideoBoundingBoxFrameViewer;
+ private final RekognizedFragmentsIndex rekognizedFragmentsIndex;
+
+ @Setter
+ private int maxTimeout = DEFAULT_MAX_TIMEOUT;
+
+ private long keyFrameTimecode;
+
+ private H264BoundingBoxFrameRenderer(final KinesisVideoBoundingBoxFrameViewer kinesisVideoBoundingBoxFrameViewer,
+ final RekognizedFragmentsIndex rekognizedFragmentsIndex) {
+ super(kinesisVideoBoundingBoxFrameViewer);
+ this.kinesisVideoBoundingBoxFrameViewer = kinesisVideoBoundingBoxFrameViewer;
+ this.rekognizedFragmentsIndex = rekognizedFragmentsIndex;
+ }
+
+ public static H264BoundingBoxFrameRenderer create(KinesisVideoBoundingBoxFrameViewer kinesisVideoBoundingBoxFrameViewer,
+ RekognizedFragmentsIndex rekognizedFragmentsIndex) {
+ return new H264BoundingBoxFrameRenderer(kinesisVideoBoundingBoxFrameViewer, rekognizedFragmentsIndex);
+ }
+
+ @Override
+ public void process(Frame frame, MkvTrackMetadata trackMetadata, Optional fragmentMetadata) {
+ final BufferedImage bufferedImage = decodeH264Frame(frame, trackMetadata);
+ Optional rekognizedOutput = getRekognizedOutput(frame, fragmentMetadata);
+ renderFrame(bufferedImage, rekognizedOutput);
+ }
+
+ private Optional getRekognizedOutput(final Frame frame,
+ final Optional fragmentMetadata) {
+
+ Optional rekognizedOutput = Optional.empty();
+ if (rekognizedFragmentsIndex != null && fragmentMetadata.isPresent()) {
+ final String fragmentNumber = fragmentMetadata.get().getFragmentNumberString();
+
+ int timeout = 0;
+ List rekognizedOutputs = null;
+
+ // if rekognizedOutputs is null then Rekognition did not return the results for this fragment.
+ // Wait until the results are received.
+ while (true) {
+ rekognizedOutputs = rekognizedFragmentsIndex.getRekognizedOutputList(fragmentNumber);
+ if (rekognizedOutputs != null) {
+ break;
+ } else {
+ timeout += waitForResults(WAIT_TIMEOUT);
+ if (timeout >= maxTimeout) {
+ log.warn("No rekognized result after waiting for {} ms ", timeout);
+ break;
+ }
+ }
+ }
+ if (rekognizedOutputs != null) {
+
+ // Currently Rekognition samples frames and calculates the frame offset from the fragment start time.
+ // So, in order to match with rekognition results, we have to compute the same frame offset from the
+ // beginning of the fragments.
+ if (frame.isKeyFrame()) {
+ keyFrameTimecode = frame.getTimeCode();
+ log.debug("Key frame timecode : {}", keyFrameTimecode);
+ }
+ final long frameOffset = (frame.getTimeCode() > keyFrameTimecode)
+ ? frame.getTimeCode() - keyFrameTimecode : 0;
+ log.debug("Current Fragment Number : {} Computed Frame offset : {}", fragmentNumber, frameOffset);
+ if (log.isDebugEnabled()) {
+ rekognizedOutputs
+ .forEach(p -> log.debug("frameOffsetInSeconds from Rekognition : {}",
+ p.getFrameOffsetInSeconds()));
+ }
+
+ // Check whether the computed offset matches the rekognized output frame offset. Rekognition
+ // output is in seconds whereas the frame offset is calculated in milliseconds.
+ // NOTE: Rekognition frame offset doesn't exactly match with the computed offset below. So
+ // take the closest one possible within 10ms delta.
+ rekognizedOutput = rekognizedOutputs.stream()
+ .filter(p -> isOffsetDeltaWithinThreshold(frameOffset, p))
+ .findFirst();
+
+ // Remove from the index once the RekognizedOutput is processed. Else it would increase the memory
+ // footprint and blow up the JVM.
+ if (rekognizedOutput.isPresent()) {
+ log.debug("Computed offset matched with retrieved offset. Delta : {}",
+ Math.abs(frameOffset - (rekognizedOutput.get().getFrameOffsetInSeconds() * MILLIS_IN_SEC)));
+ rekognizedOutputs.remove(rekognizedOutput.get());
+ if (rekognizedOutputs.isEmpty()) {
+ log.debug("All frames processed for this fragment number : {}", fragmentNumber);
+ rekognizedFragmentsIndex.removeFromIndex(fragmentNumber);
+ }
+ }
+ }
+ }
+ return rekognizedOutput;
+ }
+
+ private boolean isOffsetDeltaWithinThreshold(final long frameOffset, final RekognizedOutput output) {
+ return Math.abs(frameOffset - (output.getFrameOffsetInSeconds() * MILLIS_IN_SEC)) <= OFFSET_DELTA_THRESHOLD;
+ }
+
+ void renderFrame(BufferedImage bufferedImage, Optional rekognizedOutput) {
+ if (rekognizedOutput.isPresent()) {
+ kinesisVideoBoundingBoxFrameViewer.update(bufferedImage, rekognizedOutput.get());
+ } else {
+ kinesisVideoBoundingBoxFrameViewer.update(bufferedImage);
+ }
+ }
+
+
+ private long waitForResults(long timeout) {
+ final long startTime = System.currentTimeMillis();
+ try {
+ log.info("No rekognized results for this fragment number. Waiting ....");
+ Thread.sleep(timeout);
+ } catch (InterruptedException e) {
+ log.warn("Error while waiting for rekognized output !", e);
+ }
+ return System.currentTimeMillis() - startTime;
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
index f5b4bc5..c8cec85 100644
--- a/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
+++ b/src/main/java/com/amazonaws/kinesisvideo/parser/utilities/H264FrameRenderer.java
@@ -13,6 +13,11 @@
*/
package com.amazonaws.kinesisvideo.parser.utilities;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Optional;
+
import com.amazonaws.kinesisvideo.parser.examples.KinesisVideoFrameViewer;
import com.amazonaws.kinesisvideo.parser.mkv.Frame;
import lombok.Getter;
@@ -25,10 +30,6 @@
import org.jcodec.scale.Transform;
import org.jcodec.scale.Yuv420jToRgb;
-import java.awt.image.BufferedImage;
-import java.nio.ByteBuffer;
-import java.util.List;
-
import static org.jcodec.codecs.h264.H264Utils.splitMOVPacket;
@Slf4j
@@ -43,7 +44,7 @@ public class H264FrameRenderer implements FrameVisitor.FrameProcessor {
private byte[] codecPrivateData;
- private H264FrameRenderer(KinesisVideoFrameViewer kinesisVideoFrameViewer) {
+ protected H264FrameRenderer(final KinesisVideoFrameViewer kinesisVideoFrameViewer) {
this.kinesisVideoFrameViewer = kinesisVideoFrameViewer;
this.kinesisVideoFrameViewer.setVisible(true);
}
@@ -53,7 +54,12 @@ public static H264FrameRenderer create(KinesisVideoFrameViewer kinesisVideoFrame
}
@Override
- public void process(Frame frame, MkvTrackMetadata trackMetadata) {
+ public void process(Frame frame, MkvTrackMetadata trackMetadata, Optional fragmentMetadata) {
+ final BufferedImage bufferedImage = decodeH264Frame(frame, trackMetadata);
+ kinesisVideoFrameViewer.update(bufferedImage);
+ }
+
+ protected BufferedImage decodeH264Frame(Frame frame, MkvTrackMetadata trackMetadata) {
ByteBuffer frameBuffer = frame.getFrameData();
int pixelWidth = trackMetadata.getPixelWidth().get().intValue();
int pixelHeight = trackMetadata.getPixelHeight().get().intValue();
@@ -63,7 +69,7 @@ public void process(Frame frame, MkvTrackMetadata trackMetadata) {
// See: https://www.matroska.org/technical/specs/index.html#simpleblock_structure
Picture rgb = Picture.create(pixelWidth, pixelHeight, ColorSpace.RGB);
- BufferedImage renderImage = new BufferedImage(pixelWidth, pixelHeight, BufferedImage.TYPE_3BYTE_BGR);
+ BufferedImage bufferedImage = new BufferedImage(pixelWidth, pixelHeight, BufferedImage.TYPE_3BYTE_BGR);
AvcCBox avcC = AvcCBox.parseAvcCBox(ByteBuffer.wrap(codecPrivateData));
decoder.addSps(avcC.getSpsList());
@@ -84,10 +90,10 @@ public void process(Frame frame, MkvTrackMetadata trackMetadata) {
Picture tmpBuf = Picture.createPicture(pixelWidth, pixelHeight, dataTemp, ColorSpace.YUV420J);
transform.transform(tmpBuf, rgb);
- AWTUtil.toBufferedImage(rgb, renderImage);
- kinesisVideoFrameViewer.update(renderImage);
+ AWTUtil.toBufferedImage(rgb, bufferedImage);
frameCount++;
}
+ return bufferedImage;
}
public ByteBuffer getCodecPrivateData() {
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoExampleTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoExampleTest.java
index 8da505e..3fe05b8 100644
--- a/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoExampleTest.java
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoExampleTest.java
@@ -40,8 +40,8 @@ public void testExample() throws InterruptedException, IOException {
.build();
example.execute();
- Assert.assertEquals(5, example.getFragmentsPersisted());
- Assert.assertEquals(5, example.getFragmentsRead());
+ Assert.assertEquals(8, example.getFragmentsPersisted());
+ Assert.assertEquals(8, example.getFragmentsRead());
}
}
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExampleTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExampleTest.java
new file mode 100644
index 0000000..98116b7
--- /dev/null
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRekognitionIntegrationExampleTest.java
@@ -0,0 +1,67 @@
+/*
+Copyright 2017-2017 Amazon.com, Inc. or its affiliates. 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.
+A copy of the License is located at
+
+ http://aws.amazon.com/apache2.0/
+
+or in the "license" file accompanying this file.
+This file 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.
+*/
+package com.amazonaws.kinesisvideo.parser.examples;
+
+import java.io.IOException;
+
+import com.amazonaws.auth.profile.ProfileCredentialsProvider;
+import com.amazonaws.kinesisvideo.parser.TestResourceUtil;
+import com.amazonaws.kinesisvideo.parser.rekognition.pojo.RekognitionInput;
+import com.amazonaws.regions.Regions;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * This examples demonstrates how to integrate KVS with Rekognition and draw bounding boxes for each frame.
+ */
+public class KinesisVideoRekognitionIntegrationExampleTest {
+ /* long running test */
+ @Ignore
+ @Test
+ public void testExample() throws InterruptedException, IOException {
+ // NOTE: Rekogntion Input needs ARN for both Kinesis Video Streams and Kinesis Data Streams.
+ // For more info please refer https://docs.aws.amazon.com/rekognition/latest/dg/streaming-video.html
+ RekognitionInput rekognitionInput = RekognitionInput.builder()
+ .kinesisVideoStreamArn("")
+ .kinesisDataStreamArn("")
+ .streamingProcessorName("")
+ // Refer how to add face collection :
+ // https://docs.aws.amazon.com/rekognition/latest/dg/add-faces-to-collection-procedure.html
+ .faceCollectionId("")
+ .iamRoleArn("")
+ .matchThreshold(0.08f)
+ .build();
+
+ KinesisVideoRekognitionIntegrationExample example = KinesisVideoRekognitionIntegrationExample.builder()
+ .region(Regions.US_WEST_2)
+ .kvsStreamName("")
+ .kdsStreamName("")
+ .rekognitionInput(rekognitionInput)
+ .credentialsProvider(new ProfileCredentialsProvider())
+ // NOTE: If the input stream is not passed then the example assumes that the video fragments are
+ // ingested using other mechanisms like GStreamer sample app or AmazonKinesisVideoDemoApp
+ .inputStream(TestResourceUtil.getTestInputStream("bezos_vogels.mkv"))
+ .build();
+
+ // The test file resolution is 1280p.
+ example.setWidth(1280);
+ example.setHeight(720);
+
+ // This test might render frames with high latency until the rekognition results are returned. Change below
+ // timeout to smaller value if the frames need to be rendered with low latency when rekognition results
+ // are not present.
+ example.setRekognitionMaxTimeoutInMillis(100);
+ example.execute(30L);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExampleTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExampleTest.java
index c9dfa09..f2cd416 100644
--- a/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExampleTest.java
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/examples/KinesisVideoRendererExampleTest.java
@@ -29,7 +29,7 @@ public void testExample() throws InterruptedException, IOException {
KinesisVideoRendererExample example = KinesisVideoRendererExample.builder().region(Regions.US_WEST_2)
.streamName("render-example-stream")
.credentialsProvider(new ProfileCredentialsProvider())
- .inputVideoStream(TestResourceUtil.getTestInputStream("rendering_example_video.mkv"))
+ .inputVideoStream(TestResourceUtil.getTestInputStream("clusters.mkv"))
.build();
example.execute();
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/mkv/StreamingMkvReaderTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/mkv/StreamingMkvReaderTest.java
index fe62bbd..2e8ea6d 100644
--- a/src/test/java/com/amazonaws/kinesisvideo/parser/mkv/StreamingMkvReaderTest.java
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/mkv/StreamingMkvReaderTest.java
@@ -53,7 +53,7 @@ public void testClustersMkvAllElementsWithoutPath() throws IOException, MkvEleme
new StreamingMkvReader(false, new ArrayList<>(), parserByteSource);
CountVisitor visitor = readAllReturnedElements(streamReader);
- assertCountsOfTypes(visitor, 1, 5, 300, 3);
+ assertCountsOfTypes(visitor, 1, 8, 444, 1);
}
@Test
@@ -64,7 +64,7 @@ public void testClustersMkvAllElementsWithPath() throws IOException, MkvElementV
CountVisitor visitor = readAllReturnedElements(streamReader);
- assertCountsOfTypes(visitor, 1, 5, 300, 3);
+ assertCountsOfTypes(visitor, 1, 8, 444, 1);
}
private void assertCountsOfTypes(CountVisitor visitor,
@@ -161,7 +161,7 @@ public void testClustersMkvSimpleBlockWithPath() throws IOException, MkvElementV
new StreamingMkvReader(true, mkvTypeInfosToRead, parserByteSource);
CountVisitor visitor = readAllReturnedElements(streamReader);
- Assert.assertEquals(300, visitor.getCount(MkvTypeInfos.SIMPLEBLOCK));
+ Assert.assertEquals(444, visitor.getCount(MkvTypeInfos.SIMPLEBLOCK));
}
@Test
diff --git a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
index 0369ddf..ac05fe9 100644
--- a/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
+++ b/src/test/java/com/amazonaws/kinesisvideo/parser/utilities/FragmentMetadataVisitorTest.java
@@ -31,8 +31,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
/**
* Test class to test {@link FragmentMetadataVisitor}.
@@ -127,6 +130,34 @@ public void withOutputSegmentMergerTest() throws IOException, MkvElementVisitExc
}
}
+
+ @Test
+ public void testFragmentNumbers_NoClusterData() throws IOException, MkvElementVisitException {
+ FragmentMetadataVisitor fragmentVisitor = FragmentMetadataVisitor.create();
+ String testFile = "empty-mkv-with-tags.mkv";
+ Set expectedFragmentNumbers = new HashSet<>(
+ Arrays.asList(
+ "91343852338378294813695855977007281634605393997"
+ ));
+
+ final InputStream inputStream = TestResourceUtil.getTestInputStream(testFile);
+ Set visitedFragmentNumbers = new HashSet<>();
+ StreamingMkvReader mkvStreamReader =
+ StreamingMkvReader.createDefault(new InputStreamParserByteSource(inputStream));
+ while (mkvStreamReader.mightHaveNext()) {
+ Optional mkvElement = mkvStreamReader.nextIfAvailable();
+ if (mkvElement.isPresent()) {
+ mkvElement.get().accept(fragmentVisitor);
+ Optional fragmentMetadata = fragmentVisitor.getCurrentFragmentMetadata();
+ if (fragmentMetadata.isPresent()) {
+ String fragmentNumber = fragmentMetadata.get().getFragmentNumberString();
+ visitedFragmentNumbers.add(fragmentNumber);
+ }
+ }
+ }
+
+ Assert.assertEquals(expectedFragmentNumbers, visitedFragmentNumbers);
+ }
private static class TestCompositeVisitor extends CompositeMkvElementVisitor {
public TestCompositeVisitor(FragmentMetadataVisitor fragmentMetadataVisitor, OutputSegmentMerger merger) {
diff --git a/src/test/resources/bezos_vogels.mkv b/src/test/resources/bezos_vogels.mkv
new file mode 100644
index 0000000..dd91c3d
Binary files /dev/null and b/src/test/resources/bezos_vogels.mkv differ
diff --git a/src/test/resources/clusters.mkv b/src/test/resources/clusters.mkv
index 28a3a8b..edd914c 100755
Binary files a/src/test/resources/clusters.mkv and b/src/test/resources/clusters.mkv differ
diff --git a/src/test/resources/empty-mkv-with-tags.mkv b/src/test/resources/empty-mkv-with-tags.mkv
new file mode 100644
index 0000000..714bac7
Binary files /dev/null and b/src/test/resources/empty-mkv-with-tags.mkv differ
diff --git a/src/test/resources/log4j2.properties b/src/test/resources/log4j2.properties
index e646be8..7423243 100644
--- a/src/test/resources/log4j2.properties
+++ b/src/test/resources/log4j2.properties
@@ -1,11 +1,20 @@
-name=PropertiesConfig
-appenders = console
+#name=PropertiesConfig
+#appenders = console
+#
+#appender.console.type = Console
+#appender.console.name = STDOUT
+#appender.console.layout.type = PatternLayout
+#appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
+#
+#rootLogger.level = error
+#rootLogger.appenderRefs = stdout
+#rootLogger.appenderRef.stdout.ref = STDOUT
-appender.console.type = Console
-appender.console.name = STDOUT
-appender.console.layout.type = PatternLayout
-appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
+# Root logger option
+log4j.rootLogger=INFO, stdout
-rootLogger.level = error
-rootLogger.appenderRefs = stdout
-rootLogger.appenderRef.stdout.ref = STDOUT
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
\ No newline at end of file