|
@@ -1,9 +1,11 @@
|
1
|
|
-import sys, os, urllib2, re
|
|
1
|
+import sys, os, urllib2, re, requests
|
|
2
|
+#CLI_MODE = True
|
2
|
3
|
from kodiswift import xbmc, xbmcgui, CLI_MODE
|
3
|
4
|
from kodiswift import Plugin, storage
|
4
|
|
-from resources.lib.content import util, ContentSources, Downloader
|
5
|
|
-from twisted.web import client
|
6
|
|
-from twisted.internet import reactor, defer
|
|
5
|
+from resources.lib.content import util, ContentSources
|
|
6
|
+#from resources.lib.content import Downloader
|
|
7
|
+#from twisted.web import client
|
|
8
|
+#from twisted.internet import reactor, defer
|
7
|
9
|
|
8
|
10
|
#plugin = Plugin()
|
9
|
11
|
#plugin.load_addon_settings()
|
|
@@ -21,24 +23,191 @@ download_dir = sys.argv[4]
|
21
|
23
|
cur_directory = os.path.dirname(__file__)
|
22
|
24
|
sources_directory = os.path.join(cur_directory,"resources","lib", "content", "sources")
|
23
|
25
|
sources = ContentSources.ContentSources(sources_directory)
|
24
|
|
-if not sources.is_video(data):
|
25
|
|
- print "It is not video link"
|
26
|
|
- sys.exit(1)
|
27
|
26
|
|
28
|
|
-streams = sources.get_streams(data)
|
|
27
|
+def main():
|
|
28
|
+ if not sources.is_video(data):
|
|
29
|
+ print "It is not video link"
|
|
30
|
+ notify("It is not video link")
|
|
31
|
+ sys.exit(1)
|
29
|
32
|
|
30
|
|
-if not CLI_MODE:
|
31
|
|
- ret = xbmcgui.Dialog().select("Select stream",streams)
|
32
|
|
-else:
|
33
|
|
- ret = 0
|
34
|
|
-stream = streams[ret]
|
35
|
|
-#output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
|
36
|
|
-output = re.sub("[\\/\n\r\t,:\?\|'~\.]","_",title)
|
|
33
|
+ streams = sources.get_streams(data)
|
37
|
34
|
|
38
|
|
-for sub in stream["subs"]:
|
39
|
|
- suburl = sub["url"]
|
40
|
|
- slang = "_" + sub["lang"] if sub["lang"] else ""
|
|
35
|
+ if not CLI_MODE:
|
|
36
|
+ ret = 0
|
|
37
|
+ #ret = xbmcgui.Dialog().select("Select stream",streams) # TODO
|
|
38
|
+ else:
|
|
39
|
+ ret = 0
|
|
40
|
+ stream = streams[ret]
|
|
41
|
+ #output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
|
|
42
|
+ output = re.sub("[\\/\n\r\t,:\?\|'~\.]","_",title)
|
|
43
|
+ if isinstance(output, str):
|
|
44
|
+ output = output.decode("utf8")
|
|
45
|
+
|
|
46
|
+ for sub in stream["subs"]:
|
|
47
|
+ suburl = sub["url"]
|
|
48
|
+ slang = "_" + sub["lang"] if sub["lang"] else ""
|
|
49
|
+ download_sub(suburl, output+slang)
|
|
50
|
+
|
|
51
|
+ if "nfo" in stream and stream["nfo"]:
|
|
52
|
+ nfofile = os.path.join(download_dir, output+".nfo")
|
|
53
|
+ with open(nfofile,"w") as f:
|
|
54
|
+ nfo_txt = util.nfo2xml(stream["nfo"])
|
|
55
|
+ f.write(nfo_txt)
|
|
56
|
+
|
|
57
|
+ download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
|
|
58
|
+
|
|
59
|
+ #d = Downloader.download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
|
|
60
|
+ #reactor.run()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+ #xbmcgui.Dialog().ok("Info","Start download")
|
|
64
|
+
|
|
65
|
+ #mode = "a" if os.path.exists("context_menu.log") else "w"
|
|
66
|
+ #with open("context_menu.log", mode) as f:
|
|
67
|
+ # f.write("%s %s %s %s", sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+def download_video(url,output,headers=None):
|
|
71
|
+ #output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
|
|
72
|
+ if not headers:
|
|
73
|
+ headers = {"user-agent":"Enigma2"}
|
|
74
|
+ try:
|
|
75
|
+ h = get_header(url,headers=headers)
|
|
76
|
+ mtype = h.get("content-type")
|
|
77
|
+ ext,stream_type = get_ext(mtype)
|
|
78
|
+ except Exception as e:
|
|
79
|
+ ext,stream_type = (".ts","hls")
|
|
80
|
+ output = output+ext
|
|
81
|
+ if stream_type == "hls":
|
|
82
|
+ download_hls(url, output, headers=headers)
|
|
83
|
+ else:
|
|
84
|
+ download_file(url, output, headers=headers)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+def download_hls(url, title, download_dir="", headers=None, overwrite=True, limit=None):
|
|
88
|
+ UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
|
|
89
|
+ if not headers:
|
|
90
|
+ headers = {"User-Agent" : UA}
|
|
91
|
+ key = headers["key"] if "key" in headers else ""
|
|
92
|
+# if not "User-Agent" in headers:
|
|
93
|
+# headers["User-Agent"] = UA
|
|
94
|
+ tsname = os.path.join(download_dir,title)
|
|
95
|
+
|
|
96
|
+ notify("Download started - %s" % title)
|
|
97
|
+ print "Start download"
|
|
98
|
+ print url
|
|
99
|
+ try:
|
|
100
|
+ r = requests.get(url,headers=headers)
|
|
101
|
+ except Exception as e:
|
|
102
|
+ raise Exception("Cannot open manifsest file - %s"%url)
|
|
103
|
+ if not r.content.startswith("#EXTM3U"):
|
|
104
|
+ raise Exception("Not valid manifest file - %s" % url)
|
|
105
|
+ streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
|
|
106
|
+ i = 0
|
|
107
|
+ while streams:
|
|
108
|
+ if i > 4: break
|
|
109
|
+ sorted(streams, key=lambda item: int(item[0]), reverse=True)
|
|
110
|
+ base_url = "/".join(url.split("?")[0].split("/")[:-1])+"/"
|
|
111
|
+ url = streams[0][1]
|
|
112
|
+ if not url.startswith("http"):
|
|
113
|
+ url = base_url + url
|
|
114
|
+ print url
|
|
115
|
+ try:
|
|
116
|
+ r = requests.get(url, headers=headers)
|
|
117
|
+ except Exception as e:
|
|
118
|
+ raise Exception("Cannot open manifsest file - %s"%url)
|
|
119
|
+ i += 1
|
|
120
|
+ streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
|
|
121
|
+ ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
|
|
122
|
+ base_url = "/".join(url.split("/")[:-1])+"/"
|
41
|
123
|
|
|
124
|
+ if not len(ts_list):
|
|
125
|
+ raise Exception("Cannot read fragment list in manifsest file - %s"%url)
|
|
126
|
+
|
|
127
|
+ ts_num = 0
|
|
128
|
+ type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
|
|
129
|
+ currentbytes = 0.0
|
|
130
|
+ totalbytes = -1
|
|
131
|
+ currenttime = 0.0
|
|
132
|
+ totaltime = sum(map(float,zip(*ts_list)[0]))
|
|
133
|
+ #ts_file = open(outputfile, "wb")
|
|
134
|
+
|
|
135
|
+ if isinstance(tsname, str):
|
|
136
|
+ tsname = tsname.decode("utf8")
|
|
137
|
+ tsfile = open(tsname,"wb")
|
|
138
|
+ for ts in ts_list:
|
|
139
|
+ url2 = ts[1]
|
|
140
|
+ #print "Downloading ", url2
|
|
141
|
+ #fname = os.path.join(download_dir,url2.split("/")[-1])
|
|
142
|
+ if not url2.startswith("http"):
|
|
143
|
+ url2 = base_url + url2
|
|
144
|
+
|
|
145
|
+ r = requests.get(url2, headers=headers, verify=False)
|
|
146
|
+ content = r.content
|
|
147
|
+ if key:
|
|
148
|
+ from Crypto.Cipher import AES
|
|
149
|
+ key2 = binascii.a2b_hex(key)
|
|
150
|
+ iv = content[:16]
|
|
151
|
+ d = AES.new(key2, AES.MODE_CBC, iv)
|
|
152
|
+ content = d.decrypt(content[16:])
|
|
153
|
+ #with open(fname,"wb") as f:
|
|
154
|
+ #f.write(content)
|
|
155
|
+ tsfile.write(content)
|
|
156
|
+
|
|
157
|
+ content_length = len(content)
|
|
158
|
+ currentbytes += content_length
|
|
159
|
+ currenttime += float(ts_list[ts_num][0])
|
|
160
|
+ totalbytes = currentbytes * totaltime / currenttime
|
|
161
|
+ ts_num += 1
|
|
162
|
+ #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
|
|
163
|
+ progress = float(currentbytes)/float(totalbytes)*100
|
|
164
|
+ print "%.1f%% (%i/%i)"%(progress,currentbytes,totalbytes)
|
|
165
|
+
|
|
166
|
+ if type == "vod":
|
|
167
|
+ if ts_num >= len(ts_list) or (limit and currenttime>limit):
|
|
168
|
+ break
|
|
169
|
+ else:
|
|
170
|
+ if limit and currenttime>limit: # TODO
|
|
171
|
+ break
|
|
172
|
+
|
|
173
|
+ print "Finished"
|
|
174
|
+ notify("Download finished - %s" % title)
|
|
175
|
+
|
|
176
|
+ tsfile.close()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+def download_file(url, title, download_dir="", headers=None, overwrite=True, limit=None):
|
|
180
|
+ UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
|
|
181
|
+ if not headers:
|
|
182
|
+ headers = {"User-Agent" : UA}
|
|
183
|
+ key = headers["key"] if "key" in headers else ""
|
|
184
|
+# if not "User-Agent" in headers:
|
|
185
|
+# headers["User-Agent"] = UA
|
|
186
|
+ fname = os.path.join(download_dir,title)
|
|
187
|
+ if isinstance(fname, str):
|
|
188
|
+ fname = fname.decode("utf8")
|
|
189
|
+
|
|
190
|
+ notify("Download started - %s" % title)
|
|
191
|
+ print "Start download"
|
|
192
|
+ print url
|
|
193
|
+ try:
|
|
194
|
+ r = requests.get(url,headers=headers, stream=True)
|
|
195
|
+ except Exception as e:
|
|
196
|
+ raise Exception("Cannot open url - %s"%url)
|
|
197
|
+ currentbytes = 0.0
|
|
198
|
+ totalbytes = int(r.headers["content-length"])
|
|
199
|
+
|
|
200
|
+ with open(fname, 'wb') as fd:
|
|
201
|
+ for chunk in r.iter_content(chunk_size=1024*1024):
|
|
202
|
+ fd.write(chunk)
|
|
203
|
+ currentbytes += len(chunk)
|
|
204
|
+ progress = float(currentbytes)/float(totalbytes)*100
|
|
205
|
+ print "%.1f%% (%i/%i)"%(progress,currentbytes,totalbytes)
|
|
206
|
+
|
|
207
|
+ print "Finished"
|
|
208
|
+ notify("Download finished - %s" % title)
|
|
209
|
+
|
|
210
|
+def download_sub(suburl, output):
|
42
|
211
|
try:
|
43
|
212
|
subs = urllib2.urlopen(suburl).read()
|
44
|
213
|
except:
|
|
@@ -54,21 +223,47 @@ for sub in stream["subs"]:
|
54
|
223
|
else:
|
55
|
224
|
subext = ""
|
56
|
225
|
if subext:
|
57
|
|
- subfile = cunicode(os.path.join(download_dir, output+slang+subext))
|
|
226
|
+ subfile = cunicode(os.path.join(download_dir, output+subext))
|
58
|
227
|
with open(subfile,"w") as f:
|
59
|
228
|
f.write(subs)
|
60
|
229
|
else:
|
61
|
230
|
print "\n Error downloading subtitle %s"%suburl
|
62
|
231
|
|
63
|
|
-d = Downloader.download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
|
64
|
|
-reactor.run()
|
65
|
232
|
|
66
|
233
|
|
|
234
|
+def get_header(url,headers=None):
|
|
235
|
+ r = requests.head(url,headers=headers)
|
|
236
|
+ return r.headers
|
67
|
237
|
|
|
238
|
+def get_ext(mtype):
|
|
239
|
+ stype = "http"
|
|
240
|
+ if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
|
|
241
|
+ return ".ts","hls"
|
|
242
|
+ elif mtype in ("application/dash+xml"):
|
|
243
|
+ return ".ts","dash" # TODO dash stream type could be different !
|
|
244
|
+ elif mtype in ("video/mp4"):
|
|
245
|
+ return ".mp4","http"
|
|
246
|
+ elif mtype in ("video/MP2T","video/mp2t"):
|
|
247
|
+ return ".ts","http"
|
|
248
|
+ elif mtype in ("video/x-flv"):
|
|
249
|
+ return ".flv","http"
|
|
250
|
+ elif mtype in ("video/quicktime"):
|
|
251
|
+ return ".mov","http"
|
|
252
|
+ elif mtype in ("video/x-msvideo"):
|
|
253
|
+ return ".avi","http"
|
|
254
|
+ elif mtype in ("video/x-ms-wmv"):
|
|
255
|
+ return ".wmv","http"
|
|
256
|
+ elif mtype in ("video/x-matroska"):
|
|
257
|
+ return ".mkv","http"
|
|
258
|
+ else:
|
|
259
|
+ return ".mp4","http"
|
68
|
260
|
|
69
|
261
|
|
70
|
|
-#xbmcgui.Dialog().ok("Info","Start download")
|
|
262
|
+def notify(text, title="Info", time=10000):
|
|
263
|
+ if isinstance(text, unicode):
|
|
264
|
+ text = text.encode("utf8")
|
|
265
|
+ #xbmc.executebuiltin('Notification(Hello World,This is a simple example of notifications,5000,/script.hellow.world.png)')
|
|
266
|
+ xbmc.executebuiltin('Notification(%s, %s, %d, %s)'%("Info", text, time, xbmcgui.NOTIFICATION_INFO))
|
71
|
267
|
|
72
|
|
-#mode = "a" if os.path.exists("context_menu.log") else "w"
|
73
|
|
-#with open("context_menu.log", mode) as f:
|
74
|
|
-# f.write("%s %s %s %s", sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
|
268
|
+if __name__ == '__main__':
|
|
269
|
+ main()
|