.. image:: https://github.com/globocom/m3u8/actions/workflows/main.yml/badge.svg
.. image:: https://badge.fury.io/py/m3u8.svg
:target: https://badge.fury.io/py/m3u8
m3u8
====
Python `m3u8`_ parser.
Documentation
=============
Loading a playlist
------------------
To load a playlist into an object from uri, file path or directly from string, use the `load/loads` functions:
.. code-block:: python
import m3u8
playlist = m3u8.load('http://videoserver.com/playlist.m3u8') # this could also be an absolute filename
print(playlist.segments)
print(playlist.target_duration)
# if you already have the content as string, use
playlist = m3u8.loads('#EXTM3U8 ... etc ... ')
Dumping a playlist
------------------
To dump a playlist from an object to the console or a file, use the `dump/dumps` functions:
.. code-block:: python
import m3u8
playlist = m3u8.load('http://videoserver.com/playlist.m3u8')
print(playlist.dumps())
# if you want to write a file from its content
playlist.dump('playlist.m3u8')
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-BITRATE`_
* `#EXT-X-BYTERANGE`_
* `#EXT-X-I-FRAME-STREAM-INF`_
* `#EXT-X-IMAGES-ONLY`_
* `#EXT-X-IMAGE-STREAM-INF`_
* `#EXT-X-TILES`_
* `#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`_
* `#EXT-X-PRELOAD-HINT`_
* `#EXT-X-SESSION-KEY`_
* `#EXT-X-DATERANGE`_
* `#EXT-X-GAP`_
* `#EXT-X-CONTENT-STEERING`_
Encryption keys
---------------
The segments may or may not be encrypted. The ``keys`` attribute list will
be a 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 programmatically:
.. 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] ):
segment.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, lineno, data, state):
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
Also you are able to override parsing of existing standard tags.
To achieve this your custom_tags_parser function have to return boolean True - it will mean that you fully implement parsing of current line therefore 'main parser' can go to next line.
.. code-block:: python
import re
import m3u8
from m3u8 import protocol
from m3u8.parser import save_segment_custom_value
def parse_iptv_attributes(line, lineno, data, state):
# Customize parsing #EXTINF
if line.startswith(protocol.extinf):
title = ''
chunks = line.replace(protocol.extinf + ':', '').split(',', 1)
if len(chunks) == 2:
duration_and_props, title = chunks
elif len(chunks) == 1:
duration_and_props = chunks[0]
additional_props = {}
chunks = duration_and_props.strip().split(' ', 1)
if len(chunks) == 2:
duration, raw_props = chunks
matched_props = re.finditer(r'([\w\-]+)="([^"]*)"', raw_props)
for match in matched_props:
additional_props[match.group(1)] = match.group(2)
else:
duration = duration_and_props
if 'segment' not in state:
state['segment'] = {}
state['segment']['duration'] = float(duration)
state['segment']['title'] = title
# Helper function for saving custom values
save_segment_custom_value(state, 'extinf_props', additional_props)
# Tell 'main parser' that we expect an URL on next lines
state['expect_segment'] = True
# Tell 'main parser' that it can go to next line, we've parsed current fully.
return True
if __name__ == '__main__':
PLAYLIST = """#EXTM3U
#EXTINF:-1 timeshift="0" catchup-days="7" catchup-type="flussonic" tvg-id="channel1" group-title="Group1",Channel1
http://str00.iptv.domain/7331/mpegts?token=longtokenhere
"""
parsed = m3u8.loads(PLAYLIST, custom_tags_parser=parse_iptv_attributes)
first_segment_props = parsed.segments[0].custom_parser_values['extinf_props']
print(first_segment_props['tvg-id']) # 'channel1'
print(first_segment_props['group-title']) # 'Group1'
print(first_segment_props['catchup-type']) # 'flussonic'
Helper functions get_segment_custom_value() and save_segment_custom_value() are intended for getting/storing your parsed values per segment into Segment class.
After that all custom values will be accessible via property custom_parser_values of Segment instance.
Using different HTTP clients
----------------------------
If you don't want to use urllib to download playlists, having more control on how objects are fetched over the internet,
you can use your own client. `requests` is a well known Python HTTP library and it can be used with `m3u8`:
.. code-block:: python
import m3u8
import requests
class RequestsClient():
def download(self, uri, timeout=None, headers={}, verify_ssl=True):
o = requests.get(uri, timeout=timeout, headers=headers)
return o.text, o.url
playlist = m3u8.load('http://videoserver.com/playlist.m3u8', http_client=RequestsClient())
print(playlist.dumps())
The advantage of using a custom HTTP client is to refine SSL verification, proxies, performance, flexibility, etc.
Playlists behind proxies
------------------------
In case you need to use a proxy but can't use a system wide proxy (HTTP/HTTPS proxy environment variables), you can pass your
HTTP/HTTPS proxies as a dict to the load function.
.. code-block:: python
import m3u8
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
http_client = m3u8.httpclient.DefaultHTTPClient(proxies)
playlist = m3u8.load('http://videoserver.com/playlist.m3u8', http_client=http_client) # this could also be an absolute filename
print(playlist.dumps())
It works with the default client only. Custom HTTP clients must implement this feature.
Running Tests
=============
.. code-block:: bash
$ ./runtests
Contributing
============
All contributions are 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-ALLOW-CACHE: https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-07#section-3.3.6
.. _#EXT-X-BITRATE: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.4.8
.. _#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-IMAGES-ONLY: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf
.. _#EXT-X-IMAGE-STREAM-INF: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf
.. _#EXT-X-TILES: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf
.. _#EXT-X-SESSION-DATA: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
.. _#EXT-X-SESSION-KEY: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
.. _#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
.. _#EXT-X-PRELOAD-HINT: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.5.3
.. _#EXT-X-DATERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
.. _#EXT-X-GAP: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.2.7
.. _#EXT-X-CONTENT-STEERING: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-10#section-4.4.6.64
.. _#EXT-X-SKIP: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.2
.. _#EXT-X-RENDITION-REPORT: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.4
.. _#EXT-X-PART: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.4.9
.. _#EXT-X-PART-INF: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.3.7
.. _#EXT-X-SERVER-CONTROL: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.3.8
.. _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
Raw data
{
"_id": null,
"home_page": "https://github.com/globocom/m3u8",
"name": "m3u8",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": "",
"keywords": "",
"author": "Globo.com",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/d4/f3/10e8d8879f284c17ea658a9ddca3b4f9958697d8fd3e50d149133f2a3dc7/m3u8-4.0.0.tar.gz",
"platform": null,
"description": ".. image:: https://github.com/globocom/m3u8/actions/workflows/main.yml/badge.svg\n\n.. image:: https://badge.fury.io/py/m3u8.svg\n :target: https://badge.fury.io/py/m3u8\n\nm3u8\n====\n\nPython `m3u8`_ parser.\n\nDocumentation\n=============\n\nLoading a playlist\n------------------\n\nTo load a playlist into an object from uri, file path or directly from string, use the `load/loads` functions:\n\n.. code-block:: python\n\n import m3u8\n\n playlist = m3u8.load('http://videoserver.com/playlist.m3u8') # this could also be an absolute filename\n print(playlist.segments)\n print(playlist.target_duration)\n\n # if you already have the content as string, use\n \n playlist = m3u8.loads('#EXTM3U8 ... etc ... ')\n\nDumping a playlist\n------------------\n\nTo dump a playlist from an object to the console or a file, use the `dump/dumps` functions:\n\n.. code-block:: python\n\n import m3u8\n\n playlist = m3u8.load('http://videoserver.com/playlist.m3u8')\n print(playlist.dumps())\n\n # if you want to write a file from its content\n \n playlist.dump('playlist.m3u8')\n\n\nSupported tags\n==============\n\n* `#EXT-X-TARGETDURATION`_\n* `#EXT-X-MEDIA-SEQUENCE`_\n* `#EXT-X-DISCONTINUITY-SEQUENCE`_\n* `#EXT-X-PROGRAM-DATE-TIME`_\n* `#EXT-X-MEDIA`_\n* `#EXT-X-PLAYLIST-TYPE`_\n* `#EXT-X-KEY`_\n* `#EXT-X-STREAM-INF`_\n* `#EXT-X-VERSION`_\n* `#EXT-X-ALLOW-CACHE`_\n* `#EXT-X-ENDLIST`_\n* `#EXTINF`_\n* `#EXT-X-I-FRAMES-ONLY`_\n* `#EXT-X-BITRATE`_\n* `#EXT-X-BYTERANGE`_\n* `#EXT-X-I-FRAME-STREAM-INF`_\n* `#EXT-X-IMAGES-ONLY`_\n* `#EXT-X-IMAGE-STREAM-INF`_\n* `#EXT-X-TILES`_\n* `#EXT-X-DISCONTINUITY`_\n* #EXT-X-CUE-OUT\n* #EXT-X-CUE-OUT-CONT\n* #EXT-X-CUE-IN\n* #EXT-X-CUE-SPAN\n* #EXT-OATCLS-SCTE35\n* `#EXT-X-INDEPENDENT-SEGMENTS`_\n* `#EXT-X-MAP`_\n* `#EXT-X-START`_\n* `#EXT-X-SERVER-CONTROL`_\n* `#EXT-X-PART-INF`_\n* `#EXT-X-PART`_\n* `#EXT-X-RENDITION-REPORT`_\n* `#EXT-X-SKIP`_\n* `#EXT-X-SESSION-DATA`_\n* `#EXT-X-PRELOAD-HINT`_\n* `#EXT-X-SESSION-KEY`_\n* `#EXT-X-DATERANGE`_\n* `#EXT-X-GAP`_\n* `#EXT-X-CONTENT-STEERING`_\n\nEncryption keys\n---------------\n\nThe segments may or may not be encrypted. The ``keys`` attribute list will\nbe a list with all the different keys as described with `#EXT-X-KEY`_:\n\nEach key has the next properties:\n\n- ``method``: ex.: \"AES-128\"\n- ``uri``: the key uri, ex.: \"http://videoserver.com/key.bin\"\n- ``iv``: the initialization vector, if available. Otherwise ``None``.\n\nIf no ``#EXT-X-KEY`` is found, the ``keys`` list will have a unique element ``None``. Multiple keys are supported.\n\nIf unencrypted and encrypted segments are mixed in the M3U8 file, then the list will contain a ``None`` element, with one\nor more keys afterwards.\n\nTo traverse the list of keys available:\n\n.. code-block:: python\n\n import m3u8\n\n m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')\n len(m3u8_obj.keys) # => returns the number of keys available in the list (normally 1)\n for key in m3u8_obj.keys:\n if key: # First one could be None\n key.uri\n key.method\n key.iv\n\n\nGetting segments encrypted with one key\n---------------------------------------\n\nThere are cases where listing segments for a given key is important. It's possible to\nretrieve the list of segments encrypted with one key via ``by_key`` method in the\n``segments`` list.\n\nExample of getting the segments with no encryption:\n\n.. code-block:: python\n\n import m3u8\n\n m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')\n segmk1 = m3u8_obj.segments.by_key(None)\n\n # Get the list of segments encrypted using last key\n segm = m3u8_obj.segments.by_key( m3u8_obj.keys[-1] )\n\n\nWith this method, is now possible also to change the key from some of the segments programmatically:\n\n\n.. code-block:: python\n\n import m3u8\n\n m3u8_obj = m3u8.loads('#EXTM3U8 ... etc ...')\n\n # Create a new Key and replace it\n new_key = m3u8.Key(\"AES-128\", \"/encrypted/newkey.bin\", None, iv=\"0xf123ad23f22e441098aa87ee\")\n for segment in m3u8_obj.segments.by_key( m3u8_obj.keys[-1] ):\n segment.key = new_key\n # Remember to sync the key from the list as well\n m3u8_obj.keys[-1] = new_key\n\n\n\nVariant playlists (variable bitrates)\n-------------------------------------\n\nA playlist can have a list to other playlist files, this is used to\nrepresent multiple bitrates videos, and it's called `variant streams`_.\nSee an `example here`_.\n\n.. code-block:: python\n\n variant_m3u8 = m3u8.loads('#EXTM3U8 ... contains a variant stream ...')\n variant_m3u8.is_variant # in this case will be True\n\n for playlist in variant_m3u8.playlists:\n playlist.uri\n playlist.stream_info.bandwidth\n\nthe playlist object used in the for loop above has a few attributes:\n\n- ``uri``: the url to the stream\n- ``stream_info``: a ``StreamInfo`` object (actually a namedtuple) with\n all the attributes available to `#EXT-X-STREAM-INF`_\n- ``media``: a list of related ``Media`` objects with all the attributes\n available to `#EXT-X-MEDIA`_\n- ``playlist_type``: the type of the playlist, which can be one of `VOD`_\n (video on demand) or `EVENT`_\n\n**NOTE: the following attributes are not implemented yet**, follow\n`issue 4`_ for updates\n\n- ``alternative_audios``: its an empty list, unless it's a playlist\n with `Alternative audio`_, in this case it's a list with ``Media``\n objects with all the attributes available to `#EXT-X-MEDIA`_\n- ``alternative_videos``: same as ``alternative_audios``\n\nA variant playlist can also have links to `I-frame playlists`_, which are used\nto specify where the I-frames are in a video. See `Apple's documentation`_ on\nthis for more information. These I-frame playlists can be accessed in a similar\nway to regular playlists.\n\n.. code-block:: python\n\n variant_m3u8 = m3u8.loads('#EXTM3U ... contains a variant stream ...')\n\n for iframe_playlist in variant_m3u8.iframe_playlists:\n iframe_playlist.uri\n iframe_playlist.iframe_stream_info.bandwidth\n\nThe iframe_playlist object used in the for loop above has a few attributes:\n\n- ``uri``: the url to the I-frame playlist\n- ``base_uri``: the base uri of the variant playlist (if given)\n- ``iframe_stream_info``: a ``StreamInfo`` object (same as a regular playlist)\n\nCustom tags\n-----------\n\nQuoting the documentation::\n\n Lines that start with the character '#' are either comments or tags.\n Tags begin with #EXT. They are case-sensitive. All other lines that\n begin with '#' are comments and SHOULD be ignored.\n\nThis library ignores all the non-standard tags by default. If you want them to be collected while loading the file content,\nyou need to pass a function to the `load/loads` functions, following the example below:\n\n.. code-block:: python\n\n import m3u8\n\n def get_movie(line, lineno, data, state):\n if line.startswith('#MOVIE-NAME:'):\n custom_tag = line.split(':')\n data['movie'] = custom_tag[1].strip()\n\n m3u8_obj = m3u8.load('http://videoserver.com/playlist.m3u8', custom_tags_parser=get_movie)\n print(m3u8_obj.data['movie']) # million dollar baby\n\n\nAlso you are able to override parsing of existing standard tags.\nTo achieve this your custom_tags_parser function have to return boolean True - it will mean that you fully implement parsing of current line therefore 'main parser' can go to next line.\n\n.. code-block:: python\n\n import re\n import m3u8\n from m3u8 import protocol\n from m3u8.parser import save_segment_custom_value\n\n\n def parse_iptv_attributes(line, lineno, data, state):\n # Customize parsing #EXTINF\n if line.startswith(protocol.extinf):\n title = ''\n chunks = line.replace(protocol.extinf + ':', '').split(',', 1)\n if len(chunks) == 2:\n duration_and_props, title = chunks\n elif len(chunks) == 1:\n duration_and_props = chunks[0]\n\n additional_props = {}\n chunks = duration_and_props.strip().split(' ', 1)\n if len(chunks) == 2:\n duration, raw_props = chunks\n matched_props = re.finditer(r'([\\w\\-]+)=\"([^\"]*)\"', raw_props)\n for match in matched_props:\n additional_props[match.group(1)] = match.group(2)\n else:\n duration = duration_and_props\n\n if 'segment' not in state:\n state['segment'] = {}\n state['segment']['duration'] = float(duration)\n state['segment']['title'] = title\n\n # Helper function for saving custom values\n save_segment_custom_value(state, 'extinf_props', additional_props)\n\n # Tell 'main parser' that we expect an URL on next lines\n state['expect_segment'] = True\n\n # Tell 'main parser' that it can go to next line, we've parsed current fully.\n return True\n\n\n if __name__ == '__main__':\n PLAYLIST = \"\"\"#EXTM3U\n #EXTINF:-1 timeshift=\"0\" catchup-days=\"7\" catchup-type=\"flussonic\" tvg-id=\"channel1\" group-title=\"Group1\",Channel1\n http://str00.iptv.domain/7331/mpegts?token=longtokenhere\n \"\"\"\n\n parsed = m3u8.loads(PLAYLIST, custom_tags_parser=parse_iptv_attributes)\n\n first_segment_props = parsed.segments[0].custom_parser_values['extinf_props']\n print(first_segment_props['tvg-id']) # 'channel1'\n print(first_segment_props['group-title']) # 'Group1'\n print(first_segment_props['catchup-type']) # 'flussonic'\n\nHelper functions get_segment_custom_value() and save_segment_custom_value() are intended for getting/storing your parsed values per segment into Segment class.\nAfter that all custom values will be accessible via property custom_parser_values of Segment instance.\n\nUsing different HTTP clients\n----------------------------\n\nIf you don't want to use urllib to download playlists, having more control on how objects are fetched over the internet,\nyou can use your own client. `requests` is a well known Python HTTP library and it can be used with `m3u8`:\n\n.. code-block:: python\n\n import m3u8\n import requests\n\n class RequestsClient():\n def download(self, uri, timeout=None, headers={}, verify_ssl=True):\n o = requests.get(uri, timeout=timeout, headers=headers)\n return o.text, o.url\n\n playlist = m3u8.load('http://videoserver.com/playlist.m3u8', http_client=RequestsClient())\n print(playlist.dumps())\n\nThe advantage of using a custom HTTP client is to refine SSL verification, proxies, performance, flexibility, etc.\n\nPlaylists behind proxies\n------------------------\n\nIn case you need to use a proxy but can't use a system wide proxy (HTTP/HTTPS proxy environment variables), you can pass your\nHTTP/HTTPS proxies as a dict to the load function.\n\n.. code-block:: python\n\n import m3u8\n\n proxies = {\n 'http': 'http://10.10.1.10:3128',\n 'https': 'http://10.10.1.10:1080',\n }\n\n http_client = m3u8.httpclient.DefaultHTTPClient(proxies)\n playlist = m3u8.load('http://videoserver.com/playlist.m3u8', http_client=http_client) # this could also be an absolute filename\n print(playlist.dumps())\n\nIt works with the default client only. Custom HTTP clients must implement this feature.\n\nRunning Tests\n=============\n\n.. code-block:: bash\n\n $ ./runtests\n\nContributing\n============\n\nAll contributions are welcome, but we will merge a pull request if, and only if, it\n\n- has tests\n- follows the code conventions\n\nIf you plan to implement a new feature or something that will take more\nthan a few minutes, please open an issue to make sure we don't work on\nthe same thing.\n\n.. _m3u8: https://tools.ietf.org/html/rfc8216\n.. _#EXT-X-VERSION: https://tools.ietf.org/html/rfc8216#section-4.3.1.2\n.. _#EXTINF: https://tools.ietf.org/html/rfc8216#section-4.3.2.1\n.. _#EXT-X-ALLOW-CACHE: https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-07#section-3.3.6\n.. _#EXT-X-BITRATE: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.4.8\n.. _#EXT-X-BYTERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.2\n.. _#EXT-X-DISCONTINUITY: https://tools.ietf.org/html/rfc8216#section-4.3.2.3\n.. _#EXT-X-KEY: https://tools.ietf.org/html/rfc8216#section-4.3.2.4\n.. _#EXT-X-MAP: https://tools.ietf.org/html/rfc8216#section-4.3.2.5\n.. _#EXT-X-PROGRAM-DATE-TIME: https://tools.ietf.org/html/rfc8216#section-4.3.2.6\n.. _#EXT-X-DATERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.7\n.. _#EXT-X-TARGETDURATION: https://tools.ietf.org/html/rfc8216#section-4.3.3.1\n.. _#EXT-X-MEDIA-SEQUENCE: https://tools.ietf.org/html/rfc8216#section-4.3.3.2\n.. _#EXT-X-DISCONTINUITY-SEQUENCE: https://tools.ietf.org/html/rfc8216#section-4.3.3.3\n.. _#EXT-X-ENDLIST: https://tools.ietf.org/html/rfc8216#section-4.3.3.4\n.. _#EXT-X-PLAYLIST-TYPE: https://tools.ietf.org/html/rfc8216#section-4.3.3.5\n.. _#EXT-X-I-FRAMES-ONLY: https://tools.ietf.org/html/rfc8216#section-4.3.3.6\n.. _#EXT-X-MEDIA: https://tools.ietf.org/html/rfc8216#section-4.3.4.1\n.. _#EXT-X-STREAM-INF: https://tools.ietf.org/html/rfc8216#section-4.3.4.2\n.. _#EXT-X-I-FRAME-STREAM-INF: https://tools.ietf.org/html/rfc8216#section-4.3.4.3\n.. _#EXT-X-IMAGES-ONLY: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf\n.. _#EXT-X-IMAGE-STREAM-INF: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf\n.. _#EXT-X-TILES: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf\n.. _#EXT-X-SESSION-DATA: https://tools.ietf.org/html/rfc8216#section-4.3.4.4\n.. _#EXT-X-SESSION-KEY: https://tools.ietf.org/html/rfc8216#section-4.3.4.5\n.. _#EXT-X-INDEPENDENT-SEGMENTS: https://tools.ietf.org/html/rfc8216#section-4.3.5.1\n.. _#EXT-X-START: https://tools.ietf.org/html/rfc8216#section-4.3.5.2\n.. _#EXT-X-PRELOAD-HINT: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.5.3\n.. _#EXT-X-DATERANGE: https://tools.ietf.org/html/rfc8216#section-4.3.2.7\n.. _#EXT-X-GAP: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.2.7\n.. _#EXT-X-CONTENT-STEERING: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-10#section-4.4.6.64\n.. _#EXT-X-SKIP: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.2\n.. _#EXT-X-RENDITION-REPORT: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.4\n.. _#EXT-X-PART: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.4.9\n.. _#EXT-X-PART-INF: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.3.7\n.. _#EXT-X-SERVER-CONTROL: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.3.8\n.. _issue 1: https://github.com/globocom/m3u8/issues/1\n.. _variant streams: https://tools.ietf.org/html/rfc8216#section-6.2.4\n.. _example here: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-8.5\n.. _issue 4: https://github.com/globocom/m3u8/issues/4\n.. _I-frame playlists: https://tools.ietf.org/html/rfc8216#section-4.3.4.3\n.. _Apple's documentation: https://developer.apple.com/library/ios/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-I_FRAME_PLAYLIST\n.. _Alternative audio: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-8.7\n.. _VOD: https://developer.apple.com/library/mac/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-TNTAG2\n.. _EVENT: https://developer.apple.com/library/mac/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-EVENT_PLAYLIST\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python m3u8 parser",
"version": "4.0.0",
"project_urls": {
"Homepage": "https://github.com/globocom/m3u8"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "35291f9231f0d70bba04bb39a78703c9ac09739479cfc8c69575134b3d7e5fa5",
"md5": "7cad208e16c28c3017e9328d8829ecc5",
"sha256": "f4fbb2874c0c1632833a789025d9ce90ee43e3ad417150b1d61741bcfd747243"
},
"downloads": -1,
"filename": "m3u8-4.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7cad208e16c28c3017e9328d8829ecc5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 25349,
"upload_time": "2023-12-12T23:16:55",
"upload_time_iso_8601": "2023-12-12T23:16:55.679085Z",
"url": "https://files.pythonhosted.org/packages/35/29/1f9231f0d70bba04bb39a78703c9ac09739479cfc8c69575134b3d7e5fa5/m3u8-4.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "d4f310e8d8879f284c17ea658a9ddca3b4f9958697d8fd3e50d149133f2a3dc7",
"md5": "decf53da765e00b3e354535a88f013f5",
"sha256": "32cb3300c702e802944427ecefbe5006e09f7fb5578334ce33f01e2e4bf05470"
},
"downloads": -1,
"filename": "m3u8-4.0.0.tar.gz",
"has_sig": false,
"md5_digest": "decf53da765e00b3e354535a88f013f5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 45474,
"upload_time": "2023-12-12T23:16:58",
"upload_time_iso_8601": "2023-12-12T23:16:58.596046Z",
"url": "https://files.pythonhosted.org/packages/d4/f3/10e8d8879f284c17ea658a9ddca3b4f9958697d8fd3e50d149133f2a3dc7/m3u8-4.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-12-12 23:16:58",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "globocom",
"github_project": "m3u8",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "m3u8"
}