1 from ChannelSelection import ChannelSelection, BouquetSelector, SilentBouquetSelector
3 from Components.ActionMap import ActionMap, HelpableActionMap
4 from Components.ActionMap import NumberActionMap
5 from Components.Harddisk import harddiskmanager
6 from Components.Input import Input
7 from Components.Label import Label
8 from Components.PluginComponent import plugins
9 from Components.ServiceEventTracker import ServiceEventTracker
10 from Components.Sources.Boolean import Boolean
11 from Components.config import config, ConfigBoolean, ConfigClock
12 from Components.SystemInfo import SystemInfo
13 from Components.UsageConfig import preferredInstantRecordPath, defaultMoviePath
14 from EpgSelection import EPGSelection
15 from Plugins.Plugin import PluginDescriptor
17 from Screen import Screen
18 from Screens.ChoiceBox import ChoiceBox
19 from Screens.Dish import Dish
20 from Screens.EventView import EventViewEPGSelect, EventViewSimple
21 from Screens.InputBox import InputBox
22 from Screens.MessageBox import MessageBox
23 from Screens.MinuteInput import MinuteInput
24 from Screens.TimerSelection import TimerSelection
25 from Screens.PictureInPicture import PictureInPicture
26 from Screens.SubtitleDisplay import SubtitleDisplay
27 from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive
28 from Screens.TimeDateInput import TimeDateInput
29 from Screens.UnhandledKey import UnhandledKey
30 from ServiceReference import ServiceReference
32 from Tools import Notifications
33 from Tools.Directories import fileExists
35 from enigma import eTimer, eServiceCenter, eDVBServicePMTHandler, iServiceInformation, \
36 iPlayableService, eServiceReference, eEPGCache, eActionMap
38 from time import time, localtime, strftime
39 from os import stat as os_stat
40 from bisect import insort
42 from RecordTimer import RecordTimerEntry, RecordTimer
45 from Menu import MainMenu, mdom
49 self.dishDialog = self.session.instantiateDialog(Dish)
51 class InfoBarUnhandledKey:
53 self.unhandledKeyDialog = self.session.instantiateDialog(UnhandledKey)
54 self.hideUnhandledKeySymbolTimer = eTimer()
55 self.hideUnhandledKeySymbolTimer.callback.append(self.unhandledKeyDialog.hide)
56 self.checkUnusedTimer = eTimer()
57 self.checkUnusedTimer.callback.append(self.checkUnused)
58 self.onLayoutFinish.append(self.unhandledKeyDialog.hide)
59 eActionMap.getInstance().bindAction('', -0x7FFFFFFF, self.actionA) #highest prio
60 eActionMap.getInstance().bindAction('', 0x7FFFFFFF, self.actionB) #lowest prio
64 #this function is called on every keypress!
65 def actionA(self, key, flag):
67 if self.flags & (1<<1):
68 self.flags = self.uflags = 0
69 self.flags |= (1<<flag)
71 self.checkUnusedTimer.start(0, True)
74 #this function is only called when no other action has handled this key
75 def actionB(self, key, flag):
77 self.uflags |= (1<<flag)
79 def checkUnused(self):
80 if self.flags == self.uflags:
81 self.unhandledKeyDialog.show()
82 self.hideUnhandledKeySymbolTimer.start(2000, True)
84 class InfoBarShowHide:
85 """ InfoBar show/hide control, accepts toggleShow and hide actions, might start
93 self["ShowHideActions"] = ActionMap( ["InfobarShowHideActions"] ,
95 "toggleShow": self.toggleShow,
97 }, 1) # lower prio to make it possible to override ok and cancel..
99 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
101 iPlayableService.evStart: self.serviceStarted,
104 self.__state = self.STATE_SHOWN
107 self.hideTimer = eTimer()
108 self.hideTimer.callback.append(self.doTimerHide)
109 self.hideTimer.start(5000, True)
111 self.onShow.append(self.__onShow)
112 self.onHide.append(self.__onHide)
114 def serviceStarted(self):
116 if config.usage.show_infobar_on_zap.value:
120 self.__state = self.STATE_SHOWN
121 self.startHideTimer()
123 def startHideTimer(self):
124 if self.__state == self.STATE_SHOWN and not self.__locked:
125 idx = config.usage.infobar_timeout.index
127 self.hideTimer.start(idx*1000, True)
130 self.__state = self.STATE_HIDDEN
134 self.startHideTimer()
136 def doTimerHide(self):
137 self.hideTimer.stop()
138 if self.__state == self.STATE_SHOWN:
141 def toggleShow(self):
142 if self.__state == self.STATE_SHOWN:
144 self.hideTimer.stop()
145 elif self.__state == self.STATE_HIDDEN:
149 self.__locked = self.__locked + 1
152 self.hideTimer.stop()
154 def unlockShow(self):
155 self.__locked = self.__locked - 1
157 self.startHideTimer()
159 # def startShow(self):
160 # self.instance.m_animation.startMoveAnimation(ePoint(0, 600), ePoint(0, 380), 100)
161 # self.__state = self.STATE_SHOWN
163 # def startHide(self):
164 # self.instance.m_animation.startMoveAnimation(ePoint(0, 380), ePoint(0, 600), 100)
165 # self.__state = self.STATE_HIDDEN
167 class NumberZap(Screen):
174 self.close(int(self["number"].getText()))
176 def keyNumberGlobal(self, number):
177 self.Timer.start(3000, True) #reset timer
178 self.field = self.field + str(number)
179 self["number"].setText(self.field)
180 if len(self.field) >= 4:
183 def __init__(self, session, number):
184 Screen.__init__(self, session)
185 self.field = str(number)
187 self["channel"] = Label(_("Channel:"))
189 self["number"] = Label(self.field)
191 self["actions"] = NumberActionMap( [ "SetupActions" ],
195 "1": self.keyNumberGlobal,
196 "2": self.keyNumberGlobal,
197 "3": self.keyNumberGlobal,
198 "4": self.keyNumberGlobal,
199 "5": self.keyNumberGlobal,
200 "6": self.keyNumberGlobal,
201 "7": self.keyNumberGlobal,
202 "8": self.keyNumberGlobal,
203 "9": self.keyNumberGlobal,
204 "0": self.keyNumberGlobal
207 self.Timer = eTimer()
208 self.Timer.callback.append(self.keyOK)
209 self.Timer.start(3000, True)
211 class InfoBarNumberZap:
212 """ Handles an initial number for NumberZapping """
214 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
216 "1": self.keyNumberGlobal,
217 "2": self.keyNumberGlobal,
218 "3": self.keyNumberGlobal,
219 "4": self.keyNumberGlobal,
220 "5": self.keyNumberGlobal,
221 "6": self.keyNumberGlobal,
222 "7": self.keyNumberGlobal,
223 "8": self.keyNumberGlobal,
224 "9": self.keyNumberGlobal,
225 "0": self.keyNumberGlobal,
228 def keyNumberGlobal(self, number):
229 # print "You pressed number " + str(number)
231 if isinstance(self, InfoBarPiP) and self.pipHandles0Action():
232 self.pipDoHandle0Action()
234 self.servicelist.recallPrevService()
236 if self.has_key("TimeshiftActions") and not self.timeshift_enabled:
237 self.session.openWithCallback(self.numberEntered, NumberZap, number)
239 def numberEntered(self, retval):
240 # print self.servicelist
242 self.zapToNumber(retval)
244 def searchNumberHelper(self, serviceHandler, num, bouquet):
245 servicelist = serviceHandler.list(bouquet)
246 if not servicelist is None:
248 serviceIterator = servicelist.getNext()
249 if not serviceIterator.valid(): #check end of list
251 playable = not (serviceIterator.flags & (eServiceReference.isMarker|eServiceReference.isDirectory))
254 if not num: #found service with searched number ?
255 return serviceIterator, 0
258 def zapToNumber(self, number):
259 bouquet = self.servicelist.bouquet_root
261 serviceHandler = eServiceCenter.getInstance()
262 if not config.usage.multibouquet.value:
263 service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
265 bouquetlist = serviceHandler.list(bouquet)
266 if not bouquetlist is None:
268 bouquet = bouquetlist.getNext()
269 if not bouquet.valid(): #check end of list
271 if bouquet.flags & eServiceReference.isDirectory:
272 service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
273 if not service is None:
274 if self.servicelist.getRoot() != bouquet: #already in correct bouquet?
275 self.servicelist.clearPath()
276 if self.servicelist.bouquet_root != bouquet:
277 self.servicelist.enterPath(self.servicelist.bouquet_root)
278 self.servicelist.enterPath(bouquet)
279 self.servicelist.setCurrentSelection(service) #select the service in servicelist
280 self.servicelist.zap()
282 config.misc.initialchannelselection = ConfigBoolean(default = True)
284 class InfoBarChannelSelection:
285 """ ChannelSelection - handles the channelSelection dialog and the initial
286 channelChange actions which open the channelSelection dialog """
289 self.servicelist = self.session.instantiateDialog(ChannelSelection)
291 if config.misc.initialchannelselection.value:
292 self.onShown.append(self.firstRun)
294 self["ChannelSelectActions"] = HelpableActionMap(self, "InfobarChannelSelection",
296 "switchChannelUp": (self.switchChannelUp, _("open servicelist(up)")),
297 "switchChannelDown": (self.switchChannelDown, _("open servicelist(down)")),
298 "zapUp": (self.zapUp, _("previous channel")),
299 "zapDown": (self.zapDown, _("next channel")),
300 "historyBack": (self.historyBack, _("previous channel in history")),
301 "historyNext": (self.historyNext, _("next channel in history")),
302 "openServiceList": (self.openServiceList, _("open servicelist")),
305 def showTvChannelList(self, zap=False):
306 self.servicelist.setModeTv()
308 self.servicelist.zap()
309 self.session.execDialog(self.servicelist)
311 def showRadioChannelList(self, zap=False):
312 self.servicelist.setModeRadio()
314 self.servicelist.zap()
315 self.session.execDialog(self.servicelist)
318 self.onShown.remove(self.firstRun)
319 config.misc.initialchannelselection.value = False
320 config.misc.initialchannelselection.save()
321 self.switchChannelDown()
323 def historyBack(self):
324 self.servicelist.historyBack()
326 def historyNext(self):
327 self.servicelist.historyNext()
329 def switchChannelUp(self):
330 self.servicelist.moveUp()
331 self.session.execDialog(self.servicelist)
333 def switchChannelDown(self):
334 self.servicelist.moveDown()
335 self.session.execDialog(self.servicelist)
337 def openServiceList(self):
338 self.session.execDialog(self.servicelist)
341 if self.servicelist.inBouquet():
342 prev = self.servicelist.getCurrentSelection()
344 prev = prev.toString()
346 if config.usage.quickzap_bouquet_change.value:
347 if self.servicelist.atBegin():
348 self.servicelist.prevBouquet()
349 self.servicelist.moveUp()
350 cur = self.servicelist.getCurrentSelection()
351 if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
354 self.servicelist.moveUp()
355 self.servicelist.zap()
358 if self.servicelist.inBouquet():
359 prev = self.servicelist.getCurrentSelection()
361 prev = prev.toString()
363 if config.usage.quickzap_bouquet_change.value and self.servicelist.atEnd():
364 self.servicelist.nextBouquet()
366 self.servicelist.moveDown()
367 cur = self.servicelist.getCurrentSelection()
368 if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
371 self.servicelist.moveDown()
372 self.servicelist.zap()
375 """ Handles a menu action, to open the (main) menu """
377 self["MenuActions"] = HelpableActionMap(self, "InfobarMenuActions",
379 "mainMenu": (self.mainMenu, _("Enter main menu...")),
381 self.session.infobar = None
384 print "loading mainmenu XML..."
385 menu = mdom.getroot()
386 assert menu.tag == "menu", "root element in menu must be 'menu'!"
388 self.session.infobar = self
389 # so we can access the currently active infobar from screens opened from within the mainmenu
390 # at the moment used from the SubserviceSelection
392 self.session.openWithCallback(self.mainMenuClosed, MainMenu, menu)
394 def mainMenuClosed(self, *val):
395 self.session.infobar = None
397 class InfoBarSimpleEventView:
398 """ Opens the Eventview for now/next """
400 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
402 "showEventInfo": (self.openEventView, _("show event details")),
403 "showInfobarOrEpgWhenInfobarAlreadyVisible": self.showEventInfoWhenNotVisible,
406 def showEventInfoWhenNotVisible(self):
413 def openEventView(self):
415 self.epglist = epglist
416 service = self.session.nav.getCurrentService()
417 ref = self.session.nav.getCurrentlyPlayingServiceReference()
418 info = service.info()
426 self.session.open(EventViewSimple, epglist[0], ServiceReference(ref), self.eventViewCallback)
428 def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
429 epglist = self.epglist
432 epglist[0] = epglist[1]
436 class SimpleServicelist:
437 def __init__(self, services):
438 self.services = services
439 self.length = len(services)
442 def selectService(self, service):
448 while self.services[self.current].ref != service:
450 if self.current >= self.length:
454 def nextService(self):
457 if self.current+1 < self.length:
462 def prevService(self):
465 if self.current-1 > -1:
468 self.current = self.length - 1
470 def currentService(self):
471 if not self.length or self.current >= self.length:
473 return self.services[self.current]
476 """ EPG - Opens an EPG list when the showEPGList action fires """
478 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
480 iPlayableService.evUpdatedEventInfo: self.__evEventInfoChanged,
483 self.is_now_next = False
485 self.bouquetSel = None
486 self.eventView = None
487 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
489 "showEventInfo": (self.openEventView, _("show EPG...")),
490 "showEventInfoPlugin": (self.showEventInfoPlugins, _("list of EPG views...")),
491 "showInfobarOrEpgWhenInfobarAlreadyVisible": self.showEventInfoWhenNotVisible,
494 def showEventInfoWhenNotVisible(self):
501 def zapToService(self, service):
502 if not service is None:
503 if self.servicelist.getRoot() != self.epg_bouquet: #already in correct bouquet?
504 self.servicelist.clearPath()
505 if self.servicelist.bouquet_root != self.epg_bouquet:
506 self.servicelist.enterPath(self.servicelist.bouquet_root)
507 self.servicelist.enterPath(self.epg_bouquet)
508 self.servicelist.setCurrentSelection(service) #select the service in servicelist
509 self.servicelist.zap()
511 def getBouquetServices(self, bouquet):
513 servicelist = eServiceCenter.getInstance().list(bouquet)
514 if not servicelist is None:
516 service = servicelist.getNext()
517 if not service.valid(): #check if end of list
519 if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): #ignore non playable services
521 services.append(ServiceReference(service))
524 def openBouquetEPG(self, bouquet, withCallback=True):
525 services = self.getBouquetServices(bouquet)
527 self.epg_bouquet = bouquet
529 self.dlg_stack.append(self.session.openWithCallback(self.closed, EPGSelection, services, self.zapToService, None, self.changeBouquetCB))
531 self.session.open(EPGSelection, services, self.zapToService, None, self.changeBouquetCB)
533 def changeBouquetCB(self, direction, epg):
536 self.bouquetSel.down()
539 bouquet = self.bouquetSel.getCurrent()
540 services = self.getBouquetServices(bouquet)
542 self.epg_bouquet = bouquet
543 epg.setServices(services)
545 def closed(self, ret=False):
546 closedScreen = self.dlg_stack.pop()
547 if self.bouquetSel and closedScreen == self.bouquetSel:
548 self.bouquetSel = None
549 elif self.eventView and closedScreen == self.eventView:
550 self.eventView = None
552 dlgs=len(self.dlg_stack)
554 self.dlg_stack[dlgs-1].close(dlgs > 1)
556 def openMultiServiceEPG(self, withCallback=True):
557 bouquets = self.servicelist.getBouquetList()
562 if config.usage.multiepg_ask_bouquet.value:
563 self.openMultiServiceEPGAskBouquet(bouquets, cnt, withCallback)
565 self.openMultiServiceEPGSilent(bouquets, cnt, withCallback)
567 def openMultiServiceEPGAskBouquet(self, bouquets, cnt, withCallback):
568 if cnt > 1: # show bouquet list
570 self.bouquetSel = self.session.openWithCallback(self.closed, BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
571 self.dlg_stack.append(self.bouquetSel)
573 self.bouquetSel = self.session.open(BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
575 self.openBouquetEPG(bouquets[0][1], withCallback)
577 def openMultiServiceEPGSilent(self, bouquets, cnt, withCallback):
578 root = self.servicelist.getRoot()
579 rootstr = root.toCompareString()
581 for bouquet in bouquets:
582 if bouquet[1].toCompareString() == rootstr:
587 if cnt > 1: # create bouquet list for bouq+/-
588 self.bouquetSel = SilentBouquetSelector(bouquets, True, self.servicelist.getBouquetNumOffset(root))
590 self.openBouquetEPG(root, withCallback)
592 def changeServiceCB(self, direction, epg):
595 self.serviceSel.nextService()
597 self.serviceSel.prevService()
598 epg.setService(self.serviceSel.currentService())
600 def SingleServiceEPGClosed(self, ret=False):
601 self.serviceSel = None
603 def openSingleServiceEPG(self):
604 ref=self.session.nav.getCurrentlyPlayingServiceReference()
606 if self.servicelist.getMutableList() is not None: # bouquet in channellist
607 current_path = self.servicelist.getRoot()
608 services = self.getBouquetServices(current_path)
609 self.serviceSel = SimpleServicelist(services)
610 if self.serviceSel.selectService(ref):
611 self.session.openWithCallback(self.SingleServiceEPGClosed, EPGSelection, ref, serviceChangeCB = self.changeServiceCB)
613 self.session.openWithCallback(self.SingleServiceEPGClosed, EPGSelection, ref)
615 self.session.open(EPGSelection, ref)
617 def showEventInfoPlugins(self):
618 list = [(p.name, boundFunction(self.runPlugin, p)) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EVENTINFO)]
621 list.append((_("show single service EPG..."), self.openSingleServiceEPG))
622 list.append((_("Multi EPG"), self.openMultiServiceEPG))
623 self.session.openWithCallback(self.EventInfoPluginChosen, ChoiceBox, title=_("Please choose an extension..."), list = list, skin_name = "EPGExtensionsList")
625 self.openSingleServiceEPG()
627 def runPlugin(self, plugin):
628 plugin(session = self.session, servicelist = self.servicelist)
630 def EventInfoPluginChosen(self, answer):
631 if answer is not None:
634 def openSimilarList(self, eventid, refstr):
635 self.session.open(EPGSelection, refstr, None, eventid)
637 def getNowNext(self):
639 service = self.session.nav.getCurrentService()
640 info = service and service.info()
641 ptr = info and info.getEvent(0)
644 ptr = info and info.getEvent(1)
647 self.epglist = epglist
649 def __evEventInfoChanged(self):
650 if self.is_now_next and len(self.dlg_stack) == 1:
652 assert self.eventView
654 self.eventView.setEvent(self.epglist[0])
656 def openEventView(self):
657 ref = self.session.nav.getCurrentlyPlayingServiceReference()
659 epglist = self.epglist
661 self.is_now_next = False
662 epg = eEPGCache.getInstance()
663 ptr = ref and ref.valid() and epg.lookupEventTime(ref, -1)
666 ptr = epg.lookupEventTime(ref, ptr.getBeginTime(), +1)
670 self.is_now_next = True
672 self.eventView = self.session.openWithCallback(self.closed, EventViewEPGSelect, self.epglist[0], ServiceReference(ref), self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG, self.openSimilarList)
673 self.dlg_stack.append(self.eventView)
675 print "no epg for the service avail.. so we show multiepg instead of eventinfo"
676 self.openMultiServiceEPG(False)
678 def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
679 epglist = self.epglist
682 epglist[0]=epglist[1]
686 class InfoBarRdsDecoder:
687 """provides RDS and Rass support/display"""
689 self.rds_display = self.session.instantiateDialog(RdsInfoDisplay)
690 self.rass_interactive = None
692 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
694 iPlayableService.evEnd: self.__serviceStopped,
695 iPlayableService.evUpdatedRassSlidePic: self.RassSlidePicChanged
698 self["RdsActions"] = ActionMap(["InfobarRdsActions"],
700 "startRassInteractive": self.startRassInteractive
703 self["RdsActions"].setEnabled(False)
705 self.onLayoutFinish.append(self.rds_display.show)
706 self.rds_display.onRassInteractivePossibilityChanged.append(self.RassInteractivePossibilityChanged)
708 def RassInteractivePossibilityChanged(self, state):
709 self["RdsActions"].setEnabled(state)
711 def RassSlidePicChanged(self):
712 if not self.rass_interactive:
713 service = self.session.nav.getCurrentService()
714 decoder = service and service.rdsDecoder()
716 decoder.showRassSlidePicture()
718 def __serviceStopped(self):
719 if self.rass_interactive is not None:
720 rass_interactive = self.rass_interactive
721 self.rass_interactive = None
722 rass_interactive.close()
724 def startRassInteractive(self):
725 self.rds_display.hide()
726 self.rass_interactive = self.session.openWithCallback(self.RassInteractiveClosed, RassInteractive)
728 def RassInteractiveClosed(self, *val):
729 if self.rass_interactive is not None:
730 self.rass_interactive = None
731 self.RassSlidePicChanged()
732 self.rds_display.show()
735 """handles actions like seeking, pause"""
737 SEEK_STATE_PLAY = (0, 0, 0, ">")
738 SEEK_STATE_PAUSE = (1, 0, 0, "||")
739 SEEK_STATE_EOF = (1, 0, 0, "END")
741 def __init__(self, actionmap = "InfobarSeekActions"):
742 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
744 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
745 iPlayableService.evStart: self.__serviceStarted,
747 iPlayableService.evEOF: self.__evEOF,
748 iPlayableService.evSOF: self.__evSOF,
750 self.fast_winding_hint_message_showed = False
752 class InfoBarSeekActionMap(HelpableActionMap):
753 def __init__(self, screen, *args, **kwargs):
754 HelpableActionMap.__init__(self, screen, *args, **kwargs)
757 def action(self, contexts, action):
758 print "action:", action
759 if action[:5] == "seek:":
760 time = int(action[5:])
761 self.screen.doSeekRelative(time * 90000)
763 elif action[:8] == "seekdef:":
764 key = int(action[8:])
765 time = (-config.seek.selfdefined_13.value, False, config.seek.selfdefined_13.value,
766 -config.seek.selfdefined_46.value, False, config.seek.selfdefined_46.value,
767 -config.seek.selfdefined_79.value, False, config.seek.selfdefined_79.value)[key-1]
768 self.screen.doSeekRelative(time * 90000)
771 return HelpableActionMap.action(self, contexts, action)
773 self["SeekActions"] = InfoBarSeekActionMap(self, actionmap,
775 "playpauseService": self.playpauseService,
776 "pauseService": (self.pauseService, _("pause")),
777 "unPauseService": (self.unPauseService, _("continue")),
779 "seekFwd": (self.seekFwd, _("skip forward")),
780 "seekFwdManual": (self.seekFwdManual, _("skip forward (enter time)")),
781 "seekBack": (self.seekBack, _("skip backward")),
782 "seekBackManual": (self.seekBackManual, _("skip backward (enter time)"))
784 # give them a little more priority to win over color buttons
786 self["SeekActions"].setEnabled(False)
788 self.seekstate = self.SEEK_STATE_PLAY
789 self.lastseekstate = self.SEEK_STATE_PLAY
791 self.onPlayStateChanged = [ ]
793 self.lockedBecauseOfSkipping = False
795 self.__seekableStatusChanged()
797 def makeStateForward(self, n):
798 return (0, n, 0, ">> %dx" % n)
800 def makeStateBackward(self, n):
801 return (0, -n, 0, "<< %dx" % n)
803 def makeStateSlowMotion(self, n):
804 return (0, 0, n, "/%d" % n)
806 def isStateForward(self, state):
809 def isStateBackward(self, state):
812 def isStateSlowMotion(self, state):
813 return state[1] == 0 and state[2] > 1
815 def getHigher(self, n, lst):
821 def getLower(self, n, lst):
829 def showAfterSeek(self):
830 if isinstance(self, InfoBarShowHide):
840 service = self.session.nav.getCurrentService()
844 seek = service.seek()
846 if seek is None or not seek.isCurrentlySeekable():
851 def isSeekable(self):
852 if self.getSeek() is None:
856 def __seekableStatusChanged(self):
857 # print "seekable status changed!"
858 if not self.isSeekable():
859 self["SeekActions"].setEnabled(False)
860 # print "not seekable, return to play"
861 self.setSeekState(self.SEEK_STATE_PLAY)
863 self["SeekActions"].setEnabled(True)
866 def __serviceStarted(self):
867 self.fast_winding_hint_message_showed = False
868 self.seekstate = self.SEEK_STATE_PLAY
869 self.__seekableStatusChanged()
871 def setSeekState(self, state):
872 service = self.session.nav.getCurrentService()
877 if not self.isSeekable():
878 if state not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE):
879 state = self.SEEK_STATE_PLAY
881 pauseable = service.pause()
883 if pauseable is None:
884 print "not pauseable."
885 state = self.SEEK_STATE_PLAY
887 self.seekstate = state
889 if pauseable is not None:
890 if self.seekstate[0]:
891 print "resolved to PAUSE"
893 elif self.seekstate[1]:
894 print "resolved to FAST FORWARD"
895 pauseable.setFastForward(self.seekstate[1])
896 elif self.seekstate[2]:
897 print "resolved to SLOW MOTION"
898 pauseable.setSlowMotion(self.seekstate[2])
900 print "resolved to PLAY"
903 for c in self.onPlayStateChanged:
906 self.checkSkipShowHideLock()
910 def playpauseService(self):
911 if self.seekstate != self.SEEK_STATE_PLAY:
912 self.unPauseService()
916 def pauseService(self):
917 if self.seekstate == self.SEEK_STATE_PAUSE:
918 if config.seek.on_pause.value == "play":
919 self.unPauseService()
920 elif config.seek.on_pause.value == "step":
921 self.doSeekRelative(1)
922 elif config.seek.on_pause.value == "last":
923 self.setSeekState(self.lastseekstate)
924 self.lastseekstate = self.SEEK_STATE_PLAY
926 if self.seekstate != self.SEEK_STATE_EOF:
927 self.lastseekstate = self.seekstate
928 self.setSeekState(self.SEEK_STATE_PAUSE);
930 def unPauseService(self):
932 if self.seekstate == self.SEEK_STATE_PLAY:
934 self.setSeekState(self.SEEK_STATE_PLAY)
936 def doSeek(self, pts):
937 seekable = self.getSeek()
942 def doSeekRelative(self, pts):
943 seekable = self.getSeek()
946 prevstate = self.seekstate
948 if self.seekstate == self.SEEK_STATE_EOF:
949 if prevstate == self.SEEK_STATE_PAUSE:
950 self.setSeekState(self.SEEK_STATE_PAUSE)
952 self.setSeekState(self.SEEK_STATE_PLAY)
953 seekable.seekRelative(pts<0 and -1 or 1, abs(pts))
954 if abs(pts) > 100 and config.usage.show_infobar_on_skip.value:
958 seek = self.getSeek()
959 if seek and not (seek.isCurrentlySeekable() & 2):
960 if not self.fast_winding_hint_message_showed and (seek.isCurrentlySeekable() & 1):
961 self.session.open(MessageBox, _("No fast winding possible yet.. but you can use the number buttons to skip forward/backward!"), MessageBox.TYPE_INFO, timeout=10)
962 self.fast_winding_hint_message_showed = True
964 return 0 # trade as unhandled action
965 if self.seekstate == self.SEEK_STATE_PLAY:
966 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
967 elif self.seekstate == self.SEEK_STATE_PAUSE:
968 if len(config.seek.speeds_slowmotion.value):
969 self.setSeekState(self.makeStateSlowMotion(config.seek.speeds_slowmotion.value[-1]))
971 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
972 elif self.seekstate == self.SEEK_STATE_EOF:
974 elif self.isStateForward(self.seekstate):
975 speed = self.seekstate[1]
976 if self.seekstate[2]:
977 speed /= self.seekstate[2]
978 speed = self.getHigher(speed, config.seek.speeds_forward.value) or config.seek.speeds_forward.value[-1]
979 self.setSeekState(self.makeStateForward(speed))
980 elif self.isStateBackward(self.seekstate):
981 speed = -self.seekstate[1]
982 if self.seekstate[2]:
983 speed /= self.seekstate[2]
984 speed = self.getLower(speed, config.seek.speeds_backward.value)
986 self.setSeekState(self.makeStateBackward(speed))
988 self.setSeekState(self.SEEK_STATE_PLAY)
989 elif self.isStateSlowMotion(self.seekstate):
990 speed = self.getLower(self.seekstate[2], config.seek.speeds_slowmotion.value) or config.seek.speeds_slowmotion.value[0]
991 self.setSeekState(self.makeStateSlowMotion(speed))
994 seek = self.getSeek()
995 if seek and not (seek.isCurrentlySeekable() & 2):
996 if not self.fast_winding_hint_message_showed and (seek.isCurrentlySeekable() & 1):
997 self.session.open(MessageBox, _("No fast winding possible yet.. but you can use the number buttons to skip forward/backward!"), MessageBox.TYPE_INFO, timeout=10)
998 self.fast_winding_hint_message_showed = True
1000 return 0 # trade as unhandled action
1001 seekstate = self.seekstate
1002 if seekstate == self.SEEK_STATE_PLAY:
1003 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1004 elif seekstate == self.SEEK_STATE_EOF:
1005 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1006 self.doSeekRelative(-6)
1007 elif seekstate == self.SEEK_STATE_PAUSE:
1008 self.doSeekRelative(-1)
1009 elif self.isStateForward(seekstate):
1010 speed = seekstate[1]
1012 speed /= seekstate[2]
1013 speed = self.getLower(speed, config.seek.speeds_forward.value)
1015 self.setSeekState(self.makeStateForward(speed))
1017 self.setSeekState(self.SEEK_STATE_PLAY)
1018 elif self.isStateBackward(seekstate):
1019 speed = -seekstate[1]
1021 speed /= seekstate[2]
1022 speed = self.getHigher(speed, config.seek.speeds_backward.value) or config.seek.speeds_backward.value[-1]
1023 self.setSeekState(self.makeStateBackward(speed))
1024 elif self.isStateSlowMotion(seekstate):
1025 speed = self.getHigher(seekstate[2], config.seek.speeds_slowmotion.value)
1027 self.setSeekState(self.makeStateSlowMotion(speed))
1029 self.setSeekState(self.SEEK_STATE_PAUSE)
1031 def seekFwdManual(self):
1032 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
1034 def fwdSeekTo(self, minutes):
1035 print "Seek", minutes, "minutes forward"
1036 self.doSeekRelative(minutes * 60 * 90000)
1038 def seekBackManual(self):
1039 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
1041 def rwdSeekTo(self, minutes):
1043 self.doSeekRelative(-minutes * 60 * 90000)
1045 def checkSkipShowHideLock(self):
1046 wantlock = self.seekstate != self.SEEK_STATE_PLAY
1048 if config.usage.show_infobar_on_skip.value:
1049 if self.lockedBecauseOfSkipping and not wantlock:
1051 self.lockedBecauseOfSkipping = False
1053 if wantlock and not self.lockedBecauseOfSkipping:
1055 self.lockedBecauseOfSkipping = True
1057 def calcRemainingTime(self):
1058 seekable = self.getSeek()
1059 if seekable is not None:
1060 len = seekable.getLength()
1062 tmp = self.cueGetEndCutPosition()
1067 pos = seekable.getPlayPosition()
1068 speednom = self.seekstate[1] or 1
1069 speedden = self.seekstate[2] or 1
1070 if not len[0] and not pos[0]:
1071 if len[1] <= pos[1]:
1073 time = (len[1] - pos[1])*speedden/(90*speednom)
1078 if self.seekstate == self.SEEK_STATE_EOF:
1081 # if we are seeking forward, we try to end up ~1s before the end, and pause there.
1082 seekstate = self.seekstate
1083 if self.seekstate != self.SEEK_STATE_PAUSE:
1084 self.setSeekState(self.SEEK_STATE_EOF)
1086 if seekstate not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE): # if we are seeking
1087 seekable = self.getSeek()
1088 if seekable is not None:
1090 if seekstate == self.SEEK_STATE_PLAY: # regular EOF
1091 self.doEofInternal(True)
1093 self.doEofInternal(False)
1095 def doEofInternal(self, playing):
1096 pass # Defined in subclasses
1099 self.setSeekState(self.SEEK_STATE_PLAY)
1102 from Screens.PVRState import PVRState, TimeshiftState
1104 class InfoBarPVRState:
1105 def __init__(self, screen=PVRState, force_show = False):
1106 self.onPlayStateChanged.append(self.__playStateChanged)
1107 self.pvrStateDialog = self.session.instantiateDialog(screen)
1108 self.onShow.append(self._mayShow)
1109 self.onHide.append(self.pvrStateDialog.hide)
1110 self.force_show = force_show
1113 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
1114 self.pvrStateDialog.show()
1116 def __playStateChanged(self, state):
1117 playstateString = state[3]
1118 self.pvrStateDialog["state"].setText(playstateString)
1120 # if we return into "PLAY" state, ensure that the dialog gets hidden if there will be no infobar displayed
1121 if not config.usage.show_infobar_on_skip.value and self.seekstate == self.SEEK_STATE_PLAY and not self.force_show:
1122 self.pvrStateDialog.hide()
1126 class InfoBarTimeshiftState(InfoBarPVRState):
1128 InfoBarPVRState.__init__(self, screen=TimeshiftState, force_show = True)
1129 self.__hideTimer = eTimer()
1130 self.__hideTimer.callback.append(self.__hideTimeshiftState)
1133 if self.execing and self.timeshift_enabled:
1134 self.pvrStateDialog.show()
1135 if self.seekstate == self.SEEK_STATE_PLAY and not self.shown:
1136 self.__hideTimer.start(5*1000, True)
1138 def __hideTimeshiftState(self):
1139 self.pvrStateDialog.hide()
1141 class InfoBarShowMovies:
1143 # i don't really like this class.
1144 # it calls a not further specified "movie list" on up/down/movieList,
1145 # so this is not more than an action map
1147 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
1149 "movieList": (self.showMovies, _("movie list")),
1150 "up": (self.showMovies, _("movie list")),
1151 "down": (self.showMovies, _("movie list"))
1154 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
1158 # Timeshift works the following way:
1159 # demux0 demux1 "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
1160 # - normal playback TUNER unused PLAY enable disable disable
1161 # - user presses "yellow" button. FILE record PAUSE enable disable enable
1162 # - user presess pause again FILE record PLAY enable disable enable
1163 # - user fast forwards FILE record FF enable disable enable
1164 # - end of timeshift buffer reached TUNER record PLAY enable enable disable
1165 # - user backwards FILE record BACK # !! enable disable enable
1169 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
1170 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
1171 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
1172 # - the user can now PVR around
1173 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
1174 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
1176 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
1177 # - if the user rewinds, or press pause, timeshift will be activated again
1179 # note that a timeshift can be enabled ("recording") and
1180 # activated (currently time-shifting).
1182 class InfoBarTimeshift:
1184 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions",
1186 "timeshiftStart": (self.startTimeshift, _("start timeshift")), # the "yellow key"
1187 "timeshiftStop": (self.stopTimeshift, _("stop timeshift")) # currently undefined :), probably 'TV'
1189 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1191 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1192 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause # something like "pause key"
1193 }, prio=-1) # priority over record
1195 self.timeshift_enabled = 0
1196 self.timeshift_state = 0
1197 self.ts_rewind_timer = eTimer()
1198 self.ts_rewind_timer.callback.append(self.rewindService)
1200 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1202 iPlayableService.evStart: self.__serviceStarted,
1203 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1206 def getTimeshift(self):
1207 service = self.session.nav.getCurrentService()
1208 return service and service.timeshift()
1210 def startTimeshift(self):
1211 print "enable timeshift"
1212 ts = self.getTimeshift()
1214 self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1215 print "no ts interface"
1218 if self.timeshift_enabled:
1219 print "hu, timeshift already enabled?"
1221 if not ts.startTimeshift():
1222 self.timeshift_enabled = 1
1224 # we remove the "relative time" for now.
1225 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1228 #self.setSeekState(self.SEEK_STATE_PAUSE)
1229 self.activateTimeshiftEnd(False)
1231 # enable the "TimeshiftEnableActions", which will override
1232 # the startTimeshift actions
1233 self.__seekableStatusChanged()
1235 print "timeshift failed"
1237 def stopTimeshift(self):
1238 if not self.timeshift_enabled:
1240 print "disable timeshift"
1241 ts = self.getTimeshift()
1244 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1246 def stopTimeshiftConfirmed(self, confirmed):
1250 ts = self.getTimeshift()
1255 self.timeshift_enabled = 0
1258 self.__seekableStatusChanged()
1260 # activates timeshift, and seeks to (almost) the end
1261 def activateTimeshiftEnd(self, back = True):
1262 ts = self.getTimeshift()
1263 print "activateTimeshiftEnd"
1268 if ts.isTimeshiftActive():
1269 print "!! activate timeshift called - but shouldn't this be a normal pause?"
1273 ts.activateTimeshift() # activate timeshift will automatically pause
1274 self.setSeekState(self.SEEK_STATE_PAUSE)
1277 self.ts_rewind_timer.start(200, 1)
1279 def rewindService(self):
1280 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1282 # same as activateTimeshiftEnd, but pauses afterwards.
1283 def activateTimeshiftEndAndPause(self):
1284 print "activateTimeshiftEndAndPause"
1285 #state = self.seekstate
1286 self.activateTimeshiftEnd(False)
1288 def __seekableStatusChanged(self):
1291 # print "self.isSeekable", self.isSeekable()
1292 # print "self.timeshift_enabled", self.timeshift_enabled
1294 # when this service is not seekable, but timeshift
1295 # is enabled, this means we can activate
1297 if not self.isSeekable() and self.timeshift_enabled:
1300 # print "timeshift activate:", enabled
1301 self["TimeshiftActivateActions"].setEnabled(enabled)
1303 def __serviceStarted(self):
1304 self.timeshift_enabled = False
1305 self.__seekableStatusChanged()
1307 from Screens.PiPSetup import PiPSetup
1309 class InfoBarExtensions:
1310 EXTENSION_SINGLE = 0
1316 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1318 "extensions": (self.showExtensionSelection, _("view extensions...")),
1319 }, 1) # lower priority
1321 def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1322 self.list.append((type, extension, key))
1324 def updateExtension(self, extension, key = None):
1325 self.extensionsList.append(extension)
1327 if self.extensionKeys.has_key(key):
1331 for x in self.availableKeys:
1332 if not self.extensionKeys.has_key(x):
1337 self.extensionKeys[key] = len(self.extensionsList) - 1
1339 def updateExtensions(self):
1340 self.extensionsList = []
1341 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1342 self.extensionKeys = {}
1344 if x[0] == self.EXTENSION_SINGLE:
1345 self.updateExtension(x[1], x[2])
1348 self.updateExtension(y[0], y[1])
1351 def showExtensionSelection(self):
1352 self.updateExtensions()
1353 extensionsList = self.extensionsList[:]
1356 for x in self.availableKeys:
1357 if self.extensionKeys.has_key(x):
1358 entry = self.extensionKeys[x]
1359 extension = self.extensionsList[entry]
1361 name = str(extension[0]())
1362 list.append((extension[0](), extension))
1364 extensionsList.remove(extension)
1366 extensionsList.remove(extension)
1367 list.extend([(x[0](), x) for x in extensionsList])
1369 keys += [""] * len(extensionsList)
1370 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys, skin_name = "ExtensionsList")
1372 def extensionCallback(self, answer):
1373 if answer is not None:
1376 from Tools.BoundFunction import boundFunction
1379 # depends on InfoBarExtensions
1381 class InfoBarPlugins:
1383 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1385 def getPluginName(self, name):
1388 def getPluginList(self):
1390 for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU):
1391 args = inspect.getargspec(p.__call__)[0]
1392 if len(args) == 1 or len(args) == 2 and isinstance(self, InfoBarChannelSelection):
1393 l.append(((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None, p.name))
1394 l.sort(key = lambda e: e[2]) # sort by name
1397 def runPlugin(self, plugin):
1398 if isinstance(self, InfoBarChannelSelection):
1399 plugin(session = self.session, servicelist = self.servicelist)
1401 plugin(session = self.session)
1403 from Components.Task import job_manager
1404 class InfoBarJobman:
1406 self.addExtension(extension = self.getJobList, type = InfoBarExtensions.EXTENSION_LIST)
1408 def getJobList(self):
1409 return [((boundFunction(self.getJobName, job), boundFunction(self.showJobView, job), lambda: True), None) for job in job_manager.getPendingJobs()]
1411 def getJobName(self, job):
1412 return "%s: %s (%d%%)" % (job.getStatustext(), job.name, int(100*job.progress/float(job.end)))
1414 def showJobView(self, job):
1415 from Screens.TaskView import JobView
1416 job_manager.in_background = False
1417 self.session.openWithCallback(self.JobViewCB, JobView, job)
1419 def JobViewCB(self, in_background):
1420 job_manager.in_background = in_background
1422 # depends on InfoBarExtensions
1426 self.session.pipshown
1428 self.session.pipshown = False
1429 if SystemInfo.get("NumVideoDecoders", 1) > 1:
1431 self.addExtension((self.getShowHideName, self.showPiP, lambda: True), "blue")
1432 self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1433 self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1435 self.addExtension((self.getShowHideName, self.showPiP, self.pipShown), "blue")
1436 self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1439 return self.session.pipshown
1441 def pipHandles0Action(self):
1442 return self.pipShown() and config.usage.pip_zero_button.value != "standard"
1444 def getShowHideName(self):
1445 if self.session.pipshown:
1446 return _("Disable Picture in Picture")
1448 return _("Activate Picture in Picture")
1450 def getSwapName(self):
1451 return _("Swap Services")
1453 def getMoveName(self):
1454 return _("Move Picture in Picture")
1457 if self.session.pipshown:
1458 del self.session.pip
1459 self.session.pipshown = False
1461 self.session.pip = self.session.instantiateDialog(PictureInPicture)
1462 self.session.pip.show()
1463 newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1464 if self.session.pip.playService(newservice):
1465 self.session.pipshown = True
1466 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1468 self.session.pipshown = False
1469 del self.session.pip
1470 self.session.nav.playService(newservice)
1473 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1474 if self.session.pip.servicePath:
1475 servicepath = self.servicelist.getCurrentServicePath()
1476 ref=servicepath[len(servicepath)-1]
1477 pipref=self.session.pip.getCurrentService()
1478 self.session.pip.playService(swapservice)
1479 self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1480 if pipref.toString() != ref.toString(): # is a subservice ?
1481 self.session.nav.stopService() # stop portal
1482 self.session.nav.playService(pipref) # start subservice
1483 self.session.pip.servicePath=servicepath
1486 self.session.open(PiPSetup, pip = self.session.pip)
1488 def pipDoHandle0Action(self):
1489 use = config.usage.pip_zero_button.value
1492 elif "swapstop" == use:
1498 from RecordTimer import parseEvent, RecordTimerEntry
1500 class InfoBarInstantRecord:
1501 """Instant Record - handles the instantRecord action in order to
1502 start/stop instant records"""
1504 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1506 "instantRecord": (self.instantRecord, _("Instant Record...")),
1510 def stopCurrentRecording(self, entry = -1):
1511 if entry is not None and entry != -1:
1512 self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1513 self.recording.remove(self.recording[entry])
1515 def startInstantRecording(self, limitEvent = False):
1516 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1518 # try to get event info
1521 service = self.session.nav.getCurrentService()
1522 epg = eEPGCache.getInstance()
1523 event = epg.lookupEventTime(serviceref, -1, 0)
1525 info = service.info()
1526 ev = info.getEvent(0)
1532 end = begin + 3600 # dummy
1533 name = "instant record"
1537 if event is not None:
1538 curEvent = parseEvent(event)
1540 description = curEvent[3]
1541 eventid = curEvent[4]
1546 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1548 if isinstance(serviceref, eServiceReference):
1549 serviceref = ServiceReference(serviceref)
1551 recording = RecordTimerEntry(serviceref, begin, end, name, description, eventid, dirname = preferredInstantRecordPath())
1552 recording.dontSave = True
1554 if event is None or limitEvent == False:
1555 recording.autoincrease = True
1556 recording.setAutoincreaseEnd()
1558 simulTimerList = self.session.nav.RecordTimer.record(recording)
1560 if simulTimerList is None: # no conflict
1561 self.recording.append(recording)
1563 if len(simulTimerList) > 1: # with other recording
1564 name = simulTimerList[1].name
1565 name_date = ' '.join((name, strftime('%c', localtime(simulTimerList[1].begin))))
1566 print "[TIMER] conflicts with", name_date
1567 recording.autoincrease = True # start with max available length, then increment
1568 if recording.setAutoincreaseEnd():
1569 self.session.nav.RecordTimer.record(recording)
1570 self.recording.append(recording)
1571 self.session.open(MessageBox, _("Record time limited due to conflicting timer %s") % name_date, MessageBox.TYPE_INFO)
1573 self.session.open(MessageBox, _("Couldn't record due to conflicting timer %s") % name, MessageBox.TYPE_INFO)
1575 self.session.open(MessageBox, _("Couldn't record due to invalid service %s") % serviceref, MessageBox.TYPE_INFO)
1576 recording.autoincrease = False
1578 def isInstantRecordRunning(self):
1579 print "self.recording:", self.recording
1581 for x in self.recording:
1586 def recordQuestionCallback(self, answer):
1587 print "pre:\n", self.recording
1589 if answer is None or answer[1] == "no":
1592 recording = self.recording[:]
1594 if not x in self.session.nav.RecordTimer.timer_list:
1595 self.recording.remove(x)
1596 elif x.dontSave and x.isRunning():
1597 list.append((x, False))
1599 if answer[1] == "changeduration":
1600 if len(self.recording) == 1:
1601 self.changeDuration(0)
1603 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1604 elif answer[1] == "changeendtime":
1605 if len(self.recording) == 1:
1608 self.session.openWithCallback(self.setEndtime, TimerSelection, list)
1609 elif answer[1] == "stop":
1610 if len(self.recording) == 1:
1611 self.stopCurrentRecording(0)
1613 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1614 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1615 self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1616 if answer[1] == "manualduration":
1617 self.changeDuration(len(self.recording)-1)
1618 elif answer[1] == "manualendtime":
1619 self.setEndtime(len(self.recording)-1)
1620 print "after:\n", self.recording
1622 def setEndtime(self, entry):
1623 if entry is not None and entry >= 0:
1624 self.selectedEntry = entry
1625 self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1626 dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1627 dlg.setTitle(_("Please change recording endtime"))
1629 def TimeDateInputClosed(self, ret):
1632 localendtime = localtime(ret[1])
1633 print "stopping recording at", strftime("%c", localendtime)
1634 if self.recording[self.selectedEntry].end != ret[1]:
1635 self.recording[self.selectedEntry].autoincrease = False
1636 self.recording[self.selectedEntry].end = ret[1]
1637 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1639 def changeDuration(self, entry):
1640 if entry is not None and entry >= 0:
1641 self.selectedEntry = entry
1642 self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1644 def inputCallback(self, value):
1645 if value is not None:
1646 print "stopping recording after", int(value), "minutes."
1647 entry = self.recording[self.selectedEntry]
1649 entry.autoincrease = False
1650 entry.end = int(time()) + 60 * int(value)
1651 self.session.nav.RecordTimer.timeChanged(entry)
1653 def instantRecord(self):
1654 dir = preferredInstantRecordPath()
1655 if not dir or not fileExists(dir, 'w'):
1656 dir = defaultMoviePath()
1658 if not fileExists("/hdd", 0):
1659 print "not found /hdd"
1660 system("ln -s /media/hdd /hdd")
1665 # XXX: this message is a little odd as we might be recording to a remote device
1666 self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1669 if self.isInstantRecordRunning():
1670 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1671 title=_("A recording is currently running.\nWhat do you want to do?"), \
1672 list=((_("stop recording"), "stop"), \
1673 (_("add recording (stop after current event)"), "event"), \
1674 (_("add recording (indefinitely)"), "indefinitely"), \
1675 (_("add recording (enter recording duration)"), "manualduration"), \
1676 (_("add recording (enter recording endtime)"), "manualendtime"), \
1677 (_("change recording (duration)"), "changeduration"), \
1678 (_("change recording (endtime)"), "changeendtime"), \
1679 (_("do nothing"), "no")))
1681 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1682 title=_("Start recording?"), \
1683 list=((_("add recording (stop after current event)"), "event"), \
1684 (_("add recording (indefinitely)"), "indefinitely"), \
1685 (_("add recording (enter recording duration)"), "manualduration"), \
1686 (_("add recording (enter recording endtime)"), "manualendtime"), \
1687 (_("don't record"), "no")))
1689 from Tools.ISO639 import LanguageCodes
1691 class InfoBarAudioSelection:
1693 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions",
1695 "audioSelection": (self.audioSelection, _("Audio Options...")),
1698 def audioSelection(self):
1699 from Screens.AudioSelection import AudioSelection
1700 self.session.openWithCallback(self.audioSelected, AudioSelection, infobar=self)
1702 def audioSelected(self, ret=None):
1703 print "[infobar::audioSelected]", ret
1705 class InfoBarSubserviceSelection:
1707 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1709 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1712 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1714 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1715 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1717 self["SubserviceQuickzapAction"].setEnabled(False)
1719 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1721 iPlayableService.evUpdatedEventInfo: self.checkSubservicesAvail
1726 def checkSubservicesAvail(self):
1727 service = self.session.nav.getCurrentService()
1728 subservices = service and service.subServices()
1729 if not subservices or subservices.getNumberOfSubservices() == 0:
1730 self["SubserviceQuickzapAction"].setEnabled(False)
1732 def nextSubservice(self):
1733 self.changeSubservice(+1)
1735 def prevSubservice(self):
1736 self.changeSubservice(-1)
1738 def changeSubservice(self, direction):
1739 service = self.session.nav.getCurrentService()
1740 subservices = service and service.subServices()
1741 n = subservices and subservices.getNumberOfSubservices()
1744 ref = self.session.nav.getCurrentlyPlayingServiceReference()
1747 if subservices.getSubservice(idx).toString() == ref.toString():
1752 selection += direction
1757 newservice = subservices.getSubservice(selection)
1758 if newservice.valid():
1761 self.session.nav.playService(newservice, False)
1763 def subserviceSelection(self):
1764 service = self.session.nav.getCurrentService()
1765 subservices = service and service.subServices()
1766 self.bouquets = self.servicelist.getBouquetList()
1767 n = subservices and subservices.getNumberOfSubservices()
1770 ref = self.session.nav.getCurrentlyPlayingServiceReference()
1774 i = subservices.getSubservice(idx)
1775 if i.toString() == ref.toString():
1777 tlist.append((i.getName(), i))
1780 if self.bouquets and len(self.bouquets):
1781 keys = ["red", "blue", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1782 if config.usage.multibouquet.value:
1783 tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1785 tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1788 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1789 keys = ["red", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1792 self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys, skin_name = "SubserviceSelection")
1794 def subserviceSelected(self, service):
1796 if not service is None:
1797 if isinstance(service[1], str):
1798 if service[1] == "quickzap":
1799 from Screens.SubservicesQuickzap import SubservicesQuickzap
1800 self.session.open(SubservicesQuickzap, service[2])
1802 self["SubserviceQuickzapAction"].setEnabled(True)
1803 self.session.nav.playService(service[1], False)
1805 def addSubserviceToBouquetCallback(self, service):
1806 if len(service) > 1 and isinstance(service[1], eServiceReference):
1807 self.selectedSubservice = service
1808 if self.bouquets is None:
1811 cnt = len(self.bouquets)
1812 if cnt > 1: # show bouquet list
1813 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1814 elif cnt == 1: # add to only one existing bouquet
1815 self.addSubserviceToBouquet(self.bouquets[0][1])
1816 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1818 def bouquetSelClosed(self, confirmed):
1820 del self.selectedSubservice
1822 self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1824 def addSubserviceToBouquet(self, dest):
1825 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1827 self.bsel.close(True)
1829 del self.selectedSubservice
1831 class InfoBarAdditionalInfo:
1834 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0 and config.misc.rcused.value == 1)
1835 self["TimeshiftPossible"] = self["RecordingPossible"]
1836 self["ShowTimeshiftOnYellow"] = Boolean(fixed=(not config.misc.rcused.value == 0))
1837 self["ShowAudioOnYellow"] = Boolean(fixed=config.misc.rcused.value == 0)
1838 self["ShowRecordOnRed"] = Boolean(fixed=config.misc.rcused.value == 1)
1839 self["ExtensionsAvailable"] = Boolean(fixed=1)
1841 class InfoBarNotifications:
1843 self.onExecBegin.append(self.checkNotifications)
1844 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1845 self.onClose.append(self.__removeNotification)
1847 def __removeNotification(self):
1848 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1850 def checkNotificationsIfExecing(self):
1852 self.checkNotifications()
1854 def checkNotifications(self):
1855 notifications = Notifications.notifications
1857 n = notifications[0]
1859 del notifications[0]
1862 if n[3].has_key("onSessionOpenCallback"):
1863 n[3]["onSessionOpenCallback"]()
1864 del n[3]["onSessionOpenCallback"]
1867 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1869 dlg = self.session.open(n[1], *n[2], **n[3])
1871 # remember that this notification is currently active
1873 Notifications.current_notifications.append(d)
1874 dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1876 def __notificationClosed(self, d):
1877 Notifications.current_notifications.remove(d)
1879 class InfoBarServiceNotifications:
1881 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1883 iPlayableService.evEnd: self.serviceHasEnded
1886 def serviceHasEnded(self):
1887 print "service end!"
1890 self.setSeekState(self.SEEK_STATE_PLAY)
1894 class InfoBarCueSheetSupport:
1900 ENABLE_RESUME_SUPPORT = False
1902 def __init__(self, actionmap = "InfobarCueSheetActions"):
1903 self["CueSheetActions"] = HelpableActionMap(self, actionmap,
1905 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1906 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1907 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1911 self.is_closing = False
1912 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1914 iPlayableService.evStart: self.__serviceStarted,
1917 def __serviceStarted(self):
1920 print "new service started! trying to download cuts!"
1921 self.downloadCuesheet()
1923 if self.ENABLE_RESUME_SUPPORT:
1926 for (pts, what) in self.cut_list:
1927 if what == self.CUT_TYPE_LAST:
1930 if last is not None:
1931 self.resume_point = last
1934 if config.usage.on_movie_start.value == "ask":
1935 Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?") + "\n" + (_("Resume position at %s") % ("%d:%02d:%02d" % (l/3600, l%3600/60, l%60))), timeout=10)
1936 elif config.usage.on_movie_start.value == "resume":
1937 # TRANSLATORS: The string "Resuming playback" flashes for a moment
1938 # TRANSLATORS: at the start of a movie, when the user has selected
1939 # TRANSLATORS: "Resume from last position" as start behavior.
1940 # TRANSLATORS: The purpose is to notify the user that the movie starts
1941 # TRANSLATORS: in the middle somewhere and not from the beginning.
1942 # TRANSLATORS: (Some translators seem to have interpreted it as a
1943 # TRANSLATORS: question or a choice, but it is a statement.)
1944 Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Resuming playback"), timeout=2, type=MessageBox.TYPE_INFO)
1946 def playLastCB(self, answer):
1948 self.doSeek(self.resume_point)
1949 self.hideAfterResume()
1951 def hideAfterResume(self):
1952 if isinstance(self, InfoBarShowHide):
1955 def __getSeekable(self):
1956 service = self.session.nav.getCurrentService()
1959 return service.seek()
1961 def cueGetCurrentPosition(self):
1962 seek = self.__getSeekable()
1965 r = seek.getPlayPosition()
1970 def cueGetEndCutPosition(self):
1973 for cp in self.cut_list:
1974 if cp[1] == self.CUT_TYPE_OUT:
1978 elif cp[1] == self.CUT_TYPE_IN:
1982 def jumpPreviousNextMark(self, cmp, start=False):
1983 current_pos = self.cueGetCurrentPosition()
1984 if current_pos is None:
1986 mark = self.getNearestCutPoint(current_pos, cmp=cmp, start=start)
1987 if mark is not None:
1995 def jumpPreviousMark(self):
1996 # we add 5 seconds, so if the play position is <5s after
1997 # the mark, the mark before will be used
1998 self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
2000 def jumpNextMark(self):
2001 if not self.jumpPreviousNextMark(lambda x: x-90000):
2004 def getNearestCutPoint(self, pts, cmp=abs, start=False):
2011 bestdiff = cmp(0 - pts)
2013 nearest = [0, False]
2014 for cp in self.cut_list:
2015 if beforecut and cp[1] in (self.CUT_TYPE_IN, self.CUT_TYPE_OUT):
2017 if cp[1] == self.CUT_TYPE_IN: # Start is here, disregard previous marks
2018 diff = cmp(cp[0] - pts)
2019 if start and diff >= 0:
2025 if cp[1] == self.CUT_TYPE_IN:
2027 elif cp[1] == self.CUT_TYPE_OUT:
2029 elif cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
2030 diff = cmp(cp[0] - pts)
2031 if instate and diff >= 0 and (nearest is None or bestdiff > diff):
2036 def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
2037 current_pos = self.cueGetCurrentPosition()
2038 if current_pos is None:
2039 print "not seekable"
2042 nearest_cutpoint = self.getNearestCutPoint(current_pos)
2044 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
2046 return nearest_cutpoint
2048 self.removeMark(nearest_cutpoint)
2049 elif not onlyremove and not onlyreturn:
2050 self.addMark((current_pos, self.CUT_TYPE_MARK))
2055 def addMark(self, point):
2056 insort(self.cut_list, point)
2057 self.uploadCuesheet()
2058 self.showAfterCuesheetOperation()
2060 def removeMark(self, point):
2061 self.cut_list.remove(point)
2062 self.uploadCuesheet()
2063 self.showAfterCuesheetOperation()
2065 def showAfterCuesheetOperation(self):
2066 if isinstance(self, InfoBarShowHide):
2069 def __getCuesheet(self):
2070 service = self.session.nav.getCurrentService()
2073 return service.cueSheet()
2075 def uploadCuesheet(self):
2076 cue = self.__getCuesheet()
2079 print "upload failed, no cuesheet interface"
2081 cue.setCutList(self.cut_list)
2083 def downloadCuesheet(self):
2084 cue = self.__getCuesheet()
2087 print "download failed, no cuesheet interface"
2090 self.cut_list = cue.getCutList()
2092 class InfoBarSummary(Screen):
2094 <screen position="0,0" size="132,64">
2095 <widget source="global.CurrentTime" render="Label" position="62,46" size="82,18" font="Regular;16" >
2096 <convert type="ClockToText">WithSeconds</convert>
2098 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="82,18" zPosition="1" >
2099 <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2100 <convert type="ConditionalShowHide">Blink</convert>
2102 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2103 <convert type="ServiceName">Name</convert>
2105 <widget source="session.Event_Now" render="Progress" position="6,46" size="46,18" borderWidth="1" >
2106 <convert type="EventTime">Progress</convert>
2110 # for picon: (path="piconlcd" will use LCD picons)
2111 # <widget source="session.CurrentService" render="Picon" position="6,0" size="120,64" path="piconlcd" >
2112 # <convert type="ServiceName">Reference</convert>
2115 class InfoBarSummarySupport:
2119 def createSummary(self):
2120 return InfoBarSummary
2122 class InfoBarMoviePlayerSummary(Screen):
2124 <screen position="0,0" size="132,64">
2125 <widget source="global.CurrentTime" render="Label" position="62,46" size="64,18" font="Regular;16" halign="right" >
2126 <convert type="ClockToText">WithSeconds</convert>
2128 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="64,18" zPosition="1" >
2129 <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2130 <convert type="ConditionalShowHide">Blink</convert>
2132 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2133 <convert type="ServiceName">Name</convert>
2135 <widget source="session.CurrentService" render="Progress" position="6,46" size="56,18" borderWidth="1" >
2136 <convert type="ServicePosition">Position</convert>
2140 class InfoBarMoviePlayerSummarySupport:
2144 def createSummary(self):
2145 return InfoBarMoviePlayerSummary
2147 class InfoBarTeletextPlugin:
2149 self.teletext_plugin = None
2151 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
2152 self.teletext_plugin = p
2154 if self.teletext_plugin is not None:
2155 self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
2157 "startTeletext": (self.startTeletext, _("View teletext..."))
2160 print "no teletext plugin found!"
2162 def startTeletext(self):
2163 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
2165 class InfoBarSubtitleSupport(object):
2167 object.__init__(self)
2168 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
2169 self.__subtitles_enabled = False
2171 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2173 iPlayableService.evEnd: self.__serviceStopped,
2174 iPlayableService.evUpdatedInfo: self.__updatedInfo
2176 self.cached_subtitle_checked = False
2177 self.__selected_subtitle = None
2179 def __serviceStopped(self):
2180 self.cached_subtitle_checked = False
2181 if self.__subtitles_enabled:
2182 self.subtitle_window.hide()
2183 self.__subtitles_enabled = False
2184 self.__selected_subtitle = None
2186 def __updatedInfo(self):
2187 if not self.cached_subtitle_checked:
2188 self.cached_subtitle_checked = True
2189 subtitle = self.getCurrentServiceSubtitle()
2190 self.setSelectedSubtitle(subtitle and subtitle.getCachedSubtitle())
2191 if self.__selected_subtitle:
2192 self.setSubtitlesEnable(True)
2194 def getCurrentServiceSubtitle(self):
2195 service = self.session.nav.getCurrentService()
2196 return service and service.subtitle()
2198 def setSubtitlesEnable(self, enable=True):
2199 subtitle = self.getCurrentServiceSubtitle()
2201 if self.__selected_subtitle:
2202 if subtitle and not self.__subtitles_enabled:
2203 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2204 self.subtitle_window.show()
2205 self.__subtitles_enabled = True
2208 subtitle.disableSubtitles(self.subtitle_window.instance)
2209 self.__selected_subtitle = False
2210 self.__subtitles_enabled = False
2211 self.subtitle_window.hide()
2213 def setSelectedSubtitle(self, subtitle):
2214 self.__selected_subtitle = subtitle
2216 subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
2217 selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
2219 class InfoBarServiceErrorPopupSupport:
2221 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2223 iPlayableService.evTuneFailed: self.__tuneFailed,
2224 iPlayableService.evStart: self.__serviceStarted
2226 self.__serviceStarted()
2228 def __serviceStarted(self):
2229 self.last_error = None
2230 Notifications.RemovePopup(id = "ZapError")
2232 def __tuneFailed(self):
2233 service = self.session.nav.getCurrentService()
2234 info = service and service.info()
2235 error = info and info.getInfo(iServiceInformation.sDVBState)
2237 if error == self.last_error:
2240 self.last_error = error
2243 eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2244 eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2245 eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2246 eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2247 eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2248 eDVBServicePMTHandler.eventNewProgramInfo: None,
2249 eDVBServicePMTHandler.eventTuned: None,
2250 eDVBServicePMTHandler.eventSOF: None,
2251 eDVBServicePMTHandler.eventEOF: None,
2252 eDVBServicePMTHandler.eventMisconfiguration: _("Service unavailable!\nCheck tuner configuration!"),
2253 }.get(error) #this returns None when the key not exist in the dict
2255 if error is not None:
2256 Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2258 Notifications.RemovePopup(id = "ZapError")