Skip to content

Commit

Permalink
Merge pull request #142 from igorsobreira/master
Browse files Browse the repository at this point in the history
dumps() include EXT-X-PROGRAM-DATE-TIME consistent with loads()
  • Loading branch information
mauricioabreu authored Jun 19, 2019
2 parents 3f083d2 + 7c0a498 commit 2844133
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 14 deletions.
24 changes: 15 additions & 9 deletions m3u8/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,6 @@ def dumps(self):
if self.target_duration:
output.append('#EXT-X-TARGETDURATION:' +
int_or_float_to_string(self.target_duration))
if self.program_date_time is not None:
output.append('#EXT-X-PROGRAM-DATE-TIME:' + format_date_time(self.program_date_time))
if not (self.playlist_type is None or self.playlist_type == ''):
output.append('#EXT-X-PLAYLIST-TYPE:%s' % str(self.playlist_type).upper())
if self.start:
Expand Down Expand Up @@ -313,9 +311,15 @@ class Segment(BasePathMixin):
title attribute from EXTINF parameter
`program_date_time`
Returns the EXT-X-PROGRAM-DATE-TIME as a datetime
Returns the EXT-X-PROGRAM-DATE-TIME as a datetime. This field is only set
if EXT-X-PROGRAM-DATE-TIME exists for this segment
http://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.5
`current_program_date_time`
Returns a datetime of this segment, either the value of `program_date_time`
when EXT-X-PROGRAM-DATE-TIME is set or a calculated value based on previous
segments' EXT-X-PROGRAM-DATE-TIME and EXTINF values
`discontinuity`
Returns a boolean indicating if a EXT-X-DISCONTINUITY tag exists
http://tools.ietf.org/html/draft-pantos-http-live-streaming-13#section-3.4.11
Expand All @@ -342,15 +346,17 @@ class Segment(BasePathMixin):
Key used to encrypt the segment (EXT-X-KEY)
'''

def __init__(self, uri, base_uri, program_date_time=None, duration=None,
title=None, byterange=None, cue_out=False, discontinuity=False, key=None,
scte35=None, scte35_duration=None, keyobject=None):
def __init__(self, uri, base_uri, program_date_time=None, current_program_date_time=None,
duration=None, title=None, byterange=None, cue_out=False,
discontinuity=False, key=None, scte35=None, scte35_duration=None,
keyobject=None):
self.uri = uri
self.duration = duration
self.title = title
self.base_uri = base_uri
self.byterange = byterange
self.program_date_time = program_date_time
self.current_program_date_time = current_program_date_time
self.discontinuity = discontinuity
self.cue_out = cue_out
self.scte35 = scte35
Expand All @@ -371,9 +377,9 @@ def dumps(self, last_segment):

if self.discontinuity:
output.append('#EXT-X-DISCONTINUITY\n')
if self.program_date_time:
output.append('#EXT-X-PROGRAM-DATE-TIME:%s\n' %
format_date_time(self.program_date_time))
if self.program_date_time:
output.append('#EXT-X-PROGRAM-DATE-TIME:%s\n' %
format_date_time(self.program_date_time))
if self.cue_out:
output.append('#EXT-X-CUE-OUT-CONT\n')
output.append('#EXTINF:%s,' % int_or_float_to_string(self.duration))
Expand Down
5 changes: 4 additions & 1 deletion m3u8/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def parse(content, strict=False, custom_tags_parser=None):
if not data.get('program_date_time'):
data['program_date_time'] = program_date_time
state['current_program_date_time'] = program_date_time
state['program_date_time'] = program_date_time

elif line.startswith(protocol.ext_x_discontinuity):
state['discontinuity'] = True
Expand Down Expand Up @@ -200,8 +201,10 @@ def _parse_extinf(line, data, state, lineno, strict):

def _parse_ts_chunk(line, data, state):
segment = state.pop('segment')
if state.get('program_date_time'):
segment['program_date_time'] = state.pop('program_date_time')
if state.get('current_program_date_time'):
segment['program_date_time'] = state['current_program_date_time']
segment['current_program_date_time'] = state['current_program_date_time']
state['current_program_date_time'] += datetime.timedelta(seconds=segment['duration'])
segment['uri'] = line
segment['cue_out'] = state.pop('cue_out', False)
Expand Down
18 changes: 18 additions & 0 deletions tests/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,24 @@
'''

