diff --git a/index.js b/index.js index c6eabb2..ff57c8d 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ exports.Auth = require('./src/auth'); exports.BosClient = require('./src/bos_client'); +exports.MediaClient = require('./src/media_client'); exports.HttpClient = require('./src/http_client'); exports.MimeType = require('./src/mime.types'); diff --git a/package.json b/package.json index dc44f87..f759e45 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "author": "leeight@gmail.com", "license": "MIT", "dependencies": { + "debug": "^2.1.3", "q": "^1.1.2", "underscore": "^1.7.0" } diff --git a/src/auth.js b/src/auth.js index 516a710..0680f31 100644 --- a/src/auth.js +++ b/src/auth.js @@ -19,6 +19,8 @@ var util = require('util'); +var debug = require('debug')('auth'); + /** * @constructor @@ -57,6 +59,10 @@ Auth.prototype.generateAuthorization = function (method, resource, params, var rv = this.headersCanonicalization(headers || {}, headersToSign); var canonicalHeaders = rv[0]; var signedHeaders = rv[1]; + debug('canonicalUri = %j', canonicalUri); + debug('canonicalQueryString = %j', canonicalQueryString); + debug('canonicalHeaders = %j', canonicalHeaders); + debug('signedHeaders = %j', signedHeaders); var rawSignature = util.format('%s\n%s\n%s\n%s', method, canonicalUri, canonicalQueryString, canonicalHeaders); diff --git a/src/headers.js b/src/headers.js index e399db1..e52d7d3 100644 --- a/src/headers.js +++ b/src/headers.js @@ -16,15 +16,17 @@ /*eslint-env node*/ -exports.CONTENT_TYPE = 'Content-Type'; -exports.CONTENT_LENGTH = 'Content-Length'; -exports.CONTENT_MD5 = 'Content-MD5'; -exports.CONNECTION = 'Connection'; -exports.HOST = 'Host'; -exports.USER_AGENT = 'User-Agent'; - +exports.CONTENT_TYPE = 'content-type'; +exports.CONTENT_LENGTH = 'content-length'; +exports.CONTENT_MD5 = 'content-md5'; +exports.CONNECTION = 'connection'; +exports.HOST = 'host'; +exports.USER_AGENT = 'user-agent'; + +exports.AUTHORIZATION = 'authorization'; exports.X_BCE_DATE = 'x-bce-date'; exports.X_BCE_ACL = 'x-bce-acl'; +exports.X_BCE_REQUEST_ID = 'x-bce-request-id'; exports.X_HTTP_HEADERS = 'http_headers'; exports.X_BODY = 'body'; diff --git a/src/http_client.js b/src/http_client.js index b3953a4..ede3d7e 100644 --- a/src/http_client.js +++ b/src/http_client.js @@ -22,6 +22,7 @@ var stream = require('stream'); var u = require('underscore'); var Q = require('q'); +var debug = require('debug')('http_client'); var H = require('./headers'); @@ -65,19 +66,25 @@ HttpClient.prototype.sendRequest = function (httpMethod, path, body, headers, pa defaultHeaders[H.HOST] = options.host; headers = u.extend({}, defaultHeaders, headers); - if (typeof signFunction === 'function') { - headers.Authorization = signFunction(this.config.credentials, - httpMethod, path, params, headers); - } + + // if (!headers.hasOwnProperty(H.X_BCE_REQUEST_ID)) { + // headers[H.X_BCE_REQUEST_ID] = this._generateRequestId(); + // } // Check the content-length if (!headers.hasOwnProperty(H.CONTENT_LENGTH)) { headers[H.CONTENT_LENGTH] = this._guessContentLength(body); } + if (typeof signFunction === 'function') { + headers[H.AUTHORIZATION] = signFunction(this.config.credentials, + httpMethod, path, params, headers); + } + var api = options.protocol === 'https:' ? require('https') : require('http'); options.method = httpMethod; options.headers = headers; + debug('request headers = %j', headers); var deferred = Q.defer(); @@ -109,6 +116,20 @@ HttpClient.prototype.sendRequest = function (httpMethod, path, body, headers, pa return deferred.promise; }; +HttpClient.prototype._generateRequestId = function () { + function chunk() { + var v = (~~(Math.random() * 0xffff)).toString(16); + if (v.length < 4) { + v += new Array(4 - v.length + 1).join('0'); + } + return v; + } + + return util.format('%s%s-%s-%s-%s-%s%s%s', + chunk(), chunk(), chunk(), chunk(), + chunk(), chunk(), chunk(), chunk()); +}; + HttpClient.prototype._guessContentLength = function (data) { if (data == null) { return 0; @@ -202,6 +223,7 @@ HttpClient.prototype._sendRequest = function (req, data) { /*eslint-enable*/ if (Buffer.isBuffer(data)) { + debug('send body = %j', data.toString()); req.write(data); req.end(); } diff --git a/src/media_client.js b/src/media_client.js new file mode 100644 index 0000000..fd53e97 --- /dev/null +++ b/src/media_client.js @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved + * + * 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. + */ + +/*eslint-env node*/ +/*eslint max-params:[0,10]*/ + +var util = require('util'); +var path = require('path'); +var fs = require('fs'); + +var u = require('underscore'); +var Q = require('q'); + +var H = require('./headers'); +var Auth = require('./auth'); +var HttpClient = require('./http_client'); +var BceBaseClient = require('./bce_base_client'); +var MimeType = require('./mime.types'); +var WMStream = require('./wm_stream'); + +/** + * Media service api. + * @constructor + * @param {Object} config The media client configuration. + * @extends {BceBaseClient} + */ +function MediaClient(config) { + BceBaseClient.call(this, config, 'media', true); +} +util.inherits(MediaClient, BceBaseClient); + + +// --- B E G I N --- +MediaClient.prototype.createPipeline = function (pipelineName, sourceBucket, targetBucket, + opt_config, opt_description, opt_options) { + + var url = '/v3/pipeline'; + var options = opt_options || {}; + var body = JSON.stringify({ + pipelineName: pipelineName, + sourceBucket: sourceBucket, + targetBucket: targetBucket, + config: opt_config || {capacity: 5}, + description: opt_description || '' + }); + + return this._sendRequest('POST', url, { + body: body, + config: options.config, + }); +}; + +MediaClient.prototype.getPipeline = function (pipelineName, opt_options) { + var url = '/v3/pipeline/' + pipelineName; + var options = opt_options || {}; + + return this._sendRequest('GET', url, {config: options.config}); +}; + +MediaClient.prototype.deletePipeline = function (pipelineName, opt_options) { + var url = '/v3/pipeline/' + pipelineName; + var options = opt_options || {}; + + return this._sendRequest('DELETE', url, {config: options.config}); +}; + +MediaClient.prototype.getAllPipelines = function (opt_options) { + var url = '/v3/pipeline'; + var options = opt_options || {}; + + return this._sendRequest('GET', url, {config: options.config}); +}; + +MediaClient.prototype.createJob = function (pipelineName, source, target, presetName, opt_options) { + var url = '/v3/job'; + var options = opt_options || {}; + var body = JSON.stringify({ + pipelineName: pipelineName, + source: source, + target: target, + presetName: presetName + }); + + return this._sendRequest('POST', url, { + body: body, + config: options.config + }); +}; + +MediaClient.prototype.getAllJobs = function (pipelineName, opt_options) { + var url = '/v3/job'; + var options = opt_options || {}; + var params = {pipelineName: pipelineName}; + + return this._sendRequest('GET', url, { + params: params, + config: options.config + }); +}; + +MediaClient.prototype.getJob = function (jobId, opt_options) { + var url = '/v3/job/' + jobId; + var options = opt_options || {}; + + return this._sendRequest('GET', url, {config: options.config}); +}; + +/** + * 创建模板, 不对外部用户开放,仅服务于Console. + * @param {string} presetName 转码模板名称. + * @param {string} container 音视频文件的容器. + * @param {Object=} clip 是否截取音视频片段. + * @param {Object=} audio 音频输出信息的集合,不填写表示只处理视频部分. + * @param {Object=} video 视频输出信息的集合,不填写表示只处理音频部分. + * @param {Object=} opt_encryption HLS加解密信息的集合. + * @param {boolean=} opt_transmux 是否仅执行容器格式转换. + * @param {string=} opt_description 转码模板描述. + * @param {Object=} opt_options Media Client 的配置. + */ +MediaClient.prototype.createPreset = function (presetName, container, clip, audio, video, + opt_encryption, opt_transmux, opt_description, opt_options) { + // container: mp4, flv, hls, mp3, m4a + var url = '/v3/preset'; + var options = opt_options || {}; + var body = { + presetName: presetName, + container: container + }; + clip && (body.clip = clip); + audio && (body.audio = audio); + video && (body.video = video); + opt_encryption && (body.encryption = opt_encryption); + opt_transmux != null && (body.transmux = opt_transmux); + opt_description && (body.description = opt_description); + + return this._sendRequest('POST', url, { + body: JSON.stringify(body), + config: options.config + }); +}; + +MediaClient.prototype.getPreset = function (presetName, opt_options) { + var url = '/v3/preset/' + presetName; + var options = opt_options || {}; + + return this._sendRequest('GET', url, { + config: options.config + }); +}; + +MediaClient.prototype.deletePreset = function (presetName, opt_options) { + var url = '/v3/preset/' + presetName; + var options = opt_options || {}; + + return this._sendRequest('DELETE', url, { + config: options.config + }); +}; + +MediaClient.prototype.getMediainfo = function (bucket, key, opt_options) { + var url = '/v3/mediainfo'; + var options = opt_options || {}; + var params = { + bucket: bucket, + key: key + }; + + return this._sendRequest('GET', url, { + params: params, + config: options.config + }); +}; + +MediaClient.prototype.createSignature = function (credentials, httpMethod, path, params, headers) { + var auth = new Auth(credentials.ak, credentials.sk); + // 不能对content-type,content-length,content-md5进行签名 + // 不能对x-bce-request-id进行签名 + var headersToSign = ['host']; + return auth.generateAuthorization(httpMethod, path, params, headers, 0, 0, headersToSign); +}; +// --- E N D --- + + +MediaClient.prototype._sendRequest = function (httpMethod, resource, varArgs) { + var defaultArgs = { + bucketName: null, + key: null, + body: null, + headers: {}, + params: {}, + config: {}, + outputStream: null + }; + var args = u.extend(defaultArgs, varArgs); + + var config = u.extend({}, this.config, args.config); + + var httpClient = new HttpClient(config); + return httpClient.sendRequest(httpMethod, resource, args.body, + args.headers, args.params, u.bind(this.createSignature, this), + args.outputStream + ); +}; + +module.exports = MediaClient; + + + + + + + +/* vim: set ts=4 sw=4 sts=4 tw=120: */ diff --git a/test/media.config.js b/test/media.config.js new file mode 100644 index 0000000..9a4aadd --- /dev/null +++ b/test/media.config.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved + * + * 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. + */ + +module.exports = { + credentials: { + ak: '46bd9968a6194b4bbdf0341f2286ccce', + sk: 'ec7f4e0174254f6f9020f9680fb1da9f' + }, + connection_timeout_in_mills: 5000, // 5 seconds + // region: 'bj', + // region: 'nj', + // 有了 endpoint,region 其实就不生效了 + endpoint: 'http://multimedia.bce-testinternal.baidu.com' +}; + + + + + + + + + + + +/* vim: set ts=4 sw=4 sts=4 tw=120: */ diff --git a/test/media_client.spec.js b/test/media_client.spec.js new file mode 100644 index 0000000..816f8da --- /dev/null +++ b/test/media_client.spec.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved + * + * 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. + */ +var u = require('underscore'); +var debug = require('debug')('media_client'); + +var MediaClient = require('../src/media_client'); + +describe('MediaClient', function () { + var client; + var fail; + + beforeEach(function () { + fail = u.bind(function () { + return this.fail.call(this, JSON.stringify(arguments)); + }, this); + + client = new MediaClient(require('./media.config')); + }); + + afterEach(function () { + // TODO delete all pipelines + }); + + it('createPipeline', function (done) { + var pipelineName = 'medium_priority_pipe'; + var sourceBucket = 'exampleIuputBucket'; + var targetBucket = 'exampleTargetBucket'; + client.createPipeline(pipelineName, sourceBucket, targetBucket) + .then(function (response) { + debug('%j', response); + }) + .catch(fail) + .fin(done); + }); + + it('getPipeline', function (done) { + var pipelineName = 'medium_priority_pipe'; + client.getPipeline(pipelineName) + .then(function (response) { + expect(response.body.pipelineName).toEqual(pipelineName); + expect(response.body.sourceBucket).toEqual('jianbininput'); + expect(response.body.targetBucket).toEqual('jianbinoutput'); + }) + .catch(fail) + .fin(done); + }); + + it('getAllPipelines', function (done) { + client.getAllPipelines() + .then(function (response) { + expect(response.body.pipelines).not.toBe(null); + expect(response.body.pipelines.length > 0).toBe(true); + }) + .catch(fail) + .fin(done); + }); + + it('createJob', function (done) { + var pipelineName = 'medium_priority_pipe'; + var source = {sourceKey: 'a'}; + var target = {targetKey: 'b'}; + var presetName = 'bce_video_mp4_1920x1080_3660kbps'; + client.createJob(pipelineName, source, target, presetName) + .then(function (response) { + debug('%j', response); + }) + .catch(fail) + .fin(done); + }); + + it('getAllJobs', function (done) { + var pipelineName = 'medium_priority_pipe'; + client.getAllJobs(pipelineName) + .then(function (response) { + // debug('getAllJobs response %j', response); + expect(response.body.jobs).not.toBe(null); + }) + .catch(fail) + .fin(done); + }); + + it('getJob', function (done) { + var jobId = 'jobId'; + client.getJob(jobId) + .then(function (response) { + debug('%j', response); + }) + .catch(fail) + .fin(done); + }); + + it('deletePreset', function (done) { + var presetName = 'tmp_bce_video_mp4_320x640_128kbps'; + client.deletePreset(presetName) + .then(function (response) { + debug('deletePreset response = %j', response); + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + it('createPreset', function (done) { + var presetName = 'tmp_bce_video_mp4_320x640_128kbps'; + var container = 'mp4'; + var clip = { + startTimeInSecond: 0, + durationInSecond: 60 + }; + var audio = { + bitRateInBps: 256000 + }; + var video = { + codec: 'h264', + codecOptions: { + profile: 'baseline' + }, + bitRateInBps: 1024000, + maxFrameRate: 30, + maxWidthInPixel: 4096, + maxHeightInPixel: 3072, + sizingPolicy: 'keep' + }; + client.createPreset(presetName, container, clip, audio, video) + .then(function (response) { + debug('createPreset response = %j', response); + return client.deletePreset(presetName); + }) + .catch(fail) + .fin(done); + }); + + it('getPreset', function (done) { + var presetName = 'bce_video_mp4_1920x1080_3660kbps'; + client.getPreset(presetName) + .then(function (response) { + expect(response.body.presetName).toEqual(presetName); + expect(response.body.state).toEqual('ACTIVE'); + expect(response.body.container).toEqual('mp4'); + expect(response.body.transmux).toEqual(false); + expect(response.body.clip).toEqual({ startTimeInSecond: 0 }); + expect(response.body.audio).toEqual({ + bitRateInBps: 160000, + sampleRateInHz: 44100, + channels: 2 + }); + }) + .catch(fail) + .fin(done); + }); + + it('getMediainfo', function (done) { + var bucket = 'bucket'; + var key = 'key'; + client.getMediainfo(bucket, key) + .then(function (response) { + debug('%j', response); + }) + .catch(fail) + .fin(done); + }); +}); + + + + + + + + + +/* vim: set ts=4 sw=4 sts=4 tw=120: */