Skip to content

Commit

Permalink
feat(info): add ytdl.getBasicInfo()
Browse files Browse the repository at this point in the history
  • Loading branch information
fent committed Aug 3, 2018
1 parent 20d07ae commit ec5b1d5
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 55 deletions.
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -63,10 +63,14 @@ Emitted when the video response has been found and has started downloading or af

Emitted whenever a new chunk is received. Passes values descriping the download progress and the parsed percentage.

### ytdl.getInfo(url, [options], [callback(err, info)])
### 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.
Expand Down
50 changes: 7 additions & 43 deletions lib/index.js
Expand Up @@ -27,6 +27,8 @@ const ytdl = module.exports = function ytdl(link, options) {
return stream;
};

ytdl.getBasicInfo = getInfo.getBasicInfo;
ytdl.getInfo = getInfo.getFullInfo;
ytdl.chooseFormat = util.chooseFormat;
ytdl.filterFormats = util.filterFormats;
ytdl.validateID = util.validateID;
Expand All @@ -37,49 +39,7 @@ ytdl.getURLVideoID = util.getURLVideoID;
ytdl.getVideoID = util.getVideoID;
ytdl.cache = {
sig: sig.cache,
info: new Map(),
};


/**
* Cached version of `getInfo()`
* In case a user wants to get a video's info before downloading.
*
* @param {String} link
* @param {Object} options
* @param {Function(Error, Object)} callback
*/
ytdl.getInfo = (link, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = {};
} else if (!options) {
options = {};
}

if (!callback) {
return new Promise((resolve, reject) => {
ytdl.getInfo(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 = [id, options.lang].join('-');
if (ytdl.cache.info.has(key)) {
callback(null, ytdl.cache.info.get(key));
} else {
getInfo(id, options, (err, info) => {
if (err) return callback(err);
ytdl.cache.info.set(key, info);
setTimeout(() => { ytdl.cache.info.delete(key); }, 1000);
callback(null, info);
});
}
info: getInfo.cache,
};


Expand Down Expand Up @@ -236,6 +196,10 @@ function doDownload(stream, url, options, reconnectInfo) {
*/
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);
});
Expand Down
83 changes: 75 additions & 8 deletions lib/info.js
Expand Up @@ -21,13 +21,13 @@ const KEYS_TO_SPLIT = [


/**
* Gets info from a video.
* Gets info from a video without getting additional formats.
*
* @param {String} id
* @param {Object} options
* @param {Function(Error, Object)} callback
*/
module.exports = function getInfo(id, options, 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;
Expand Down Expand Up @@ -158,10 +158,29 @@ function gotConfig(id, options, additional, config, fromEmbed, callback) {
// Add additional properties to info.
Object.assign(info, additional);
info.age_restricted = fromEmbed;
info.html5player = config.assets.js;
if (config.args.dashmpd && info.dashmpd !== config.args.dashmpd) {
info.dashmpd2 = config.args.dashmpd;
}

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);
if (info.formats.length ||
config.args.dashmpd || info.dashmpd || info.hlsvp) {
const html5playerfile = urllib.resolve(VIDEO_URL, config.assets.js);
info.dashmpd || info.dashmpd2 || info.hlsvp) {
const html5playerfile = urllib.resolve(VIDEO_URL, info.html5player);
sig.getTokens(html5playerfile, options, (err, tokens) => {
if (err) return callback(err);

Expand All @@ -174,8 +193,8 @@ function gotConfig(id, options, additional, config, fromEmbed, callback) {
funcs.push(getDashManifest.bind(null, info.dashmpd, options));
}

if (config.args.dashmpd && info.dashmpd !== config.args.dashmpd) {
info.dashmpd2 = decipherURL(config.args.dashmpd, tokens);
if (info.dashmpd2) {
info.dashmpd2 = decipherURL(info.dashmpd2, tokens);
funcs.push(getDashManifest.bind(null, info.dashmpd2, options));
}

Expand Down Expand Up @@ -205,15 +224,15 @@ function gotConfig(id, options, additional, config, fromEmbed, callback) {

info.formats.forEach(util.addFormatMeta);
info.formats.sort(util.sortFormats);
info.full = true;
callback(null, info);

});
});
} else {
callback(new Error('This video is unavailable'));
}
});
}
};


/**
Expand Down Expand Up @@ -307,3 +326,51 @@ function getM3U8(url, options, callback) {
callback(null, formats);
});
}


// Cached for getting basic/full info.
exports.cache = new Map();


// 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); }, 1000);
callback(null, info);
});
}
};
}
2 changes: 1 addition & 1 deletion test/files/videos/pJk0p-98Xzc-vevo/expected_info.json

Large diffs are not rendered by default.

61 changes: 59 additions & 2 deletions test/info-test.js
Expand Up @@ -3,7 +3,7 @@ const path = require('path');
const fs = require('fs');
const assert = require('assert-diff');
const nock = require('./nock');
const sinon = require('sinon');
const sinon = require('sinon');
const muk = require('muk-prop');


Expand Down Expand Up @@ -42,6 +42,63 @@ describe('ytdl.getInfo()', () => {
});
});

describe('Use ytdl.getBasicInfo()', () => {
it('Retrieves just enough metainfo', (done) => {
const scope = nock(id, {
type: 'vevo',
dashmpd: false,
get_video_info: true,
});

ytdl.getBasicInfo(id, (err, info) => {
assert.ifError(err);
scope.done();
assert.equal(info.formats.length, expectedInfo.formats.length - 1);
done();
});
});

describe('Followed by ytdl.getInfo()', () => {
it('Does not make extra requests', (done) => {
const scope = nock(id, {
type: 'vevo',
dashmpd: true,
get_video_info: true,
player: 'player-en_US-vflV3n15C',
});

ytdl.getBasicInfo(id, (err, info) => {
assert.ifError(err);
assert.equal(info.formats.length, expectedInfo.formats.length - 1);
ytdl.getInfo(id, (err, info) => {
scope.done();
assert.equal(info.formats.length, expectedInfo.formats.length);
done();
});
});
});
});

describe('Use `ytdl.downloadFromInfo()`', () => {
it('Throw error', (done) => {
const scope = nock(id, {
type: 'vevo',
dashmpd: false,
get_video_info: true,
});

ytdl.getBasicInfo(id, (err, info) => {
assert.ifError(err);
scope.done();
assert.throws(() => {
ytdl.downloadFromInfo(info);
}, /Cannot use/);
done();
});
});
});
});

describe('Use `ytdl.downloadFromInfo()`', () => {
it('Retrives video file', (done) => {
const stream = ytdl.downloadFromInfo(expectedInfo);
Expand Down Expand Up @@ -116,7 +173,7 @@ describe('ytdl.getInfo()', () => {
describe('Called twice', () => {
const id = 'pJk0p-98Xzc';

it('Only makes requests once', (done) => {
it('Makes requests once', (done) => {
const scope = nock(id, {
type: 'vevo',
dashmpd: true,
Expand Down
3 changes: 3 additions & 0 deletions typings/index.d.ts
Expand Up @@ -177,6 +177,7 @@ declare module 'ytdl-core' {
video_id: string;
dbp: string;
ad_flags: string;
html5player: string;
dashmpd?: string;
dashmpd2?: string;
hlsvp?: string;
Expand Down Expand Up @@ -208,6 +209,8 @@ declare module 'ytdl-core' {
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;
Expand Down

0 comments on commit ec5b1d5

Please sign in to comment.