Enigma2 plugin to to play various online streams (mostly Latvian).

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #!/usr/bin/env python
  2. # coding=utf8
  3. #
  4. # This file is part of PlayStream - enigma2 plugin to play video streams from various sources
  5. # Copyright (c) 2016 ivars777 (ivars777@gmail.com)
  6. # Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
  7. #
  8. try:
  9. import json
  10. except:
  11. import simplejson as json
  12. import urllib2, urllib
  13. import datetime, re, sys,os
  14. import ConfigParser
  15. from SourceBase import SourceBase
  16. import base64
  17. from collections import OrderedDict
  18. import sys
  19. import ssl
  20. if "_create_unverified_context" in dir(ssl):
  21. ssl._create_default_https_context = ssl._create_unverified_context
  22. try:
  23. import util
  24. except:
  25. sys.path.insert(0,'..')
  26. import util
  27. headers2dict = lambda h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
  28. class Source(SourceBase):
  29. def __init__(self,country="",cfg_path=None):
  30. self.name = "filmix"
  31. self.title = "filmix.me"
  32. self.img = "http://cs5324.vk.me/g33668783/a_903fcc63.jpg"
  33. self.desc = "filmix.me satura skatīšanās"
  34. self.country=country
  35. self.headers = headers2dict("""
  36. Host: filmix.me
  37. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
  38. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  39. Accept-Language: en-US,en;q=0.5
  40. """)
  41. self.headers2 = headers2dict("""
  42. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
  43. X-Requested-With: XMLHttpRequest
  44. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  45. """)
  46. self.url = "https://filmix.me/"
  47. #self.login()
  48. def login(self,user="",password=""):
  49. return True
  50. def get_content(self, data):
  51. print "[filmix] get_content:", data
  52. source, data, path, plist, clist, params, qs = self.parse_data(data)
  53. content=[]
  54. content.append(("..return", "back","","Return back"))
  55. if clist=="home":
  56. content.extend([
  57. ("Search", "filmix::search/{0}","","Search"),
  58. ("Movies", "filmix::movies","","Movies"),
  59. ("Series", "filmix::series","","TV Series"),
  60. ("Cartoons", "filmix::cartoons","","Cartoons"),
  61. ])
  62. return content
  63. #elif clist=="search":
  64. # TODO
  65. #return content
  66. elif data in ("movies","series","cartoons"):
  67. r = self.call("")
  68. r = r.decode("cp1251").encode("utf8")
  69. if data == "movies":
  70. sname = "Фильмы"
  71. elif data=="series":
  72. sname = "Сериалы"
  73. else:
  74. sname = "Мультфильмы"
  75. # <span class="menu-title">Фильмы</span>
  76. m = re.search('<span class="menu-title">%s</span>(.+?)<li>\s+?<span'%sname, r, re.DOTALL|re.UNICODE)
  77. if not m: return content
  78. r2 = m.group(1)
  79. result = re.findall(r'<a .*?href="https://filmix\.me/([^"]+)".*?>([^<]+)</', r2, re.DOTALL)
  80. for item in result:
  81. if "catalog" in item[0]: continue
  82. title = item[1]
  83. data2 = item[0]
  84. img = self.img
  85. desc = title
  86. content.append((title,self.name+"::"+data2,img,desc))
  87. return content
  88. ## Seriāls
  89. elif clist=="play":
  90. r = self.call(path)
  91. r = r.decode("cp1251").encode("utf8")
  92. title = title0 = util.unescape(re.search("titlePlayer = '([^']+)'", r, re.DOTALL).group(1))
  93. m = re.search('<meta itemprop="thumbnailUrl" content="([^"]+)',r,re.DOTALL)
  94. img = m.group(1) if m else self.img
  95. m = re.search('<meta itemprop="duration" content="([^"]+)" />', r, re.DOTALL)
  96. duration = "(%s)"%m.group(1) if m else ""
  97. m = re.search('<p itemprop="description"[^>]+>([^<]+)<', r, re.DOTALL)
  98. desc = desc0 = util.unescape(m.group(1).strip()) if m else ""
  99. vid = plist[-1]
  100. m = re.search(r"meta_key = \['(\w+)', '(\w+)', '(\w+)'\]", r, re.IGNORECASE)
  101. key = m.group(3) if m else ""
  102. js = self.get_movie_info(vid,key)
  103. translations = js["message"]["translations"]["flash"]
  104. for pl in translations:
  105. if translations[pl].startswith("http"):
  106. continue
  107. pl_link = translations[pl]
  108. lang = pl.encode("utf8")
  109. break
  110. else:
  111. raise Exception("No episodes list found!")
  112. #pl_link = js["message"]["translations"]["flash"].values()[0]
  113. # TODO process several players, currently taking the first
  114. if not pl_link.startswith("http"):
  115. pl_link = self.decode_uppod_text(pl_link)
  116. js = self._http_request(pl_link)
  117. js = self.decode_uppod_text(js)
  118. js = json.loads(js)
  119. if "s" in qs:
  120. s = int(qs["s"])
  121. for i,ep in enumerate(js["playlist"][s-1]["playlist"]):
  122. title = title0+" - "+js["playlist"][s-1]["playlist"][i]["comment"].encode("utf8")
  123. serie = js["playlist"][s-1]["playlist"][i]["comment"].encode("utf8")
  124. data2 = data+"&e=%s"%(i+1)
  125. desc = serie +"\n"+desc0
  126. content.append((title,self.name+"::"+data2,img,desc))
  127. else:
  128. for i,ep in enumerate(js["playlist"]):
  129. title = title0 +" - "+js["playlist"][i]["comment"].encode("utf8")
  130. serie = js["playlist"][i]["comment"].encode("utf8")
  131. if "file" in ep and ep["file"]:
  132. data2 = data+"?e=%s"%(i+1)
  133. else:
  134. data2 = data+"?s=%s"%(i+1)
  135. desc = serie +"\n"+desc0
  136. content.append((title,self.name+"::"+data2,img,desc))
  137. return content
  138. #r = self._http_request(url)
  139. ### saraksts ###
  140. else:
  141. r = self.call(data)
  142. r = r.decode("cp1251").encode("utf8")
  143. for r2 in re.findall('<article class="shortstory line".+?</article>', r, re.DOTALL):
  144. m = re.search(r'<a href="https://filmix\.me/play/(\d+)" class="watch icon-play">', r2, re.DOTALL)
  145. if not m: continue
  146. vid = m.group(1)
  147. data2 = "play/%s"%vid
  148. #title = re.search('itemprop="name">([^<]+)</div>', r2, re.DOTALL).group(1)
  149. title = re.search('itemprop="name" content="([^"]+)"', r2, re.DOTALL).group(1)
  150. m = re.search('itemprop="alternativeHeadline" content="([^"]+)"', r2, re.DOTALL)
  151. if m:
  152. title = title + "/"+m.group(1)
  153. m = re.search('<img src="([^"]+.jpg)"', r2, re.DOTALL)
  154. img = m.group(1) if m else self.img
  155. m = re.search(r'<a itemprop="copyrightYear".+?>(\d+)<', r2, re.DOTALL)
  156. if m:
  157. year = m.group(1) if m else ""
  158. title = "%s (%s)"%(title,year)
  159. title = util.unescape(title)
  160. genre = re.findall('<a itemprop="genre"[^>]+?">([^<]+)</a>', r2, re.DOTALL)
  161. genre = ",".join(genre)
  162. m = re.search('<p itemprop="description">([^<]+)</p>', r2, re.DOTALL)
  163. desc0 = util.unescape(m.group(1)) if m else ""
  164. m = re.search('<div class="quality">([^<]+)</div>', r2, re.DOTALL)
  165. quality = m.group(1) if m else ""
  166. actors = re.findall('itemprop="actor">([^<]+)<', r2, re.DOTALL)
  167. actors = ",".join(actors)
  168. desc="%s\n%s\n%s\n%s\n%s"%(title,genre,desc0,actors,quality)
  169. content.append((title,self.name+"::"+data2,img,desc))
  170. if '<div class="navigation">' in r:
  171. m = re.search(r'href="https://filmix\.me/([^"]+)" class="next icon-arowRight btn-tooltip"', r, re.DOTALL)
  172. if m:
  173. data2 = m.group(1)
  174. else:
  175. m = re.search("/page/(\d)+",data)
  176. if m:
  177. page = int(m.group(1))+1
  178. data2 = re.sub("/page/(\d)+", "/page/%s"%page, data)
  179. else:
  180. data2 = data + "/page/2"
  181. content.append(("Next page",self.name+"::"+data2,self.img,"Next page"))
  182. return content
  183. def is_video(self,data):
  184. source,data,path,plist,clist,params,qs = self.parse_data(data)
  185. if clist == "play" and "e=" in data:
  186. return True
  187. elif clist=="play" and not params:
  188. r = self.call(path)
  189. #r = r.decode("cp1251").encode("utf8")
  190. m = re.search('itemprop="contentUrl" content="(.+?)"', r, re.IGNORECASE | re.DOTALL)
  191. if not m:
  192. raise Exception("Can not find video link")
  193. #return False
  194. video_link = m.group(1)
  195. if video_link=='{video-link}':
  196. return False
  197. else:
  198. return True
  199. else:
  200. return False
  201. def get_streams(self, data):
  202. print "[filmix] get_streams:", data
  203. source,data,path,plist,clist,params,qs = self.parse_data(data)
  204. r = self.call(path)
  205. if not r:
  206. return []
  207. streams = []
  208. r = r.decode("cp1251").encode("utf8")
  209. title = title0 = util.unescape(re.search("titlePlayer = '([^']+)'", r, re.DOTALL).group(1))
  210. m = re.search('<meta itemprop="thumbnailUrl" content="([^"]+)',r,re.DOTALL)
  211. img = m.group(1) if m else self.img
  212. m = re.search('<meta itemprop="duration" content="([^"]+)" />', r, re.DOTALL)
  213. duration = "(%s)"%m.group(1) if m else ""
  214. m = re.search('<p itemprop="description"[^>]+>([^<]+)<', r, re.DOTALL)
  215. desc = desc0 = util.unescape(m.group(1).strip()) if m else ""
  216. m = re.search('itemprop="contentUrl" content="(.+?)"', r, re.IGNORECASE | re.DOTALL)
  217. if not m:
  218. raise Exception("Can not find video link")
  219. #return []
  220. video_link = m.group(1)
  221. series = True if video_link == '{video-link}' else False
  222. vid = plist[1]
  223. m = re.search(r"meta_key = \['(\w+)', '(\w+)', '(\w+)'\]", r, re.IGNORECASE)
  224. key = m.group(3) if m else ""
  225. js = self.get_movie_info(vid,key)
  226. translations = js["message"]["translations"]["flash"]
  227. for pl in translations:
  228. if translations[pl].startswith("http"):
  229. continue
  230. pl_link = translations[pl]
  231. lang = pl.encode("utf8")
  232. break
  233. else:
  234. raise Exception("No episodes list found!")
  235. if not pl_link.startswith("http"):
  236. pl_link = self.decode_uppod_text(pl_link)
  237. if not series : # Filma
  238. url0 = pl_link
  239. streams2 = self.get_streams2(url0)
  240. for st in streams2:
  241. stream = util.item()
  242. stream["url"]=st[1]
  243. stream["lang"]=lang
  244. stream["quality"]=st[0]
  245. stream["name"]= title
  246. stream["desc"]=desc
  247. streams.append(stream)
  248. return streams
  249. else: # Seriāls
  250. #pl_link = video_link
  251. js = self._http_request(pl_link)
  252. js = self.decode_uppod_text(js)
  253. js = json.loads(js)
  254. if "e" in qs:
  255. if "s" in qs:
  256. s = int(qs["s"])
  257. else:
  258. s = None
  259. e = int(qs["e"])
  260. if s: # sezona + epizode
  261. serie = js["playlist"][s-1]["playlist"][e-1]["comment"].encode("utf8")
  262. title = title0+" - "+ serie
  263. url0 = js["playlist"][s-1]["playlist"][e-1]["file"].encode("utf8")
  264. else: # tikai epizode, nav sezonas
  265. title = title0 +" - "+js["playlist"][e-1]["comment"].encode("utf8")
  266. serie = js["playlist"][e-1]["comment"].encode("utf8")
  267. url0 = js["playlist"][e-1]["file"].encode("utf8")
  268. streams2 = self.get_streams2(url0)
  269. for st in streams2:
  270. stream = util.item()
  271. stream["url"]=st[1]
  272. stream["lang"]=lang
  273. stream["quality"]=st[0]
  274. stream["name"]= title
  275. stream["desc"]=desc
  276. streams.append(stream)
  277. return streams
  278. def call(self, data,params=None,headers=None,lang=""):
  279. if not headers: headers = self.headers
  280. url = self.url+data
  281. result = self._http_request(url,params,headers=headers)
  282. return result
  283. def get_movie_info(self,vid,key=""):
  284. headers = headers2dict("""
  285. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0
  286. Accept: application/json, text/javascript, */*; q=0.01
  287. Accept-Language: en-US,en;q=0.5
  288. Content-Type: application/x-www-form-urlencoded; charset=UTF-8
  289. X-Requested-With: XMLHttpRequest
  290. Referer: https://filmix.me/play/%s
  291. Cookie: ad_win12=1;
  292. """%vid )
  293. post_data = {"post_id":vid,"key=":key}
  294. r = util.post("https://filmix.me/api/movies/player_data", data=post_data, headers = headers)
  295. if not r:
  296. raise Exception("Can not get movie info")
  297. #return []
  298. js = json.loads(r)
  299. return js
  300. def decode_uppod_text(self, text):
  301. Client_codec_a = ["l", "u", "T", "D", "Q", "H", "0", "3", "G", "1", "f", "M", "p", "U", "a", "I", "6", "k", "d", "s", "b", "W", "5", "e", "y", "="]
  302. Client_codec_b = ["w", "g", "i", "Z", "c", "R", "z", "v", "x", "n", "N", "2", "8", "J", "X", "t", "9", "V", "7", "4", "B", "m", "Y", "o", "L", "h"]
  303. text = text.replace("\n", "").strip()
  304. for i in range(len(Client_codec_a)):
  305. char1 = Client_codec_b[i]
  306. char2 = Client_codec_a[i]
  307. text = text.replace(char1, "___")
  308. text = text.replace(char2, char1)
  309. text = text.replace("___", char2)
  310. result = base64.b64decode(text)
  311. print result
  312. return result
  313. def get_streams2(self,url0):
  314. m = re.search("\[([\d\w,]+)\]",url0)
  315. if not m:
  316. return [("?",url0)]
  317. res = m.group(1)
  318. streams=[]
  319. for res in res.split(","):
  320. if not res: continue
  321. if res in ["1080p"]: continue #TODO fullhd only in PRO+ version
  322. url=re.sub("\[[\d\w,]+\]",res,url0)
  323. streams.append((res,url))
  324. return streams
  325. if __name__ == "__main__":
  326. c = Source()
  327. #s = "ZnVuY3Rpb24gc2VuZE1lc3NhZ2U2MDc3ODkoZSl7dmFyIGg9bWdfd3M2MDc3ODkub25tZXNzYWdlOyBtZ193czYwNzc4OS5yZWFkeVN0YXRlPT1tZ193czYwNzc4OS5DTE9TRUQmJihtZ193czYwNzc4OT1uZXcgV2ViU29ja2V0KG1nX3dzNjA3Nzg5X2xvY2F0aW9uKSksbWdfd3M2MDc3ODkub25tZXNzYWdlPWgsd2FpdEZvclNvY2tldENvbm5lY3Rpb242MDc3ODkobWdfd3M2MDc3ODksZnVuY3Rpb24oKXttZ193czYwNzc4OS5zZW5kKGUpfSl9ZnVuY3Rpb24gd2FpdEZvclNvY2tldENvbm5lY3Rpb242MDc3ODkoZSx0KXtzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIDE9PT1lLnJlYWR5U3RhdGU/dm9pZChudWxsIT10JiZ0KCkpOnZvaWQgd2FpdEZvclNvY2tldENvbm5lY3Rpb242MDc3ODkoZSx0KX0sNSl9OyB2YXIgbWdfd3M2MDc3ODlfbG9jYXRpb24gPSAid3NzOi8vd3NwLm1hcmtldGdpZC5jb20vd3MiOyBtZ193czYwNzc4OSA9IG5ldyBXZWJTb2NrZXQobWdfd3M2MDc3ODlfbG9jYXRpb24pLCBtZ193czYwNzc4OS5vbm1lc3NhZ2UgPSBmdW5jdGlvbiAodCkge3Bvc3RNZXNzYWdlKHQuZGF0YSk7fSwgb25tZXNzYWdlID0gZnVuY3Rpb24oZSl7c2VuZE1lc3NhZ2U2MDc3ODkoZS5kYXRhKX0="
  328. #txt = c.decode_uppod_text(s)
  329. if len(sys.argv)>1:
  330. data= sys.argv[1]
  331. else:
  332. data = "home"
  333. content = c.get_content(data)
  334. for item in content:
  335. print item
  336. #cat = api.get_categories(country)
  337. #chan = api.get_channels("lv")
  338. #prog = api.get_programs(channel=6400)
  339. #prog = api.get_programs(category=55)
  340. #seas = api.get_seasons(program=6453)
  341. #str = api.get_streams(660243)
  342. #res = api.get_videos(802)
  343. #formats = api.getAllFormats()
  344. #det = api.detailed("1516")
  345. #vid = api.getVideos("13170")
  346. pass