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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #!/usr/bin/env python
  2. # coding=utf8
  3. import sys, os, traceback
  4. from Tkinter import *
  5. try:
  6. from ttk import *
  7. except:
  8. pass
  9. import tkMessageBox as tkm
  10. import tkSimpleDialog as tkd
  11. import PIL, StringIO
  12. from PIL import ImageTk, Image
  13. import requests, urllib2
  14. from ContentSources import ContentSources
  15. from sources.SourceBase import stream_type
  16. import util
  17. cunicode = lambda s: s.decode("utf8") if isinstance(s, str) else s
  18. cstr = lambda s: s.encode("utf8") if isinstance(s, unicode) else s
  19. class Main(Frame):
  20. def __init__(self, sources, cfg_file="streams.cfg"):
  21. self.root = Tk()
  22. self.root.geometry("1050x600")
  23. Frame.__init__(self, self.root)
  24. img = PhotoImage(file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'icon.gif'))
  25. #img = PhotoImage(file= 'icon.gif')
  26. self.root.tk.call('wm', 'iconphoto', self.root._w, img)
  27. self.pack(fill=BOTH, expand=1)
  28. self.initUI()
  29. items = ["item %s" % i for i in range(20)]
  30. if sources:
  31. self.sources = sources
  32. else:
  33. self.sources = ContentSources("sources", cfg_file)
  34. self.picons_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "picons")
  35. self.tmp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tmp")
  36. if not os.path.exists(self.tmp_path):
  37. os.mkdir(self.tmp_path)
  38. self.history = []
  39. self.cur_index = 0
  40. self.cur = ("Home", "config::home", "", "PlayStream home")
  41. def initUI(self):
  42. self.master.title("PlayStream")
  43. self.txt1 = Label(self)
  44. self.txt1.pack(side=TOP, fill=X, padx=15)
  45. self.txt1.configure(font=("Tahoma", 14, "bold"))
  46. self.txt1.config(text="")
  47. self.txt2 = Label(self)
  48. self.txt2.pack(side=TOP, fill=X, padx=15)
  49. self.txt2.config(text="")
  50. self.txt2.configure(font=("Tahoma", 12))
  51. frame1 = Frame(self)
  52. frame1.pack(side=TOP, fill=X, padx=15)
  53. scrollbar = Scrollbar(frame1, orient=VERTICAL)
  54. self.listbox = Listbox(frame1, width=80, height=30, yscrollcommand=scrollbar.set)
  55. scrollbar.config(command=self.listbox.yview)
  56. #self.listbox.config(yscrollcommand=scrollbar.set)
  57. self.listbox.pack(side=LEFT)
  58. scrollbar.pack(side=LEFT, fill=Y)
  59. self.listbox.focus_set()
  60. self.listbox.bind('<<ListboxSelect>>', lambda evt: self.list_action(evt, "Changed"))
  61. self.listbox.bind('<Double-1> ', lambda evt:self.list_action(None, "Return"))
  62. self.listbox.bind('<Key> ', self.list_action)
  63. self.pic = Canvas(frame1, width=400, height=250)
  64. self.pic.pack(side=TOP, expand=1)
  65. frame2 = Frame(frame1, height=250, width=400)
  66. frame2.pack(side=TOP)
  67. scrollbar2 = Scrollbar(frame2)
  68. self.desc = Text(frame2, height=15, width=70, wrap="word", yscrollcommand=scrollbar2.set) #, borderwidth=0, highlightthickness=0)
  69. scrollbar2.config(command=self.desc.yview)
  70. scrollbar2.pack(side="right", fill="y")
  71. self.desc.configure(font=("Tahoma", 10))
  72. self.desc.pack(anchor=N)
  73. frame3 = Frame(self, height=40)
  74. frame3.pack(side=TOP, fill=X, padx=15)
  75. self.b_select = Button(frame3, text="Select",
  76. command=lambda :self.list_action(None, "Return"), width=10)
  77. self.b_select.pack(side=LEFT, anchor=S)
  78. self.b_back = Button(frame3, text="Back",
  79. command=lambda :self.list_action(None, "Escape"), width=10)
  80. self.b_back.pack(side=LEFT, anchor=S)
  81. self.b_options = Button(frame3, text="Options",
  82. command=lambda :self.list_action(None, "Options"), width=10)
  83. self.b_options.pack(side=LEFT, anchor=S)
  84. self.b_config = Button(frame3, text="Config",
  85. command=lambda :self.list_action(None, "Config"), width=10)
  86. self.b_config.pack(side=LEFT, anchor=S)
  87. self.b_exit = Button(frame3, text="Exit",
  88. command=self.quit, width=10)
  89. self.b_exit.pack(side=LEFT, anchor=S)
  90. def list_action(self, evt=None, key=None):
  91. cs = int(self.listbox.curselection()[0])
  92. self.cur_index = cs
  93. if (not key) and evt:
  94. w = evt.widget
  95. value = w.get(cs)
  96. key = evt.keysym
  97. #print 'Key: %s' % (key)
  98. data = self.content[cs][1]
  99. cur2 = self.content[cs]
  100. if key == "Changed":
  101. self.show_desc()
  102. self.show_pic()
  103. elif key == "Return" and data <> "back":
  104. if not self.sources.is_video(data):
  105. if "{0}" in data:
  106. a = tkd.askstring("Search", "Search for")
  107. cur2 = (cur2[0],cur2[1].format(a),cur2[2],cur2[3])
  108. self.history.append((self.cur, cs))
  109. self.cur = self.content[cs]
  110. self.cur_index = 0
  111. try:
  112. self.show_content(cur2)
  113. except Exception as e:
  114. print unicode(e)
  115. traceback.print_exc()
  116. tkm.showerror("Error getting content", unicode(e))
  117. prev = self.history.pop()
  118. self.cur = prev[0]
  119. self.cur_index = prev[1]
  120. self.show_content(self.cur, self.cur_index)
  121. return
  122. else:
  123. self.play_video(self.content[cs])
  124. self.listbox.selection_set(self.cur_index)
  125. self.listbox.activate(self.cur_index)
  126. self.listbox.focus_set()
  127. elif key == "BackSpace" or key == "Escape" or (key == "Return" and data == "back"):
  128. if self.history:
  129. prev = self.history.pop()
  130. self.cur = prev[0]
  131. self.cur_index = prev[1]
  132. self.show_content(self.cur, self.cur_index)
  133. elif key == "Right":
  134. index2 = self.cur_index + 20
  135. index2 = min(index2, self.listbox.index(END) - 1)
  136. self.set_list_item(index2)
  137. elif key == "Left":
  138. index2 = self.cur_index - 20
  139. index2 = max(index2, 0)
  140. self.set_list_item(index2)
  141. elif key in ("i", "I"):
  142. print "TODO - indo"
  143. def set_list_item(self, index):
  144. self.cur_index = index
  145. self.listbox.selection_clear(0, END)
  146. self.listbox.selection_set(index)
  147. self.listbox.activate(index)
  148. self.listbox.focus_set()
  149. self.update()
  150. def show_content(self, cur2, cur_index=0):
  151. data = cur2[1]
  152. self.txt1.config(text=cur2[0])
  153. self.content = self.sources.get_content(data)
  154. self.listbox.delete(0, END)
  155. for item in self.content:
  156. self.listbox.insert(END, item[0])
  157. self.listbox.selection_set(cur_index)
  158. self.listbox.activate(cur_index)
  159. self.listbox.focus_set()
  160. self.show_desc()
  161. self.show_pic()
  162. def show_desc(self):
  163. cs = self.listbox.curselection()[0]
  164. self.txt2.config(text=self.content[cs][0])
  165. self.desc.config(state=NORMAL)
  166. self.desc.delete("1.0", END)
  167. self.desc.insert(END, self.content[cs][3])
  168. self.desc.insert(END, "\n\n"+self.content[cs][1])
  169. self.desc.insert(END, "\n"+self.content[cs][2])
  170. self.desc.config(state=DISABLED)
  171. def show_pic(self):
  172. cs = self.listbox.curselection()[0]
  173. img = self.content[cs][2]
  174. self.pic.delete(ALL)
  175. if not img:
  176. return
  177. if not img.startswith("http"):
  178. img_path = os.path.join(self.picons_path, img)
  179. if img and os.path.exists(img_path):
  180. im = Image.open(img_path)
  181. else:
  182. im = None
  183. print "No image found ", img_path
  184. elif img:
  185. fcache = img.replace(":", "_").replace("/", "-").replace(":", "_")
  186. fcache = os.path.join(self.tmp_path, fcache)
  187. if os.path.exists(fcache):
  188. im = Image.open(fcache)
  189. else:
  190. r = requests.get(img)
  191. if r.status_code == 200:
  192. img_data = r.content
  193. im = Image.open(StringIO.StringIO(r.content))
  194. with open(fcache, "wb") as f:
  195. f.write(r.content)
  196. else:
  197. im = None
  198. if im:
  199. im.thumbnail((400, 250))
  200. image = ImageTk.PhotoImage(im)
  201. imagesprite = self.pic.create_image(200, 125,image=image)
  202. self.pic.image = image
  203. def play_video(self, cur2):
  204. if self.sources.stream_type(cur2[1]):
  205. stream = util.item()
  206. stream["url"] = cur2[1]
  207. stream["name"] = cur2[0]
  208. streams = [stream]
  209. else:
  210. try:
  211. streams = self.sources.get_streams(cur2[1])
  212. except Exception as e:
  213. print unicode(e)
  214. traceback.print_exc()
  215. tkm.showerror("Error getting streams", unicode(e))
  216. return
  217. if not streams:
  218. tkm.showerror("Error getting streams", "No streams found")
  219. return
  220. if len(streams) > 1:
  221. lst = []
  222. for i,s in enumerate(streams):
  223. s = {k:v.decode("utf8") if v and isinstance(v, str) else v for k,v in zip(s.keys(), s.values())}
  224. lst.append(("[%s,%s] %s"%(s["lang"],s["quality"],s["name"]),i))
  225. a = ChoiceBox("ChoiceBox test", "Select stream", lst, parent=self, width=40).result
  226. stream = streams[a]
  227. else:
  228. stream = streams[0]
  229. play_stream(stream, self.tmp_path)
  230. def start(self):
  231. self.root.mainloop()
  232. class ChoiceBox(tkd.Dialog):
  233. #a = ChoiceBox("ChoiceBox test", "Select stream", items, parent=self).result
  234. def __init__(self, title, prompt, items, width=20, height=15,
  235. initialvalue=0, parent = None):
  236. if not parent:
  237. import Tkinter
  238. parent = Tkinter._default_root
  239. self.prompt = prompt
  240. self.items = items
  241. self.width = width
  242. self.height = height
  243. self.result = None
  244. self.initialvalue = initialvalue
  245. tkd.Dialog.__init__(self, parent, title)
  246. def body(self, master):
  247. w = Label(self, text=self.prompt, justify=LEFT)
  248. w.pack(side=TOP)
  249. frame1 = Frame(self)
  250. frame1.pack(side=TOP, fill=X, expand=1, padx=5)
  251. scrollbar = Scrollbar(frame1, orient=VERTICAL)
  252. self.listbox = Listbox(frame1, yscrollcommand=scrollbar.set, width=self.width, height=self.height)
  253. scrollbar.config(command=self.listbox.yview)
  254. #self.listbox.config(yscrollcommand=scrollbar.set)
  255. self.listbox.pack(side=LEFT, fill=BOTH, expand=1)
  256. scrollbar.pack(side=LEFT, fill=Y)
  257. for item in self.items:
  258. self.listbox.insert(END, item)
  259. self.listbox.selection_set(self.initialvalue)
  260. self.listbox.activate(self.initialvalue)
  261. self.listbox.bind('<Double-1> ', self.list_select)
  262. self.listbox.bind('<Key> ', self.list_key)
  263. self.update()
  264. self.geometry("+%d+%d"%(self.parent.winfo_rootx()+100, self.parent.winfo_rooty()+100 ))
  265. return self.listbox
  266. def list_key(self, evt):
  267. key = evt.keysym
  268. #print 'Key %s' % (key)
  269. if key == "Return":
  270. self.ok()
  271. elif key == "Escape":
  272. self.cancel()
  273. def list_select(self, evt):
  274. self.ok()
  275. def apply(self):
  276. self.result = self.listbox.curselection()[0]
  277. class Info(tkd.Dialog):
  278. #a = ChoiceBox("ChoiceBox test", "Select stream", items, parent=self).result
  279. def __init__(self, title, items, width=20, height=15,
  280. initialvalue=0, parent = None):
  281. if not parent:
  282. import Tkinter
  283. parent = Tkinter._default_root
  284. self.prompt = prompt
  285. self.items = items
  286. self.width = width
  287. self.height = height
  288. self.result = None
  289. self.initialvalue = initialvalue
  290. tkd.Dialog.__init__(self, parent, title)
  291. def body(self, master):
  292. w = Label(self, text=self.prompt, justify=LEFT)
  293. w.pack(side=TOP)
  294. frame1 = Frame(self)
  295. frame1.pack(side=TOP, fill=X, expand=1, padx=5)
  296. scrollbar = Scrollbar(frame1, orient=VERTICAL)
  297. self.listbox = Listbox(frame1, yscrollcommand=scrollbar.set, width=self.width, height=self.height)
  298. scrollbar.config(command=self.listbox.yview)
  299. #self.listbox.config(yscrollcommand=scrollbar.set)
  300. self.listbox.pack(side=LEFT, fill=BOTH, expand=1)
  301. scrollbar.pack(side=LEFT, fill=Y)
  302. for item in self.items:
  303. self.listbox.insert(END, item)
  304. self.listbox.selection_set(self.initialvalue)
  305. self.listbox.activate(self.initialvalue)
  306. self.listbox.bind('<Double-1> ', self.list_select)
  307. self.listbox.bind('<Key> ', self.list_key)
  308. self.update()
  309. self.geometry("+%d+%d"%(self.parent.winfo_rootx()+100, self.parent.winfo_rooty()+100 ))
  310. return self.listbox
  311. def list_key(self, evt):
  312. key = evt.keysym
  313. #print 'Key %s' % (key)
  314. if key == "Return":
  315. self.ok()
  316. elif key == "Escape":
  317. self.cancel()
  318. def list_select(self, evt):
  319. self.ok()
  320. def apply(self):
  321. self.result = self.listbox.curselection()[0]
  322. def run(sources=None, data="config::home", title="Home", cfg_file="streams.cfg"):
  323. app = Main(sources, cfg_file=cfg_file)
  324. app.show_content((title, data, "", ""))
  325. app.start()
  326. def run_cli(sources, data="config::home", title="Home", cfg_file="streams.cfg"):
  327. #options = sources.options_read("ltc")
  328. #print options
  329. history = []
  330. cur = ("Home",data,None,None)
  331. content = sources.get_content(cur[1])
  332. exit_loop = False
  333. while True:
  334. print
  335. for i,item in enumerate(content):
  336. s = "%i: %s - %s %s"%(i,item[0],item[1],item[2])
  337. print s #.encode(sys.stdout.encoding,"replace")
  338. while True:
  339. a = raw_input("Enter number, (-) for download, q for exit: ")
  340. if a in ("q","Q","x","X"):
  341. exit_loop = True
  342. print "Exiting"
  343. break
  344. try:
  345. n = int(a)
  346. break
  347. except:
  348. print "Not number!"
  349. if exit_loop: break
  350. download = False
  351. if n<0:
  352. n = abs(n)
  353. download = True
  354. cur2 = content[n]
  355. data0 = cur2[1].split("::")[1] if "::" in cur2[1] else cur2[1]
  356. if not data0:
  357. pass
  358. elif cur2[1] == "back":
  359. cur = history.pop()
  360. elif sources.is_video(cur2[1]):
  361. if sources.stream_type(cur2[1]):
  362. stream = util.item()
  363. stream["url"] = cur2[1]
  364. stream["name"] = cur2[0]
  365. streams = [stream]
  366. else:
  367. try:
  368. if not download:
  369. streams = sources.get_streams(cur2[1])
  370. else:
  371. stream = util.item()
  372. stream["url"] = cur2[1]
  373. stream["name"] = cur2[0]
  374. stream["url"] = util.streamproxy_encode2(stream["url"])
  375. print stream["url"]
  376. streams = [stream]
  377. except Exception as e:
  378. print unicode(e)
  379. traceback.print_exc()
  380. streams = []
  381. if streams:
  382. if not download:
  383. play_video(streams)
  384. else:
  385. #urlp = util.streamproxy_encode2(streams[0]["url"])
  386. #print urlp
  387. #util.player(urlp)
  388. #Downloader.download_video(streams)
  389. pass
  390. else:
  391. print "**No stream to play - %s "%(
  392. cur2[1])
  393. raw_input("Press any key")
  394. #import os
  395. #os.system('"c:\Program Files (x86)\VideoLAN\VLC\vlc.exe" "%s"'%cur2[1])
  396. else:
  397. if "{0}" in cur2[1]:
  398. a = raw_input("Enter value:")
  399. cur2 = (cur2[0],cur2[1].format(a),cur2[2],cur2[3])
  400. history.append(cur)
  401. cur = cur2
  402. try:
  403. content = sources.get_content(cur[1])
  404. except Exception as e:
  405. print unicode(e)
  406. traceback.print_exc()
  407. raw_input("Continue?")
  408. def play_video(streams, select=False):
  409. if len(streams)>1 and select:
  410. for i,s in enumerate(streams):
  411. print "%s: [%s,%s,%s] %s"%(i,s["quality"],s["lang"],s["type"],s["name"])
  412. a = raw_input("Select stram to play: ")
  413. try:
  414. n = int(a)
  415. except:
  416. n = 0
  417. if n>=len(streams):
  418. stream = streams[-1]
  419. else:
  420. stream = streams[n]
  421. else:
  422. stream = streams[0]
  423. play_stream(stream)
  424. def play_stream(stream, tmp_path=""):
  425. if not tmp_path:
  426. tmp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tmp")
  427. stream = util.stream_change(stream)
  428. title = stream["name"] if not "nfo" in stream or not stream["nfo"] else util.nfo2title(stream["nfo"])
  429. desc = stream["desc"] if not "nfo" in stream or not stream["nfo"] else util.nfo2desc(stream["nfo"])
  430. img = stream["img"]
  431. url = stream["url"]
  432. suburl = ""
  433. subfile = ""
  434. print url
  435. if "subs" in stream and len(stream["subs"]) > 0 and stream["subs"][0]:
  436. suburl = stream["subs"][0]["url"]
  437. subfile = ""
  438. print "\n**Download subtitles %s - %s"%(title,suburl)
  439. try:
  440. subs = urllib2.urlopen(suburl).read()
  441. except:
  442. subs = None
  443. if subs:
  444. fname0 = re.sub("[/\n\r\t,:\?\|'~\.]","_",title)
  445. if ".xml" in suburl:
  446. subs = util.ttaf2srt(subs)
  447. subext = ".srt"
  448. elif ".vtt" in suburl:
  449. subext = ".vtt"
  450. elif ".srt" in suburl:
  451. subext = ".srt"
  452. else:
  453. subext = ""
  454. if subext:
  455. subfile = cunicode(os.path.join(tmp_path,fname0+subext))
  456. with open(subfile,"w") as f:
  457. f.write(subs)
  458. else:
  459. print "\n Error downloading subtitle %s"%suburl
  460. print "\n**Play stream %s\n%s" % (title, url.encode("utf8"))
  461. player(url,title,subfile,stream["headers"])
  462. def player(url, title = "", subfile = "",headers={}, player="ffplay"):
  463. from subprocess import call
  464. title = cunicode(title).encode(sys.getfilesystemencoding())
  465. # encode(sys.getfilesystemencoding())
  466. subfile = unicode(subfile).encode(sys.getfilesystemencoding())
  467. cmd1 = [r"c:\Program Files\VideoLAN\VLC\vlc.exe",url,
  468. "--meta-title",title,
  469. "--http-user-agent","Enigma2"
  470. ]
  471. # 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
  472. cmd2 = [
  473. r"C:\gstreamer\1.0\x86_64\bin\gst-launch-1.0","-v",
  474. #"playbin", 'uri="%s"'%url,
  475. "souphttpsrc", "ssl-strict=false",
  476. "proxy=127.0.0.1:8888",
  477. 'location="%s"'%url,
  478. '!decodebin!autovideosink'
  479. ]
  480. vf = r"drawtext=text='%{pts\:hms}':box=1:x=w-tw-10:y=h-th-10:boxcolor=black@0.5:fontsize=20:fontcolor=white"
  481. if subfile and os.path.exists(subfile):
  482. subfile = subfile.replace("\\", "\\\\\\\\")
  483. subfile = re.sub("\w:", "", subfile)
  484. vf = 'subtitles=%s,' % subfile + vf
  485. cmd3 = ["ffplay.exe",url, "-window_title", title, "-vf", vf]
  486. if not player:
  487. cmd = cmd3 if url.startswith("https") else cmd2
  488. else:
  489. cmd = cmd3 if player == "ffplay" else cmd2
  490. #cmd = cmd2 # visu spēlē ar gst
  491. ret = call(cmd)
  492. #if ret:
  493. #a = raw_input("*** Error, continue")
  494. return
  495. if __name__ == "__main__":
  496. show_hidden = False
  497. data= sys.argv[1] if len(sys.argv) > 1 else "config::home"
  498. cfg_file = sys.argv[2] if len(sys.argv) > 2 else "streams.cfg"
  499. #sources = ContentSources("sources")
  500. sources = None
  501. run(sources, data, cfg_file=cfg_file)