diff --git a/video/cloud-client/README.md b/video/cloud-client/README.md new file mode 100644 index 00000000000..edef15190d1 --- /dev/null +++ b/video/cloud-client/README.md @@ -0,0 +1,61 @@ +# Video Feature Detection Sample + +[Google Cloud Video Intelligence API][video] provides feature detection for +videos. This API is part of the larger collection of Cloud Machine Learning +APIs. + +This sample Java application demonstrates how to access the Cloud Video API +using the [Google Cloud Client Library for Java][google-cloud-java]. + +[video]: https://cloud.google.com/video-intelligence/docs/ +[google-cloud-java]: https://github.com/GoogleCloudPlatform/google-cloud-java + +## Build the sample + +Install [Maven](http://maven.apache.org/). + +Build your project with: + +``` +mvn clean compile assembly:single +``` + +### Analyze a video +Please follow the [Set Up Your Project](https://cloud.google.com/video-intelligence/docs/getting-started#set_up_your_project) +steps in the Quickstart doc to create a project and enable the Google Cloud +Video Intelligence API. Following those steps, make sure that you +[Set Up a Service Account](https://cloud.google.com/video-intelligence/docs/common/auth#set_up_a_service_account), +and export the following environment variable: + +``` +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-project-credentials.json +``` + +After you have authorized, you can analyze videos. + +Detect Faces +``` +java -cp target/video-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.video.Detect faces gs://cloudmleap/video/next/volleyball_court.mp4 +``` + +Detect Labels +``` +java -cp target/video-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.video.Detect labels gs://demomaker/cat.mp4 + +java -cp target/video-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.video.Detect labels-file ./resources/cat.mp4 +``` + +Detect Safe Search annotations +``` +java -cp target/video-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.video.Detect safesearch gs://demomaker/cat.mp4 +``` + +Detect Shots +``` +java -cp target/video-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.video.Detect shots gs://cloudmleap/video/next/gbikes_dinosaur.mp4 +``` diff --git a/video/cloud-client/pom.xml b/video/cloud-client/pom.xml new file mode 100644 index 00000000000..b8cf3117731 --- /dev/null +++ b/video/cloud-client/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + com.example.video + video-google-cloud-samples + jar + + + + doc-samples + com.google.cloud + 1.0.0 + ../.. + + + + 1.8 + 1.8 + UTF-8 + + + + + + com.google.guava + guava + 20.0 + + + com.google.cloud + google-cloud + 0.18.0-alpha + + + com.google.guava + guava-jdk5 + + + + + com.google.cloud + google-cloud-video-intelligence + 0.18.0-alpha + + + com.google.guava + guava-jdk5 + + + + + com.google.auth + google-auth-library-oauth2-http + 0.6.1 + + + com.google.auth + google-auth-library-credentials + 0.6.1 + + + io.netty + netty-tcnative-boringssl-static + 1.1.33.Fork26 + + + + + + junit + junit + 4.12 + test + + + com.google.truth + truth + 0.31 + test + + + + + + maven-assembly-plugin + + + + com.example.video.Detect + + + + jar-with-dependencies + + + + + + diff --git a/video/cloud-client/src/main/java/com/example/video/Detect.java b/video/cloud-client/src/main/java/com/example/video/Detect.java new file mode 100644 index 00000000000..35009aa01fe --- /dev/null +++ b/video/cloud-client/src/main/java/com/example/video/Detect.java @@ -0,0 +1,324 @@ +/* + * Copyright 2017, Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.video; + +import com.google.api.gax.grpc.OperationFuture; +import com.google.cloud.videointelligence.spi.v1beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.spi.v1beta1.VideoIntelligenceServiceSettings; +import com.google.cloud.videointelligence.v1beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1beta1.FaceAnnotation; +import com.google.cloud.videointelligence.v1beta1.Feature; +import com.google.cloud.videointelligence.v1beta1.LabelAnnotation; +import com.google.cloud.videointelligence.v1beta1.LabelLocation; +import com.google.cloud.videointelligence.v1beta1.Likelihood; +import com.google.cloud.videointelligence.v1beta1.SafeSearchAnnotation; +import com.google.cloud.videointelligence.v1beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1beta1.VideoSegment; +import org.apache.commons.codec.binary.Base64; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; + + +public class Detect { + /** + * Detects entities,sentiment and syntax in a document using the Natural Language API. + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + * + * @throws IOException on Input/Output errors. + */ + public static void main(String[] args) throws IOException, InterruptedException { + try { + argsHelper(args); + } catch (Exception e) { + System.out.println("Exception while running:\n" + e.getMessage() + "\n"); + e.printStackTrace(System.out); + } + } + + /** + * Helper that handles the input passed to the program. + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + * + * @throws IOException on Input/Output errors. + */ + public static void argsHelper(String[] args) throws + ExecutionException, IOException, InterruptedException { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tfaces | labels | shots\n" + + "Path:\n\tA URI for a Cloud Storage resource (gs://...)\n" + + "Examples: ", + Detect.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + if (command.equals("faces")) { + analyzeFaces(path); + } + if (command.equals("labels")) { + analyzeLabels(path); + } + if (command.equals("labels-file")) { + analyzeLabelsFile(path); + } + if (command.equals("shots")) { + analyzeShots(path); + } + if (command.equals("safesearch")) { + analyzeSafeSearch(path); + } + } + + /** + * Performs facial analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeFaces(String gcsUri) throws ExecutionException, + IOException, InterruptedException { + VideoIntelligenceServiceSettings settings = + VideoIntelligenceServiceSettings.defaultBuilder().build(); + VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create(settings); + + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.FACE_DETECTION) + .build(); + + OperationFuture operation = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults result : operation.get().getAnnotationResultsList()) { + if (result.getFaceAnnotationsCount() > 0) { + System.out.println("Faces:"); + for (FaceAnnotation annotation : result.getFaceAnnotationsList()) { + System.out.println("\tFace Thumb is of length: " + annotation.getThumbnail().length()); + for (VideoSegment seg : annotation.getSegmentsList()) { + System.out.printf("\t\tLocation: %fs - %fs\n", + seg.getStartTimeOffset() / 1000000.0, + seg.getEndTimeOffset() / 1000000.0); + } + System.out.println(); + } + System.out.println(); + } else { + System.out.println("No faces detected in " + gcsUri); + } + } + } + + /** + * Performs label analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeLabels(String gcsUri) throws + ExecutionException, IOException, InterruptedException { + VideoIntelligenceServiceSettings settings = + VideoIntelligenceServiceSettings.defaultBuilder().build(); + VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create(settings); + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + + OperationFuture operation = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults result : operation.get().getAnnotationResultsList()) { + if (result.getLabelAnnotationsCount() > 0) { + System.out.println("Labels:"); + for (LabelAnnotation annotation : result.getLabelAnnotationsList()) { + System.out.println("\tDescription: " + annotation.getDescription()); + for (LabelLocation loc : annotation.getLocationsList()) { + if (loc.getSegment().getStartTimeOffset() == -1 + && loc.getSegment().getEndTimeOffset() == -1) { + System.out.println("\tLocation: Entire video"); + } else { + System.out.printf( + "\tLocation: %fs - %fs\n", + loc.getSegment().getStartTimeOffset() / 1000000.0, + loc.getSegment().getEndTimeOffset() / 1000000.0); + } + } + System.out.println(); + } + } else { + System.out.println("No labels detected in " + gcsUri); + } + } + } + + /** + * Performs label analysis on the video at the provided file path. + * + * @param filePath the path to the video file to analyze. + */ + public static void analyzeLabelsFile(String filePath) throws + ExecutionException, IOException, InterruptedException { + VideoIntelligenceServiceSettings settings = + VideoIntelligenceServiceSettings.defaultBuilder().build(); + VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create(settings); + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + byte[] encodedBytes = Base64.encodeBase64(data); + + AnnotateVideoRequest request = AnnotateVideoRequest.newBuilder() + .setInputContent(new String(encodedBytes, "UTF-8")) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + + OperationFuture operation = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults result : operation.get().getAnnotationResultsList()) { + if (result.getLabelAnnotationsCount() > 0) { + System.out.println("Labels:"); + for (LabelAnnotation annotation : result.getLabelAnnotationsList()) { + System.out.println("\tDescription: " + annotation.getDescription()); + for (LabelLocation loc : annotation.getLocationsList()) { + if (loc.getSegment().getStartTimeOffset() == -1 + && loc.getSegment().getEndTimeOffset() == -1) { + System.out.println("\tLocation: Entire video"); + } else { + System.out.printf( + "\tLocation: %fs - %fs\n", + loc.getSegment().getStartTimeOffset() / 1000000.0, + loc.getSegment().getEndTimeOffset() / 1000000.0); + } + } + System.out.println(); + } + } else { + System.out.println("No labels detected in " + filePath); + } + } + } + + /** + * Performs shot analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeShots(String gcsUri) + throws ExecutionException, IOException, InterruptedException { + VideoIntelligenceServiceSettings settings = + VideoIntelligenceServiceSettings.defaultBuilder().build(); + VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create(settings); + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SHOT_CHANGE_DETECTION) + .build(); + + OperationFuture operation = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + + // Print detected shot changes and their location ranges in the analyzed video. + for (VideoAnnotationResults result : operation.get().getAnnotationResultsList()) { + if (result.getShotAnnotationsCount() > 0) { + System.out.println("Shots:"); + int segCount = 0; + for (VideoSegment seg : result.getShotAnnotationsList()) { + System.out.println("\tSegment: " + segCount++); + System.out.printf("\tLocation: %fs - %fs\n", + seg.getStartTimeOffset() / 1000000.0, + seg.getEndTimeOffset() / 1000000.0); + } + System.out.println(); + } else { + System.out.println("No shot changes detected in " + gcsUri); + } + } + } + + /** + * Performs safe search analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeSafeSearch(String gcsUri) + throws ExecutionException, IOException, InterruptedException { + VideoIntelligenceServiceSettings settings = + VideoIntelligenceServiceSettings.defaultBuilder().build(); + VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create(settings); + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SAFE_SEARCH_DETECTION) + .build(); + + OperationFuture operation = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + + // Print detected safe search annotations and their positions in the analyzed video. + boolean foundUnsafe = false; + for (VideoAnnotationResults result : operation.get().getAnnotationResultsList()) { + if (result.getSafeSearchAnnotationsCount() > 0) { + System.out.println("Safe search annotations:"); + for (SafeSearchAnnotation note : result.getSafeSearchAnnotationsList()) { + System.out.printf("Location: %fs\n", note.getTimeOffset() / 1000000.0); + System.out.println("\tAdult: " + note.getAdult().name()); + System.out.println("\tMedical: " + note.getMedical().name()); + System.out.println("\tRacy: " + note.getRacy().name()); + System.out.println("\tSpoof: " + note.getSpoof().name()); + System.out.println("\tViolent: " + note.getViolent().name()); + System.out.println(); + + if (note.getAdult().compareTo(Likelihood.LIKELY) > 1 + || note.getViolent().compareTo(Likelihood.LIKELY) > 1 + || note.getRacy().compareTo(Likelihood.LIKELY) > 1) { + foundUnsafe = false; + } + } + } else { + System.out.println("No safe search annotations detected in " + gcsUri); + } + } + + if (foundUnsafe) { + System.out.println("Found potentially unsafe content."); + } else { + System.out.println("Did not detect unsafe content."); + } + } +} diff --git a/video/cloud-client/src/test/java/com/example/video/DetectIT.java b/video/cloud-client/src/test/java/com/example/video/DetectIT.java new file mode 100644 index 00000000000..eb43a148341 --- /dev/null +++ b/video/cloud-client/src/test/java/com/example/video/DetectIT.java @@ -0,0 +1,108 @@ +/* + Copyright 2017, Google, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.example.video; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.concurrent.ExecutionException; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIT { + private ByteArrayOutputStream bout; + private PrintStream out; + private Detect app; + + static final String FACES_FILE_LOCATION = "gs://demomaker/gbike.mp4"; + static final String LABEL_FILE_LOCATION = "gs://demomaker/cat.mp4"; + static final String SHOTS_FILE_LOCATION = "gs://demomaker/gbikes_dinosaur.mp4"; + static final String SAFE_SEARCH_FILE_LOCATION = "gs://cloudmleap/video/next/animals.mp4"; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testFaces() throws IOException, InterruptedException, ExecutionException { + String[] args = {"faces", FACES_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + + // Model changes have caused the results from face detection to change to an + // empty response (e.g. no faces detected) so we check either for an empty + // response or that a response with face thumbnails was returned. + if (got.indexOf("No faces detected") == 0) { + assertThat(got).contains("Face Thumb is of size:"); + } else { + // No faces detected, verify sample reports this. + assertThat(got).contains("No faces detected in " + FACES_FILE_LOCATION); + } + } + + @Test + public void testLabels() throws IOException, InterruptedException, ExecutionException { + String[] args = {"labels", LABEL_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + + // Test that the video with a cat has the whiskers label (may change). + assertThat(got.toUpperCase()).contains("WHISKERS"); + } + + @Test + public void testSafeSearch() throws IOException, InterruptedException, ExecutionException { + String[] args = {"safesearch", SAFE_SEARCH_FILE_LOCATION}; + app.argsHelper(args); + String got = bout.toString(); + + // Check that the API returned at least one safe search annotation. + assertThat(got).contains("Medical:"); + // Check that the API detected positions for the annotations. + assertThat(got).contains("Location: "); + } + + @Test + public void testShots() throws IOException, InterruptedException, ExecutionException { + String[] args = {"shots", SHOTS_FILE_LOCATION}; + app.argsHelper(args); + String got = bout.toString(); + + // Check that the API returned at least one segment. + assertThat(got).contains("Segment:"); + // Check that the API detected a segment at the begining of the video. + assertThat(got).contains("Location: 0"); + } + +}