YT: try harder to find suitable DASH streams

Instead of searching for 1080p only, accept also other H.264 formats for DASH streaming
This commit is contained in:
Rafał Dzięgiel
2021-04-12 17:41:42 +02:00
parent ab32b2dbbc
commit 901fc8d760
2 changed files with 90 additions and 43 deletions

View File

@@ -37,7 +37,9 @@ function generateDash(dashInfo)
function _addAdaptationSet(streamsArr)
{
const mimeInfo = _getMimeInfo(streamsArr[0].mimeType);
/* We just need it for adaptation type,
* so any stream will do */
const { mimeInfo } = streamsArr[0];
const adaptArr = [
`contentType="${mimeInfo.content}"`,
@@ -93,11 +95,9 @@ function _addAdaptationSet(streamsArr)
function _getStreamRepresentation(stream)
{
const mimeInfo = _getMimeInfo(stream.mimeType);
const repOptsArr = [
`id="${stream.itag}"`,
`codecs="${mimeInfo.codecs}"`,
`codecs="${stream.mimeInfo.codecs}"`,
`bandwidth="${stream.bitrate}"`,
];
@@ -120,13 +120,8 @@ function _getStreamRepresentation(stream)
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
}
const encodedURL = Misc.encodeHTML(stream.url)
.replace('?', '/')
.replace(/&amp;/g, '/')
.replace(/=/g, '/');
repArr.push(
` <BaseURL>${encodedURL}</BaseURL>`
` <BaseURL>${stream.url}</BaseURL>`
);
if(stream.indexRange) {
@@ -152,22 +147,6 @@ function _getStreamRepresentation(stream)
return repArr.join('\n');
}
function _getMimeInfo(mimeType)
{
const mimeArr = mimeType.split(';');
let codecs = mimeArr.find(info => info.includes('codecs')).split('=')[1];
codecs = codecs.substring(1, codecs.length - 1);
const mimeInfo = {
content: mimeArr[0].split('/')[0],
type: mimeArr[0],
codecs,
};
return mimeInfo;
}
function _getPar(width, height)
{
const gcd = _getGCD(width, height);

View File

@@ -357,27 +357,75 @@ var YouTubeClient = GObject.registerClass({
)
return null;
/* TODO: Options in prefs to set preferred video formats for adaptive streaming */
const videoStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('video/mp4') && stream.quality === 'hd1080');
});
const audioStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('audio/mp4'));
});
/* TODO: Options in prefs to set preferred video formats and adaptive streaming */
const isAdaptiveEnabled = false;
const allowedFormats = {
video: [
133,
134,
135,
136,
137,
298,
299,
],
audio: [
140,
]
};
if(!videoStream || !audioStream)
return null;
const filteredStreams = {
video: [],
audio: [],
};
for(let fmt of ['video', 'audio']) {
debug(`filtering ${fmt} streams`);
let index = allowedFormats[fmt].length;
while(index--) {
const itag = allowedFormats[fmt][index];
const foundStream = info.streamingData.adaptiveFormats.find(stream => (stream.itag == itag));
if(foundStream) {
/* Parse and convert mimeType string into object */
foundStream.mimeInfo = this._getMimeInfo(foundStream.mimeType);
/* Sanity check */
if(!foundStream.mimeInfo || foundStream.mimeInfo.content !== fmt) {
debug(new Error(`mimeType parsing failed on stream: ${itag}`));
continue;
}
/* Sort from worst to best */
filteredStreams[fmt].unshift(foundStream);
debug(`added ${fmt} itag: ${foundStream.itag}`);
if(!isAdaptiveEnabled)
break;
}
}
if(!filteredStreams[fmt].length) {
debug(`dash info ${fmt} streams list is empty`);
return null;
}
}
debug('following redirects');
for(let stream of [videoStream, audioStream]) {
debug(`initial URL: ${stream.url}`);
for(let fmtArr of Object.values(filteredStreams)) {
for(let stream of fmtArr) {
debug(`initial URL: ${stream.url}`);
const result = await this._downloadDataPromise(stream.url, 'HEAD').catch(debug);
if(!result) return null;
const result = await this._downloadDataPromise(stream.url, 'HEAD').catch(debug);
if(!result) return null;
stream.url = result.uri;
debug(`resolved URL: ${stream.url}`);
stream.url = Misc.encodeHTML(result.uri)
.replace('?', '/')
.replace(/&amp;/g, '/')
.replace(/=/g, '/');
debug(`resolved URL: ${stream.url}`);
}
}
debug('all redirects resolved');
@@ -385,8 +433,8 @@ var YouTubeClient = GObject.registerClass({
return {
duration: info.videoDetails.lengthSeconds,
adaptations: [
[videoStream],
[audioStream],
filteredStreams.video,
filteredStreams.audio,
]
};
}
@@ -527,6 +575,26 @@ var YouTubeClient = GObject.registerClass({
return reduced;
}
_getMimeInfo(mimeType)
{
debug(`parsing mimeType: ${mimeType}`);
const mimeArr = mimeType.split(';');
let codecs = mimeArr.find(info => info.includes('codecs')).split('=')[1];
codecs = codecs.substring(1, codecs.length - 1);
const mimeInfo = {
content: mimeArr[0].split('/')[0],
type: mimeArr[0],
codecs,
};
debug(`parsed mimeType: ${JSON.stringify(mimeInfo)}`);
return mimeInfo;
}
_getPlayerInfoPromise(videoId)
{
const data = this._getPlayerPostData(videoId);