m3u8

Python m3u8 Parser for HTTP Live Streaming (HLS) Transmissions.

Documentation

The basic usage is to create a playlist object from uri, file path or
directly from a string:

import m3u8

m3u8_obj = m3u8.load('http://videoserver.com/playlist.m3u8')  # this could also be an absolute filename
print m3u8_obj.segments
print m3u8_obj.target_duration

# if you already have the content as string, use

m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ... ')

Supported tags

  • EXT-X-TARGETDURATION

  • EXT-X-MEDIA-SEQUENCE

  • EXT-X-DISCONTINUITY-SEQUENCE

  • EXT-X-PROGRAM-DATE-TIME

  • EXT-X-MEDIA

  • EXT-X-PLAYLIST-TYPE

  • EXT-X-KEY

  • EXT-X-STREAM-INF

  • EXT-X-VERSION

  • EXT-X-ALLOW-CACHE

  • EXT-X-ENDLIST

  • EXTINF

  • EXT-X-I-FRAMES-ONLY

  • EXT-X-BYTERANGE

  • EXT-X-I-FRAME-STREAM-INF

  • EXT-X-DISCONTINUITY

  • EXT-X-CUE-OUT

  • EXT-X-CUE-OUT-CONT

  • EXT-X-INDEPENDENT-SEGMENTS

  • EXT-OATCLS-SCTE35

  • EXT-X-CUE-OUT

  • EXT-X-CUE-IN

  • EXT-X-CUE-SPAN

  • EXT-X-MAP

  • EXT-X-START

Encryption keys

The segments may be or not encrypted. The keys attribute list will
be an list with all the different keys as described with #EXT-X-KEY_:

Each key has the next properties:

If no #EXT-X-KEY is found, the keys list will have a unique element None. Multiple keys are supported.

If unencrypted and encrypted segments are mixed in the M3U8 file, then the list will contain a None element, with one
or more keys afterwards.

To traverse the list of keys available:

import m3u8

m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')
len(m3u8_obj.keys) => returns the number of keys available in the list (normally 1)
for key in m3u8_obj.keys:
   if key:  # First one could be None
      key.uri
      key.method
      key.iv

Getting segments encrypted with one key

There are cases where listing segments for a given key is important. It's possible to
retrieve the list of segments encrypted with one key via by_key method in the
segments list.

Example of getting the segments with no encryption:

import m3u8

m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')
segmk1 = m3u8_obj.segments.by_key(None)

# Get the list of segments encrypted using last key
segm = m3u8_obj.segments.by_key( m3u8_obj.keys[-1] )

With this method, is now possible also to change the key from some of the segments programatically:

import m3u8

m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')

# Create a new Key and replace it
new_key = m3u8.Key("AES-128", "/encrypted/newkey.bin", None, iv="0xf123ad23f22e441098aa87ee")
for segment in m3u8_obj.segments.by_key( m3u8_obj.keys[-1] ):
    segm.key = new_key
# Remember to sync the key from the list as well
m3u8_obj.keys[-1] = new_key

Variant playlists (variable bitrates)

A playlist can have a list to other playlist files, this is used to
represent multiple bitrates videos, and it's called variant streams.
See an example here
.

variant_m3u8 = m3u8.loads('#EXTM3U8 ... contains a variant stream ...')
variant_m3u8.is_variant    # in this case will be True

for playlist in variant_m3u8.playlists:
    playlist.uri
    playlist.stream_info.bandwidth

the playlist object used in the for loop above has a few attributes:

  • uri: the url to the stream
  • stream_info: a StreamInfo object (actually a namedtuple) with
    all the attributes available to #EXT-X-STREAM-INF_
  • media: a list of related Media objects with all the attributes
    available to #EXT-X-MEDIA_
  • playlist_type: the type of the playlist, which can be one of VOD_
    (video on demand) or EVENT_

NOTE: the following attributes are not implemented yet, follow
issue 4_ for updates

  • alternative_audios: its an empty list, unless it's a playlist
    with Alternative audio, in this case it's a list with Media
    objects with all the attributes available to #EXT-X-MEDIA
  • alternative_videos: same as alternative_audios

A variant playlist can also have links to I-frame playlists, which are used
to specify where the I-frames are in a video. See Apple's documentation
on
this for more information. These I-frame playlists can be accessed in a similar
way to regular playlists.

variant_m3u8 = m3u8.loads('#EXTM3U ... contains a variant stream ...')

for iframe_playlist in variant_m3u8.iframe_playlists:
    iframe_playlist.uri
    iframe_playlist.iframe_stream_info.bandwidth

The iframe_playlist object used in the for loop above has a few attributes:

  • uri: the url to the I-frame playlist
  • base_uri: the base uri of the variant playlist (if given)
  • iframe_stream_info: a StreamInfo object (same as a regular playlist)

Running Tests

$ ./runtests

GitHub