Added ability to generate a playlist, added use cases to README

master
Meliurwen 4 years ago
parent 92e983d7b5
commit 3bb3cab83b
Signed by: meliurwen
GPG Key ID: 818A8B35E9F1CE10
  1. 26
      README.md
  2. 11
      channels.json
  3. 127
      iptv_network_extract.py

@ -22,7 +22,13 @@ Extracts original m3u8 links from multiple IPTV networks.
## Use ## Use
To list the supported _sources_: To show _help_ message:
```sh
./iptv_network_extract.py -h
```
To list the supported _sources_ with their _channels_:
```sh ```sh
./iptv_network_extract.py -l ./iptv_network_extract.py -l
@ -45,3 +51,21 @@ You can also pipe the link directly to a player like `mpv`:
```sh ```sh
./iptv_network_extract.py -s SOURCE -c CHANNEL | mpv --playlist=- ./iptv_network_extract.py -s SOURCE -c CHANNEL | mpv --playlist=-
``` ```
To generate a playlist piping in a list of channels:
```sh
./iptv_network_extract.py -l | ./iptv_network_extract.py -p > /tmp/playlist.m3u8
```
You can also wire everything together like this:
```sh
./iptv_network_extract.py s- dplay-it -l | ./iptv_network_extract.py -p > /tmp/playlist.m3u8 && mpv --playlist=/tmp/playlist.m3u8
```
Or if you prefer record the streams:
```sh
ffmpeg -i "$(./iptv_network_extract.py -s dplay-it -c dmax)" -bsf:a aac_adtstoasc -vcodec copy -c copy file.mp4
```

