]> code.delx.au - webdl/blob - iview.py
Add support for ABC iView HLS livestreams
[webdl] / iview.py
1 from common import append_to_qs, grab_json, grab_text, Node, download_hls
2 import hashlib
3 import hmac
4 import requests_cache
5 import string
6 import time
7 import urllib.parse
8
9 BASE_URL = "https://iview.abc.net.au"
10 API_URL = "https://iview.abc.net.au/api"
11
12 def format_episode_title(series, ep):
13 if ep:
14 return series + " " + ep
15 else:
16 return series
17
18 def add_episode(parent, ep_info):
19 video_key = ep_info["episodeHouseNumber"]
20 series_title = ep_info["seriesTitle"]
21 title = ep_info.get("title", None)
22 episode_title = format_episode_title(series_title, title)
23
24 IviewEpisodeNode(episode_title, parent, video_key)
25
26 class IviewEpisodeNode(Node):
27 def __init__(self, title, parent, video_key):
28 Node.__init__(self, title, parent)
29 self.video_key = video_key
30 self.filename = title + ".ts"
31 self.can_download = True
32
33 def find_hls_url(self, playlist):
34 for video in playlist:
35 if video["type"] in ["program", "livestream"]:
36 streams = video["streams"]["hls"]
37 for quality in ["720", "sd", "sd-low"]:
38 if quality in streams:
39 return streams[quality]
40 raise Exception("Missing program stream for " + self.video_key + " -- " + self.title)
41
42 def get_auth_token(self):
43 path = "/auth/hls/sign?ts=%s&hn=%s&d=android-tablet" % (int(time.time()), self.video_key)
44 sig = hmac.new(b'android.content.res.Resources', path.encode("utf-8"), hashlib.sha256).hexdigest()
45 auth_url = BASE_URL + path + "&sig=" + sig
46 with requests_cache.disabled():
47 auth_token = grab_text(auth_url)
48 return auth_token
49
50 def download(self):
51 info = grab_json(API_URL + "/programs/" + self.video_key)
52 if "playlist" not in info:
53 return False
54 video_url = self.find_hls_url(info["playlist"])
55 auth_token = self.get_auth_token()
56 video_url = append_to_qs(video_url, {"hdnea": auth_token})
57 return download_hls(self.filename, video_url)
58
59
60 class IviewIndexNode(Node):
61 def __init__(self, title, parent, url):
62 Node.__init__(self, title, parent)
63 self.url = url
64 self.unique_series = set()
65
66 def fill_children(self):
67 info = grab_json(self.url)
68 for key in ["carousels", "collections", "index"]:
69 for collection_list in info.get(key, None):
70 if isinstance(collection_list, dict):
71 for ep_info in collection_list.get("episodes", []):
72 self.add_series(ep_info)
73
74 def add_series(self, ep_info):
75 title = ep_info["seriesTitle"]
76 if title in self.unique_series:
77 return
78 self.unique_series.add(title)
79 url = API_URL + "/" + ep_info["href"]
80 IviewSeriesNode(title, self, url)
81
82 class IviewSeriesNode(Node):
83 def __init__(self, title, parent, url):
84 Node.__init__(self, title, parent)
85 self.url = url
86
87 def fill_children(self):
88 ep_info = grab_json(self.url)
89 series_slug = ep_info["href"].split("/")[1]
90 series_url = API_URL + "/series/" + series_slug + "/" + ep_info["seriesHouseNumber"]
91 info = grab_json(series_url)
92 for ep_info in info.get("episodes", []):
93 add_episode(self, ep_info)
94
95 class IviewFlatNode(Node):
96 def __init__(self, title, parent, url):
97 Node.__init__(self, title, parent)
98 self.url = url
99
100 def fill_children(self):
101 info = grab_json(self.url)
102 for ep_info in info:
103 add_episode(self, ep_info)
104
105
106 class IviewRootNode(Node):
107 def load_categories(self):
108 by_category_node = Node("By Category", self)
109
110 data = grab_json(API_URL + "/categories")
111 categories = data["categories"]
112
113 for category_data in categories:
114 category_title = category_data["title"]
115 category_title = string.capwords(category_title)
116
117 category_href = category_data["href"]
118
119 IviewIndexNode(category_title, by_category_node, API_URL + "/" + category_href)
120
121 def load_channels(self):
122 by_channel_node = Node("By Channel", self)
123
124 data = grab_json(API_URL + "/channel")
125 channels = data["channels"]
126
127 for channel_data in channels:
128 channel_id = channel_data["categoryID"]
129 channel_title = {
130 "abc1": "ABC1",
131 "abc2": "ABC2",
132 "abc3": "ABC3",
133 "abc4kids": "ABC4Kids",
134 "news": "News",
135 "abcarts": "ABC Arts",
136 }.get(channel_id, channel_data["title"])
137
138 channel_href = channel_data["href"]
139
140 IviewIndexNode(channel_title, by_channel_node, API_URL + "/" + channel_href)
141
142 def load_featured(self):
143 IviewFlatNode("Featured", self, API_URL + "/featured")
144
145 def fill_children(self):
146 self.load_categories()
147 self.load_channels()
148 self.load_featured()
149
150
151 def fill_nodes(root_node):
152 IviewRootNode("ABC iView", root_node)
153