Skip to content

Commit

Permalink
Adds HDCP-LEVEL to StreamInfo and IFramePlaylist
Browse files Browse the repository at this point in the history
  • Loading branch information
hjmallon authored Dec 20, 2020
1 parent c96ad20 commit 70ea61b
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 6 deletions.
15 changes: 12 additions & 3 deletions m3u8/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,8 @@ def __init__(self, uri, stream_info, media, base_uri):
resolution=resolution_pair,
codecs=stream_info.get('codecs'),
frame_rate=stream_info.get('frame_rate'),
video_range=stream_info.get('video_range')
video_range=stream_info.get('video_range'),
hdcp_level=stream_info.get('hdcp_level')
)
self.media = []
for media_type in ('audio', 'video', 'subtitles'):
Expand Down Expand Up @@ -820,8 +821,8 @@ class IFramePlaylist(BasePathMixin):
Attributes:
`iframe_stream_info` is a named tuple containing the attributes:
`program_id`, `bandwidth`, `average_bandwidth`, `codecs`, `video_range` and
`resolution` which is a tuple (w, h) of integers
`program_id`, `bandwidth`, `average_bandwidth`, `codecs`, `video_range`,
`hdcp_level` and `resolution` which is a tuple (w, h) of integers
More info: http://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.13
'''
Expand Down Expand Up @@ -850,6 +851,7 @@ def __init__(self, base_uri, uri, iframe_stream_info):
resolution=resolution_pair,
codecs=iframe_stream_info.get('codecs'),
video_range=iframe_stream_info.get('video_range'),
hdcp_level=iframe_stream_info.get('hdcp_level'),
frame_rate=None
)

Expand All @@ -874,6 +876,9 @@ def __str__(self):
if self.iframe_stream_info.video_range:
iframe_stream_inf.append('VIDEO-RANGE=%s' %
self.iframe_stream_info.video_range)
if self.iframe_stream_info.hdcp_level:
iframe_stream_inf.append('HDCP-LEVEL=%s' %
self.iframe_stream_info.hdcp_level)
if self.uri:
iframe_stream_inf.append('URI=' + quoted(self.uri))

Expand All @@ -892,6 +897,7 @@ class StreamInfo(object):
subtitles = None
frame_rate = None
video_range = None
hdcp_level = None

def __init__(self, **kwargs):
self.bandwidth = kwargs.get("bandwidth")
Expand All @@ -905,6 +911,7 @@ def __init__(self, **kwargs):
self.subtitles = kwargs.get("subtitles")
self.frame_rate = kwargs.get("frame_rate")
self.video_range = kwargs.get("video_range")
self.hdcp_level = kwargs.get("hdcp_level")

def __str__(self):
stream_inf = []
Expand All @@ -927,6 +934,8 @@ def __str__(self):
stream_inf.append('CODECS=' + quoted(self.codecs))
if self.video_range is not None:
stream_inf.append('VIDEO-RANGE=%s' % self.video_range)
if self.hdcp_level is not None:
stream_inf.append('HDCP-LEVEL=%s' % self.hdcp_level)
return ",".join(stream_inf)


Expand Down
2 changes: 2 additions & 0 deletions m3u8/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ def _parse_stream_inf(line, data, state):
atribute_parser["average_bandwidth"] = int
atribute_parser["frame_rate"] = float
atribute_parser["video_range"] = str
atribute_parser["hdcp_level"] = str
state['stream_info'] = _parse_attribute_list(protocol.ext_x_stream_inf, line, atribute_parser)


Expand All @@ -304,6 +305,7 @@ def _parse_i_frame_stream_inf(line, data):
atribute_parser["bandwidth"] = int
atribute_parser["average_bandwidth"] = int
atribute_parser["video_range"] = str
atribute_parser["hdcp_level"] = str
iframe_stream_info = _parse_attribute_list(protocol.ext_x_i_frame_stream_inf, line, atribute_parser)
iframe_playlist = {'uri': iframe_stream_info.pop('uri'),
'iframe_stream_info': iframe_stream_info}
Expand Down
30 changes: 27 additions & 3 deletions tests/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@
http://example.com/hdr.m3u8
'''

