diff --git a/m3u8/model.py b/m3u8/model.py index ed5e3c57..960a5ab3 100644 --- a/m3u8/model.py +++ b/m3u8/model.py @@ -167,7 +167,12 @@ def _initialize_attributes(self): self.segment_map = [InitializationSection(base_uri=self.base_uri, **params) if params else None for params in self.data.get('segment_map', [])] self.segments = SegmentList([ - Segment(base_uri=self.base_uri, keyobject=find_key(segment.get('key', {}), self.keys), **segment) + Segment( + base_uri=self.base_uri, + keyobjects=[ + find_key(segment_key, self.keys) + for segment_key in segment['keys']], + **segment) for segment in self.data.get('segments', []) ]) @@ -430,8 +435,8 @@ class Segment(BasePathMixin): `byterange` byterange attribute from EXT-X-BYTERANGE parameter - `key` - Key used to encrypt the segment (EXT-X-KEY) + `keys` + Keys used to encrypt the segment (EXT-X-KEY) `parts` partial segments that make up this segment @@ -448,9 +453,9 @@ class Segment(BasePathMixin): def __init__(self, uri=None, base_uri=None, program_date_time=None, current_program_date_time=None, duration=None, title=None, bitrate=None, byterange=None, cue_out=False, - cue_out_start=False, cue_in=False, discontinuity=False, key=None, scte35=None, + cue_out_start=False, cue_in=False, discontinuity=False, keys=None, scte35=None, oatcls_scte35=None, scte35_duration=None, scte35_elapsedtime=None, asset_metadata=None, - keyobject=None, parts=None, init_section=None, dateranges=None, gap_tag=None, + keyobjects=None, parts=None, init_section=None, dateranges=None, gap_tag=None, media_sequence=None, custom_parser_values=None): self.media_sequence = media_sequence self.uri = uri @@ -470,7 +475,7 @@ def __init__(self, uri=None, base_uri=None, program_date_time=None, current_prog self.scte35_duration = scte35_duration self.scte35_elapsedtime = scte35_elapsedtime self.asset_metadata = asset_metadata - self.key = keyobject + self.keys = keyobjects self.parts = PartialSegmentList( [ PartialSegment(base_uri=self._base_uri, **partial) for partial in parts ] if parts else [] ) if init_section is not None: self.init_section = InitializationSection(self._base_uri, **init_section) @@ -486,14 +491,9 @@ def add_part(self, part): def dumps(self, last_segment, timespec='milliseconds'): output = [] - if last_segment and self.key != last_segment.key: - output.append(str(self.key)) - output.append('\n') - else: - # The key must be checked anyway now for the first segment - if self.key and last_segment is None: - output.append(str(self.key)) - output.append('\n') + if not last_segment or (self.keys and self.keys != last_segment.keys): + for key in self.keys: + output.append(str(key) + '\n') if last_segment and self.init_section != last_segment.init_section: if not self.init_section: @@ -610,7 +610,7 @@ def uri(self): def by_key(self, key): - return [ segment for segment in self if segment.key == key ] + return [ segment for segment in self if key in segment.keys ] diff --git a/m3u8/parser.py b/m3u8/parser.py index 82d43071..47e615db 100644 --- a/m3u8/parser.py +++ b/m3u8/parser.py @@ -2,6 +2,7 @@ # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. +from copy import copy import iso8601 import datetime @@ -62,7 +63,7 @@ def parse(content, strict=False, custom_tags_parser=None): state = { 'expect_segment': False, 'expect_playlist': False, - 'current_key': None, + 'current_keys': [], 'current_segment_map': None, } @@ -95,6 +96,7 @@ def parse(content, strict=False, custom_tags_parser=None): elif line.startswith(protocol.ext_x_discontinuity_sequence): _parse_simple_parameter(line, data, int) + state['current_keys'].clear() elif line.startswith(protocol.ext_x_program_date_time): _, program_date_time = _parse_simple_parameter_raw_value(line, cast_date_time) @@ -135,7 +137,7 @@ def parse(content, strict=False, custom_tags_parser=None): elif line.startswith(protocol.ext_x_key): key = _parse_key(line) - state['current_key'] = key + state['current_keys'].append(key) if key not in data['keys']: data['keys'].append(key) @@ -222,6 +224,7 @@ def parse(content, strict=False, custom_tags_parser=None): elif state['expect_segment']: _parse_ts_chunk(line, data, state) state['expect_segment'] = False + state['current_keys'].clear() elif state['expect_playlist']: _parse_variant_playlist(line, data, state) @@ -280,9 +283,8 @@ def _parse_ts_chunk(line, data, state): segment['scte35_elapsedtime'] = scte_op('current_cue_out_elapsedtime', None) segment['asset_metadata'] = scte_op('asset_metadata', None) segment['discontinuity'] = state.pop('discontinuity', False) - if state.get('current_key'): - segment['key'] = state['current_key'] - else: + segment['keys'] = copy(state['current_keys']) + if not state['current_keys']: # For unencrypted segments, the initial key would be None if None not in data['keys']: data['keys'].append(None)