Python module (submodule repositary), which provides content (video streams) from various online stream sources to corresponding Enigma2, Kodi, Plex plugins

replay.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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 os
  13. import urllib2, urllib
  14. import datetime, time, re, sys
  15. import ssl
  16. if "_create_unverified_context" in dir(ssl):
  17. ssl._create_default_https_context = ssl._create_unverified_context
  18. from SourceBase import SourceBase
  19. import util
  20. API_URL = 'https://replay.lsm.lv/%s/'
  21. headers2dict = lambda h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
  22. headers0 = headers2dict("""
  23. User-Agent: Mozilla/5.0 (Linux; U; Android 4.4.4; Nexus 5 Build/KTU84P) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
  24. """)
  25. import HTMLParser
  26. h = HTMLParser.HTMLParser()
  27. from YouTubeVideoUrl import YouTubeVideoUrl
  28. class Source(SourceBase):
  29. def __init__(self,country="lv",cfg_path=None):
  30. self.name = "replay"
  31. self.title = "Replay.lv (LTV)"
  32. self.img = "replay.png"
  33. self.desc = "LSM replay.lv satura skatīšanās"
  34. self.country=country
  35. self.pic_size = "327x250" #"1000x765"
  36. def get_content(self, data):
  37. print "[replay] get_content:", data
  38. source, data, path, plist, clist, params, qs = self.parse_data(data)
  39. lang = qs["lang"] if "lang" in qs else self.country
  40. content=[]
  41. content.append(("..return", "back","back.png","Return back"))
  42. if clist=="home":
  43. content.extend([
  44. ("Live streams", "replay::tiesraide","","TV live streams"),
  45. ("Search LV", "replay::meklet?term={0}&lang=lv","","Search content LV"),
  46. ("Last videos LV", "replay::visi/jaunakie?mtype=video&page=1&lang=lv","","Last aired videos LV"),
  47. ("Last videos by categories LV", "replay::kategorijas?lang=lv","","Last videos by categories LV"),
  48. ("Last chance LV", "replay::visi/pedeja-iespeja?lang=lv","","Last videos by categories LV"),
  49. ("Videos by popularity LV", "replay::visi/popularie?mtype=video&lang=lv","","Programs by popularity"),
  50. ("All programs LV", "replay::raidijumi?mtype=video&lang=lv","","All programs by name LV"),
  51. ("Programs by categories LV", "replay::categories?lang=lv","","All programs by categories LV"),
  52. #("Channels", "replay::channels?language=%s"%self.country,"","TV channels"),
  53. ("Search RU", "replay::poisk?term={0}&lang=ru","","Search content RU"),
  54. ("Last videos RU", "replay::vse/novie?mtype=video&lang=ru","","Last aired videos RU"),
  55. ("Last videos by categories RU", "replay::kategorijas?lang=ru","","Last videos by categories RU"),
  56. ("All programs RU", "replay::peredachi?lang=ru&mtype=video","","All programs by name"),
  57. ("Programs by categories RU", "replay::categories?lang=ru","","Programs by categories RU")
  58. ])
  59. return content
  60. ### programmu kategorijas ###
  61. elif clist=="categories":
  62. url = "https://replay.lsm.lv/lv/raidijumi" if lang =="lv" else "https://replay.lsm.lv/ru/peredachi"
  63. r = self._http_request(url)
  64. icons = {
  65. #TODO
  66. "1": "https://www.iconfinder.com/icons/314161/download/png/256",
  67. }
  68. result = re.findall(r'<a href="https://replay\.lsm\.lv/(lv|ru)/([^\?^"]+\?category=\d+)" class="badge badge-pill">([^<]+)</a>', r)
  69. for item in result:
  70. title = item[2].strip()
  71. data2 = item[1].replace("&amp;", "&") + "&lang=%s" % item[0]
  72. #theme = item[0].split("theme=")[1]
  73. img = self.img # TODO icons["1"]
  74. desc = title
  75. content.append((title,self.name+"::"+data2,img,desc))
  76. return content
  77. ### jaunāko raidijumu kategorijas ###
  78. elif clist=="kategorijas":
  79. url = "https://replay.lsm.lv/lv/" if lang =="lv" else "https://replay.lsm.lv/ru/"
  80. r = self._http_request(url)
  81. for m in re.finditer(r'<a href="https://replay\.lsm\.lv/(lv|ru)/(kategorija/[\w-]+)" class="">.+?<span>([^<]+)</span>', r, re.DOTALL):
  82. title = m.group(3)
  83. data2 = "%s?lang=%s"%(m.group(2), m.group(1))
  84. img = self.img
  85. desc = title
  86. content.append((title,self.name+"::"+data2,img,desc))
  87. return content
  88. ### Raidijumi (programmas) ###
  89. elif clist in ( "raidijumi","peredachi"):
  90. r = self.call(data, lang=lang)
  91. result = re.findall(r'<a href="https://replay\.lsm\.lv/(lv|ru)/([^"]+)"><span>([^<]+)</span>', r, re.DOTALL)
  92. for item in result:
  93. title = item[2].strip()
  94. data2 = item[1]+"?lang=%s"%item[0]
  95. img = ""
  96. desc = title
  97. content.append((title,self.name+"::"+data2,img,desc))
  98. return content
  99. ### Tiešraides kanānālu saraksts
  100. elif path=="tiesraide":
  101. url = "https://replay.lsm.lv/lv/tiesraide/ltv1/"
  102. # <div class="visible-xs">
  103. r = self._http_request(url)
  104. result = re.findall(r'class="small-guide.+?href="https://replay\.lsm\.lv/lv/([^"]+)".+?title="([^"]+)".+?<div class="item-body">([^<]+)</div>.+?<time>([^<]+)</time>', r, re.DOTALL)
  105. for item in result:
  106. data2 = item[0]
  107. ch = data2.split("/")[1]
  108. title = item[1]
  109. img = ch + ".png"
  110. desc = item[2].strip()
  111. desc = desc + " [%s]" % item[3]
  112. title = title + " - " + desc
  113. content.append((title,self.name+"::"+data2,img,desc))
  114. url = "https://ltv.lsm.lv/lv/tieshraide/"
  115. r = self._http_request(url)
  116. result = re.findall('<article class="entry highlight"><figure><a href="/lv/([^"]+)"><img src="([^"]+)".+?>([^<]+)</a>', r, re.IGNORECASE | re.DOTALL | re.MULTILINE)
  117. for item in result:
  118. data2 = item[0]
  119. ch = data2.split("/")[1]
  120. title = item[2] + " (ltv.lsm.lv)"
  121. img = "https://ltv.lsm.lv/" + item[1]
  122. desc = title
  123. content.append((title,self.name+"::"+data2,img,desc))
  124. return content
  125. ### Meklēsana ###
  126. # TODO (jāsakrārto pēc jaunā templeita)
  127. elif clist in ("search"):
  128. r = self.call(data, lang=lang)
  129. if not r:
  130. return content
  131. #for r2 in re.findall('<article itemtype="http://schema.org/Article" itemscope class="thumbnail thumbnail--default ">(.+?)</article>', r2, re.DOTALL):
  132. for item in re.findall('itemprop="image" data-image="([^"]+)".+?<figcaption><h5 itemprop="name"><a itemprop="url" href="([^<]+)">([^<]+)</a></h5></figcaption>', r):
  133. title = item[2]
  134. data2 = item[1].replace("/%s/"%lang,"")+"?lang=%s"%lang
  135. img = "https://replay.lsm.lv" + item[0]
  136. desc = title
  137. content.append((title,self.name+"::"+data2,img,desc))
  138. #for item in re.findall('itemprop="image" data-image="([^"]+)".+?<figcaption><h4 itemprop="about"><a href="([^"]+)">([^<]+)</a></h4>.*?<h5 itemprop="name"><a itemprop="url" href="([^"]+)">([^<]+)</a></h5>.+?datetime="([^"]+)" class="thumbnail__date ">([^<]+)</time>', r2):
  139. for item in re.findall('itemprop="image" data-image="([^"]+)".+? class="icon-(ltv|lr).+?<figcaption><h4 itemprop="about"><a href="([^"]+)">([^<]+)</a></h4>.*?<h5 itemprop="name"><a itemprop="url" href="([^"]+)">([^<]+)</a></h5>.+?datetime="([^"]+)" class="thumbnail__date ">([^<]+)</time>', r):
  140. if item[1]=="lr":continue
  141. title = "%s - %s (%s)"%(item[3],item[5],item[7])
  142. data2 = item[4].replace("/%s/"%lang,"")+"?lang=%s"%lang
  143. img = item[0] #.replace("https:","http:")
  144. img = img.replace("lv//", "lv/") # TODO eksperiments
  145. desc = title
  146. content.append((title,self.name+"::"+data2,img,desc))
  147. # Raidijumu saraksti
  148. elif True:
  149. r = self.call(data, lang=lang)
  150. if not r:
  151. return content
  152. result = re.findall("<article.+?>(.+?)</article>", r, re.DOTALL)
  153. for item in result:
  154. m = re.search(r'<h4 class="thumbnail__title"><a href="https://replay\.lsm\.lv/(\w+)/([^"]+)">([^<]+)</a></h4>', item, re.DOTALL)
  155. if not m:
  156. continue
  157. title = m.group(3).strip()
  158. desc = title
  159. data2 = m.group(2)
  160. lang = m.group(1)
  161. m = re.search(r'style="background-image: url\(([^\)]+)\)"', item, re.DOTALL)
  162. img = m.group(1) if m else ""
  163. m = re.search('datePublished" datetime="([^"]+)"', item, re.DOTALL)
  164. if m:
  165. published = m.group(1)
  166. try:
  167. published = util.datetime_from_zulu_to_utc(published)
  168. published = util.datetime_from_utc_to_local(published)
  169. published_datetime = datetime.datetime.strftime(published,"%d.%m.%Y %H:%M")
  170. published_date = datetime.datetime.strftime(published,"%d.%m.%Y")
  171. title = title + " (%s)" % published_date
  172. desc = desc + "\nPublished: %s" % published_datetime
  173. except:
  174. a = 1
  175. pass
  176. m = re.search('thumbnail__duration">([^<]+)<', item, re.DOTALL)
  177. if m:
  178. desc = desc + "\nDuration: %s" % m.group(1)
  179. m = re.search(r'<a href="https://replay\.lsm\.lv/lv/raidijums/[^"]+">([^<]+)</a>', item, re.DOTALL)
  180. if m:
  181. desc = desc + "\nProgram: %s" % m.group(1)
  182. m = re.search('<li class=".*?">(Vēl[^<]+)</li>', item, re.DOTALL)
  183. if m:
  184. desc = desc + "\nRemaining: %s" % m.group(1)
  185. content.append((title,self.name+"::"+data2,img,desc))
  186. m = re.search(r'page=(\d+)" rel="next"', r, re.DOTALL)
  187. if m:
  188. page = int(m.group(1))
  189. if "page="in data:
  190. data2 = re.sub("page=\d+","page=%s"%page,data)
  191. else:
  192. if "?" in data:
  193. data2 =data+"&page=%s"%page
  194. else:
  195. data2 =data+"?page=%s"%page
  196. content.append(("Next page",self.name+"::"+data2,"next.png","Next page"))
  197. return content
  198. #===========================================================================
  199. def get_streams(self, data):
  200. source, data, path, plist, clist, params, qs = self.parse_data(data)
  201. lang = "ru" if clist == 'statja' else "lv"
  202. headers2 = {}
  203. nfo = {}
  204. ### Kanāla tiesraide
  205. if clist == "tiesraide" and "/" in data or clist == "tieshraide":
  206. if clist == "tiesraide":
  207. url = "https://replay.lsm.lv/lv/" + data
  208. #r = self.call(data, lang=lang)
  209. else:
  210. url = "https://ltv.lsm.lv/lv/" + data
  211. r = self._http_request(url)
  212. ch = data.split('/')[1]
  213. veids = "audio" if "lr" in ch else "video"
  214. img = ch + ".png"
  215. m = re.search('span class="badge badge-primary">Tagad</span>([^<]+)<.+?<time>([^<]+)</time>', r, re.DOTALL)
  216. if clist == "tieshraide":
  217. m = re.search("<h1>([^<]+)</h1>", r)
  218. title = m.group(1).strip() if m else ch.upper()
  219. desc = title
  220. else:
  221. tagad = m.group(1).strip() if m else ""
  222. laiks = m.group(2).strip() if m else ""
  223. laiks = h.unescape(laiks).encode("utf8")
  224. title = m.group(1).strip() if m else path.split("/")[1].upper()
  225. dd = datetime.datetime.now().strftime("%d.%m.%Y")
  226. title = "%s - %s (%s) [%s]"%(ch.upper(), title, dd, laiks)
  227. desc = title
  228. if veids == "video":
  229. m = re.search('<iframe.+?src="([^"]+)"', r)
  230. if not m:
  231. raise Exception("No stream found")
  232. headers = headers2dict("""
  233. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
  234. Referer: %s
  235. """% url)
  236. url = m.group(1)
  237. r = self._http_request(url,headers=headers)
  238. #m = re.search('<div class="video-player"><iframe.+src="([^"]+)"', r)
  239. m = re.search(r'iframe src=\\"([^"]+)\\', r)
  240. if m:
  241. #raise Exception("No stream found")
  242. headers["Referer"] = url
  243. url2 = m.group(1).replace('\\/', "/")
  244. m = re.search('poster":"([^"]+)"', r, re.DOTALL)
  245. img = "https://ltv.lsm.lv" + m.group(1).replace("\\","") if m else ch + '.png'
  246. r = self._http_request(url2,headers=headers)
  247. url = url2
  248. m = re.search('"([^"]+m3u8[^"]+)"', r)
  249. if not m:
  250. raise Exception("No stream found")
  251. data2 = m.group(1).replace("\\","")
  252. headers2["Referer"] = url
  253. headers2["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"
  254. headers2["Origin"] = "https://embed.lsm.lv"
  255. headers2["Host"] = data2.split("/")[2]
  256. #r = self._http_request(data2, headers=headers2)
  257. a = 1
  258. else: # audio
  259. lrn = ch.replace("lr","")
  260. url = "https://latvijasradio.lsm.lv/lv/tiesraide/?channel=%s"%lrn
  261. r = self._http_request(url)
  262. m = re.search('"file":"([^"]+?m3u8.*?)"', r)
  263. if not m:
  264. raise Exception("No stream found")
  265. data2 = m.group(1).replace("\\","")
  266. img = ch + ".png"
  267. ### Ieraksts (video) ###
  268. elif clist in ("ieraksts","statja"):
  269. r = self.call(data, lang=lang)
  270. m = re.search('<iframe src="([^"]+)"', r)
  271. if not m:
  272. raise Exception("No stream found")
  273. url2 = m.group(1).replace("&amp;", "&")
  274. #m = re.search('<h1 class="title">([^<]+)</h1>', r)
  275. m = re.search('<meta name="name" content="([^"]+)">', r)
  276. title = m.group(1)
  277. title = title.replace("- REplay.lv", "")
  278. title = title.strip()
  279. m = re.search('<meta content="([^"]+)" property="og:video:release_date"', r)
  280. if m:
  281. dd = m.group(1).split("T")[0].split("-")
  282. title += " (%s.%s.%s)" % (dd[2], dd[1], dd[0])
  283. m = re.search('<meta content="([^"]+)" property="og:image">', r)
  284. img = m.group(1) if m else ""
  285. m = re.search('<div class="description">(.+?)</div>', r, re.DOTALL)
  286. if m:
  287. plot = m.group(1)
  288. rr = re.sub("<.+?>", " ", m.group(1))
  289. rr = re.sub("[\t\n]", " ", rr)
  290. plot = re.sub(" {2,100}", " ", rr).strip()
  291. else:
  292. plot = ""
  293. desc = [title]
  294. m = re.search(r'<figure><span class="channel-logo (\w+)"></span></figure>([^<]+)</a>', r, re.IGNORECASE)
  295. if m:
  296. desc.append("Programma: %s [%s]" % (m.group(2), m.group(1).upper()))
  297. m = re.search('<ul class="media-info-list">(.+?)</ul>', r, re.DOTALL)
  298. if m:
  299. for m2 in re.finditer("<strong>([^<]+)</strong> *(<a[^<]+?>)*([^<]+)*<", m.group(1)):
  300. desc.append("%s %s" % (m2.group(1).strip(),m2.group(3).strip() if m2.group(3) else ""))
  301. m2 = re.search('li class="[^"]*">(Pieejam.+)</li>', m.group(1))
  302. if m2:
  303. desc.append(m2.group(1))
  304. desc.append(plot)
  305. desc = "\n".join(desc)
  306. nfo["title"] = title
  307. nfo["thumb"] = img
  308. #nfo["runtime"] = None
  309. #nfo["quality"] = None
  310. nfo["plot"] = desc
  311. nfo["tagline"] = plot
  312. headers = headers2dict("""
  313. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
  314. Referer: https://replay.lsm.lv/%s/%s
  315. """% (lang, data))
  316. r2 = self._http_request(url2,headers=headers)
  317. m = re.search(r"<script>LTV.Video.players.create\('videoplay', ({.+?})\);</script>", r2, re.DOTALL)
  318. #m = re.search('"file":"([^"]+)', r2)
  319. if m:
  320. js = json.loads(m.group(1))
  321. if js["ext_video_id"]:
  322. data2 = "https://www.youtube.com/watch?v=" + js["ext_video_id"]
  323. elif "playlist" in js["player"]:
  324. data2 = js["player"]["playlist"][0]["sources"][0]["file"]
  325. elif "clip" in js["player"]:
  326. if len(js["player"]["clip"]["sources"]) > 0:
  327. data2 = js["player"]["clip"]["sources"][0]["src"]
  328. else:
  329. raise Exception("No streams found!")
  330. elif "file" in js["player"]:
  331. data2 = js["player"]["file"]
  332. else:
  333. raise Exception("No stram found")
  334. else: # futbols u.c.
  335. m = re.search('<div class="video-player">.+?<iframe src="([^"]+)', r2, re.DOTALL)
  336. if not m:
  337. raise Exception("No stream found")
  338. url3 = m.group(1)
  339. if url3.startswith("//"):
  340. url3 = "https:" + url3
  341. headers["Referer"] = url2
  342. r3 = self._http_request(url3, headers=headers)
  343. if not r3:
  344. raise Exception("No stream found")
  345. m = re.search(r"src: '([^']+)',\s+?type: 'application/x-mpegurl'", r3, re.DOTALL|re.IGNORECASE)
  346. if not m:
  347. raise Exception("No stream found")
  348. data2 = "https:" + m.group(1)
  349. if "youtube" in data2:
  350. video_id = re.search(r"/watch\?v=([^&]+)",data2).group(1)
  351. data2 = YouTubeVideoUrl().extract(video_id)
  352. if not data2:
  353. raise Exception("No stream found %s"%data)
  354. r2 = self._http_request(data2, headers=headers)
  355. if not r2:
  356. raise Exception("Stream is not accesible %s"%data)
  357. streams = []
  358. slist = re.findall("#EXT-X-STREAM-INF:([^\n]+)\n([^\n]+)", r2, re.DOTALL)
  359. if not slist:
  360. slist = [('BANDWIDTH=0,NAME="SD",RESOLUTION=?', data2)]
  361. for cc in slist:
  362. m = re.search("RESOLUTION=([\dx]+)",cc[0])
  363. resolution = m.group(1) if m else "?"
  364. m = re.search("BANDWIDTH=([\d]+)",cc[0])
  365. bitrate = int(m.group(1)) if m else 0
  366. url2 = cc[1].encode("utf8")
  367. if not url2.startswith("http"):
  368. uu = data2.split("/")[:-1]
  369. uu.append(url2)
  370. url2 = "/".join(uu)
  371. stream = util.item()
  372. stream["name"] = title
  373. stream["url"] = url2
  374. stream["img"] = img
  375. stream["desc"] = desc
  376. stream["resolver"] = "replay"
  377. stream["headers"] = headers
  378. stream["nfo"] = {"movie":nfo}
  379. stream["quality"] = resolution
  380. stream["order"] = bitrate
  381. #stream["filename"] = filename # TODO jāģenere jēdzigs faila nosaukums, kuru pēc tam izmanto downloads
  382. streams.append(stream)
  383. streams = sorted(streams,key=lambda item: item["order"],reverse=True)
  384. return streams
  385. def is_video(self,data):
  386. if "::" in data:
  387. data = data.split("::")[1]
  388. cmd = data.split("/")
  389. if cmd[0] in ("ieraksts","statja"):
  390. return True
  391. elif cmd[0]=="tiesraide" and len(cmd)>1:
  392. return True
  393. elif cmd[0] == "tieshraide":
  394. return True
  395. else:
  396. return False
  397. def call(self, data,headers=headers0,lang=""):
  398. if not lang: lang = self.country
  399. url = API_URL%lang + data
  400. #print "[TVPlay Api] url: ",url
  401. result = []
  402. content = self._http_request(url,headers=headers0)
  403. return content
  404. if __name__ == "__main__":
  405. sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
  406. import run
  407. source = Source()
  408. data= sys.argv[1] if len(sys.argv)>1 else source.name+"::home"
  409. run.run(source, data)
  410. sys.exit()