VARIANT_PLAYLIST_WITH_HDCP_LEVEL = '''
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=NONE"
http://example.com/none.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=TYPE-0"
http://example.com/type0.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=TYPE-1"
http://example.com/type1.m3u8
'''

VARIANT_PLAYLIST_WITH_BANDWIDTH_FLOAT = '''
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1280000.0
Expand Down Expand Up @@ -1092,18 +1102,32 @@

VARIANT_PLAYLIST_WITH_IFRAME_VIDEO_RANGE = '''
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=SDR"
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=SDR
http://example.com/sdr.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=PQ"
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=PQ
http://example.com/hdr-pq.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=HLG"
#EXT-X-STREAM-INF:PROGRAM-ID=1,VIDEO-RANGE=HLG
http://example.com/hdr-hlg.m3u8
#EXT-X-I-FRAME-STREAM-INF:VIDEO_RANGE=SDR,URI="http://example.com/sdr-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:VIDEO_RANGE=PQ,URI="http://example.com/hdr-pq-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:VIDEO_RANGE=HLG,URI="http://example.com/hdr-hlg-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:URI="http://example.com/unknown-iframes.m3u8"
'''

VARIANT_PLAYLIST_WITH_IFRAME_HDCP_LEVEL = '''
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=NONE
http://example.com/none.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=TYPE-0
http://example.com/type0.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,HDCP-LEVEL=TYPE-1
http://example.com/type1.m3u8
#EXT-X-I-FRAME-STREAM-INF:HDCP-LEVEL=NONE,URI="http://example.com/none-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:HDCP-LEVEL=TYPE-0,URI="http://example.com/type0-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:HDCP-LEVEL=TYPE-1,URI="http://example.com/type1-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:URI="http://example.com/unknown-iframes.m3u8"
'''

DELTA_UPDATE_SKIP_DATERANGES_PLAYLIST = '''#EXTM3U
#EXT-X-VERSION:10
#EXT-X-TARGETDURATION:6
Expand Down
24 changes: 24 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ def test_should_parse_variant_playlist_with_video_range():
assert 'SDR' == playlists_list[0]['stream_info']['video_range']
assert 'PQ' == playlists_list[1]['stream_info']['video_range']

def test_should_parse_variant_playlist_with_hdcp_level():
data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_HDCP_LEVEL)
playlists_list = list(data['playlists'])
assert 'NONE' == playlists_list[0]['stream_info']['hdcp_level']
assert 'TYPE-0' == playlists_list[1]['stream_info']['hdcp_level']
assert 'TYPE-1' == playlists_list[2]['stream_info']['hdcp_level']

# This is actually not according to specification but as for example Twitch.tv
# is producing master playlists that have bandwidth as floats (issue 72)
# this tests that this situation does not break the parser and will just
Expand Down Expand Up @@ -533,6 +540,23 @@ def test_should_parse_variant_playlist_with_iframe_with_video_range():
assert 'http://example.com/unknown-iframes.m3u8' == iframe_playlists[3]['uri']
assert 'video_range' not in iframe_playlists[3]['iframe_stream_info']

def test_should_parse_variant_playlist_with_iframe_with_hdcp_level():
data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IFRAME_HDCP_LEVEL)
iframe_playlists = list(data['iframe_playlists'])

assert True == data['is_variant']

assert 4 == len(iframe_playlists)

assert 'http://example.com/none-iframes.m3u8' == iframe_playlists[0]['uri']
assert 'NONE' == iframe_playlists[0]['iframe_stream_info']['hdcp_level']
assert 'http://example.com/type0-iframes.m3u8' == iframe_playlists[1]['uri']
assert 'TYPE-0' == iframe_playlists[1]['iframe_stream_info']['hdcp_level']
assert 'http://example.com/type1-iframes.m3u8' == iframe_playlists[2]['uri']
assert 'TYPE-1' == iframe_playlists[2]['iframe_stream_info']['hdcp_level']
assert 'http://example.com/unknown-iframes.m3u8' == iframe_playlists[3]['uri']
assert 'hdcp_level' not in iframe_playlists[3]['iframe_stream_info']

