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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. # -*- coding: UTF-8 -*-
  2. # /*
  3. # * Copyright (C) 2011 Libor Zoubek,ivars777
  4. # *
  5. # *
  6. # * This Program is free software; you can redistribute it and/or modify
  7. # * it under the terms of the GNU General Public License as published by
  8. # * the Free Software Foundation; either version 2, or (at your option)
  9. # * any later version.
  10. # *
  11. # * This Program is distributed in the hope that it will be useful,
  12. # * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # * GNU General Public License for more details.
  15. # *
  16. # * You should have received a copy of the GNU General Public License
  17. # * along with this program; see the file COPYING. If not, write to
  18. # * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  19. # * http://www.gnu.org/copyleft/gpl.html
  20. # *
  21. # */
  22. import os, sys, re
  23. import urllib, urllib2
  24. import datetime
  25. import traceback
  26. import cookielib
  27. import requests
  28. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  29. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  30. from htmlentitydefs import name2codepoint as n2cp
  31. import HTMLParser
  32. import StringIO
  33. #import threading
  34. #import Queue
  35. import pickle
  36. import string
  37. import simplejson as json
  38. #from demjson import demjson
  39. #import demjson
  40. import json
  41. #from bs4 import BeautifulSoup
  42. UA = 'Mozilla/6.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.5) Gecko/2008092417 Firefox/3.0.3'
  43. LOG = 2
  44. _cookie_jar = None
  45. CACHE_COOKIES = 'cookies'
  46. def system():
  47. if "kodi" in sys.executable.lower():
  48. return "kodi"
  49. elif sys.platform == "win32":
  50. return "windows"
  51. elif sys.platform == "linux2":
  52. return "enigma2"
  53. else:
  54. return "unknown"
  55. def play_video(streams):
  56. if len(streams)>1:
  57. for i,s in enumerate(streams):
  58. print "%s: [%s,%s,%s] %s"%(i,s["quality"],s["lang"],s["type"],s["name"])
  59. a = raw_input("Select stram to play: ")
  60. try:
  61. n = int(a)
  62. except:
  63. n = 0
  64. if n>=len(streams):
  65. stream = streams[-1]
  66. else:
  67. stream = streams[n]
  68. else:
  69. stream = streams[0]
  70. stream = stream_change(stream)
  71. title = stream["name"]
  72. url = stream["url"]
  73. suburl = ""
  74. print url
  75. if "subs" in stream and stream["subs"]:
  76. suburl = stream["subs"][0]["url"]
  77. print "\n**Download subtitles %s - %s"%(title,suburl)
  78. subs = urllib2.urlopen(suburl).read()
  79. if subs:
  80. fname0 = re.sub("[/\n\r\t,]","_",title)
  81. subext = ".srt"
  82. subfile = os.path.join("",fname0+subext)
  83. if ".xml" in suburl:
  84. subs = ttaf2srt(subs)
  85. with open(subfile,"w") as f:
  86. f.write(subs)
  87. else:
  88. print "\n Error downloading subtitle %s"%suburl
  89. return player(url,stream["name"],suburl,stream["headers"])
  90. def player(url,title="",suburl="",headers={}):
  91. from subprocess import call
  92. print "\n**Play stream %s\n%s"%(title,url.encode("utf8"))
  93. cmd1 = [r"c:\Program Files\VideoLAN\VLC\vlc.exe",url,
  94. "--meta-title",title.decode("utf8").encode(sys.getfilesystemencoding()),
  95. "--http-user-agent","Enigma2"
  96. ]
  97. # gst-launch-1.0 -v souphttpsrc ssl-strict=false proxy=127.0.0.1:8888 extra-headers="Origin:adadadasd" location="http://bitdash-a.akamaihd.net/content/sintel/sintel.mpd" ! decodebin! autovideosink
  98. cmd2 = [
  99. r"C:\gstreamer\1.0\x86_64\bin\gst-launch-1.0","-v",
  100. "playbin", 'uri="%s"'%url,
  101. #"souphttpsrc", "ssl-strict=false",
  102. #"proxy=127.0.0.1:8888",
  103. #'location="%s"'%url,
  104. #'!decodebin!autovideosink'
  105. ]
  106. cmd3 = ["ffplay.exe",url]
  107. cmd = cmd1 if url.startswith("https") else cmd2
  108. ret = call(cmd3)
  109. #if ret:
  110. #a = raw_input("*** Error, continue")
  111. return
  112. SPLIT_CHAR = "~"
  113. SPLIT_CODE = urllib.quote(SPLIT_CHAR)
  114. EQ_CODE = urllib.quote("=")
  115. COL_CODE = urllib.quote(":")
  116. SPACE_CODE = urllib.quote(" ")
  117. PROXY_URL = "http://localhost:88/"
  118. def make_fname(title):
  119. "Make file name from title"
  120. title = title.strip()
  121. fname0 = re.sub("[/\n\r\t,:]"," ",title)
  122. fname0 = re.sub("['""]","",fname0)
  123. return fname0
  124. def stream_change(stream):
  125. #return stream # TODO
  126. if "resolver" in stream and stream["resolver"] in ("viaplay","hqq","filmas") or \
  127. "surl" in stream and re.search("https*://(hqq|goo.\gl)",stream["surl"]):
  128. stream["url"] = streamproxy_encode(stream["url"],stream["headers"])
  129. stream["headers"] = {}
  130. return stream
  131. else:
  132. return stream
  133. def streamproxy_encode(url,headers=[]):
  134. if not "?" in url:
  135. url = url+"?"
  136. url2 = url.replace(SPLIT_CHAR,SPLIT_CODE).replace(":",COL_CODE).replace(" ",SPACE_CODE)
  137. url2 = PROXY_URL + url2
  138. if headers:
  139. headers2 = []
  140. for h in headers:
  141. headers2.append("%s=%s"%(h,headers[h].replace("=",EQ_CODE).replace(SPLIT_CHAR,SPLIT_CODE).replace(" ",SPACE_CODE)))
  142. headers2 = SPLIT_CHAR.join(headers2)
  143. url2 = url2+SPLIT_CHAR+headers2
  144. return url2
  145. def streamproxy_decode(urlp):
  146. import urlparse
  147. path = urlp.replace(re.search("http://[^/]+",urlp).group(0),"")
  148. p = path.split(SPLIT_CHAR)
  149. url = urllib.unquote(p[0][1:])
  150. #headers = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/47.0.2526.70 Mobile/13C71 Safari/601.1.46"}
  151. headers={}
  152. if len(p)>1:
  153. for h in p[1:]:
  154. #h = urllib.unquote()
  155. headers[h.split("=")[0]]=urllib.unquote(h.split("=")[1])
  156. return url,headers
  157. class Captions(object):
  158. def __init__(self,uri):
  159. self.uri = uri
  160. self.subs = []
  161. self.styles = {}
  162. if uri.startswith("http"):
  163. r = requests.get(uri)
  164. if r.status_code == 200:
  165. self.loads(r.content)
  166. def loads(self,s):
  167. if "WEBVTT" in s[:s.find("\n")]: # vtt captions
  168. self.load_vtt(s)
  169. elif "<?xml" in s[:s.find("\n")]:
  170. self.load_ttaf(s)
  171. else:
  172. self.load_vtt(s) # TODO
  173. def load_ttaf(self,s):
  174. for r2 in re.findall("<style .+?/>", s):
  175. st = {}
  176. for a in re.findall(r'(\w+)="([^ "]+)"', r2):
  177. st[a[0]] = a[1]
  178. if a[0] == "id":
  179. sid = a[1]
  180. self.styles[sid] = st
  181. for r2 in re.findall("<p .+?</p>", s):
  182. sub = {}
  183. sub["begin"] = str2sec(re.search('begin="([^"]+)"', r2).group(1)) if re.search('begin="([^"]+)"', r2) else -1
  184. sub["end"] = str2sec(re.search('end="([^"]+)"', r2).group(1)) if re.search('end="([^"]+)"', r2) else -1
  185. sub["style"] = re.search('style="([^"]+)"', r2).group(1) if re.search('style="([^"]+)"', r2) else None
  186. sub["text"] = re.search("<p[^>]+>(.+)</p>", r2).group(1).replace("\n","")
  187. sub["text"] = re.sub("<br\s*?/>","\n",sub["text"])
  188. sub["text"] = re.sub("<.+?>"," ",sub["text"])
  189. self.subs.append(sub)
  190. pass
  191. def load_vtt(self,s):
  192. f = StringIO.StringIO(s)
  193. while True:
  194. line = f.readline()
  195. if not line:
  196. break
  197. m = re.search(r"([\d\.\,:]+)\s*-->\s*([\d\.\,\:]+)",line)
  198. if m:
  199. sub = {}
  200. sub["begin"] = str2sec(m.group(1))
  201. sub["end"] = str2sec(m.group(2))
  202. sub["style"] = None
  203. sub["text"] = []
  204. line = f.readline()
  205. while line.strip():
  206. txt = line.strip()
  207. if isinstance(txt,unicode):
  208. txt = txt.encode("utf8")
  209. sub["text"].append(txt)
  210. line = f.readline()
  211. sub["text"] = "\n".join(sub["text"])
  212. self.subs.append(sub)
  213. else:
  214. continue
  215. pass
  216. def get_srt(self):
  217. out = ""
  218. i = 0
  219. for sub in self.subs:
  220. i +=1
  221. begin = sub["begin"]
  222. begin = "%s,%03i"%(str(datetime.timedelta(seconds=begin/1000)),begin%1000)
  223. end = sub["end"]
  224. end = "%s,%03i"%(str(datetime.timedelta(seconds=end/1000)),end%1000)
  225. txt2 = sub["text"]
  226. out += "%s\n%s --> %s\n%s\n\n\n"%(i,begin,end,txt2)
  227. return out
  228. def str2sec(r):
  229. # Convert str time to miliseconds
  230. r= r.replace(",",".")
  231. m = re.search(r"(\d+\:)*(\d+)\:(\d+\.\d+)", r)
  232. if m:
  233. sec = int(m.group(1)[:-1])*60*60*1000 if m.group(1) else 0
  234. sec += int(m.group(2))*60*1000 + int(float(m.group(3))*1000)
  235. return sec
  236. else:
  237. return -1
  238. #c = Captions("http://195.13.216.2/mobile-vod/mp4:lb_barbecue_fr_lq.mp4/lb_barbecue_lv.vtt")
  239. #c = Captions("http://www.bbc.co.uk/iplayer/subtitles/ng/modav/bUnknown-0edd6227-0f38-411c-8d46-fa033c4c61c1_b05ql1s3_1479853893356.xml")
  240. #url = "http://195.13.216.2/mobile-vod/mp4:ac_now_you_see_me_2_en_lq.mp4/ac_now_you_see_me_2_lv.vtt"
  241. #c = Captions(url)
  242. #pass
  243. def ttaf2srt(s):
  244. out = u""
  245. i = 0
  246. for p,txt in re.findall("<p ([^>]+)>(.+?)</p>", s, re.DOTALL):
  247. i +=1
  248. begin = re.search('begin="(.+?)"',p).group(1)
  249. begin = begin.replace(".",",")
  250. end = re.search('end="(.+?)"',p).group(1)
  251. end = end.replace(".",",")
  252. txt2 = re.sub("<br */>","\n",txt)
  253. out += "%s\n%s --> %s\n%s\n\n"%(i,begin,end,txt2)
  254. return out
  255. def item():
  256. """Default item content"""
  257. stream0 = {
  258. 'name': '', #
  259. 'url': '',
  260. 'quality': '?',
  261. 'surl': '',
  262. 'subs': [],
  263. 'headers': {},
  264. "desc":"",
  265. "img":"",
  266. "lang":"",
  267. "type":"",
  268. "resolver":"",
  269. "order":0,
  270. "live":False
  271. }
  272. return stream0
  273. class _StringCookieJar(cookielib.LWPCookieJar):
  274. def __init__(self, string=None, filename=None, delayload=False, policy=None):
  275. cookielib.LWPCookieJar.__init__(self, filename, delayload, policy)
  276. if string and len(string) > 0:
  277. self._cookies = pickle.loads(str(string))
  278. def dump(self):
  279. return pickle.dumps(self._cookies)
  280. def init_urllib(cache=None):
  281. """
  282. Initializes urllib cookie handler
  283. """
  284. global _cookie_jar
  285. data = None
  286. if cache is not None:
  287. data = cache.get(CACHE_COOKIES)
  288. _cookie_jar = _StringCookieJar(data)
  289. opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(_cookie_jar))
  290. urllib2.install_opener(opener)
  291. def cache_cookies(cache):
  292. """
  293. Saves cookies to cache
  294. """
  295. global _cookie_jar
  296. if _cookie_jar:
  297. cache.set(CACHE_COOKIES, _cookie_jar.dump())
  298. def request0(url, headers={}):
  299. debug('request: %s' % url)
  300. req = urllib2.Request(url, headers=headers)
  301. req.add_header('User-Agent', UA)
  302. try:
  303. response = urllib2.urlopen(req)
  304. data = response.read()
  305. response.close()
  306. except urllib2.HTTPError, error:
  307. data = error.read()
  308. debug('len(data) %s' % len(data))
  309. return data
  310. def request(url, headers={}):
  311. debug('request: %s' % url)
  312. #req = urllib2.Request(url, headers=headers)
  313. #req.add_header('User-Agent', UA)
  314. if 'User-Agent' not in headers:
  315. headers['User-Agent']= UA
  316. try:
  317. r = requests.get(url, headers=headers)
  318. data = r.content
  319. except:
  320. data = r.content
  321. debug('len(data) %s' % len(data))
  322. return data
  323. def post(url, data, headers={}):
  324. postdata = urllib.urlencode(data)
  325. #req = urllib2.Request(url, postdata, headers)
  326. #req.add_header('User-Agent', UA)
  327. import requests
  328. if 'User-Agent' not in headers:
  329. headers['User-Agent']= UA
  330. try:
  331. r = requests.post(url, data=postdata,headers=headers)
  332. data = r.content
  333. except urllib2.HTTPError, error:
  334. data = r.content
  335. return data
  336. def post0(url, data, headers={}):
  337. postdata = urllib.urlencode(data)
  338. req = urllib2.Request(url, postdata, headers)
  339. req.add_header('User-Agent', UA)
  340. try:
  341. response = urllib2.urlopen(req)
  342. data = response.read()
  343. response.close()
  344. except urllib2.HTTPError, error:
  345. data = error.read()
  346. return data
  347. def post_json(url, data, headers={}):
  348. postdata = json.dumps(data)
  349. headers['Content-Type'] = 'application/json'
  350. req = urllib2.Request(url, postdata, headers)
  351. req.add_header('User-Agent', UA)
  352. response = urllib2.urlopen(req)
  353. data = response.read()
  354. response.close()
  355. return data
  356. #def run_parallel_in_threads(target, args_list):
  357. #result = Queue.Queue()
  358. ## wrapper to collect return value in a Queue
  359. #def task_wrapper(*args):
  360. #result.put(target(*args))
  361. #threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]
  362. #for t in threads:
  363. #t.start()
  364. #for t in threads:
  365. #t.join()
  366. #return result
  367. def substr(data, start, end):
  368. i1 = data.find(start)
  369. i2 = data.find(end, i1)
  370. return data[i1:i2]
  371. def save_to_file(url, file):
  372. try:
  373. return save_data_to_file(request(url), file)
  374. except:
  375. traceback.print_exc()
  376. def save_data_to_file(data, file):
  377. try:
  378. f = open(file, 'wb')
  379. f.write(data)
  380. f.close()
  381. info('File %s saved' % file)
  382. return True
  383. except:
  384. traceback.print_exc()
  385. def read_file(file):
  386. if not os.path.exists(file):
  387. return ''
  388. f = open(file, 'r')
  389. data = f.read()
  390. f.close()
  391. return data
  392. def _substitute_entity(match):
  393. ent = match.group(3)
  394. if match.group(1) == '#':
  395. # decoding by number
  396. if match.group(2) == '':
  397. # number is in decimal
  398. return unichr(int(ent))
  399. elif match.group(2) == 'x':
  400. # number is in hex
  401. return unichr(int('0x' + ent, 16))
  402. else:
  403. # they were using a name
  404. cp = n2cp.get(ent)
  405. if cp:
  406. return unichr(cp)
  407. else:
  408. return match.group()
  409. def decode_html(data):
  410. if not type(data) == str:
  411. return data
  412. try:
  413. if not type(data) == unicode:
  414. data = unicode(data, 'utf-8', errors='ignore')
  415. entity_re = re.compile(r'&(#?)(x?)(\w+);')
  416. return entity_re.subn(_substitute_entity, data)[0]
  417. except:
  418. traceback.print_exc()
  419. print[data]
  420. return data
  421. def unescape(s0):
  422. #s2 = re.sub("&#\w+;",HTMLParser.HTMLParser().unescape("\1"),s)
  423. s0 = s0.replace("&amp;","&")
  424. for s in re.findall("&#\w+;",s0):
  425. s2 = HTMLParser.HTMLParser().unescape(s)
  426. if isinstance(s0,str):
  427. s2 = s2.encode("utf8")
  428. s0 = s0.replace(s,s2)
  429. pass
  430. return s0
  431. def debug(text):
  432. if LOG > 1:
  433. print('[DEBUG] ' + str([text]))
  434. def info(text):
  435. if LOG > 0:
  436. print('[INFO] ' + str([text]))
  437. def error(text):
  438. print('[ERROR] ' + str([text]))
  439. _diacritic_replace = {u'\u00f3': 'o',
  440. u'\u0213': '-',
  441. u'\u00e1': 'a',
  442. u'\u010d': 'c',
  443. u'\u010c': 'C',
  444. u'\u010f': 'd',
  445. u'\u010e': 'D',
  446. u'\u00e9': 'e',
  447. u'\u011b': 'e',
  448. u'\u00ed': 'i',
  449. u'\u0148': 'n',
  450. u'\u0159': 'r',
  451. u'\u0161': 's',
  452. u'\u0165': 't',
  453. u'\u016f': 'u',
  454. u'\u00fd': 'y',
  455. u'\u017e': 'z',
  456. u'\xed': 'i',
  457. u'\xe9': 'e',
  458. u'\xe1': 'a',
  459. }
  460. def replace_diacritic(string):
  461. ret = []
  462. for char in string:
  463. if char in _diacritic_replace:
  464. ret.append(_diacritic_replace[char])
  465. else:
  466. ret.append(char)
  467. return ''.join(ret)
  468. def params(url=None):
  469. if not url:
  470. url = sys.argv[2]
  471. param = {}
  472. paramstring = url
  473. if len(paramstring) >= 2:
  474. params = url
  475. cleanedparams = params.replace('?', '')
  476. if (params[len(params) - 1] == '/'):
  477. params = params[0:len(params) - 2]
  478. pairsofparams = cleanedparams.split('&')
  479. param = {}
  480. for i in range(len(pairsofparams)):
  481. splitparams = {}
  482. splitparams = pairsofparams[i].split('=')
  483. if (len(splitparams)) == 2:
  484. param[splitparams[0]] = splitparams[1]
  485. for p in param.keys():
  486. param[p] = param[p].decode('hex')
  487. return param
  488. def int_to_base(number, base):
  489. digs = string.digits + string.letters
  490. if number < 0:
  491. sign = -1
  492. elif number == 0:
  493. return digs[0]
  494. else:
  495. sign = 1
  496. number *= sign
  497. digits = []
  498. while number:
  499. digits.append(digs[number % base])
  500. number /= base
  501. if sign < 0:
  502. digits.append('-')
  503. digits.reverse()
  504. return ''.join(digits)
  505. def extract_jwplayer_setup(data):
  506. """
  507. Extracts jwplayer setup configuration and returns it as a dictionary.
  508. :param data: A string to extract the setup from
  509. :return: A dictionary containing the setup configuration
  510. """
  511. data = re.search(r'<script.+?}\(\'(.+)\',\d+,\d+,\'([\w\|]+)\'.*</script>', data, re.I | re.S)
  512. if data:
  513. replacements = data.group(2).split('|')
  514. data = data.group(1)
  515. for i in reversed(range(len(replacements))):
  516. if len(replacements[i]) > 0:
  517. data = re.sub(r'\b%s\b' % int_to_base(i, 36), replacements[i], data)
  518. data = re.search(r'\.setup\(([^\)]+?)\);', data)
  519. if data:
  520. return json.loads(data.group(1).decode('string_escape'))
  521. #return demjson.decode(data.group(1).decode('string_escape')) ### III
  522. return None
  523. #def parse_html(url):
  524. # return BeautifulSoup(request(url), 'html5lib', from_encoding='utf-8')
  525. if __name__ == "__main__":
  526. s = 'B\xc4\x93thovena D\xc4\x81rgumu Taka (2014)/Beethoven&#x27;s Treasure [LV]'
  527. #s = s.decode("utf8")
  528. #s=unescape(s)
  529. #url = "http://localhost:88/https://walterebert.com/playground/video/hls/ts/480x270.m3u8?token=xxxx~User-Agent=Enigma2~Cookie=xxxxx"
  530. url = "http://hyt4d6.vkcache.com/secip/0/UMQ3q2gNjTlOPnEVm3iTiA/ODAuMjMyLjI0MC42/1479610800/hls-vod-s3/flv/api/files/videos/2015/09/11/144197748923a22.mp4.m3u8http://hyt4d6.vkcache.com/secip/0/Y-ZA1qRm8toplc0dN_L6_w/ODAuMjMyLjI0MC42/1479654000/hls-vod-s3/flv/api/files/videos/2015/09/11/144197748923a22.mp4.m3u8"
  531. headers = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/47.0.2526.70 Mobile/13C71 Safari/601.1.46"}
  532. urlp = streamproxy_encode(url,headers)
  533. print urlp
  534. player(urlp)
  535. pass