Browse Source

Sākotnējais kommits

Ivars 8 years ago
commit
8653fd45cb
6 changed files with 12570 additions and 0 deletions
  1. 921
    0
      PlayStream.py
  2. 6767
    0
      PlayStream.wpr
  3. 4836
    0
      PlayStream.wpu
  4. BIN
      plugin.png
  5. 21
    0
      plugin.py
  6. 25
    0
      readme.txt

+ 921
- 0
PlayStream.py View File

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

+ 6767
- 0
PlayStream.wpr
File diff suppressed because it is too large
View File


+ 4836
- 0
PlayStream.wpu
File diff suppressed because it is too large
View File


BIN
plugin.png View File


+ 21
- 0
plugin.py View File

@@ -0,0 +1,21 @@
1
+from Plugins.Plugin import PluginDescriptor
2
+import PlayStream
3
+from . import _
4
+
5
+def main(session, **kwargs):
6
+    try:
7
+        reload(PlayStream)
8
+        session.open(PlayStream.MainScreen)
9
+    except Exception as e:
10
+        print "Error loading PlayStream - "+ e.message
11
+
12
+def menu(menuid, **kwargs):
13
+    if menuid == "mainmenu":
14
+        return [("Play Stream", main, "playstream", 57)]
15
+    return []
16
+
17
+def Plugins(**kwargs):
18
+    return [
19
+        PluginDescriptor(name = "Play Stream",description = _("Play online streams from various sources"),where = [PluginDescriptor.WHERE_PLUGINMENU,PluginDescriptor.WHERE_EXTENSIONSMENU], icon = "plugin.png", fnc = main),
20
+        PluginDescriptor(name="TV Play", description=_("Play online streams from various sources"), where = [PluginDescriptor.WHERE_MENU], fnc=menu)
21
+    ]

+ 25
- 0
readme.txt View File

@@ -0,0 +1,25 @@
1
+=== PlayStream ===
2
+Enigma2 plugin to to play various online streams.
3
+Stream sources are in  subfolder "sources", and can be added/customized
4
+Currently implemented:
5
+    - Main menu and manual stream definition in streams.cfg file
6
+    - replay.lv media portal content (Latvian TV)
7
+    - skaties.lv media portal content (MTG media portal Latvian version), other countries MTG contents are available through customizing main menu (country=xx) 
8
+	- lattelecom.tv media portal content
9
+	- play24.lv media portal content
10
+	- filmon.tv media portal content
11
+	- ustvnow.tv
12
+	- serialguru.ru
13
+	- filmix.net
14
+    
15
+Version 0.3b
16
+
17
+Copyright (c) 2016 ivars777 (ivars777@gmail.com)
18
+Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
19
+Used fragments of code from enigma2-plugin-tv3play by Taapat (https://github.com/Taapat/enigma2-plugin-tv3play)
20
+Contains files from enigma2-plugin-youtube by Taapat (https://github.com/Taapat/enigma2-plugin-youtube), originally from https://github.com/rg3/youtube-dl/blob/master/youtube_dl
21
+
22
+
23
+
24
+
25
+