Python m3u8 parser
Python m3u8 parser.
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 ... ')
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:
- method: ex.: “AES-128”
- uri: the key uri, ex.: “http://videoserver.com/key.bin”
- iv: the initialization vector, if available. Otherwise None.
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)
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)
All contribution is welcome, but we will merge a pull request if, and only if, it
- has tests
- follows the code conventions
If you plan to implement a new feature or something that will take more than a few minutes, please open an issue to make sure we don’t work on the same thing.