Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video update #476

Merged
merged 13 commits into from
Sep 21, 2017
12 changes: 6 additions & 6 deletions video/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The [Cloud Video Intelligence API](https://cloud.google.com/video-intelligence)

yarn install

[prereq]: ../README.md#prerequisities
[prereq]: ../README.md#prerequisites
[run]: ../README.md#how-to-run-a-sample

## Samples
Expand All @@ -44,17 +44,17 @@ Commands:
Intelligence API.
labels-gcs <gcsUri> Labels objects in a video stored in Google Cloud Storage using the Cloud Video Intelligence API.
labels-file <gcsUri> Labels objects in a video stored locally using the Cloud Video Intelligence API.
safe-search <gcsUri> Detects adult content in a video stored in Google Cloud Storage.
safe-search <gcsUri> Detects explicit content in a video stored in Google Cloud Storage.

Options:
--help Show help [boolean]

Examples:
node analyze.js faces gs://demomaker/volleyball_court.mp4
node analyze.js shots gs://demomaker/volleyball_court.mp4
node analyze.js labels-gcs gs://demomaker/volleyball_court.mp4
node analyze.js faces gs://demomaker/larry_sergey_ice_bucket_short.mp4
node analyze.js shots gs://demomaker/sushi.mp4
node analyze.js labels-gcs gs://demomaker/tomatoes.mp4
node analyze.js labels-file cat.mp4
node analyze.js safe-search gs://demomaker/volleyball_court.mp4
node analyze.js safe-search gs://demomaker/tomatoes.mp4

For more information, see https://cloud.google.com/video-intelligence/docs
```
Expand Down
96 changes: 60 additions & 36 deletions video/analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ function analyzeFaces (gcsUri) {
const Video = require('@google-cloud/video-intelligence');

// Instantiates a client
const video = Video();
const video = Video({
servicePath: `alpha-videointelligence.googleapis.com`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder: remove this (and other instances of "alpha-") before merging.

});

// The GCS filepath of the video to analyze
// const gcsUri = 'gs://my-bucket/my-video.mp4';
Expand All @@ -41,13 +43,18 @@ 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`);
console.log(`\tAppearance #${segmentIdx}:`);
console.log(`\t\tStart: ${segment.startTimeOffset / 1e6}s`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the time offset fields have been changed to return seconds and nanoseconds, perhaps startTimeOffset.seconds and startTimeOffset.nanos.

(also for other instances of {start,end}TimeOffsets)

console.log(`\t\tEnd: ${segment.endTimeOffset / 1e6}s`);
});
console.log(`\tLocations:`);
face.locations.forEach((location, locationIdx) => {
const box = location.boundingBox;
console.log(`\t\tTime ${location.timeOffset / 1e6}s: (${box.top}, ${box.left}) - (${box.bottom}, ${box.right})`);
});
});
})
Expand All @@ -63,7 +70,9 @@ function analyzeLabelsGCS (gcsUri) {
const Video = require('@google-cloud/video-intelligence');

// Instantiates a client
const video = Video();
const video = Video({
servicePath: `alpha-videointelligence.googleapis.com`
});

// The GCS filepath of the video to analyze
// const gcsUri = 'gs://my-bucket/my-video.mp4';
Expand All @@ -83,22 +92,27 @@ function analyzeLabelsGCS (gcsUri) {
.then((results) => {
// Gets labels
const labels = results[0].annotationResults[0].labelAnnotations;
// TODO What does "level" mean?
// TODO Why are there no segment-level annotations?

console.log('Labels:');
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) => {
label.locations.forEach((location) => {
const isEntireVideo = label.locations.some((location) =>
location.segment.startTimeOffset.toNumber() === -1 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we no longer return -1 as time offsets to indicate entire video labels.

location.segment.endTimeOffset.toNumber() === -1
);

if (isEntireVideo) {
console.log(`\tEntire video`);
} else {
console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`);
console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`);
});
}
}

console.log(`\tConfidence: ${location.confidence}`);
});
});
})
.catch((err) => {
Expand All @@ -114,7 +128,9 @@ function analyzeLabelsLocal (path) {
const fs = require('fs');

// Instantiates a client
const video = Video();
const video = Video({
servicePath: `alpha-videointelligence.googleapis.com`
});

// The local filepath of the video to analyze
// const path = 'my-file.mp4';
Expand All @@ -139,22 +155,26 @@ function analyzeLabelsLocal (path) {
.then((results) => {
// Gets labels for first video
const labels = results[0].annotationResults[0].labelAnnotations;
// TODO What does "level" mean?
// TODO Why are there no segment-level annotations?

console.log('Labels:');
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) => {

label.locations.forEach((location) => {
const isEntireVideo =
location.segment.startTimeOffset.toNumber() === -1 &&
location.segment.endTimeOffset.toNumber() === -1;

if (isEntireVideo) {
console.log(`\tEntire video`);
} else {
console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`);
console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`);
});
}
}
console.log(`\tConfidence: ${location.confidence}`);
});
});
})
.catch((err) => {
Expand All @@ -169,7 +189,9 @@ function analyzeShots (gcsUri) {
const Video = require('@google-cloud/video-intelligence');

// Instantiates a client
const video = Video();
const video = Video({
servicePath: `alpha-videointelligence.googleapis.com`
});

// The GCS filepath of the video to analyze
// const gcsUri = 'gs://my-bucket/my-video.mp4';
Expand Down Expand Up @@ -213,7 +235,9 @@ function analyzeSafeSearch (gcsUri) {
const Video = require('@google-cloud/video-intelligence');

// Instantiates a client
const video = Video();
const video = Video({
servicePath: `alpha-videointelligence.googleapis.com`
});

// The GCS filepath of the video to analyze
// const gcsUri = 'gs://my-bucket/my-video.mp4';
Expand All @@ -235,9 +259,9 @@ function analyzeSafeSearch (gcsUri) {
})
.then((results) => {
// Gets unsafe content
const safeSearchResults = results[0].annotationResults[0].safeSearchAnnotations;
console.log('Safe search results:');
safeSearchResults.forEach((result) => {
const explicitContentResults = results[0].annotationResults[0].safeSearchAnnotations;
console.log('Explicit content results:');
explicitContentResults.forEach((result) => {
console.log(`Time: ${result.timeOffset / 1e6}s`);
console.log(`\tAdult: ${likelihoods[result.adult]}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoof, medical etc are removed (the API has not been returning non-empty values for those), and only adult (renamed pornography_likelihood) remains.

console.log(`\tSpoof: ${likelihoods[result.spoof]}`);
Expand Down Expand Up @@ -280,7 +304,7 @@ require(`yargs`) // eslint-disable-line
)
.command(
`safe-search <gcsUri>`,
`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)
)
Expand Down
10 changes: 5 additions & 5 deletions video/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
"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.2.0",
"googleapis": "21.3.0",
"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": {
Expand Down
38 changes: 27 additions & 11 deletions video/quickstart.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ const Video = require('@google-cloud/video-intelligence');

// Instantiates a client
const video = Video({
servicePath: 'alpha-videointelligence.googleapis.com',
projectId: process.env.GCLOUD_PROJECT // Replace with your Google Cloud project ID
});

// The GCS filepath of the video to analyze
const gcsUri = 'gs://demomaker/tomatoes.mp4';
const gcsUri = 'gs://nodejs-docs-samples-video/quickstart_short.mp4';

// Human-readable likelihoods
const likelihoods = ['UNKNOWN', 'VERY_UNLIKELY', 'UNLIKELY', 'POSSIBLE', 'LIKELY', 'VERY_LIKELY'];

// Construct request
const request = {
Expand Down Expand Up @@ -59,19 +63,19 @@ video.annotateVideo(request)
const labels = annotations.labelAnnotations;
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
);
label.locations.forEach((location) => {
const isEntireVideo =
location.segment.startTimeOffset.toNumber() === -1 &&
location.segment.endTimeOffset.toNumber() === -1;

if (isEntireVideo) {
console.log(`\tEntire video`);
} else {
label.locations.forEach((location) => {
if (isEntireVideo) {
console.log(`\tEntire video`);
} else {
console.log(`\tStart: ${location.segment.startTimeOffset / 1e6}s`);
console.log(`\tEnd: ${location.segment.endTimeOffset / 1e6}s`);
});
}
}
console.log(`\tConfidence: ${location.confidence}`);
});
});

// Gets shot changes for video from its annotations
Expand All @@ -85,6 +89,18 @@ video.annotateVideo(request)
console.log(`\tEnd: ${shot.endTimeOffset / 1e6}s`);
});
}

// Gets explicit content data for video from its annotations
const explicitContentResults = annotations.safeSearchAnnotations;
console.log('Explicit content results:');
explicitContentResults.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]}`);
});
})
.catch((err) => {
console.error('ERROR:', err);
Expand Down
46 changes: 27 additions & 19 deletions video/system-test/analyze.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,58 @@ 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, /Time \d+\.\d+s: \(\d+, \d+\) - \(\d+, \d+\)/);
});

// 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:/);
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, /Entire video/);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might no longer show "Entire video" in the output. (see comments above about time offsets)

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, /Entire video/);
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:/);
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, /Entire video/);
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);
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, /Shot 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);
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, /Spoof:/);
});
3 changes: 2 additions & 1 deletion video/system-test/quickstart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ 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, /Label standing occurs at:/);
t.regex(output, /Entire video/);
t.regex(output, /Confidence: \d+\.\d+/);
t.regex(output, /The entire video is one scene./);
});