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