@ -1,66 +1,77 @@
{ {
"dplay-it": { "dplay-it": {
"realtime":{ "realtime":{
"name": "Real Time",
"url":"https://it.dplay.com/ajax/playbackjson/channel/2", "url":"https://it.dplay.com/ajax/playbackjson/channel/2",
"data":{ "data":{
"referer":"https://it.dplay.com/realtime/" "referer":"https://it.dplay.com/realtime/"
} }
}, },
"nove":{ "nove":{
"name": "NOVE",
"url":"https://it.dplay.com/ajax/playbackjson/channel/3", "url":"https://it.dplay.com/ajax/playbackjson/channel/3",
"data":{ "data":{
"referer":"https://it.dplay.com/nove/" "referer":"https://it.dplay.com/nove/"
} }
}, },
"dmax":{ "dmax":{
"name": "DMAX",
"url":"https://it.dplay.com/ajax/playbackjson/channel/5", "url":"https://it.dplay.com/ajax/playbackjson/channel/5",
"data":{ "data":{
"referer":"https://it.dplay.com/dmax/" "referer":"https://it.dplay.com/dmax/"
} }
}, },
"giallo":{ "giallo":{
"name": "Giallo",
"url":"https://it.dplay.com/ajax/playbackjson/channel/6", "url":"https://it.dplay.com/ajax/playbackjson/channel/6",
"data":{ "data":{
"referer":"https://it.dplay.com/giallo/" "referer":"https://it.dplay.com/giallo/"
} }
}, },
"k-2":{ "k-2":{
"name": "K2",
"url":"https://it.dplay.com/ajax/playbackjson/channel/7", "url":"https://it.dplay.com/ajax/playbackjson/channel/7",
"data":{ "data":{
"referer":"https://it.dplay.com/k-2/" "referer":"https://it.dplay.com/k-2/"
} }
}, },
"frisbee":{ "frisbee":{
"name": "Frisbee",
"url":"https://it.dplay.com/ajax/playbackjson/channel/8", "url":"https://it.dplay.com/ajax/playbackjson/channel/8",
"data":{ "data":{
"referer":"https://it.dplay.com/frisbee/" "referer":"https://it.dplay.com/frisbee/"
} }
}, },
"motor-trend":{ "motor-trend":{
"name": "Motor Trend",
"url":"https://it.dplay.com/ajax/playbackjson/channel/19", "url":"https://it.dplay.com/ajax/playbackjson/channel/19",
"data":{ "data":{
"referer":"https://it.dplay.com/motor-trend/" "referer":"https://it.dplay.com/motor-trend/"
} }
}, },
"food-network":{ "food-network":{
"name": "Food Network",
"url":"https://it.dplay.com/ajax/playbackjson/channel/21", "url":"https://it.dplay.com/ajax/playbackjson/channel/21",
"data":{ "data":{
"referer":"https://it.dplay.com/food-network/" "referer":"https://it.dplay.com/food-network/"
} }
}, },
"home-and-garden-tv":{ "home-and-garden-tv":{
"name": "Home and garden TV",
"url":"https://it.dplay.com/ajax/playbackjson/channel/36", "url":"https://it.dplay.com/ajax/playbackjson/channel/36",
"data":{ "data":{
"referer":"https://it.dplay.com/home-and-garden-tv/" "referer":"https://it.dplay.com/home-and-garden-tv/"
} }
}, },
"eurosport1":{ "eurosport1":{
"name": "Eurosport1",
"url":"https://it.dplay.com/ajax/playbackjson/channel/49", "url":"https://it.dplay.com/ajax/playbackjson/channel/49",
"data":{ "data":{
"referer":"https://it.dplay.com/eurosport1/" "referer":"https://it.dplay.com/eurosport1/"
} }
}, },
"eurosport2":{ "eurosport2":{
"name": "Eurosport2",
"url":"https://it.dplay.com/ajax/playbackjson/channel/50", "url":"https://it.dplay.com/ajax/playbackjson/channel/50",
"data":{ "data":{
"referer":"https://it.dplay.com/eurosport2/" "referer":"https://it.dplay.com/eurosport2/"

@ -3,13 +3,14 @@
import sys import sys
import json import json
import argparse import argparse
import re
import requests import requests
__author__ = "Meliurwen" __author__ = "Meliurwen"
__version__ = "0.1.1" __version__ = "0.1.1"
__license__ = "GPL3" __license__ = "GPL3"
def get_link_m3u(source, channel, chan_dict): def gen_link_m3u(chan_dict, source, channel):
headers = { headers = {
"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; rv:75.0) " "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; rv:75.0) "
@ -36,22 +37,21 @@ def get_link_m3u(source, channel, chan_dict):
"url": content.url "url": content.url
} }
if content["status_code"] == 200: if content["status_code"] != 200:
ugly_payload = json.loads(content["content"].decode('unicode_escape')[1:-1])
print( print(
ugly_payload["data"]["attributes"]["streaming"]["hls"]["url"], "[ERROR] Http status code {stat_code} "
sep='', "for {source}:{channel} at {url}".format(
end='\n', stat_code=content["status_code"],
file=sys.stdout, source=source,
flush=False channel=channel,
) url=content["url"]),
sys.exit(0)
else:
print(
"[ERROR] Http status code: {stat_code}".format(stat_code=content["status_code"]),
file=sys.stderr file=sys.stderr
) )
sys.exit(1) return None
ugly_payload = json.loads(content["content"].decode('unicode_escape')[1:-1])
return ugly_payload["data"]["attributes"]["streaming"]["hls"]["url"]
def is_supp_source(chan_dict, source): def is_supp_source(chan_dict, source):
@ -60,7 +60,9 @@ def is_supp_source(chan_dict, source):
"[ERROR] Source not supported: {source}".format(source=source), "[ERROR] Source not supported: {source}".format(source=source),
file=sys.stderr file=sys.stderr
) )
sys.exit(1) return False
return True
def is_supp_channel(chan_dict, source, channel): def is_supp_channel(chan_dict, source, channel):
@ -69,40 +71,96 @@ def is_supp_channel(chan_dict, source, channel):
"[ERROR] Channel not supported: {channel}".format(channel=channel), "[ERROR] Channel not supported: {channel}".format(channel=channel),
file=sys.stderr file=sys.stderr
) )
sys.exit(1) return False
return True
def gen_list(source, chan_dict):
def get_list(source, chan_dict): is_success = False
if source: if source:
is_supp_source(chan_dict, source) if not is_supp_source(chan_dict, source):
print(*chan_dict[source].keys(), sep = "\n") return False
for channel in chan_dict[source].keys():
print("{source}:{channel}".format(source=source, channel=channel))
is_success = True
else: else:
print(*chan_dict.keys(), sep = "\n") for src in chan_dict.keys():
for channel in chan_dict[src].keys():
sys.exit(1) print("{source}:{channel}".format(source=src, channel=channel))
is_success = True
return is_success
def gen_playlist(chan_dict):
is_success = False
re_list = re.compile('^([a-z0-9-_]{1,32}):([a-z0-9-_]{1,32})\n?$', re.IGNORECASE)
is_first_line = True
for line in sys.stdin:
stream = re_list.findall(line)
if stream:
if is_first_line:
print("#EXTM3U")
is_first_line = False
source, channel = stream[0][0], stream[0][1]
if is_supp_source(chan_dict, source) and is_supp_channel(chan_dict, source, channel):
link = gen_link_m3u(chan_dict, source, channel)
if link:
print("#EXTINF:-1"
"group-title=\"{source}\",{chanName}".format(
source=source,
chanName=chan_dict[source][channel]["name"])
)
print(link)
is_success = True
if not is_success:
print("[ERROR] No playlist created because empty", file=sys.stderr)
return False
return True
def main(arguments): def main(arguments):
source = arguments.source[0] if args.source else None source = arguments.source[0] if args.source else None
channel = arguments.channel[0] if args.channel else None channel = arguments.channel[0] if args.channel else None
is_list = arguments.list if args.list else False is_list = arguments.list if args.list else False
is_playlist = arguments.playlist if args.playlist else False
with open('channels.json', 'r') as myfile: with open('channels.json', 'r') as myfile:
chan_dict = json.loads(myfile.read()) chan_dict = json.loads(myfile.read())
if source and channel: if source and channel:
is_supp_source(chan_dict, source) if not (is_supp_source(chan_dict, source) or is_supp_channel(chan_dict, source, channel)):
is_supp_channel(chan_dict, source, channel) sys.exit(1)
get_link_m3u(source, channel, chan_dict) link = gen_link_m3u(chan_dict, source, channel)
if link:
print(
link,
sep='',
end='\n',
file=sys.stdout,
flush=False
)
else:
sys.exit(1)
if is_list: if is_list:
get_list(source, chan_dict) if not gen_list(source, chan_dict):
sys.exit(1)
if is_playlist:
if not gen_playlist(chan_dict):
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog='IPTV Network Extract', prog='IPTV Network Extract',
usage='%(prog)s [-h] [[-s SOURCE -c CHANNEL] | [-s SOURCE -l]] [-l] [--version]', usage='%(prog)s [-h] [-s SOURCE [-c CHANNEL | -l]] [-l] [-p] [--version]',
description='Extracts original m3u8 links from multiple IPTV networks.' description='Extracts original m3u8 links from multiple IPTV networks.'
) )
parser.add_argument( parser.add_argument(
@ -115,13 +173,19 @@ if __name__ == "__main__":
"-c", "--channel", "-c", "--channel",
nargs=1, nargs=1,
required=False, required=False,
help="TV channel name" help="channel name"
) )
parser.add_argument( parser.add_argument(
"-l", "--list", "-l", "--list",
required=False, required=False,
action='store_true', action='store_true',
help="List supported networks and channels" help="list supported networks and channels"
)
parser.add_argument(
"-p", "--playlist",
required=False,
action='store_true',
help="generate a m3u8 playlist from stdin"
) )
parser.add_argument( parser.add_argument(
"--version", "--version",
@ -134,11 +198,14 @@ if __name__ == "__main__":
if len(sys.argv) <= 1: if len(sys.argv) <= 1:
parser.error("at least 1 argument is required") parser.error("at least 1 argument is required")
if len(sys.argv) > 2 and args.playlist:
parser.error("--playlist must be a single parameter")
if len(sys.argv) == 3 and args.channel: if len(sys.argv) == 3 and args.channel:
parser.error("--source is required for this parameter") parser.error("--source is required for this parameter")
if args.source and not bool(args.channel) ^ bool(args.list): if args.source and not bool(args.channel) ^ bool(args.list):
parser.error("--source requires either --channel CHANNEL or --list") parser.error("--source requires either --channel or --list")
if args.list and args.channel: if args.list and args.channel:
parser.error("--list should either be used singularly or with --source") parser.error("--list should either be used singularly or with --source")

Loading…
Cancel
Save