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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. #!/usr/bin/env python
  2. # coding=utf8
  3. #
  4. #
  5. # This file is part of PlayStream - enigma2 plugin to play video streams from various sources
  6. # Copyright (c) 2016 ivars777 (ivars777@gmail.com)
  7. # Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
  8. # Used fragments of code from enigma2-plugin-tv3play by Taapat (https://github.com/Taapat/enigma2-plugin-tv3play)
  9. #
  10. import os,time,sys,os,os.path
  11. import datetime,re
  12. import urllib2
  13. from enigma import ePicLoad, eServiceReference, eTimer, getDesktop
  14. from Components.ActionMap import ActionMap, HelpableActionMap
  15. from Screens.InfoBarGenerics import InfoBarShowHide
  16. from Components.AVSwitch import AVSwitch
  17. from Components.Label import Label
  18. from Components.MenuList import MenuList
  19. from Components.Pixmap import Pixmap
  20. from Components.Sources.List import List
  21. from Components.Sources.StaticText import StaticText
  22. from Components.Button import Button
  23. from Plugins.Plugin import PluginDescriptor
  24. from Screens.InfoBar import MoviePlayer
  25. from Screens.MessageBox import MessageBox
  26. from Tools import Notifications
  27. from Screens.LocationBox import LocationBox
  28. #from Screens.InputBox import InputBox
  29. from Screens.ChoiceBox import ChoiceBox
  30. from Screens.VirtualKeyBoard import VirtualKeyBoard
  31. from Components.Input import Input
  32. from Screens.Screen import Screen
  33. from Tools.BoundFunction import boundFunction
  34. from Tools.Directories import resolveFilename, SCOPE_PLUGINS
  35. from Tools.LoadPixmap import LoadPixmap
  36. from twisted.web.client import downloadPage,defer,reactor
  37. from Components.Task import job_manager
  38. from Components.config import config, ConfigSubsection, ConfigText, ConfigInteger, ConfigLocations, ConfigDirectory, ConfigSet, ConfigYesNo, ConfigSelection, getConfigListEntry, ConfigSelectionNumber
  39. import ContentSources
  40. import util
  41. from VideoDownload import downloadJob, HLSDownloadJob,VideoDownloadList
  42. #import enigma2_api
  43. e2 = None
  44. ##########################################################################
  45. from Components.config import config, ConfigSubsection, ConfigYesNo, getConfigListEntry, \
  46. ConfigSelection, ConfigNumber, ConfigDirectory,ConfigText
  47. from Components.ConfigList import ConfigListScreen
  48. from Screens.LocationBox import LocationBox
  49. config.plugins.playstream = ConfigSubsection()
  50. config.plugins.playstream.locations = ConfigLocations(default=["/media/hdd/movie/"])
  51. config.plugins.playstream.download_dir = ConfigDirectory(default="/media/hdd/movie/")
  52. config.plugins.playstream.tmp_dir = ConfigDirectory(default="/tmp/playstream/")
  53. config.plugins.playstream.streamproxy_start = ConfigYesNo(default = True)
  54. #config.plugins.playstream.size = ConfigSelection({"400x240":"400x240","220x132":"220x132","100x60":"100x60"}, default="220x132")
  55. config.plugins.playstream.clear_tmp = ConfigYesNo(default = True)
  56. class ConfigScreen(ConfigListScreen,Screen):
  57. skin = """
  58. <screen position="center,center" size="560,400" title="PlayStream Configuration" >
  59. <ePixmap name="red" position="0,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
  60. <ePixmap name="green" position="140,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
  61. <ePixmap name="yellow" position="280,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
  62. <ePixmap name="blue" position="420,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
  63. <widget name="key_red" position="0,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  64. <widget name="key_green" position="140,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  65. <widget name="key_yellow" position="280,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  66. <widget name="key_blue" position="420,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  67. <widget name="config" position="10,40" size="540,340" scrollbarMode="showOnDemand" />
  68. </screen>"""
  69. def __init__(self, session, args = 0):
  70. self.session = session
  71. #self.setup_title = "Options"
  72. Screen.__init__(self, session)
  73. cfg = config.plugins.playstream
  74. self.list = [
  75. getConfigListEntry(_("Download folder"), cfg.download_dir),
  76. getConfigListEntry(_("TMP folder"), cfg.tmp_dir),
  77. getConfigListEntry(_("Clear tmp folder on exit"), cfg.clear_tmp),
  78. getConfigListEntry(_("Start playstreamproxy"), cfg.streamproxy_start),
  79. ]
  80. ConfigListScreen.__init__(self, self.list, session = self.session)
  81. self["key_red"] = Button(_("Cancel"))
  82. self["key_green"] = Button(_("Save"))
  83. self["key_yellow"] = Button("")
  84. self["key_blue"] = Button("")
  85. self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
  86. {
  87. "red": self.cancel,
  88. "green": self.save,
  89. "save": self.save,
  90. "cancel": self.cancel,
  91. "ok": self.ok,
  92. }, -2)
  93. def getCurrentEntry(self):
  94. return self["config"].getCurrent()[0]
  95. def getCurrentValue(self):
  96. return str(self["config"].getCurrent()[1].getText())
  97. def ok(self):
  98. if self["config"].getCurrent()[1] == config.plugins.playstream.download_dir:
  99. folder = config.plugins.playstream.download_dir.value
  100. #self.session.openWithCallback(self.select_download_dir, LocationBox,"Select Folder")
  101. self.session.openWithCallback(self.select_download_dir, LocationBox,"Select download folder","",config.plugins.playstream.download_dir.value,config.plugins.playstream.locations,False,"Select folder",None,True,True)
  102. elif self["config"].getCurrent()[1] == config.plugins.playstream.tmp_dir:
  103. self.session.openWithCallback(self.select_tmp_dir, LocationBox,"Select tmp folder","",config.plugins.playstream.download_dir.value,config.plugins.playstream.locations,False,"Select folder",None,True,True)
  104. else:
  105. self.save()
  106. def select_download_dir(self, folder, select=None):
  107. if not folder:
  108. return
  109. print "Folder selected - %s"%folder
  110. config.plugins.playstream.download_dir.setValue(folder)
  111. config.plugins.playstream.download_dir.save()
  112. config.plugins.playstream.locations.save()
  113. config.save()
  114. def select_tmp_dir(self, folder, select=None):
  115. if not folder:
  116. return
  117. print "Folder selected - %s"%folder
  118. config.plugins.playstream.tmp_dir.setValue(folder)
  119. config.plugins.playstream.tmp_dir.save()
  120. config.plugins.playstream.locations.save()
  121. config.save()
  122. def save(self):
  123. print "saving"
  124. self.saveAll()
  125. self.close(True,self.session)
  126. def cancel(self):
  127. #print "cancel"
  128. self.close(False,self.session)
  129. def make_service(stream):
  130. url = stream["url"]
  131. headers = []
  132. if stream.has_key("headers"):
  133. for h in stream["headers"]:
  134. headers.append("%s=%s"%(h,stream["headers"][h]))
  135. if headers:
  136. headers ="&".join(headers)
  137. url = url+"#"+headers
  138. print "stream_url with headers=",headers
  139. url = url.encode("utf8")
  140. if "|" in url:
  141. url = url.replace("|","#")
  142. service_type = 4097
  143. if re.search("\.ts$",url.split("?")[0],re.IGNORECASE):
  144. service_type = 1
  145. if "resolver" in stream and stream["resolver"] in ("hqq","viaplay"):
  146. url = util.streamproxy_encode(stream["url"], stream["headers"])
  147. if os.path.exists("/usr/bin/exteplayer3"): # problem playing hqq streams with gstreamer, use exteplayer3
  148. service_type = 5002
  149. print "service_type=",service_type
  150. print "stream_url=",url
  151. service = eServiceReference(service_type, 0, url)
  152. service.setName( stream["name"])
  153. return service
  154. #####################################################################################################################
  155. class PSSubs(Screen):
  156. def __init__(self, session):
  157. desktopWidth = getDesktop(0).size().width()
  158. desktopHeight = getDesktop(0).size().height()
  159. offset = 20
  160. screenWidth = desktopWidth - (2 * offset)
  161. widgetWidth = screenWidth / 2 - 5
  162. self.skin = """
  163. <screen position="%d,%d" size="%d,140" zPosition="2" backgroundColor="transparent" flags="wfNoBorder">
  164. <widget name="subtitle" position="0,0" size="%d,140" valign="center" halign="center" font="Regular;36" transparent="1" foregroundColor="white" shadowColor="#40101010" shadowOffset="2,2" />
  165. </screen>""" % (offset, desktopHeight-offset-140, screenWidth, screenWidth)
  166. self['subtitle'] = Label()
  167. Screen.__init__(self, session)
  168. #####################################################################################################################
  169. class PSPlayer(MoviePlayer):
  170. def __init__(self, session, streams):
  171. print "PSPlayer init: ",streams
  172. self.session = session
  173. self.streams = streams
  174. self.cur_stream = self.streams[0] # TODO
  175. self.selected = 0
  176. self.resume_pos = 0
  177. service = make_service(self.cur_stream)
  178. MoviePlayer.__init__(self, session, service)
  179. self.skinName = "MoviePlayer"
  180. self["actions"] = ActionMap(["MediaPlayerActions","MediaPlayerSeekActions","MoviePlayerActions","InfobarSeekActions","MovieSelectionActions","ColorActions"], {
  181. "stop":self.leavePlayer,
  182. "leavePlayer":self.leavePlayer,
  183. "audio":self.select_stream,
  184. "AudioSelection":self.select_stream,
  185. "green":self.select_stream,
  186. "subtitles":self.select_captions,
  187. "text":self.select_captions,
  188. "yellow_key":self.select_captions,
  189. "yellow":self.select_captions,
  190. "pauseServiceYellow":self.select_captions,
  191. "pause":self.select_captions,
  192. "showEventInfo":self.service_info,
  193. "info":self.service_info
  194. })
  195. self.stimer = eTimer()
  196. if "callback" in dir(self.stimer):
  197. self.stimer.callback.append(self.update_subtitles)
  198. elif "timeout":
  199. self.stimer_conn = self.stimer.timeout.connect(self.update_subtitles)
  200. else:
  201. self.stimer = None
  202. self.stimer_step = 500
  203. if self.cur_stream["subs"]:
  204. self.subs = self.cur_stream["subs"]
  205. self.cur_subs = 0 # TODO - no konfiguracijas
  206. self.svisible = True # TODO - no konfiguracija
  207. self.sind = 0
  208. self.get_subs_current()
  209. else:
  210. self.subs = []
  211. self.cur_subs = 0
  212. self.svisible = False
  213. self.subtitle_window = self.session.instantiateDialog (PSSubs)
  214. self.onLayoutFinish.append(self.start_subtitles_timer)
  215. def start_subtitles_timer(self):
  216. if self.stimer:
  217. self.subtitle_window.show()
  218. print "start_subtitles_timer"
  219. self.stimer.start(self.stimer_step)
  220. def get_sub_pts(self,pts):
  221. sc = self.get_sub_ind(self.sind) # current subbtitle
  222. while True:
  223. if not sc:
  224. return "Error - no subs find" # TODO
  225. if pts > sc["end"]:
  226. self.sind += 1
  227. sc = self.get_sub_ind(self.sind)
  228. continue
  229. else:
  230. if pts <sc["begin"]:
  231. return " "
  232. else:
  233. txt = sc["text"] if sc["text"] else " "
  234. return txt
  235. def get_sub_ind(self,ind):
  236. subs_object = self.get_subs_current() # current subs object
  237. if subs_object:
  238. return subs_object.subs[ind] if ind<len(subs_object.subs) else None
  239. else:
  240. return None
  241. def get_subs_current(self):
  242. "Return current sub_object"
  243. if not "subs" in self.subs[self.cur_subs]:
  244. print "===== Captions to download", self.subs[self.cur_subs]["url"]
  245. subs_object = util.Captions(self.subs[self.cur_subs]["url"])
  246. print len(subs_object.subs), "items"
  247. self.subs[self.cur_subs]["subs"] = subs_object
  248. if not subs_object:
  249. return None
  250. else:
  251. return subs_object
  252. else:
  253. return self.subs[self.cur_subs]["subs"]
  254. def update_subtitles(self):
  255. if not self.shown and self.svisible:
  256. seek = self.getSeek()
  257. pos = seek.getPlayPosition()
  258. pts = pos[1]/90
  259. txt0 = "%d:%02d (%i)" % (pts/60/1000, (pts/1000)%60, pts)
  260. #print "Update_subtitles", txt0
  261. if not self.subtitle_window.shown:
  262. self.subtitle_window.show()
  263. if not self.subs:
  264. return
  265. txt = self.get_sub_pts(pts)
  266. #print "Show subtitle",txt
  267. #txt = txt0+": "+ txt
  268. self.subtitle_window["subtitle"].setText(txt)
  269. elif self.shown and self.svisible:
  270. if self.subtitle_window.shown:
  271. self.subtitle_window.hide()
  272. elif not self.svisible:
  273. if self.subtitle_window.shown:
  274. self.subtitle_window.hide()
  275. # struct SubtitleTrack
  276. # int type;
  277. # int pid;
  278. # int page_number;
  279. # int magazine_number;
  280. # std::string language_code;
  281. #selectedSubtitle = ???
  282. #self.enableSubtitle(selectedSubtitle)
  283. def start_subtitles_timer(self):
  284. self.stimer.start(self.stimer_step)
  285. def play_service(self,service):
  286. self.movieSelected(service)
  287. #def doShow(self):
  288. #self.svisible = False
  289. #InfoBarShowHide.doShow(self)
  290. #def doHide(self):
  291. #self.svisible = True
  292. #InfoBarShowHide.doHide(self)
  293. def service_info(self):
  294. print "########[MoviePlayer] service_info"
  295. text = "%s\n%s %s\n%s"%(self.cur_stream["name"],self.cur_stream["lang"],self.cur_stream["quality"],self.cur_stream["desc"])
  296. text = text.encode("utf8")
  297. #print text
  298. mtype = MessageBox.TYPE_INFO
  299. Notifications.AddPopup(text = text, type=mtype, timeout = 10)
  300. #Notifications.MessageBox(self.session, text=text, type=MessageBox.TYPE_INFO, timeout=10,
  301. #close_on_any_key=True,
  302. #default=True,
  303. #enable_input=True,
  304. #msgBoxID=None,
  305. #picon=False,
  306. #simple=False,
  307. #wizard=False,
  308. #list=None,
  309. #skin_name=None,
  310. #timeout_default=None)
  311. #return True
  312. def select_stream(self):
  313. print "########[MoviePlayer] select_stream"
  314. lst = []
  315. title = "Select stream"
  316. for i,s in enumerate(self.streams):
  317. lst.append(("[%s,%s] %s"%(s["lang"],s["quality"],s["name"]),i))
  318. self.session.openWithCallback(self.cb_select_stream, ChoiceBox, title = title, list = lst,selection = self.selected)
  319. def cb_select_stream(self,answer):
  320. #print "item_menu_selected",answer
  321. if not answer:
  322. return
  323. self.selected = answer[1]
  324. service = make_service(self.streams[self.selected])
  325. self.resume_pos = self.getSeek().getPlayPosition()[1]
  326. self.play_service(service)
  327. def serviceStarted(self):
  328. print "serviceStarted"
  329. if not self.resume_pos:
  330. self.resume_pos = 0
  331. print "doSeek",self.resume_pos
  332. #self.doSeek(self.resume_pos)
  333. def select_captions(self):
  334. print "########[MoviePlayer] select_caption"
  335. lst = []
  336. title = "Select subtitles"
  337. for i,s in enumerate(self.subs):
  338. lst.append((("%s - %s"%(s["lang"],s["name"])).encode("utf8"),i))
  339. if self.svisible:
  340. selection = self.cur_subs
  341. else:
  342. selection = len(lst)
  343. lst.append(("No captions",-1))
  344. self.session.openWithCallback(self.cb_select_captions, ChoiceBox, title = title, list = lst,selection = selection)
  345. def cb_select_captions(self,answer):
  346. #print "item_menu_selected",answer
  347. if not answer:
  348. return
  349. if answer[1] == -1:
  350. self.svisible = False
  351. else:
  352. self.cur_subs = answer[1]
  353. self.svisible = True
  354. def leavePlayer(self):
  355. self.close()
  356. #self.session.openWithCallback(self.leavePlayerConfirmed, MessageBox, _("Stop playing?"))
  357. def leavePlayerConfirmed(self, answer):
  358. if answer:
  359. self.close()
  360. def doEofInternal(self, playing):
  361. self.close()
  362. #def getPluginList(self):
  363. #from Components.PluginComponent import plugins
  364. #list = []
  365. #for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU):
  366. #if p.name != _("TV Play"):
  367. #list.append(((boundFunction(self.getPluginName, p.name),
  368. #boundFunction(self.runPlugin, p), lambda: True), None))
  369. #return list
  370. #def showMovies(self):
  371. #pass
  372. #####################################################################################################################
  373. class MainScreen(Screen):
  374. skin = """
  375. <screen position="center,center" size="1015,570" title="Play Stream">
  376. <eLabel position="5,0" size="1000,2" backgroundColor="#aaaaaa" />
  377. <widget name="title" position="10,2" size="1000,38" font="Regular;30" />
  378. <widget source="list" render="Listbox" position="10,55" size="580,470" \
  379. scrollbarMode="showOnDemand" >
  380. <convert type="TemplatedMultiContent" >
  381. {
  382. "template": [MultiContentEntryText(pos=(10, 1), size=(560, 30), \
  383. font=0, flags=RT_HALIGN_LEFT, text=0)],
  384. "fonts": [gFont("Regular", 20)],
  385. "itemHeight": 30
  386. }
  387. </convert>
  388. </widget>
  389. <widget name="pic" position="646,55" size="327,250" alphatest="on" />
  390. <widget name="cur" position="610,300" size="400,250" halign="center" font="Regular;20"/>
  391. <ePixmap name="exit" position="10,540" zPosition="2" size="140,40" pixmap="skin_default/buttons/key_exit.png" transparent="1" alphatest="on" />
  392. <widget name="key_exit" position="10,535" size="140,40" valign="center" halign="center" zPosition="4" backgroundColor="blue" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  393. <ePixmap name="red" position="150,535" zPosition="2" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
  394. <widget name="key_red" position="150,535" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  395. <ePixmap name="green" position="290,535" zPosition="2" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
  396. <widget name="key_green" position="290,535" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  397. <ePixmap name="yellow" position="430,535" zPosition="2" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
  398. <widget name="key_yellow" position="430,535" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  399. <ePixmap name="blue" position="570,535" zPosition="2" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
  400. <widget name="key_blue" position="570,535" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  401. <ePixmap name="menu" position="750,540" zPosition="2" size="140,40" pixmap="skin_default/buttons/key_menu.png" transparent="1" alphatest="on" />
  402. <widget name="key_menu" position="750,535" size="140,40" valign="center" halign="center" zPosition="4" backgroundColor="blue" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  403. </screen>"""
  404. def __init__(self, session):
  405. Screen.__init__(self, session)
  406. #self.setTitle2("Home")
  407. self.session = session
  408. self.e2 = None
  409. self["key_red"] = Button(_("Exit"))
  410. self["key_green"] = Button(_("Select"))
  411. self["key_yellow"] = Button(_("Options"))
  412. self["key_blue"] = Button(_("Config"))
  413. self["key_menu"] = Button("Menu")
  414. self["key_exit"] = Button("Back")
  415. self["actions"] = ActionMap(["OkCancelActions", "ColorActions","MenuActions", "NumberActions"],
  416. {
  417. #"cancel": self.Cancel,
  418. "ok": self.Ok,
  419. "green": self.Ok,
  420. "red": self.Cancel,
  421. "yellow": self.options_screen,
  422. "blue": self.config_screen,
  423. "menu": self.item_menu,
  424. "cancel": self.Back,
  425. })
  426. self["list"] = List([])
  427. self["list"].onSelectionChanged.append(self.SelectionChanged)
  428. self["pic"] = Pixmap()
  429. self["cur"] = Label()
  430. self["title"] = Label()
  431. self.downloading = 0
  432. self.activeDownloads = 0
  433. if not os.path.exists(config.plugins.playstream.tmp_dir.value):
  434. os.mkdir(config.plugins.playstream.tmp_dir.value)
  435. self.onLayoutFinish.append(self.LayoutFinish)
  436. def LayoutFinish(self):
  437. self.cur_directory = os.path.dirname(os.path.realpath(__file__))
  438. self.defimage = LoadPixmap(os.path.join(self.cur_directory,"PlayStream.png"))
  439. #self.defimage = None
  440. #sc = AVSwitch().getFramebufferScale()
  441. #self.defimage0 = ePicLoad()
  442. #self.defimage0.PictureData.get().append(boundFunction(self.FinishDecodeDef, os.path.join(self.cur_directory,"PlayStream.png")))
  443. #self.defimage0.setPara((self["pic"].instance.size().width(),self["pic"].instance.size().height(),sc[0], sc[1], False, 0, "#00000000"))
  444. #self.defimage0.startDecode("default")
  445. self.activeDownloads = 0
  446. self.images = {}
  447. self.images_url = {}
  448. self.picloads = {}
  449. self.history = []
  450. reload(ContentSources)
  451. self.sources = ContentSources.ContentSources(os.path.join(self.cur_directory,"sources"))
  452. self.config = self.sources.plugins["config"]
  453. self.cur_menu = ("Home","config::home","","Sākums") #
  454. self.content = self.sources.get_content(self.cur_menu[1])
  455. #print self.content
  456. self["list"].setList(self.content)
  457. self["cur"].setText(self.content[0][3])
  458. self.setTitle2(self.cur_menu[0])
  459. self.ShowPic(self.content[0][2])
  460. def SelectionChanged(self):
  461. current = self["list"].getCurrent()
  462. print "[PlayStream] SelectionChanged: current=",current
  463. if not current: return
  464. self["cur"].setText(current[3]) if current[3] else self["cur"].setText("")
  465. if current[2]:
  466. self.ShowPic(current[2])
  467. else:
  468. self.ShowDefPic()
  469. def setTitle2(self,title):
  470. #print self.keys()
  471. self["title"].setText(title)
  472. def ShowDefPic(self):
  473. if self.defimage:
  474. self["pic"].instance.setPixmap(self.defimage)
  475. def ShowPic(self,image_url):
  476. if image_url == "default":
  477. self.ShowDefPic()
  478. return
  479. elif self.images.has_key(image_url):
  480. if self.images[image_url] in (-1,-2):
  481. return
  482. elif self.images[image_url] == -3:
  483. self.ShowDefPic()
  484. return
  485. else:
  486. self["pic"].instance.setPixmap(self.images[image_url])
  487. else:
  488. if image_url.startswith("http"):
  489. fname = image_url.replace(":","-").replace("/","_")
  490. image_path = os.path.join(config.plugins.playstream.tmp_dir.value, fname)
  491. self.download_image(image_path, image_url)
  492. else: # local file
  493. image_path = os.path.join(self.cur_directory,image_url)
  494. self.start_decode(image_path,image_url)
  495. def start_decode(self,image_path,image_url):
  496. self.images[image_url] = -2
  497. self.images_url[image_path] = image_url
  498. sc = AVSwitch().getFramebufferScale()
  499. if not self.picloads.has_key(image_path):
  500. self.picloads[image_path] = ePicLoad()
  501. self.picloads[image_path].PictureData.get().append(boundFunction(self.FinishDecode, image_path))
  502. self.picloads[image_path].setPara((self["pic"].instance.size().width(),
  503. self["pic"].instance.size().height(),
  504. sc[0], sc[1], True, 0, "#00000000"))
  505. #print image_path,image_url
  506. self.picloads[image_path].startDecode(image_path)
  507. def FinishDecode(self, image_path,picInfo = None):
  508. image_url = self.images_url[image_path]
  509. del self.images_url[image_path] #III
  510. self.images[image_url] = self.picloads[image_path].getData()
  511. self["pic"].instance.setPixmap(self.images[image_url])
  512. del self.picloads[image_path]
  513. if len(self.images)>30:
  514. del self.images[self.images.keys()[0]]
  515. # self.images.pop()
  516. #def FinishDecodeDef(self, image_path,picInfo = None):
  517. # self.defimage = self.defimage0.getData()
  518. # del self.defimage0
  519. # self["pic"].instance.setPixmap(self.defimage)
  520. def download_image(self,image_path,image_url):
  521. #print "Image download started",self.downloading,image_path,image_url
  522. self.downloading += 1
  523. self.images[image_url] = -1
  524. downloadPage(image_url, image_path).addCallback(boundFunction(self.downloadFinished, image_path,image_url)).addErrback(boundFunction(self.downloadFailed, image_path,image_url))
  525. def downloadFinished(self, image_path, image_url, result):
  526. self.downloading -= 1
  527. #print "[ Play] Image downloaded finished ",self.downloading,image_path, image_url,result
  528. self.start_decode(image_path,image_url)
  529. def downloadFailed(self, image_path, image_url,result):
  530. self.downloading -= 1
  531. print "[TV Play] Image downloaded failed ",self.downloading,image_path, image_url,result
  532. self.images[image_url] = -3
  533. def Ok(self):
  534. current = self["list"].getCurrent()
  535. self.current = current
  536. index = self["list"].getIndex()
  537. self.index = index
  538. print "[PlayStream] - menu selected ", current
  539. data = current[1].split("::")[1] if "::" in current[1] else current[1]
  540. if not data:
  541. return
  542. elif self.sources.is_video(current[1]):
  543. if self.sources.stream_type(current[1]):
  544. stream = util.item()
  545. stream["url"] = current[1]
  546. stream["name"] = current[0]
  547. streams = [stream]
  548. else:
  549. try:
  550. streams = self.sources.get_streams(current[1])
  551. except Exception,e:
  552. print str(e)
  553. self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
  554. return
  555. if streams:
  556. #print streams
  557. for s in streams:
  558. if not s["name"]: s["name"] = current[0]
  559. if not s["img"]: s["img"] = current[2]
  560. if not s["desc"]: s["desc"] = current[3]
  561. try:
  562. self.session.open(PSPlayer, streams)
  563. except Exception as e:
  564. self.msg2("Error launching player - " + str(e))
  565. return
  566. else:
  567. self.msg("No stream found - %s"%(self.current[1]))
  568. return
  569. elif current[1] == "back":
  570. cur_menu_old = self.cur_menu
  571. self.cur_menu = self.history.pop()
  572. new_content = self.sources.get_content(self.cur_menu[1])
  573. try:
  574. index = zip(*new_content)[1].index(cur_menu_old[1])
  575. except:
  576. index = 0
  577. self.setTitle2(self.cur_menu[0])
  578. self.show_content(new_content,index)
  579. else:
  580. print "selected=",current
  581. if "{0}" in current[1]:
  582. self.session.openWithCallback(self.cb_input,VirtualKeyBoard, title="Enter value", text="")
  583. #a = raw_input("Enter value:")
  584. #a = "big bang"
  585. #current = (current[0],current[1].format(a),current[2],current[3])
  586. #self.get_content(current)
  587. else:
  588. self.get_content(current)
  589. def cb_input(self,value):
  590. if not value:
  591. return
  592. current = self.current
  593. current = (current[0],current[1].format(value),current[2],current[3])
  594. self.get_content(current)
  595. def get_content(self,current):
  596. self.history.append(self.cur_menu)
  597. self.cur_menu = current
  598. try:
  599. new_content = self.sources.get_content(self.cur_menu[1])
  600. except Exception,e:
  601. self.cur_menu = self.history.pop()
  602. self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
  603. return
  604. self.setTitle2(self.cur_menu[0])
  605. self.show_content(new_content)
  606. def Back(self):
  607. self["list"].setIndex(0)
  608. self.Ok()
  609. def show_content(self,content,index=0):
  610. self["list"].setList(content)
  611. self["list"].setIndex(index)
  612. self.SelectionChanged()
  613. def Cancel(self):
  614. print "Exiting PlayStream"
  615. if config.plugins.playstream.clear_tmp.value and os.path.exists(config.plugins.playstream.tmp_dir.value):
  616. for name in os.listdir(config.plugins.playstream.tmp_dir.value):
  617. #print "remove "+os.path.join(config.plugins.playstream.tmp_dir.value, name)
  618. os.remove(os.path.join(config.plugins.playstream.tmp_dir.value, name))
  619. #os.rmdir(config.plugins.playstream.tmp_dir.value)
  620. self.close()
  621. def item_menu(self):
  622. print "\n[PlayStream] options\n"
  623. self.current = self["list"].getCurrent()
  624. self.index = self["list"].getIndex()
  625. #self.session. open(MessageBox, "Item options - %s"%current[0] , MessageBox.TYPE_INFO)
  626. #args=[current,self.cur_menu]
  627. #self.session.open(ItemMenuScreen,current,index,self)
  628. lst = [
  629. ("Aditional information","info","Display additional information about item"),
  630. ("Add to bouquet","bouquet","Add current item to Enigma2 bouquet"),
  631. ("Add to favorites","favorites","Add current item to PlayStrem favorites"),
  632. ("Show active downloads","download_list","Show active downloads list"),
  633. ("Set download folder","download_folder","Set download folder")
  634. ]
  635. if self.sources.is_video(self.current[1]):
  636. lst.extend([
  637. ("Download video","download","Download video in background"),
  638. ])
  639. else:
  640. lst.extend([
  641. ("Download videos in folder","download","Download videos in folder (if any)"),
  642. ])
  643. if "config::" in self.cur_menu[1]:
  644. lst.extend([
  645. ("Rename item","rename","Rename list item"),
  646. ("Move item","move","Move list item"),
  647. ("Delete item","delete","Delete list item"),
  648. ("Add submenu","add_list","Add submenu before selected item"),
  649. ])
  650. title = self.current[0]
  651. self.session.openWithCallback(self.cb_item_menu, ChoiceBox, title = title, list = lst) #TODO
  652. def cb_item_menu(self,answer):
  653. #print "item_menu_selected",answer
  654. if not answer:
  655. return
  656. if answer[1] == "info":
  657. self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO)
  658. pass # TODO parada papildus info
  659. elif answer[1] == "bouquet":
  660. #if not e2:
  661. #e2 = enigma2_api.DBServices()
  662. #print "load_buuquets - ",e2._load_bouquets()
  663. self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO)
  664. elif answer[1] == "favorites":
  665. lists = self.config.get_lists()
  666. lists2 = [(l,l) for l in lists]
  667. self.session.openWithCallback(self.cb_favorites, ChoiceBox, title="Selected menu item will be added",list = lists2)
  668. elif answer[1] == 'download':
  669. current = self.current
  670. if not self.sources.is_video(current[1]):
  671. #self.msg("Can not download listst (yet) - %s"%(current[1]))
  672. n = 0
  673. for current2 in self.sources.get_content(current[1]):
  674. if self.sources.is_video(current2[1]):
  675. n += 1
  676. self.download_video(current2)
  677. if n>0:
  678. self.msg("%s videos download started"%n)
  679. else:
  680. self.msg("No videos to download")
  681. else:
  682. if self.download_video(current):
  683. self.msg("Video download started")
  684. elif answer[1] == 'download_list':
  685. self.download_list()
  686. elif answer[1] == 'download_folder':
  687. #downloadDir = "/media/hdd/movie" #config.plugins.playstream.downloadDir.value TODO
  688. self.session.openWithCallback(self.select_download_dir, LocationBox,"Select download folder","",config.plugins.playstream.download_dir.value,config.plugins.playstream.locations,False,"Select folder",None,True,True)
  689. elif answer[1] == "delete":
  690. lst = self.cur_menu[1].replace("config::","")
  691. #print lst
  692. self.config.del_item(lst,self.index)
  693. self.config.write_streams()
  694. txt = "'%s' deleted from favourite stream list '%s'"%(self.current[0],lst)
  695. self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=5)
  696. elif answer[1] == "rename":
  697. #name2 = "Renamed"
  698. self.session.openWithCallback(self.cb_rename,VirtualKeyBoard, title="Enter new item name", text=self.current[0])
  699. elif answer[1] == "add_list":
  700. self.session.open(MessageBox, "Not yet implemented!", MessageBox.TYPE_INFO,timeout=5)
  701. pass #TODO
  702. return
  703. def select_download_dir(self, downloadDir, select=None):
  704. if not downloadDir:
  705. return
  706. print "Folder selected - %s"%downloadDir
  707. config.plugins.playstream.download_dir.setValue(downloadDir)
  708. config.plugins.playstream.download_dir.save()
  709. config.plugins.playstream.locations.save()
  710. config.save()
  711. def download_list(self):
  712. self.session.open(VideoDownloadList)
  713. def download_video(self,current):
  714. if self.sources.stream_type(current[1]):
  715. stream = util.item()
  716. stream["url"] = current[1]
  717. stream["name"] = current[0]
  718. streams = [stream]
  719. else:
  720. try:
  721. streams = self.sources.get_streams(current[1])
  722. except Exception,e:
  723. print "Error - %s"%str(e)
  724. self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
  725. return
  726. if not streams:
  727. self.msg("No stream found to download - %s"%(self.current[1]))
  728. return
  729. for s in streams:
  730. if not s["name"]: s["name"] = current[0]
  731. if not s["img"]: s["img"] = current[2]
  732. if not s["desc"]: s["desc"] = current[3]
  733. if len(streams)>1:
  734. stream = streams[0] # TODO iespeja izvelēties strīmu, ja to ir vairāki
  735. else:
  736. stream = streams[0]
  737. stream = util.stream_change(stream)
  738. return self.download_stream(stream)
  739. def download_stream(self,stream):
  740. print "download stream",stream
  741. #self.msg("Start downloading..")
  742. self.stream = stream
  743. stream_type = self.stream["type"] #self.sources.stream_type(stream["url"])
  744. if not stream_type: #
  745. print "Not supported stream type found to download - %s"%(self.current[1])
  746. self.msg("Not supported stream type found to download - %s"%(self.current[1]))
  747. return
  748. title = self.stream["name"].strip()
  749. url = self.stream["url"]
  750. stream_type = self.stream["type"] #self.sources.stream_type(stream["url"])
  751. downloadDir = config.plugins.playstream.download_dir.value
  752. if not os.path.exists(downloadDir):
  753. print 'Sorry, download directory "%s" not exist!\nPlease specify in the settings existing directory'%downloadDir
  754. self.msg0(_('Sorry, download directory "%s" not exist!\nPlease specify in the settings existing directory'%downloadDir))
  755. return
  756. fname0 = re.sub("[/\n\r\t,:]"," ",title)
  757. fname0 = re.sub("['""]","",fname0)
  758. fname = fname0 +".mp4"
  759. outputfile = os.path.join(downloadDir, fname)
  760. #print "Trying to download - ", current
  761. if self.stream["subs"]:
  762. suburl = self.stream["subs"][0]["url"]
  763. print "\n**Download subtitles %s - %s"%(title,suburl)
  764. subs = urllib2.urlopen(suburl).read()
  765. if subs:
  766. #fname0 = re.sub("[/\n\r\t,:]"," ",title)
  767. subext = ".srt"
  768. subfile = os.path.join(downloadDir,fname0+subext)
  769. if ".xml" in suburl:
  770. subs = util.ttaf2srt(subs)
  771. with open(subfile,"w") as f:
  772. f.write(subs)
  773. else:
  774. print "\n Error downloading subtitle %s"%suburl
  775. if os.path.exists(outputfile):
  776. self.msg( _('Sorry, this file already exists:\n%s') % outputfile)
  777. return False
  778. #os.remove(outputfile)
  779. if stream_type in ("http","https"):
  780. print "\n**Download %s - %s"%(title,url)
  781. #reload(downloadJob)
  782. job_manager.AddJob(downloadJob(url, outputfile, title[:20], self.video_download_stop))
  783. self.activeDownloads += 1
  784. #self.msg(_('Video download started!'))
  785. return True
  786. elif stream_type == "hls":
  787. #self.msg("HLS stream download not yet implemented!")
  788. print "\n**Download %s - %s"%(title,url)
  789. #reload(HLSDownloadJob)
  790. print "HLSDownload", url,outputfile
  791. job_manager.AddJob(HLSDownloadJob(url, outputfile, title[:20], self.video_download_stop))
  792. self.activeDownloads += 1
  793. #self.msg(_('Video download started!'))
  794. return True
  795. elif stream_type == "rstp":
  796. self.msg("RSTP stream download not yet implemented!")
  797. return False
  798. else:
  799. self.msg("Unkown stream type!")
  800. return False
  801. def cb_rename(self,value):
  802. if not value:
  803. return
  804. lst = self.cur_menu[1].replace("config::","")
  805. pos = self.index
  806. print value
  807. item2 = list(self.current)
  808. item2[0]=value
  809. item2 = tuple(item2)
  810. self.config.replace_item(lst,item2,pos)
  811. self.config.write_streams()
  812. txt = "'%s' renamed to '%s'"%(self.current[0],value)
  813. self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=5)
  814. def cb_favorites(self,answer):
  815. print "cb_favorites",answer,self.current
  816. if not answer:
  817. return
  818. value = answer[1]
  819. self.config.add_item(value,self.current)
  820. self.config.write_streams()
  821. txt = "'%s' added to favourite stream list '%s'"%(self.current[0],value)
  822. self.session.open(MessageBox, txt, MessageBox.TYPE_INFO,timeout=3)
  823. #self.session.openWithCallback(self.callMyMsg, MessageBox, _("Do you want to exit the plugin?"), MessageBox.TYPE_INFO)
  824. def config_screen(self):
  825. self.session.open(ConfigScreen,self)
  826. def options_screen(self):
  827. source = self.cur_menu[1].split("::")[0]
  828. options = self.sources.options_read(source)
  829. print source
  830. if not options:
  831. self.session. open(MessageBox, "No options available for source %s (%s)"%(self.cur_menu[0],source) , MessageBox.TYPE_INFO)
  832. else:
  833. self.session.open(OptionsScreen,self)
  834. def video_download_stop(self,title):
  835. #self.activeDownloads -= 1
  836. #self.msg("Download '%s'finished!"%title)
  837. print "video_download_stop ", title
  838. def msg2(self,msg,timeout=10,mtype = None):
  839. mtype=mtype if mtype else MessageBox.TYPE_INFO
  840. Notifications.AddPopup(text = msg, type=mtype, timeout=timeout)
  841. def msg(self,msg,timeout=10):
  842. self.session.open(MessageBox, msg, MessageBox.TYPE_INFO, timeout)
  843. ##########################################################################
  844. from Components.config import config, ConfigSubsection, ConfigYesNo,\
  845. getConfigListEntry, ConfigSelection, ConfigNumber, ConfigDirectory,ConfigText, ConfigSubDict
  846. from Components.ConfigList import ConfigListScreen
  847. #from Screens.LocationBox import LocationBox
  848. #config.plugins.playstream = ConfigSubDict()
  849. class OptionsScreen(ConfigListScreen,Screen):
  850. skin = """
  851. <screen position="center,center" size="560,400" >
  852. <ePixmap name="red" position="0,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
  853. <widget name="key_red" position="0,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  854. <ePixmap name="green" position="140,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
  855. <widget name="key_green" position="140,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  856. <ePixmap name="yellow" position="280,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
  857. <widget name="key_yellow" position="280,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  858. <ePixmap name="blue" position="420,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
  859. <widget name="key_blue" position="420,0" size="140,40" valign="center" halign="center" zPosition="4" foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
  860. <widget name="config" position="10,40" size="540,340" scrollbarMode="showOnDemand" />
  861. </screen>"""
  862. def __init__(self, session,*args):
  863. self.session = session
  864. Screen.__init__(self, session)
  865. self.main = args[0]
  866. self.setTitle(self.main.cur_menu[0]+" options")
  867. self.source = self.main.cur_menu[1].split("::")[0]
  868. self.cfg = ConfigSubDict() #config.plugins.playstream
  869. self.list = []
  870. self.options = self.main.sources.options_read(self.source)
  871. if not self.options:
  872. #self.session. open(MessageBox, "No options available for source %s (%s)"%(self.main.cur_menu[0],self.source) , MessageBox.TYPE_INFO)
  873. self.close(False,self.session)
  874. for k in self.options:
  875. self.cfg[k]=ConfigText(default=self.options[k],fixed_size=False)
  876. self.list.append(getConfigListEntry(k, self.cfg[k]))
  877. ConfigListScreen.__init__(self, self.list, session = self.session)
  878. self["key_red"] = Button(_("Cancel"))
  879. self["key_green"] = Button("Save")
  880. self["key_yellow"] = Button("")
  881. self["key_blue"] = Button("")
  882. self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
  883. {
  884. "red": self.cancel,
  885. "green": self.save,
  886. "save": self.save,
  887. "cancel": self.cancel,
  888. "ok": self.ok,
  889. }, -2)
  890. def getCurrentEntry(self):
  891. return self["config"].getCurrent()[0]
  892. def getCurrentValue(self):
  893. return str(self["config"].getCurrent()[1].getText())
  894. def ok(self):
  895. self.save()
  896. #if self["config"].getCurrent()[1] == config.plugins.getpicons.folder:
  897. #folder = config.plugins.getpicons.folder.value
  898. #self.session.openWithCallback(self.change_dir, LocationBox,"Select Folder")
  899. #else:
  900. #def change_dir(self, folder, select=None):
  901. #if folder:
  902. ##print "change_dir to %s"%folder
  903. #config.plugins.getpicons.folder.value = folder
  904. def save(self):
  905. print "saving"
  906. #self.saveAll()
  907. for k in self.options.keys():
  908. self.options[k]=self.cfg[k].value
  909. print "%s=%s"%(k,self.cfg[k].value)
  910. self.main.sources.options_write(self.source,self.options)
  911. self.close(True,self.session)
  912. def cancel(self):
  913. print "cancel"
  914. self.close(False,self.session)