Kodi plugin to to play various online streams (mostly Latvian)

Downloader.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. USER_AGENT = "Enigma2 HbbTV/1.1.1 (+PVR+RTSP+DL;OpenATV;;;)"
  7. #####################################################################################################
  8. class HTTPProgressDownloader(client.HTTPDownloader):
  9. def __init__(self, url, outfile, headers=None):
  10. agent = USER_AGENT
  11. if headers and "user-agent" in headers:
  12. agent = headers["user-agent"]
  13. if headers and "User-Agent" in headers:
  14. agent = headers["User-Agent"]
  15. client.HTTPDownloader.__init__(self, url, outfile, headers=headers, agent=agent)
  16. self.status = None
  17. self.progress_callback = None
  18. self.deferred = defer.Deferred()
  19. def noPage(self, reason):
  20. if self.status == "304":
  21. print reason.getErrorMessage()
  22. client.HTTPDownloader.page(self, "")
  23. else:
  24. client.HTTPDownloader.noPage(self, reason)
  25. def gotHeaders(self, headers):
  26. if self.status == "200":
  27. if headers.has_key("content-length"):
  28. self.totalbytes = int(headers["content-length"][0])
  29. else:
  30. self.totalbytes = 0
  31. self.currentbytes = 0.0
  32. return client.HTTPDownloader.gotHeaders(self, headers)
  33. def pagePart(self, packet):
  34. if self.status == "200":
  35. self.currentbytes += len(packet)
  36. if self.totalbytes and self.progress_callback:
  37. self.progress_callback(self.currentbytes, self.totalbytes)
  38. return client.HTTPDownloader.pagePart(self, packet)
  39. def pageEnd(self):
  40. return client.HTTPDownloader.pageEnd(self)
  41. class DownloadWithProgress:
  42. def __init__(self, url, outputfile, headers=None, limit=0, contextFactory=None, *args, **kwargs):
  43. self.limit = limit
  44. uri = urlparse.urlparse(url)
  45. scheme = uri.scheme
  46. host = uri.hostname
  47. port = uri.port if uri.port else 80
  48. path = uri.path
  49. if not headers:
  50. headers = {"user-agent":USER_AGENT}
  51. self.factory = HTTPProgressDownloader(url, outputfile, headers, *args, **kwargs)
  52. if scheme == "https":
  53. self.connection = reactor.connectSSL(host, port, self.factory, ssl.ClientContextFactory())
  54. else:
  55. self.connection = reactor.connectTCP(host, port, self.factory)
  56. def start(self):
  57. return self.factory.deferred
  58. def stop(self):
  59. if self.connection:
  60. print "[stop]"
  61. self.connection.disconnect()
  62. def addProgress(self, progress_callback):
  63. print "[addProgress]"
  64. self.factory.progress_callback = progress_callback
  65. #####################################################################################################
  66. class DownloadWithProgressFragmented:
  67. def __init__(self, url, outputfile, headers = None, limit = 0, contextFactory=None, *args, **kwargs):
  68. self.url = url
  69. self.outputfile = outputfile
  70. self.base_url = "/".join(url.split("/")[:-1])+"/"
  71. self.headers = headers if headers else {"user-agent":"Enigma2"}
  72. self.limit = limit
  73. self.agent = kwargs["agent"] if "agent" in kwargs else None
  74. self.cookie = kwargs["cookie"] if "cookie" in kwargs else None
  75. self.deferred = defer.Deferred()
  76. #self.deferred.addCallback(self.start_download)
  77. def start_download(self):
  78. print "Start download"
  79. try:
  80. r = requests.get(self.url,headers=self.headers)
  81. except Exception as e:
  82. #self.deferred.errback("Cannot open manifsest file - %s"%url)
  83. self.deferred.errback(e)
  84. if not r.content.startswith("#EXTM3U"):
  85. self.deferred.errback(Exception("Not valid manifest file - %s"%self.url))
  86. streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
  87. if streams:
  88. sorted(streams, key=lambda item: int(item[0]), reverse=True)
  89. url = streams[0][1]
  90. if not url.startswith("http"):
  91. url = self.base_url + url
  92. try:
  93. r = requests.get(url, headers=self.headers)
  94. except Exception as e:
  95. self.deferred.errback(Exception("Cannot open manifsest file - %s"%url))
  96. self.ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
  97. if not len(self.ts_list):
  98. self.deferred.errback(Exception("Cannot read fragment list in manifsest file - %s"%url))
  99. self.ts_num = 0
  100. self.type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
  101. self.currentbytes = 0.0
  102. self.totalbytes = -1
  103. self.currenttime = 0.0
  104. self.totaltime = sum(map(float,zip(*self.ts_list)[0]))
  105. try:
  106. self.ts_file = open(self.outputfile, "wb")
  107. except Exception as e:
  108. self.deferred.errback(Exception("Cannot open output file - %s" % self.outputfile))
  109. self.download_fragment()
  110. def download_fragment(self):
  111. if self.ts_num>=len(self.ts_list):
  112. pass
  113. print "Call later"
  114. reactor.callLater(10,self.update_manifest)
  115. reactor.callLater(10, self.download_fragment)
  116. else:
  117. print "Start fragment download"
  118. url = self.ts_list[self.ts_num][1]
  119. if not "://" in url:
  120. url = self.base_url+url
  121. self.d = client.getPage(url,headers = self.headers)
  122. self.d.addCallbacks(self.download_ok,self.download_err)
  123. def download_ok(self,content):
  124. content_length = len(content)
  125. self.currentbytes += content_length
  126. self.currenttime += float(self.ts_list[self.ts_num][0])
  127. self.totalbytes = self.currentbytes * self.totaltime / self.currenttime
  128. self.ts_num += 1
  129. #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
  130. self.ts_file.write(content)
  131. self.progress_callback(self.currentbytes, self.totalbytes)
  132. if self.type == "vod":
  133. if self.ts_num >= len(self.ts_list) or (self.limit and self.currenttime>self.limit):
  134. self.ts_file.close()
  135. self.download_finished()
  136. else:
  137. self.download_fragment()
  138. else:
  139. if self.limit and self.currenttime>self.limit: # TODO
  140. self.ts_file.close()
  141. self.download_finished()
  142. else:
  143. self.download_fragment()
  144. def update_manifest(self):
  145. self.d2 = client.getPage(self.url, headers=self.headers)
  146. self.d2.addCallbacks(self.update_manifest_ok, self.update_manifest_err)
  147. def update_manifest_ok(self,content):
  148. print "Update manifest"
  149. ts_list = re.findall(r"#EXTINF:([\d\.]+),\n(.+?)$", content, re.IGNORECASE | re.MULTILINE)
  150. last_ts = self.ts_list[-1]
  151. found = False
  152. for ts in ts_list:
  153. if ts == last_ts:
  154. found = True
  155. elif found:
  156. print "Append %s"%ts[1]
  157. self.ts_list.append(ts)
  158. #reactor.callLater(5,self.download_fragment)
  159. def update_manifest_err(self,content):
  160. return
  161. def download_err(self,content):
  162. self.deferred.errback("Error while downloading %s"%self.ts_list[self.ts_num][1])
  163. def download_finished(self):
  164. self.totalbytes = self.currentbytes
  165. self.deferred.callback("Done")
  166. def start(self):
  167. reactor.callLater(1,self.start_download)
  168. return self.deferred
  169. def stop(self):
  170. self.deferred.errback() # TODO
  171. def addProgress(self, progress_callback):
  172. print "[addProgress]"
  173. self.progress_callback = progress_callback
  174. #####################################################################################################
  175. def get_header(url,headers=None):
  176. headers = {"user-agent":USER_AGENT}
  177. r = requests.head(url,headers=headers)
  178. return r.headers
  179. def get_ext(mtype):
  180. stype = "http"
  181. if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
  182. return ".ts","hls"
  183. elif mtype in ("application/dash+xml"):
  184. return ".ts","dash" # TODO dash stream type could be different !
  185. elif mtype in ("video/mp4"):
  186. return ".mp4","http"
  187. elif mtype in ("video/MP2T","video/mp2t"):
  188. return ".ts","http"
  189. elif mtype in ("video/x-flv"):
  190. return ".flv","http"
  191. elif mtype in ("video/quicktime"):
  192. return ".mov","http"
  193. elif mtype in ("video/x-msvideo"):
  194. return ".avi","http"
  195. elif mtype in ("video/x-ms-wmv"):
  196. return ".wmv","http"
  197. elif mtype in ("video/x-matroska"):
  198. return ".mkv","http"
  199. else:
  200. return ".mp4","http"
  201. ##############################################
  202. def print_progress(currentbytes, totalbytes):
  203. progress = float(currentbytes)/float(totalbytes)*100
  204. print "%s (%i/%i)"%(progress,currentbytes,totalbytes)
  205. def download_ok(*args):
  206. print "Download OK"
  207. reactor.stop()
  208. def download_err(e):
  209. print "Download Error %s"%e.getBriefTraceback()
  210. pass
  211. def stop():
  212. reactor.stop()
  213. ###############################################
  214. if __name__ == "__main__":
  215. if len(sys.argv)>2:
  216. url= sys.argv[1]
  217. output = sys.argv[1]
  218. else:
  219. url = "http://walterebert.com/playground/video/hls/ts/480x270.m3u8"
  220. url = "http://techslides.com/demos/sample-videos/small.mp4"
  221. #url = "http://wx17.poiuytrew.pw/s/c507282042b1bf25e0b72c34a68426f3/hd_30/Jackie.2016.D.iTunes.BDRip.1080p_720.mp4"
  222. url = "http://player.tvnet.lv/live/amlst:11/chunklist_w361981294_b528000.m3u8"
  223. #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"
  224. #url = "https://58174450afee9.streamlock.net/vod/mp4:_definst_/f/e/8e49fc32.mp4/playlist.m3u8?safwerwfasendtime=1490877870&safwerwfasstarttime=1490859339&safwerwfashash=hS2FfVZysQVazBQ6RJn1IhUevBkKxIF09Ly3BjfT43U="
  225. try:
  226. h = get_header(url,headers={"user-agent":"Enigma2"})
  227. mtype = h.get("content-type")
  228. ext,stream_type = get_ext(mtype)
  229. except:
  230. ext,stream_type = (".ts","hls")
  231. output = urlparse.urlparse(url)[2].split('/')[-1] + ext
  232. output = os.path.join("downloads", output)
  233. if stream_type == "hls":
  234. d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
  235. else:
  236. d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
  237. d.addProgress(print_progress)
  238. d.start().addCallback(download_ok).addErrback(download_err)
  239. reactor.run()