1 #####################################################
2 # Permanent Timeshift Plugin for Enigma2 Dreamboxes
3 # Coded by Homey (c) 2013
6 # Support: www.dreambox-plugins.de
7 #####################################################
8 from Components.ActionMap import ActionMap
9 from Components.ConfigList import ConfigList, ConfigListScreen
10 from Components.config import config, configfile, getConfigListEntry, ConfigSubsection, ConfigYesNo, ConfigInteger, ConfigSelection, NoSave
11 from Components.Harddisk import harddiskmanager
12 from Components.Label import Label
13 from Components.Language import language
14 from Components.Pixmap import Pixmap
15 from Components.ServiceEventTracker import ServiceEventTracker
16 from Components.Sources.StaticText import StaticText
17 from Components.SystemInfo import SystemInfo
18 from Components.Task import Task, Job, job_manager as JobManager
19 from Components.UsageConfig import preferredInstantRecordPath, defaultMoviePath, defaultStorageDevice
20 from Screens.ChoiceBox import ChoiceBox
21 from Screens.ChannelSelection import ChannelSelection
22 from Screens.InfoBar import InfoBar as InfoBarOrg
23 from Screens.InfoBarGenerics import NumberZap, InfoBarSeek, InfoBarNumberZap, InfoBarTimeshiftState, InfoBarInstantRecord, InfoBarChannelSelection
24 from Screens.MessageBox import MessageBox
25 from Screens.Screen import Screen
26 from Screens.Setup import SetupSummary
27 from Screens.Standby import Standby, TryQuitMainloop
28 from Screens.PVRState import TimeshiftState
29 from ServiceReference import ServiceReference
30 from Tools import Directories, ASCIItranslit, Notifications
31 from Tools.Directories import fileExists, copyfile, resolveFilename, SCOPE_LANGUAGE, SCOPE_PLUGINS
32 from Plugins.Plugin import PluginDescriptor
33 from RecordTimer import RecordTimer, RecordTimerEntry, parseEvent
35 from random import randint
36 from enigma import eTimer, eServiceCenter, eBackgroundFileEraser, iPlayableService, iRecordableService, iServiceInformation
37 from os import environ, stat as os_stat, listdir as os_listdir, link as os_link, path as os_path, system as os_system, statvfs
38 from time import localtime, time, gmtime, strftime
39 from timer import TimerEntry
42 import Screens.InfoBar
43 import Screens.Standby
45 ##############################
46 ### Multilanguage Init ###
47 ##############################
50 lang = language.getLanguage()
51 environ["LANGUAGE"] = lang[:2]
52 gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE))
53 gettext.textdomain("enigma2")
54 gettext.bindtextdomain("PermanentTimeshift", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/PermanentTimeshift/locale/"))
57 t = gettext.dgettext("PermanentTimeshift", txt)
59 t = gettext.gettext(txt)
63 language.addCallback(localeInit)
65 ##############################
66 ##### CONFIG SETTINGS #####
67 ##############################
70 config.plugins.pts = ConfigSubsection()
71 config.plugins.pts.enabled = ConfigYesNo(default = True)
72 config.plugins.pts.maxevents = ConfigInteger(default=5, limits=(1, 99))
73 config.plugins.pts.maxlength = ConfigInteger(default=180, limits=(5, 999))
74 config.plugins.pts.startdelay = ConfigInteger(default=5, limits=(5, 999))
75 config.plugins.pts.showinfobar = ConfigYesNo(default = False)
76 config.plugins.pts.stopwhilerecording = ConfigYesNo(default = False)
77 config.plugins.pts.favoriteSaveAction = ConfigSelection([("askuser", _("Ask user")),("savetimeshift", _("Save and stop")),("savetimeshiftandrecord", _("Save and record")),("noSave", _("Don't save"))], "askuser")
78 config.plugins.pts.permanentrecording = ConfigYesNo(default = False)
79 config.plugins.pts.isRecording = NoSave(ConfigYesNo(default = False))
81 ###################################
82 ### PTS TimeshiftState Screen ###
83 ###################################
85 class PTSTimeshiftState(Screen):
87 <screen position="center,40" zPosition="2" size="420,70" backgroundColor="transpBlack" flags="wfNoBorder">
88 <widget name="state" position="10,3" size="80,27" font="Regular;20" halign="center" backgroundColor="transpBlack" />
89 <widget source="session.CurrentService" render="Label" position="95,5" size="120,27" font="Regular;20" halign="left" foregroundColor="white" backgroundColor="transpBlack">
90 <convert type="ServicePosition">Position</convert>
92 <widget source="session.CurrentService" render="Label" position="335,5" size="70,27" font="Regular;20" halign="left" foregroundColor="white" backgroundColor="transpBlack">
93 <convert type="ServicePosition">Length</convert>
95 <widget name="PTSSeekPointer" position="8,30" zPosition="3" size="19,50" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/timeline-now.png" alphatest="on" />
96 <ePixmap position="10,33" size="840,15" zPosition="1" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/slider_back.png" alphatest="on"/>
97 <widget source="session.CurrentService" render="Progress" position="10,33" size="390,15" zPosition="2" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/slider.png" transparent="1">
98 <convert type="ServicePosition">Position</convert>
100 <widget name="eventname" position="10,49" zPosition="4" size="420,20" font="Regular;18" halign="center" backgroundColor="transpBlack" />
103 def __init__(self, session):
104 Screen.__init__(self, session)
105 self["state"] = Label(text="")
106 self["PTSSeekPointer"] = Pixmap()
107 self["eventname"] = Label(text="")
109 ###################################
110 ### PTS CopyTimeshift Task ###
111 ###################################
113 class CopyTimeshiftJob(Job):
114 def __init__(self, toolbox, cmdline, srcfile, destfile, eventname):
115 Job.__init__(self, _("Saving Timeshift files"))
116 self.toolbox = toolbox
117 AddCopyTimeshiftTask(self, cmdline, srcfile, destfile, eventname)
119 class AddCopyTimeshiftTask(Task):
120 def __init__(self, job, cmdline, srcfile, destfile, eventname):
121 Task.__init__(self, job, eventname)
122 self.toolbox = job.toolbox
123 self.setCmdline(cmdline)
124 self.srcfile = config.usage.timeshift_path.value + "/" + srcfile + ".copy"
125 self.destfile = destfile + ".ts"
127 self.ProgressTimer = eTimer()
128 self.ProgressTimer_conn = self.ProgressTimer.timeout.connect(self.ProgressUpdate)
130 def ProgressUpdate(self):
131 if self.srcsize <= 0 or not fileExists(self.destfile, 'r'):
134 self.setProgress(int((os_path.getsize(self.destfile)/float(self.srcsize))*100))
135 self.ProgressTimer.start(15000, True)
138 if fileExists(self.srcfile, 'r'):
139 self.srcsize = os_path.getsize(self.srcfile)
140 self.ProgressTimer.start(15000, True)
142 self.toolbox.ptsFrontpanelActions("start")
143 config.plugins.pts.isRecording.value = True
146 self.setProgress(100)
147 self.ProgressTimer.stop()
148 self.toolbox.ptsCopyFilefinished(self.srcfile, self.destfile)
150 ###################################
151 ### PTS MergeTimeshift Task ###
152 ###################################
154 class MergeTimeshiftJob(Job):
155 def __init__(self, toolbox, cmdline, srcfile, destfile, eventname):
156 Job.__init__(self, _("Merging Timeshift files"))
157 self.toolbox = toolbox
158 AddMergeTimeshiftTask(self, cmdline, srcfile, destfile, eventname)
160 class AddMergeTimeshiftTask(Task):
161 def __init__(self, job, cmdline, srcfile, destfile, eventname):
162 Task.__init__(self, job, eventname)
163 self.toolbox = job.toolbox
164 self.setCmdline(cmdline)
165 self.srcfile = config.usage.default_path.value + "/" + srcfile
166 self.destfile = config.usage.default_path.value + "/" + destfile
168 self.ProgressTimer = eTimer()
169 self.ProgressTimer_conn = self.ProgressTimer.timeout.connect(self.ProgressUpdate)
171 def ProgressUpdate(self):
172 if self.srcsize <= 0 or not fileExists(self.destfile, 'r'):
175 self.setProgress(int((os_path.getsize(self.destfile)/float(self.srcsize))*100))
176 self.ProgressTimer.start(7500, True)
179 if fileExists(self.srcfile, 'r') and fileExists(self.destfile, 'r'):
180 fsize1 = os_path.getsize(self.srcfile)
181 fsize2 = os_path.getsize(self.destfile)
182 self.srcsize = fsize1 + fsize2
183 self.ProgressTimer.start(7500, True)
185 self.toolbox.ptsFrontpanelActions("start")
186 config.plugins.pts.isRecording.value = True
189 self.setProgress(100)
190 self.ProgressTimer.stop()
191 self.toolbox.ptsMergeFilefinished(self.srcfile, self.destfile)
193 ##################################
194 ### Create APSC Files Task ###
195 ##################################
197 class CreateAPSCFilesJob(Job):
198 def __init__(self, toolbox, cmdline, eventname):
199 Job.__init__(self, _("Creating AP and SC Files"))
200 self.toolbox = toolbox
201 CreateAPSCFilesTask(self, cmdline, eventname)
203 class CreateAPSCFilesTask(Task):
204 def __init__(self, job, cmdline, eventname):
205 Task.__init__(self, job, eventname)
206 self.toolbox = job.toolbox
207 self.setCmdline(cmdline)
210 self.toolbox.ptsFrontpanelActions("start")
211 config.plugins.pts.isRecording.value = True
214 self.setProgress(100)
215 self.toolbox.ptsSaveTimeshiftFinished()
217 ###########################
218 ##### Class InfoBar #####
219 ###########################
220 class InfoBar(InfoBarOrg):
221 def __init__(self, session):
222 InfoBarOrg.__init__(self, session)
223 InfoBarOrg.instance = self
225 self.__event_tracker = ServiceEventTracker(screen = self, eventmap =
227 iPlayableService.evStart: self.__evStart,
228 iPlayableService.evEnd: self.__evEnd,
229 iPlayableService.evSOF: self.__evSOF,
230 iPlayableService.evUpdatedInfo: self.__evInfoChanged,
231 iPlayableService.evUpdatedEventInfo: self.__evEventInfoChanged,
232 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
233 iPlayableService.evUser+1: self.ptsTimeshiftFileChanged
236 self["PTSactions"] = ActionMap(["PTS_GlobalActions"],{"instantRecord": self.instantRecord, "restartTimeshift": self.restartTimeshift},-2)
237 self["PTSSeekPointerActions"] = ActionMap(["PTS_SeekPointerActions"],{"SeekPointerOK": self.ptsSeekPointerOK, "SeekPointerLeft": self.ptsSeekPointerLeft, "SeekPointerRight": self.ptsSeekPointerRight},-2)
238 self["PTSSeekPointerActions"].setEnabled(False)
240 self.pts_begintime = 0
241 self.pts_pathchecked = False
242 self.pts_pvrStateDialog = "TimeshiftState"
243 self.pts_seektoprevfile = False
244 self.pts_switchtolive = False
245 self.pts_currplaying = 1
246 self.pts_lastseekspeed = 0
247 self.pts_service_changed = False
248 self.pts_record_running = self.session.nav.RecordTimer.isRecording()
249 self.save_current_timeshift = False
250 self.save_timeshift_postaction = None
251 self.save_timeshift_filename = None
252 self.service_changed = 0
254 # Init Global Variables
255 self.session.ptsmainloopvalue = 0
256 config.plugins.pts.isRecording.value = False
258 # Init eBackgroundFileEraser
259 self.BgFileEraser = eBackgroundFileEraser.getInstance()
261 # Init PTS Delay-Timer
262 self.pts_delay_timer = eTimer()
263 self.pts_delay_timer_conn = self.pts_delay_timer.timeout.connect(self.activatePermanentTimeshift)
265 # Init PTS LengthCheck-Timer
266 self.pts_LengthCheck_timer = eTimer()
267 self.pts_LengthCheck_timer_conn = self.pts_LengthCheck_timer.timeout.connect(self.ptsLengthCheck)
269 # Init PTS MergeRecords-Timer
270 self.pts_mergeRecords_timer = eTimer()
271 self.pts_mergeRecords_timer_conn = self.pts_mergeRecords_timer.timeout.connect(self.ptsMergeRecords)
273 # Init PTS Merge Cleanup-Timer
274 self.pts_mergeCleanUp_timer = eTimer()
275 self.pts_mergeCleanUp_timer_conn = self.pts_mergeCleanUp_timer.timeout.connect(self.ptsMergePostCleanUp)
277 # Init PTS QuitMainloop-Timer
278 self.pts_QuitMainloop_timer = eTimer()
279 self.pts_QuitMainloop_timer_conn = self.pts_QuitMainloop_timer.timeout.connect(self.ptsTryQuitMainloop)
281 # Init PTS CleanUp-Timer
282 self.pts_cleanUp_timer = eTimer()
283 self.pts_cleanUp_timer_conn = self.pts_cleanUp_timer.timeout.connect(self.ptsCleanTimeshiftFolder)
284 self.pts_cleanUp_timer.start(30000, True)
286 # Init PTS SeekBack-Timer
287 self.pts_SeekBack_timer = eTimer()
288 self.pts_SeekBack_timer_conn = self.pts_SeekBack_timer.timeout.connect(self.ptsSeekBackTimer)
290 # Init Block-Zap Timer
291 self.pts_blockZap_timer = eTimer()
293 # Record Event Tracker
294 self.session.nav.RecordTimer.on_state_change.append(self.ptsTimerEntryStateChange)
296 # Keep Current Event Info for recordings
297 self.pts_eventcount = 1
298 self.pts_curevent_begin = int(time())
299 self.pts_curevent_end = 0
300 self.pts_curevent_name = _("Timeshift")
301 self.pts_curevent_description = ""
302 self.pts_curevent_servicerefname = ""
303 self.pts_curevent_station = ""
304 self.pts_curevent_eventid = None
307 self.pts_seekpointer_MinX = 8
308 self.pts_seekpointer_MaxX = 396 # make sure you can divide this through 2
311 self.service_changed = 1
312 self.pts_delay_timer.stop()
313 self.pts_service_changed = True
316 self.service_changed = 0
319 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
322 if self.pts_currplaying == 1:
323 preptsfile = config.plugins.pts.maxevents.value
325 preptsfile = self.pts_currplaying-1
327 # Switch to previous TS file by jumping to next one
328 if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value, preptsfile), 'r') and preptsfile != self.pts_eventcount:
329 self.pts_seektoprevfile = True
330 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (preptsfile))
331 self.doSeek(3600 * 24 * 90000)
333 def __evInfoChanged(self):
334 if self.service_changed:
335 self.service_changed = 0
337 # We zapped away before saving the file, save it now!
338 if self.save_current_timeshift:
339 self.SaveTimeshift("pts_livebuffer.%s" % (self.pts_eventcount))
341 # Delete Timeshift Records on zap
342 self.pts_eventcount = 0
343 self.pts_cleanUp_timer.start(3000, True)
345 def __evEventInfoChanged(self):
346 if not config.plugins.pts.enabled.value:
349 # Get Current Event Info
350 service = self.session.nav.getCurrentService()
351 old_begin_time = self.pts_begintime
352 info = service and service.info()
353 ptr = info and info.getEvent(0)
354 self.pts_begintime = ptr and ptr.getBeginTime() or 0
356 # Save current TimeShift permanently now ...
357 if info.getInfo(iServiceInformation.sVideoPID) != -1:
359 # Take care of Record Margin Time ...
360 if self.save_current_timeshift and self.timeshift_enabled:
361 if config.recording.margin_after.value > 0 and len(self.recording) == 0:
362 self.SaveTimeshift(mergelater=True)
363 recording = RecordTimerEntry(ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()), time(), time()+(config.recording.margin_after.value*60), self.pts_curevent_name, self.pts_curevent_description, self.pts_curevent_eventid, dirname = config.usage.default_path.value)
364 recording.dontSave = True
365 self.session.nav.RecordTimer.record(recording)
366 self.recording.append(recording)
370 # Restarting active timers after zap ...
371 if self.pts_delay_timer.isActive() and not self.timeshift_enabled:
372 self.pts_delay_timer.start(config.plugins.pts.startdelay.value*1000, True)
373 if self.pts_cleanUp_timer.isActive() and not self.timeshift_enabled:
374 self.pts_cleanUp_timer.start(3000, True)
376 # (Re)Start TimeShift
377 if not self.pts_delay_timer.isActive():
378 if not self.timeshift_enabled or old_begin_time != self.pts_begintime or old_begin_time == 0:
379 if self.pts_service_changed:
380 self.pts_service_changed = False
381 self.pts_delay_timer.start(config.plugins.pts.startdelay.value*1000, True)
383 self.pts_delay_timer.start(1000, True)
385 def __seekableStatusChanged(self):
387 if not self.isSeekable() and self.timeshift_enabled:
389 self["TimeshiftActivateActions"].setEnabled(enabled)
392 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.timeshift_enabled and self.isSeekable():
395 self["PTSSeekPointerActions"].setEnabled(enabled)
397 # Reset Seek Pointer And Eventname in InfoBar
398 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.timeshift_enabled and not self.isSeekable():
399 if self.pts_pvrStateDialog == "PTSTimeshiftState":
400 self.pvrStateDialog["eventname"].setText("")
401 self.ptsSeekPointerReset()
403 # setNextPlaybackFile() when switching back to live tv
404 if config.plugins.pts.enabled.value and self.timeshift_enabled and not self.isSeekable():
405 if self.pts_starttime <= (time()-5):
406 self.pts_blockZap_timer.start(3000, True)
407 self.pts_currplaying = self.pts_eventcount
408 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (self.pts_eventcount))
410 def activatePermanentTimeshift(self):
411 if self.ptsCheckTimeshiftPath() is False or self.session.screen["Standby"].boolean is True or self.ptsLiveTVStatus() is False or (config.plugins.pts.stopwhilerecording.value and self.pts_record_running):
414 # Replace PVR Timeshift State Icon
415 if config.plugins.pts.showinfobar.value:
416 if self.pts_pvrStateDialog != "PTSTimeshiftState":
417 self.pts_pvrStateDialog = "PTSTimeshiftState"
418 self.pvrStateDialog = self.session.instantiateDialog(PTSTimeshiftState)
419 elif not config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog != "TimeshiftState":
420 self.pts_pvrStateDialog = "TimeshiftState"
421 self.pvrStateDialog = self.session.instantiateDialog(TimeshiftState)
423 # Set next-file on event change only when watching latest timeshift ...
424 if self.isSeekable() and self.pts_eventcount == self.pts_currplaying:
425 pts_setnextfile = True
427 pts_setnextfile = False
429 # Update internal Event Counter
430 if self.pts_eventcount >= config.plugins.pts.maxevents.value:
431 self.pts_eventcount = 0
433 self.pts_eventcount += 1
435 # Do not switch back to LiveTV while timeshifting
436 if self.isSeekable():
441 # setNextPlaybackFile() on event change while timeshifting
442 if self.pts_eventcount > 1 and self.isSeekable() and pts_setnextfile:
443 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (self.pts_eventcount))
445 # (Re)start Timeshift now
446 self.stopTimeshiftConfirmed(True, switchToLive)
447 ts = self.getTimeshift()
448 if ts and not ts.startTimeshift():
449 self.pts_starttime = time()
450 self.pts_LengthCheck_timer.start(120000)
451 self.timeshift_enabled = 1
452 self.save_timeshift_postaction = None
453 self.ptsGetEventInfo()
454 self.ptsCreateHardlink()
455 self.__seekableStatusChanged()
457 self.pts_eventcount = 0
459 def startTimeshift(self):
460 if config.plugins.pts.enabled.value:
461 self.pts_delay_timer.stop()
462 self.activatePermanentTimeshift()
463 self.activateTimeshiftEndAndPause()
465 InfoBarOrg.startTimeshift(self)
467 def stopTimeshift(self):
468 if not self.timeshift_enabled:
471 # Jump Back to Live TV
472 if config.plugins.pts.enabled.value and self.timeshift_enabled:
473 if self.isSeekable():
474 self.pts_switchtolive = True
475 self.ptsSetNextPlaybackFile("")
476 if self.seekstate != self.SEEK_STATE_PLAY:
477 self.setSeekState(self.SEEK_STATE_PLAY)
478 self.doSeek(3600 * 24 * 90000)
481 InfoBarOrg.stopTimeshift(self)
483 def stopTimeshiftConfirmed(self, confirmed, switchToLive=True):
484 was_enabled = self.timeshift_enabled
488 ts = self.getTimeshift()
494 ts.stopTimeshift(switchToLive)
498 self.timeshift_enabled = 0
499 self.__seekableStatusChanged()
501 if was_enabled and not self.timeshift_enabled:
502 self.timeshift_enabled = 0
503 self.pts_LengthCheck_timer.stop()
505 def restartTimeshift(self):
506 self.activatePermanentTimeshift()
507 Notifications.AddNotification(MessageBox, _("PTS-Plugin: Restarting Timeshift!"), MessageBox.TYPE_INFO, timeout=5)
509 def saveTimeshiftPopup(self):
510 self.session.openWithCallback(self.saveTimeshiftPopupCallback, ChoiceBox, \
511 title=_("The Timeshift record was not saved yet!\nWhat do you want to do now with the timeshift file?"), \
512 list=((_("Save Timeshift as Movie and stop recording"), "savetimeshift"), \
513 (_("Save Timeshift as Movie and continue recording"), "savetimeshiftandrecord"), \
514 (_("Don't save Timeshift as Movie"), "noSave")))
516 def saveTimeshiftPopupCallback(self, answer):
520 if answer[1] == "savetimeshift":
521 self.saveTimeshiftActions("savetimeshift", self.save_timeshift_postaction)
522 elif answer[1] == "savetimeshiftandrecord":
523 self.saveTimeshiftActions("savetimeshiftandrecord", self.save_timeshift_postaction)
524 elif answer[1] == "noSave":
525 self.save_current_timeshift = False
526 self.saveTimeshiftActions("noSave", self.save_timeshift_postaction)
528 def saveTimeshiftEventPopup(self):
531 entrylist.append((_("Current Event:")+" %s" % (self.pts_curevent_name), "savetimeshift"))
533 filelist = os_listdir(config.usage.timeshift_path.value)
535 if filelist is not None:
538 for filename in filelist:
539 if (filename.startswith("pts_livebuffer.") is True) and (filename.endswith(".del") is False and filename.endswith(".meta") is False and filename.endswith(".eit") is False and filename.endswith(".copy") is False):
540 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
541 if statinfo.st_mtime < (time()-5.0):
542 # Get Event Info from meta file
543 readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,filename), "r")
544 servicerefname = readmetafile.readline()[0:-1]
545 eventname = readmetafile.readline()[0:-1]
546 description = readmetafile.readline()[0:-1]
547 begintime = readmetafile.readline()[0:-1]
552 entrylist.append((_("Record") + " #%s (%s): %s" % (filecount,strftime("%H:%M",localtime(int(begintime))),eventname), "%s" % filename))
554 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, title=_("Which event do you want to save permanently?"), list=entrylist)
556 def saveTimeshiftActions(self, action=None, postaction=None):
557 self.save_timeshift_postaction = postaction
560 if config.plugins.pts.favoriteSaveAction.value == "askuser":
561 self.saveTimeshiftPopup()
563 elif config.plugins.pts.favoriteSaveAction.value == "savetimeshift":
565 elif config.plugins.pts.favoriteSaveAction.value == "savetimeshiftandrecord":
566 if self.pts_curevent_end > time():
567 self.SaveTimeshift(mergelater=True)
568 self.ptsRecordCurrentEvent()
571 elif config.plugins.pts.favoriteSaveAction.value == "noSave":
572 config.plugins.pts.isRecording.value = False
573 self.save_current_timeshift = False
574 elif action == "savetimeshift":
576 elif action == "savetimeshiftandrecord":
577 if self.pts_curevent_end > time():
578 self.SaveTimeshift(mergelater=True)
579 self.ptsRecordCurrentEvent()
582 elif action == "noSave":
583 config.plugins.pts.isRecording.value = False
584 self.save_current_timeshift = False
586 # Workaround: Show Dummy Popup for a second to prevent StandBy Bug
587 if action is None and postaction == "standby" and (config.plugins.pts.favoriteSaveAction.value == "savetimeshift" or config.plugins.pts.favoriteSaveAction.value == "savetimeshiftandrecord"):
588 self.session.open(MessageBox, _("Saving timeshift as movie now. This might take a while!"), MessageBox.TYPE_INFO, timeout=1)
590 # Post PTS Actions like ZAP or whatever the user requested
591 if self.save_timeshift_postaction == "zapUp":
592 InfoBarChannelSelection.zapUp(self)
593 elif self.save_timeshift_postaction == "zapDown":
594 InfoBarChannelSelection.zapDown(self)
595 elif self.save_timeshift_postaction == "historyBack":
596 InfoBarChannelSelection.historyBack(self)
597 elif self.save_timeshift_postaction == "historyNext":
598 InfoBarChannelSelection.historyNext(self)
599 elif self.save_timeshift_postaction == "switchChannelUp":
600 InfoBarChannelSelection.switchChannelUp(self)
601 elif self.save_timeshift_postaction == "switchChannelDown":
602 InfoBarChannelSelection.switchChannelDown(self)
603 elif self.save_timeshift_postaction == "openServiceList":
604 InfoBarChannelSelection.openServiceList(self)
605 elif self.save_timeshift_postaction == "showRadioChannelList":
606 InfoBarChannelSelection.showRadioChannelList(self, zap=True)
607 elif self.save_timeshift_postaction == "standby":
608 Notifications.AddNotification(Screens_Standby_Standby)
610 def SaveTimeshift(self, timeshiftfile=None, mergelater=False):
611 self.save_current_timeshift = False
614 if timeshiftfile is not None:
615 savefilename = timeshiftfile
617 if savefilename is None:
618 for filename in os_listdir(config.usage.timeshift_path.value):
619 if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy") and not filename.endswith(".sc"):
621 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
622 if statinfo.st_mtime > (time()-5.0):
623 savefilename=filename
624 except Exception, errormsg:
625 Notifications.AddNotification(MessageBox, _("PTS Plugin Error: %s" % (errormsg)), MessageBox.TYPE_ERROR)
627 if savefilename is None:
628 Notifications.AddNotification(MessageBox, _("No Timeshift found to save as recording!"), MessageBox.TYPE_ERROR)
630 timeshift_saved = True
631 timeshift_saveerror1 = ""
632 timeshift_saveerror2 = ""
635 config.plugins.pts.isRecording.value = True
638 self.pts_mergeRecords_timer.start(120000, True)
639 metamergestring = "pts_merge\n"
642 if timeshiftfile is None:
643 # Save Current Event by creating hardlink to ts file
644 if self.pts_starttime >= (time()-60):
645 self.pts_starttime -= 60
647 ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name)
649 if config.usage.setup_level.index >= 2:
650 if config.recording.filename_composition.value == "long" and self.pts_curevent_name != pts_curevent_description:
651 ptsfilename = "%s - %s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name,self.pts_curevent_description)
652 elif config.recording.filename_composition.value == "short":
653 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(self.pts_starttime)),self.pts_curevent_name)
654 except Exception, errormsg:
655 print "PTS-Plugin: Using default filename"
657 if config.recording.ascii_filenames.value:
658 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
660 fullname = Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
661 os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s.ts" % (fullname))
662 metafile = open("%s.ts.meta" % (fullname), "w")
663 metafile.write("%s\n%s\n%s\n%i\n%s" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime),metamergestring))
665 self.ptsCreateEITFile(fullname)
666 elif timeshiftfile.startswith("pts_livebuffer"):
667 # Save stored timeshift by creating hardlink to ts file
668 readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile), "r")
669 servicerefname = readmetafile.readline()[0:-1]
670 eventname = readmetafile.readline()[0:-1]
671 description = readmetafile.readline()[0:-1]
672 begintime = readmetafile.readline()[0:-1]
675 ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname)
677 if config.usage.setup_level.index >= 2:
678 if config.recording.filename_composition.value == "long" and eventname != description:
679 ptsfilename = "%s - %s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname,description)
680 elif config.recording.filename_composition.value == "short":
681 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(int(begintime))),eventname)
682 except Exception, errormsg:
683 print "PTS-Plugin: Using default filename"
685 if config.recording.ascii_filenames.value:
686 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
688 fullname=Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
689 os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts" % (fullname))
690 os_link("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
691 if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
692 os_link("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
694 # Add merge-tag to metafile
696 metafile = open("%s.ts.meta" % (fullname), "a")
697 metafile.write("%s\n" % (metamergestring))
700 # Create AP and SC Files when not merging
702 self.ptsCreateAPSCFiles(fullname+".ts")
704 except Exception, errormsg:
705 timeshift_saved = False
706 timeshift_saveerror1 = errormsg
708 # Hmpppf! Saving Timeshift via Hardlink-Method failed. Probably other device?
709 # Let's try to copy the file in background now! This might take a while ...
710 if not timeshift_saved:
712 stat = statvfs(config.usage.default_path.value)
713 freespace = stat.f_bfree / 1000 * stat.f_bsize / 1000
714 randomint = randint(1, 999)
716 if timeshiftfile is None:
717 # Get Filesize for Free Space Check
718 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value,savefilename)) / (1024*1024))
720 # Save Current Event by copying it to the other device
721 if filesize <= freespace:
722 os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,savefilename,randomint))
723 copy_file = savefilename
724 metafile = open("%s.ts.meta" % (fullname), "w")
725 metafile.write("%s\n%s\n%s\n%i\n%s" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime),metamergestring))
727 self.ptsCreateEITFile(fullname)
728 elif timeshiftfile.startswith("pts_livebuffer"):
729 # Get Filesize for Free Space Check
730 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value, timeshiftfile)) / (1024*1024))
732 # Save stored timeshift by copying it to the other device
733 if filesize <= freespace:
734 os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,timeshiftfile,randomint))
735 copyfile("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
736 if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
737 copyfile("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
738 copy_file = timeshiftfile
740 # Add merge-tag to metafile
742 metafile = open("%s.ts.meta" % (fullname), "a")
743 metafile.write("%s\n" % (metamergestring))
746 # Only copy file when enough disk-space available!
747 if filesize <= freespace:
748 timeshift_saved = True
749 copy_file = copy_file+"."+str(randomint)
751 # Get Event Info from meta file
752 if fileExists("%s.ts.meta" % (fullname)):
753 readmetafile = open("%s.ts.meta" % (fullname), "r")
754 servicerefname = readmetafile.readline()[0:-1]
755 eventname = readmetafile.readline()[0:-1]
759 JobManager.AddJob(CopyTimeshiftJob(self, "cp \"%s/%s.copy\" \"%s.ts\"" % (config.usage.timeshift_path.value,copy_file,fullname), copy_file, fullname, eventname))
760 if not Screens.Standby.inTryQuitMainloop and not Screens.Standby.inStandby and not mergelater and self.save_timeshift_postaction != "standby":
761 Notifications.AddNotification(MessageBox, _("Saving timeshift as movie now. This might take a while!"), MessageBox.TYPE_INFO, timeout=5)
763 timeshift_saved = False
764 timeshift_saveerror1 = ""
765 timeshift_saveerror2 = _("Not enough free Diskspace!\n\nFilesize: %sMB\nFree Space: %sMB\nPath: %s" % (filesize,freespace,config.usage.default_path.value))
767 except Exception, errormsg:
768 timeshift_saved = False
769 timeshift_saveerror2 = errormsg
771 if not timeshift_saved:
772 config.plugins.pts.isRecording.value = False
773 self.save_timeshift_postaction = None
774 errormessage = str(timeshift_saveerror1) + "\n" + str(timeshift_saveerror2)
775 Notifications.AddNotification(MessageBox, _("Timeshift save failed!")+"\n\n%s" % errormessage, MessageBox.TYPE_ERROR)
777 def ptsCleanTimeshiftFolder(self):
778 if not config.plugins.pts.enabled.value or self.ptsCheckTimeshiftPath() is False or self.session.screen["Standby"].boolean is True:
782 for filename in os_listdir(config.usage.timeshift_path.value):
783 if (filename.startswith("timeshift.") or filename.startswith("pts_livebuffer.")) and (filename.endswith(".del") is False and filename.endswith(".copy") is False and filename.endswith(".meta") is False and filename.endswith(".eit") is False):
785 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
786 # if no write for 5 sec = stranded timeshift
787 if statinfo.st_mtime < (time()-5.0):
788 print "PTS-Plugin: Erasing stranded timeshift %s" % filename
789 self.BgFileEraser.erase("%s/%s" % (config.usage.timeshift_path.value,filename))
791 # Delete Meta and EIT File too
792 if filename.startswith("pts_livebuffer.") is True:
793 self.BgFileEraser.erase("%s/%s.meta" % (config.usage.timeshift_path.value,filename))
794 self.BgFileEraser.erase("%s/%s.eit" % (config.usage.timeshift_path.value,filename))
796 print "PTS: IO-Error while cleaning Timeshift Folder ..."
798 def ptsGetEventInfo(self):
801 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
802 serviceHandler = eServiceCenter.getInstance()
803 info = serviceHandler.info(serviceref)
805 self.pts_curevent_servicerefname = serviceref.toString()
806 self.pts_curevent_station = info.getName(serviceref)
808 service = self.session.nav.getCurrentService()
809 info = service and service.info()
810 event = info and info.getEvent(0)
811 except Exception, errormsg:
812 Notifications.AddNotification(MessageBox, _("Getting Event Info failed!")+"\n\n%s" % errormsg, MessageBox.TYPE_ERROR, timeout=10)
814 if event is not None:
815 curEvent = parseEvent(event)
816 self.pts_curevent_begin = int(curEvent[0])
817 self.pts_curevent_end = int(curEvent[1])
818 self.pts_curevent_name = curEvent[2]
819 self.pts_curevent_description = curEvent[3]
820 self.pts_curevent_eventid = curEvent[4]
822 def ptsFrontpanelActions(self, action=None):
823 if self.session.nav.RecordTimer.isRecording() or SystemInfo.get("NumFrontpanelLEDs", 0) == 0:
827 if action == "start":
828 if fileExists("/proc/stb/fp/led_set_pattern"):
829 open("/proc/stb/fp/led_set_pattern", "w").write("0xa7fccf7a")
830 elif fileExists("/proc/stb/fp/led0_pattern"):
831 open("/proc/stb/fp/led0_pattern", "w").write("0x55555555")
832 if fileExists("/proc/stb/fp/led_pattern_speed"):
833 open("/proc/stb/fp/led_pattern_speed", "w").write("20")
834 elif fileExists("/proc/stb/fp/led_set_speed"):
835 open("/proc/stb/fp/led_set_speed", "w").write("20")
836 elif action == "stop":
837 if fileExists("/proc/stb/fp/led_set_pattern"):
838 open("/proc/stb/fp/led_set_pattern", "w").write("0")
839 elif fileExists("/proc/stb/fp/led0_pattern"):
840 open("/proc/stb/fp/led0_pattern", "w").write("0")
841 except Exception, errormsg:
842 print "PTS Plugin: %s" % (errormsg)
844 def ptsCreateHardlink(self):
845 for filename in os_listdir(config.usage.timeshift_path.value):
846 if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy") and not filename.endswith(".sc"):
848 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
849 if statinfo.st_mtime > (time()-5.0):
851 self.BgFileEraser.erase("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
852 self.BgFileEraser.erase("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount))
853 except Exception, errormsg:
854 print "PTS Plugin: %s" % (errormsg)
857 # Create link to pts_livebuffer file
858 os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
861 metafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount), "w")
862 metafile.write("%s\n%s\n%s\n%i\n" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime)))
864 except Exception, errormsg:
865 Notifications.AddNotification(MessageBox, _("Creating Hardlink to Timeshift file failed!")+"\n"+_("The Filesystem on your Timeshift-Device does not support hardlinks.\nMake sure it is formated in EXT2 or EXT3!")+"\n\n%s" % errormsg, MessageBox.TYPE_ERROR)
868 self.ptsCreateEITFile("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
870 # Permanent Recording Hack
871 if config.plugins.pts.permanentrecording.value:
873 fullname = Directories.getRecordingFilename("%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name),config.usage.default_path.value)
874 os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s.ts" % (fullname))
876 metafile = open("%s.ts.meta" % (fullname), "w")
877 metafile.write("%s\n%s\n%s\n%i\nautosaved\n" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime)))
879 except Exception, errormsg:
880 print "PTS Plugin: %s" % (errormsg)
881 except Exception, errormsg:
882 errormsg = str(errormsg)
883 if errormsg.find('Input/output error') != -1:
884 errormsg += _("\nAn Input/output error usually indicates a corrupted filesystem! Please check the filesystem of your timeshift-device!")
885 Notifications.AddNotification(MessageBox, _("Creating Hardlink to Timeshift file failed!")+"\n%s" % (errormsg), MessageBox.TYPE_ERROR)
887 def ptsRecordCurrentEvent(self):
888 recording = RecordTimerEntry(ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()), time(), self.pts_curevent_end, self.pts_curevent_name, self.pts_curevent_description, self.pts_curevent_eventid, dirname = config.usage.default_path.value)
889 recording.dontSave = True
890 self.session.nav.RecordTimer.record(recording)
891 self.recording.append(recording)
893 def ptsMergeRecords(self):
894 if self.session.nav.RecordTimer.isRecording():
895 self.pts_mergeRecords_timer.start(120000, True)
900 ptsmergeeventname = ""
901 ptsgetnextfile = False
902 ptsfilemerged = False
904 filelist = os_listdir(config.usage.default_path.value)
906 if filelist is not None:
909 for filename in filelist:
910 if filename.endswith(".meta"):
911 # Get Event Info from meta file
912 readmetafile = open("%s/%s" % (config.usage.default_path.value,filename), "r")
913 servicerefname = readmetafile.readline()[0:-1]
914 eventname = readmetafile.readline()[0:-1]
915 eventtitle = readmetafile.readline()[0:-1]
916 eventtime = readmetafile.readline()[0:-1]
917 eventtag = readmetafile.readline()[0:-1]
921 ptsgetnextfile = False
922 ptsmergeSRC = filename[0:-5]
924 if ASCIItranslit.legacyEncode(eventname) == ASCIItranslit.legacyEncode(ptsmergeeventname):
926 if fileExists("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3])):
927 copyfile("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3]),"%s/%s.eit" % (config.usage.default_path.value, ptsmergeDEST[0:-3]))
929 # Delete AP and SC Files
930 self.BgFileEraser.erase("%s/%s.ap" % (config.usage.default_path.value, ptsmergeDEST))
931 self.BgFileEraser.erase("%s/%s.sc" % (config.usage.default_path.value, ptsmergeDEST))
933 # Add Merge Job to JobManager
934 JobManager.AddJob(MergeTimeshiftJob(self, "cat \"%s/%s\" >> \"%s/%s\"" % (config.usage.default_path.value,ptsmergeSRC,config.usage.default_path.value,ptsmergeDEST), ptsmergeSRC, ptsmergeDEST, eventname))
935 config.plugins.pts.isRecording.value = True
938 ptsgetnextfile = True
940 if eventtag == "pts_merge" and not ptsgetnextfile:
941 ptsgetnextfile = True
942 ptsmergeDEST = filename[0:-5]
943 ptsmergeeventname = eventname
944 ptsfilemerged = False
946 # If still recording or transfering, try again later ...
947 if fileExists("%s/%s" % (config.usage.default_path.value,ptsmergeDEST)):
948 statinfo = os_stat("%s/%s" % (config.usage.default_path.value,ptsmergeDEST))
949 if statinfo.st_mtime > (time()-10.0):
950 self.pts_mergeRecords_timer.start(120000, True)
953 # Rewrite Meta File to get rid of pts_merge tag
954 metafile = open("%s/%s.meta" % (config.usage.default_path.value,ptsmergeDEST), "w")
955 metafile.write("%s\n%s\n%s\n%i\n" % (servicerefname,eventname.replace("\n", ""),eventtitle.replace("\n", ""),int(eventtime)))
959 if not ptsfilemerged and ptsgetnextfile:
960 Notifications.AddNotification(MessageBox,_("PTS-Plugin: Merging records failed!"), MessageBox.TYPE_ERROR)
962 def ptsCreateAPSCFiles(self, filename):
963 if fileExists(filename, 'r'):
964 if fileExists(filename+".meta", 'r'):
965 # Get Event Info from meta file
966 readmetafile = open(filename+".meta", "r")
967 servicerefname = readmetafile.readline()[0:-1]
968 eventname = readmetafile.readline()[0:-1]
971 JobManager.AddJob(CreateAPSCFilesJob(self, "/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/createapscfiles \"%s\"" % (filename), eventname))
973 self.ptsSaveTimeshiftFinished()
975 def ptsCreateEITFile(self, filename):
976 if self.pts_curevent_eventid is not None:
979 serviceref = ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()).ref.toString()
980 eitsave.SaveEIT(serviceref, filename+".eit", self.pts_curevent_eventid, -1, -1)
981 except Exception, errormsg:
982 print "PTS Plugin: %s" % (errormsg)
984 def ptsCopyFilefinished(self, srcfile, destfile):
986 if fileExists(srcfile):
987 self.BgFileEraser.erase(srcfile)
989 # Restart Merge Timer
990 if self.pts_mergeRecords_timer.isActive():
991 self.pts_mergeRecords_timer.stop()
992 self.pts_mergeRecords_timer.start(15000, True)
994 # Create AP and SC Files
995 self.ptsCreateAPSCFiles(destfile)
997 def ptsMergeFilefinished(self, srcfile, destfile):
998 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
999 # Rename files and delete them later ...
1000 self.pts_mergeCleanUp_timer.start(120000, True)
1001 os_system("echo \"\" > \"%s.pts.del\"" % (srcfile[0:-3]))
1003 # Delete Instant Record permanently now ... R.I.P.
1004 self.BgFileEraser.erase("%s" % (srcfile))
1005 self.BgFileEraser.erase("%s.ap" % (srcfile))
1006 self.BgFileEraser.erase("%s.sc" % (srcfile))
1007 self.BgFileEraser.erase("%s.meta" % (srcfile))
1008 self.BgFileEraser.erase("%s.cuts" % (srcfile))
1009 self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1011 # Create AP and SC Files
1012 self.ptsCreateAPSCFiles(destfile)
1014 # Run Merge-Process one more time to check if there are more records to merge
1015 self.pts_mergeRecords_timer.start(10000, True)
1017 def ptsSaveTimeshiftFinished(self):
1018 if not self.pts_mergeCleanUp_timer.isActive():
1019 self.ptsFrontpanelActions("stop")
1020 config.plugins.pts.isRecording.value = False
1022 if Screens.Standby.inTryQuitMainloop:
1023 self.pts_QuitMainloop_timer.start(30000, True)
1025 Notifications.AddNotification(MessageBox, _("Timeshift saved to your harddisk!"), MessageBox.TYPE_INFO, timeout = 5)
1027 def ptsMergePostCleanUp(self):
1028 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
1029 config.plugins.pts.isRecording.value = True
1030 self.pts_mergeCleanUp_timer.start(120000, True)
1033 self.ptsFrontpanelActions("stop")
1034 config.plugins.pts.isRecording.value = False
1036 filelist = os_listdir(config.usage.default_path.value)
1037 for filename in filelist:
1038 if filename.endswith(".pts.del"):
1039 srcfile = config.usage.default_path.value + "/" + filename[0:-8] + ".ts"
1040 self.BgFileEraser.erase("%s" % (srcfile))
1041 self.BgFileEraser.erase("%s.ap" % (srcfile))
1042 self.BgFileEraser.erase("%s.sc" % (srcfile))
1043 self.BgFileEraser.erase("%s.meta" % (srcfile))
1044 self.BgFileEraser.erase("%s.cuts" % (srcfile))
1045 self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1046 self.BgFileEraser.erase("%s.pts.del" % (srcfile[0:-3]))
1048 # Restart QuitMainloop Timer to give BgFileEraser enough time
1049 if Screens.Standby.inTryQuitMainloop and self.pts_QuitMainloop_timer.isActive():
1050 self.pts_QuitMainloop_timer.start(60000, True)
1052 def ptsTryQuitMainloop(self):
1053 if Screens.Standby.inTryQuitMainloop and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeCleanUp_timer.isActive()):
1054 self.pts_QuitMainloop_timer.start(60000, True)
1057 if Screens.Standby.inTryQuitMainloop and self.session.ptsmainloopvalue:
1058 self.session.dialog_stack = []
1059 self.session.summary_stack = [None]
1060 self.session.open(TryQuitMainloop, self.session.ptsmainloopvalue)
1062 def ptsGetSeekInfo(self):
1063 s = self.session.nav.getCurrentService()
1064 return s and s.seek()
1066 def ptsGetPosition(self):
1067 seek = self.ptsGetSeekInfo()
1070 pos = seek.getPlayPosition()
1075 def ptsGetLength(self):
1076 seek = self.ptsGetSeekInfo()
1079 length = seek.getLength()
1084 def ptsGetSaveTimeshiftStatus(self):
1085 return self.save_current_timeshift
1087 def ptsSeekPointerOK(self):
1088 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1089 if not self.pvrstate_hide_timer.isActive():
1090 if self.seekstate != self.SEEK_STATE_PLAY:
1091 self.setSeekState(self.SEEK_STATE_PLAY)
1095 length = self.ptsGetLength()
1096 position = self.ptsGetPosition()
1098 if length is None or position is None:
1101 cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1102 jumptox = int(cur_pos[0]) - int(self.pts_seekpointer_MinX)
1103 jumptoperc = round((jumptox / 400.0) * 100, 0)
1104 jumptotime = int((length / 100) * jumptoperc)
1105 jumptodiff = position - jumptotime
1107 self.doSeekRelative(-jumptodiff)
1111 def ptsSeekPointerLeft(self):
1112 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1113 self.ptsMoveSeekPointer(direction="left")
1117 def ptsSeekPointerRight(self):
1118 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1119 self.ptsMoveSeekPointer(direction="right")
1123 def ptsSeekPointerReset(self):
1124 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled:
1125 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX,self.pvrStateDialog["PTSSeekPointer"].position[1])
1127 def ptsSeekPointerSetCurrentPos(self):
1128 if not self.pts_pvrStateDialog == "PTSTimeshiftState" or not self.timeshift_enabled or not self.isSeekable():
1131 position = self.ptsGetPosition()
1132 length = self.ptsGetLength()
1135 tpixels = int((float(int((position*100)/length))/100)*400)
1136 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX+tpixels, self.pvrStateDialog["PTSSeekPointer"].position[1])
1138 def ptsMoveSeekPointer(self, direction=None):
1139 if direction is None or self.pts_pvrStateDialog != "PTSTimeshiftState":
1143 cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1144 InfoBarTimeshiftState._mayShow(self)
1146 if direction == "left":
1147 minmaxval = self.pts_seekpointer_MinX
1149 if cur_pos[0]+movepixels > minmaxval:
1151 elif direction == "right":
1152 minmaxval = self.pts_seekpointer_MaxX
1154 if cur_pos[0]+movepixels < minmaxval:
1160 self.pvrStateDialog["PTSSeekPointer"].setPosition(cur_pos[0]+movepixels,cur_pos[1])
1162 self.pvrStateDialog["PTSSeekPointer"].setPosition(minmaxval,cur_pos[1])
1164 def ptsTimeshiftFileChanged(self):
1165 # Reset Seek Pointer
1166 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value:
1167 self.ptsSeekPointerReset()
1169 if self.pts_switchtolive:
1170 self.pts_switchtolive = False
1173 if self.pts_seektoprevfile:
1174 if self.pts_currplaying == 1:
1175 self.pts_currplaying = config.plugins.pts.maxevents.value
1177 self.pts_currplaying -= 1
1179 if self.pts_currplaying == config.plugins.pts.maxevents.value:
1180 self.pts_currplaying = 1
1182 self.pts_currplaying += 1
1184 if not fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_currplaying), 'r'):
1185 self.pts_currplaying = self.pts_eventcount
1187 # Set Eventname in PTS InfoBar
1188 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog == "PTSTimeshiftState":
1190 if self.pts_eventcount != self.pts_currplaying:
1191 readmetafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_currplaying), "r")
1192 servicerefname = readmetafile.readline()[0:-1]
1193 eventname = readmetafile.readline()[0:-1]
1194 readmetafile.close()
1195 self.pvrStateDialog["eventname"].setText(eventname)
1197 self.pvrStateDialog["eventname"].setText("")
1198 except Exception, errormsg:
1199 self.pvrStateDialog["eventname"].setText("")
1201 # Get next pts file ...
1202 if self.pts_currplaying+1 > config.plugins.pts.maxevents.value:
1205 nextptsfile = self.pts_currplaying+1
1207 # Seek to previous file
1208 if self.pts_seektoprevfile:
1209 self.pts_seektoprevfile = False
1211 if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r'):
1212 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1214 self.ptsSeekBackHack()
1216 if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r') and nextptsfile <= self.pts_eventcount:
1217 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1218 if nextptsfile == self.pts_currplaying:
1219 self.pts_switchtolive = True
1220 self.ptsSetNextPlaybackFile("")
1222 def ptsSetNextPlaybackFile(self, nexttsfile):
1223 ts = self.getTimeshift()
1228 ts.setNextPlaybackFile("%s/%s" % (config.usage.timeshift_path.value,nexttsfile))
1230 print "PTS-Plugin: setNextPlaybackFile() not supported by OE. Enigma2 too old !?"
1232 def ptsSeekBackHack(self):
1233 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1236 self.setSeekState(self.SEEK_STATE_PAUSE)
1237 self.doSeek(-90000*4) # seek ~4s before end
1238 self.pts_SeekBack_timer.start(1000, True)
1240 def ptsSeekBackTimer(self):
1241 if self.pts_lastseekspeed == 0:
1242 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1244 self.setSeekState(self.makeStateBackward(int(-self.pts_lastseekspeed)))
1246 def ptsCheckTimeshiftPath(self):
1247 if self.pts_pathchecked:
1250 if fileExists(config.usage.timeshift_path.value, 'w'):
1251 self.pts_pathchecked = True
1254 Notifications.AddNotification(MessageBox, _("Could not activate Permanent-Timeshift!\nTimeshift-Path does not exist"), MessageBox.TYPE_ERROR, timeout=15)
1255 if self.pts_delay_timer.isActive():
1256 self.pts_delay_timer.stop()
1257 if self.pts_cleanUp_timer.isActive():
1258 self.pts_cleanUp_timer.stop()
1261 def ptsTimerEntryStateChange(self, timer):
1262 if not config.plugins.pts.enabled.value or not config.plugins.pts.stopwhilerecording.value:
1265 self.pts_record_running = self.session.nav.RecordTimer.isRecording()
1267 # Abort here when box is in standby mode
1268 if self.session.screen["Standby"].boolean is True:
1271 # Stop Timeshift when Record started ...
1272 if timer.state == TimerEntry.StateRunning and self.timeshift_enabled and self.pts_record_running:
1273 if self.ptsLiveTVStatus() is False:
1274 self.timeshift_enabled = 0
1275 self.pts_LengthCheck_timer.stop()
1278 if self.seekstate != self.SEEK_STATE_PLAY:
1279 self.setSeekState(self.SEEK_STATE_PLAY)
1281 if self.isSeekable():
1282 Notifications.AddNotification(MessageBox,_("Record started! Stopping timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1284 self.stopTimeshiftConfirmed(True, False)
1286 # Restart Timeshift when all records stopped
1287 if timer.state == TimerEntry.StateEnded and not self.timeshift_enabled and not self.pts_record_running:
1288 self.activatePermanentTimeshift()
1290 # Restart Merge-Timer when all records stopped
1291 if timer.state == TimerEntry.StateEnded and self.pts_mergeRecords_timer.isActive():
1292 self.pts_mergeRecords_timer.stop()
1293 self.pts_mergeRecords_timer.start(15000, True)
1295 # Restart FrontPanel LED when still copying or merging files
1296 # ToDo: Only do this on PTS Events and not events from other jobs
1297 if timer.state == TimerEntry.StateEnded and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeRecords_timer.isActive()):
1298 self.ptsFrontpanelActions("start")
1299 config.plugins.pts.isRecording.value = True
1301 def ptsLiveTVStatus(self):
1302 service = self.session.nav.getCurrentService()
1303 info = service and service.info()
1304 sTSID = info and info.getInfo(iServiceInformation.sTSID) or -1
1306 if sTSID is None or sTSID == -1:
1311 def ptsLengthCheck(self):
1312 # Check if we are in TV Mode ...
1313 if self.ptsLiveTVStatus() is False:
1314 self.timeshift_enabled = 0
1315 self.pts_LengthCheck_timer.stop()
1318 if config.plugins.pts.stopwhilerecording.value and self.pts_record_running:
1322 if config.plugins.pts.enabled.value and self.session.screen["Standby"].boolean is not True and self.timeshift_enabled and (time() - self.pts_starttime) >= (config.plugins.pts.maxlength.value * 60):
1323 if self.save_current_timeshift:
1324 self.saveTimeshiftActions("savetimeshift")
1325 self.activatePermanentTimeshift()
1326 self.save_current_timeshift = True
1328 self.activatePermanentTimeshift()
1329 Notifications.AddNotification(MessageBox,_("Maximum Timeshift length per Event reached!\nRestarting Timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1331 #Replace the InfoBar with our version ;)
1332 Screens.InfoBar.InfoBar = InfoBar
1334 ################################
1335 ##### Class Standby Hack 1 #####
1336 ################################
1337 TryQuitMainloop_getRecordEvent = Screens.Standby.TryQuitMainloop.getRecordEvent
1339 class TryQuitMainloopPTS(TryQuitMainloop):
1340 def __init__(self, session, retvalue=1, timeout=-1, default_yes = True):
1341 TryQuitMainloop.__init__(self, session, retvalue, timeout, default_yes)
1343 self.session.ptsmainloopvalue = retvalue
1345 def getRecordEvent(self, recservice, event):
1346 if event == iRecordableService.evEnd and (config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1):
1349 TryQuitMainloop_getRecordEvent(self, recservice, event)
1351 Screens.Standby.TryQuitMainloop = TryQuitMainloopPTS
1353 ################################
1354 ##### Class Standby Hack 2 #####
1355 ################################
1357 Screens_Standby_Standby = Screens.Standby.Standby
1359 class StandbyPTS(Standby):
1360 def __init__(self, session):
1361 if InfoBar and InfoBar.instance and InfoBar.ptsGetSaveTimeshiftStatus(InfoBar.instance):
1362 self.skin = """<screen position="0,0" size="0,0"/>"""
1363 Screen.__init__(self, session)
1364 self.onFirstExecBegin.append(self.showMessageBox)
1365 self.onHide.append(self.close)
1367 Standby.__init__(self, session)
1368 self.skinName = "Standby"
1370 def showMessageBox(self):
1371 if InfoBar and InfoBar.instance:
1372 InfoBar.saveTimeshiftActions(InfoBar.instance, postaction="standby")
1374 Screens.Standby.Standby = StandbyPTS
1379 InfoBarChannelSelection_zapUp = InfoBarChannelSelection.zapUp
1382 if self.pts_blockZap_timer.isActive():
1385 if self.save_current_timeshift and self.timeshift_enabled:
1386 InfoBar.saveTimeshiftActions(self, postaction="zapUp")
1388 InfoBarChannelSelection_zapUp(self)
1390 InfoBarChannelSelection.zapUp = zapUp
1395 InfoBarChannelSelection_zapDown = InfoBarChannelSelection.zapDown
1398 if self.pts_blockZap_timer.isActive():
1401 if self.save_current_timeshift and self.timeshift_enabled:
1402 InfoBar.saveTimeshiftActions(self, postaction="zapDown")
1404 InfoBarChannelSelection_zapDown(self)
1406 InfoBarChannelSelection.zapDown = zapDown
1411 InfoBarChannelSelection_historyBack = InfoBarChannelSelection.historyBack
1413 def historyBack(self):
1414 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1415 InfoBarTimeshiftState._mayShow(self)
1416 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1417 if self.seekstate != self.SEEK_STATE_PLAY:
1418 self.setSeekState(self.SEEK_STATE_PLAY)
1419 self.ptsSeekPointerOK()
1420 elif self.save_current_timeshift and self.timeshift_enabled:
1421 InfoBar.saveTimeshiftActions(self, postaction="historyBack")
1423 InfoBarChannelSelection_historyBack(self)
1425 InfoBarChannelSelection.historyBack = historyBack
1430 InfoBarChannelSelection_historyNext = InfoBarChannelSelection.historyNext
1432 def historyNext(self):
1433 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1434 InfoBarTimeshiftState._mayShow(self)
1435 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1436 if self.seekstate != self.SEEK_STATE_PLAY:
1437 self.setSeekState(self.SEEK_STATE_PLAY)
1438 self.ptsSeekPointerOK()
1439 elif self.save_current_timeshift and self.timeshift_enabled:
1440 InfoBar.saveTimeshiftActions(self, postaction="historyNext")
1442 InfoBarChannelSelection_historyNext(self)
1444 InfoBarChannelSelection.historyNext = historyNext
1446 ######################
1447 #switchChannelUp Hack#
1448 ######################
1449 InfoBarChannelSelection_switchChannelUp = InfoBarChannelSelection.switchChannelUp
1451 def switchChannelUp(self):
1452 if self.save_current_timeshift and self.timeshift_enabled:
1453 InfoBar.saveTimeshiftActions(self, postaction="switchChannelUp")
1455 InfoBarChannelSelection_switchChannelUp(self)
1457 InfoBarChannelSelection.switchChannelUp = switchChannelUp
1459 ########################
1460 #switchChannelDown Hack#
1461 ########################
1462 InfoBarChannelSelection_switchChannelDown = InfoBarChannelSelection.switchChannelDown
1464 def switchChannelDown(self):
1465 if self.save_current_timeshift and self.timeshift_enabled:
1466 InfoBar.saveTimeshiftActions(self, postaction="switchChannelDown")
1468 InfoBarChannelSelection_switchChannelDown(self)
1470 InfoBarChannelSelection.switchChannelDown = switchChannelDown
1472 ######################
1473 #openServiceList Hack#
1474 ######################
1475 InfoBarChannelSelection_openServiceList = InfoBarChannelSelection.openServiceList
1477 def openServiceList(self):
1478 if self.save_current_timeshift and self.timeshift_enabled:
1479 InfoBar.saveTimeshiftActions(self, postaction="openServiceList")
1481 InfoBarChannelSelection_openServiceList(self)
1483 InfoBarChannelSelection.openServiceList = openServiceList
1485 ###########################
1486 #showRadioChannelList Hack#
1487 ###########################
1488 InfoBarChannelSelection_showRadioChannelList = InfoBarChannelSelection.showRadioChannelList
1490 def showRadioChannelList(self, zap=False):
1491 if self.save_current_timeshift and self.timeshift_enabled:
1492 InfoBar.saveTimeshiftActions(self, postaction="showRadioChannelList")
1494 InfoBarChannelSelection_showRadioChannelList(self, zap)
1496 InfoBarChannelSelection.showRadioChannelList = showRadioChannelList
1498 #######################
1499 #InfoBarNumberZap Hack#
1500 #######################
1501 InfoBarNumberZap_keyNumberGlobal = InfoBarNumberZap.keyNumberGlobal
1503 def keyNumberGlobal(self, number):
1504 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable() and number == 0:
1505 InfoBarTimeshiftState._mayShow(self)
1506 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX/2, self.pvrStateDialog["PTSSeekPointer"].position[1])
1507 if self.seekstate != self.SEEK_STATE_PLAY:
1508 self.setSeekState(self.SEEK_STATE_PLAY)
1509 self.ptsSeekPointerOK()
1512 if self.pts_blockZap_timer.isActive():
1515 if self.save_current_timeshift and self.timeshift_enabled:
1516 InfoBar.saveTimeshiftActions(self)
1519 InfoBarNumberZap_keyNumberGlobal(self, number)
1520 if number and config.plugins.pts.enabled.value and self.timeshift_enabled and not self.isSeekable():
1521 self.session.openWithCallback(self.numberEntered, NumberZap, number)
1523 InfoBarNumberZap.keyNumberGlobal = keyNumberGlobal
1525 #############################
1526 # getNextRecordingTime Hack #
1527 #############################
1528 RecordTimer_getNextRecordingTime = RecordTimer.getNextRecordingTime
1530 def getNextRecordingTime(self):
1531 nextrectime = RecordTimer_getNextRecordingTime(self)
1532 faketime = time()+300
1534 if config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1:
1535 if nextrectime > 0 and nextrectime < faketime:
1542 RecordTimer.getNextRecordingTime = getNextRecordingTime
1544 ############################
1545 #InfoBarTimeshiftState Hack#
1546 ############################
1548 if InfoBar and InfoBar.instance and self.execing and self.timeshift_enabled and self.isSeekable():
1549 InfoBar.ptsSeekPointerSetCurrentPos(self)
1550 self.pvrStateDialog.show()
1552 self.pvrstate_hide_timer = eTimer()
1553 self.pvrstate_hide_timer_conn = self.pvrstate_hide_timer.timeout.connect(self.pvrStateDialog.hide)
1554 self.pvrstate_hide_timer.stop()
1556 if self.seekstate == self.SEEK_STATE_PLAY:
1557 idx = config.usage.infobar_timeout.index
1560 self.pvrstate_hide_timer.start(idx*1000, True)
1562 self.pvrstate_hide_timer.stop()
1563 elif self.execing and self.timeshift_enabled and not self.isSeekable():
1564 self.pvrStateDialog.hide()
1566 InfoBarTimeshiftState._mayShow = _mayShow
1571 InfoBarSeek_seekBack = InfoBarSeek.seekBack
1574 InfoBarSeek_seekBack(self)
1575 self.pts_lastseekspeed = self.seekstate[1]
1577 InfoBarSeek.seekBack = seekBack
1579 ########################
1580 # doSeekRelative Hack #
1581 ########################
1582 InfoBarSeek_doSeekRelative = InfoBarSeek.doSeekRelative
1584 def doSeekRelative(self, pts):
1585 InfoBarSeek_doSeekRelative(self, pts)
1586 if config.plugins.pts.enabled.value and config.usage.show_infobar_on_skip.value:
1587 self.showAfterSeek()
1589 InfoBarSeek.doSeekRelative = doSeekRelative
1591 ####################
1592 #instantRecord Hack#
1593 ####################
1594 InfoBarInstantRecord_instantRecord = InfoBarInstantRecord.instantRecord
1596 def instantRecord(self):
1597 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1598 InfoBarInstantRecord_instantRecord(self)
1601 dir = preferredInstantRecordPath()
1602 if not dir or not fileExists(dir, 'w'):
1603 dir = defaultMoviePath()
1605 if not harddiskmanager.inside_mountpoint(dir):
1606 if harddiskmanager.HDDCount() and not harddiskmanager.HDDEnabledCount():
1607 self.session.open(MessageBox, _("Unconfigured storage devices found!") + "\n" \
1608 + _("Please make sure to set up your storage devices with the storage management in menu -> setup -> system -> storage devices."), MessageBox.TYPE_ERROR)
1610 elif harddiskmanager.HDDEnabledCount() and defaultStorageDevice() == "<undefined>":
1611 self.session.open(MessageBox, _("No default storage device found!") + "\n" \
1612 + _("Please make sure to set up your default storage device in menu -> setup -> system -> recording paths."), MessageBox.TYPE_ERROR)
1614 elif harddiskmanager.HDDEnabledCount() and defaultStorageDevice() != "<undefined>":
1615 part = harddiskmanager.getDefaultStorageDevicebyUUID(defaultStorageDevice())
1617 self.session.open(MessageBox, _("Default storage device is not available!") + "\n" \
1618 + _("Please verify if your default storage device is attached or set up your default storage device in menu -> setup -> system -> recording paths."), MessageBox.TYPE_ERROR)
1621 # XXX: this message is a little odd as we might be recording to a remote device
1622 self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1625 if self.isInstantRecordRunning():
1626 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1627 title=_("A recording is currently running.\nWhat do you want to do?"), \
1628 list=((_("stop recording"), "stop"), \
1629 (_("add recording (stop after current event)"), "event"), \
1630 (_("add recording (indefinitely)"), "indefinitely"), \
1631 (_("add recording (enter recording duration)"), "manualduration"), \
1632 (_("add recording (enter recording endtime)"), "manualendtime"), \
1633 (_("change recording (duration)"), "changeduration"), \
1634 (_("change recording (endtime)"), "changeendtime"), \
1635 (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1636 (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1637 (_("do nothing"), "no")))
1639 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1640 title=_("Start recording?"), \
1641 list=((_("add recording (stop after current event)"), "event"), \
1642 (_("add recording (indefinitely)"), "indefinitely"), \
1643 (_("add recording (enter recording duration)"), "manualduration"), \
1644 (_("add recording (enter recording endtime)"), "manualendtime"), \
1645 (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1646 (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1647 (_("don't record"), "no")))
1649 InfoBarInstantRecord.instantRecord = instantRecord
1651 #############################
1652 #recordQuestionCallback Hack#
1653 #############################
1654 InfoBarInstantRecord_recordQuestionCallback = InfoBarInstantRecord.recordQuestionCallback
1656 def recordQuestionCallback(self, answer):
1657 InfoBarInstantRecord_recordQuestionCallback(self, answer)
1659 if config.plugins.pts.enabled.value:
1660 if answer is not None and answer[1] == "savetimeshift":
1661 if InfoBarSeek.isSeekable(self) and self.pts_eventcount != self.pts_currplaying:
1662 InfoBar.SaveTimeshift(self, timeshiftfile="pts_livebuffer.%s" % self.pts_currplaying)
1664 Notifications.AddNotification(MessageBox,_("Timeshift will get saved at end of event!"), MessageBox.TYPE_INFO, timeout=5)
1665 self.save_current_timeshift = True
1666 config.plugins.pts.isRecording.value = True
1667 if answer is not None and answer[1] == "savetimeshiftEvent":
1668 InfoBar.saveTimeshiftEventPopup(self)
1670 if answer is not None and answer[1].startswith("pts_livebuffer") is True:
1671 InfoBar.SaveTimeshift(self, timeshiftfile=answer[1])
1673 InfoBarInstantRecord.recordQuestionCallback = recordQuestionCallback
1675 ############################
1676 ##### SETTINGS SCREEN #####
1677 ############################
1678 class PermanentTimeShiftSetup(Screen, ConfigListScreen):
1679 def __init__(self, session):
1680 Screen.__init__(self, session)
1681 self.skinName = [ "PTSSetup", "Setup" ]
1682 self.setup_title = _("Permanent Timeshift Settings Version %s") %VERSION
1684 self.onChangedEntry = [ ]
1686 ConfigListScreen.__init__(self, self.list, session = session, on_change = self.changedEntry)
1688 self["actions"] = ActionMap(["SetupActions", "ColorActions"],
1690 "ok": self.SaveSettings,
1691 "green": self.SaveSettings,
1696 self["key_green"] = StaticText(_("OK"))
1697 self["key_red"] = StaticText(_("Cancel"))
1700 self.onLayoutFinish.append(self.layoutFinished)
1702 def layoutFinished(self):
1703 self.setTitle(self.setup_title)
1705 def createSetup(self):
1706 self.list = [ getConfigListEntry(_("Permanent Timeshift Enable"), config.plugins.pts.enabled) ]
1707 if config.plugins.pts.enabled.value:
1709 getConfigListEntry(_("Permanent Timeshift Max Events"), config.plugins.pts.maxevents),
1710 getConfigListEntry(_("Permanent Timeshift Max Length"), config.plugins.pts.maxlength),
1711 getConfigListEntry(_("Permanent Timeshift Start Delay"), config.plugins.pts.startdelay),
1712 getConfigListEntry(_("Timeshift-Save Action on zap"), config.plugins.pts.favoriteSaveAction),
1713 getConfigListEntry(_("Stop timeshift while recording?"), config.plugins.pts.stopwhilerecording),
1714 getConfigListEntry(_("Show PTS Infobar while timeshifting?"), config.plugins.pts.showinfobar)
1717 # Permanent Recording Hack
1718 if fileExists("/usr/lib/enigma2/python/Plugins/Extensions/HouseKeeping/plugin.py"):
1719 self.list.append(getConfigListEntry(_("Beta: Enable Permanent Recording?"), config.plugins.pts.permanentrecording))
1721 self["config"].list = self.list
1722 self["config"].setList(self.list)
1725 ConfigListScreen.keyLeft(self)
1726 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1730 ConfigListScreen.keyRight(self)
1731 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1734 def changedEntry(self):
1735 for x in self.onChangedEntry:
1738 def getCurrentEntry(self):
1739 return self["config"].getCurrent()[0]
1741 def getCurrentValue(self):
1742 return str(self["config"].getCurrent()[1].getText())
1744 def createSummary(self):
1747 def SaveSettings(self):
1748 config.plugins.pts.save()
1755 #################################################
1757 def startSetup(menuid):
1758 if menuid != "system":
1760 return [(_("Timeshift Settings"), PTSSetupMenu, "pts_setup", 50)]
1762 def PTSSetupMenu(session, **kwargs):
1763 session.open(PermanentTimeShiftSetup)
1765 def Plugins(path, **kwargs):
1766 return [ PluginDescriptor(name=_("Permanent Timeshift Settings"), description=_("Permanent Timeshift Settings"), where=PluginDescriptor.WHERE_MENU, fnc=startSetup) ]