|
|
|
@ -3,13 +3,14 @@ |
|
|
|
|
import sys |
|
|
|
|
import json |
|
|
|
|
import argparse |
|
|
|
|
import re |
|
|
|
|
import requests |
|
|
|
|
|
|
|
|
|
__author__ = "Meliurwen" |
|
|
|
|
__version__ = "0.1.1" |
|
|
|
|
__license__ = "GPL3" |
|
|
|
|
|
|
|
|
|
def get_link_m3u(source, channel, chan_dict): |
|
|
|
|
def gen_link_m3u(chan_dict, source, channel): |
|
|
|
|
|
|
|
|
|
headers = { |
|
|
|
|
"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 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if content["status_code"] == 200: |
|
|
|
|
ugly_payload = json.loads(content["content"].decode('unicode_escape')[1:-1]) |
|
|
|
|
if content["status_code"] != 200: |
|
|
|
|
print( |
|
|
|
|
ugly_payload["data"]["attributes"]["streaming"]["hls"]["url"], |
|
|
|
|
sep='', |
|
|
|
|
end='\n', |
|
|
|
|
file=sys.stdout, |
|
|
|
|
flush=False |
|
|
|
|
) |
|
|
|
|
sys.exit(0) |
|
|
|
|
else: |
|
|
|
|
print( |
|
|
|
|
"[ERROR] Http status code: {stat_code}".format(stat_code=content["status_code"]), |
|
|
|
|
"[ERROR] Http status code {stat_code} " |
|
|
|
|
"for {source}:{channel} at {url}".format( |
|
|
|
|
stat_code=content["status_code"], |
|
|
|
|
source=source, |
|
|
|
|
channel=channel, |
|
|
|
|
url=content["url"]), |
|
|
|
|
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): |
|
|
|
|
|
|
|
|
@ -60,7 +60,9 @@ def is_supp_source(chan_dict, source): |
|
|
|
|
"[ERROR] Source not supported: {source}".format(source=source), |
|
|
|
|
file=sys.stderr |
|
|
|
|
) |
|
|
|
|
sys.exit(1) |
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
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), |
|
|
|
|
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: |
|
|
|
|
is_supp_source(chan_dict, source) |
|
|
|
|
print(*chan_dict[source].keys(), sep = "\n") |
|
|
|
|
if not is_supp_source(chan_dict, source): |
|
|
|
|
return False |
|
|
|
|
for channel in chan_dict[source].keys(): |
|
|
|
|
print("{source}:{channel}".format(source=source, channel=channel)) |
|
|
|
|
is_success = True |
|
|
|
|
else: |
|
|
|
|
print(*chan_dict.keys(), sep = "\n") |
|
|
|
|
|
|
|
|
|
sys.exit(1) |
|
|
|
|
for src in chan_dict.keys(): |
|
|
|
|
for channel in chan_dict[src].keys(): |
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
source = arguments.source[0] if args.source else None |
|
|
|
|
channel = arguments.channel[0] if args.channel else None |
|
|
|
|
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: |
|
|
|
|
chan_dict = json.loads(myfile.read()) |
|
|
|
|
|
|
|
|
|
if source and channel: |
|
|
|
|
is_supp_source(chan_dict, source) |
|
|
|
|
is_supp_channel(chan_dict, source, channel) |
|
|
|
|
get_link_m3u(source, channel, chan_dict) |
|
|
|
|
if not (is_supp_source(chan_dict, source) or is_supp_channel(chan_dict, source, channel)): |
|
|
|
|
sys.exit(1) |
|
|
|
|
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: |
|
|
|
|
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__": |
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser( |
|
|
|
|
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.' |
|
|
|
|
) |
|
|
|
|
parser.add_argument( |
|
|
|
@ -115,13 +173,19 @@ if __name__ == "__main__": |
|
|
|
|
"-c", "--channel", |
|
|
|
|
nargs=1, |
|
|
|
|
required=False, |
|
|
|
|
help="TV channel name" |
|
|
|
|
help="channel name" |
|
|
|
|
) |
|
|
|
|
parser.add_argument( |
|
|
|
|
"-l", "--list", |
|
|
|
|
required=False, |
|
|
|
|
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( |
|
|
|
|
"--version", |
|
|
|
@ -134,11 +198,14 @@ if __name__ == "__main__": |
|
|
|
|
if len(sys.argv) <= 1: |
|
|
|
|
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: |
|
|
|
|
parser.error("--source is required for this parameter") |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
parser.error("--list should either be used singularly or with --source") |
|
|
|
|