1
0
mirror of https://github.com/musix-org/musix-oss synced 2025-06-16 12:36:01 +00:00
This commit is contained in:
MatteZ02
2019-05-30 12:06:47 +03:00
parent cbdffcf19c
commit 5eb0264906
2502 changed files with 360854 additions and 0 deletions

21
node_modules/ytdl-core/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (C) 2012 by fent
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

165
node_modules/ytdl-core/README.md generated vendored Normal file
View File

@ -0,0 +1,165 @@
# node-ytdl-core
[![Build Status](https://secure.travis-ci.org/fent/node-ytdl-core.svg)](http://travis-ci.org/fent/node-ytdl-core)
[![Dependency Status](https://david-dm.org/fent/node-ytdl-core.svg)](https://david-dm.org/fent/node-ytdl-core)
[![codecov](https://codecov.io/gh/fent/node-ytdl-core/branch/master/graph/badge.svg)](https://codecov.io/gh/fent/node-ytdl-core)
[![Discord](https://img.shields.io/discord/484464227067887645.svg)](https://discord.gg/V3vSCs7)
Yet another youtube downloading module. Written with only Javascript and a node-friendly streaming interface.
For a CLI version of this, check out [ytdl](https://github.com/fent/node-ytdl), [pully](https://github.com/JimmyBoh/pully), and [yodl](https://github.com/Luxray5474/yodl).
# Support
You can contact us for support on our [chat server](https://discord.gg/V3vSCs7)
# Usage
```js
const fs = require('fs');
const ytdl = require('ytdl-core');
ytdl('http://www.youtube.com/watch?v=A02s8omM_hI')
.pipe(fs.createWriteStream('video.flv'));
```
# API
### ytdl(url, [options])
Attempts to download a video from the given url. Returns a [readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable). `options` can have the following keys
* `quality` - Video quality to download. Can be an [itag value](http://en.wikipedia.org/wiki/YouTube#Quality_and_formats), a list of itag values, or `highest`/`lowest`/`highestaudio`/`lowestaudio`/`highestvideo`/`lowestvideo`. `highestaudio`/`lowestaudio`/`highestvideo`/`lowestvideo` all prefer audio/video only respectively. Defaults to `highest`.
* `filter` - Used to decide what format to download. Can be `audioandvideo` to filter formats that contain both video and audio, `video` to filter for formats that contain video, or `videoonly` for formats that contain video and no additional audio track. Can also be `audio` or `audioonly`. You can give a filtering function that gets called with each format available. This function is given the `format` object as its first argument, and should return true if the format is preferable.
* `format` - Primarily used to download specific video or audio streams. This can be a specific `format` object returned from `getInfo`.
* Supplying this option will ignore the `filter` and `quality` options since the format is explicitly provided.
* `range` - A byte range in the form `{start: INT, end: INT}` that specifies part of the file to download, ie {start: 10355705, end: 12452856}.
* This downloads a portion of the file, and not a separately spliced video.
* `begin` - What time in the video to begin. Supports formats `00:00:00.000`, `0ms, 0s, 0m, 0h`, or number of milliseconds. Example: `1:30`, `05:10.123`, `10m30s`. For live videos, this also accepts a unix timestamp or Date, and defaults to `Date.now()`.
* This option may not work on super short (less than 30s) videos, and has to be at or above 6s, see [#129](https://github.com/fent/node-ytdl-core/issues/129).
* It may also not work for some formats, see [#219](https://github.com/fent/node-ytdl-core/issues/219).
* `liveBuffer` - How much time buffer to use for live videos in milliseconds. Default is `20000`.
* `requestOptions` - Anything to merge into the request options which [miniget](https://github.com/fent/node-miniget) is called with, such as headers.
* `highWaterMark` - How much of the video download to buffer into memory. See [node's docs](https://nodejs.org/api/stream.html#stream_constructor_new_stream_writable_options) for more.
* `lang` - The 2 character symbol of a language. Default is `en`.
```js
// Example with `filter` option.
ytdl(url, { filter: (format) => format.container === 'mp4' })
.pipe(fs.createWriteStream('video.mp4'));
```
#### Event: info
* [`ytdl.videoInfo`](example/info.json) - Info.
* [`ytdl.videoFormat`](typings/index.d.ts#L22) - Video Format.
Emitted when the a video's `info` hash is fetched, along with the chosen format metadata to download. `format.url` might be different if `start` was given.
#### Event: response
* [`http.ServerResponse`](https://nodejs.org/api/http.html#http_class_http_serverresponse) - Response.
Emitted when the video response has been found and has started downloading or after any successful reconnects. Can be used to get the size of the download.
#### Event: progress
* `number` - Chunk byte length.
* `number` - Total bytes or segments downloaded.
* `number` - Total bytes or segments.
Emitted whenever a new chunk is received. Passes values describing the download progress.
### ytdl.getBasicInfo(url, [options], [callback(err, info)])
Use this if you only want to get metainfo from a video. If `callback` isn't given, returns a promise.
### ytdl.getInfo(url, [options], [callback(err, info)])
Gets metainfo from a video. Includes additional formats, and ready to download deciphered URL. This is what the `ytdl()` function uses internally. If `callback` isn't given, returns a promise.
### ytdl.downloadFromInfo(info, options)
Once you have received metadata from a video with the `ytdl.getInfo` function, you may pass that information along with other options to this function.
### ytdl.chooseFormat(formats, options)
Can be used if you'd like to choose a format yourself with the [options above](#ytdlurl-options).
```js
// Example of choosing a video format.
ytdl.getInfo(videoID, (err, info) => {
if (err) throw err;
let format = ytdl.chooseFormat(info.formats, { quality: '134' });
if (format) {
console.log('Format found!');
}
});
```
### ytdl.filterFormats(formats, filter)
If you'd like to work with only some formats, you can use the [`filter` option above](#ytdlurl-options).
```js
// Example of filtering the formats to audio only.
ytdl.getInfo(videoID, (err, info) => {
if (err) throw err;
let audioFormats = ytdl.filterFormats(info.formats, 'audioonly');
console.log('Formats with only audio: ' + audioFormats.length);
});
```
### ytdl.validateID(id)
Returns true if the given string satisfies YouTube's ID format.
### ytdl.validateURL(url)
Returns true if able to parse out a valid video ID.
### ytdl.getURLVideoID(url)
Returns a video ID from a YouTube URL.
### ytdl.getVideoID(str)
Same as the above `ytdl.getURLVideoID()`, but can be called with the video ID directly, in which case it returns it. This is what ytdl uses internally.
## Limitations
ytdl cannot download videos that fall into the following
* Regionally restricted (requires a [proxy](example/proxy.js))
* Private
* Rentals
YouTube intentionally ratelimits downloads, likely to prevent bandwidth abuse. The download rate is still faster than a media player can play the video, even on 2x. See [#294](https://github.com/fent/node-ytdl-core/issues/294).
## Handling Separate Streams
Typically 1080p or better video does not have audio encoded with it. The audio must be downloaded separately and merged via an appropriate encoding library. `ffmpeg` is the most widely used tool, with many [Node.js modules available](https://www.npmjs.com/search?q=ffmpeg). Use the `format` objects returned from `ytdl.getInfo` to download specific streams to combine to fit your needs. Look at [example/ffmpeg.js](example/ffmpeg.js) for an example on doing this.
## What if it stops working?
Youtube updates their website all the time, it's not that rare for this to stop working. If it doesn't work for you and you're using the latest version, feel free to open up an issue. Make sure to check if there isn't one already with the same error.
If you'd like to help fix the issue, look at the type of error first. The most common one is
Could not extract signature deciphering actions
Run the tests at `test/irl-test.js` just to make sure that this is actually an issue with ytdl-core.
mocha test/irl-test.js
These tests are not mocked, and they actually try to start downloading a few videos. If these fail, then it's time to debug.
For getting started with that, you can look at the `extractActions()` function in [`/lib/sig.js`](https://github.com/fent/node-ytdl-core/blob/master/lib/sig.js).
# Install
```bash
npm install ytdl-core
```
# Tests
Tests are written with [mocha](https://mochajs.org)
```bash
npm test
```

737
node_modules/ytdl-core/lib/formats.js generated vendored Normal file
View File

@ -0,0 +1,737 @@
/**
* http://en.wikipedia.org/wiki/YouTube#Quality_and_formats
*/
module.exports = {
'5': {
container: 'flv',
resolution: '240p',
encoding: 'Sorenson H.283',
profile: null,
bitrate: '0.25',
audioEncoding: 'mp3',
audioBitrate: 64,
},
'6': {
container: 'flv',
resolution: '270p',
encoding: 'Sorenson H.263',
profile: null,
bitrate: '0.8',
audioEncoding: 'mp3',
audioBitrate: 64,
},
'13': {
container: '3gp',
resolution: null,
encoding: 'MPEG-4 Visual',
profile: null,
bitrate: '0.5',
audioEncoding: 'aac',
audioBitrate: null,
},
'17': {
container: '3gp',
resolution: '144p',
encoding: 'MPEG-4 Visual',
profile: 'simple',
bitrate: '0.05',
audioEncoding: 'aac',
audioBitrate: 24,
},
'18': {
container: 'mp4',
resolution: '360p',
encoding: 'H.264',
profile: 'baseline',
bitrate: '0.5',
audioEncoding: 'aac',
audioBitrate: 96,
},
'22': {
container: 'mp4',
resolution: '720p',
encoding: 'H.264',
profile: 'high',
bitrate: '2-3',
audioEncoding: 'aac',
audioBitrate: 192,
},
'34': {
container: 'flv',
resolution: '360p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.5',
audioEncoding: 'aac',
audioBitrate: 128,
},
'35': {
container: 'flv',
resolution: '480p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.8-1',
audioEncoding: 'aac',
audioBitrate: 128,
},
'36': {
container: '3gp',
resolution: '240p',
encoding: 'MPEG-4 Visual',
profile: 'simple',
bitrate: '0.175',
audioEncoding: 'aac',
audioBitrate: 32,
},
'37': {
container: 'mp4',
resolution: '1080p',
encoding: 'H.264',
profile: 'high',
bitrate: '3-5.9',
audioEncoding: 'aac',
audioBitrate: 192,
},
'38': {
container: 'mp4',
resolution: '3072p',
encoding: 'H.264',
profile: 'high',
bitrate: '3.5-5',
audioEncoding: 'aac',
audioBitrate: 192,
},
'43': {
container: 'webm',
resolution: '360p',
encoding: 'VP8',
profile: null,
bitrate: '0.5-0.75',
audioEncoding: 'vorbis',
audioBitrate: 128,
},
'44': {
container: 'webm',
resolution: '480p',
encoding: 'VP8',
profile: null,
bitrate: '1',
audioEncoding: 'vorbis',
audioBitrate: 128,
},
'45': {
container: 'webm',
resolution: '720p',
encoding: 'VP8',
profile: null,
bitrate: '2',
audioEncoding: 'vorbis',
audioBitrate: 192,
},
'46': {
container: 'webm',
resolution: '1080p',
encoding: 'vp8',
profile: null,
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 192,
},
'82': {
container: 'mp4',
resolution: '360p',
encoding: 'H.264',
profile: '3d',
bitrate: '0.5',
audioEncoding: 'aac',
audioBitrate: 96,
},
'83': {
container: 'mp4',
resolution: '240p',
encoding: 'H.264',
profile: '3d',
bitrate: '0.5',
audioEncoding: 'aac',
audioBitrate: 96,
},
'84': {
container: 'mp4',
resolution: '720p',
encoding: 'H.264',
profile: '3d',
bitrate: '2-3',
audioEncoding: 'aac',
audioBitrate: 192,
},
'85': {
container: 'mp4',
resolution: '1080p',
encoding: 'H.264',
profile: '3d',
bitrate: '3-4',
audioEncoding: 'aac',
audioBitrate: 192,
},
'100': {
container: 'webm',
resolution: '360p',
encoding: 'VP8',
profile: '3d',
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 128,
},
'101': {
container: 'webm',
resolution: '360p',
encoding: 'VP8',
profile: '3d',
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 192,
},
'102': {
container: 'webm',
resolution: '720p',
encoding: 'VP8',
profile: '3d',
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 192,
},
// DASH (video only)
'133': {
container: 'mp4',
resolution: '240p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.2-0.3',
audioEncoding: null,
audioBitrate: null,
},
'134': {
container: 'mp4',
resolution: '360p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.3-0.4',
audioEncoding: null,
audioBitrate: null,
},
'135': {
container: 'mp4',
resolution: '480p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.5-1',
audioEncoding: null,
audioBitrate: null,
},
'136': {
container: 'mp4',
resolution: '720p',
encoding: 'H.264',
profile: 'main',
bitrate: '1-1.5',
audioEncoding: null,
audioBitrate: null,
},
'137': {
container: 'mp4',
resolution: '1080p',
encoding: 'H.264',
profile: 'high',
bitrate: '2.5-3',
audioEncoding: null,
audioBitrate: null,
},
'138': {
container: 'mp4',
resolution: '4320p',
encoding: 'H.264',
profile: 'high',
bitrate: '13.5-25',
audioEncoding: null,
audioBitrate: null,
},
'160': {
container: 'mp4',
resolution: '144p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.1',
audioEncoding: null,
audioBitrate: null,
},
'242': {
container: 'webm',
resolution: '240p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '0.1-0.2',
audioEncoding: null,
audioBitrate: null,
},
'243': {
container: 'webm',
resolution: '360p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '0.25',
audioEncoding: null,
audioBitrate: null,
},
'244': {
container: 'webm',
resolution: '480p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '0.5',
audioEncoding: null,
audioBitrate: null,
},
'247': {
container: 'webm',
resolution: '720p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '0.7-0.8',
audioEncoding: null,
audioBitrate: null,
},
'248': {
container: 'webm',
resolution: '1080p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '1.5',
audioEncoding: null,
audioBitrate: null,
},
'264': {
container: 'mp4',
resolution: '1440p',
encoding: 'H.264',
profile: 'high',
bitrate: '4-4.5',
audioEncoding: null,
audioBitrate: null,
},
'266': {
container: 'mp4',
resolution: '2160p',
encoding: 'H.264',
profile: 'high',
bitrate: '12.5-16',
audioEncoding: null,
audioBitrate: null,
},
'271': {
container: 'webm',
resolution: '1440p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '9',
audioEncoding: null,
audioBitrate: null,
},
'272': {
container: 'webm',
resolution: '4320p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '20-25',
audioEncoding: null,
audioBitrate: null,
},
'278': {
container: 'webm',
resolution: '144p 15fps',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '0.08',
audioEncoding: null,
audioBitrate: null,
},
'298': {
container: 'mp4',
resolution: '720p',
encoding: 'H.264',
profile: 'main',
bitrate: '3-3.5',
audioEncoding: null,
audioBitrate: null,
},
'299': {
container: 'mp4',
resolution: '1080p',
encoding: 'H.264',
profile: 'high',
bitrate: '5.5',
audioEncoding: null,
audioBitrate: null,
},
'302': {
container: 'webm',
resolution: '720p HFR',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '2.5',
audioEncoding: null,
audioBitrate: null,
},
'303': {
container: 'webm',
resolution: '1080p HFR',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '5',
audioEncoding: null,
audioBitrate: null,
},
'308': {
container: 'webm',
resolution: '1440p HFR',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '10',
audioEncoding: null,
audioBitrate: null,
},
'313': {
container: 'webm',
resolution: '2160p',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '13-15',
audioEncoding: null,
audioBitrate: null,
},
'315': {
container: 'webm',
resolution: '2160p HFR',
encoding: 'VP9',
profile: 'profile 0',
bitrate: '20-25',
audioEncoding: null,
audioBitrate: null,
},
'330': {
container: 'webm',
resolution: '144p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '0.08',
audioEncoding: null,
audioBitrate: null,
},
'331': {
container: 'webm',
resolution: '240p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '0.1-0.15',
audioEncoding: null,
audioBitrate: null,
},
'332': {
container: 'webm',
resolution: '360p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '0.25',
audioEncoding: null,
audioBitrate: null,
},
'333': {
container: 'webm',
resolution: '240p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '0.5',
audioEncoding: null,
audioBitrate: null,
},
'334': {
container: 'webm',
resolution: '720p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '1',
audioEncoding: null,
audioBitrate: null,
},
'335': {
container: 'webm',
resolution: '1080p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '1.5-2',
audioEncoding: null,
audioBitrate: null,
},
'336': {
container: 'webm',
resolution: '1440p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '5-7',
audioEncoding: null,
audioBitrate: null,
},
'337': {
container: 'webm',
resolution: '2160p HDR, HFR',
encoding: 'VP9',
profile: 'profile 2',
bitrate: '12-14',
audioEncoding: null,
audioBitrate: null,
},
// DASH (audio only)
'139': {
container: 'mp4',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'aac',
audioBitrate: 48,
},
'140': {
container: 'm4a',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'aac',
audioBitrate: 128,
},
'141': {
container: 'mp4',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'aac',
audioBitrate: 256,
},
'171': {
container: 'webm',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 128,
},
'172': {
container: 'webm',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'vorbis',
audioBitrate: 192,
},
'249': {
container: 'webm',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'opus',
audioBitrate: 48,
},
'250': {
container: 'webm',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'opus',
audioBitrate: 64,
},
'251': {
container: 'webm',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'opus',
audioBitrate: 160,
},
// Live streaming
'91': {
container: 'ts',
resolution: '144p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.1',
audioEncoding: 'aac',
audioBitrate: 48,
},
'92': {
container: 'ts',
resolution: '240p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.15-0.3',
audioEncoding: 'aac',
audioBitrate: 48,
},
'93': {
container: 'ts',
resolution: '360p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.5-1',
audioEncoding: 'aac',
audioBitrate: 128,
},
'94': {
container: 'ts',
resolution: '480p',
encoding: 'H.264',
profile: 'main',
bitrate: '0.8-1.25',
audioEncoding: 'aac',
audioBitrate: 128,
},
'95': {
container: 'ts',
resolution: '720p',
encoding: 'H.264',
profile: 'main',
bitrate: '1.5-3',
audioEncoding: 'aac',
audioBitrate: 256,
},
'96': {
container: 'ts',
resolution: '1080p',
encoding: 'H.264',
profile: 'high',
bitrate: '2.5-6',
audioEncoding: 'aac',
audioBitrate: 256,
},
'120': {
container: 'flv',
resolution: '720p',
encoding: 'H.264',
profile: 'Main@L3.1',
bitrate: '2',
audioEncoding: 'aac',
audioBitrate: 128,
},
'127': {
container: 'ts',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'aac',
audioBitrate: 96,
},
'128': {
container: 'ts',
resolution: null,
encoding: null,
profile: null,
bitrate: null,
audioEncoding: 'aac',
audioBitrate: 96,
},
'132': {
container: 'ts',
resolution: '240p',
encoding: 'H.264',
profile: 'baseline',
bitrate: '0.15-0.2',
audioEncoding: 'aac',
audioBitrate: 48,
},
'151': {
container: 'ts',
resolution: '720p',
encoding: 'H.264',
profile: 'baseline',
bitrate: '0.05',
audioEncoding: 'aac',
audioBitrate: 24,
},
};

156
node_modules/ytdl-core/lib/index.js generated vendored Normal file
View File

@ -0,0 +1,156 @@
const PassThrough = require('stream').PassThrough;
const getInfo = require('./info');
const util = require('./util');
const sig = require('./sig');
const request = require('miniget');
const m3u8stream = require('m3u8stream');
const parseTime = require('m3u8stream/lib/parse-time');
/**
* @param {string} link
* @param {!Object} options
* @return {ReadableStream}
*/
const ytdl = (link, options) => {
const stream = createStream(options);
ytdl.getInfo(link, options, (err, info) => {
if (err) {
stream.emit('error', err);
return;
}
downloadFromInfoCallback(stream, info, options);
});
return stream;
};
module.exports = ytdl;
ytdl.getBasicInfo = getInfo.getBasicInfo;
ytdl.getInfo = getInfo.getFullInfo;
ytdl.chooseFormat = util.chooseFormat;
ytdl.filterFormats = util.filterFormats;
ytdl.validateID = util.validateID;
ytdl.validateURL = util.validateURL;
ytdl.getURLVideoID = util.getURLVideoID;
ytdl.getVideoID = util.getVideoID;
ytdl.cache = {
sig: sig.cache,
info: getInfo.cache,
};
const createStream = (options) => {
const stream = new PassThrough({
highWaterMark: options && options.highWaterMark || null,
});
stream.destroy = () => { stream._isDestroyed = true; };
return stream;
};
/**
* Chooses a format to download.
*
* @param {stream.Readable} stream
* @param {Object} info
* @param {Object} options
*/
const downloadFromInfoCallback = (stream, info, options) => {
options = options || {};
const format = util.chooseFormat(info.formats, options);
if (format instanceof Error) {
// The caller expects this function to be async.
setImmediate(() => {
stream.emit('error', format);
});
return;
}
stream.emit('info', info, format);
if (stream._isDestroyed) { return; }
let contentLength, downloaded = 0;
const ondata = (chunk) => {
downloaded += chunk.length;
stream.emit('progress', chunk.length, downloaded, contentLength);
};
let req;
if (format.isHLS || format.isDashMPD) {
req = m3u8stream(format.url, {
chunkReadahead: +info.live_chunk_readahead,
begin: options.begin || format.live && Date.now(),
liveBuffer: options.liveBuffer,
requestOptions: options.requestOptions,
parser: format.isDashMPD ? 'dash-mpd' : 'm3u8',
id: format.itag,
});
req.on('progress', (segment, totalSegments) => {
stream.emit('progress', segment.size, segment.num, totalSegments);
});
} else {
if (options.begin) {
format.url += '&begin=' + parseTime.humanStr(options.begin);
}
let requestOptions = Object.assign({}, options.requestOptions, {
maxReconnects: 5
});
if (options.range && (options.range.start || options.range.end)) {
requestOptions.headers = Object.assign({}, requestOptions.headers, {
Range: `bytes=${options.range.start || '0'}-${options.range.end || ''}`
});
}
req = request(format.url, requestOptions);
req.on('response', (res) => {
if (stream._isDestroyed) { return; }
if (!contentLength) {
contentLength = parseInt(res.headers['content-length'], 10);
}
});
req.on('data', ondata);
}
stream.destroy = () => {
stream._isDestroyed = true;
if (req.abort) req.abort();
req.end();
req.removeListener('data', ondata);
req.unpipe();
};
// Forward events from the request to the stream.
[
'abort', 'request', 'response', 'error', 'retry', 'reconnect'
].forEach((event) => {
req.prependListener(event, (arg) => {
stream.emit(event, arg); });
});
req.pipe(stream);
};
/**
* Can be used to download video after its `info` is gotten through
* `ytdl.getInfo()`. In case the user might want to look at the
* `info` object before deciding to download.
*
* @param {Object} info
* @param {!Object} options
*/
ytdl.downloadFromInfo = (info, options) => {
const stream = createStream(options);
if (!info.full) {
throw new Error('Cannot use `ytdl.downloadFromInfo()` when called ' +
'with info from `ytdl.getBasicInfo()`');
}
setImmediate(() => {
downloadFromInfoCallback(stream, info, options);
});
return stream;
};

134
node_modules/ytdl-core/lib/info-extras.js generated vendored Normal file
View File

@ -0,0 +1,134 @@
const qs = require('querystring');
const url = require('url');
const Entities = require('html-entities').AllHtmlEntities;
const util = require('./util');
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
const getMetaItem = (body, name) => {
return util.between(body, `<meta itemprop="${name}" content="`, '">');
};
/**
* Get video description from html
*
* @param {string} html
* @return {string}
*/
exports.getVideoDescription = (html) => {
const regex = /<p.*?id="eow-description".*?>(.+?)<\/p>[\n\r\s]*?<\/div>/im;
const description = html.match(regex);
return description ?
Entities.decode(util.stripHTML(description[1])) : '';
};
/**
* Get video media (extra information) from html
*
* @param {string} body
* @return {Object}
*/
exports.getVideoMedia = (body) => {
let mediainfo = util.between(body,
'<div id="watch-description-extras">',
'<div id="watch-discussion" class="branded-page-box yt-card">');
if (mediainfo === '') {
return {};
}
const regexp = /<h4 class="title">([\s\S]*?)<\/h4>[\s\S]*?<ul .*?class=".*?watch-info-tag-list">[\s\S]*?<li>([\s\S]*?)<\/li>(?:\s*?<li>([\s\S]*?)<\/li>)?/g;
const contentRegexp = /(?: - (\d{4}) \()?<a .*?(?:href="([^"]+)")?.*?>(.*?)<\/a>/;
const imgRegexp = /<img src="([^"]+)".*?>/;
const media = {};
const image = imgRegexp.exec(mediainfo);
if (image) {
media.image = url.resolve(VIDEO_URL, image[1]);
}
let match;
while ((match = regexp.exec(mediainfo)) != null) {
let [, key, value, detail] = match;
key = Entities.decode(key).trim().replace(/\s/g, '_').toLowerCase();
const content = contentRegexp.exec(value);
if (content) {
let [, year, mediaUrl, value2] = content;
if (year) {
media.year = parseInt(year);
} else if (detail) {
media.year = parseInt(detail);
}
value = value.slice(0, content.index);
if (key !== 'game' || value2 !== 'YouTube Gaming') {
value += value2;
}
media[key + '_url'] = url.resolve(VIDEO_URL, mediaUrl);
}
media[key] = Entities.decode(value);
}
return media;
};
/**
* Get video Owner from html.
*
* @param {string} body
* @return {Object}
*/
const userRegexp = /<a href="\/user\/([^"]+)/;
const verifiedRegexp = /<span .*?(aria-label="Verified")(.*?(?=<\/span>))/;
exports.getAuthor = (body) => {
let ownerinfo = util.between(body,
'<div id="watch7-user-header" class=" spf-link ">',
'<div id="watch8-action-buttons" class="watch-action-buttons clearfix">');
if (ownerinfo === '') {
return {};
}
const channelName = Entities.decode(util.between(util.between(
ownerinfo, '<div class="yt-user-info">', '</div>'), '>', '</a>'));
const userMatch = ownerinfo.match(userRegexp);
const verifiedMatch = ownerinfo.match(verifiedRegexp);
const channelID = getMetaItem(body, 'channelId');
const username = userMatch ? userMatch[1] : util.between(
util.between(body, '<span itemprop="author"', '</span>'), '/user/', '">');
return {
id: channelID,
name: channelName,
avatar: url.resolve(VIDEO_URL, util.between(ownerinfo,
'data-thumb="', '"')),
verified: !!verifiedMatch,
user: username,
channel_url: 'https://www.youtube.com/channel/' + channelID,
user_url: 'https://www.youtube.com/user/' + username,
};
};
/**
* Get video published at from html.
*
* @param {string} body
* @return {string}
*/
exports.getPublished = (body) => {
return Date.parse(getMetaItem(body, 'datePublished'));
};
/**
* Get video published at from html.
* Credits to https://github.com/paixaop.
*
* @param {string} body
* @return {Array.<Object>}
*/
exports.getRelatedVideos = (body) => {
let jsonStr = util.between(body, '\'RELATED_PLAYER_ARGS\': {"rvs":', '},');
try {
jsonStr = JSON.parse(jsonStr);
} catch (err) {
return [];
}
return jsonStr.split(',').map((link) => qs.parse(link));
};

376
node_modules/ytdl-core/lib/info.js generated vendored Normal file
View File

@ -0,0 +1,376 @@
const urllib = require('url');
const querystring = require('querystring');
const sax = require('sax');
const request = require('miniget');
const util = require('./util');
const extras = require('./info-extras');
const sig = require('./sig');
const FORMATS = require('./formats');
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
const EMBED_URL = 'https://www.youtube.com/embed/';
const VIDEO_EURL = 'https://youtube.googleapis.com/v/';
const INFO_HOST = 'www.youtube.com';
const INFO_PATH = '/get_video_info';
const KEYS_TO_SPLIT = [
'fmt_list',
'fexp',
'watermark'
];
/**
* Gets info from a video without getting additional formats.
*
* @param {string} id
* @param {Object} options
* @param {Function(Error, Object)} callback
*/
exports.getBasicInfo = (id, options, callback) => {
// Try getting config from the video page first.
const params = 'hl=' + (options.lang || 'en');
let url = VIDEO_URL + id + '&' + params +
'&bpctr=' + Math.ceil(Date.now() / 1000);
// Remove header from watch page request.
// Otherwise, it'll use a different framework for rendering content.
const reqOptions = Object.assign({}, options.requestOptions);
reqOptions.headers = Object.assign({}, reqOptions.headers, {
'User-Agent': ''
});
request(url, reqOptions, (err, res, body) => {
if (err) return callback(err);
// Check if there are any errors with this video page.
const unavailableMsg = util.between(body, '<div id="player-unavailable"', '>');
if (unavailableMsg &&
!/\bhid\b/.test(util.between(unavailableMsg, 'class="', '"'))) {
// Ignore error about age restriction.
if (!body.includes('<div id="watch7-player-age-gate-content"')) {
return callback(Error(util.between(body,
'<h1 id="unavailable-message" class="message">', '</h1>').trim()));
}
}
// Parse out additional metadata from this page.
const additional = {
// Get the author/uploader.
author: extras.getAuthor(body),
// Get the day the vid was published.
published: extras.getPublished(body),
// Get description.
description: extras.getVideoDescription(body),
// Get media info.
media: extras.getVideoMedia(body),
// Get related videos.
related_videos: extras.getRelatedVideos(body),
// Give the standard link to the video.
video_url: VIDEO_URL + id,
};
const jsonStr = util.between(body, 'ytplayer.config = ', '</script>');
let config;
if (jsonStr) {
config = jsonStr.slice(0, jsonStr.lastIndexOf(';ytplayer.load'));
gotConfig(id, options, additional, config, false, callback);
} else {
// If the video page doesn't work, maybe because it has mature content.
// and requires an account logged in to view, try the embed page.
url = EMBED_URL + id + '?' + params;
request(url, options.requestOptions, (err, res, body) => {
if (err) return callback(err);
config = util.between(body, 't.setConfig({\'PLAYER_CONFIG\': ', /\}(,'|\}\);)/);
gotConfig(id, options, additional, config, true, callback);
});
}
});
};
/**
* @param {Object} id
* @param {Object} options
* @param {Object} additional
* @param {Object} config
* @param {boolean} fromEmbed
* @param {Function(Error, Object)} callback
*/
const gotConfig = (id, options, additional, config, fromEmbed, callback) => {
if (!config) {
return callback(Error('Could not find player config'));
}
try {
config = JSON.parse(config + (fromEmbed ? '}' : ''));
} catch (err) {
return callback(Error('Error parsing config: ' + err.message));
}
const url = urllib.format({
protocol: 'https',
host: INFO_HOST,
pathname: INFO_PATH,
query: {
video_id: id,
eurl: VIDEO_EURL + id,
ps: 'default',
gl: 'US',
hl: (options.lang || 'en'),
sts: config.sts,
},
});
request(url, options.requestOptions, (err, res, body) => {
if (err) return callback(err);
let info = querystring.parse(body);
if (info.status === 'fail') {
if (config.args && (config.args.fmt_list ||
config.args.url_encoded_fmt_stream_map || config.args.adaptive_fmts)) {
info = config.args;
info.no_embed_allowed = true;
} else {
return callback(
Error(`Code ${info.errorcode}: ${util.stripHTML(info.reason)}`));
}
}
const player_response = config.args.player_response || info.player_response;
if (player_response) {
try {
info.player_response = JSON.parse(player_response);
} catch (err) {
return callback(
Error('Error parsing `player_response`: ' + err.message));
}
let playability = info.player_response.playabilityStatus;
if (playability && playability.status === 'UNPLAYABLE') {
return callback(Error(playability.reason));
}
}
// Split some keys by commas.
KEYS_TO_SPLIT.forEach((key) => {
if (!info[key]) return;
info[key] = info[key]
.split(',')
.filter((v) => v !== '');
});
info.fmt_list = info.fmt_list ?
info.fmt_list.map((format) => format.split('/')) : [];
info.formats = util.parseFormats(info);
// Add additional properties to info.
Object.assign(info, additional);
info.age_restricted = fromEmbed;
info.html5player = config.assets.js;
callback(null, info);
});
};
/**
* Gets info from a video additional formats and deciphered URLs.
*
* @param {string} id
* @param {Object} options
* @param {Function(Error, Object)} callback
*/
exports.getFullInfo = (id, options, callback) => {
return exports.getBasicInfo(id, options, (err, info) => {
if (err) return callback(err);
const hasManifest =
info.player_response && info.player_response.streamingData && (
info.player_response.streamingData.dashManifestUrl ||
info.player_response.streamingData.hlsManifestUrl
);
if (info.formats.length || hasManifest) {
const html5playerfile = urllib.resolve(VIDEO_URL, info.html5player);
sig.getTokens(html5playerfile, options, (err, tokens) => {
if (err) return callback(err);
sig.decipherFormats(info.formats, tokens, options.debug);
let funcs = [];
if (hasManifest && info.player_response.streamingData.dashManifestUrl) {
let url = decipherURL(info.player_response.streamingData.dashManifestUrl, tokens);
funcs.push(getDashManifest.bind(null, url, options));
}
if (hasManifest && info.player_response.streamingData.hlsManifestUrl) {
let url = decipherURL(info.player_response.streamingData.hlsManifestUrl, tokens);
funcs.push(getM3U8.bind(null, url, options));
}
util.parallel(funcs, (err, results) => {
if (err) return callback(err);
if (results[0]) { mergeFormats(info, results[0]); }
if (results[1]) { mergeFormats(info, results[1]); }
if (!info.formats.length) {
callback(Error('No formats found'));
return;
}
if (options.debug) {
info.formats.forEach((format) => {
const itag = format.itag;
if (!FORMATS[itag]) {
console.warn(`No format metadata for itag ${itag} found`);
}
});
}
info.formats.forEach(util.addFormatMeta);
info.formats.sort(util.sortFormats);
info.full = true;
callback(null, info);
});
});
} else {
callback(Error('This video is unavailable'));
}
});
};
/**
* @param {string} url
* @param {Array.<string>} tokens
*/
const decipherURL = (url, tokens) => {
return url.replace(/\/s\/([a-fA-F0-9.]+)/, (_, s) => {
return '/signature/' + sig.decipher(tokens, s);
});
};
/**
* Merges formats from DASH or M3U8 with formats from video info page.
*
* @param {Object} info
* @param {Object} formatsMap
*/
const mergeFormats = (info, formatsMap) => {
info.formats.forEach((f) => {
if (!formatsMap[f.itag]) {
formatsMap[f.itag] = f;
}
});
info.formats = [];
for (let itag in formatsMap) { info.formats.push(formatsMap[itag]); }
};
/**
* Gets additional DASH formats.
*
* @param {string} url
* @param {Object} options
* @param {Function(!Error, Array.<Object>)} callback
*/
const getDashManifest = (url, options, callback) => {
let formats = {};
const parser = sax.parser(false);
parser.onerror = callback;
parser.onopentag = (node) => {
if (node.name === 'REPRESENTATION') {
const itag = node.attributes.ID;
formats[itag] = { itag, url };
}
};
parser.onend = () => { callback(null, formats); };
const req = request(urllib.resolve(VIDEO_URL, url), options.requestOptions);
req.setEncoding('utf8');
req.on('error', callback);
req.on('data', (chunk) => { parser.write(chunk); });
req.on('end', parser.close.bind(parser));
};
/**
* Gets additional formats.
*
* @param {string} url
* @param {Object} options
* @param {Function(!Error, Array.<Object>)} callback
*/
const getM3U8 = (url, options, callback) => {
url = urllib.resolve(VIDEO_URL, url);
request(url, options.requestOptions, (err, res, body) => {
if (err) return callback(err);
let formats = {};
body
.split('\n')
.filter((line) => /https?:\/\//.test(line))
.forEach((line) => {
const itag = line.match(/\/itag\/(\d+)\//)[1];
formats[itag] = { itag: itag, url: line };
});
callback(null, formats);
});
};
// Cached for getting basic/full info.
exports.cache = new Map();
exports.cache.timeout = 1000;
// Cache get info functions.
// In case a user wants to get a video's info before downloading.
for (let fnName of ['getBasicInfo', 'getFullInfo']) {
/**
* @param {string} link
* @param {Object} options
* @param {Function(Error, Object)} callback
*/
const fn = exports[fnName];
exports[fnName] = (link, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = {};
} else if (!options) {
options = {};
}
if (!callback) {
return new Promise((resolve, reject) => {
exports[fnName](link, options, (err, info) => {
if (err) return reject(err);
resolve(info);
});
});
}
const id = util.getVideoID(link);
if (id instanceof Error) return callback(id);
const key = [fnName, id, options.lang].join('-');
if (exports.cache.has(key)) {
callback(null, exports.cache.get(key));
} else {
fn(id, options, (err, info) => {
if (err) return callback(err);
exports.cache.set(key, info);
setTimeout(() => { exports.cache.delete(key); }, exports.cache.timeout);
callback(null, info);
});
}
};
}
// Export a few helpers.
exports.validateID = util.validateID;
exports.validateURL = util.validateURL;
exports.getURLVideoID = util.getURLVideoID;
exports.getVideoID = util.getVideoID;

264
node_modules/ytdl-core/lib/sig.js generated vendored Normal file
View File

@ -0,0 +1,264 @@
const url = require('url');
const request = require('miniget');
// A shared cache to keep track of html5player.js tokens.
exports.cache = new Map();
/**
* Extract signature deciphering tokens from html5player file.
*
* @param {string} html5playerfile
* @param {Object} options
* @param {Function(!Error, Array.<string>)} callback
*/
exports.getTokens = (html5playerfile, options, callback) => {
let key, cachedTokens;
const rs = /(?:html5)?player[-_]([a-zA-Z0-9\-_]+)(?:\.js|\/)/
.exec(html5playerfile);
if (rs) {
key = rs[1];
cachedTokens = exports.cache.get(key);
} else {
console.warn('Could not extract html5player key:', html5playerfile);
}
if (cachedTokens) {
callback(null, cachedTokens);
} else {
request(html5playerfile, options.requestOptions, (err, res, body) => {
if (err) return callback(err);
const tokens = exports.extractActions(body);
if (key && (!tokens || !tokens.length)) {
callback(Error('Could not extract signature deciphering actions'));
return;
}
exports.cache.set(key, tokens);
callback(null, tokens);
});
}
};
/**
* Decipher a signature based on action tokens.
*
* @param {Array.<string>} tokens
* @param {string} sig
* @return {string}
*/
exports.decipher = (tokens, sig) => {
sig = sig.split('');
for (let i = 0, len = tokens.length; i < len; i++) {
let token = tokens[i], pos;
switch (token[0]) {
case 'r':
sig = sig.reverse();
break;
case 'w':
pos = ~~token.slice(1);
sig = swapHeadAndPosition(sig, pos);
break;
case 's':
pos = ~~token.slice(1);
sig = sig.slice(pos);
break;
case 'p':
pos = ~~token.slice(1);
sig.splice(0, pos);
break;
}
}
return sig.join('');
};
/**
* Swaps the first element of an array with one of given position.
*
* @param {Array.<Object>} arr
* @param {number} position
* @return {Array.<Object>}
*/
const swapHeadAndPosition = (arr, position) => {
const first = arr[0];
arr[0] = arr[position % arr.length];
arr[position] = first;
return arr;
};
const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
const jsEmptyStr = `(?:''|"")`;
const reverseStr = ':function\\(a\\)\\{' +
'(?:return )?a\\.reverse\\(\\)' +
'\\}';
const sliceStr = ':function\\(a,b\\)\\{' +
'return a\\.slice\\(b\\)' +
'\\}';
const spliceStr = ':function\\(a,b\\)\\{' +
'a\\.splice\\(0,b\\)' +
'\\}';
const swapStr = ':function\\(a,b\\)\\{' +
'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
'\\}';
const actionsObjRegexp = new RegExp(
`var (${jsVarStr})=\\{((?:(?:` +
jsKeyStr + reverseStr + '|' +
jsKeyStr + sliceStr + '|' +
jsKeyStr + spliceStr + '|' +
jsKeyStr + swapStr +
'),?\\r?\\n?)+)\\};'
);
const actionsFuncRegexp = new RegExp(`function(?: ${jsVarStr})?\\(a\\)\\{` +
`a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
`((?:(?:a=)?${jsVarStr}` +
jsPropStr +
'\\(a,\\d+\\);)+)' +
`return a\\.join\\(${jsEmptyStr}\\)` +
'\\}'
);
const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
/**
* Extracts the actions that should be taken to decipher a signature.
*
* This searches for a function that performs string manipulations on
* the signature. We already know what the 3 possible changes to a signature
* are in order to decipher it. There is
*
* * Reversing the string.
* * Removing a number of characters from the beginning.
* * Swapping the first character with another position.
*
* Note, `Array#slice()` used to be used instead of `Array#splice()`,
* it's kept in case we encounter any older html5player files.
*
* After retrieving the function that does this, we can see what actions
* it takes on a signature.
*
* @param {string} body
* @return {Array.<string>}
*/
exports.extractActions = (body) => {
const objResult = actionsObjRegexp.exec(body);
const funcResult = actionsFuncRegexp.exec(body);
if (!objResult || !funcResult) { return null; }
const obj = objResult[1].replace(/\$/g, '\\$');
const objBody = objResult[2].replace(/\$/g, '\\$');
const funcBody = funcResult[1].replace(/\$/g, '\\$');
let result = reverseRegexp.exec(objBody);
const reverseKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = sliceRegexp.exec(objBody);
const sliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = spliceRegexp.exec(objBody);
const spliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = swapRegexp.exec(objBody);
const swapKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
const myreg = '(?:a=)?' + obj +
`(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
'\\(a,(\\d+)\\)';
const tokenizeRegexp = new RegExp(myreg, 'g');
const tokens = [];
while ((result = tokenizeRegexp.exec(funcBody)) !== null) {
let key = result[1] || result[2] || result[3];
switch (key) {
case swapKey:
tokens.push('w' + result[4]);
break;
case reverseKey:
tokens.push('r');
break;
case sliceKey:
tokens.push('s' + result[4]);
break;
case spliceKey:
tokens.push('p' + result[4]);
break;
}
}
return tokens;
};
/**
* @param {Object} format
* @param {string} sig
* @param {boolean} debug
*/
exports.setDownloadURL = (format, sig, debug) => {
let decodedUrl;
if (format.url) {
decodedUrl = format.url;
} else {
if (debug) {
console.warn('Download url not found for itag ' + format.itag);
}
return;
}
try {
decodedUrl = decodeURIComponent(decodedUrl);
} catch (err) {
if (debug) {
console.warn('Could not decode url: ' + err.message);
}
return;
}
// Make some adjustments to the final url.
const parsedUrl = url.parse(decodedUrl, true);
// Deleting the `search` part is necessary otherwise changes to
// `query` won't reflect when running `url.format()`
delete parsedUrl.search;
let query = parsedUrl.query;
// This is needed for a speedier download.
// See https://github.com/fent/node-ytdl-core/issues/127
query.ratebypass = 'yes';
if (sig) {
query.signature = sig;
}
format.url = url.format(parsedUrl);
};
/**
* Applies `sig.decipher()` to all format URL's.
*
* @param {Array.<Object>} formats
* @param {Array.<string>} tokens
* @param {boolean} debug
*/
exports.decipherFormats = (formats, tokens, debug) => {
formats.forEach((format) => {
const sig = tokens && format.s ? exports.decipher(tokens, format.s) : null;
exports.setDownloadURL(format, sig, debug);
});
};

414
node_modules/ytdl-core/lib/util.js generated vendored Normal file
View File

@ -0,0 +1,414 @@
const qs = require('querystring');
const url = require('url');
const FORMATS = require('./formats');
// Use these to help sort formats, higher is better.
const audioEncodingRanks = {
mp3: 1,
vorbis: 2,
aac: 3,
opus: 4,
flac: 5,
};
const videoEncodingRanks = {
'Sorenson H.283': 1,
'MPEG-4 Visual': 2,
'VP8': 3,
'VP9': 4,
'H.264': 5,
};
/**
* Sort formats from highest quality to lowest.
* By resolution, then video bitrate, then audio bitrate.
*
* @param {Object} a
* @param {Object} b
*/
exports.sortFormats = (a, b) => {
const ares = a.resolution ? parseInt(a.resolution.slice(0, -1), 10) : 0;
const bres = b.resolution ? parseInt(b.resolution.slice(0, -1), 10) : 0;
const afeats = ~~!!ares * 2 + ~~!!a.audioBitrate;
const bfeats = ~~!!bres * 2 + ~~!!b.audioBitrate;
const getBitrate = (c) => {
if (c.bitrate) {
let s = c.bitrate.split('-');
return parseFloat(s[s.length - 1], 10);
} else {
return 0;
}
};
const audioScore = (c) => {
const abitrate = c.audioBitrate || 0;
const aenc = audioEncodingRanks[c.audioEncoding] || 0;
return abitrate + aenc / 10;
};
if (afeats === bfeats) {
if (ares === bres) {
let avbitrate = getBitrate(a);
let bvbitrate = getBitrate(b);
if (avbitrate === bvbitrate) {
let aascore = audioScore(a);
let bascore = audioScore(b);
if (aascore === bascore) {
let avenc = videoEncodingRanks[a.encoding] || 0;
let bvenc = videoEncodingRanks[b.encoding] || 0;
return bvenc - avenc;
} else {
return bascore - aascore;
}
} else {
return bvbitrate - avbitrate;
}
} else {
return bres - ares;
}
} else {
return bfeats - afeats;
}
};
/**
* Choose a format depending on the given options.
*
* @param {Array.<Object>} formats
* @param {Object} options
* @return {Object|Error}
*/
exports.chooseFormat = (formats, options) => {
if (typeof options.format === 'object') {
return options.format;
}
if (options.filter) {
formats = exports.filterFormats(formats, options.filter);
if (formats.length === 0) {
return Error('No formats found with custom filter');
}
}
let format;
const quality = options.quality || 'highest';
const getBitrate = (f) => {
let s = f.bitrate.split('-');
return parseFloat(s[s.length - 1], 10);
};
switch (quality) {
case 'highest':
format = formats[0];
break;
case 'lowest':
format = formats[formats.length - 1];
break;
case 'highestaudio':
formats = exports.filterFormats(formats, 'audio');
format = null;
for (let f of formats) {
if (!format
|| f.audioBitrate > format.audioBitrate
|| (f.audioBitrate === format.audioBitrate && format.encoding && !f.encoding))
format = f;
}
break;
case 'lowestaudio':
formats = exports.filterFormats(formats, 'audio')
format = null;
for (let f of formats) {
if (!format
|| f.audioBitrate < format.audioBitrate
|| (f.audioBitrate === format.audioBitrate && format.encoding && !f.encoding))
format = f;
}
break;
case 'highestvideo':
formats = exports.filterFormats(formats, 'video');
format = null;
for (let f of formats) {
if (!format
|| getBitrate(f) > getBitrate(format)
|| (getBitrate(f) === getBitrate(format) && format.audioEncoding && !f.audioEncoding))
format = f;
}
break;
case 'lowestvideo':
formats = exports.filterFormats(formats, 'video')
format = null;
for (let f of formats) {
if (!format
|| getBitrate(f) < getBitrate(format)
|| (getBitrate(f) === getBitrate(format) && format.audioEncoding && !f.audioEncoding))
format = f;
}
break;
default: {
let getFormat = (itag) => {
return formats.find((format) => format.itag === '' + itag);
};
if (Array.isArray(quality)) {
quality.find((q) => format = getFormat(q));
} else {
format = getFormat(quality);
}
}
}
if (!format) {
return Error('No such format found: ' + quality);
}
return format;
};
/**
* @param {Array.<Object>} formats
* @param {Function} filter
* @return {Array.<Object>}
*/
exports.filterFormats = (formats, filter) => {
let fn;
switch (filter) {
case 'audioandvideo':
fn = (format) => format.bitrate && format.audioBitrate;
break;
case 'video':
fn = (format) => format.bitrate;
break;
case 'videoonly':
fn = (format) => format.bitrate && !format.audioBitrate;
break;
case 'audio':
fn = (format) => format.audioBitrate;
break;
case 'audioonly':
fn = (format) => !format.bitrate && format.audioBitrate;
break;
default:
if (typeof filter === 'function') {
fn = filter;
} else {
throw TypeError(`Given filter (${filter}) is not supported`);
}
}
return formats.filter(fn);
};
/**
* String#indexOf() that supports regex too.
*
* @param {string} haystack
* @param {string|RegExp} needle
* @return {number}
*/
const indexOf = (haystack, needle) => {
return needle instanceof RegExp ?
haystack.search(needle) : haystack.indexOf(needle);
};
/**
* Extract string inbetween another.
*
* @param {string} haystack
* @param {string} left
* @param {string} right
* @return {string}
*/
exports.between = (haystack, left, right) => {
let pos = indexOf(haystack, left);
if (pos === -1) { return ''; }
haystack = haystack.slice(pos + left.length);
pos = indexOf(haystack, right);
if (pos === -1) { return ''; }
haystack = haystack.slice(0, pos);
return haystack;
};
/**
* Get video ID.
*
* There are a few type of video URL formats.
* - https://www.youtube.com/watch?v=VIDEO_ID
* - https://m.youtube.com/watch?v=VIDEO_ID
* - https://youtu.be/VIDEO_ID
* - https://www.youtube.com/v/VIDEO_ID
* - https://www.youtube.com/embed/VIDEO_ID
* - https://music.youtube.com/watch?v=VIDEO_ID
* - https://gaming.youtube.com/watch?v=VIDEO_ID
*
* @param {string} link
* @return {string|Error}
*/
const validQueryDomains = new Set([
'youtube.com',
'www.youtube.com',
'm.youtube.com',
'music.youtube.com',
'gaming.youtube.com',
]);
const validPathDomains = new Set([
'youtu.be',
'youtube.com',
'www.youtube.com',
]);
exports.getURLVideoID = (link) => {
const parsed = url.parse(link, true);
let id = parsed.query.v;
if (validPathDomains.has(parsed.hostname) && !id) {
const paths = parsed.pathname.split('/');
id = paths[paths.length - 1];
} else if (parsed.hostname && !validQueryDomains.has(parsed.hostname)) {
return Error('Not a YouTube domain');
}
if (!id) {
return Error('No video id found: ' + link);
}
id = id.substring(0, 11);
if (!exports.validateID(id)) {
return TypeError(`Video id (${id}) does not match expected ` +
`format (${idRegex.toString()})`);
}
return id;
};
/**
* Gets video ID either from a url or by checking if the given string
* matches the video ID format.
*
* @param {string} str
* @return {string|Error}
*/
exports.getVideoID = (str) => {
if (exports.validateID(str)) {
return str;
} else {
return exports.getURLVideoID(str);
}
};
/**
* Returns true if given id satifies YouTube's id format.
*
* @param {string} id
* @return {boolean}
*/
const idRegex = /^[a-zA-Z0-9-_]{11}$/;
exports.validateID = (id) => {
return idRegex.test(id);
};
/**
* Checks wether the input string includes a valid id.
*
* @param {string} string
* @return {boolean}
*/
exports.validateURL = (string) => {
return !(exports.getURLVideoID(string) instanceof Error);
};
/**
* @param {Object} info
* @return {Array.<Object>}
*/
exports.parseFormats = (info) => {
let formats = [];
if (info.url_encoded_fmt_stream_map) {
formats = formats
.concat(info.url_encoded_fmt_stream_map.split(','));
}
if (info.adaptive_fmts) {
formats = formats.concat(info.adaptive_fmts.split(','));
}
formats = formats.map((format) => qs.parse(format));
delete info.url_encoded_fmt_stream_map;
delete info.adaptive_fmts;
return formats;
};
/**
* @param {Object} format
*/
exports.addFormatMeta = (format) => {
const meta = FORMATS[format.itag];
for (let key in meta) {
format[key] = meta[key];
}
format.live = /\/source\/yt_live_broadcast\//.test(format.url);
format.isHLS = /\/manifest\/hls_(variant|playlist)\//.test(format.url);
format.isDashMPD = /\/manifest\/dash\//.test(format.url);
};
/**
* Get only the string from an HTML string.
*
* @param {string} html
* @return {string}
*/
exports.stripHTML = (html) => {
return html
.replace(/\n/g, ' ')
.replace(/\s*<\s*br\s*\/?\s*>\s*/gi, '\n')
.replace(/<\s*\/\s*p\s*>\s*<\s*p[^>]*>/gi, '\n')
.replace(/<.*?>/gi, '')
.trim();
};
/**
* @param {Array.<Function>} funcs
* @param {Function(!Error, Array.<Object>)} callback
*/
exports.parallel = (funcs, callback) => {
let funcsDone = 0;
let errGiven = false;
let results = [];
const len = funcs.length;
const checkDone = (index, err, result) => {
if (errGiven) { return; }
if (err) {
errGiven = true;
callback(err);
return;
}
results[index] = result;
if (++funcsDone === len) {
callback(null, results);
}
};
if (len > 0) {
funcs.forEach((f, i) => { f(checkDone.bind(null, i)); });
} else {
callback(null, results);
}
};

99
node_modules/ytdl-core/package.json generated vendored Normal file
View File

@ -0,0 +1,99 @@
{
"_from": "ytdl-core@^0.29.1",
"_id": "ytdl-core@0.29.1",
"_inBundle": false,
"_integrity": "sha512-J/TW3rupqE6y+TczHTTFjuwf23tweA9fasmnbU/5yYH0O2IRFLcwgHbbvEPt3t8mtXWGX207+TWH7uuJpB1FKQ==",
"_location": "/ytdl-core",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ytdl-core@^0.29.1",
"name": "ytdl-core",
"escapedName": "ytdl-core",
"rawSpec": "^0.29.1",
"saveSpec": null,
"fetchSpec": "^0.29.1"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-0.29.1.tgz",
"_shasum": "2d7a1505d47a34a02551639a8d4092af959c4902",
"_spec": "ytdl-core@^0.29.1",
"_where": "C:\\Users\\matia\\Bot Files",
"author": {
"name": "fent",
"url": "https://github.com/fent"
},
"bugs": {
"url": "https://github.com/fent/node-ytdl-core/issues"
},
"bundleDependencies": false,
"contributors": [
{
"name": "Tobias Kutscha",
"url": "https://github.com/TimeForANinja"
},
{
"name": "Andrew Kelley",
"url": "https://github.com/andrewrk"
},
{
"name": "Mauricio Allende",
"url": "https://github.com/mallendeo"
},
{
"name": "Rodrigo Altamirano",
"url": "https://github.com/raltamirano"
},
{
"name": "Jim Buck",
"url": "https://github.com/JimmyBoh"
}
],
"dependencies": {
"html-entities": "^1.1.3",
"m3u8stream": "^0.6.2",
"miniget": "^1.4.0",
"sax": "^1.1.3"
},
"deprecated": false,
"description": "Youtube video downloader in pure javascript.",
"devDependencies": {
"@types/node": "^10.0.3",
"assert-diff": "^2.0.0",
"istanbul": "^0.4.5",
"mocha": "^5.0.0",
"muk-prop": "^2.0.0",
"nock": "^10.0.0",
"sinon": "^6.0.0",
"stream-equal": "~1.1.0"
},
"engines": {
"node": ">=6"
},
"files": [
"lib",
"typings"
],
"homepage": "https://github.com/fent/node-ytdl-core#readme",
"keywords": [
"youtube",
"video",
"download"
],
"license": "MIT",
"main": "./lib/index.js",
"name": "ytdl-core",
"repository": {
"type": "git",
"url": "git://github.com/fent/node-ytdl-core.git"
},
"scripts": {
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -t 16000 test/*-test.js"
},
"types": "./typings/index.d.ts",
"version": "0.29.1"
}

257
node_modules/ytdl-core/typings/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,257 @@
declare module 'ytdl-core' {
import { ClientRequest } from 'http';
import { Readable } from 'stream';
namespace ytdl {
type downloadOptions = {
quality?: 'lowest' | 'highest' | 'highestaudio' | 'lowestaudio' | 'highestvideo' | 'lowestvideo' | string | number;
filter?: 'video' | 'videoonly' | 'audio' | 'audioonly' | ((format: videoFormat) => boolean);
format?: videoFormat;
range?: {
start?: number;
end?: number;
};
begin?: string | number | Date;
liveBuffer?: number;
requestOptions?: {};
highWaterMark?: number;
lang?: string;
}
type videoFormat = {
s?: string;
sig?: string;
xtags?: string;
clen?: string;
size?: string;
projection_type?: string;
lmt?: string;
init?: string;
fps?: string;
index?: string;
type?: string;
quality?: 'hd720' | 'medium' | 'small' | string;
quality_label?: '144p' | '240p' | '270p' | '360p' | '480p' | '720p' | '1080p' | '1440p' | '2160p' | '4320p';
url: string;
itag: string;
container: 'flv' | '3gp' | 'mp4' | 'webm' | 'ts';
resolution: '144p' | '240p' | '270p' | '360p' | '480p' | '720p' | '1080p' | '1440p' | '2160p' | '4320p';
encoding: 'Sorenson H.283' | 'MPEG-4 Visual' | 'VP8' | 'VP9' | 'H.264';
profile: '3d' | 'high' | 'main' | 'simple' | 'baseline' | 'Main@L3.1';
bitrate: string;
audioEncoding: 'mp3' | 'vorbis' | 'aac' | 'opus' | 'flac';
audioBitrate: number;
live: boolean;
isHLS: boolean;
isDashMPD: boolean;
}
type videoInfo = {
iv_load_policy?: string;
iv_allow_in_place_switch?: string;
iv_endscreen_url?: string;
iv_invideo_url?: string;
iv3_module?: string;
rmktEnabled?: string;
uid?: string;
vid?: string;
focEnabled?: string;
baseUrl?: string;
storyboard_spec?: string;
serialized_ad_ux_config?: string;
player_error_log_fraction?: string;
sffb?: string;
ldpj?: string;
videostats_playback_base_url?: string;
innertube_context_client_version?: string;
t?: string;
fade_in_start_milliseconds: string;
timestamp: string;
ad3_module: string;
relative_loudness: string;
allow_below_the_player_companion: string;
eventid: string;
token: string;
atc: string;
title: string;
cr: string;
apply_fade_on_midrolls: string;
cl: string;
fexp: string[];
apiary_host: string;
fade_in_duration_milliseconds: string;
fflags: string;
ssl: string;
pltype: string;
media: {
image?: string;
category: string;
category_url: string;
game?: string;
game_url?: string;
year?: number;
song?: string;
artist?: string;
artist_url?: string;
writers?: string;
licensed_by?: string;
},
author: {
id: string;
name: string;
avatar: string;
verified: boolean;
user: string;
channel_url: string;
user_url: string;
};
enabled_engage_types: string;
hl: string;
is_listed: string;
gut_tag: string;
apiary_host_firstparty: string;
enablecsi: string;
csn: string;
status: string;
afv_ad_tag: string;
idpj: string;
sfw_player_response: string;
account_playback_token: string;
encoded_ad_safety_reason: string;
tag_for_children_directed: string;
no_get_video_log: string;
ppv_remarketing_url: string;
fmt_list: string[][];
ad_slots: string;
fade_out_duration_milliseconds: string;
instream_long: string;
allow_html5_ads: string;
core_dbp: string;
ad_device: string;
itct: string;
root_ve_type: string;
excluded_ads: string;
aftv: string;
loeid: string;
cver: string;
shortform: string;
dclk: string;
csi_page_type: string;
ismb: string;
gpt_migration: string;
loudness: string;
ad_tag: string;
of: string;
probe_url: string;
vm: string;
afv_ad_tag_restricted_to_instream: string;
gapi_hint_params: string;
cid: string;
c: string;
oid: string;
ptchn: string;
as_launched_in_country: string;
avg_rating: string;
fade_out_start_milliseconds: string;
length_seconds: string;
midroll_prefetch_size: string;
allow_ratings: string;
thumbnail_url: string;
iurlsd: string;
iurlmq: string;
iurlhq: string;
iurlmaxres: string;
ad_preroll: string;
tmi: string;
trueview: string;
host_language: string;
innertube_api_key: string;
show_content_thumbnail: string;
afv_instream_max: string;
innertube_api_version: string;
mpvid: string;
allow_embed: string;
ucid: string;
plid: string;
midroll_freqcap: string;
ad_logging_flag: string;
ptk: string;
vmap: string;
watermark: string[];
video_id: string;
dbp: string;
ad_flags: string;
html5player: string;
formats: videoFormat[];
published: number;
description: string;
related_videos: relatedVideo[];
video_url: string;
no_embed_allowed?: boolean;
age_restricted: boolean;
player_response: {
playabilityStatus: {
status: string;
};
streamingData: {
expiresInSeconds: string;
formats: {}[];
adaptiveFormats: {}[];
};
videoDetails: {
videoId: string;
title: string;
lengthSeconds: number;
keywords: string[];
channelId: string;
isCrawlable: boolean;
thumbnail: {
thumbnails: {
url: string;
width: number;
height: number
}[];
};
viewCount: number;
author: string;
isLiveContent: boolean;
}
};
}
type relatedVideo = {
id?: string;
title?: string;
author?: string;
length_seconds?: string;
iurlmq?: string;
short_view_count_text?: string;
session_data: string;
endscreen_autoplay_session_data?: string;
iurlhq?: string;
playlist_iurlhq?: string;
playlist_title?: string;
playlist_length?: string;
playlist_iurlmq?: string;
video_id?: string;
list?: string;
thumbnail_ids?: string;
}
function getBasicInfo(url: string, callback?: (err: Error, info: videoInfo) => void): Promise<videoInfo>;
function getBasicInfo(url: string, options?: downloadOptions, callback?: (err: Error, info: videoInfo) => void): Promise<videoInfo>;
function getInfo(url: string, callback?: (err: Error, info: videoInfo) => void): Promise<videoInfo>;
function getInfo(url: string, options?: downloadOptions, callback?: (err: Error, info: videoInfo) => void): Promise<videoInfo>;
function downloadFromInfo(info: videoInfo, options?: downloadOptions): Readable;
function chooseFormat(format: videoFormat | videoFormat[], options?: downloadOptions): videoFormat | Error;
function filterFormats(formats: videoFormat | videoFormat[], filter?: 'video' | 'videoonly' | 'audio' | 'audioonly' | ((format: videoFormat) => boolean)): videoFormat[];
function validateID(string: string): boolean;
function validateURL(string: string): boolean;
function getURLVideoID(string: string): string | Error;
function getVideoID(string: string): string | Error;
}
function ytdl(link: string, options?: ytdl.downloadOptions): Readable;
export = ytdl;
}