PLAYLIST_WITH_PROGRAM_DATE_TIME_WITHOUT_DISCONTINUITY = '''
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:EVENT
#EXT-X-MEDIA-SEQUENCE:50
#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:00.000Z
#EXTINF:6.000,
manifest_1_50.ts?m=1559946393
#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:06.000Z
#EXTINF:6.000,
manifest_1_51.ts?m=1559946393
#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:12.000Z
#EXTINF:6.000,
manifest_1_52.ts?m=1559946393
#EXT-X-ENDLIST
'''

CUE_OUT_PLAYLIST = '''
#EXTM3U
#EXT-X-TARGETDURATION:10
Expand Down
48 changes: 44 additions & 4 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,16 @@ def test_program_date_time_attribute_for_each_segment():
obj = m3u8.M3U8(playlists.SIMPLE_PLAYLIST_WITH_PROGRAM_DATE_TIME)

first_program_date_time = datetime.datetime(2014, 8, 13, 13, 36, 33, tzinfo=utc)
for idx, segment in enumerate(obj.segments):
assert segment.program_date_time == first_program_date_time + \
datetime.timedelta(seconds=idx * 3)

# first segment contains both program_date_time and current_program_date_time
assert obj.segments[0].program_date_time == first_program_date_time
assert obj.segments[0].current_program_date_time == first_program_date_time

# other segments contain only current_program_date_time
for idx, segment in enumerate(obj.segments[1:]):
assert segment.program_date_time is None
assert segment.current_program_date_time == first_program_date_time + \
datetime.timedelta(seconds=(idx+1) * 3)


def test_program_date_time_attribute_with_discontinuity():
Expand All @@ -69,9 +76,32 @@ def test_program_date_time_attribute_with_discontinuity():

segments = obj.segments

# first segment has EXT-X-PROGRAM-DATE-TIME
assert segments[0].program_date_time == first_program_date_time
assert segments[0].current_program_date_time == first_program_date_time

# second segment does not have EXT-X-PROGRAM-DATE-TIME
assert segments[1].program_date_time is None
assert segments[1].current_program_date_time == first_program_date_time + datetime.timedelta(seconds=3)

# segment with EXT-X-DISCONTINUITY also has EXT-X-PROGRAM-DATE-TIME
assert segments[5].program_date_time == discontinuity_program_date_time
assert segments[6].program_date_time == discontinuity_program_date_time + datetime.timedelta(seconds=3)
assert segments[5].current_program_date_time == discontinuity_program_date_time

# subsequent segment does not have EXT-X-PROGRAM-DATE-TIME
assert segments[6].current_program_date_time == discontinuity_program_date_time + datetime.timedelta(seconds=3)
assert segments[6].program_date_time is None


def test_program_date_time_attribute_without_discontinuity():
obj = m3u8.M3U8(playlists.PLAYLIST_WITH_PROGRAM_DATE_TIME_WITHOUT_DISCONTINUITY)

first_program_date_time = datetime.datetime(2019, 6, 10, 0, 5, tzinfo=utc)

for idx, segment in enumerate(obj.segments):
program_date_time = first_program_date_time + datetime.timedelta(seconds=idx * 6)
assert segment.program_date_time == program_date_time
assert segment.current_program_date_time == program_date_time


def test_segment_discontinuity_attribute():
Expand Down Expand Up @@ -488,6 +518,16 @@ def test_dump_should_include_segment_level_program_date_time():
# Tag being expected is in the segment level, not the global one
assert "#EXT-X-PROGRAM-DATE-TIME:2014-08-13T13:36:55+00:00" in obj.dumps().strip()


def test_dump_should_include_segment_level_program_date_time_without_discontinuity():
obj = m3u8.M3U8(playlists.PLAYLIST_WITH_PROGRAM_DATE_TIME_WITHOUT_DISCONTINUITY)

output = obj.dumps().strip()
assert "#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:00+00:00" in output
assert "#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:06+00:00" in output
assert "#EXT-X-PROGRAM-DATE-TIME:2019-06-10T00:05:12+00:00" in output


def test_dump_should_include_map_attributes():
obj = m3u8.M3U8(playlists.MAP_URI_PLAYLIST_WITH_BYTERANGE)

Expand Down

0 comments on commit 2844133

Please sign in to comment.