|
@@ -0,0 +1,293 @@
|
|
1
|
+#from boxbranding import getMachineBrand, getMachineName
|
|
2
|
+import sys,os, os.path, re
|
|
3
|
+import urlparse, requests
|
|
4
|
+from twisted.web import client
|
|
5
|
+from twisted.internet import reactor, defer, ssl
|
|
6
|
+
|
|
7
|
+USER_AGENT = "Enigma2 HbbTV/1.1.1 (+PVR+RTSP+DL;OpenATV;;;)"
|
|
8
|
+
|
|
9
|
+#####################################################################################################
|
|
10
|
+class HTTPProgressDownloader(client.HTTPDownloader):
|
|
11
|
+ def __init__(self, url, outfile, headers=None):
|
|
12
|
+ agent = USER_AGENT
|
|
13
|
+ if headers and "user-agent" in headers:
|
|
14
|
+ agent = headers["user-agent"]
|
|
15
|
+ if headers and "User-Agent" in headers:
|
|
16
|
+ agent = headers["User-Agent"]
|
|
17
|
+ client.HTTPDownloader.__init__(self, url, outfile, headers=headers, agent=agent)
|
|
18
|
+ self.status = None
|
|
19
|
+ self.progress_callback = None
|
|
20
|
+ self.deferred = defer.Deferred()
|
|
21
|
+
|
|
22
|
+ def noPage(self, reason):
|
|
23
|
+ if self.status == "304":
|
|
24
|
+ print reason.getErrorMessage()
|
|
25
|
+ client.HTTPDownloader.page(self, "")
|
|
26
|
+ else:
|
|
27
|
+ client.HTTPDownloader.noPage(self, reason)
|
|
28
|
+
|
|
29
|
+ def gotHeaders(self, headers):
|
|
30
|
+ if self.status == "200":
|
|
31
|
+ if headers.has_key("content-length"):
|
|
32
|
+ self.totalbytes = int(headers["content-length"][0])
|
|
33
|
+ else:
|
|
34
|
+ self.totalbytes = 0
|
|
35
|
+ self.currentbytes = 0.0
|
|
36
|
+ return client.HTTPDownloader.gotHeaders(self, headers)
|
|
37
|
+
|
|
38
|
+ def pagePart(self, packet):
|
|
39
|
+ if self.status == "200":
|
|
40
|
+ self.currentbytes += len(packet)
|
|
41
|
+ if self.totalbytes and self.progress_callback:
|
|
42
|
+ self.progress_callback(self.currentbytes, self.totalbytes)
|
|
43
|
+ return client.HTTPDownloader.pagePart(self, packet)
|
|
44
|
+
|
|
45
|
+ def pageEnd(self):
|
|
46
|
+ return client.HTTPDownloader.pageEnd(self)
|
|
47
|
+
|
|
48
|
+class DownloadWithProgress:
|
|
49
|
+ def __init__(self, url, outputfile, headers=None, limit=0, contextFactory=None, *args, **kwargs):
|
|
50
|
+ self.limit = limit
|
|
51
|
+ uri = urlparse.urlparse(url)
|
|
52
|
+ scheme = uri.scheme
|
|
53
|
+ host = uri.hostname
|
|
54
|
+ port = uri.port if uri.port else 80
|
|
55
|
+ path = uri.path
|
|
56
|
+ if not headers:
|
|
57
|
+ headers = {"user-agent":USER_AGENT}
|
|
58
|
+ self.factory = HTTPProgressDownloader(url, outputfile, headers, *args, **kwargs)
|
|
59
|
+ if scheme == "https":
|
|
60
|
+ self.connection = reactor.connectSSL(host, port, self.factory, ssl.ClientContextFactory())
|
|
61
|
+ else:
|
|
62
|
+ self.connection = reactor.connectTCP(host, port, self.factory)
|
|
63
|
+
|
|
64
|
+ def start(self):
|
|
65
|
+ return self.factory.deferred
|
|
66
|
+
|
|
67
|
+ def stop(self):
|
|
68
|
+ if self.connection:
|
|
69
|
+ print "[stop]"
|
|
70
|
+ self.connection.disconnect()
|
|
71
|
+
|
|
72
|
+ def addProgress(self, progress_callback):
|
|
73
|
+ print "[addProgress]"
|
|
74
|
+ self.factory.progress_callback = progress_callback
|
|
75
|
+
|
|
76
|
+#####################################################################################################
|
|
77
|
+class DownloadWithProgressFragmented:
|
|
78
|
+ def __init__(self, url, outputfile, headers = None, limit = 0, contextFactory=None, *args, **kwargs):
|
|
79
|
+ self.url = url
|
|
80
|
+ self.outputfile = outputfile
|
|
81
|
+ self.base_url = "/".join(url.split("/")[:-1])+"/"
|
|
82
|
+ self.headers = headers if headers else {"user-agent":"Enigma2"}
|
|
83
|
+ self.limit = limit
|
|
84
|
+ self.agent = kwargs["agent"] if "agent" in kwargs else None
|
|
85
|
+ self.cookie = kwargs["cookie"] if "cookie" in kwargs else None
|
|
86
|
+ self.deferred = defer.Deferred()
|
|
87
|
+ #self.deferred.addCallback(self.start_download)
|
|
88
|
+
|
|
89
|
+ def start_download(self):
|
|
90
|
+ print "Start download"
|
|
91
|
+ try:
|
|
92
|
+ r = requests.get(self.url,headers=self.headers)
|
|
93
|
+ except Exception as e:
|
|
94
|
+ #self.deferred.errback("Cannot open manifsest file - %s"%url)
|
|
95
|
+ self.deferred.errback(e)
|
|
96
|
+ if not r.content.startswith("#EXTM3U"):
|
|
97
|
+ self.deferred.errback(Exception("Not valid manifest file - %s"%self.url))
|
|
98
|
+ streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
|
|
99
|
+ if streams:
|
|
100
|
+ sorted(streams, key=lambda item: int(item[0]), reverse=True)
|
|
101
|
+ url = streams[0][1]
|
|
102
|
+ if not url.startswith("http"):
|
|
103
|
+ url = self.base_url + url
|
|
104
|
+ try:
|
|
105
|
+ r = requests.get(url, headers=self.headers)
|
|
106
|
+ except Exception as e:
|
|
107
|
+ self.deferred.errback(Exception("Cannot open manifsest file - %s"%url))
|
|
108
|
+ self.ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
|
|
109
|
+ if not len(self.ts_list):
|
|
110
|
+ self.deferred.errback(Exception("Cannot read fragment list in manifsest file - %s"%url))
|
|
111
|
+ self.ts_num = 0
|
|
112
|
+ self.type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
|
|
113
|
+ self.currentbytes = 0.0
|
|
114
|
+ self.totalbytes = -1
|
|
115
|
+ self.currenttime = 0.0
|
|
116
|
+ self.totaltime = sum(map(float,zip(*self.ts_list)[0]))
|
|
117
|
+ self.ts_file = open(self.outputfile, "wb")
|
|
118
|
+ self.download_fragment()
|
|
119
|
+
|
|
120
|
+ def download_fragment(self):
|
|
121
|
+ if self.ts_num>=len(self.ts_list):
|
|
122
|
+ pass
|
|
123
|
+ #print "Call later"
|
|
124
|
+ reactor.callLater(10,self.update_manifest)
|
|
125
|
+ reactor.callLater(10, self.download_fragment)
|
|
126
|
+ else:
|
|
127
|
+ print "Start fragment download"
|
|
128
|
+ url = self.ts_list[self.ts_num][1]
|
|
129
|
+ if not "://" in url:
|
|
130
|
+ url = self.base_url+url
|
|
131
|
+ self.d = client.getPage(url,headers = self.headers)
|
|
132
|
+ self.d.addCallbacks(self.download_ok,self.download_err)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+ def download_ok(self,content):
|
|
136
|
+ content_length = len(content)
|
|
137
|
+ self.currentbytes += content_length
|
|
138
|
+ self.currenttime += float(self.ts_list[self.ts_num][0])
|
|
139
|
+ self.totalbytes = self.currentbytes * self.totaltime / self.currenttime
|
|
140
|
+ self.ts_num += 1
|
|
141
|
+ #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
|
|
142
|
+ self.ts_file.write(content)
|
|
143
|
+ self.progress_callback(self.currentbytes, self.totalbytes)
|
|
144
|
+ if self.type == "vod":
|
|
145
|
+ if self.ts_num >= len(self.ts_list) or (self.limit and self.currenttime>self.limit):
|
|
146
|
+ self.ts_file.close()
|
|
147
|
+ self.download_finished()
|
|
148
|
+ else:
|
|
149
|
+ self.download_fragment()
|
|
150
|
+ else:
|
|
151
|
+ if self.limit and self.currenttime>self.limit: # TODO
|
|
152
|
+ self.ts_file.close()
|
|
153
|
+ self.download_finished()
|
|
154
|
+ else:
|
|
155
|
+ self.download_fragment()
|
|
156
|
+
|
|
157
|
+ def update_manifest(self):
|
|
158
|
+ self.d2 = client.getPage(self.url, headers=self.headers)
|
|
159
|
+ self.d2.addCallbacks(self.update_manifest_ok, self.update_manifest_err)
|
|
160
|
+
|
|
161
|
+ def update_manifest_ok(self,content):
|
|
162
|
+ print "Update manifest"
|
|
163
|
+ ts_list = re.findall(r"#EXTINF:([\d\.]+),\n(.+?)$", content, re.IGNORECASE | re.MULTILINE)
|
|
164
|
+ last_ts = self.ts_list[-1]
|
|
165
|
+ found = False
|
|
166
|
+ for ts in ts_list:
|
|
167
|
+ if ts == last_ts:
|
|
168
|
+ found = True
|
|
169
|
+ elif found:
|
|
170
|
+ print "Append %s"%ts[1]
|
|
171
|
+ self.ts_list.append(ts)
|
|
172
|
+ #reactor.callLater(5,self.download_fragment)
|
|
173
|
+
|
|
174
|
+ def update_manifest_err(self,content):
|
|
175
|
+ return
|
|
176
|
+
|
|
177
|
+ def download_err(self,content):
|
|
178
|
+ self.deferred.errback("Error while downloading %s"%self.ts_list[self.ts_num][1])
|
|
179
|
+
|
|
180
|
+ def download_finished(self):
|
|
181
|
+ self.totalbytes = self.currentbytes
|
|
182
|
+ self.deferred.callback("Done")
|
|
183
|
+
|
|
184
|
+ def start(self):
|
|
185
|
+ reactor.callLater(1,self.start_download)
|
|
186
|
+ return self.deferred
|
|
187
|
+
|
|
188
|
+ def stop(self):
|
|
189
|
+ self.deferred.errback() # TODO
|
|
190
|
+
|
|
191
|
+ def addProgress(self, progress_callback):
|
|
192
|
+ print "[addProgress]"
|
|
193
|
+ self.progress_callback = progress_callback
|
|
194
|
+
|
|
195
|
+#####################################################################################################
|
|
196
|
+def get_header(url,headers=None):
|
|
197
|
+ headers = {"user-agent":USER_AGENT}
|
|
198
|
+ r = requests.head(url,headers=headers)
|
|
199
|
+ return r.headers
|
|
200
|
+
|
|
201
|
+def get_ext(mtype):
|
|
202
|
+ stype = "http"
|
|
203
|
+ if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
|
|
204
|
+ return ".ts","hls"
|
|
205
|
+ elif mtype in ("application/dash+xml"):
|
|
206
|
+ return ".ts","dash" # TODO dash stream type could be different !
|
|
207
|
+ elif mtype in ("video/mp4"):
|
|
208
|
+ return ".mp4","http"
|
|
209
|
+ elif mtype in ("video/MP2T","video/mp2t"):
|
|
210
|
+ return ".ts","http"
|
|
211
|
+ elif mtype in ("video/x-flv"):
|
|
212
|
+ return ".flv","http"
|
|
213
|
+ elif mtype in ("video/quicktime"):
|
|
214
|
+ return ".mov","http"
|
|
215
|
+ elif mtype in ("video/x-msvideo"):
|
|
216
|
+ return ".avi","http"
|
|
217
|
+ elif mtype in ("video/x-ms-wmv"):
|
|
218
|
+ return ".wmv","http"
|
|
219
|
+ elif mtype in ("video/x-matroska"):
|
|
220
|
+ return ".mkv","http"
|
|
221
|
+ else:
|
|
222
|
+ return ".mp4","http"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+##############################################
|
|
227
|
+def print_progress(currentbytes, totalbytes):
|
|
228
|
+ progress = float(currentbytes)/float(totalbytes)*100
|
|
229
|
+ print "%s (%i/%i)"%(progress,currentbytes,totalbytes)
|
|
230
|
+
|
|
231
|
+def download_ok(*args):
|
|
232
|
+ print "Download OK"
|
|
233
|
+ reactor.stop()
|
|
234
|
+
|
|
235
|
+def download_err(e):
|
|
236
|
+ print "Download Error %s"%e.getBriefTraceback()
|
|
237
|
+ pass
|
|
238
|
+def stop():
|
|
239
|
+ reactor.stop()
|
|
240
|
+###############################################
|
|
241
|
+
|
|
242
|
+def download_video(stream):
|
|
243
|
+ stream = stream[0]
|
|
244
|
+ url = stream["url"]
|
|
245
|
+ headers = stream["headers"]
|
|
246
|
+ output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
|
|
247
|
+ try:
|
|
248
|
+ h = get_header(url,headers={"user-agent":"Enigma2"})
|
|
249
|
+ mtype = h.get("content-type")
|
|
250
|
+ ext,stream_type = get_ext(mtype)
|
|
251
|
+ except:
|
|
252
|
+ ext,stream_type = (".ts","hls")
|
|
253
|
+ #output = urlparse.urlparse(url)[2].split('/')[-1] + ext
|
|
254
|
+ output = output+ext
|
|
255
|
+ output = os.path.join("downloads", output)
|
|
256
|
+ if stream_type == "hls":
|
|
257
|
+ d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
|
|
258
|
+ else:
|
|
259
|
+ d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
|
|
260
|
+ d.addProgress(print_progress)
|
|
261
|
+ d.start().addCallback(download_ok).addErrback(download_err)
|
|
262
|
+ reactor.run()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+if __name__ == "__main__":
|
|
266
|
+ if len(sys.argv)>2:
|
|
267
|
+ url= sys.argv[1]
|
|
268
|
+ output = sys.argv[1]
|
|
269
|
+ else:
|
|
270
|
+ url = "http://walterebert.com/playground/video/hls/ts/480x270.m3u8"
|
|
271
|
+ url = "https://r3---sn-bavc5ajvh-gpme.googlevideo.com/videoplayback?key=yt6&mime=video%2Fmp4&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cupn%2Cexpire&expire=1490986184&lmt=1490940183963773&dur=1302.639&itag=18&ratebypass=yes&mm=31&requiressl=yes&ipbits=0&upn=azFGj8gY02w&ip=85.254.87.15&pl=23&ei=aFDeWLzqDcn-dLC_gdAM&signature=083F353AC09CD98A70AD7D9438DD3C91C781166B.715456B9C35F040BDC4728CA76A0D1779B684A90&source=youtube&mv=m&mt=1490964451&ms=au&mn=sn-bavc5ajvh-gpme&gir=yes&clen=73596250&id=o-AGH9y-hWn1MtW1VzCyI_8XYYEWODsTDBZbfagQH3BrfQ&initcwndbps=4493750"
|
|
272
|
+ #url = "http://techslides.com/demos/sample-videos/small.mp4"
|
|
273
|
+ #url = "http://wx17.poiuytrew.pw/s/c507282042b1bf25e0b72c34a68426f3/hd_30/Jackie.2016.D.iTunes.BDRip.1080p_720.mp4"
|
|
274
|
+ #url = "http://player.tvnet.lv/live/amlst:11/chunklist_w361981294_b528000.m3u8"
|
|
275
|
+ #url = "http://vod-hls-uk-live.akamaized.net/usp/auth/vod/piff_abr_full_hd/a3e90e-b08ktytr/vf_b08ktytr_f9d55583-afc7-49bb-9bf4-d8f1ac99f56f.ism.hlsv2.ism/vf_b08ktytr_f9d55583-afc7-49bb-9bf4-d8f1ac99f56f.ism.hlsv2-audio=128000-video=5070000.m3u8"
|
|
276
|
+ #url = "https://58174450afee9.streamlock.net/vod/mp4:_definst_/f/e/8e49fc32.mp4/playlist.m3u8?safwerwfasendtime=1490877870&safwerwfasstarttime=1490859339&safwerwfashash=hS2FfVZysQVazBQ6RJn1IhUevBkKxIF09Ly3BjfT43U="
|
|
277
|
+ try:
|
|
278
|
+ h = get_header(url,headers={"user-agent":"Enigma2"})
|
|
279
|
+ mtype = h.get("content-type")
|
|
280
|
+ ext,stream_type = get_ext(mtype)
|
|
281
|
+ except:
|
|
282
|
+ ext,stream_type = (".ts","hls")
|
|
283
|
+ output = urlparse.urlparse(url)[2].split('/')[-1] + ext
|
|
284
|
+ output = os.path.join("downloads", output)
|
|
285
|
+ if stream_type == "hls":
|
|
286
|
+ d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
|
|
287
|
+ else:
|
|
288
|
+ d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
|
|
289
|
+ d.addProgress(print_progress)
|
|
290
|
+ d.start().addCallback(download_ok).addErrback(download_err)
|
|
291
|
+ reactor.run()
|
|
292
|
+
|
|
293
|
+
|