Haha-Yes/node_modules/ytdl-core/lib/index.js
2018-09-09 21:20:36 +02:00

153 lines
4.2 KiB
JavaScript

const PassThrough = require('stream').PassThrough;
const deprecate = require('util').deprecate;
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 = module.exports = function 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;
};
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.validateLink = deprecate(util.validateURL,
'ytdl.validateLink: Renamed to ytdl.validateURL');
ytdl.getURLVideoID = util.getURLVideoID;
ytdl.getVideoID = util.getVideoID;
ytdl.cache = {
sig: sig.cache,
info: getInfo.cache,
};
function 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
*/
function 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 url = format.url;
if (format.live) {
let req = m3u8stream(url, {
chunkReadahead: +info.live_chunk_readahead,
begin: options.begin || Date.now(),
liveBuffer: options.liveBuffer,
requestOptions: options.requestOptions,
parser: /\/manifest\/dash\//.test(format.url) ? 'dash-mpd' : 'm3u8',
id: format.itag,
});
req.on('error', stream.emit.bind(stream, 'error'));
stream.destroy = req.end.bind(req);
req.pipe(stream);
} else {
if (options.begin) {
url += '&begin=' + parseTime(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 || ''}`
});
}
const req = request(url, requestOptions);
const ondata = (chunk) => {
downloaded += chunk.length;
stream.emit('progress', chunk.length, downloaded, contentLength);
};
stream.destroy = () => {
stream._isDestroyed = true;
req.abort();
req.removeListener('data', ondata);
req.unpipe();
};
// Forward events from the request to the stream.
[
'abort', 'request', 'response', 'error', 'retry', 'reconnect'
].forEach((event) => {
req.on(event, (arg) => { stream.emit(event, arg); });
});
let contentLength, downloaded = 0;
req.on('response', (res) => {
if (stream._isDestroyed) { return; }
if (!contentLength) {
contentLength = parseInt(res.headers['content-length'], 10);
}
});
req.on('data', ondata);
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;
};