diff --git a/src/index.d.ts b/src/index.d.ts index 427189f..bbca1c8 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -21,6 +21,8 @@ export * from './vision/computerVision'; export * from './vision/face'; export * from './vision/video'; +export * from './vision/videoIndexerV2' + export interface ContentTypeHeaders { /** * Media type of the body sent to the API. diff --git a/src/index.js b/src/index.js index c8ae336..2ab3b95 100644 --- a/src/index.js +++ b/src/index.js @@ -24,5 +24,6 @@ module.exports = { textTranslator: require('./language/textTranslator'), bingSpeech: require('./speech/bingSpeech'), bingEntitySearch: require('./search/bingEntitySearch'), - contentModerator: require('./vision/contentModerator') + contentModerator: require('./vision/contentModerator'), + videoIndexerV2: require('./vision/videoIndexerV2') }; \ No newline at end of file diff --git a/src/vision/videoIndexerV2.d.ts b/src/vision/videoIndexerV2.d.ts new file mode 100644 index 0000000..8a607ac --- /dev/null +++ b/src/vision/videoIndexerV2.d.ts @@ -0,0 +1,33 @@ +import { CommonConstructorOptions, ContentTypeHeaders, OcpApimSubscriptionKeyHeaders, ContentTypeHeaderTypes } from "../index"; + +/** + * Video Indexer is a cloud service that enables you to extract the following insights from your videos using artificial intelligence technologies: + */ +export class videoIndexer { + + constructor(options: videoIndexerV2Options); + getAccounts(options: GetAccountOptions): Promise; + getAccountAccessToken(options: GetAccountAccessTokenOptions): Promise + getUserAccessToken(options: GetUserAccessTokenOptions): Promise +} + +export interface videoIndexerV2Options { + apiKey: string +} + +export interface GetAccountOptions { + location: string + generateAccessTokens?: boolean, + allowEdit?: boolean, +} + +export interface GetAccountAccessTokenOptions { + location: string, + accountId: string, + allowEdit?: boolean +} + +export interface GetUserAccessTokenOptions { + location: string, + allowEdit?: boolean +} diff --git a/src/vision/videoIndexerV2.js b/src/vision/videoIndexerV2.js new file mode 100644 index 0000000..a5d6538 --- /dev/null +++ b/src/vision/videoIndexerV2.js @@ -0,0 +1,319 @@ +const commonService = require('../commonService'); +const querystring = require('querystring'); + +/** + * Video Indexer is a cloud service that enables you to extract the following insights from your videos using artificial intelligence technologies: + */ +class videoIndexerV2 extends commonService { + /** + * Constructor. + * + * @param {Object} obj + * @param {string} obj.apiKey + */ + constructor({ apiKey }) { + const endpoint = "api.videoindexer.ai"; + super({ apiKey, endpoint }); + this.endpoints = [ + endpoint + ]; + // https://docs.microsoft.com/en-us/azure/media-services/media-services-media-encoder-standard-formats + this.supportedFormats = ['flv', 'mxf', 'gxf', 'ts', 'ps', '3gp', '3gpp', 'mpg', 'wmv', 'asf', 'avi', 'mp4', 'm4a', 'm4v', 'isma', 'ismv', 'dvr-ms', 'mkv', 'wav', 'mov'] + } + + /** + * Get Accounts - returns the account details associated + * with an API key. + * @param {Object} obj + * @param {string} obj.location + * @param {boolean} obj.generateAccessTokens + * @param {boolean} obj.allowEdit + * @returns {Promise<[Object]>} A promise containing an array of objects + */ + getAccounts({ + location, + generateAccessTokens, + allowEdit, + }) { + const operation = { + parameters: [ + { + name: 'location', + required: true, + type: 'routeParam', + typeName: 'string' + }, + { + name: 'generateAccessTokens', + required: false, + type: 'queryStringParam', + typeName: 'boolean', + },{ + name: 'allowEdit', + required: false, + type: 'queryStringParam', + typeName: 'boolean' + }, + ], + path: `auth/{location}/Accounts`, + method: 'GET' + }; + + const requestParameters = { + location, + generateAccessTokens, + allowEdit + } + + return this.makeRequest({ + operation: operation, + parameters: requestParameters + }) + } + + /** + * Get Account Access Token - returns an account access token as a string. + * @param {Object} obj + * @param {string} obj.location + * @param {boolean} obj.accountId + * @param {boolean} obj.allowEdit + * @returns {Promise} A promise containing an access token + */ + getAccountAccessToken({ + location, + accountId, + allowEdit, + }){ + const operation = { + parameters: [ + { + name: 'location', + required: true, + type: 'routeParam', + typeName: 'string' + }, + { + name: 'accountId', + required: true, + type: 'routeParam', + typeName: 'string', + },{ + name: 'allowEdit', + required: false, + type: 'queryStringParam', + typeName: 'boolean' + }, + ], + path: `auth/{location}/Accounts/{accountId}/AccessToken`, + method: 'GET' + }; + + const requestParameters = { + location, + accountId, + allowEdit + } + + return this.makeRequest({ + operation: operation, + parameters: requestParameters + }) + } + + /** + * Get User Access Token - returns a user access token as a string. + * @param {Object} obj + * @param {string} obj.location + * @param {boolean} obj.allowEdit + * @returns {Promise} A promise containing an access token + */ + getUserAccessToken({ + location, + allowEdit, + }){ + const operation = { + parameters: [ + { + name: 'location', + required: true, + type: 'routeParam', + typeName: 'string' + },{ + name: 'allowEdit', + required: false, + type: 'queryStringParam', + typeName: 'boolean' + }, + ], + path: `auth/{location}/Users/Me/AccessToken`, + method: 'GET' + }; + + const requestParameters = { + location, + allowEdit + } + + return this.makeRequest({ + operation: operation, + parameters: requestParameters + }) + } + + /** + * Uploads a video to the Video Indexer + */ + uploadVideo(params){ + if(!params.path && !params.videoUrl) { + throw new Error('no video to upload. A video must be specified, either via videoUrl or a path'); + } + + const pathToVideo = (params.path) ? params.path : params.videoUrl; + + const isFormatSupported = this.supportedFormats.filter(sf => { + return pathToVideo.endsWith(sf); + }).length + if (isFormatSupported == 0) { + return Promise.reject(reject(new Error('Unsupported format. Supported formats are ' + this.supportedFormats.join(',')))); + } + + const requestHeaders = {}; + + if(params.videoUrl && !((/(http)s?(:\/\/)/).test(params.videoUrl))){ + // video should point to public url + throw new Error('videoUrl is not an http/https link. A videoUrl must contain a public path'); + } else if(params.videoUrl) { + // video needs to be url encoded + params.videoUrl = querystring.stringify(params.videoUrl); + } + + if(params.path && ((/(http)s?(:\/\/)/).test(params.path))){ + // path should not point to public url + throw new Error('path is an http/https link. A path must point to a local path'); + } else if(params.path) { + // set content type as video will be sent in request body + requestHeaders['Content-Type'] = 'multipart/form-data'; + } + + const operation = { + parameters: [ + { + name: 'location', + required: true, + type: 'routeParam', + typeName: 'string' + },{ + name: 'accountId', + required: true, + type: 'routeParam', + typeName: 'string', + },{ + name: 'accessToken', + required: true, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'name', + required: true, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'indexingPreset', + required: false, + type: 'queryStringParam', + typeName: 'string', + options: ['Default',"AudioOnly","DefaultWithNoiseReduction"] + },{ + name: 'description', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'partition', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'externalId', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'callbackUrl', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'metadata', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'language', + required: false, + type: 'queryStringParam', + typeName: 'string', + options: ['ar-EG',"zh-Hans","en-US","fr-FR", "de-DE", "it-IT", "ja-JP", "pt-BR", "ru-RU", "es-ES", "auto"] + },{ + name: 'videoUrl', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'fileName', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'streamingPreset', + required: false, + type: 'queryStringParam', + typeName: 'string', + options: ['Default',"SingleBitrate","AdaptiveBitrate","NoStreaming"] + },{ + name: 'linguisticModelId', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'privacy', + required: false, + type: 'queryStringParam', + typeName: 'string', + options: ['Private','Public'] + },{ + name: 'externalUrl', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'assetId', + required: false, + type: 'queryStringParam', + typeName: 'string' + },{ + name: 'priority', + required: false, + type: 'queryStringParam', + typeName: 'string', + options: ['Low','Normal','High'] + },{ + "name": "path", + "description": "The path to the file", + "required": false, + "typeName": "string" + } + ], + path: '{location}/Accounts/{accountId}/Videos', + method: 'POST' + }; + + return this.makeRequest({ + operation: operation, + parameters: params, + headers: requestHeaders + }); + } +} + +module.exports = videoIndexerV2; \ No newline at end of file diff --git a/test/config.js b/test/config.js index b07bacb..b815531 100644 --- a/test/config.js +++ b/test/config.js @@ -70,5 +70,9 @@ module.exports = { apiKey: "insert-key-here", appID: "insert-appID-here", versionID: "0.1" + }, + videoIndexerV2: { + apiKey: "insert-key-here", + accountId: "insert-accountId-here" } } \ No newline at end of file diff --git a/test/vision/videoIndexerV2Test.js b/test/vision/videoIndexerV2Test.js new file mode 100644 index 0000000..2be6dea --- /dev/null +++ b/test/vision/videoIndexerV2Test.js @@ -0,0 +1,118 @@ +const cognitive = require('../../src/index.js'); +const config = require('../config.js'); +const should = require('should'); + +describe('Video Indexer V2', () => { + + const client = new cognitive.videoIndexerV2({ + apiKey: config.videoIndexerV2.apiKey + }); + + const videoUrl = 'https://mparnisari.blob.core.windows.net/storage/video_girl_laughing.mp4'; + const videoPath = './test/assets/video_girl_laughing.mp4'; + + describe('Get Accounts', () => { + const requestParams = { + location: "trial", + allowEdit: false, + generateAccessTokens: true + }; + + it('should return an array contain an object with url, id and accounts access token', done => { + client.getAccounts(requestParams) + .then(response => { + should(response).not.be.undefined(); + should(response).be.an.Array(); + should(response[0]).have.property('id'); + should(response[0]).have.property('url'); + should(response[0]).have.property('accessToken'); + done(); + }) + .catch(err => { + done(err); + }) + }) + }) + + describe('Get Account Access Token', () => { + const requestParams = { + location: "trial", + accountId: config.videoIndexerV2.accountId, + allowEdit: false + }; + + it('should return an account access token', done => { + client.getAccountAccessToken(requestParams) + .then(response => { + should(response).not.be.undefined(); + should(response).startWith("ey", "the encoding of brackets alwys generates this at the start"); + done(); + }) + .catch(err => { + done(err); + }) + }) + }) + + describe('Get User Access Token', () => { + const requestParams = { + location: "trial", + allowEdit: false + }; + + it('should return a user access token', done => { + client.getUserAccessToken(requestParams) + .then(response => { + should(response).not.be.undefined(); + should(response).startWith("ey", "the encoding of brackets alwys generates this at the start"); + done(); + }) + .catch(err => { + done(err); + }) + }) + }) + + describe.only('Upload Video', () => { + let accessToken; + const videoName = 'A girl laughing'; + + beforeEach('generate an access token', (done) => { + const requestParams = { + location: "trial", + accountId: config.videoIndexerV2.accountId, + allowEdit: true + }; + client.getAccountAccessToken(requestParams) + .then(response => { + accessToken = response; + done(); + }) + .catch((err) => { + done(err); + }) + }); + + it(`should return a json with state, "Uploaded" or "Processing", and a video with name, ${videoName} and a Video id`, done => { + const requestParams = { + path: videoPath, + location: "trial", + accountId: config.videoIndexerV2.accountId, + accessToken: accessToken, + name: videoName, + privacy: 'Public' + }; + + client.uploadVideo(requestParams) + .then(response => { + should(response).have.property('name', requestParams.name); + should(response.state).be.equalOneOf('Processing', 'Uploaded') + should(response.id).not.be.null(); + done(); + }) + .catch(err => { + done(err); + }) + }); + }) +})