├── index.js ├── lib ├── video-url-link.js ├── util.js ├── twitter │ └── index.js └── youtube │ ├── index.js │ └── formats.js ├── test ├── twitter.js └── youtube.js ├── package.json ├── LICENSE ├── .gitignore └── README.md /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/video-url-link'); -------------------------------------------------------------------------------- /lib/video-url-link.js: -------------------------------------------------------------------------------- 1 | exports.youtube = require('./youtube'); 2 | exports.twitter = require('./twitter'); -------------------------------------------------------------------------------- /test/twitter.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const videoUrlLink = require('..'); 3 | 4 | describe('videoUrlLink.twitter', () => { 5 | const urls = [ 6 | 'https://twitter.com/taylorswift13/status/1121935013484867585', 7 | 'https://twitter.com/blakelively/status/1045768952872398848' 8 | ] 9 | urls.forEach(function (url) { 10 | it('twitter.getInfo ' + url, (done) => { 11 | videoUrlLink.twitter.getInfo(url, { }, (error, info) => { 12 | console.log(info); 13 | done(error); 14 | }); 15 | }); 16 | }); 17 | }); -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | exports.getReqOpt = (options) => { 4 | let defaultOptions = { 5 | gzip: true, 6 | method: 'GET', 7 | timeout: 5000, 8 | headers: { 9 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 10 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' 11 | }, 12 | jar: true 13 | }; 14 | return _.extend(defaultOptions, options); 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "video-url-link", 3 | "version": "0.1.5", 4 | "description": "Get Video Download URL, Catch Video From Any Site.", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "mocha --timeout 20000" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/catcto/video-url-link.git" 12 | }, 13 | "keywords": [ 14 | "video downloader", 15 | "youtube", 16 | "facebook", 17 | "twitter" 18 | ], 19 | "author": "catcto (https://github.com/catcto)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/catcto/video-url-link/issues" 23 | }, 24 | "homepage": "https://github.com/catcto/video-url-link#readme", 25 | "devDependencies": { 26 | "mocha": "^10.2.0" 27 | }, 28 | "dependencies": { 29 | "cheerio": "^1.0.0-rc.12", 30 | "lodash": "^4.17.21", 31 | "request": "^2.88.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alan Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /test/youtube.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const videoUrlLink = require('..'); 3 | 4 | describe('videoUrlLink.youtube', () => { 5 | const urls = [ 6 | 'https://www.youtube.com/watch?v=ftlvreFtA2A', 7 | 'https://m.youtube.com/watch?v=ftlvreFtA2A', 8 | 'https://youtu.be/ftlvreFtA2A', 9 | 'https://www.youtube.com/v/ftlvreFtA2A', 10 | 'https://www.youtube.com/embed/ftlvreFtA2A', 11 | 'https://music.youtube.com/watch?v=ftlvreFtA2A', 12 | 'https://gaming.youtube.com/watch?v=ftlvreFtA2A', 13 | ] 14 | 15 | urls.forEach((url) => { 16 | it('youtube.getID ' + url, () => { 17 | let id = videoUrlLink.youtube.getID(url); 18 | assert.equal(id, 'ftlvreFtA2A'); 19 | }); 20 | }) 21 | 22 | it('youtube.getInfo', (done) => { 23 | videoUrlLink.youtube.getInfo('https://www.youtube.com/watch?v=x7OCFcgf504', { hl: 'en', timeout: 15000, proxy: 'http://127.0.0.1:18880'}, (error, info) => { 24 | if (error) { 25 | done(error); 26 | } else { 27 | console.log(JSON.stringify(info, null, 4)); 28 | done(); 29 | } 30 | }); 31 | }); 32 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # video-url-link 2 | 3 | This module is used to get the video download url link from the website. 4 | 5 | Module in pure javascript for node.js 6 | 7 | Supported Sites: YouTube, Twitter 8 | 9 | ## Installation 10 | 11 | This is a [Node.js](https://nodejs.org/en/) module available through the 12 | [npm registry](https://www.npmjs.com/). 13 | 14 | Before installing, [download and install Node.js](https://nodejs.org/en/download/). 15 | Node.js 8.0 or higher is required. 16 | 17 | Installation is done using the 18 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 19 | 20 | ```bash 21 | $ npm install video-url-link 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```js 27 | const videoUrlLink = require('video-url-link'); 28 | ``` 29 | 30 | ### Get YouTube Info 31 | 32 | videoUrlLink.youtube.getInfo(url, [options], callback(error, info)) 33 | 34 | ```js 35 | videoUrlLink.youtube.getInfo('https://youtu.be/{ID}', { hl: 'en' }, (error, info) => { 36 | if (error) { 37 | console.error(error); 38 | } else { 39 | console.log(info.details); 40 | console.log(info.formats); 41 | } 42 | }); 43 | ``` 44 | 45 | ### Get Twitter Info 46 | 47 | videoUrlLink.twitter.getInfo(url, [options], callback(error, info)) 48 | 49 | ```js 50 | videoUrlLink.twitter.getInfo('https://twitter.com/{@}/status/{ID}', {}, (error, info) => { 51 | if (error) { 52 | console.error(error); 53 | } else { 54 | console.log(info.full_text); 55 | console.log(info.variants); 56 | } 57 | }); 58 | ``` 59 | 60 | ## Supported Sites 61 | 62 | | Site | URL | Video? | Details? | 63 | | :--- | :--- | :--- | :--- | 64 | | YouTube | | ✓ | ✓ | 65 | | Twitter | | ✓ | ✓ | 66 | 67 | ## Tests 68 | 69 | Tests are written with [mocha](https://mochajs.org) 70 | 71 | ```bash 72 | npm test 73 | ``` -------------------------------------------------------------------------------- /lib/twitter/index.js: -------------------------------------------------------------------------------- 1 | const Util = require('util'); 2 | const request = require('request').defaults({ jar: true }); 3 | var _ = require('lodash'); 4 | const AUTHORIZATION = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA'; 5 | const API_GUEST = 'https://api.twitter.com/1.1/guest/activate.json'; 6 | const API_TIMELINE = 'https://api.twitter.com/2/timeline/conversation/%s.json?tweet_mode=extended' 7 | 8 | /** 9 | * Get twitter ID 10 | * 11 | * @param {string} url 12 | * @return {string} 13 | */ 14 | exports.getID = (url) => { 15 | var regex = /twitter\.com\/[^/]+\/status\/(\d+)/; 16 | var matches = regex.exec(url); 17 | return matches && matches[1]; 18 | }; 19 | 20 | /** 21 | * Get twitter Info 22 | * 23 | * @param {string} url 24 | * @param {Object} options 25 | * @param {Function(Error, Object)} callback 26 | */ 27 | exports.getInfo = (url, options, callback) => { 28 | if (typeof options === 'function') callback = options, options = {}; 29 | const id = exports.getID(url); 30 | if (id) { 31 | req({ 32 | url: API_GUEST, 33 | method: 'POST', 34 | }, options, (error, body) => { 35 | if (error) { 36 | callback(error); 37 | } else { 38 | let guest_token; 39 | try { 40 | guest_token = JSON.parse(body).guest_token; 41 | } catch (errInfo) { 42 | return callback(new Error(errInfo)); 43 | } 44 | req({ 45 | url: Util.format(API_TIMELINE, id), 46 | method: 'GET', 47 | headers: { 48 | 'x-guest-token': guest_token 49 | } 50 | }, options, (error, body) => { 51 | if (error) { 52 | callback(error); 53 | } else { 54 | try { 55 | const info = JSON.parse(body); 56 | callback(null, { 57 | full_text: info['globalObjects']['tweets'][id]['full_text'], 58 | variants: info['globalObjects']['tweets'][id]['extended_entities']['media'][0]['video_info']['variants'] 59 | }); 60 | } catch (errInfo) { 61 | return callback(new Error(errInfo)); 62 | } 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | callback(new Error('Not a twitter URL')); 69 | } 70 | } 71 | 72 | function req(opt, options, callback) { 73 | opt = _.defaultsDeep({ 74 | headers: { 75 | 'authorization': AUTHORIZATION 76 | } 77 | }, opt, options); 78 | request(opt, (error, response, body) => { 79 | if (error) { 80 | callback(error); 81 | } else { 82 | if (response.statusCode == 200 && body) { 83 | callback(null, body); 84 | } else { 85 | callback(new Error('twitter API error')); 86 | } 87 | } 88 | }); 89 | } -------------------------------------------------------------------------------- /lib/youtube/index.js: -------------------------------------------------------------------------------- 1 | const querystring = require('querystring'); 2 | const Url = require('url'); 3 | const request = require('request'); 4 | const util = require('../util'); 5 | const FORMATS = require('./formats'); 6 | const VIDEO_URL = 'https://www.youtube.com/watch?v='; 7 | const EMBED_URL = 'https://www.youtube.com/embed/'; 8 | const VIDEO_EURL = 'https://youtube.googleapis.com/v/'; 9 | const INFO_PROTOCOL = 'https'; 10 | const INFO_HOST = 'www.youtube.com'; 11 | const INFO_PATH = '/get_video_info'; 12 | const KEYS_TO_SPLIT = [ 13 | 'fmt_list', 14 | 'fexp', 15 | 'watermark' 16 | ]; 17 | 18 | /** 19 | * Validate youtube ID 20 | * 21 | * @param {string} id 22 | * @return {boolean} 23 | */ 24 | const idRegex = /^[a-zA-Z0-9-_]{11}$/; 25 | exports.validateID = (id) => { 26 | return idRegex.test(id); 27 | }; 28 | 29 | /** 30 | * Get youtube ID 31 | * 32 | * URL formats 33 | * - https://www.youtube.com/watch?v={ID} 34 | * - https://m.youtube.com/watch?v={ID} 35 | * - https://youtu.be/{ID} 36 | * - https://www.youtube.com/v/{ID} 37 | * - https://www.youtube.com/embed/{ID} 38 | * - https://music.youtube.com/watch?v={ID} 39 | * - https://gaming.youtube.com/watch?v={ID} 40 | * 41 | * @param {string} url 42 | * @return {string} 43 | */ 44 | const queryHost = new Set([ 45 | 'youtube.com', 46 | 'www.youtube.com', 47 | 'm.youtube.com', 48 | 'music.youtube.com', 49 | 'gaming.youtube.com', 50 | ]); 51 | const pathHost = new Set([ 52 | 'youtu.be', 53 | 'youtube.com', 54 | 'www.youtube.com', 55 | ]); 56 | exports.getID = (url) => { 57 | const urlObj = Url.parse(url, true); 58 | let id = null; 59 | if (urlObj.query.v && queryHost.has(urlObj.hostname)) { 60 | id = urlObj.query.v; 61 | } else if (pathHost.has(urlObj.hostname)) { 62 | try { 63 | const paths = urlObj.pathname.split('/'); 64 | id = paths[paths.length - 1]; 65 | } catch (error) { 66 | console.error(error); 67 | } 68 | } 69 | if (!exports.validateID(id)) { 70 | console.error('Not a youtube URL'); 71 | } 72 | return id; 73 | }; 74 | 75 | /** 76 | * @param {Object} info 77 | * @return {Array.} 78 | */ 79 | exports.parseFormats = (info) => { 80 | let formats = []; 81 | if (info.url_encoded_fmt_stream_map) { 82 | formats = formats.concat(info.url_encoded_fmt_stream_map.split(',')); 83 | formats = formats.map((format) => querystring.parse(format)); 84 | } 85 | if (info.adaptive_fmts) { 86 | formats = formats.concat(info.adaptive_fmts.split(',')); 87 | formats = formats.map((format) => querystring.parse(format)); 88 | } 89 | if (info.player_response.streamingData && info.player_response.streamingData.adaptiveFormats) { 90 | formats = formats.concat(info.player_response.streamingData.adaptiveFormats); 91 | } 92 | formats.forEach((format) => { 93 | const meta = FORMATS[format.itag]; 94 | for (let key in meta) { 95 | format[key] = meta[key]; 96 | } 97 | if (/\/live\/1\//.test(format.url)) { 98 | format.live = true; 99 | } 100 | }); 101 | return formats; 102 | }; 103 | 104 | /** 105 | * Get youtube Info 106 | * 107 | * @param {string} url 108 | * @param {Object} options 109 | * @param {Function(Error, Object)} callback 110 | */ 111 | exports.getInfo = (url, options, callback) => { 112 | if(typeof options === 'function') callback = options, options = {}; 113 | const id = exports.getID(url); 114 | if (id) { 115 | options = util.getReqOpt(options); 116 | const infoUrl = Url.format({ 117 | protocol: INFO_PROTOCOL, 118 | host: INFO_HOST, 119 | pathname: INFO_PATH, 120 | query: { 121 | video_id: id, 122 | eurl: VIDEO_EURL + id, 123 | ps: 'default', 124 | gl: (options.gl || 'US'), 125 | hl: (options.hl || 'en') 126 | } 127 | }); 128 | console.log(infoUrl); 129 | options.url = infoUrl; 130 | request(options, (error, response, body) => { 131 | if (error) { 132 | callback(error); 133 | } else { 134 | if (response.statusCode == 200 && body) { 135 | let info = querystring.parse(body); 136 | const errInfo = 'Some error in the video format'; 137 | if (info.status === 'fail') { 138 | callback(Error(errInfo)); 139 | } else { 140 | if (info.player_response) { 141 | try { 142 | info.player_response = JSON.parse(info.player_response); 143 | } catch (err) { 144 | return callback(new Error(errInfo)); 145 | } 146 | let playability = info.player_response.playabilityStatus; 147 | if (playability && playability.status === 'UNPLAYABLE') { 148 | return callback(new Error(errInfo)); 149 | } 150 | KEYS_TO_SPLIT.forEach((key) => { 151 | if (!info[key]) return; 152 | info[key] = info[key] 153 | .split(',') 154 | .filter((v) => v !== ''); 155 | }); 156 | var videoInfo = {}; 157 | videoInfo.fmt_list = info.fmt_list ? info.fmt_list.map((format) => format.split('/')) : []; 158 | videoInfo.formats = exports.parseFormats(info); 159 | console.log(JSON.stringify(videoInfo.formats,null,4)); 160 | videoInfo.details = info.player_response.videoDetails; 161 | callback(null, videoInfo); 162 | } else { 163 | callback(new Error('Not Found youtube')); 164 | } 165 | } 166 | } else { 167 | callback(new Error('Not Found youtube')); 168 | } 169 | } 170 | }); 171 | } else { 172 | callback(new Error('Not a youtube URL')); 173 | } 174 | } -------------------------------------------------------------------------------- /lib/youtube/formats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://en.wikipedia.org/wiki/YouTube#Quality_and_formats 3 | */ 4 | module.exports = { 5 | '5': { 6 | container: 'flv', 7 | resolution: '240p', 8 | encoding: 'Sorenson H.283', 9 | profile: null, 10 | bitrate: '0.25', 11 | audioEncoding: 'mp3', 12 | audioBitrate: 64, 13 | }, 14 | '6': { 15 | container: 'flv', 16 | resolution: '270p', 17 | encoding: 'Sorenson H.263', 18 | profile: null, 19 | bitrate: '0.8', 20 | audioEncoding: 'mp3', 21 | audioBitrate: 64, 22 | }, 23 | '13': { 24 | container: '3gp', 25 | resolution: null, 26 | encoding: 'MPEG-4 Visual', 27 | profile: null, 28 | bitrate: '0.5', 29 | audioEncoding: 'aac', 30 | audioBitrate: null, 31 | }, 32 | '17': { 33 | container: '3gp', 34 | resolution: '144p', 35 | encoding: 'MPEG-4 Visual', 36 | profile: 'simple', 37 | bitrate: '0.05', 38 | audioEncoding: 'aac', 39 | audioBitrate: 24, 40 | }, 41 | '18': { 42 | container: 'mp4', 43 | resolution: '360p', 44 | encoding: 'H.264', 45 | profile: 'baseline', 46 | bitrate: '0.5', 47 | audioEncoding: 'aac', 48 | audioBitrate: 96, 49 | }, 50 | '22': { 51 | container: 'mp4', 52 | resolution: '720p', 53 | encoding: 'H.264', 54 | profile: 'high', 55 | bitrate: '2-3', 56 | audioEncoding: 'aac', 57 | audioBitrate: 192, 58 | }, 59 | '34': { 60 | container: 'flv', 61 | resolution: '360p', 62 | encoding: 'H.264', 63 | profile: 'main', 64 | bitrate: '0.5', 65 | audioEncoding: 'aac', 66 | audioBitrate: 128, 67 | }, 68 | '35': { 69 | container: 'flv', 70 | resolution: '480p', 71 | encoding: 'H.264', 72 | profile: 'main', 73 | bitrate: '0.8-1', 74 | audioEncoding: 'aac', 75 | audioBitrate: 128, 76 | }, 77 | '36': { 78 | container: '3gp', 79 | resolution: '240p', 80 | encoding: 'MPEG-4 Visual', 81 | profile: 'simple', 82 | bitrate: '0.175', 83 | audioEncoding: 'aac', 84 | audioBitrate: 32, 85 | }, 86 | '37': { 87 | container: 'mp4', 88 | resolution: '1080p', 89 | encoding: 'H.264', 90 | profile: 'high', 91 | bitrate: '3-5.9', 92 | audioEncoding: 'aac', 93 | audioBitrate: 192, 94 | }, 95 | '38': { 96 | container: 'mp4', 97 | resolution: '3072p', 98 | encoding: 'H.264', 99 | profile: 'high', 100 | bitrate: '3.5-5', 101 | audioEncoding: 'aac', 102 | audioBitrate: 192, 103 | }, 104 | '43': { 105 | container: 'webm', 106 | resolution: '360p', 107 | encoding: 'VP8', 108 | profile: null, 109 | bitrate: '0.5-0.75', 110 | audioEncoding: 'vorbis', 111 | audioBitrate: 128, 112 | }, 113 | '44': { 114 | container: 'webm', 115 | resolution: '480p', 116 | encoding: 'VP8', 117 | profile: null, 118 | bitrate: '1', 119 | audioEncoding: 'vorbis', 120 | audioBitrate: 128, 121 | }, 122 | '45': { 123 | container: 'webm', 124 | resolution: '720p', 125 | encoding: 'VP8', 126 | profile: null, 127 | bitrate: '2', 128 | audioEncoding: 'vorbis', 129 | audioBitrate: 192, 130 | }, 131 | '46': { 132 | container: 'webm', 133 | resolution: '1080p', 134 | encoding: 'vp8', 135 | profile: null, 136 | bitrate: null, 137 | audioEncoding: 'vorbis', 138 | audioBitrate: 192, 139 | }, 140 | '82': { 141 | container: 'mp4', 142 | resolution: '360p', 143 | encoding: 'H.264', 144 | profile: '3d', 145 | bitrate: '0.5', 146 | audioEncoding: 'aac', 147 | audioBitrate: 96, 148 | }, 149 | '83': { 150 | container: 'mp4', 151 | resolution: '240p', 152 | encoding: 'H.264', 153 | profile: '3d', 154 | bitrate: '0.5', 155 | audioEncoding: 'aac', 156 | audioBitrate: 96, 157 | }, 158 | '84': { 159 | container: 'mp4', 160 | resolution: '720p', 161 | encoding: 'H.264', 162 | profile: '3d', 163 | bitrate: '2-3', 164 | audioEncoding: 'aac', 165 | audioBitrate: 192, 166 | }, 167 | '85': { 168 | container: 'mp4', 169 | resolution: '1080p', 170 | encoding: 'H.264', 171 | profile: '3d', 172 | bitrate: '3-4', 173 | audioEncoding: 'aac', 174 | audioBitrate: 192, 175 | }, 176 | '100': { 177 | container: 'webm', 178 | resolution: '360p', 179 | encoding: 'VP8', 180 | profile: '3d', 181 | bitrate: null, 182 | audioEncoding: 'vorbis', 183 | audioBitrate: 128, 184 | }, 185 | '101': { 186 | container: 'webm', 187 | resolution: '360p', 188 | encoding: 'VP8', 189 | profile: '3d', 190 | bitrate: null, 191 | audioEncoding: 'vorbis', 192 | audioBitrate: 192, 193 | }, 194 | '102': { 195 | container: 'webm', 196 | resolution: '720p', 197 | encoding: 'VP8', 198 | profile: '3d', 199 | bitrate: null, 200 | audioEncoding: 'vorbis', 201 | audioBitrate: 192, 202 | }, 203 | // DASH (video only) 204 | '133': { 205 | container: 'mp4', 206 | resolution: '240p', 207 | encoding: 'H.264', 208 | profile: 'main', 209 | bitrate: '0.2-0.3', 210 | audioEncoding: null, 211 | audioBitrate: null, 212 | }, 213 | '134': { 214 | container: 'mp4', 215 | resolution: '360p', 216 | encoding: 'H.264', 217 | profile: 'main', 218 | bitrate: '0.3-0.4', 219 | audioEncoding: null, 220 | audioBitrate: null, 221 | }, 222 | '135': { 223 | container: 'mp4', 224 | resolution: '480p', 225 | encoding: 'H.264', 226 | profile: 'main', 227 | bitrate: '0.5-1', 228 | audioEncoding: null, 229 | audioBitrate: null, 230 | }, 231 | '136': { 232 | container: 'mp4', 233 | resolution: '720p', 234 | encoding: 'H.264', 235 | profile: 'main', 236 | bitrate: '1-1.5', 237 | audioEncoding: null, 238 | audioBitrate: null, 239 | }, 240 | '137': { 241 | container: 'mp4', 242 | resolution: '1080p', 243 | encoding: 'H.264', 244 | profile: 'high', 245 | bitrate: '2.5-3', 246 | audioEncoding: null, 247 | audioBitrate: null, 248 | }, 249 | '138': { 250 | container: 'mp4', 251 | resolution: '4320p', 252 | encoding: 'H.264', 253 | profile: 'high', 254 | bitrate: '13.5-25', 255 | audioEncoding: null, 256 | audioBitrate: null, 257 | }, 258 | '160': { 259 | container: 'mp4', 260 | resolution: '144p', 261 | encoding: 'H.264', 262 | profile: 'main', 263 | bitrate: '0.1', 264 | audioEncoding: null, 265 | audioBitrate: null, 266 | }, 267 | '242': { 268 | container: 'webm', 269 | resolution: '240p', 270 | encoding: 'VP9', 271 | profile: 'profile 0', 272 | bitrate: '0.1-0.2', 273 | audioEncoding: null, 274 | audioBitrate: null, 275 | }, 276 | '243': { 277 | container: 'webm', 278 | resolution: '360p', 279 | encoding: 'VP9', 280 | profile: 'profile 0', 281 | bitrate: '0.25', 282 | audioEncoding: null, 283 | audioBitrate: null, 284 | }, 285 | '244': { 286 | container: 'webm', 287 | resolution: '480p', 288 | encoding: 'VP9', 289 | profile: 'profile 0', 290 | bitrate: '0.5', 291 | audioEncoding: null, 292 | audioBitrate: null, 293 | }, 294 | '247': { 295 | container: 'webm', 296 | resolution: '720p', 297 | encoding: 'VP9', 298 | profile: 'profile 0', 299 | bitrate: '0.7-0.8', 300 | audioEncoding: null, 301 | audioBitrate: null, 302 | }, 303 | '248': { 304 | container: 'webm', 305 | resolution: '1080p', 306 | encoding: 'VP9', 307 | profile: 'profile 0', 308 | bitrate: '1.5', 309 | audioEncoding: null, 310 | audioBitrate: null, 311 | }, 312 | '264': { 313 | container: 'mp4', 314 | resolution: '1440p', 315 | encoding: 'H.264', 316 | profile: 'high', 317 | bitrate: '4-4.5', 318 | audioEncoding: null, 319 | audioBitrate: null, 320 | }, 321 | '266': { 322 | container: 'mp4', 323 | resolution: '2160p', 324 | encoding: 'H.264', 325 | profile: 'high', 326 | bitrate: '12.5-16', 327 | audioEncoding: null, 328 | audioBitrate: null, 329 | }, 330 | '271': { 331 | container: 'webm', 332 | resolution: '1440p', 333 | encoding: 'VP9', 334 | profile: 'profle 0', 335 | bitrate: '9', 336 | audioEncoding: null, 337 | audioBitrate: null, 338 | }, 339 | '272': { 340 | container: 'webm', 341 | resolution: '4320p', 342 | encoding: 'VP9', 343 | profile: 'profile 0', 344 | bitrate: '20-25', 345 | audioEncoding: null, 346 | audioBitrate: null, 347 | }, 348 | '278': { 349 | container: 'webm', 350 | resolution: '144p 15fps', 351 | encoding: 'VP9', 352 | profile: 'profile 0', 353 | bitrate: '0.08', 354 | audioEncoding: null, 355 | audioBitrate: null, 356 | }, 357 | '298': { 358 | container: 'mp4', 359 | resolution: '720p', 360 | encoding: 'H.264', 361 | profile: 'main', 362 | bitrate: '3-3.5', 363 | audioEncoding: null, 364 | audioBitrate: null, 365 | }, 366 | '299': { 367 | container: 'mp4', 368 | resolution: '1080p', 369 | encoding: 'H.264', 370 | profile: 'high', 371 | bitrate: '5.5', 372 | audioEncoding: null, 373 | audioBitrate: null, 374 | }, 375 | '302': { 376 | container: 'webm', 377 | resolution: '720p HFR', 378 | encoding: 'VP9', 379 | profile: 'profile 0', 380 | bitrate: '2.5', 381 | audioEncoding: null, 382 | audioBitrate: null, 383 | }, 384 | '303': { 385 | container: 'webm', 386 | resolution: '1080p HFR', 387 | encoding: 'VP9', 388 | profile: 'profile 0', 389 | bitrate: '5', 390 | audioEncoding: null, 391 | audioBitrate: null, 392 | }, 393 | '308': { 394 | container: 'webm', 395 | resolution: '1440p HFR', 396 | encoding: 'VP9', 397 | profile: 'profile 0', 398 | bitrate: '10', 399 | audioEncoding: null, 400 | audioBitrate: null, 401 | }, 402 | '313': { 403 | container: 'webm', 404 | resolution: '2160p', 405 | encoding: 'VP9', 406 | profile: 'profile 0', 407 | bitrate: '13-15', 408 | audioEncoding: null, 409 | audioBitrate: null, 410 | }, 411 | '315': { 412 | container: 'webm', 413 | resolution: '2160p HFR', 414 | encoding: 'VP9', 415 | profile: 'profile 0', 416 | bitrate: '20-25', 417 | audioEncoding: null, 418 | audioBitrate: null, 419 | }, 420 | '330': { 421 | container: 'webm', 422 | resolution: '144p HDR, HFR', 423 | encoding: 'VP9', 424 | profile: 'profile 2', 425 | bitrate: '0.08', 426 | audioEncoding: null, 427 | audioBitrate: null, 428 | }, 429 | '331': { 430 | container: 'webm', 431 | resolution: '240p HDR, HFR', 432 | encoding: 'VP9', 433 | profile: 'profile 2', 434 | bitrate: '0.1-0.15', 435 | audioEncoding: null, 436 | audioBitrate: null, 437 | }, 438 | '332': { 439 | container: 'webm', 440 | resolution: '360p HDR, HFR', 441 | encoding: 'VP9', 442 | profile: 'profile 2', 443 | bitrate: '0.25', 444 | audioEncoding: null, 445 | audioBitrate: null, 446 | }, 447 | '333': { 448 | container: 'webm', 449 | resolution: '240p HDR, HFR', 450 | encoding: 'VP9', 451 | profile: 'profile 2', 452 | bitrate: '0.5', 453 | audioEncoding: null, 454 | audioBitrate: null, 455 | }, 456 | '334': { 457 | container: 'webm', 458 | resolution: '720p HDR, HFR', 459 | encoding: 'VP9', 460 | profile: 'profile 2', 461 | bitrate: '1', 462 | audioEncoding: null, 463 | audioBitrate: null, 464 | }, 465 | '335': { 466 | container: 'webm', 467 | resolution: '1080p HDR, HFR', 468 | encoding: 'VP9', 469 | profile: 'profile 2', 470 | bitrate: '1.5-2', 471 | audioEncoding: null, 472 | audioBitrate: null, 473 | }, 474 | '336': { 475 | container: 'webm', 476 | resolution: '1440p HDR, HFR', 477 | encoding: 'VP9', 478 | profile: 'profile 2', 479 | bitrate: '5-7', 480 | audioEncoding: null, 481 | audioBitrate: null, 482 | }, 483 | '337': { 484 | container: 'webm', 485 | resolution: '2160p HDR, HFR', 486 | encoding: 'VP9', 487 | profile: 'profile 2', 488 | bitrate: '12-14', 489 | audioEncoding: null, 490 | audioBitrate: null, 491 | }, 492 | // DASH (audio only) 493 | '139': { 494 | container: 'mp4', 495 | resolution: null, 496 | encoding: null, 497 | profile: null, 498 | bitrate: null, 499 | audioEncoding: 'aac', 500 | audioBitrate: 48, 501 | }, 502 | '140': { 503 | container: 'm4a', 504 | resolution: null, 505 | encoding: null, 506 | profile: null, 507 | bitrate: null, 508 | audioEncoding: 'aac', 509 | audioBitrate: 128, 510 | }, 511 | '141': { 512 | container: 'mp4', 513 | resolution: null, 514 | encoding: null, 515 | profile: null, 516 | bitrate: null, 517 | audioEncoding: 'aac', 518 | audioBitrate: 256, 519 | }, 520 | '171': { 521 | container: 'webm', 522 | resolution: null, 523 | encoding: null, 524 | profile: null, 525 | bitrate: null, 526 | audioEncoding: 'vorbis', 527 | audioBitrate: 128, 528 | }, 529 | '172': { 530 | container: 'webm', 531 | resolution: null, 532 | encoding: null, 533 | profile: null, 534 | bitrate: null, 535 | audioEncoding: 'vorbis', 536 | audioBitrate: 192, 537 | }, 538 | '249': { 539 | container: 'webm', 540 | resolution: null, 541 | encoding: null, 542 | profile: null, 543 | bitrate: null, 544 | audioEncoding: 'opus', 545 | audioBitrate: 48, 546 | }, 547 | '250': { 548 | container: 'webm', 549 | resolution: null, 550 | encoding: null, 551 | profile: null, 552 | bitrate: null, 553 | audioEncoding: 'opus', 554 | audioBitrate: 64, 555 | }, 556 | '251': { 557 | container: 'webm', 558 | resolution: null, 559 | encoding: null, 560 | profile: null, 561 | bitrate: null, 562 | audioEncoding: 'opus', 563 | audioBitrate: 160, 564 | }, 565 | // Live streaming 566 | '91': { 567 | container: 'ts', 568 | resolution: '144p', 569 | encoding: 'H.264', 570 | profile: 'main', 571 | bitrate: '0.1', 572 | audioEncoding: 'aac', 573 | audioBitrate: 48, 574 | }, 575 | '92': { 576 | container: 'ts', 577 | resolution: '240p', 578 | encoding: 'H.264', 579 | profile: 'main', 580 | bitrate: '0.15-0.3', 581 | audioEncoding: 'aac', 582 | audioBitrate: 48, 583 | }, 584 | '93': { 585 | container: 'ts', 586 | resolution: '360p', 587 | encoding: 'H.264', 588 | profile: 'main', 589 | bitrate: '0.5-1', 590 | audioEncoding: 'aac', 591 | audioBitrate: 128, 592 | }, 593 | '94': { 594 | container: 'ts', 595 | resolution: '480p', 596 | encoding: 'H.264', 597 | profile: 'main', 598 | bitrate: '0.8-1.25', 599 | audioEncoding: 'aac', 600 | audioBitrate: 128, 601 | }, 602 | '95': { 603 | container: 'ts', 604 | resolution: '720p', 605 | encoding: 'H.264', 606 | profile: 'main', 607 | bitrate: '1.5-3', 608 | audioEncoding: 'aac', 609 | audioBitrate: 256, 610 | }, 611 | '96': { 612 | container: 'ts', 613 | resolution: '1080p', 614 | encoding: 'H.264', 615 | profile: 'high', 616 | bitrate: '2.5-6', 617 | audioEncoding: 'aac', 618 | audioBitrate: 256, 619 | }, 620 | '120': { 621 | container: 'flv', 622 | resolution: '720p', 623 | encoding: 'H.264', 624 | profile: 'Main@L3.1', 625 | bitrate: '2', 626 | audioEncoding: 'aac', 627 | audioBitrate: 128, 628 | }, 629 | '127': { 630 | container: 'ts', 631 | resolution: null, 632 | encoding: null, 633 | profile: null, 634 | bitrate: null, 635 | audioEncoding: 'aac', 636 | audioBitrate: 96, 637 | }, 638 | '128': { 639 | container: 'ts', 640 | resolution: null, 641 | encoding: null, 642 | profile: null, 643 | bitrate: null, 644 | audioEncoding: 'aac', 645 | audioBitrate: 96, 646 | }, 647 | '132': { 648 | container: 'ts', 649 | resolution: '240p', 650 | encoding: 'H.264', 651 | profile: 'baseline', 652 | bitrate: '0.15-0.2', 653 | audioEncoding: 'aac', 654 | audioBitrate: 48, 655 | }, 656 | '151': { 657 | container: 'ts', 658 | resolution: '720p', 659 | encoding: 'H.264', 660 | profile: 'baseline', 661 | bitrate: '0.05', 662 | audioEncoding: 'aac', 663 | audioBitrate: 24, 664 | } 665 | }; --------------------------------------------------------------------------------