File size: 9,298 Bytes
27867f1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
.. image:: https://travis-ci.org/globocom/m3u8.svg
:target: https://travis-ci.org/globocom/m3u8
.. image:: https://coveralls.io/repos/globocom/m3u8/badge.png?branch=master
:target: https://coveralls.io/r/globocom/m3u8?branch=master
.. image:: https://badge.fury.io/py/m3u8.svg
:target: https://badge.fury.io/py/m3u8
m3u8
====
Python `m3u8`_ parser.
Documentation
=============
The basic usage is to create a playlist object from uri, file path or
directly from a string:
.. code-block:: python
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-CUE-IN
* #EXT-X-CUE-SPAN
* #EXT-OATCLS-SCTE35
* `#EXT-X-INDEPENDENT-SEGMENTS`_
* `#EXT-X-MAP`_
* `#EXT-X-START`_
* #EXT-X-SERVER-CONTROL
* #EXT-X-PART-INF
* #EXT-X-PART
* #EXT-X-RENDITION-REPORT
* #EXT-X-SKIP
* `#EXT-X-SESSION-DATA`_
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:
- ``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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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`_.
.. code-block:: python
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.
.. code-block:: python
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)
Custom tags
-----------
Quoting the documentation::
Lines that start with the character '#' are either comments or tags.
Tags begin with #EXT. They are case-sensitive. All other lines that
begin with '#' are comments and SHOULD be ignored.
This library ignores all the non standard tags by default. If you want them to be collected while loading the file content,
you need to pass a function to the `load/loads` functions, following the example below:
.. code-block:: python
import m3u8
def get_movie(line, data, lineno):
if line.startswith('#MOVIE-NAME:'):
custom_tag = line.split(':')
data['movie'] = custom_tag[1].strip()
m3u8_obj = m3u8.load('http://videoserver.com/playlist.m3u8', custom_tags_parser=get_movie)
print(m3u8_obj.data['movie']) # million dollar baby
Running Tests
=============
.. code-block:: bash
$ ./runtests
Contributing
============
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.
.. _m3u8: https://tools.ietf.org/html/rfc8216
.. _#EXT-X-VERSION: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
.. _#EXTINF: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
.. _#EXT-X-BYTERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
.. _#EXT-X-DISCONTINUITY: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
.. _#EXT-X-KEY: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
.. _#EXT-X-MAP: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
.. _#EXT-X-PROGRAM-DATE-TIME: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
.. _#EXT-X-DATERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
.. _#EXT-X-TARGETDURATION: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
.. _#EXT-X-MEDIA-SEQUENCE: https://tools.ietf.org/html/rfc8216#section-4.3.3.2
.. _#EXT-X-DISCONTINUITY-SEQUENCE: https://tools.ietf.org/html/rfc8216#section-4.3.3.3
.. _#EXT-X-ENDLIST: https://tools.ietf.org/html/rfc8216#section-4.3.3.4
.. _#EXT-X-PLAYLIST-TYPE: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
.. _#EXT-X-I-FRAMES-ONLY: https://tools.ietf.org/html/rfc8216#section-4.3.3.6
.. _#EXT-X-MEDIA: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
.. _#EXT-X-STREAM-INF: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
.. _#EXT-X-I-FRAME-STREAM-INF: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
.. _#EXT-X-SESSION-DATA: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
.. _#EXT-X-INDEPENDENT-SEGMENTS: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
.. _#EXT-X-START: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
.. _issue 1: https://github.com/globocom/m3u8/issues/1
.. _variant streams: https://tools.ietf.org/html/rfc8216#section-6.2.4
.. _example here: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-8.5
.. _issue 4: https://github.com/globocom/m3u8/issues/4
.. _I-frame playlists: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
.. _Apple's documentation: https://developer.apple.com/library/ios/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-I_FRAME_PLAYLIST
.. _Alternative audio: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-8.7
.. _VOD: https://developer.apple.com/library/mac/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-TNTAG2
.. _EVENT: https://developer.apple.com/library/mac/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-EVENT_PLAYLIST
|