Package: m3u-ipytv
A python3 library to parse IPTV playlists in the M3U Plus format.M3U Plus and IPTVThe M3U Plus format is ade factostandard for distributing IPTV playlists on
the Internet.The termsIPTV playlistandM3U Plus playlistare generally used
interchangeably, but in this repositoryM3U Plusrefers to the data format,
whileIPTV Playlistrefers to playlists in M3U Plus format.M3U Plus stems from theextended M3U8format, of
which it supports only 2 tags (#EXTM3Uand#EXTINF).The syntax of the#EXTM3Uand#EXTINFtags has been modified to include
extra attributes (e.g., logo, group, language). Unfortunately this has broken
the backward compatibility with the original M3U8 standard (as explained in
detailhere).This library has been created from scratch to parse and handle the M3U Plus
format only. It does not fully support regular M3U8 playlists (only basic
channel attributes are parsed).Supported tagsOnly#EXTM3U,#EXTINFand plain url rows are supported (i.e. they are parsed
and their value is made available as properties of anIPTVChannelobject).All tags that are found between an#EXTINFrow and its related url row are
added asextrasto a channel, but without performing any parsing (i.e. they're
treated like plain strings).In the example below, the#EXTVLCOPTrow is not parsed, but copiedas-is:#EXTINF:-1 tvg-id="" tvg-name="hello" tvg-country="IT" tvg-url="" group-title="Greetings",Hello!
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0 
https://my-website.com/vod/hello.tsInstallationThis library requires Python 3 (and the relatedpipinstaller).PLEASE NOTE: the library makes use of themultiprocessing.Poolclass that
requires some care when working with theIDLEenvironment.To install the library system-wide, run:pipinstallm3u-ipytvTo install it within a virtual environment, run:python-mvenv.venvsource.venv/bin/activate
pipinstallm3u-ipytvUsageModulesThe library comprises several modules, each with a specific area of competence:channelEverything related to the handling of channels in a playlist.doctorA collection of functions to fix common errors found in M3U files.exceptionsAll the exceptions thrown by the library.m3uConstants and functions related to M3U files.playlistEverything related to the loading and handling of M3U playlists.Loading an IPTV PlaylistFrom a fileUse theplaylist.loadf(file)function:fromipytvimportplaylistfile="~/Documents/my_playlist.m3u"pl=playlist.loadf(file)print(pl.length())from a URLUse theplaylist.loadu(url)function:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/classic.m3u"pl=playlist.loadu(url)print(pl.length())From a stringUse theplaylist.loads(string)function:fromipytvimportplayliststring="""#EXTM3U#EXTINF:-1 tvg-id="Rai 1" tvg-name="Rai 1" group-title="RAI",Rai 1http://myown.link:80/luke/210274/78482"""pl=playlist.loads(string)print(pl.length())From a listUse theplaylist.loadl(rows)function:fromipytvimportplaylistrows=['#EXTM3U','#EXTINF:-1 tvg-id="Rai 1" tvg-name="Rai 1" group-title="RAI",Rai 1','http://myown.link:80/luke/210274/78482']pl=playlist.loadl(rows)print(pl.length())M3UPlaylist classEvery load function above returns an object of theM3UPlaylistclass.This class models the concept of a playlist (which is, basically, a list of
channels) and offers methods to interact with the playlist itself and with its
channels.There are two main properties in a playlist, and they are:AttributesChannelsWhat these properties are and how they can be accessed is described in the next
paragraphs.Accessing the attributes of a playlistKey-value pairs that are specified in the#EXTM3Urow are treated as
playlist-wide attributes (i.e. they apply to the playlist itself or to every
channel in the playlist).For example thex-tvg-urlpart below:#EXTM3U x-tvg-url="http://myown.link:80/luke/220311/22311"These attributes, in the form of a dictionary, can be accessed via theget_attributes()method:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/kids.m3u"pl=playlist.loadu(url)attributes=pl.get_attributes()fork,vinattributes.items():print(f'"{k}": "{v}"')In alternative, when the name of the property is known beforehand, its value can
be retrieved with:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/kids.m3u"pl=playlist.loadu(url)attributes=pl.get_attributes()tvg_url=pl.get_attribute("x-tvg-url")print(f"x-tvg-url:{tvg_url}")The attributes can also be added, modified and removed by using the following
methods:fromipytv.playlistimportM3UPlaylistpl=M3UPlaylist()attribute_name='tvg-shift'# Add the 'tvg-shift' attribute and set it to 1pl.add_attribute(attribute_name,"1")# Update the 'tvg-shift' attribute to -2pl.update_attribute(attribute_name,"-2")# Completely remove the 'tvg-shift' attributevalue_before_deletion=pl.remove_attribute(attribute_name)There is also a method that allows to add multiple attributes at once (instead
of single attributes) in the form of a dictionary:pl.add_attributes({})Accessing the channels of a playlistTheM3UPlaylistclass is basically a list of channels with some commodity
functions. The channels in a playlist can be accessed by using one of the
following methods.Individually (by index)By using theget_channel(index)method:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/classic.m3u"pl=playlist.loadu(url)# Let's retrieve the first channel in the listchannel=pl.get_channel(0)print(f'channel\"{channel.name}\":{channel.url}')# The next line will throw IndexOutOfBoundsExceptionchannel=pl.get_channel(-1)IterativelyBy looping over the channels in anM3UPlaylistobject:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/classic.m3u"pl=playlist.loadu(url)forchannelinpl:print(f'channel\"{channel.name}\":{channel.url}')Low levelIn all cases where the previous two access methods are not sufficient, the inner
channel list can be accessed via theget_channels()method:fromipytvimportplaylisturl="https://iptv-org.github.io/iptv/categories/classic.m3u"pl=playlist.loadu(url)chan_list=pl.get_channels()ten_channels=chan_list[:10]The channels can also be added, modified and removed by using the following
methods:fromipytv.playlistimportM3UPlaylistfromipytv.channelimportIPTVChannelpl=M3UPlaylist()channel=IPTVChannel()# Add a channel to the end of the list (last index)pl.append_channel(channel)# Insert a channel in the specified position (all succeeding channels are# shifted right by 1 position)pl.insert_channel(0,channel)new_channel=IPTVChannel()# Replace the second channel of the playlist with a new channelpl.update_channel(1,new_channel)# Remove the channel at the specified position (all succeeding channels are# shifted left by 1 position)old_channel=pl.remove_channel(0)There are also two methods that allow to add list of channels (instead of single
channels):pl.append_channels([])pl.insert_channels([])Accessing the properties of a channelTheget_channels()method of an M3UPlaylist object returns a list of objects
of theIPTVChannelclass.AnIPTVChannelobject has 3 basic properties (url,nameandduration) and two optional fields:attributes(a dictionary) andextras(a list of strings).For example:fromipytv.channelimportIPTVAttr,IPTVChannelchannel=IPTVChannel(url="http://myown.link:80/luke/210274/78482",name="Rai 1",duration="-1",attributes={IPTVAttr.TVG_ID.value:"Rai 1",IPTVAttr.TVG_NAME.value:"Rai 1",IPTVAttr.TVG_LOGO.value:"https://static.epg.best/it/RaiUno.it.png",IPTVAttr.GROUP_TITLE.value:"RAI"},extras=['#EXTVLCOPT:http-user-agent=Lavf53.32.100'])print(channel.name)print(channel.attributes[IPTVAttr.GROUP_TITLE.value])print(channel.extras[0])TheIPTVAttrenum class contains attribute names that are commonly found in
IPTV Playlists.ThedoctormoduleInternet-sourced IPTV playlists, often contain a number of format errors. This
module aims to detect and fix some of these errors.The module contains three classes, each with its own scope:M3UDoctorIt contains methods to fix errors in m3u files (i.e. errors that would
make it impossible to load an m3u file as a playlist).IPTVChannelDoctorIt contains methods to fix errors in a channel (i.e. errors in the
attributes of an #EXTINF row).M3UPlaylistDoctorIt applies the fixes inIPTVChannelDoctorto all channels in the
playlist.All the classes above offer one public, static method namedsanitize()that is
in charge of applying all different fixes. It can be used as follows:fromipytv.doctorimportM3UDoctor,M3UPlaylistDoctorfromipytvimportplaylistwithopen('my-broken-playlist.m3u',encoding='utf-8')asin_file:content=in_file.readlines()fixed_content=M3UDoctor.sanitize(content)pl=playlist.loadl(fixed_content)fixed_pl=M3UPlaylistDoctor.sanitize(pl)withopen('my-fixed-playlist.m3u','w',encoding='utf-8')asout_file:content=fixed_pl.to_m3u_plus_playlist()out_file.write(content)LoggingIPyTV supports python's
standardlogging system.To enable IPyTV's logging, add a logging configuration to your application:importloggingfromipytvimportplaylistlogging.basicConfig(level=logging.INFO)pl=playlist.loadu("https://iptv-org.github.io/iptv/categories/classic.m3u")Format considerationsThe extensions to the#EXTM3Uand#EXTINFtags introduced by the M3U Plus
format have broken the compatibility with the M3U8 format.This is what a standard#EXTINFrow should look like:#EXTINF:-1,Rai 1Theformatis pretty
straightforward:#EXTINF:<duration>,[<title>]Let's break it down:The#EXTINF:tag.The duration of the content (as an integer or float, signed or not).A comma character.A title.This is what an#EXTINFrow in the M3U Plus format looks like:#EXTINF:-1 tvg-id="Rai 1" tvg-name="Rai 1" tvg-logo="https://static.epg.best/it/RaiUno.it.png" group-title="RAI",Rai 1If we break it down, we see that points 3. and 4. below have been added (and
they break the previous definition for the#EXTINFtag):The#EXTINF:tag.The duration of the content (as an integer or float, signed or not).A space.A variable-length, space-separated list of attributes.A comma character.A title.The attributes in point 4 are in theattribute="value"format, wherevaluemay also contain non-escaped commas and, sometimes, even unescaped double
quotes. This really complicates the parsing logic.It's worth noting that the M3U8 RFC document specifies howattribute listsshould be
formatted, but the M3U Plus implementation doesn't comply with the standard.In conclusion, the M3U Plus format with its quirks and idiosyncrasies is hard to
read for humans and hard to parse for computers. It's an ugly format, but it's
too widespread to be ignored and for Python to lack a parsing library.On a funny note, this is how the VLC programmers named theparsing functionfor the IPTV playlists in the M3U Plus format:staticvoidparseEXTINFIptvDiots(...)Just saying... :sweat_smile:LicenseThis project is licensed under the terms of the MIT license.SeeLICENSE.txtfor details.