calculate skin positions and sizes to look also nice using HD skins
[enigma2-plugins.git] / mosaic / src / plugin.py
1 # -*- coding: UTF-8 -*-
2 # Mosaic by AliAbdul
3 from Components.ActionMap import NumberActionMap
4 from Components.config import config, ConfigSubsection, ConfigInteger
5 from Components.Console import Console
6 from Components.Label import Label
7 from Components.Language import language
8 from Components.Pixmap import Pixmap
9 from Components.VideoWindow import VideoWindow
10 from enigma import eServiceCenter, eServiceReference, eTimer, getDesktop, loadPNG
11 from os import environ
12 from Plugins.Plugin import PluginDescriptor
13 from Screens.ChannelSelection import BouquetSelector
14 from Screens.MessageBox import MessageBox
15 from Screens.Screen import Screen
16 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE, SCOPE_LANGUAGE, SCOPE_PLUGINS
17 from Tools.LoadPixmap import LoadPixmap
18 import gettext
19
20 ################################################
21
22 grab_binary = "/usr/bin/grab"
23 grab_picture = "/tmp/mosaic.jpg"
24 grab_errorlog = "/tmp/mosaic.log"
25
26 config_limits = (3, 30)
27 config.plugins.Mosaic = ConfigSubsection()
28 config.plugins.Mosaic.countdown = ConfigInteger(default=5, limits=config_limits)
29
30 playingIcon = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/ico_mp_play.png'))
31 pausedIcon = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/ico_mp_pause.png'))
32
33 ################################################
34
35 lang = language.getLanguage()
36 environ["LANGUAGE"] = lang[:2]\r
37 gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE))\r
38 gettext.textdomain("enigma2")
39 gettext.bindtextdomain("Mosaic", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/Mosaic/locale/"))
40
41 def _(txt):\r
42         t = gettext.dgettext("Mosaic", txt)\r
43         if t == txt:\r
44                 t = gettext.gettext(txt)\r
45         return t
46
47 ################################################
48
49 class Mosaic(Screen):
50         PLAY = 0
51         PAUSE = 1
52         
53         desktop = getDesktop(0)
54         size = desktop.size()
55         width = size.width()
56         height = size.height()
57         windowWidth = width / 4
58         windowHeight = height / 4
59         
60         positions = []
61         x = 80
62         y = 50
63         for i in range(1, 10):
64                 positions.append([x, y])
65                 x += windowWidth
66                 x += ((width - 160) - (windowWidth * 3)) / 2
67                 if (i == 3) or (i == 6):
68                         y = y + windowHeight + 20
69                         x = 80
70         
71         skin = ""
72         skin += """<screen position="0,0" size="%d,%d" title="Mosaic" flags="wfNoBorder" backgroundColor="#ffffff" >""" % (width, height)
73         skin += """<widget name="playState" position="55,55" size="16,16" alphatest="on" />"""
74         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight)
75         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight)
76         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight)
77         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight)
78         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight)
79         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight)
80         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight)
81         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight)
82         skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight)
83         skin += """<widget name="channel1" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[0][0], positions[0][1]-18, windowWidth-4)
84         skin += """<widget name="channel2" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[1][0], positions[1][1]-18, windowWidth-4)
85         skin += """<widget name="channel3" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[2][0], positions[2][1]-18, windowWidth-4)
86         skin += """<widget name="channel4" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[3][0], positions[3][1]-18, windowWidth-4)
87         skin += """<widget name="channel5" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[4][0], positions[4][1]-18, windowWidth-4)
88         skin += """<widget name="channel6" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[5][0], positions[5][1]-18, windowWidth-4)
89         skin += """<widget name="channel7" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[6][0], positions[6][1]-18, windowWidth-4)
90         skin += """<widget name="channel8" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[7][0], positions[7][1]-18, windowWidth-4)
91         skin += """<widget name="channel9" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[8][0], positions[8][1]-18, windowWidth-4)
92         skin += """<widget name="window1" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight)
93         skin += """<widget name="window2" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight)
94         skin += """<widget name="window3" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight)
95         skin += """<widget name="window4" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight)
96         skin += """<widget name="window5" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight)
97         skin += """<widget name="window6" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight)
98         skin += """<widget name="window7" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight)
99         skin += """<widget name="window8" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight)
100         skin += """<widget name="window9" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight)
101         skin += """<widget name="video1" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight)
102         skin += """<widget name="video2" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight)
103         skin += """<widget name="video3" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight)
104         skin += """<widget name="video4" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight)
105         skin += """<widget name="video5" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight)
106         skin += """<widget name="video6" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight)
107         skin += """<widget name="video7" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight)
108         skin += """<widget name="video8" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight)
109         skin += """<widget name="video9" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight)
110         skin += """<widget name="event1" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth)
111         skin += """<widget name="event2" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth)
112         skin += """<widget name="event3" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth)
113         skin += """<widget name="event4" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth)
114         skin += """<widget name="event5" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth)
115         skin += """<widget name="event6" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth)
116         skin += """<widget name="event7" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth)
117         skin += """<widget name="event8" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth)
118         skin += """<widget name="event9" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth)
119
120         skin += """<widget name="countdown" position="80,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (height-50, windowWidth)
121         skin += """<widget name="count" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" halign="right" />
122         </screen>""" % (positions[2][0] ,height-50, windowWidth)
123
124         def __init__(self, session, services):
125                 Screen.__init__(self, session)
126                 
127                 self.session = session
128                 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
129                 self.consoleCmd = ""
130                 self.Console = Console()
131                 self.serviceHandler = eServiceCenter.getInstance()
132                 self.ref_list = services
133                 self.window_refs = [None, None, None, None, None, None, None, None, None]
134                 self.current_refidx = 0
135                 self.current_window = 1
136                 self.countdown = config.plugins.Mosaic.countdown.value
137                 self.working = False
138                 self.state = self.PLAY
139                 
140                 self["playState"] = Pixmap()
141                 for i in range(1, 10):
142                         self["window" + str(i)] = Pixmap()
143                         self["video" + str(i)] = VideoWindow(decoder=0, fb_width=self.width, fb_height=self.height)
144                         self["video" + str(i)].hide()
145                         self["channel" + str(i)] = Label("")
146                         self["event" + str(i)] = Label("")
147                         self["event" + str(i)].hide()
148                 self["video1"].decoder = 0
149                 self["video1"].show()
150                 self["countdown"] = Label()
151                 self.updateCountdownLabel()
152                 self["count"] = Label()
153                 
154                 self["actions"] = NumberActionMap(["MosaicActions"],
155                         {
156                                 "ok": self.exit,
157                                 "cancel": self.closeWithOldService,
158                                 "green": self.play,
159                                 "yellow": self.pause,
160                                 "channelup": self.countdownPlus,
161                                 "channeldown": self.countdownMinus,
162                                 "1": self.numberPressed,
163                                 "2": self.numberPressed,
164                                 "3": self.numberPressed,
165                                 "4": self.numberPressed,
166                                 "5": self.numberPressed,
167                                 "6": self.numberPressed,
168                                 "7": self.numberPressed,
169                                 "8": self.numberPressed,
170                                 "9": self.numberPressed
171                         }, prio=-1)
172                 
173                 self.updateTimer = eTimer()
174                 self.updateTimer.callback.append(self.updateCountdown)
175                 self.checkTimer = eTimer()
176                 self.checkTimer.callback.append(self.checkGrab)
177                 self.checkTimer.start(500, 1)
178
179         def checkGrab(self):
180                 # Start the first service in the bouquet and show the service-name
181                 ref = self.ref_list[0]
182                 self.window_refs[0] = ref
183                 info = self.serviceHandler.info(ref)
184                 name = info.getName(ref).replace('\xc2\x86', '').replace('\xc2\x87', '')
185                 event_name = self.getEventName(info, ref)
186                 self["channel1"].setText(name)
187                 self["event1"].setText(event_name)
188                 self.session.nav.playService(ref)
189                 self["count"].setText(_("Channel: ") + "1 / " + str(len(self.ref_list)))
190                 self["playState"].instance.setPixmap(playingIcon)
191                 # Start updating the video-screenshots
192                 self.updateTimer.start(1, 1)
193
194         def exit(self, callback=None):
195                 self.deleteConsoleCallbacks()
196                 self.close()
197
198         def deleteConsoleCallbacks(self):
199                 if self.Console.appContainers.has_key(self.consoleCmd):
200                         del self.Console.appContainers[self.consoleCmd].dataAvail[:]
201                         del self.Console.appContainers[self.consoleCmd].appClosed[:]
202                         del self.Console.appContainers[self.consoleCmd]
203                         del self.Console.extra_args[self.consoleCmd]
204                         del self.Console.callbacks[self.consoleCmd]
205
206         def closeWithOldService(self):
207                 self.session.nav.playService(self.oldService)
208                 self.deleteConsoleCallbacks()
209                 self.close()
210
211         def numberPressed(self, number):
212                 ref = self.window_refs[number-1]
213                 if ref is not None:
214                         self.session.nav.playService(ref)
215                         self.deleteConsoleCallbacks()
216                         self.close()
217
218         def play(self):
219                 if self.working == False and self.state == self.PAUSE:
220                         self.state = self.PLAY
221                         self.updateTimer.start(1000, 1)
222                         self["playState"].instance.setPixmap(playingIcon)
223
224         def pause(self):
225                 if self.working == False and self.state == self.PLAY:
226                         self.state = self.PAUSE
227                         self.updateTimer.stop()
228                         self["playState"].instance.setPixmap(pausedIcon)
229
230         def countdownPlus(self):
231                 self.changeCountdown(1)
232
233         def countdownMinus(self):
234                 self.changeCountdown(-1)
235
236         def changeCountdown(self, direction):
237                 if self.working == False:
238                         configNow = config.plugins.Mosaic.countdown.value
239                         configNow += direction
240                         
241                         if configNow < config_limits[0]:
242                                 configNow = config_limits[0]
243                         elif configNow > config_limits[1]:
244                                 configNow = config_limits[1]
245                         
246                         config.plugins.Mosaic.countdown.value = configNow
247                         config.plugins.Mosaic.countdown.save()
248                         
249                         self.updateCountdownLabel()
250
251         def makeNextScreenshot(self):
252                 # Grab video
253                 if not self.Console:
254                         self.Console = Console()
255                 self.consoleCmd = "%s -v -r %d -l -j 100 %s" % (grab_binary, self.windowWidth, grab_picture)
256                 self.Console.ePopen(self.consoleCmd, self.showNextScreenshot)
257
258         def showNextScreenshot(self, result, retval, extra_args):
259                 if retval == 0:
260                         # Show screenshot in the current window
261                         pic = LoadPixmap(grab_picture)
262                         self["window" + str(self.current_window)].instance.setPixmap(pic)
263                 
264                         # Hide current video-window and show the running event-name
265                         self["video" + str(self.current_window)].hide()
266                         self["event" + str(self.current_window)].show()
267                 
268                         # Get next ref
269                         self.current_refidx += 1
270                         if self.current_refidx > (len(self.ref_list) -1):
271                                 self.current_refidx = 0
272                 
273                         # Play next ref
274                         ref = self.ref_list[self.current_refidx]
275                         info = self.serviceHandler.info(ref)
276                         name = info.getName(ref).replace('\xc2\x86', '').replace('\xc2\x87', '')
277                         event_name = self.getEventName(info, ref)
278                         self.session.nav.playService(ref)
279                 
280                         # Get next window index
281                         self.current_window += 1
282                         if self.current_window > 9:
283                                 self.current_window = 1
284                 
285                         # Save the ref
286                         self.window_refs[self.current_window-1] = ref
287                 
288                         # Save the event-name and hide the label
289                         self["event" + str(self.current_window)].hide()
290                         self["event" + str(self.current_window)].setText(event_name)
291                 
292                         # Show the new video-window
293                         self["video" + str(self.current_window)].show()
294                         self["video" + str(self.current_window)].decoder = 0
295                 
296                         # Show the servicename
297                         self["channel" + str(self.current_window)].setText(name)
298                         self["count"].setText(_("Channel: ") + str(self.current_refidx + 1) + " / " + str(len(self.ref_list)))
299                 
300                         # Restart timer
301                         self.working = False
302                         self.updateTimer.start(1, 1)
303                 else:
304                         print "[Mosaic] retval: %d result: %s" % (retval, result)
305                         
306                         try:
307                                 f = open(grab_errorlog, "w")
308                                 f.write("retval: %d\nresult: %s" % (retval, result))
309                                 f.close()
310                         except:
311                                 pass
312                         
313                         self.session.openWithCallback(self.exit, MessageBox, _("Error while creating screenshot. You need grab version 0.8 or higher!"), MessageBox.TYPE_ERROR, timeout=5)
314
315         def updateCountdown(self, callback=None):
316                 self.countdown -= 1
317                 self.updateCountdownLabel()
318                 if self.countdown == 0:
319                         self.countdown = config.plugins.Mosaic.countdown.value
320                         self.working = True
321                         self.makeNextScreenshot()
322                 else:
323                         self.updateTimer.start(1000, 1)
324
325         def updateCountdownLabel(self):
326                 self["countdown"].setText("%s %s / %s" % (_("Countdown:"), str(self.countdown), str(config.plugins.Mosaic.countdown.value)))
327
328         def getEventName(self, info, ref):
329                 event = info.getEvent(ref)
330                 if event is not None:
331                         eventName = event.getEventName()
332                         if eventName is None:
333                                 eventName = ""
334                 else:
335                         eventName = ""
336                 return eventName
337
338 ################################################
339 # Most stuff stolen from the GraphMultiEPG
340
341 Session = None
342 Servicelist = None
343 BouquetSelectorScreen = None
344
345 def getBouquetServices(bouquet):
346         services = []
347         Servicelist = eServiceCenter.getInstance().list(bouquet)
348         if Servicelist is not None:
349                 while True:
350                         service = Servicelist.getNext()
351                         if not service.valid():
352                                 break
353                         if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker):
354                                 continue
355                         services.append(service)
356         return services
357
358 def closeBouquetSelectorScreen(ret=None):
359         if BouquetSelectorScreen is not None:
360                 BouquetSelectorScreen.close()
361
362 def openMosaic(bouquet):
363         if bouquet is not None:
364                 services = getBouquetServices(bouquet)
365                 if len(services):
366                         Session.openWithCallback(closeBouquetSelectorScreen, Mosaic, services)
367
368 def main(session, servicelist, **kwargs):
369         global Session
370         Session = session
371         global Servicelist
372         Servicelist = servicelist
373         global BouquetSelectorScreen
374         
375         bouquets = Servicelist.getBouquetList()
376         if bouquets is not None:
377                 if len(bouquets) == 1:
378                         openMosaic(bouquets[0][1])
379                 elif len(bouquets) > 1:
380                         BouquetSelectorScreen = Session.open(BouquetSelector, bouquets, openMosaic, enableWrapAround=True)
381
382 def Plugins(**kwargs):
383         return PluginDescriptor(name=_("Mosaic"), where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=main)