├── test ├── mocha.opts ├── resources │ ├── settings.json.enc │ └── local_settings.json ├── util.js ├── bitcodin.statistics.test.js ├── rest.test.js ├── bitcodin.thumbnail.test.js ├── bitcodin.sprite.test.js ├── bitcodin.transmux.test.js ├── bitcodin.test.js ├── bitcodin.output.test.js ├── bitcodin.encoding-profile.test.js ├── bitcodin.input.test.js └── bitcodin.job.test.js ├── examples ├── package.json ├── createTransmux.js ├── createEncryptedTransmux.js ├── transcodeSintelToDASHAndHLS.js ├── transcodeSintelToDASHAndHLSwithHLSEncryption.js ├── transcodeSintelToDASHAndHLSwithPlayreadyDRM.js ├── transcodeSintelToDASHAndHLSwithWidevinePlayreadyDRMcombined.js ├── transcodeSintelToDASHAndHLSwithWidevineDRM.js ├── transcodeSintelToDASHAndHLSwithMultipleAudioStreams.js ├── mergeAudioStreams.js ├── createThumbnail.js └── createLiveStream.js ├── .travis.yml ├── README.md ├── .gitignore ├── .npmignore ├── package.json ├── LICENSE └── lib ├── rest.js └── bitcodin.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --bail 2 | --recursive 3 | --timeout 60000 4 | -------------------------------------------------------------------------------- /test/resources/settings.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitmovin/bitcodin-node/HEAD/test/resources/settings.json.enc -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "description": "bitcodin-node examples", 4 | "author": "martin.fillafer@bitmovin.net", 5 | "dependencies": { 6 | "bitcodin": "^0.4.0", 7 | "q": "^1.4.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.11' 4 | - '0.10' 5 | branches: 6 | only: 7 | - master 8 | notifications: 9 | email: 10 | - bitcodin-node@bitmovin.net 11 | - dominic.miglar@bitmovin.net 12 | before_install: 13 | - openssl aes-256-cbc -K $encrypted_6f4c562257d5_key -iv $encrypted_6f4c562257d5_iv 14 | -in test/resources/settings.json.enc -out test/resources/settings.json -d 15 | script: npm run-script cover 16 | after_script: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 17 | -------------------------------------------------------------------------------- /test/resources/local_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": "EMAIL", 3 | "password": "PASSWORD", 4 | "apiKey": "API_KEY", 5 | "s3OutputEUWest": { 6 | "type": "s3", 7 | "accessKey": "AWS_ACCESS_KEY", 8 | "secretKey": "AWS_SECRET_KEY", 9 | "region": "S3_BUCKET_REGION", 10 | "name": "NAME", 11 | "bucket": "S3_BUCKET_NAME", 12 | "prefix": "SUBFOLDER" 13 | }, 14 | "ftpOutput": { 15 | "type": "ftp", 16 | "name": "FTP_NAME", 17 | "host": "FTP_HOST", 18 | "username": "FTP_USERNAME", 19 | "password": "FTP_PASSWORD", 20 | "passive": false 21 | } 22 | } -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 24.06.15. 3 | */ 4 | 5 | require('mocha'); 6 | 7 | var chai = require('chai').use(require("chai-as-promised")), 8 | Q = require('q'); 9 | 10 | chai.should(); 11 | 12 | module.exports = { 13 | 'expect': chai.expect, 14 | 'settings': require('./resources/settings.json'), 15 | 'testAll': function (elems, fun) { 16 | var promises = []; 17 | 18 | for (var i in elems) { 19 | if(elems.hasOwnProperty(i)) 20 | promises.push(fun(elems[i])); 21 | } 22 | 23 | return Q.all(promises); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This API client is deprecated! 2 | This API client works with our previous encoding service available at https://app.bitmovin.com only, which is no longer available to new customers. So, if you are planning to integrate our services, please use our new Bitmovin API which is available at [https://dashboard.bitmovin.com ](https://dashboard.bitmovin.com) and its new API clients! 3 | 4 | --- 5 | 6 | **Bitmovin Node API Client**: [https://github.com/bitmovin/bitmovin-javascript](https://github.com/bitmovin/bitmovin-javascript) 7 | 8 | **Bitmovin API documentation:** [https://developer.bitmovin.com/hc](https://developer.bitmovin.com/hc) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | settings.json 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | .idea 31 | 32 | test/resources/settings.json -------------------------------------------------------------------------------- /test/bitcodin.statistics.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Statistic', function () { 9 | /* 10 | it('should get the output statistics for the current calendar month', function () { 11 | return bitcodin.statistic.get().should.eventually.be.fulfilled; 12 | }); 13 | 14 | it('should get the job statistics for the given time window', function () { 15 | var fromDate = '2000-12-24'; 16 | var toDate = '2100-12-24'; 17 | return bitcodin.statistic.jobs(fromDate, toDate).should.eventually.be.fulfilled; 18 | }); 19 | */ 20 | }); 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | .idea 30 | 31 | .npmignore 32 | .gitignore 33 | test/resources/settings.json 34 | nodeTest.js 35 | travis 36 | .travis.yml -------------------------------------------------------------------------------- /test/rest.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | rest = require('../lib/rest'); 7 | 8 | describe('REST', function () { 9 | it('should return status code 401', function () { 10 | var err = {"message": "Given bitcodin-api-key is not authorized!", "status": 401}; 11 | return rest.get('/').should.eventually.be.rejectedWith(err); 12 | }); 13 | 14 | it('should return status code 404', function () { 15 | rest.addHeader('bitcodin-api-key', util.settings.apiKey); 16 | 17 | var err = {"message": "unknown api-request-url", "status": 404}; 18 | return rest.get('/').should.eventually.be.rejectedWith(err); 19 | }); 20 | 21 | it('should list sintel as default input', function () { 22 | rest.addHeader('bitcodin-api-key', util.settings.apiKey); 23 | 24 | return rest.get('/inputs').should.eventually.have.property('inputs'); 25 | }); 26 | 27 | it('should set the hostname', function() { 28 | rest.setHostname('localhost'); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/bitcodin.thumbnail.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by lkroepfl on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Thumbnail', function () { 9 | var thumbnails = []; 10 | 11 | it('should create a thumbnail', function (done) { 12 | var jobPromise = bitcodin.job.list(0, 'finished'); 13 | 14 | jobPromise.then(function (result) { 15 | var thumbnailConfiguration = { 16 | "jobId": result.jobs[0].jobId, 17 | "height": 320, 18 | "position": 50, 19 | "async": true 20 | }; 21 | 22 | var promise = bitcodin.thumbnail.create(thumbnailConfiguration); 23 | 24 | promise.then(function (data) { 25 | thumbnails.push(data); 26 | done(); 27 | }, done); 28 | }, done); 29 | }); 30 | 31 | it('should get a thumbnail for a given id', function () { 32 | return bitcodin.thumbnail.get(thumbnails[0].id).should.eventually.be.fulfilled; 33 | }); 34 | 35 | }); -------------------------------------------------------------------------------- /test/bitcodin.sprite.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by lkroepfl on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Sprite', function () { 9 | var sprites = []; 10 | 11 | it('should create a sprite', function (done) { 12 | var jobPromise = bitcodin.job.list(0, 'finished'); 13 | 14 | jobPromise.then(function (result) { 15 | var spriteConfiguration = { 16 | "jobId": result.jobs[0].jobId, 17 | "height": 240, 18 | "width": 320, 19 | "distance": 5, 20 | "async": true 21 | }; 22 | 23 | var promise = bitcodin.sprite.create(spriteConfiguration); 24 | 25 | promise.then(function (data) { 26 | sprites.push(data); 27 | done(); 28 | }, done); 29 | }, done); 30 | }); 31 | 32 | it('should get a sprite for a given id', function () { 33 | return bitcodin.sprite.get(sprites[0].id).should.eventually.be.fulfilled; 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcodin", 3 | "version": "0.7.0", 4 | "description": "Bitcodin API wrapper", 5 | "homepage": "https://github.com/bitmovin/bitcodin-node", 6 | "main": "lib/bitcodin.js", 7 | "scripts": { 8 | "test": "mocha", 9 | "cover": "istanbul cover _mocha -- -R spec" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/bitmovin/bitcodin-node.git" 14 | }, 15 | "keywords": [ 16 | "bitcodin", 17 | "video transcoding", 18 | "api", 19 | "bitmovin" 20 | ], 21 | "author": { 22 | "name": "Martin Fillafer", 23 | "email": "martin.fillafer@bitmovin.com", 24 | "url": "https://bitmovin.com/" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/bitmovin/bitcodin-node/issues" 28 | }, 29 | "engines": { 30 | "node": "v0.11" 31 | }, 32 | "dependencies": { 33 | "lodash": "^3.9.3", 34 | "q": "^1.4.1" 35 | }, 36 | "devDependencies": { 37 | "chai": "^3.0.0", 38 | "chai-as-promised": "^5.1.0", 39 | "coveralls": "^2.11.2", 40 | "istanbul": "^0.3.15", 41 | "mocha": "^2.2.5" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "Martin Fillafer", 46 | "email": "martin.fillafer@bitmovin.com" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /examples/createTransmux.js: -------------------------------------------------------------------------------- 1 | 2 | var bitcodin = require('bitcodin')('YOUR_API_KEY'), createTransmuxPromise, transmuxDetails; 3 | 4 | //Please see our documentation regarding transmuxing for more details: 5 | //http://docs.bitcodinrestapi.apiary.io/#reference/transmux-*beta*/transmux/create-a-transmux 6 | 7 | var transmuxConfiguration = { 8 | "jobId": 123456, //required 9 | "videoRepresentationId": 98765, //required 10 | "audioRepresentationIds": [91234], //required 11 | "filename": "optional-custom-output-filename.mp4" //optional 12 | }; 13 | 14 | // Create a Transmux 15 | createTransmuxPromise = bitcodin.transmux.create(transmuxConfiguration); 16 | createTransmuxPromise.then( 17 | function (transmuxResponse) { 18 | transmuxDetails = transmuxResponse; 19 | console.log('Successfully created a new transmuxing'); 20 | }, 21 | function (transmuxDetails) { 22 | console.log('Error while creating a new transmuxing', transmuxDetails); 23 | } 24 | ); 25 | 26 | createTransmuxPromise = bitcodin.transmux.get(transmuxDetails.id); 27 | createTransmuxPromise.then( 28 | function (transmuxResponse) { 29 | console.log('Successfully got transmuxing details: ', transmuxResponse); 30 | }, 31 | function (response) { 32 | console.log('Error while fetching transmuxing details', response); 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /examples/createEncryptedTransmux.js: -------------------------------------------------------------------------------- 1 | 2 | var bitcodin = require('bitcodin')('YOUR_API_KEY'), createTransmuxPromise, transmuxDetails; 3 | 4 | //Please see our documentation regarding transmuxing for more details: 5 | //http://docs.bitcodinrestapi.apiary.io/#reference/transmux-*beta*/transmux/create-a-transmux-with-encryption 6 | 7 | var transmuxConfiguration = { 8 | "jobId": 123456, 9 | "videoRepresentationId": 98765, 10 | "audioRepresentationIds": [91234], 11 | "filename": "optional-custom-output-filename-encrypted.mp4", 12 | "encryptionConfig": { 13 | "keyAscii": "YOUR_32_CHARACTER_KEY_IN_HEX_FORMAT", //required 14 | "kid": "YOUR_32_CHARACTER_KEY_IN_HEX_FORMAT" //required 15 | } 16 | }; 17 | 18 | // Create a Transmux 19 | createTransmuxPromise = bitcodin.transmux.create(transmuxConfiguration); 20 | createTransmuxPromise.then( 21 | function (transmuxResponse) { 22 | transmuxDetails = transmuxResponse; 23 | console.log('Successfully created an encrypted transmuxing'); 24 | }, 25 | function (transmuxDetails) { 26 | console.log('Error while creating an encrypted transmuxing', transmuxDetails); 27 | } 28 | ); 29 | 30 | crateTransmuxPromise = bitcodin.transmux.get(transmuxDetails.id); 31 | createTransmuxPromise.then( 32 | function (transmuxResponse) { 33 | console.log('Successfully got encrypted transmuxing details: ', transmuxResponse); 34 | }, 35 | function (response) { 36 | console.log('Error while fetching encrypted transmuxing details', response); 37 | } 38 | ); -------------------------------------------------------------------------------- /test/bitcodin.transmux.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by lkroepfl on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Transmux', function () { 9 | var transmuxings = []; 10 | 11 | it('should transmux an encoding to an mp4', function (done) { 12 | var jobPromise = bitcodin.job.list(0, 'finished'); 13 | 14 | jobPromise.then(function (result) { 15 | var jobDetails = result.jobs[0]; 16 | var videoStreamConfigs = jobDetails.encodingProfiles[0].videoStreamConfigs; 17 | var audioStreamConfigs = jobDetails.encodingProfiles[0].audioStreamConfigs; 18 | var transmuxConfiguration = { 19 | "jobId": jobDetails.jobId 20 | }; 21 | 22 | var audioRepresentationIds = []; 23 | if(audioStreamConfigs.length > 0) { 24 | for (var i = 0, len = audioStreamConfigs.length; i < len; i++) { 25 | audioRepresentationIds.push(audioStreamConfigs[i].representationId); 26 | } 27 | transmuxConfiguration.audioRepresentationIds = audioRepresentationIds; 28 | } 29 | 30 | if(videoStreamConfigs.length > 0) { 31 | transmuxConfiguration.videoRepresentationId = videoStreamConfigs[0].representationId; 32 | } 33 | 34 | var promise = bitcodin.transmux.create(transmuxConfiguration); 35 | 36 | promise.then(function (data) { 37 | transmuxings.push(data); 38 | done(); 39 | }, done); 40 | }, done); 41 | }); 42 | 43 | it('should get a transmuxing for a given id', function () { 44 | return bitcodin.transmux.get(transmuxings[0].id).should.eventually.be.fulfilled; 45 | }); 46 | 47 | }); -------------------------------------------------------------------------------- /test/bitcodin.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var expect = require('chai').expect, 6 | util = require('./util'), 7 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 8 | 9 | describe('Bitcodin', function () { 10 | 11 | it('should get an error for having not api key', function () { 12 | expect(function() { 13 | require('../lib/bitcodin')(); 14 | }).to.throw(Error); 15 | }); 16 | 17 | it('should get an error for having a wrong version key', function () { 18 | expect(function() { 19 | require('../lib/bitcodin')(util.settings.apiKey, 'wrong') 20 | }).to.throw(Error); 21 | }); 22 | 23 | /* it('should get the job statistics for the given time window', function () { 24 | var fromDate = '2000-12-24'; 25 | var toDate = '2100-12-24'; 26 | return bitcodin.statistic.jobs(fromDate, toDate).should.eventually.be.fulfilled; 27 | }); 28 | 29 | it('should fail to get job statistics for wrong from Date', function () { 30 | var fromDate = '2000-13-24'; 31 | var toDate = '2100-12-24'; 32 | return bitcodin.statistic.jobs(fromDate, toDate).should.eventually.be.rejected; 33 | }); 34 | 35 | it('should fail to get job statistics for missing from Date', function () { 36 | return bitcodin.statistic.jobs().should.eventually.be.fulfilled; 37 | }); 38 | 39 | it('should fail to get job statistics for wrong to Date', function () { 40 | var fromDate = '2000-12-24'; 41 | var toDate = '2100-13-24'; 42 | return bitcodin.statistic.jobs(fromDate, toDate).should.eventually.be.rejected; 43 | }); 44 | 45 | it('should fail to get job statistics for missing to Date', function () { 46 | var fromDate = '2000-12-24'; 47 | return bitcodin.statistic.jobs(fromDate).should.eventually.be.fulfilled; 48 | }); 49 | */ 50 | }); 51 | -------------------------------------------------------------------------------- /test/bitcodin.output.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Output', function () { 9 | var outputIds = []; 10 | 11 | it('should create a S3 output config', function () { 12 | var promise = bitcodin.output.s3.create(util.settings.s3OutputEUWest); 13 | 14 | promise.then(function (data) { 15 | data.should.have.property('outputId'); 16 | outputIds.push(data.outputId); 17 | }); 18 | 19 | return promise.should.eventually.be.fulfilled; 20 | }); 21 | 22 | // TODO: create GCS output 23 | 24 | it('should create a FTP output config', function () { 25 | var promise = bitcodin.output.ftp.create(util.settings.ftpOutput); 26 | 27 | promise.then(function (data) { 28 | outputIds.push(data.outputId); 29 | }); 30 | 31 | return promise.should.eventually.be.fulfilled; 32 | }); 33 | 34 | it('should list outputs', function () { 35 | return bitcodin.output.list().should.eventually.be.fulfilled; 36 | }); 37 | 38 | it('should list outputs of a given page', function () { 39 | var page = 2; 40 | return bitcodin.output.list(page).should.eventually.be.fulfilled; 41 | }); 42 | 43 | it('should get output details for a given output id', function () { 44 | return bitcodin.output.getDetails(outputIds[0]).should.eventually.be.fulfilled; 45 | }); 46 | 47 | it('should not get output details for a invalid output id', function () { 48 | return util.testAll([undefined, 'foo'], function(outputId) { 49 | return bitcodin.output.getDetails(outputId).should.eventually.be.rejected; 50 | }); 51 | }); 52 | 53 | it('should delete an output for a given output id', function () { 54 | return util.testAll(outputIds, function(outputId) { 55 | return bitcodin.output.delete(outputId).should.eventually.be.fulfilled; 56 | }); 57 | }); 58 | 59 | it('should not delete output for a invalid output id', function () { 60 | return util.testAll([undefined, 'foo'], function(outputId) { 61 | return bitcodin.output.delete(outputId).should.eventually.be.rejected; 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /lib/rest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 22.06.15. 3 | */ 4 | 5 | var http = require('http'), 6 | Q = require('q'), 7 | basePath = '/api', 8 | options = { 9 | hostname: 'portal.bitcodin.com', 10 | port: 80, 11 | method: 'GET', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | 'Accepted': 'application/json', 15 | 'User-Agent': 'Bitcodin/v1 NodeBindings/' + require('../package').version 16 | } 17 | }; 18 | 19 | module.exports = { 20 | 21 | setHostname: function (hostname) { 22 | options.hostname = hostname; 23 | }, 24 | 25 | addHeader: function (key, value) { 26 | options.headers[key] = value; 27 | }, 28 | 29 | addHeaders: function (obj) { 30 | for (var i in obj) { 31 | if (obj.hasOwnProperty(i)) 32 | this.addHeader(i, obj[i]); 33 | } 34 | }, 35 | 36 | get: function (path) { 37 | return this.request('GET', path); 38 | }, 39 | 40 | post: function (path, data) { 41 | return this.request('POST', path, JSON.stringify(data)); 42 | }, 43 | 44 | delete: function (path) { 45 | return this.request('DELETE', path); 46 | }, 47 | 48 | patch: function (path) { 49 | return this.request('PATCH', path); 50 | }, 51 | 52 | request: function (method, path, body) { 53 | options.method = method; 54 | options.path = basePath + path; 55 | 56 | var def = Q.defer(), 57 | response = '', 58 | req = http.request(options, function (res) { 59 | res.setEncoding('utf8'); 60 | res.on('data', function (chunk) { 61 | response += chunk; 62 | }); 63 | 64 | res.on('end', function () { 65 | var json = response ? JSON.parse(response) : undefined; 66 | 67 | if (res.statusCode >= 200 && res.statusCode < 300) { 68 | json ? def.resolve(json) : def.resolve(); 69 | } else { 70 | json ? def.reject(json) : def.reject(); 71 | } 72 | }); 73 | }); 74 | 75 | req.on('error', function (e) { 76 | def.reject(e); 77 | }); 78 | 79 | if (body) req.write(body); 80 | 81 | req.end(); 82 | 83 | return def.promise; 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /test/bitcodin.encoding-profile.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('EncodingProfile', function () { 9 | var encodingProfileIds = []; 10 | 11 | it('should create a new encoding profile', function () { 12 | var encodingProfile = { 13 | "name": "bitcodin Encoding Profile", 14 | "videoStreamConfigs": [ 15 | { 16 | "defaultStreamId": 0, 17 | "bitrate": 1024000, 18 | "profile": "Main", 19 | "preset": "premium", 20 | "height": 480, 21 | "width": 204 22 | } 23 | ], 24 | "audioStreamConfigs": [ 25 | { 26 | "defaultStreamId": 0, 27 | "bitrate": 256000 28 | } 29 | ] 30 | }; 31 | 32 | var promise = bitcodin.encodingProfile.create(encodingProfile); 33 | 34 | promise.then(function (data) { 35 | encodingProfileIds.push(data.encodingProfileId); 36 | }); 37 | 38 | return promise.should.eventually.be.fulfilled; 39 | }); 40 | 41 | it('should list available encoding profiles', function () { 42 | return bitcodin.encodingProfile.list().should.eventually.be.fulfilled; 43 | }); 44 | 45 | it('should list available encoding profiles of a given page', function () { 46 | var page = 2; 47 | return bitcodin.encodingProfile.list(page).should.eventually.be.fulfilled; 48 | }); 49 | 50 | it('should get the encoding profile for a given id', function () { 51 | return bitcodin.encodingProfile.get(encodingProfileIds[0]).should.eventually.be.fulfilled; 52 | }); 53 | 54 | it('should not get the encoding profile for a invalid id', function () { 55 | return util.testAll([undefined, 'foo'], function(encodingProfileId) { 56 | return bitcodin.encodingProfile.get(encodingProfileId).should.eventually.be.rejected; 57 | }); 58 | }); 59 | 60 | it('should delete all created encodingProfiles', function () { 61 | return util.testAll(encodingProfileIds, function (encodingProfileId) { 62 | return bitcodin.encodingProfile.delete(encodingProfileId).should.eventually.be.fulfilled; 63 | }); 64 | }); 65 | 66 | it('should not delete the encoding profile for a invalid id', function () { 67 | return util.testAll([undefined, 'foo'], function(encodingProfileId) { 68 | return bitcodin.encodingProfile.delete(encodingProfileId).should.eventually.be.rejected; 69 | }); 70 | }); 71 | }); -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"] 62 | }; 63 | 64 | Q.all([createInputPromise, createEncodingProfilePromise]) 65 | .then( 66 | function (result) { 67 | console.log('Successfully created input and encoding profile'); 68 | jobConfiguration.inputId = result[0].inputId; 69 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 70 | 71 | bitcodin.job.create(jobConfiguration) 72 | .then( 73 | function (newlyCreatedJob) { 74 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 75 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 76 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 77 | }, 78 | function (error) { 79 | console.log('Error while creating a new transcoding job:', error); 80 | } 81 | ); 82 | }, 83 | function (error) { 84 | console.log('Error while creating input and/or encoding profile:', error); 85 | } 86 | ); 87 | -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLSwithHLSEncryption.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"], 62 | "speed": "standard", 63 | "hlsEncryptionConfig": { 64 | "method": "SAMPLE-AES", 65 | "key": "cab5b529ae28d5cc5e3e7bc3fd4a544d", 66 | "iv": "08eecef4b026deec395234d94218273d" 67 | } 68 | }; 69 | 70 | Q.all([createInputPromise, createEncodingProfilePromise]) 71 | .then( 72 | function (result) { 73 | console.log('Successfully created input and encoding profile'); 74 | jobConfiguration.inputId = result[0].inputId; 75 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 76 | 77 | bitcodin.job.create(jobConfiguration) 78 | .then( 79 | function (newlyCreatedJob) { 80 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 81 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 82 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 83 | }, 84 | function (error) { 85 | console.log('Error while creating a new transcoding job:', error); 86 | } 87 | ); 88 | }, 89 | function (error) { 90 | console.log('Error while creating input and/or encoding profile:', error); 91 | } 92 | ); 93 | -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLSwithPlayreadyDRM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"], 62 | "speed": "standard", 63 | "drmConfig": { 64 | "system": "playready", 65 | "keySeed": "XVBovsmzhP9gRIZxWfFta3VVRPzVEWmJsazEJ46I", 66 | "laUrl": "http://playready.directtaps.net/pr/svc/rightsmanager.asmx", 67 | "method": "mpeg_cenc", 68 | "kid": "746573745f69645f4639465043304e4f" 69 | } 70 | }; 71 | 72 | Q.all([createInputPromise, createEncodingProfilePromise]) 73 | .then( 74 | function (result) { 75 | console.log('Successfully created input and encoding profile'); 76 | jobConfiguration.inputId = result[0].inputId; 77 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 78 | 79 | bitcodin.job.create(jobConfiguration) 80 | .then( 81 | function (newlyCreatedJob) { 82 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 83 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 84 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 85 | }, 86 | function (error) { 87 | console.log('Error while creating a new transcoding job:', error); 88 | } 89 | ); 90 | }, 91 | function (error) { 92 | console.log('Error while creating input and/or encoding profile:', error); 93 | } 94 | ); 95 | -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLSwithWidevinePlayreadyDRMcombined.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"], 62 | "speed": "standard", 63 | "drmConfig": { 64 | "system": "widevine_playready", 65 | "kid": "eb676abbcb345e96bbcf616630f1a3da", 66 | "key": "100b6c20940f779a4589152b57d2dacb", 67 | "laUrl": "http://playready.directtaps.net/pr/svc/rightsmanager.asmx?PlayRight=1&ContentKey=EAtsIJQPd5pFiRUrV9Layw==", 68 | "method": "mpeg_cenc", 69 | "pssh": "#CAESEOtnarvLNF6Wu89hZjDxo9oaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAkhEMgA=" 70 | } 71 | }; 72 | 73 | Q.all([createInputPromise, createEncodingProfilePromise]) 74 | .then( 75 | function (result) { 76 | console.log('Successfully created input and encoding profile'); 77 | jobConfiguration.inputId = result[0].inputId; 78 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 79 | 80 | bitcodin.job.create(jobConfiguration) 81 | .then( 82 | function (newlyCreatedJob) { 83 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 84 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 85 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 86 | }, 87 | function (error) { 88 | console.log('Error while creating a new transcoding job:', error); 89 | } 90 | ); 91 | }, 92 | function (error) { 93 | console.log('Error while creating input and/or encoding profile:', error); 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLSwithWidevineDRM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"], 62 | "speed": "standard", 63 | "drmConfig": { 64 | system: "widevine", 65 | provider: "widevine_test", 66 | signingKey: "1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9", 67 | signingIV: "d58ce954203b7c9a9a9d467f59839249", 68 | requestUrl: "http://license.uat.widevine.com/cenc/getcontentkey", 69 | contentId: "746573745f69645f4639465043304e4f", 70 | method: "mpeg_cenc" 71 | } 72 | }; 73 | 74 | Q.all([createInputPromise, createEncodingProfilePromise]) 75 | .then( 76 | function (result) { 77 | console.log('Successfully created input and encoding profile'); 78 | jobConfiguration.inputId = result[0].inputId; 79 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 80 | 81 | bitcodin.job.create(jobConfiguration) 82 | .then( 83 | function (newlyCreatedJob) { 84 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 85 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 86 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 87 | }, 88 | function (error) { 89 | console.log('Error while creating a new transcoding job:', error); 90 | } 91 | ); 92 | }, 93 | function (error) { 94 | console.log('Error while creating input and/or encoding profile:', error); 95 | } 96 | ); 97 | -------------------------------------------------------------------------------- /examples/transcodeSintelToDASHAndHLSwithMultipleAudioStreams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 02.07.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('THIS_IS_MY_API_KEY'), 7 | openMovieUrl = 'http://bitbucketireland.s3.amazonaws.com/Sintel-two-audio-streams-short.mkv', 8 | createInputPromise, createEncodingProfilePromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile Multiple Audio Streams Test", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 192000 48 | }, 49 | { 50 | "defaultStreamId": 1, 51 | "bitrate": 192000 52 | } 53 | ] 54 | }; 55 | 56 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 57 | 58 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 59 | // how a job configuration object should look like can be found at the following link below 60 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 61 | 62 | var jobConfiguration = { 63 | "inputId": -1, 64 | "encodingProfileId": -1, 65 | "manifestTypes": ["mpd", "m3u8"], 66 | "speed": "standard", 67 | "audioMetaData": [ 68 | { 69 | "defaultStreamId": 0, 70 | "language": 'de', 71 | "label": "Just Sound" 72 | }, 73 | { 74 | "defaultStreamId": 1, 75 | "language": "en", 76 | "label": "Sound and Voice" 77 | } 78 | ] 79 | }; 80 | 81 | Q.all([createInputPromise, createEncodingProfilePromise]) 82 | .then( 83 | function (result) { 84 | console.log('Successfully created input and encoding profile'); 85 | jobConfiguration.inputId = result[0].inputId; 86 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 87 | 88 | bitcodin.job.create(jobConfiguration) 89 | .then( 90 | function (newlyCreatedJob) { 91 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 92 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 93 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 94 | }, 95 | function (error) { 96 | console.log('Error while creating a new transcoding job:', error); 97 | } 98 | ); 99 | }, 100 | function (error) { 101 | console.log('Error while creating input and/or encoding profile:', error); 102 | } 103 | ); 104 | -------------------------------------------------------------------------------- /examples/mergeAudioStreams.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | bitcodin = require('bitcodin')('YOUR API KEY HERE'), 3 | openMovieUrl = 'http://bitbucketireland.s3.amazonaws.com/Sintel-two-audio-streams-short.mkv', 4 | createInputPromise, createEncodingProfilePromise; 5 | 6 | var inputConfig = {}; 7 | inputConfig.type = 'url'; 8 | inputConfig.url = openMovieUrl; 9 | inputConfig.skipAnalysis = true; 10 | 11 | // Create bitcodin Input 12 | createInputPromise = bitcodin.input.create(inputConfig); 13 | 14 | /* 15 | * The encoding configuration for the resulting video 16 | */ 17 | var encodingProfileConfiguration = { 18 | "name": "bitcodin Encoding Profile Multiple Audio Streams Test", 19 | "videoStreamConfigs": [ 20 | { 21 | "defaultStreamId": 0, 22 | "bitrate": 4800000, 23 | "profile": "Main", 24 | "preset": "premium", 25 | "height": 1080, 26 | "width": 1920 27 | }, 28 | { 29 | "defaultStreamId": 0, 30 | "bitrate": 2400000, 31 | "profile": "Main", 32 | "preset": "premium", 33 | "height": 720, 34 | "width": 1280 35 | }, 36 | { 37 | "defaultStreamId": 0, 38 | "bitrate": 1200000, 39 | "profile": "Main", 40 | "preset": "premium", 41 | "height": 480, 42 | "width": 854 43 | } 44 | ], 45 | "audioStreamConfigs": [ 46 | { 47 | "defaultStreamId": 0, 48 | "bitrate": 192000 49 | } 50 | ] 51 | }; 52 | 53 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 54 | 55 | /* 56 | Merge channel 1 and 2 from input video to a new stereo channel in the resulting transcoded video 57 | Input video (http://bitbucketireland.s3.amazonaws.com/Sintel-two-audio-streams-short.mkv) channels: 58 | 0 -> Video stream 59 | 1 -> First audio stream 60 | 2 -> Second audio stream 61 | */ 62 | var mergeAudioChannelConfigs = [{"audioChannels": [1, 2]}]; 63 | 64 | /* 65 | * The job configuration, inputId and encodingProfiles are set when the the input and the encodingprofile promises are 66 | * successfully fullfilled so at this time they can be -1. 67 | */ 68 | var jobConfiguration = { 69 | "inputId": -1, 70 | "encodingProfileId": -1, 71 | "mergeAudioChannelConfigs": mergeAudioChannelConfigs, 72 | "manifestTypes": ["mpd", "m3u8"], 73 | "speed": "standard", 74 | "audioMetaData": [ 75 | { 76 | "defaultStreamId": 0, 77 | "language": 'de', 78 | "label": "New Stereo Stream generated from two mono audio streams" 79 | } 80 | ] 81 | }; 82 | console.log("Start creating input and encoding profile"); 83 | Q.all([createInputPromise, createEncodingProfilePromise]) 84 | .then( 85 | function (result) { 86 | console.log('Successfully created input and encoding profile'); 87 | jobConfiguration.inputId = result[0].inputId; 88 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 89 | 90 | bitcodin.job.create(jobConfiguration) 91 | .then( 92 | function (newlyCreatedJob) { 93 | console.log('Successfully created a new transcoding job:', newlyCreatedJob); 94 | console.log('MPD-Url:', newlyCreatedJob.manifestUrls.mpdUrl); 95 | console.log('M3U8-Url:', newlyCreatedJob.manifestUrls.m3u8Url); 96 | }, 97 | function (error) { 98 | console.log('Error while creating a new transcoding job:', error); 99 | } 100 | ); 101 | }, 102 | function (error) { 103 | console.log('Error while creating input and/or encoding profile:', error); 104 | } 105 | ); 106 | -------------------------------------------------------------------------------- /test/bitcodin.input.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Input', function () { 9 | var inputIds = []; 10 | 11 | it('should create an input from a URL string with skipAnalysis set', function () { 12 | this.timeout(30000); 13 | var input = {}; 14 | input.skipAnalysis = true; 15 | input.type = 'url'; 16 | input.url = 'http://bitbucketireland.s3.amazonaws.com/Sintel-original-short.mkv'; 17 | 18 | var promise = bitcodin.input.create(input); 19 | 20 | promise.then(function (data) { 21 | data.should.have.property('inputId'); 22 | inputIds.push(data.inputId); 23 | }).done(); 24 | return promise.should.eventually.be.fulfilled; 25 | }); 26 | 27 | it('should create an input from a http input config object', function () { 28 | this.timeout(30000); 29 | var input = { 30 | type: 'url', 31 | url: 'http://bitbucketireland.s3.amazonaws.com/Sintel-original-short.mkv' 32 | }; 33 | var promise = bitcodin.input.create(input); 34 | 35 | promise.then(function (data) { 36 | data.should.have.property('inputId'); 37 | inputIds.push(data.inputId); 38 | }).done(); 39 | 40 | return promise.should.eventually.be.fulfilled; 41 | }); 42 | 43 | it('should create an input asynchronously from a URL', function () { 44 | this.timeout(30000); 45 | var input = {}; 46 | input.skipAnalysis = true; 47 | input.type = 'url'; 48 | input.url = 'http://bitbucketireland.s3.amazonaws.com/Sintel-original-short.mkv'; 49 | 50 | var promise = bitcodin.input.createAsync(input); 51 | 52 | promise.then(function (data) { 53 | data.should.have.property('inputId'); 54 | inputIds.push(data.inputId); 55 | }).done(); 56 | 57 | return promise.should.eventually.be.fulfilled; 58 | }); 59 | 60 | it('should analyze an input', function () { 61 | this.timeout(60000); 62 | var promise = bitcodin.input.analyze(inputIds[0]); 63 | return promise.should.eventually.have.property('inputId', inputIds[0]); 64 | }); 65 | 66 | it('should not analyze input for a invalid input id', function () { 67 | return util.testAll([undefined, 'foo'], function(inputId) { 68 | return bitcodin.input.analyze(inputId).should.eventually.be.rejected; 69 | }); 70 | }); 71 | 72 | it('should get input details for a given input id', function () { 73 | var promise = bitcodin.input.get(inputIds[0]); 74 | return promise.should.eventually.have.property('inputId', inputIds[0]); 75 | }); 76 | 77 | it('should not get input details for a invalid input id', function () { 78 | return util.testAll([undefined, 'foo'], function(inputId) { 79 | return bitcodin.input.get(inputId).should.eventually.be.rejected; 80 | }); 81 | }); 82 | 83 | it('should list available inputs', function () { 84 | return bitcodin.input.list().should.eventually.be.fulfilled; 85 | }); 86 | 87 | it('should list inputs of a given page', function () { 88 | var page = 2; 89 | return bitcodin.input.list(page).should.eventually.be.fulfilled; 90 | }); 91 | 92 | it('should not delete input for a invalid input id', function () { 93 | return util.testAll([undefined, 'foo'], function(inputId) { 94 | return bitcodin.input.delete(inputId).should.eventually.be.rejected; 95 | }); 96 | }); 97 | 98 | it('should delete all created inputs', function () { 99 | return util.testAll(inputIds, function (inputId) { 100 | return bitcodin.input.delete(inputId).should.eventually.be.fulfilled; 101 | }); 102 | }); 103 | }); -------------------------------------------------------------------------------- /examples/createThumbnail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by lkroepfl on 24.05.16. 3 | */ 4 | 5 | var Q = require('q'), 6 | bitcodin = require('bitcodin')('INSERT YOUR API KEY'), 7 | openMovieUrl = 'http://eu-storage.bitcodin.com/inputs/Sintel.2010.720p.mkv', 8 | createInputPromise, createEncodingProfilePromise, createThumbnailPromise; 9 | 10 | // Create bitcodin Input 11 | createInputPromise = bitcodin.input.create(openMovieUrl); 12 | 13 | // Create bitcodin encoding profile. The ApiAry documentation which explains how such a 14 | // encoding profile should look like can be found at the link below 15 | // http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 16 | var encodingProfileConfiguration = { 17 | "name": "bitcodin Encoding Profile", 18 | "videoStreamConfigs": [ 19 | { 20 | "defaultStreamId": 0, 21 | "bitrate": 4800000, 22 | "profile": "Main", 23 | "preset": "premium", 24 | "height": 1080, 25 | "width": 1920 26 | }, 27 | { 28 | "defaultStreamId": 0, 29 | "bitrate": 2400000, 30 | "profile": "Main", 31 | "preset": "premium", 32 | "height": 720, 33 | "width": 1280 34 | }, 35 | { 36 | "defaultStreamId": 0, 37 | "bitrate": 1200000, 38 | "profile": "Main", 39 | "preset": "premium", 40 | "height": 480, 41 | "width": 854 42 | } 43 | ], 44 | "audioStreamConfigs": [ 45 | { 46 | "defaultStreamId": 0, 47 | "bitrate": 256000 48 | } 49 | ] 50 | }; 51 | 52 | createEncodingProfilePromise = bitcodin.encodingProfile.create(encodingProfileConfiguration); 53 | 54 | // Create a bitcodin job which transcodes the video to DASH and HLS. The ApiAry documentation which explains 55 | // how a job configuration object should look like can be found at the following link below 56 | // http://docs.bitcodinrestapi.apiary.io/#reference/jobs/job/create-a-job 57 | 58 | var jobConfiguration = { 59 | "inputId": -1, 60 | "encodingProfileId": -1, 61 | "manifestTypes": ["mpd", "m3u8"] 62 | }; 63 | 64 | Q.all([createInputPromise, createEncodingProfilePromise]) 65 | .then( 66 | function (result) { 67 | console.log('Successfully created input and encoding profile'); 68 | jobConfiguration.inputId = result[0].inputId; 69 | jobConfiguration.encodingProfileId = result[1].encodingProfileId; 70 | 71 | bitcodin.job.create(jobConfiguration) 72 | .then( 73 | function (newlyCreatedJob) { 74 | console.log('Successfully created a new transcoding job'); 75 | createThumbnailFromJobId(newlyCreatedJob.jobId); 76 | }, 77 | function () { 78 | console.log('Error while creating a new transcoding job'); 79 | } 80 | ); 81 | }, 82 | function () { 83 | console.log('Error while creating input and/or encoding profile'); 84 | } 85 | ); 86 | 87 | // Create a thumbnail at second 50 with a height of 320px from a given job 88 | // Note: You don't have to create a new job for a thumbnail, you can use finished jobs too. 89 | function createThumbnailFromJobId(jobId) { 90 | if(isNaN(jobId)) { 91 | console.log('JobId is not a number.'); 92 | return; 93 | } 94 | 95 | var thumbnailConfiguration = { 96 | "jobId": jobId, 97 | "height": 320, 98 | "position": 50, 99 | "async": true //synchronous thumbnail creation is deprecated 100 | }; 101 | 102 | createThumbnailPromise = bitcodin.thumbnail.create(thumbnailConfiguration); 103 | 104 | Q.all([createThumbnailPromise]) 105 | .then( 106 | function (result) { 107 | console.log('Successfully created a thumbnail', result); 108 | }, 109 | function (error) { 110 | console.log('Error while creating thumbnail:', error); 111 | } 112 | ); 113 | } -------------------------------------------------------------------------------- /examples/createLiveStream.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | bitcodin = require('bitcodin')('INSERT YOUR API KEY'), 3 | createEncodingProfilePromise, createOutputPromise; 4 | 5 | /* 6 | Create an encoding profile for your live stream. 7 | http://docs.bitcodinrestapi.apiary.io/#reference/encoding-profiles/create-an-encoding-profile 8 | */ 9 | var liveEncodingProfile = { 10 | "name": "live stream profile", 11 | "videoStreamConfigs": [ 12 | { 13 | /* 3 Mbit, 1920x1080, Full HD */ 14 | "defaultStreamId": 0, 15 | "bitrate": 3000000, 16 | "profile": "Main", 17 | "preset": "premium", 18 | "height": 1080, 19 | "width": 1920 20 | }, 21 | { 22 | /* 1,5 Mbit, 1280x720, 720p */ 23 | "defaultStreamId": 0, 24 | "bitrate": 1500000, 25 | "profile": "Main", 26 | "preset": "premium", 27 | "height": 720, 28 | "width": 1280 29 | }, 30 | { 31 | /* 1 Mbit, 640x480, 480p */ 32 | "defaultStreamId": 0, 33 | "bitrate": 1000000, 34 | "profile": "Main", 35 | "preset": "premium", 36 | "height": 480, 37 | "width": 640 38 | } 39 | ], 40 | "audioStreamConfigs": [ 41 | { 42 | "defaultStreamId": 0, 43 | "bitrate": 256000 44 | } 45 | ] 46 | }; 47 | 48 | createEncodingProfilePromise = bitcodin.encodingProfile.create(liveEncodingProfile); 49 | 50 | /* 51 | Create an output where the live stream files should be stored. 52 | https://jsapi.apiary.io/apis/bitcodinrestapi/reference/outputs/create-output/create-an-s3-output.html 53 | */ 54 | var date = new Date(); 55 | 56 | var prefix = "livestream" + date.getFullYear() + date.getMonth() + date.getDay() + date.getHours() + date.getMinutes(); 57 | 58 | var liveStreamOutput = { 59 | "type": "gcs", 60 | "name": "Test Output Name", 61 | "accessKey": "YOUR ACCESS KEY", 62 | "secretKey": "YOUR SECRET KEY", 63 | "bucket": "YOUR BUCKET NAME", 64 | "prefix": prefix, 65 | "makePublic": true 66 | }; 67 | 68 | createOutputPromise = bitcodin.output.gcs.create(liveStreamOutput); 69 | 70 | Q.all([createEncodingProfilePromise, createOutputPromise]) 71 | .then( 72 | function (result) { 73 | console.log('Successfully created output and encoding profile!'); 74 | console.log('Starting live stream...'); 75 | /* 76 | Create your live stream 77 | */ 78 | 79 | var liveStreamConfig = { 80 | "label": "test-livestream", 81 | "streamKey": "stream", 82 | "timeshift": 120, 83 | "encodingProfileId": result[0].encodingProfileId, 84 | "outputId": result[1].outputId 85 | }; 86 | 87 | return bitcodin.livestream.create(liveStreamConfig); 88 | }, 89 | function (error) { 90 | console.log('Error while creating output and/or encoding profile:', error); 91 | } 92 | ) 93 | .then( 94 | function(livestream) { 95 | console.log("Successfully created live stream!"); 96 | console.log(livestream); 97 | console.log("\n"); 98 | console.log("****************************************************************"); 99 | console.log("INFO: DONT FORGET TO DELETE LIVE STREAM WHEN FINISHED STREAMING!"); 100 | console.log("****************************************************************"); 101 | /* 102 | Delete live stream when finished streaming 103 | */ 104 | console.log("Deleting livestream..."); 105 | return bitcodin.livestream.delete(livestream.id) 106 | }, 107 | function(createError) 108 | { 109 | console.log("Error while creating live stream!", createError); 110 | } 111 | ) 112 | .then( 113 | function() { 114 | console.log("Successfully deleted live stream!"); 115 | }, 116 | function(error) { 117 | console.log("Error while deleting live stream!", error); 118 | } 119 | ); 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/bitcodin.job.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 23.06.15. 3 | */ 4 | 5 | var util = require('./util'), 6 | bitcodin = require('../lib/bitcodin')(util.settings.apiKey); 7 | 8 | describe('Job', function () { 9 | var jobIds = []; 10 | 11 | it('should create a new job', function () { 12 | var jobConfig = { 13 | 'inputId': 3315, 14 | 'encodingProfileId': 7365, 15 | 'manifestTypes': [ 16 | 'mpd', 17 | 'm3u8' 18 | ] 19 | }; 20 | 21 | var promise = bitcodin.job.create(jobConfig); 22 | 23 | promise.then(function (data) { 24 | jobIds.push(data.jobId); 25 | }); 26 | 27 | return promise.should.eventually.be.fulfilled; 28 | }); 29 | 30 | it('should create a new job with widevine drm configuration', function () { 31 | var jobConfig = { 32 | 'inputId': 3315, 33 | 'encodingProfileId': 7365, 34 | 'manifestTypes': [ 35 | 'mpd', 36 | 'm3u8' 37 | ], 38 | 'speed': 'standard', 39 | 'drmConfig': { 40 | system: 'widevine', 41 | provider: 'widevine_test', 42 | signingKey: '1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9', 43 | signingIV: 'd58ce954203b7c9a9a9d467f59839249', 44 | requestUrl: 'http://license.uat.widevine.com/cenc/getcontentkey', 45 | contentId: '746573745f69645f4639465043304e4f', 46 | method: 'mpeg_cenc' 47 | } 48 | }; 49 | 50 | var promise = bitcodin.job.create(jobConfig); 51 | 52 | promise.then(function (data) { 53 | jobIds.push(data.jobId); 54 | }); 55 | 56 | return promise.should.eventually.be.fulfilled; 57 | }); 58 | 59 | it('should create a new job with playready drm configuration', function () { 60 | var jobConfig = { 61 | 'inputId': 3315, 62 | 'encodingProfileId': 7365, 63 | 'manifestTypes': [ 64 | 'mpd', 65 | 'm3u8' 66 | ], 67 | 'speed': 'standard', 68 | 'drmConfig': { 69 | "system": "playready", 70 | "keySeed": "XVBovsmzhP9gRIZxWfFta3VVRPzVEWmJsazEJ46I", 71 | "laUrl": "http://playready.directtaps.net/pr/svc/rightsmanager.asmx", 72 | "method": "mpeg_cenc", 73 | "kid": "746573745f69645f4639465043304e4f" 74 | } 75 | }; 76 | 77 | var promise = bitcodin.job.create(jobConfig); 78 | 79 | promise.then(function (data) { 80 | jobIds.push(data.jobId); 81 | }); 82 | 83 | return promise.should.eventually.be.fulfilled; 84 | }); 85 | 86 | it('should create a new job with widevine and playready (combined) drm configuration', function () { 87 | var jobConfig = { 88 | 'inputId': 3315, 89 | 'encodingProfileId': 7365, 90 | 'manifestTypes': [ 91 | 'mpd', 92 | 'm3u8' 93 | ], 94 | 'speed': 'standard', 95 | 'drmConfig': { 96 | "system": "widevine_playready", 97 | "kid": "eb676abbcb345e96bbcf616630f1a3da", 98 | "key": "100b6c20940f779a4589152b57d2dacb", 99 | "laUrl": "http://playready.directtaps.net/pr/svc/rightsmanager.asmx?PlayRight=1&ContentKey=EAtsIJQPd5pFiRUrV9Layw==", 100 | "method": "mpeg_cenc", 101 | "pssh": "#CAESEOtnarvLNF6Wu89hZjDxo9oaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAkhEMgA=" 102 | } 103 | }; 104 | 105 | var promise = bitcodin.job.create(jobConfig); 106 | 107 | promise.then(function (data) { 108 | jobIds.push(data.jobId); 109 | }); 110 | 111 | return promise.should.eventually.be.fulfilled; 112 | }); 113 | 114 | it('should create a new job with hls encryption configuration', function () { 115 | var jobConfig = { 116 | 'inputId': 3315, 117 | 'encodingProfileId': 7365, 118 | 'manifestTypes': [ 119 | 'mpd', 120 | 'm3u8' 121 | ], 122 | 'speed': 'standard', 123 | 'hlsEncryptionConfig': { 124 | "method": "SAMPLE-AES", 125 | "key": "cab5b529ae28d5cc5e3e7bc3fd4a544d", 126 | "iv": "08eecef4b026deec395234d94218273d" 127 | } 128 | }; 129 | 130 | var promise = bitcodin.job.create(jobConfig); 131 | 132 | promise.then(function (data) { 133 | jobIds.push(data.jobId); 134 | }); 135 | 136 | return promise.should.eventually.be.fulfilled; 137 | }); 138 | 139 | it('should create a new job with an input which has multiple audio streams', function () { 140 | var jobConfig = { 141 | 'inputId': 13405, 142 | 'encodingProfileId': 16613, 143 | 'manifestTypes': [ 144 | 'mpd', 145 | 'm3u8' 146 | ], 147 | 'speed': 'standard', 148 | 'audioMetaData': [ 149 | { 150 | 'defaultStreamId': 0, 151 | 'language': 'de', 152 | 'label': 'Just Sound' 153 | }, 154 | { 155 | 'defaultStreamId': 1, 156 | 'language': 'en', 157 | 'label': 'Sound and Voice' 158 | } 159 | ] 160 | }; 161 | 162 | var promise = bitcodin.job.create(jobConfig); 163 | 164 | promise.then(function (data) { 165 | jobIds.push(data.jobId); 166 | }); 167 | 168 | return promise.should.eventually.be.fulfilled; 169 | }); 170 | 171 | it('should list jobs', function () { 172 | return bitcodin.job.list().should.eventually.be.fulfilled; 173 | }); 174 | 175 | it('should list jobs of a given page', function () { 176 | var page = 2; 177 | return bitcodin.job.list(page).should.eventually.be.fulfilled; 178 | }); 179 | 180 | it('should get the job details for a given job id', function () { 181 | return bitcodin.job.getDetails(2874).should.eventually.be.fulfilled; 182 | }); 183 | 184 | it('should not get job details for a invalid id', function () { 185 | return util.testAll([undefined, 'foo'], function(jobId) { 186 | return bitcodin.job.getDetails(jobId).should.eventually.be.rejected; 187 | }); 188 | }); 189 | 190 | it('should get the job status for a given job id', function () { 191 | return bitcodin.job.getStatus(2874).should.eventually.be.fulfilled; 192 | }); 193 | 194 | it('should not get job status for a invalid id', function () { 195 | return util.testAll([undefined, 'foo'], function(jobId) { 196 | return bitcodin.job.getStatus(jobId).should.eventually.be.rejected; 197 | }); 198 | }); 199 | 200 | it('should create a new transfer job', function () { 201 | var transferJobConfig = { 202 | 'jobId': 2874, 203 | 'outputId': 2564 204 | }; 205 | 206 | return bitcodin.job.transfer.create(transferJobConfig).should.eventually.be.fulfilled; 207 | }); 208 | 209 | it('should get the transfer job details for a given id', function () { 210 | return bitcodin.job.transfer.list(2874).should.eventually.be.fulfilled; 211 | }); 212 | 213 | it('should not list transfer job details for a invalid id', function () { 214 | return util.testAll([undefined, 'foo'], function(jobId) { 215 | return bitcodin.job.transfer.list(jobId).should.eventually.be.rejected; 216 | }); 217 | }); 218 | }); -------------------------------------------------------------------------------- /lib/bitcodin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mfillafer on 22.06.15. 3 | */ 4 | 5 | var Q = require('q'), 6 | rest = require('./rest'), 7 | API_VERSION = 'v1'; 8 | 9 | function Bitcodin(key, version) { 10 | 11 | if (!(this instanceof Bitcodin)) { 12 | return new Bitcodin(key, version); 13 | } 14 | 15 | if (!key || typeof key !== 'string') { 16 | throw new Error('No bitcodin API Key given'); 17 | } else { 18 | this.key = key; 19 | } 20 | 21 | this.version = API_VERSION; 22 | 23 | if (version) { 24 | if (!/v\d+/.test(version)) { 25 | throw new Error('Given bitcodin version is not valid'); 26 | } else { 27 | this.version = version; 28 | } 29 | } 30 | 31 | rest.addHeaders({ 32 | 'bitcodin-api-version': this.version, 33 | 'bitcodin-api-key': this.key 34 | }); 35 | } 36 | 37 | 38 | Bitcodin.prototype = { 39 | 40 | input: { 41 | create: function (inputConfig) { 42 | if (typeof inputConfig === 'string') { 43 | inputConfig = { 44 | type: 'url', 45 | url: inputConfig 46 | } 47 | } 48 | 49 | return rest.post('/input/create', inputConfig); 50 | }, 51 | 52 | createAsync: function(inputConfig) { 53 | var api = this; 54 | 55 | if (typeof inputConfig === 'string') { 56 | inputConfig = { 57 | type: 'url', 58 | url: inputConfig 59 | } 60 | } 61 | 62 | return rest.post('/input/createasync', inputConfig).then( 63 | function(response) { 64 | return api.getAsync(response.inputId); 65 | } 66 | ); 67 | }, 68 | 69 | analyze: function (inputId) { 70 | if (isNaN(inputId)) { 71 | var def = Q.defer(); 72 | def.reject('inputId is no number'); 73 | return def.promise; 74 | } 75 | 76 | return rest.patch('/input/' + inputId + '/analyze'); 77 | }, 78 | 79 | list: function (pageNumber) { 80 | if (pageNumber !== undefined && !isNaN(pageNumber)) { 81 | pageNumber = '/' + pageNumber 82 | } else { 83 | pageNumber = '' 84 | } 85 | 86 | return rest.get('/inputs' + pageNumber); 87 | }, 88 | 89 | get: function (inputId) { 90 | if (isNaN(inputId)) { 91 | var def = Q.defer(); 92 | def.reject('inputId is no number'); 93 | return def.promise; 94 | } 95 | 96 | return rest.get('/input/' + inputId); 97 | }, 98 | 99 | getAsync: function (inputId) { 100 | var def = Q.defer(); 101 | 102 | if (isNaN(inputId)) { 103 | def.reject('inputId is no number'); 104 | return def.promise; 105 | } 106 | 107 | (function poll() { 108 | rest.get('/input/' + inputId + '/asyncstatus').then( 109 | function (response) { 110 | switch(response.status) { 111 | case 'CREATED': 112 | def.resolve(response); 113 | break; 114 | case 'ERROR': 115 | def.reject("Error while creating input!"); 116 | break; 117 | default: 118 | setTimeout(poll, 5000); 119 | } 120 | }, 121 | def.reject 122 | ); 123 | })(); 124 | 125 | return def.promise; 126 | }, 127 | 128 | delete: function (inputId) { 129 | if (isNaN(inputId)) { 130 | var def = Q.defer(); 131 | def.reject('inputId is no number'); 132 | return def.promise; 133 | } 134 | 135 | return rest.delete('/input/' + inputId); 136 | } 137 | }, 138 | 139 | output: { 140 | s3: { 141 | create: function (s3OutputConfig) { 142 | return rest.post('/output/create', s3OutputConfig); 143 | } 144 | }, 145 | 146 | gcs: { 147 | create: function (gcsOutputConfig) { 148 | return rest.post('/output/create', gcsOutputConfig); 149 | } 150 | }, 151 | 152 | ftp: { 153 | create: function (ftpOutputConfig) { 154 | return rest.post('/output/create', ftpOutputConfig); 155 | } 156 | }, 157 | 158 | list: function (pageNumber) { 159 | if (pageNumber !== undefined && !isNaN(pageNumber)) { 160 | pageNumber = '/' + pageNumber 161 | } else { 162 | pageNumber = '' 163 | } 164 | 165 | return rest.get('/outputs' + pageNumber); 166 | }, 167 | 168 | getDetails: function (outputId) { 169 | if (isNaN(outputId)) { 170 | var def = Q.defer(); 171 | def.reject('outputId is no number'); 172 | return def.promise; 173 | } 174 | 175 | return rest.get('/output/' + outputId); 176 | }, 177 | 178 | delete: function (outputId) { 179 | if (isNaN(outputId)) { 180 | var def = Q.defer(); 181 | def.reject('outputId is no number'); 182 | return def.promise; 183 | } 184 | 185 | return rest.delete('/output/' + outputId); 186 | } 187 | }, 188 | 189 | encodingProfile: { 190 | create: function (encodingProfile) { 191 | return rest.post('/encoding-profile/create', encodingProfile); 192 | }, 193 | 194 | list: function (pageNumber) { 195 | if (pageNumber !== undefined && !isNaN(pageNumber)) { 196 | pageNumber = '/' + pageNumber 197 | } else { 198 | pageNumber = '' 199 | } 200 | 201 | return rest.get('/encoding-profiles' + pageNumber); 202 | }, 203 | 204 | get: function (encodingProfileId) { 205 | if (isNaN(encodingProfileId)) { 206 | var def = Q.defer(); 207 | def.reject('encodingProfileId is no number'); 208 | return def.promise; 209 | } 210 | 211 | return rest.get('/encoding-profile/' + encodingProfileId); 212 | }, 213 | 214 | delete: function (encodingProfileId) { 215 | if (isNaN(encodingProfileId)) { 216 | var def = Q.defer(); 217 | def.reject('encodingProfileId is no number'); 218 | return def.promise; 219 | } 220 | 221 | return rest.delete('/encoding-profile/' + encodingProfileId); 222 | } 223 | }, 224 | 225 | job: { 226 | create: function (job) { 227 | return rest.post('/job/create', job); 228 | }, 229 | 230 | list: function (pageNumber, status) { 231 | var params = ''; 232 | if (pageNumber !== undefined && !isNaN(pageNumber)) { 233 | params += '/' + pageNumber; 234 | } 235 | if(status !== undefined && ['all', 'finished', 'enqueued', 'inprogress', 'error'].indexOf(status) != -1) { 236 | params += '/' + status; 237 | } 238 | 239 | return rest.get('/jobs' + params); 240 | }, 241 | 242 | getDetails: function (jobId) { 243 | if (isNaN(jobId)) { 244 | var def = Q.defer(); 245 | def.reject('jobId is no number'); 246 | return def.promise; 247 | } 248 | 249 | return rest.get('/job/' + jobId); 250 | }, 251 | 252 | getStatus: function (jobId) { 253 | if (isNaN(jobId)) { 254 | var def = Q.defer(); 255 | def.reject('jobId is no number'); 256 | return def.promise; 257 | } 258 | 259 | return rest.get('/job/' + jobId + '/status'); 260 | }, 261 | 262 | transfer: { 263 | create: function (transferJob) { 264 | return rest.post('/job/transfer', transferJob); 265 | }, 266 | 267 | list: function (jobId) { 268 | if (isNaN(jobId)) { 269 | var def = Q.defer(); 270 | def.reject('jobId is no number'); 271 | return def.promise; 272 | } 273 | 274 | return rest.get('/job/' + jobId + '/transfers'); 275 | } 276 | }, 277 | delete: function (jobId){ 278 | if (isNaN(jobId)) { 279 | var def = Q.defer(); 280 | def.reject('jobId is no number!'); 281 | return def.promise; 282 | } 283 | return rest.delete('/job/' + jobId); 284 | }, 285 | getManifestInfo: function(jobId){ 286 | if (isNaN(jobId)) { 287 | var def = Q.defer(); 288 | def.reject('jobId is no number!'); 289 | return def.promise; 290 | } 291 | return rest.get('/job/' + jobId + '/manifest-info'); 292 | } 293 | }, 294 | 295 | livestream: { 296 | get: function (livestreamId) { 297 | if (isNaN(livestreamId)) { 298 | var def = Q.defer(); 299 | def.reject('livestreamId is no number'); 300 | return def.promise; 301 | } 302 | return rest.get('/livestream/' + livestreamId); 303 | }, 304 | create: function (livestream) { 305 | var def = Q.defer(); 306 | 307 | rest.post('/livestream', livestream).then( 308 | function(response){ 309 | var poll = function() { 310 | setTimeout(function () { 311 | rest.get('/livestream/' + response.id).then( 312 | function (response) { 313 | if (response.status == 'RUNNING') { 314 | def.resolve(response); 315 | } 316 | else if (response.status == 'ERROR') { 317 | def.reject("Error while creating live stream!"); 318 | } 319 | else 320 | { 321 | poll(); 322 | } 323 | }, 324 | function (error) { 325 | def.reject(error); 326 | } 327 | ) 328 | }, 5000); 329 | }; 330 | poll(); 331 | }, 332 | function(error){ 333 | def.reject(error); 334 | } 335 | ); 336 | 337 | return def.promise; 338 | }, 339 | delete: function (livestreamId) { 340 | if (isNaN(livestreamId)) { 341 | var def = Q.defer(); 342 | def.reject('livestreamId is no number!'); 343 | return def.promise; 344 | } 345 | return rest.delete('/livestream/' + livestreamId); 346 | } 347 | }, 348 | /*statistic: { 349 | 350 | get: function () { 351 | return rest.get('/statistics'); 352 | }, 353 | 354 | jobs: function (fromDate, toDate) { 355 | var now = new Date().toISOString().substr(0, 10); 356 | var def = Q.defer(); 357 | 358 | if (fromDate === undefined) { 359 | fromDate = now; 360 | } else if (isNaN(Date.parse(fromDate))) { 361 | def.reject('fromDate is not a date'); 362 | return def.promise; 363 | } 364 | 365 | if (toDate === undefined) { 366 | toDate = now; 367 | } else if (isNaN(Date.parse(toDate))) { 368 | def.reject('toDate is not a date'); 369 | return def.promise; 370 | } 371 | 372 | return rest.get('/statistics/jobs/' + fromDate + '/' + toDate); 373 | } 374 | 375 | },*/ 376 | manifest: { 377 | mpd: { 378 | vtt: function (jobId, subtitles) { 379 | if (isNaN(jobId)) { 380 | var def = Q.defer(); 381 | def.reject('jobId is no number'); 382 | return def.promise; 383 | } 384 | if (!subtitles){ 385 | var def = Q.defer(); 386 | def.reject('subtitles need to be a url'); 387 | return def.promise; 388 | } 389 | var payload = { jobId: jobId, subtitles: subtitles } 390 | return rest.post('/manifest/mpd/vtt', payload); 391 | } 392 | }, 393 | hls: { 394 | vtt: function (jobId, subtitles) { 395 | if (isNaN(jobId)) { 396 | var def = Q.defer(); 397 | def.reject('jobId is no number'); 398 | return def.promise; 399 | } 400 | if (!subtitles){ 401 | var def = Q.defer(); 402 | def.reject('you need to provide subtitles array'); 403 | return def.promise; 404 | } 405 | var payload = { jobId: jobId, subtitles: subtitles } 406 | return rest.post('/manifest/hls/vtt', payload); 407 | } 408 | } 409 | }, 410 | 411 | thumbnail : { 412 | get: function(thumbnailId) { 413 | return rest.get('/thumbnail/' + thumbnailId); 414 | }, 415 | create: function(thumbnail) { 416 | var def = Q.defer(); 417 | 418 | rest.post('/thumbnail', thumbnail).then( 419 | function(response) { 420 | (function poll() { 421 | rest.get('/thumbnail/' + response.id).then( 422 | function (response) { 423 | switch(response.state) { 424 | case 'FINISHED': 425 | def.resolve(response); 426 | break; 427 | case 'ERROR': 428 | def.reject("Error while creating thumbnail!"); 429 | break; 430 | default: 431 | setTimeout(poll, 5000); 432 | } 433 | }, 434 | def.reject 435 | ); 436 | })(); 437 | }, 438 | def.reject 439 | ); 440 | 441 | return def.promise; 442 | } 443 | }, 444 | 445 | sprite : { 446 | get: function(spriteId) { 447 | return rest.get('/sprite/' + spriteId); 448 | }, 449 | create: function(sprite) { 450 | var def = Q.defer(); 451 | 452 | rest.post('/sprite', sprite).then( 453 | function(response) { 454 | (function poll() { 455 | rest.get('/sprite/' + response.id).then( 456 | function (response) { 457 | switch(response.state) { 458 | case 'FINISHED': 459 | def.resolve(response); 460 | break; 461 | case 'ERROR': 462 | def.reject("Error while creating sprite!"); 463 | break; 464 | default: 465 | setTimeout(poll, 5000); 466 | } 467 | }, 468 | def.reject 469 | ); 470 | })(); 471 | }, 472 | def.reject 473 | ); 474 | 475 | return def.promise; 476 | } 477 | }, 478 | 479 | transmux : { 480 | get: function(transmuxId) { 481 | return rest.get('/transmux/' + transmuxId); 482 | }, 483 | create: function(transmux) { 484 | var def = Q.defer(); 485 | 486 | rest.post('/transmux', transmux).then( 487 | function(response) { 488 | (function poll() { 489 | rest.get('/transmux/' + response.id).then( 490 | function (response) { 491 | switch(response.status) { 492 | case 'created': 493 | def.resolve(response); 494 | break; 495 | case 'assigned': 496 | def.resolve(response); 497 | break; 498 | case 'enqueued': 499 | def.resolve(response); 500 | break; 501 | case 'inprogress': 502 | def.resolve(response); 503 | break; 504 | case 'finished': 505 | def.resolve(response); 506 | break; 507 | case 'error': 508 | def.reject("Error while creating transmux!"); 509 | break; 510 | default: 511 | def.reject("Unexpected Error while creating transmux!"); 512 | break; 513 | } 514 | }, 515 | def.reject 516 | ); 517 | })(); 518 | }, 519 | def.reject 520 | ); 521 | 522 | return def.promise; 523 | } 524 | } 525 | }; 526 | 527 | module.exports = Bitcodin; 528 | --------------------------------------------------------------------------------