yt-stream
Version:
Create easily readable streams from YouTube video url's
147 lines (136 loc) • 6.25 kB
JavaScript
const validate = require('../validate.js').validateVideoURL;
const getInfo = require('../info.js').getInfo;
const Stream = require('../classes/stream.js');
const Data = require('../classes/ytdata.js');
const cipher = require('./decipher.js');
function parseAudio(formats){
const audio = [];
var audioFormats = formats.filter(f => f.mimeType.startsWith('audio'));
for(var i = 0; i < audioFormats.length; i++){
var format = audioFormats[i];
const type = format.mimeType;
if(type.startsWith('audio')){
format.codec = type.split('codecs=')[1].split('"')[0];
format.container = type.split('audio/')[1].split(';')[0];
audio.push(format);
}
}
return audio;
}
function parseVideo(formats){
const video = [];
var videoFormats = formats.filter(f => f.mimeType.startsWith('video'));
for(var i = 0; i < videoFormats.length; i++){
var format = videoFormats[i];
const type = format.mimeType;
if(type.startsWith('video')){
format.codec = type.split('codecs=')[1].split('"')[0];
format.container = type.split('video/')[1].split(';')[0];
video.push(format);
}
}
return video;
}
function getStreamURL(info, options){
return new Promise((resolve, reject) => {
const formats = info.formats;
var selectedFormat = null;
var _options = options || {};
var vid = _options['type'] === 'video' ? parseVideo(formats) : parseAudio(formats);
if(vid.length === 0) vid = _options['type'] === 'video' ? parseAudio(formats) : parseVideo(formats);
if(vid.length === 0) return reject(`There were no playable formats found`);
_options['quality'] = typeof _options['quality'] === 'string' ? _options['quality'].toLowerCase() : _options['quality'];
if(typeof _options['quality'] !== 'number'){
for(var i = 0; i < vid.length; i++){
let format = vid[i];
if(!selectedFormat){
selectedFormat = format;
} else {
if(_options['quality'] === 'high'){
if(format.bitrate > selectedFormat.bitrate){
selectedFormat = format;
}
} else {
if(format.bitrate < selectedFormat.bitrate){
selectedFormat = format;
}
}
}
}
} else {
if(_options['quality'] > vid.length) _options['quality'] = vid.length - 1;
else if(_options['quality'] < vid.length) _options['quality'] = 0;
selectedFormat = vid[_options['quality']];
}
var { url } = selectedFormat;
let type = selectedFormat.codec === 'opus' && selectedFormat.container === 'webm' ? 'webm/opus' : 'arbitrary';
resolve({
url: url,
contentLength: selectedFormat.contentLength,
type: type,
quality: typeof _options['quality'] === 'string' ? (_options['quality'] === 'high' ? vid.length - 1 : 0) : (_options['quality'] || 0),
req_type: (_options['type'] || 'audio'),
container: selectedFormat.container,
format: selectedFormat
});
});
}
function stream(ytstream, info, options){
if(typeof info !== 'object' && typeof info !== 'string') throw new Error(`Info is a required parameter and must be an object or a string`);
var _options = typeof options === 'object' ? options : {};
var _info = info;
return new Promise(async (resolve, reject) => {
var stream_res;
if(typeof info === 'string'){
if(!validate(ytstream, info)) return reject(`URL is not a valid YouTube URL`);
try{
_info = await getInfo(ytstream, info);
if(typeof _info.clientInfo === 'object' && _info.clientInfo !== null && _info.formats.filter(f => Object.keys(f).indexOf('url') < 0).length === 0) _info.formats.map(f => f.url += "&cver="+_info.clientInfo.context.client.clientVersion)
else {
const _ci = await cipher.format_decipher(_info.formats, _info.cver, _info.html5player, ytstream.agent);
_info.formats = _ci;
}
stream_res = await getStreamURL(_info, _options);
} catch (err) {
return reject(err);
}
} else if(info instanceof Data){
try{
if(typeof _info.clientInfo === 'object' && _info.clientInfo !== null) _info.formats.map(f => f.url += "&cver="+_info.clientInfo.context.client.clientVersion)
else {
const _ci = await cipher.format_decipher(_info.formats, _info.cver, _info.html5player, ytstream.agent);
_info.formats = _ci;
}
stream_res = await getStreamURL(_info, _options);
} catch (err) {
return reject(err);
}
} else return reject(`Invalid info has been parsed to the stream function`);
const stream = new Stream(ytstream, stream_res.url, {
highWaterMark: _options['highWaterMark'] || undefined,
duration: _info.duration,
contentLength: stream_res.contentLength,
type: stream_res.type,
quality: stream_res.quality,
video_url: _info.url,
req_type: stream_res.req_type,
container: stream_res.container,
download: typeof _options['download'] === 'boolean' ? _options['download'] : true,
format: stream_res.format,
ytstream: ytstream
}, _info);
if(stream.ready === true){
resolve(stream);
} else {
stream.once('ready', () => {
resolve(stream);
});
stream.once('error', err => {
reject(err);
});
}
});
}
module.exports = {
stream: stream
};