diff --git a/video-intelligence/analyze.js b/video-intelligence/analyze.js index 5ed2312cbd..f82c904a8c 100644 --- a/video-intelligence/analyze.js +++ b/video-intelligence/analyze.js @@ -41,14 +41,30 @@ function analyzeFaces (gcsUri) { .then((results) => { // Gets faces const faces = results[0].annotationResults[0].faceAnnotations; - console.log('Faces:'); faces.forEach((face, faceIdx) => { - console.log('Thumbnail size:', face.thumbnail.length); + console.log(`Face #${faceIdx}`); + console.log(`\tThumbnail size: ${face.thumbnail.length}`); face.segments.forEach((segment, segmentIdx) => { - console.log(`Face #${faceIdx}, appearance #${segmentIdx}:`); - console.log(`\tStart: ${segment.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${segment.endTimeOffset / 1e6}s`); + segment = segment.segment; + if (segment.startTimeOffset.seconds === undefined) { + segment.startTimeOffset.seconds = 0; + } + if (segment.startTimeOffset.nanos === undefined) { + segment.startTimeOffset.nanos = 0; + } + if (segment.endTimeOffset.seconds === undefined) { + segment.endTimeOffset.seconds = 0; + } + if (segment.endTimeOffset.nanos === undefined) { + segment.endTimeOffset.nanos = 0; + } + console.log(`\tAppearance #${segmentIdx}:`); + console.log(`\t\tStart: ${segment.startTimeOffset.seconds}` + + `.${(segment.startTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\t\tEnd: ${segment.endTimeOffset.seconds}.` + + `${(segment.endTimeOffset.nanos / 1e6).toFixed(0)}s`); }); + console.log(`\tLocations:`); }); }) .catch((err) => { @@ -63,7 +79,9 @@ function analyzeLabelsGCS (gcsUri) { const Video = require('@google-cloud/video-intelligence'); // Instantiates a client - const video = Video(); + const video = Video({ + servicePath: `videointelligence.googleapis.com` + }); // The GCS filepath of the video to analyze // const gcsUri = 'gs://my-bucket/my-video.mp4'; @@ -81,24 +99,32 @@ function analyzeLabelsGCS (gcsUri) { return operation.promise(); }) .then((results) => { - // Gets labels - const labels = results[0].annotationResults[0].labelAnnotations; - console.log('Labels:'); + // Gets annotations for video + const annotations = results[0].annotationResults[0]; + + const labels = annotations.segmentLabelAnnotations; labels.forEach((label) => { - console.log(`Label ${label.description} occurs at:`); - const isEntireVideo = label.locations.some((location) => - location.segment.startTimeOffset.toNumber() === -1 && - location.segment.endTimeOffset.toNumber() === -1 - ); - - if (isEntireVideo) { - console.log(`\tEntire video`); - } else { - label.locations.forEach((location) => { - console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`); - }); - } + console.log(`Label ${label.entity.description} occurs at:`); + label.segments.forEach((segment) => { + let time = segment.segment; + if (time.startTimeOffset.seconds === undefined) { + time.startTimeOffset.seconds = 0; + } + if (time.startTimeOffset.nanos === undefined) { + time.startTimeOffset.nanos = 0; + } + if (time.endTimeOffset.seconds === undefined) { + time.endTimeOffset.seconds = 0; + } + if (time.endTimeOffset.nanos === undefined) { + time.endTimeOffset.nanos = 0; + } + console.log(`\tStart: ${time.startTimeOffset.seconds}` + + `.${(time.startTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tEnd: ${time.endTimeOffset.seconds}.` + + `${(time.endTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tConfidence: ${segment.confidence}`); + }); }); }) .catch((err) => { @@ -137,24 +163,32 @@ function analyzeLabelsLocal (path) { return operation.promise(); }) .then((results) => { - // Gets labels for first video - const labels = results[0].annotationResults[0].labelAnnotations; - console.log('Labels:'); + // Gets annotations for video + const annotations = results[0].annotationResults[0]; + + const labels = annotations.segmentLabelAnnotations; labels.forEach((label) => { - console.log(`Label ${label.description} occurs at:`); - const isEntireVideo = label.locations.some((location) => - location.segment.startTimeOffset.toNumber() === -1 && - location.segment.endTimeOffset.toNumber() === -1 - ); - - if (isEntireVideo) { - console.log(`\tEntire video`); - } else { - label.locations.forEach((location) => { - console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`); - }); - } + console.log(`Label ${label.entity.description} occurs at:`); + label.segments.forEach((segment) => { + let time = segment.segment; + if (time.startTimeOffset.seconds === undefined) { + time.startTimeOffset.seconds = 0; + } + if (time.startTimeOffset.nanos === undefined) { + time.startTimeOffset.nanos = 0; + } + if (time.endTimeOffset.seconds === undefined) { + time.endTimeOffset.seconds = 0; + } + if (time.endTimeOffset.nanos === undefined) { + time.endTimeOffset.nanos = 0; + } + console.log(`\tStart: ${time.startTimeOffset.seconds}` + + `.${(time.startTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tEnd: ${time.endTimeOffset.seconds}.` + + `${(time.endTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tConfidence: ${segment.confidence}`); + }); }); }) .catch((err) => { @@ -195,9 +229,29 @@ function analyzeShots (gcsUri) { console.log(`The entire video is one shot.`); } else { shotChanges.forEach((shot, shotIdx) => { - console.log(`Shot ${shotIdx} occurs from:`); - console.log(`\tStart: ${shot.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${shot.endTimeOffset / 1e6}s`); + console.log(`Scene ${shotIdx} occurs from:`); + if (shot.startTimeOffset === undefined) { + shot.startTimeOffset = {}; + } + if (shot.endTimeOffset === undefined) { + shot.endTimeOffset = {}; + } + if (shot.startTimeOffset.seconds === undefined) { + shot.startTimeOffset.seconds = 0; + } + if (shot.startTimeOffset.nanos === undefined) { + shot.startTimeOffset.nanos = 0; + } + if (shot.endTimeOffset.seconds === undefined) { + shot.endTimeOffset.seconds = 0; + } + if (shot.endTimeOffset.nanos === undefined) { + shot.endTimeOffset.nanos = 0; + } + console.log(`\tStart: ${shot.startTimeOffset.seconds}` + + `.${(shot.startTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tEnd: ${shot.endTimeOffset.seconds}.` + + `${(shot.endTimeOffset.nanos / 1e6).toFixed(0)}s`); }); } }) @@ -220,7 +274,7 @@ function analyzeSafeSearch (gcsUri) { const request = { inputUri: gcsUri, - features: ['SAFE_SEARCH_DETECTION'] + features: ['EXPLICIT_CONTENT_DETECTION'] }; // Human-readable likelihoods @@ -235,15 +289,21 @@ function analyzeSafeSearch (gcsUri) { }) .then((results) => { // Gets unsafe content - const safeSearchResults = results[0].annotationResults[0].safeSearchAnnotations; - console.log('Safe search results:'); - safeSearchResults.forEach((result) => { - console.log(`Time: ${result.timeOffset / 1e6}s`); - console.log(`\tAdult: ${likelihoods[result.adult]}`); - console.log(`\tSpoof: ${likelihoods[result.spoof]}`); - console.log(`\tMedical: ${likelihoods[result.medical]}`); - console.log(`\tViolent: ${likelihoods[result.violent]}`); - console.log(`\tRacy: ${likelihoods[result.racy]}`); + const explicitContentResults = results[0].annotationResults[0].explicitAnnotation; + console.log('Explicit annotation results:'); + explicitContentResults.frames.forEach((result) => { + if (result.timeOffset === undefined) { + result.timeOffset = {}; + } + if (result.timeOffset.seconds === undefined) { + result.timeOffset.seconds = 0; + } + if (result.timeOffset.nanos === undefined) { + result.timeOffset.nanos = 0; + } + console.log(`\tTime: ${result.timeOffset.seconds}` + + `.${(result.timeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\t\tPornography liklihood: ${likelihoods[result.pornographyLikelihood]}`); }); }) .catch((err) => { @@ -280,7 +340,7 @@ require(`yargs`) // eslint-disable-line ) .command( `safe-search `, - `Detects adult content in a video stored in Google Cloud Storage.`, + `Detects explicit content in a video stored in Google Cloud Storage.`, {}, (opts) => analyzeSafeSearch(opts.gcsUri) ) diff --git a/video-intelligence/package.json b/video-intelligence/package.json index 6a4ff41ca5..e17c092aa4 100644 --- a/video-intelligence/package.json +++ b/video-intelligence/package.json @@ -17,15 +17,14 @@ "test": "samples test run --cmd ava -- -T 5m --verbose system-test/*.test.js" }, "dependencies": { - "@google-cloud/video-intelligence": "0.1.0", - "googleapis": "19.0.0", + "@google-cloud/video-intelligence": "^0.3.2", "long": "^3.2.0", - "safe-buffer": "5.1.0", + "safe-buffer": "5.1.1", "yargs": "8.0.2" }, "devDependencies": { - "@google-cloud/nodejs-repo-tools": "1.4.15", - "ava": "0.19.1", + "@google-cloud/nodejs-repo-tools": "1.4.17", + "ava": "0.22.0", "proxyquire": "1.8.0" }, "cloud-repo-tools": { diff --git a/video-intelligence/quickstart.js b/video-intelligence/quickstart.js index 3aeec4334c..bb40eafe9f 100644 --- a/video-intelligence/quickstart.js +++ b/video-intelligence/quickstart.js @@ -20,17 +20,15 @@ const Video = require('@google-cloud/video-intelligence'); // Instantiates a client -const video = Video({ - projectId: process.env.GCLOUD_PROJECT // Replace with your Google Cloud project ID -}); +const video = Video(); // The GCS filepath of the video to analyze -const gcsUri = 'gs://demomaker/tomatoes.mp4'; +const gcsUri = 'gs://nodejs-docs-samples-video/quickstart_short.mp4'; // Construct request const request = { inputUri: gcsUri, - features: ['FACE_DETECTION', 'LABEL_DETECTION', 'SHOT_CHANGE_DETECTION'] + features: ['LABEL_DETECTION'] }; // Execute request @@ -44,47 +42,30 @@ video.annotateVideo(request) // Gets annotations for video const annotations = results[0].annotationResults[0]; - // Gets faces for video from its annotations - const faces = annotations.faceAnnotations; - faces.forEach((face, faceIdx) => { - console.log('Thumbnail size:', face.thumbnail.length); - face.segments.forEach((segment, segmentIdx) => { - console.log(`Face #${faceIdx}, appearance #${segmentIdx}:`); - console.log(`\tStart: ${segment.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${segment.endTimeOffset / 1e6}s`); - }); - }); - // Gets labels for video from its annotations - const labels = annotations.labelAnnotations; + const labels = annotations.segmentLabelAnnotations; labels.forEach((label) => { - console.log(`Label ${label.description} occurs at:`); - const isEntireVideo = label.locations.some((location) => - location.segment.startTimeOffset.toNumber() === -1 && - location.segment.endTimeOffset.toNumber() === -1 - ); - - if (isEntireVideo) { - console.log(`\tEntire video`); - } else { - label.locations.forEach((location) => { - console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`); - }); - } - }); - - // Gets shot changes for video from its annotations - const shotChanges = annotations.shotAnnotations; - if (shotChanges.length === 1) { - console.log(`The entire video is one scene.`); - } else { - shotChanges.forEach((shot, shotIdx) => { - console.log(`Scene ${shotIdx} occurs from:`); - console.log(`\tStart: ${shot.startTimeOffset / 1e6}s`); - console.log(`\tEnd: ${shot.endTimeOffset / 1e6}s`); + console.log(`Label ${label.entity.description} occurs at:`); + label.segments.forEach((segment) => { + segment = segment.segment; + if (segment.startTimeOffset.seconds === undefined) { + segment.startTimeOffset.seconds = 0; + } + if (segment.startTimeOffset.nanos === undefined) { + segment.startTimeOffset.nanos = 0; + } + if (segment.endTimeOffset.seconds === undefined) { + segment.endTimeOffset.seconds = 0; + } + if (segment.endTimeOffset.nanos === undefined) { + segment.endTimeOffset.nanos = 0; + } + console.log(`\tStart: ${segment.startTimeOffset.seconds}` + + `.${(segment.startTimeOffset.nanos / 1e6).toFixed(0)}s`); + console.log(`\tEnd: ${segment.endTimeOffset.seconds}.` + + `${(segment.endTimeOffset.nanos / 1e6).toFixed(0)}s`); }); - } + }); }) .catch((err) => { console.error('ERROR:', err); diff --git a/video-intelligence/system-test/analyze.test.js b/video-intelligence/system-test/analyze.test.js index 7ac7d1d7af..ce54904743 100644 --- a/video-intelligence/system-test/analyze.test.js +++ b/video-intelligence/system-test/analyze.test.js @@ -24,50 +24,54 @@ const tools = require(`@google-cloud/nodejs-repo-tools`); const cmd = `node analyze.js`; const cwd = path.join(__dirname, `..`); +const url = `gs://nodejs-docs-samples-video/quickstart.mp4`; +const shortUrl = `gs://nodejs-docs-samples-video/quickstart_short.mp4`; +const file = `resources/cat.mp4`; + // analyze_faces -test(`should analyze faces in a GCS file`, async (t) => { - const output = await tools.runAsync(`${cmd} faces gs://demomaker/larry_sergey_ice_bucket_short.mp4`, cwd); +test.serial(`should analyze faces in a GCS file`, async (t) => { + const output = await tools.runAsync(`${cmd} faces ${url}`, cwd); t.regex(output, /Thumbnail size: \d+/); - t.regex(output, /Start: \d+\.\d+s/); - t.regex(output, /End: \d+\.\d+s/); + t.regex(output, /Start:.*\d+\.\d+s/); + t.regex(output, /End:.*\d+\.\d+s/); }); // analyze_labels_gcs (one scene) -test(`should analyze labels in a GCS file with one scene`, async (t) => { - const output = await tools.runAsync(`${cmd} labels-gcs gs://demomaker/tomatoes.mp4`, cwd); - t.regex(output, /Label Tomato occurs at:/); - t.regex(output, /Entire video/); +test.serial(`should analyze labels in a GCS file with one scene`, async (t) => { + const output = await tools.runAsync(`${cmd} labels-gcs ${shortUrl}`, cwd); + t.regex(output, /Label shirt occurs at:/); + t.regex(output, /Confidence: \d+\.\d+/); }); // analyze_labels_gcs (multiple scenes) -test(`should analyze labels in a GCS file with multiple scenes`, async (t) => { - const output = await tools.runAsync(`${cmd} labels-gcs gs://demomaker/sushi.mp4`, cwd); - t.regex(output, /Label Food occurs at:/); - t.regex(output, /Start: \d+\.\d+s/); - t.regex(output, /End: \d+\.\d+s/); +test.serial(`should analyze labels in a GCS file with multiple scenes`, async (t) => { + const output = await tools.runAsync(`${cmd} labels-gcs ${url}`, cwd); + t.regex(output, /Label shirt occurs at:/); + t.regex(output, /Confidence: \d+\.\d+/); }); // analyze_labels_local -test(`should analyze labels in a local file`, async (t) => { - const output = await tools.runAsync(`${cmd} labels-file resources/cat.mp4`, cwd); - t.regex(output, /Label Whiskers occurs at:/); - t.regex(output, /Entire video/); +test.serial(`should analyze labels in a local file`, async (t) => { + const output = await tools.runAsync(`${cmd} labels-file ${file}`, cwd); + t.regex(output, /Label whiskers occurs at:/); + t.regex(output, /Confidence: \d+\.\d+/); }); // analyze_shots (multiple shots) -test(`should analyze shots in a GCS file with multiple shots`, async (t) => { - const output = await tools.runAsync(`${cmd} shots gs://demomaker/sushi.mp4`, cwd); - t.regex(output, /Shot 0 occurs from:/); +test.serial(`should analyze shots in a GCS file with multiple shots`, async (t) => { + const output = await tools.runAsync(`${cmd} shots ${url}`, cwd); + t.regex(output, /Scene 0 occurs from:/); }); // analyze_shots (one shot) -test(`should analyze shots in a GCS file with one shot`, async (t) => { - const output = await tools.runAsync(`${cmd} shots gs://demomaker/tomatoes.mp4`, cwd); +test.serial(`should analyze shots in a GCS file with one shot`, async (t) => { + const output = await tools.runAsync(`${cmd} shots ${shortUrl}`, cwd); t.regex(output, /The entire video is one shot./); }); // analyze_safe_search -test(`should analyze safe search results in a GCS file`, async (t) => { - const output = await tools.runAsync(`${cmd} safe-search gs://demomaker/tomatoes.mp4`, cwd); - t.regex(output, /Spoof:/); +test.serial(`should analyze safe search results in a GCS file`, async (t) => { + const output = await tools.runAsync(`${cmd} safe-search ${url}`, cwd); + t.regex(output, /Time: \d+\.\d+s/); + t.regex(output, /Explicit annotation results:/); }); diff --git a/video-intelligence/system-test/quickstart.test.js b/video-intelligence/system-test/quickstart.test.js index bff0f3c49a..20965be0c6 100644 --- a/video-intelligence/system-test/quickstart.test.js +++ b/video-intelligence/system-test/quickstart.test.js @@ -24,7 +24,5 @@ const cwd = path.join(__dirname, `..`); test(`should analyze a hardcoded video`, async (t) => { const output = await tools.runAsync(cmd, cwd); - t.regex(output, /Label Tomato occurs at:/); - t.regex(output, /Entire video/); - t.regex(output, /The entire video is one scene./); + t.regex(output, /Label standing occurs at:/); });