diff --git a/lib/vision/index.js b/lib/vision/index.js index 1132828de32..dbc6f310386 100644 --- a/lib/vision/index.js +++ b/lib/vision/index.js @@ -29,6 +29,7 @@ var fs = require('fs'); var is = require('is'); var nodeutil = require('util'); var prop = require('propprop'); +var request = require('request'); /** * @type {module:storage/file} @@ -183,7 +184,7 @@ Vision.prototype.annotate = function(requests, callback) { * * @param {string|string[]|module:storage/file|module:storage/file[]} images - * The source image(s) to run the detection on. It can be either a local - * image path or a gcloud File object. + * image path, a remote image URL, or a gcloud File object. * @param {string[]|object=} options - An array of types or a configuration * object. * @param {number} options.maxResults - The maximum number of results, per type, @@ -218,6 +219,16 @@ Vision.prototype.annotate = function(requests, callback) { * }); * * //- + * // Run feature detection over a remote image. + * // + * // *Note: This is not an officially supported feature of the Vision API. Our + * // library will make a request to the URL given, convert it to base64, and + * // send that upstream.* + * //- + * var img = 'https://upload.wikimedia.org/wikipedia/commons/5/51/Google.png'; + * vision.detect(img, types, function(err, detection, apiResponse) {}); + * + * //- * // Supply multiple images for feature detection. * //- * var images = [ @@ -1155,8 +1166,9 @@ Vision.convertToBoolean_ = function(baseLikelihood, object) { /** * Determine the type of image the user is asking to be annotated. If a - * {module:storage/file}, convert to its "gs://{bucket}/{file}" URL. If a file - * path, convert to a base64 string. + * {module:storage/file}, convert to its "gs://{bucket}/{file}" URL. If a remote + * URL, read the contents and convert to a base64 string. If a file path to a + * local file, convert to a base64 string. * * @private */ @@ -1178,6 +1190,24 @@ Vision.findImages_ = function(images, callback) { return; } + // File is a URL. + if (/^http/.test(image)) { + request({ + method: 'GET', + uri: image, + encoding: 'base64' + }, function(err, resp, body) { + if (err) { + callback(err); + return; + } + + callback(null, { content: body }); + }); + + return; + } + // File exists on disk. fs.readFile(image, { encoding: 'base64' }, function(err, contents) { if (err) { diff --git a/system-test/vision.js b/system-test/vision.js index 93179f7fc57..e9f4e5d20cf 100644 --- a/system-test/vision.js +++ b/system-test/vision.js @@ -17,6 +17,8 @@ 'use strict'; var assert = require('assert'); +var fs = require('fs'); +var http = require('http'); var is = require('is'); var multiline = require('multiline'); var normalizeNewline = require('normalize-newline'); @@ -37,6 +39,29 @@ describe('Vision', function() { vision = new Vision(env); }); + it('should detect from a URL', function(done) { + var server = http.createServer(function(req, res) { + fs.readFile(IMAGES.logo, function(err, resp) { + assert.ifError(err); + res.end(resp); + }); + }); + + server.listen(8800, function(err) { + assert.ifError(err); + + var url = 'http://localhost:8800/logo.png'; + + vision.detect(url, ['logos'], function(err, logos) { + assert.ifError(err); + + assert.deepEqual(logos, ['Google']); + + done(); + }); + }); + }); + it('should perform multiple detections', function(done) { var detectionTypes = ['faces', 'labels', 'safeSearch']; diff --git a/test/vision/index.js b/test/vision/index.js index 7bb2b13400f..db93183cf33 100644 --- a/test/vision/index.js +++ b/test/vision/index.js @@ -42,6 +42,11 @@ function FakeFile() { this.calledWith_ = arguments; } +var requestOverride = null; +var fakeRequest = function() { + return (requestOverride || util.noop).apply(this, arguments); +}; + describe('Vision', function() { var IMAGE = './image.jpg'; var PROJECT_ID = 'project-id'; @@ -50,6 +55,7 @@ describe('Vision', function() { var vision; before(function() { + mockery.registerMock('request', fakeRequest); mockery.registerMock('../../lib/storage/file.js', FakeFile); mockery.registerMock('../../lib/common/service.js', FakeService); mockery.registerMock('../../lib/common/util.js', fakeUtil); @@ -68,6 +74,8 @@ describe('Vision', function() { }); beforeEach(function() { + requestOverride = null; + vision = new Vision({ projectId: PROJECT_ID }); @@ -766,6 +774,44 @@ describe('Vision', function() { }); }); + it('should get a file from a URL', function(done) { + var imageUri = 'http://www.google.com/logo.png'; + var body = 'body'; + + requestOverride = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'GET'); + assert.strictEqual(reqOpts.uri, imageUri); + assert.strictEqual(reqOpts.encoding, 'base64'); + + callback(null, {}, body); + }; + + Vision.findImages_(imageUri, function(err, images) { + assert.ifError(err); + assert.deepEqual(images, [ + { + content: body + } + ]); + done(); + }); + }); + + it('should return an error from reading a URL', function(done) { + var imageUri = 'http://www.google.com/logo.png'; + + var error = new Error('Error.'); + + requestOverride = function(reqOpts, callback) { + callback(error); + }; + + Vision.findImages_(imageUri, function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + it('should read from a file path', function(done) { tmp.setGracefulCleanup();