From d1ef005b092cfab54528bc14a820959ef7a9cf5c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 18 May 2023 06:52:45 +1000
Subject: [PATCH 01/13] Update itag format details
---
.../kodion/impl/xbmc/xbmc_context.py | 1 +
.../youtube/helper/video_info.py | 55 +++++++++++++------
2 files changed, 38 insertions(+), 18 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 867fb190e..2f3e4630a 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -308,6 +308,7 @@ def inputstream_adaptive_capabilities(self, capability=None):
capability_map = {
'live': '2.0.12',
+ 'dts': '2.1.15',
'drm': '2.2.12',
'vp9': '2.3.14',
'vp9.2': '2.3.14',
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index acd0545be..af12e8e73 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -290,9 +290,10 @@ class VideoInfo(object):
'271': {'container': 'webm',
'dash/video': True,
'video': {'height': 1440, 'encoding': 'vp9'}},
- '272': {'container': 'webm',
+ '272': {'container': 'webm', # was VP9 2160p30
'dash/video': True,
- 'video': {'height': 2160, 'encoding': 'vp9'}},
+ 'fps': 60,
+ 'video': {'height': 4320, 'encoding': 'vp9'}},
'278': {'container': 'webm',
'dash/video': True,
'video': {'height': 144, 'encoding': 'vp9'}},
@@ -395,6 +396,14 @@ class VideoInfo(object):
'dash/video': True,
'fps': 30,
'video': {'height': 2160, 'encoding': 'av1'}},
+ '402': {'container': 'mp4',
+ 'dash/video': True,
+ 'fps': 30,
+ 'video': {'height': 4320, 'encoding': 'av1'}},
+ '571': {'container': 'mp4',
+ 'dash/video': True,
+ 'fps': 30,
+ 'video': {'height': 4320, 'encoding': 'av1'}},
'694': {'container': 'mp4',
'dash/video': True,
'fps': 60,
@@ -443,39 +452,39 @@ class VideoInfo(object):
# === Dash (audio only)
'139': {'container': 'mp4',
'sort': [48, 0],
- 'title': 'aac@48',
+ 'title': 'he-aac@48',
'dash/audio': True,
'audio': {'bitrate': 48, 'encoding': 'aac'}},
'140': {'container': 'mp4',
'sort': [129, 0],
- 'title': 'aac@128',
+ 'title': 'aac-lc@128',
'dash/audio': True,
'audio': {'bitrate': 128, 'encoding': 'aac'}},
'141': {'container': 'mp4',
'sort': [143, 0],
- 'title': 'aac@256',
+ 'title': 'aac-lc@256',
'dash/audio': True,
'audio': {'bitrate': 256, 'encoding': 'aac'}},
'256': {'container': 'mp4',
- 'title': 'aac/itag 256',
+ 'title': 'he-aac@192',
'dash/audio': True,
- 'unsupported': True,
- 'audio': {'bitrate': 0, 'encoding': 'aac'}},
+ 'audio': {'bitrate': 192, 'encoding': 'aac'}},
'258': {'container': 'mp4',
- 'title': 'aac/itag 258',
+ 'title': 'aac-LC@384',
'dash/audio': True,
- 'unsupported': True,
- 'audio': {'bitrate': 0, 'encoding': 'aac'}},
+ 'audio': {'bitrate': 384, 'encoding': 'aac'}},
'325': {'container': 'mp4',
- 'title': 'dtse/itag 325',
+ 'title': 'dtse@384',
'dash/audio': True,
- 'unsupported': True,
- 'audio': {'bitrate': 0, 'encoding': 'aac'}},
+ 'audio': {'bitrate': 384, 'encoding': 'dtse'}},
+ '327': {'container': 'mp4',
+ 'title': 'aac-lc@256',
+ 'dash/audio': True,
+ 'audio': {'bitrate': 256, 'encoding': 'aac'}},
'328': {'container': 'mp4',
- 'title': 'ec-3/itag 328',
+ 'title': 'ec-3@384',
'dash/audio': True,
- 'unsupported': True,
- 'audio': {'bitrate': 0, 'encoding': 'aac'}},
+ 'audio': {'bitrate': 384, 'encoding': 'ec-3'}},
'171': {'container': 'webm',
'sort': [128, 0],
'title': 'vorbis@128',
@@ -501,6 +510,15 @@ class VideoInfo(object):
'title': 'opus@160',
'dash/audio': True,
'audio': {'bitrate': 160, 'encoding': 'opus'}},
+ '338': {'container': 'webm',
+ 'sort': [141, 0],
+ 'title': 'opus@480',
+ 'dash/audio': True,
+ 'audio': {'bitrate': 480, 'encoding': 'opus'}},
+ '380': {'container': 'mp4',
+ 'title': 'ac-3@384',
+ 'dash/audio': True,
+ 'audio': {'bitrate': 384, 'encoding': 'ac-3'}},
# === DASH adaptive audio only
'9997': {'container': 'mpd',
'sort': [-1, 0],
@@ -1369,7 +1387,8 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if ((quality_type and container != quality_type)
or (mime_type == 'video/webm' and not {'vp9', 'vp9.2'} & ia_capabilities)
or (mime_type == 'audio/webm' and not {'vorbis', 'opus'} & ia_capabilities)
- or (codec in {'av01', 'av1'} and 'av1' not in ia_capabilities)):
+ or (codec in {'av01', 'av1'} and 'av1' not in ia_capabilities)
+ or (codec.startswith('dts') and 'dts' not in ia_capabilities)):
continue
if 'audioTrack' in stream_map:
From f79e3ab62f982db70d3c6cd1b6ba119e3950aa85 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 24 May 2023 11:44:26 +1000
Subject: [PATCH 02/13] Make case of stream title consistent with other titles
---
resources/lib/youtube_plugin/youtube/helper/video_info.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index af12e8e73..2ceaec0b0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -470,7 +470,7 @@ class VideoInfo(object):
'dash/audio': True,
'audio': {'bitrate': 192, 'encoding': 'aac'}},
'258': {'container': 'mp4',
- 'title': 'aac-LC@384',
+ 'title': 'aac-lc@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'aac'}},
'325': {'container': 'mp4',
From ec0b0b4748c31a39df6d25ca6a78fe0a4340a918 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 25 May 2023 12:07:19 +1000
Subject: [PATCH 03/13] Fix possible regression in capability evaluation
- The meaning of None has changed a few times (#127, #146)
- Add comment to define possible values
- Vorbis audio doesn't seem to be offered by Youtube anymore
- Vorbis appears to have been supported by ISA for a long time
---
.../lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 2f3e4630a..7a933521b 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -306,13 +306,17 @@ def inputstream_adaptive_capabilities(self, capability=None):
if not use_dash or not inputstream_version:
return frozenset() if capability is None else None
+ # Values of capability map can be any of the following:
+ # - required version number as string for comparison with actual installed InputStream.Adaptive version
+ # - any Falsey value to exclude capability regardless of version
+ # - True to include capability regardless of version
capability_map = {
'live': '2.0.12',
'dts': '2.1.15',
'drm': '2.2.12',
'vp9': '2.3.14',
'vp9.2': '2.3.14',
- 'vorbis': None,
+ 'vorbis': '2.3.14',
'opus': '19.0.7',
'av1': '20.3.0',
}
@@ -320,7 +324,8 @@ def inputstream_adaptive_capabilities(self, capability=None):
if capability is None:
ia_loose_version = utils.loose_version(inputstream_version)
capabilities = frozenset(key for key, version in capability_map.items()
- if version and ia_loose_version >= utils.loose_version(version))
+ if version is True
+ or version and ia_loose_version >= utils.loose_version(version))
return capabilities
return capability_map.get(capability)
From 3a3d0882c1a9c8276bf4b080fc6d4e2fd5541726 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Thu, 25 May 2023 22:06:46 +1000
Subject: [PATCH 04/13] Allow HDR when Adaptive MP4 video is selected
---
.../lib/youtube_plugin/kodion/impl/abstract_settings.py | 2 --
resources/settings.xml | 5 +----
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index 6a2e28870..fee535381 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -142,8 +142,6 @@ def use_dash_videos(self):
return self.get_bool(constants.setting.DASH_VIDEOS, False)
def include_hdr(self):
- if self.get_mpd_quality() == 'mp4':
- return False
return self.get_bool(constants.setting.DASH_INCL_HDR, False)
def use_dash_live_streams(self):
diff --git a/resources/settings.xml b/resources/settings.xml
index 14bfc4524..ec002b14b 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -268,10 +268,7 @@
false
-
- true
- 8
-
+ true
From c306ab7f8d3ea8b4f09c66e209975ac49c8cbdbe Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sat, 10 Jun 2023 17:10:41 +1000
Subject: [PATCH 05/13] Make lang attribute value conform to MPD specs
Also display language in stream quality selector
---
.../lib/youtube_plugin/youtube/helper/video_info.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 2ceaec0b0..a7a3f9760 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1281,7 +1281,7 @@ def _method_get_video_info(self):
stream_details['audio']['bitrate'] = bitrate
if not video_info:
stream_details['title'] = '{0}@{1}'.format(codec, bitrate)
- if audio_info['lang']:
+ if audio_info['lang'] not in {'', 'und'}:
stream_details['title'] += ' Multi-language'
video_stream.update(stream_details)
@@ -1316,6 +1316,8 @@ def parse_to_stream_list(streams):
'headers': curl_headers,
'playback_stats': playback_stats}
stream.update(yt_format)
+ if 'audioTrack' in stream_map:
+ stream['title'] = '{0} {1}'.format(stream['title'], stream_map['audioTrack']['displayName'])
stream_list.append(stream)
# extract streams from map
@@ -1393,7 +1395,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if 'audioTrack' in stream_map:
audio_track = stream_map['audioTrack']
- language_code = audio_track.get('id', '')[0:2]
+ language_code = audio_track.get('id', '')[0:2] or 'und'
label = audio_track.get('displayName', '')
audio_type = 'main' if audio_track.get('audioIsDefault') else 'dub'
height = None
@@ -1402,8 +1404,8 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if language_code == self._language_base:
preferred_audio = '_'+language_code
elif media_type == 'audio':
- language_code = ''
- label = ''
+ language_code = 'und'
+ label = stream_map.get('audioQuality', '')
audio_type = 'main'
height = None
width = None
From bc2522c5fb8b5f374dae275b58557885a6c82de3 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Sun, 11 Jun 2023 03:05:59 +1000
Subject: [PATCH 06/13] Improve handling of dubbed and descriptive audio
Also align MPD manifest with spec and ISA quirks
---
.../youtube/helper/video_info.py | 103 ++++++++++++------
1 file changed, 72 insertions(+), 31 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index a7a3f9760..2c697d3d0 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1357,7 +1357,11 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
}
data = {}
- preferred_audio = ''
+ preferred_audio = {
+ 'id': '',
+ 'language_code': None,
+ 'audio_type': 0,
+ }
for stream_map in adaptive_fmts:
mime_type = stream_map.get('mimeType')
if not mime_type:
@@ -1395,25 +1399,50 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if 'audioTrack' in stream_map:
audio_track = stream_map['audioTrack']
- language_code = audio_track.get('id', '')[0:2] or 'und'
+ language = audio_track.get('id', '')
+ if '.' in language:
+ language_code, audio_type = language.split('.')
+ audio_type = int(audio_type)
+ else:
+ language_code = language or 'und'
+ audio_type = 4
label = audio_track.get('displayName', '')
- audio_type = 'main' if audio_track.get('audioIsDefault') else 'dub'
+ if audio_type == 4 or audio_track.get('audioIsDefault'):
+ audio_role = 'main'
+ elif audio_type == 3:
+ audio_role = 'dub'
+ elif audio_type == 2:
+ audio_role = 'description'
+ # Unsure of what other audio types are actually available
+ # Role set to "alternate" as default fallback
+ else:
+ audio_role = 'alternate'
height = None
width = None
- key = '{0}_{1}'.format(mime_type, language_code)
- if language_code == self._language_base:
- preferred_audio = '_'+language_code
+ key = '{0}_{1}'.format(mime_type, language)
+ if (language_code == self._language_base and (
+ not preferred_audio['id']
+ or audio_role == 'main'
+ or audio_type > preferred_audio['audio_type']
+ )):
+ preferred_audio = {
+ 'id': '_'+language,
+ 'language_code': language_code,
+ 'audio_type': audio_type,
+ }
elif media_type == 'audio':
language_code = 'und'
label = stream_map.get('audioQuality', '')
- audio_type = 'main'
+ audio_role = 'main'
height = None
width = None
key = mime_type
else:
+ # Could use "zxx" language code for Non-Linguistic, Not Applicable
+ # but that is too verbose
language_code = ''
label = stream_map.get('qualityLabel', '')
- audio_type = None
+ audio_role = None
height = stream_map.get('height')
width = stream_map.get('width')
key = mime_type
@@ -1454,7 +1483,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
'indexRange': '{start}-{end}'.format(**index_range),
'initRange': '{start}-{end}'.format(**init_range),
'lang': language_code,
- 'audioType': audio_type,
+ 'audioRole': audio_role,
'sampleRate': int(stream_map.get('audioSampleRate', '0'), 10),
'channels': stream_map.get('audioChannels'),
}
@@ -1494,8 +1523,8 @@ def _stream_sort(stream):
else:
stream_info['video'] = data['video/webm'][0]
- mp4_audio = data.get('audio/mp4'+preferred_audio, [None])[0]
- webm_audio = data.get('audio/webm'+preferred_audio, [None])[0]
+ mp4_audio = data.get('audio/mp4'+preferred_audio['id'], [None])[0]
+ webm_audio = data.get('audio/webm'+preferred_audio['id'], [None])[0]
if _stream_sort(mp4_audio) > _stream_sort(webm_audio):
stream_info['audio'] = mp4_audio
else:
@@ -1513,21 +1542,24 @@ def _stream_sort(stream):
for streams in data.values():
default = False
original = False
+ impaired = False
+ label = ''
main_stream = streams[0]
media_type = main_stream['mediaType']
if media_type == 'video':
- # InputStream.Adaptive seems to mark any video AdaptationSet as default
- # regardless of Role or default attribute
if stream_info[media_type] == main_stream:
default = True
- video_type = 'main'
+ role = 'main'
else:
- video_type = 'alternate'
+ role = 'alternate'
original = ''
elif media_type == 'audio':
label = main_stream['label']
- audio_type = main_stream['audioType']
- original = audio_type == 'main'
+ role = main_stream['audioRole']
+ if role == 'main':
+ original = True
+ elif role == 'description':
+ impaired = True
if stream_info[media_type] == main_stream:
default = True
# Use main audio stream with same container format as video stream
@@ -1542,8 +1574,16 @@ def _stream_sort(stream):
' id="', str(set_id), '"'
' mimeType="', main_stream['mimeType'], '"'
' lang="', main_stream['lang'], '"'
+ # name attribute is ISA specific and does not exist in the MPD spec
+ # Should be a child Label element instead
+ ' name="', label, '"'
+ # original, default and impaired are ISA specific attributes
' original="', str(original).lower(), '"'
- ' default="', str(default).lower(), '">\n'
+ ' default="', str(default).lower(), '"'
+ ' impaired="', str(impaired).lower(), '">\n'
+ # AdaptationSet Label element not currently used by ISA
+ '\t\t\t\n'
+ '\t\t\t\n'
))
if license_url:
@@ -1554,35 +1594,36 @@ def _stream_sort(stream):
'\t\t\t\n'
))
+ num_streams = len(streams)
if media_type == 'audio':
- # InputStream.Adaptive seems to mark any AdaptationSet with a Role as default
- # regardless of what the role is. Omit Role as a workaround
- # out_list.extend((
- # '\t\t\t\n',
- # '\t\t\t\n'
- # ))
out_list.extend(((
- '\t\t\t\n'
+ '\t\t\t\n'
'\t\t\t\t\n'
+ # Representation Label element not currently used by ISA
+ '\t\t\t\t\n'
'\t\t\t\t{baseUrl}\n'
'\t\t\t\t\n'
'\t\t\t\t\t\n'
'\t\t\t\t\n'
'\t\t\t\n'
- ).format(**stream) for stream in streams))
+ ).format(quality=(idx + 1), priority=(num_streams - idx), **stream) for idx, stream in enumerate(streams)))
elif media_type == 'video':
- out_list.extend((
- '\t\t\t\n'
- ))
out_list.extend(((
- '\t\t\t\n'
+ '\t\t\t\n'
+ # Representation Label element not currently used by ISA
'\t\t\t\t\n'
'\t\t\t\t{baseUrl}\n'
'\t\t\t\t\n'
'\t\t\t\t\t\n'
'\t\t\t\t\n'
'\t\t\t\n'
- ).format(**stream) for stream in streams))
+ ).format(quality=(idx + 1), priority=(num_streams - idx), **stream) for idx, stream in enumerate(streams)))
out_list.append('\t\t\n')
set_id += 1
From daccb437018a672a3a27ff6376581fdfd8393467 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Wed, 14 Jun 2023 10:43:24 +1000
Subject: [PATCH 07/13] Further improve stream labelling
- Use custom video label (Youtube qualityLabel is not accurate)
- Avoid duplicate tokens (from ISA and Youtube descriptions)
- Provide bitrate of audio stream per AdaptationSet (not shown by ISA)
---
.../youtube/helper/video_info.py | 128 ++++++++++--------
1 file changed, 69 insertions(+), 59 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 2c697d3d0..5c56ecb70 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1343,7 +1343,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
mpd_quality = self._context.get_settings().get_mpd_quality()
quality_type = isinstance(mpd_quality, str) and mpd_quality or ''
quality_height = isinstance(mpd_quality, int) and mpd_quality or 0
- hdr = self._context.get_settings().include_hdr() and {'vp9.2', 'av1'} & ia_capabilities
+ include_hdr = self._context.get_settings().include_hdr() and {'vp9.2', 'av1'} & ia_capabilities
limit_30fps = self._context.get_settings().mpd_30fps_limit()
ipaddress = self._context.get_settings().httpd_listen()
@@ -1397,67 +1397,76 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
or (codec.startswith('dts') and 'dts' not in ia_capabilities)):
continue
- if 'audioTrack' in stream_map:
- audio_track = stream_map['audioTrack']
- language = audio_track.get('id', '')
- if '.' in language:
- language_code, audio_type = language.split('.')
- audio_type = int(audio_type)
- else:
- language_code = language or 'und'
- audio_type = 4
- label = audio_track.get('displayName', '')
- if audio_type == 4 or audio_track.get('audioIsDefault'):
- audio_role = 'main'
- elif audio_type == 3:
- audio_role = 'dub'
- elif audio_type == 2:
- audio_role = 'description'
- # Unsure of what other audio types are actually available
- # Role set to "alternate" as default fallback
+ if media_type == 'audio':
+ if 'audioTrack' in stream_map:
+ audio_track = stream_map['audioTrack']
+ language = audio_track.get('id', '')
+ if '.' in language:
+ language_code, audio_type = language.split('.')
+ audio_type = int(audio_type)
+ else:
+ language_code = language or 'und'
+ audio_type = 4
+ if audio_type == 4 or audio_track.get('audioIsDefault'):
+ role = 'main'
+ label = 'Original'
+ elif audio_type == 3:
+ role = 'dub'
+ label = 'Dubbed'
+ elif audio_type == 2:
+ role = 'description'
+ label = 'Descriptive'
+ # Unsure of what other audio types are actually available
+ # Role set to "alternate" as default fallback
+ else:
+ role = 'alternate'
+ label = 'Alternate'
+ label = '{0} - {1:.0f} kbps'.format(label,
+ stream_map.get('averageBitrate', 0) / 1000)
+ key = '{0}_{1}'.format(mime_type, language)
+ if (language_code == self._language_base and (
+ not preferred_audio['id']
+ or role == 'main'
+ or audio_type > preferred_audio['audio_type']
+ )):
+ preferred_audio = {
+ 'id': '_'+language,
+ 'language_code': language_code,
+ 'audio_type': audio_type,
+ }
else:
- audio_role = 'alternate'
- height = None
- width = None
- key = '{0}_{1}'.format(mime_type, language)
- if (language_code == self._language_base and (
- not preferred_audio['id']
- or audio_role == 'main'
- or audio_type > preferred_audio['audio_type']
- )):
- preferred_audio = {
- 'id': '_'+language,
- 'language_code': language_code,
- 'audio_type': audio_type,
- }
- elif media_type == 'audio':
- language_code = 'und'
- label = stream_map.get('audioQuality', '')
- audio_role = 'main'
- height = None
- width = None
- key = mime_type
+ language_code = 'und'
+ role = 'main'
+ label = 'Original - {0:.0f} kbps'.format(stream_map.get('averageBitrate', 0) / 1000)
+ key = mime_type
+ sample_rate = int(stream_map.get('audioSampleRate', '0'), 10)
+ channels = stream_map.get('audioChannels', 2)
+ height = width = fps = frame_rate = hdr = None
else:
# Could use "zxx" language code for Non-Linguistic, Not Applicable
# but that is too verbose
language_code = ''
- label = stream_map.get('qualityLabel', '')
- audio_role = None
height = stream_map.get('height')
+ if 0 < quality_height < height:
+ continue
width = stream_map.get('width')
+ # map frame rates to a more common representation to lessen the chance of double refresh changes
+ # sometimes 30 fps is 30 fps, more commonly it is 29.97 fps (same for all mapped frame rates)
+ fps = stream_map.get('fps', 0)
+ if limit_30fps and fps > 30:
+ continue
+ if fps:
+ frame_rate = '{0}/{1}'.format(fps * 1000, fps_scale_map.get(fps, 1000))
+ else:
+ frame_rate = None
+ hdr = 'HDR' in stream_map.get('qualityLabel', '')
+ if hdr and not include_hdr:
+ continue
+ label = '{0}p{1}{2}'.format(height,
+ fps if fps > 30 else '',
+ ' HDR' if hdr else '')
key = mime_type
-
- # map frame rates to a more common representation to lessen the chance of double refresh changes
- # sometimes 30 fps is 30 fps, more commonly it is 29.97 fps (same for all mapped frame rates)
- fps = stream_map.get('fps', 0)
- if fps:
- frame_rate = '{0}/{1}'.format(fps * 1000, fps_scale_map.get(fps, 1000))
- else:
- frame_rate = None
-
- if ((not hdr and 'HDR' in label) or (limit_30fps and fps > 30)
- or (height and 0 < quality_height < height)):
- continue
+ role = sample_rate = channels = None
if key not in data:
data[key] = {}
@@ -1480,12 +1489,13 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
'bitrate': stream_map.get('bitrate', 0),
'fps': fps,
'frameRate': frame_rate,
+ 'hdr': hdr,
'indexRange': '{start}-{end}'.format(**index_range),
'initRange': '{start}-{end}'.format(**init_range),
'lang': language_code,
- 'audioRole': audio_role,
- 'sampleRate': int(stream_map.get('audioSampleRate', '0'), 10),
- 'channels': stream_map.get('audioChannels'),
+ 'role': role,
+ 'sampleRate': sample_rate,
+ 'channels': channels,
}
if 'video/mp4' not in data and 'video/webm' not in data:
@@ -1498,7 +1508,7 @@ def _stream_sort(stream):
return (
stream['height'],
stream['fps'],
- hdr and ('HDR' in stream['label']),
+ stream['hdr'],
# Prefer lower bitrate for video streams
# Used to preference more advanced codecs
-stream['bitrate'],
@@ -1555,7 +1565,7 @@ def _stream_sort(stream):
original = ''
elif media_type == 'audio':
label = main_stream['label']
- role = main_stream['audioRole']
+ role = main_stream['role']
if role == 'main':
original = True
elif role == 'description':
From 50e61a32c8f5d12a53623ec00c1a9ba58d62b626 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 00:13:42 +1000
Subject: [PATCH 08/13] Remove conditions for 30 fps limit setting
- Should not be linked to HDR setting as there are non-HDR HFR itags
- Should not be disabled if a preferred container is selected
---
.../lib/youtube_plugin/kodion/impl/abstract_settings.py | 2 --
resources/settings.xml | 9 ++-------
2 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index fee535381..5dd184d7d 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -238,8 +238,6 @@ def mpd_video_qualities(self):
return qualities
def mpd_30fps_limit(self):
- if self.include_hdr() or isinstance(self.get_mpd_quality(), str):
- return False
return self.get_bool(constants.setting.MPD_30FPS_LIMIT, False)
def remote_friendly_search(self):
diff --git a/resources/settings.xml b/resources/settings.xml
index ec002b14b..c13e8d5af 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -273,17 +273,12 @@
-
+
0
false
-
- true
- false
- 8
- 9
-
+ true
From ef904ac0aeaa3d8fba36b19cd406221b7e180c8b Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 00:20:45 +1000
Subject: [PATCH 09/13] Improve selection and display of video resolution
- Correctly handles portrait videos
- Correctly handles ultra wide AR videos
- Code is messy due to backwards compatibility with quality settings
---
.../kodion/impl/abstract_settings.py | 59 +++++++++----------
.../youtube/helper/video_info.py | 44 +++++++++-----
2 files changed, 57 insertions(+), 46 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
index 5dd184d7d..29b8a93d7 100644
--- a/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
+++ b/resources/lib/youtube_plugin/kodion/impl/abstract_settings.py
@@ -202,39 +202,36 @@ def get_play_count_min_percent(self):
def use_playback_history(self):
return self.get_bool(constants.setting.USE_PLAYBACK_HISTORY, False)
- @staticmethod
- def __get_mpd_quality_map():
- return {
- 0: 240,
- 1: 360,
- 2: 480,
- 3: 720,
- 4: 1080,
- 5: 1440,
- 6: 2160,
- 7: 4320,
- 8: 'mp4',
- 9: 'webm'
- }
-
- def get_mpd_quality(self):
- quality_map = self.__get_mpd_quality_map()
- quality_enum = self.get_int(constants.setting.MPD_QUALITY_SELECTION, 8)
- return quality_map.get(quality_enum, 'mp4')
-
- def mpd_video_qualities(self):
+ # Selections based on max width and min height at common (utra-)wide aspect ratios
+ # 8K and 4K at 32:9, 2K at 8:3, remainder at 22:9 (2.444...)
+ # MPD_QUALITY_SELECTION value
+ _QUALITY_SELECTIONS = ['mp4', # 8 (default)
+ 'webm', # 9
+ {'width': 256, 'height': 105, 'label': '144p{0}{1}'}, # No setting
+ {'width': 426, 'height': 175, 'label': '240p{0}{1}'}, # 0
+ {'width': 640, 'height': 263, 'label': '360p{0}{1}'}, # 1
+ {'width': 854, 'height': 350, 'label': '480p{0}{1}'}, # 2
+ {'width': 1280, 'height': 525, 'label': '720p{0} (HD){1}'}, # 3
+ {'width': 1920, 'height': 787, 'label': '1080p{0} (FHD){1}'}, # 4
+ {'width': 2560, 'height': 984, 'label': '1440p{0} (2K){1}'}, # 5
+ {'width': 3840, 'height': 1080, 'label': '2160p{0} (4K){1}'}, # 6
+ {'width': 7680, 'height': 3148, 'label': '4320p{0} (8K){1}'}, # 7
+ {'width': 0, 'height': 0, 'label': '{2}p{0}{1}'}] # Unknown quality
+
+ def get_mpd_video_qualities(self, list_all=False):
if not self.use_dash_videos():
return []
-
- quality = self.get_mpd_quality()
-
- if not isinstance(quality, int):
- return quality
-
- quality_map = self.__get_mpd_quality_map()
- qualities = sorted([x for x in list(quality_map.values())
- if isinstance(x, int) and x <= quality], reverse=True)
-
+ if list_all:
+ # to be converted to selection index 2
+ selected = 7
+ else:
+ selected = self.get_int(constants.setting.MPD_QUALITY_SELECTION, 8)
+ if 8 <= selected <= 9:
+ # converted to selection index 0 or 1
+ return self._QUALITY_SELECTIONS[selected - 8]
+ # converted to selection index starting from 2
+ qualities = self._QUALITY_SELECTIONS[2:]
+ del qualities[2 + selected:-1]
return qualities
def mpd_30fps_limit(self):
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 5c56ecb70..1644fcbba 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1340,9 +1340,14 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
return None
ia_capabilities = self._context.inputstream_adaptive_capabilities()
- mpd_quality = self._context.get_settings().get_mpd_quality()
- quality_type = isinstance(mpd_quality, str) and mpd_quality or ''
- quality_height = isinstance(mpd_quality, int) and mpd_quality or 0
+ qualities = self._context.get_settings().get_mpd_video_qualities()
+ if isinstance(qualities, str):
+ max_quality = None
+ selected_container = qualities
+ qualities = self._context.get_settings().get_mpd_video_qualities(list_all=True)
+ else:
+ max_quality = qualities[-2]
+ selected_container = None
include_hdr = self._context.get_settings().include_hdr() and {'vp9.2', 'av1'} & ia_capabilities
limit_30fps = self._context.get_settings().mpd_30fps_limit()
@@ -1367,7 +1372,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if not mime_type:
continue
- itag = str(stream_map.get('itag', ''))
+ itag = stream_map.get('itag')
if not itag:
continue
@@ -1390,7 +1395,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
if codec:
codec = codec.group(1)
media_type, container = mime_type.split('/')
- if ((quality_type and container != quality_type)
+ if ((selected_container and container != selected_container)
or (mime_type == 'video/webm' and not {'vp9', 'vp9.2'} & ia_capabilities)
or (mime_type == 'audio/webm' and not {'vorbis', 'opus'} & ia_capabilities)
or (codec in {'av01', 'av1'} and 'av1' not in ia_capabilities)
@@ -1447,24 +1452,33 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
# but that is too verbose
language_code = ''
height = stream_map.get('height')
- if 0 < quality_height < height:
- continue
width = stream_map.get('width')
- # map frame rates to a more common representation to lessen the chance of double refresh changes
- # sometimes 30 fps is 30 fps, more commonly it is 29.97 fps (same for all mapped frame rates)
+ if height > width:
+ compare_width = height
+ compare_height = width
+ else:
+ compare_width = width
+ compare_height = height
+ if max_quality and compare_width > max_quality['width']:
+ continue
fps = stream_map.get('fps', 0)
if limit_30fps and fps > 30:
continue
+ hdr = 'HDR' in stream_map.get('qualityLabel', '')
+ if hdr and not include_hdr:
+ continue
+ # map frame rates to a more common representation to lessen the chance of double refresh changes
+ # sometimes 30 fps is 30 fps, more commonly it is 29.97 fps (same for all mapped frame rates)
if fps:
frame_rate = '{0}/{1}'.format(fps * 1000, fps_scale_map.get(fps, 1000))
else:
frame_rate = None
- hdr = 'HDR' in stream_map.get('qualityLabel', '')
- if hdr and not include_hdr:
- continue
- label = '{0}p{1}{2}'.format(height,
- fps if fps > 30 else '',
- ' HDR' if hdr else '')
+ for quality in qualities:
+ if compare_width <= quality['width'] and compare_height >= quality['height']:
+ break
+ label = quality['label'].format(fps if fps > 30 else '',
+ ' HDR' if hdr else '',
+ compare_height)
key = mime_type
role = sample_rate = channels = None
From ade10b55e57cd55fccbbdf5a70cfc15aee7e4487 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 16:57:39 +1000
Subject: [PATCH 10/13] Fix sorting of audio in stream selection dialog
---
.../youtube/helper/video_info.py | 24 ++++++++++++-------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 1644fcbba..9968844df 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -451,71 +451,77 @@ class VideoInfo(object):
'video': {'height': 4320, 'encoding': 'av1'}},
# === Dash (audio only)
'139': {'container': 'mp4',
- 'sort': [48, 0],
+ 'sort': [0, 48 * 0.9],
'title': 'he-aac@48',
'dash/audio': True,
'audio': {'bitrate': 48, 'encoding': 'aac'}},
'140': {'container': 'mp4',
- 'sort': [129, 0],
+ 'sort': [0, 128 * 0.9],
'title': 'aac-lc@128',
'dash/audio': True,
'audio': {'bitrate': 128, 'encoding': 'aac'}},
'141': {'container': 'mp4',
- 'sort': [143, 0],
+ 'sort': [0, 256 * 0.9],
'title': 'aac-lc@256',
'dash/audio': True,
'audio': {'bitrate': 256, 'encoding': 'aac'}},
'256': {'container': 'mp4',
+ 'sort': [0, 192 * 0.9],
'title': 'he-aac@192',
'dash/audio': True,
'audio': {'bitrate': 192, 'encoding': 'aac'}},
'258': {'container': 'mp4',
+ 'sort': [0, 384 * 0.9],
'title': 'aac-lc@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'aac'}},
'325': {'container': 'mp4',
+ 'sort': [0, 384 * 1.3],
'title': 'dtse@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'dtse'}},
'327': {'container': 'mp4',
+ 'sort': [0, 256 * 0.9],
'title': 'aac-lc@256',
'dash/audio': True,
'audio': {'bitrate': 256, 'encoding': 'aac'}},
'328': {'container': 'mp4',
+ 'sort': [0, 384 * 1.2],
'title': 'ec-3@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'ec-3'}},
'171': {'container': 'webm',
- 'sort': [128, 0],
+ 'sort': [0, 128 * 0.75],
'title': 'vorbis@128',
'dash/audio': True,
'audio': {'bitrate': 128, 'encoding': 'vorbis'}},
'172': {'container': 'webm',
- 'sort': [142, 0],
+ 'sort': [0, 192 * 0.75],
'title': 'vorbis@192',
'dash/audio': True,
'audio': {'bitrate': 192, 'encoding': 'vorbis'}},
'249': {'container': 'webm',
- 'sort': [50, 0],
+ 'sort': [0, 50],
'title': 'opus@50',
'dash/audio': True,
'audio': {'bitrate': 50, 'encoding': 'opus'}},
'250': {'container': 'webm',
- 'sort': [70, 0],
+ 'sort': [0, 70],
'title': 'opus@70',
'dash/audio': True,
'audio': {'bitrate': 70, 'encoding': 'opus'}},
'251': {'container': 'webm',
- 'sort': [141, 0],
+ 'sort': [0, 160],
'title': 'opus@160',
'dash/audio': True,
'audio': {'bitrate': 160, 'encoding': 'opus'}},
'338': {'container': 'webm',
- 'sort': [141, 0],
+ 'sort': [0, 480],
'title': 'opus@480',
'dash/audio': True,
'audio': {'bitrate': 480, 'encoding': 'opus'}},
'380': {'container': 'mp4',
+ 'sort': [0, 384 * 1.1],
'title': 'ac-3@384',
'dash/audio': True,
'audio': {'bitrate': 384, 'encoding': 'ac-3'}},
From 0f1f71ba61283257e49687fc160781575cfef295 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 17:07:05 +1000
Subject: [PATCH 11/13] Update ISA capabilities map and simplify matching
---
.../kodion/impl/xbmc/xbmc_context.py | 15 +++++++++++----
.../youtube_plugin/youtube/helper/video_info.py | 14 +++++++-------
2 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
index 7a933521b..17915993d 100644
--- a/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
+++ b/resources/lib/youtube_plugin/kodion/impl/xbmc/xbmc_context.py
@@ -312,13 +312,20 @@ def inputstream_adaptive_capabilities(self, capability=None):
# - True to include capability regardless of version
capability_map = {
'live': '2.0.12',
- 'dts': '2.1.15',
'drm': '2.2.12',
- 'vp9': '2.3.14',
- 'vp9.2': '2.3.14',
+ # audio
'vorbis': '2.3.14',
'opus': '19.0.7',
- 'av1': '20.3.0',
+ 'mp4a': True,
+ 'ac-3': '2.1.15',
+ 'ec-3': '2.1.15',
+ 'dts': '2.1.15',
+ # video
+ 'avc1': True,
+ 'av01': '20.3.0',
+ 'vp8': False,
+ 'vp9': '2.3.14',
+ 'vp9.2': '2.3.14',
}
if capability is None:
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index 9968844df..ccd0cf265 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1345,7 +1345,6 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
self._context.log_debug('Failed to create directories: %s' % basepath)
return None
- ia_capabilities = self._context.inputstream_adaptive_capabilities()
qualities = self._context.get_settings().get_mpd_video_qualities()
if isinstance(qualities, str):
max_quality = None
@@ -1354,7 +1353,9 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
else:
max_quality = qualities[-2]
selected_container = None
- include_hdr = self._context.get_settings().include_hdr() and {'vp9.2', 'av1'} & ia_capabilities
+
+ ia_capabilities = self._context.inputstream_adaptive_capabilities()
+ include_hdr = self._context.get_settings().include_hdr()
limit_30fps = self._context.get_settings().mpd_30fps_limit()
ipaddress = self._context.get_settings().httpd_listen()
@@ -1397,15 +1398,14 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
continue
mime_type, codecs = unquote(mime_type).split('; ')
- codec = re.match(r'codecs="([a-z0-9]+)', codecs)
+ codec = re.match(r'codecs="([a-z0-9]+([.\-][0-9](?="))?)', codecs)
if codec:
codec = codec.group(1)
+ if codec.startswith('dts'):
+ codec = 'dts'
media_type, container = mime_type.split('/')
if ((selected_container and container != selected_container)
- or (mime_type == 'video/webm' and not {'vp9', 'vp9.2'} & ia_capabilities)
- or (mime_type == 'audio/webm' and not {'vorbis', 'opus'} & ia_capabilities)
- or (codec in {'av01', 'av1'} and 'av1' not in ia_capabilities)
- or (codec.startswith('dts') and 'dts' not in ia_capabilities)):
+ or codec not in ia_capabilities):
continue
if media_type == 'audio':
From 42e36bce298e128f4d6191fa4ec5bf2bb63852a7 Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 17:08:15 +1000
Subject: [PATCH 12/13] Improve labelling of more odd video resolutions
---
resources/lib/youtube_plugin/youtube/helper/video_info.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index ccd0cf265..d8deb1fa1 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1353,6 +1353,7 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
else:
max_quality = qualities[-2]
selected_container = None
+ qualities = list(enumerate(qualities))
ia_capabilities = self._context.inputstream_adaptive_capabilities()
include_hdr = self._context.get_settings().include_hdr()
@@ -1479,8 +1480,10 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
frame_rate = '{0}/{1}'.format(fps * 1000, fps_scale_map.get(fps, 1000))
else:
frame_rate = None
- for quality in qualities:
- if compare_width <= quality['width'] and compare_height >= quality['height']:
+ for idx, quality in qualities:
+ if compare_width <= quality['width']:
+ if compare_height < quality['height']:
+ quality = qualities[idx - 1][1]
break
label = quality['label'].format(fps if fps > 30 else '',
' HDR' if hdr else '',
From a9b90baf60b776fb66bf3d4729165e21fa0db98c Mon Sep 17 00:00:00 2001
From: MoojMidge <56883549+MoojMidge@users.noreply.github.com>
Date: Fri, 16 Jun 2023 17:13:13 +1000
Subject: [PATCH 13/13] Improve MPD sorting and default quality selection
- This won't fix incorrect ordering by ISA for manual selection
- ISA performs it's own simple sorting that results in H264 being preferred over better AV1 streams
---
.../youtube/helper/video_info.py | 27 +++++++++++++++----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py
index d8deb1fa1..0b3cc836f 100644
--- a/resources/lib/youtube_plugin/youtube/helper/video_info.py
+++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py
@@ -1369,6 +1369,21 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
60: 1001
}
+ bitrate_bias_map = {
+ # video - order based on comparative compression ratio
+ 'av01': 1,
+ 'vp9': 0.75,
+ 'vp8': 0.55,
+ 'avc1': 0.5,
+ # audio - order based on preference
+ 'vorbis': 0.75,
+ 'mp4a': 0.9,
+ 'opus': 1,
+ 'ac-3': 1.1,
+ 'ec-3': 1.2,
+ 'dts': 1.3,
+ }
+
data = {}
preferred_audio = {
'id': '',
@@ -1498,6 +1513,9 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
url = self._process_url_params(url)
url = url.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
+ bitrate = stream_map.get('bitrate', 0)
+ biased_bitrate = bitrate * bitrate_bias_map.get(codec, 1)
+
data[key][itag] = {
'mimeType': mime_type,
'baseUrl': url,
@@ -1509,7 +1527,8 @@ def generate_mpd(self, adaptive_fmts, duration, license_url):
'width': width,
'height': height,
'label': label,
- 'bitrate': stream_map.get('bitrate', 0),
+ 'bitrate': bitrate,
+ 'biasedBitrate': biased_bitrate,
'fps': fps,
'frameRate': frame_rate,
'hdr': hdr,
@@ -1532,13 +1551,11 @@ def _stream_sort(stream):
stream['height'],
stream['fps'],
stream['hdr'],
- # Prefer lower bitrate for video streams
- # Used to preference more advanced codecs
- -stream['bitrate'],
+ stream['biasedBitrate'],
) if stream['mediaType'] == 'video' else (
stream['channels'],
stream['sampleRate'],
- stream['bitrate'],
+ stream['biasedBitrate'],
)
data = {