Musique/node_modules/ytdl-core/lib/format-utils.js
2021-12-04 20:00:33 +01:00

250 lines
6.7 KiB
JavaScript

const utils = require('./utils');
const FORMATS = require('./formats');
// Use these to help sort formats, higher index is better.
const audioEncodingRanks = [
'mp4a',
'mp3',
'vorbis',
'aac',
'opus',
'flac',
];
const videoEncodingRanks = [
'mp4v',
'avc1',
'Sorenson H.283',
'MPEG-4 Visual',
'VP8',
'VP9',
'H.264',
];
const getVideoBitrate = format => format.bitrate || 0;
const getVideoEncodingRank = format =>
videoEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
const getAudioBitrate = format => format.audioBitrate || 0;
const getAudioEncodingRank = format =>
audioEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
/**
* Sort formats by a list of functions.
*
* @param {Object} a
* @param {Object} b
* @param {Array.<Function>} sortBy
* @returns {number}
*/
const sortFormatsBy = (a, b, sortBy) => {
let res = 0;
for (let fn of sortBy) {
res = fn(b) - fn(a);
if (res !== 0) {
break;
}
}
return res;
};
const sortFormatsByVideo = (a, b) => sortFormatsBy(a, b, [
format => parseInt(format.qualityLabel),
getVideoBitrate,
getVideoEncodingRank,
]);
const sortFormatsByAudio = (a, b) => sortFormatsBy(a, b, [
getAudioBitrate,
getAudioEncodingRank,
]);
/**
* Sort formats from highest quality to lowest.
*
* @param {Object} a
* @param {Object} b
* @returns {number}
*/
exports.sortFormats = (a, b) => sortFormatsBy(a, b, [
// Formats with both video and audio are ranked highest.
format => +!!format.isHLS,
format => +!!format.isDashMPD,
format => +(format.contentLength > 0),
format => +(format.hasVideo && format.hasAudio),
format => +format.hasVideo,
format => parseInt(format.qualityLabel) || 0,
getVideoBitrate,
getAudioBitrate,
getVideoEncodingRank,
getAudioEncodingRank,
]);
/**
* Choose a format depending on the given options.
*
* @param {Array.<Object>} formats
* @param {Object} options
* @returns {Object}
* @throws {Error} when no format matches the filter/format rules
*/
exports.chooseFormat = (formats, options) => {
if (typeof options.format === 'object') {
if (!options.format.url) {
throw Error('Invalid format given, did you use `ytdl.getInfo()`?');
}
return options.format;
}
if (options.filter) {
formats = exports.filterFormats(formats, options.filter);
}
// We currently only support HLS-Formats for livestreams
// So we (now) remove all non-HLS streams
if (formats.some(fmt => fmt.isHLS)) {
formats = formats.filter(fmt => fmt.isHLS || !fmt.isLive);
}
let format;
const quality = options.quality || 'highest';
switch (quality) {
case 'highest':
format = formats[0];
break;
case 'lowest':
format = formats[formats.length - 1];
break;
case 'highestaudio': {
formats = exports.filterFormats(formats, 'audio');
formats.sort(sortFormatsByAudio);
// Filter for only the best audio format
const bestAudioFormat = formats[0];
formats = formats.filter(f => sortFormatsByAudio(bestAudioFormat, f) === 0);
// Check for the worst video quality for the best audio quality and pick according
// This does not loose default sorting of video encoding and bitrate
const worstVideoQuality = formats.map(f => parseInt(f.qualityLabel) || 0).sort((a, b) => a - b)[0];
format = formats.find(f => (parseInt(f.qualityLabel) || 0) === worstVideoQuality);
break;
}
case 'lowestaudio':
formats = exports.filterFormats(formats, 'audio');
formats.sort(sortFormatsByAudio);
format = formats[formats.length - 1];
break;
case 'highestvideo': {
formats = exports.filterFormats(formats, 'video');
formats.sort(sortFormatsByVideo);
// Filter for only the best video format
const bestVideoFormat = formats[0];
formats = formats.filter(f => sortFormatsByVideo(bestVideoFormat, f) === 0);
// Check for the worst audio quality for the best video quality and pick according
// This does not loose default sorting of audio encoding and bitrate
const worstAudioQuality = formats.map(f => f.audioBitrate || 0).sort((a, b) => a - b)[0];
format = formats.find(f => (f.audioBitrate || 0) === worstAudioQuality);
break;
}
case 'lowestvideo':
formats = exports.filterFormats(formats, 'video');
formats.sort(sortFormatsByVideo);
format = formats[formats.length - 1];
break;
default:
format = getFormatByQuality(quality, formats);
break;
}
if (!format) {
throw Error(`No such format found: ${quality}`);
}
return format;
};
/**
* Gets a format based on quality or array of quality's
*
* @param {string|[string]} quality
* @param {[Object]} formats
* @returns {Object}
*/
const getFormatByQuality = (quality, formats) => {
let getFormat = itag => formats.find(format => `${format.itag}` === `${itag}`);
if (Array.isArray(quality)) {
return getFormat(quality.find(q => getFormat(q)));
} else {
return getFormat(quality);
}
};
/**
* @param {Array.<Object>} formats
* @param {Function} filter
* @returns {Array.<Object>}
*/
exports.filterFormats = (formats, filter) => {
let fn;
switch (filter) {
case 'videoandaudio':
case 'audioandvideo':
fn = format => format.hasVideo && format.hasAudio;
break;
case 'video':
fn = format => format.hasVideo;
break;
case 'videoonly':
fn = format => format.hasVideo && !format.hasAudio;
break;
case 'audio':
fn = format => format.hasAudio;
break;
case 'audioonly':
fn = format => !format.hasVideo && format.hasAudio;
break;
default:
if (typeof filter === 'function') {
fn = filter;
} else {
throw TypeError(`Given filter (${filter}) is not supported`);
}
}
return formats.filter(format => !!format.url && fn(format));
};
/**
* @param {Object} format
* @returns {Object}
*/
exports.addFormatMeta = format => {
format = Object.assign({}, FORMATS[format.itag], format);
format.hasVideo = !!format.qualityLabel;
format.hasAudio = !!format.audioBitrate;
format.container = format.mimeType ?
format.mimeType.split(';')[0].split('/')[1] : null;
format.codecs = format.mimeType ?
utils.between(format.mimeType, 'codecs="', '"') : null;
format.videoCodec = format.hasVideo && format.codecs ?
format.codecs.split(', ')[0] : null;
format.audioCodec = format.hasAudio && format.codecs ?
format.codecs.split(', ').slice(-1)[0] : null;
format.isLive = /\bsource[/=]yt_live_broadcast\b/.test(format.url);
format.isHLS = /\/manifest\/hls_(variant|playlist)\//.test(format.url);
format.isDashMPD = /\/manifest\/dash\//.test(format.url);
return format;
};