def test_delta_playlist_daterange_skipping():
data = m3u8.parse(playlists.DELTA_UPDATE_SKIP_DATERANGES_PLAYLIST)
assert data['skip']['recently_removed_dateranges'] == "1"
Expand Down
79 changes: 79 additions & 0 deletions tests/test_variant_m3u8.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,49 @@ def test_variant_playlist_with_video_range():
"""
assert expected_content == variant_m3u8.dumps()

def test_variant_playlist_with_hdcp_level():
variant_m3u8 = m3u8.M3U8()

none_playlist = m3u8.Playlist(
'http://example.com/none.m3u8',
stream_info={'bandwidth': 1280000,
'hdcp_level': 'NONE',
'program_id': 1},
media=[],
base_uri=None
)
type0_playlist = m3u8.Playlist(
'http://example.com/type0.m3u8',
stream_info={'bandwidth': 3000000,
'hdcp_level': 'TYPE-0',
'program_id': 1},
media=[],
base_uri=None
)
type1_playlist = m3u8.Playlist(
'http://example.com/type1.m3u8',
stream_info={'bandwidth': 4000000,
'hdcp_level': 'TYPE-1',
'program_id': 1},
media=[],
base_uri=None
)

variant_m3u8.add_playlist(none_playlist)
variant_m3u8.add_playlist(type0_playlist)
variant_m3u8.add_playlist(type1_playlist)

expected_content = """\
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000,HDCP-LEVEL=NONE
http://example.com/none.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3000000,HDCP-LEVEL=TYPE-0
http://example.com/type0.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4000000,HDCP-LEVEL=TYPE-1
http://example.com/type1.m3u8
"""
assert expected_content == variant_m3u8.dumps()

def test_variant_playlist_with_multiple_media():
variant_m3u8 = m3u8.loads(playlists.MULTI_MEDIA_PLAYLIST)
assert variant_m3u8.dumps() == playlists.MULTI_MEDIA_PLAYLIST
Expand Down Expand Up @@ -242,3 +285,39 @@ def test_create_a_variant_m3u8_with_iframe_with_video_range_playlists():
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=3000000,VIDEO-RANGE=HLG,URI="video-HLG-iframes.m3u8"
"""
assert expected_content == variant_m3u8.dumps()


def test_create_a_variant_m3u8_with_iframe_with_hdcp_level_playlists():
variant_m3u8 = m3u8.M3U8()

for hdcplv in ['NONE', 'TYPE-0', 'TYPE-1']:
playlist = m3u8.Playlist(
uri='video-%s.m3u8' % hdcplv,
stream_info={'bandwidth': 3000000,
'hdcp_level': hdcplv},
media=[],
base_uri='http://example.com/%s' % hdcplv
)
iframe_playlist = m3u8.IFramePlaylist(
uri='video-%s-iframes.m3u8' % hdcplv,
iframe_stream_info={'bandwidth': 3000000,
'hdcp_level': hdcplv},
base_uri='http://example.com/%s' % hdcplv
)

variant_m3u8.add_playlist(playlist)
variant_m3u8.add_iframe_playlist(iframe_playlist)

expected_content = """\
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=NONE
video-NONE.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=TYPE-0
video-TYPE-0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=TYPE-1
video-TYPE-1.m3u8
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=NONE,URI="video-NONE-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=TYPE-0,URI="video-TYPE-0-iframes.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=3000000,HDCP-LEVEL=TYPE-1,URI="video-TYPE-1-iframes.m3u8"
"""
assert expected_content == variant_m3u8.dumps()

0 comments on commit 70ea61b

Please sign in to comment.