def program_seek_vps_multiple_closed(self, retval): self.program_seek_vps_multiple_started = -1 self.found_vps_multiple = sorted(self.found_vps_multiple) for evt_begin, evt_id, evt in self.found_vps_multiple: # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: self.next_events.append(evt_id) self.timer.log(0, "[VPS] add event_id " + str(evt_id)) else: canbeadded = True evt_begin += 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 now = time() for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - now) > 3600 * 24: break if checktimer.service_ref.ref.toCompareString( ) == self.timer.service_ref.ref.toCompareString( ) or checktimer.service_ref.ref.toCompareString( ) == self.rec_ref.toCompareString(): if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if not checktimer.vpsplugin_enabled or not checktimer.vpsplugin_overwrite: canbeadded = False # manuell angelegter Timer mit VPS if checktimer.vpsplugin_enabled and checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = evt_id checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription( ) checktimer.vpsplugin_time = None checktimer.log( 0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)" ) canbeadded = False break if canbeadded: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.log( 0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)" ) # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. NavigationInstance.instance.RecordTimer.record(newEntry)
def program_seek_vps_multiple_closed(self, retval): self.program_seek_vps_multiple_started = -1 self.found_vps_multiple = sorted(self.found_vps_multiple) for evt_begin, evt_id, evt in self.found_vps_multiple: # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: self.next_events.append(evt_id) self.timer.log(0, "[VPS] add event_id "+ str(evt_id)) else: canbeadded = True evt_begin += 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 now = time() for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - now) > 3600*24: break if checktimer.service_ref.ref.toCompareString() == self.timer.service_ref.ref.toCompareString() or checktimer.service_ref.ref.toCompareString() == self.rec_ref.toCompareString(): if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if not checktimer.vpsplugin_enabled or not checktimer.vpsplugin_overwrite: canbeadded = False # manuell angelegter Timer mit VPS if checktimer.vpsplugin_enabled and checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = evt_id checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription() checktimer.vpsplugin_time = None checktimer.log(0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)") canbeadded = False break if canbeadded: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.log(0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)") # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. NavigationInstance.instance.RecordTimer.record(newEntry)
def check_and_add_event(self, neweventid): if not config.plugins.vps.allow_seeking_multiple_pdc.value: return epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, neweventid) if evt: evt_begin = evt.getBeginTime() + 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 if evt_begin < self.timer.begin: return for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - evt_begin) > 3600*2: break compareString = checktimer.service_ref.ref.toCompareString() if compareString == self.timer.service_ref.ref.toCompareString() or compareString == self.rec_ref.toCompareString(): if checktimer.eit == neweventid: return if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if checktimer.vpsplugin_enabled is None or not checktimer.vpsplugin_enabled: return # manuell angelegter Timer mit VPS if checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = neweventid checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription() checktimer.vpsplugin_time = None checktimer.log(0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)") return # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: check_already_existing = [x for (x,y) in self.next_events if y == neweventid] if len(check_already_existing) > 0: start = check_already_existing.pop() if start == evt_begin: return else: self.next_events.remove( (start, neweventid) ) self.timer.log(0, "[VPS] delete event_id "+ str(neweventid) +" because of delay "+ str(evt_begin - start)) self.next_events.append( (evt_begin, neweventid) ) self.next_events = sorted(self.next_events) self.timer.log(0, "[VPS] add event_id "+ str(neweventid)) else: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.dirname = self.timer.dirname newEntry.log(0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)") # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. res = NavigationInstance.instance.RecordTimer.record(newEntry) self.timer.log(0, "[VPS] added another timer, res "+ str(res))
class vps_timer: def __init__(self, timer, session): self.timer = timer self.session = session self.program = eConsoleAppContainer() self.dataAvail_conn = self.program.dataAvail.connect(self.program_dataAvail) self.appClosed_conn = self.program.appClosed.connect(self.program_closed) self.program_running = False self.program_try_search_running = False self.activated_auto_increase = False self.simulate_recordService = None self.demux = -1 self.rec_ref = None self.found_pdc = False self.dont_restart_program = False self.org_timer_end = 0 self.org_timer_begin = 0 self.max_extending_timer = 4*3600 self.next_events = [ ] self.new_timer_copy = None self.pausing = False def program_closed(self, retval): self.timer.log(0, "[VPS] stop monitoring (process terminated)") if self.program_running or self.program_try_search_running: self.program_running = False self.program_try_search_running = False self.stop_simulation() def program_dataAvail(self, str): if self.timer is None or self.timer.state == TimerEntry.StateEnded or self.timer.cancelled: self.program_abort() self.stop_simulation() return if self.timer.vpsplugin_enabled == False or config.plugins.vps.enabled.value == False: if self.activated_auto_increase: self.timer.autoincrease = False self.program_abort() self.stop_simulation() return lines = str.split("\n") for line in lines: data = line.split() if len(data) == 0: continue self.timer.log(0, "[VPS] " + line) if data[0] == "RUNNING_STATUS": if data[1] == "0": # undefined if data[2] == "FOLLOWING": data[1] = "1" else: data[1] = "4" if data[1] == "1": # not running # Wenn der Eintrag im Following (Section_Number = 1) ist, # dann nicht beenden (Sendung begann noch gar nicht) if data[2] == "FOLLOWING": self.activate_autoincrease() elif self.timer.state == TimerEntry.StateRunning and not self.pausing and not self.set_next_event(): self.stop_recording() self.dont_restart_program = True self.program_abort() elif data[1] == "2": # starts in a few seconds self.activate_autoincrease() if self.timer.state == TimerEntry.StateWaiting: self.session.nav.RecordTimer.doActivate(self.timer) elif data[1] == "3": # pausing self.pausing = True if self.timer.state == TimerEntry.StateRunning: self.activate_autoincrease() elif data[1] == "4": # running self.pausing = False if self.timer.state == TimerEntry.StateRunning: self.activate_autoincrease() elif self.timer.state == TimerEntry.StateWaiting or self.timer.state == TimerEntry.StatePrepared: # setze Startzeit auf jetzt self.timer.begin = int(time()) self.session.nav.RecordTimer.timeChanged(self.timer) self.activate_autoincrease() self.program_abort() self.stop_simulation() vps_timers.checksoon(2000) # Programm neu starten elif data[1] == "5": # service off-air self.timer.vpsplugin_overwrite = False if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False elif data[0] == "EVENT_ENDED": if not self.set_next_event(): if self.timer.state == TimerEntry.StateRunning: self.stop_recording() self.program_abort() self.stop_simulation() elif data[0] == "OTHER_TS_RUNNING_STATUS": if self.timer.state == TimerEntry.StateWaiting: self.timer.start_prepare = int(time()) self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() vps_timers.checksoon(2000) # PDC elif data[0] == "PDC_FOUND_EVENT_ID": self.found_pdc = True self.timer.eit = int(data[1]) epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, self.timer.eit) if evt: self.timer.name = evt.getEventName() self.timer.description = evt.getShortDescription() self.program_abort() vps_timers.checksoon(500) elif data[0] == "FOUND_EVENT_ON_SCHEDULE": starttime = int(data[1]) duration = int(data[2]) # Soll die Sendung laut EPG erst nach dem Ende dieses Timers beginnen? if (not self.timer.vpsplugin_overwrite and (self.timer.end + 300) < starttime) or (self.timer.vpsplugin_overwrite and (self.timer.end + self.max_extending_timer - 1800) < starttime): if self.new_timer_copy is None: if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False self.copyTimer(starttime, duration) self.timer.log(0, "[VPS] copied this timer, since the event may start later than this timer ends") elif not self.activated_auto_increase: self.activate_autoincrease() elif data[0] == "EVENT_OVER" or data[0] == "CANNOT_FIND_EVENT": self.max_extending_timer = 2*3600 if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False elif data[0] == "PDC_MULTIPLE_FOUND_EVENT": self.check_and_add_event(int(data[1])) # Programm meldet, dass die EIT (present/following) des Senders offenbar # momentan fehlerhaft ist elif data[0] == "EIT_APPARENTLY_UNRELIABLE": if self.timer.vpsplugin_overwrite: self.timer.vpsplugin_overwrite = False self.timer.log(0, "[VPS] can't trust EPG currently, go to safe mode") def stop_recording(self): self.activated_auto_increase = False self.timer.autoincrease = False if self.timer.vpsplugin_overwrite: # Stopp nach margin_after seconds if config.plugins.vps.margin_after.value == 0: self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) else: self.timer.end = int(time()) + config.plugins.vps.margin_after.value self.session.nav.RecordTimer.timeChanged(self.timer) self.stop_simulation() else: new_end_time = int(time()) + config.plugins.vps.margin_after.value if new_end_time > self.timer.end: self.timer.end = new_end_time self.session.nav.RecordTimer.timeChanged(self.timer) def activate_autoincrease(self): if not self.activated_auto_increase: self.activated_auto_increase = True self.timer.autoincrease = True self.timer.autoincreasetime = 60 if self.org_timer_end == 0: self.org_timer_end = self.timer.end self.timer.log(0, "[VPS] enable autoincrease") if self.new_timer_copy is not None and (self.new_timer_copy in self.session.nav.RecordTimer.timer_list): self.new_timer_copy.afterEvent = AFTEREVENT.NONE self.new_timer_copy.dontSave = True NavigationInstance.instance.RecordTimer.removeEntry(self.new_timer_copy) self.new_timer_copy = None self.timer.log(0, "[VPS] delete timer copy") # Noch ein Event aufnehmen? def set_next_event(self): if not self.timer.vpsplugin_overwrite and len(self.next_events) > 0: if not self.activated_auto_increase: self.activate_autoincrease() (starttime, neweventid) = self.next_events.pop(0) self.timer.eit = neweventid self.dont_restart_program = False self.program_abort() self.timer.log(0, "[VPS] record now event_id "+ str(neweventid)) vps_timers.checksoon(3000) return True else: return False def program_abort(self): if self.program_running or self.program_try_search_running: #self.program.sendCtrlC() self.program.kill() self.program_running = False self.program_try_search_running = False self.timer.log(0, "[VPS] stop monitoring") def stop_simulation(self): if self.simulate_recordService: NavigationInstance.instance.stopRecordService(self.simulate_recordService) self.simulate_recordService = None self.timer.log(0, "[VPS] stop RecordService (simulation)") def check_and_add_event(self, neweventid): if not config.plugins.vps.allow_seeking_multiple_pdc.value: return epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, neweventid) if evt: evt_begin = evt.getBeginTime() + 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 if evt_begin < self.timer.begin: return for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - evt_begin) > 3600*2: break compareString = checktimer.service_ref.ref.toCompareString() if compareString == self.timer.service_ref.ref.toCompareString() or compareString == self.rec_ref.toCompareString(): if checktimer.eit == neweventid: return if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if checktimer.vpsplugin_enabled is None or not checktimer.vpsplugin_enabled: return # manuell angelegter Timer mit VPS if checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = neweventid checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription() checktimer.vpsplugin_time = None checktimer.log(0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)") return # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: check_already_existing = [x for (x,y) in self.next_events if y == neweventid] if len(check_already_existing) > 0: start = check_already_existing.pop() if start == evt_begin: return else: self.next_events.remove( (start, neweventid) ) self.timer.log(0, "[VPS] delete event_id "+ str(neweventid) +" because of delay "+ str(evt_begin - start)) self.next_events.append( (evt_begin, neweventid) ) self.next_events = sorted(self.next_events) self.timer.log(0, "[VPS] add event_id "+ str(neweventid)) else: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.dirname = self.timer.dirname newEntry.log(0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)") # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. res = NavigationInstance.instance.RecordTimer.record(newEntry) self.timer.log(0, "[VPS] added another timer, res "+ str(res)) def copyTimer(self, start, duration): starttime = start - config.recording.margin_before.value * 60 endtime = start + duration + config.recording.margin_after.value * 60 self.new_timer_copy = RecordTimerEntry(ServiceReference(self.rec_ref), starttime, endtime, self.timer.name, self.timer.description, self.timer.eit, False, False, AFTEREVENT.AUTO, False, self.timer.dirname, self.timer.tags) self.new_timer_copy.vpsplugin_enabled = True self.new_timer_copy.vpsplugin_overwrite = self.timer.vpsplugin_overwrite self.new_timer_copy.log(0, "[VPS] added this timer") NavigationInstance.instance.RecordTimer.record(self.new_timer_copy) # startet den Hintergrundprozess def program_do_start(self, mode): if self.program_running or self.program_try_search_running: self.program_abort() if mode == 1: self.demux = -1 current_service = NavigationInstance.instance.getCurrentService() if current_service: stream = current_service.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux == -1: return; self.program_try_search_running = True self.program_running = False mode_program = 1 else: self.program_try_search_running = False self.program_running = True mode_program = 0 sid = self.rec_ref.getData(1) tsid = self.rec_ref.getData(2) onid = self.rec_ref.getData(3) demux = "/dev/dvb/adapter0/demux" + str(self.demux) # PDC-Zeit? if (self.timer.name == "" or self.timer.eit is None) and self.timer.vpsplugin_time is not None and not self.found_pdc: mode_program += 2 day = strftime("%d", localtime(self.timer.vpsplugin_time)) month = strftime("%m", localtime(self.timer.vpsplugin_time)) hour = strftime("%H", localtime(self.timer.vpsplugin_time)) minute = strftime("%M", localtime(self.timer.vpsplugin_time)) cmd = vps_exe + " "+ demux +" "+ str(mode_program) +" "+ str(onid) +" "+ str(tsid) +" "+ str(sid) +" 0 "+ day +" "+ month +" "+ hour +" "+ minute self.timer.log(0, "[VPS] seek PDC-Time") self.program.execute(cmd) return cmd = vps_exe + " "+ demux +" "+ str(mode_program) +" "+ str(onid) +" "+ str(tsid) +" "+ str(sid) +" "+ str(self.timer.eit) self.timer.log(0, "[VPS] start monitoring running-status") self.program.execute(cmd) def program_start(self): self.demux = -1 if self.dont_restart_program: return self.rec_ref = self.timer.service_ref and self.timer.service_ref.ref if self.rec_ref and self.rec_ref.flags & eServiceReference.isGroup: self.rec_ref = getBestPlayableServiceReference(self.rec_ref, eServiceReference()) # recordService (Simulation) ggf. starten if self.timer.state == TimerEntry.StateWaiting: if self.simulate_recordService is None: if self.rec_ref: self.simulate_recordService = NavigationInstance.instance.recordService(self.rec_ref, True) if self.simulate_recordService: res = self.simulate_recordService.start() self.timer.log(0, "[VPS] start recordService (simulation) " + str(res)) if res != 0 and res != -1: # Fehler aufgetreten (kein Tuner frei?) NavigationInstance.instance.stopRecordService(self.simulate_recordService) self.simulate_recordService = None # in einer Minute ggf. nochmal versuchen if 60 < self.nextExecution: self.nextExecution = 60 # Bei Overwrite versuchen ohne Fragen auf Sender zu schalten if self.timer.vpsplugin_overwrite == True: cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference() if cur_ref and not cur_ref.getPath() and self.rec_ref.toCompareString() != cur_ref.toCompareString(): self.timer.log(9, "[VPS-Plugin] zap without asking (simulation)") Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20) NavigationInstance.instance.playService(self.rec_ref) if 3 < self.nextExecution: self.nextExecution = 3 else: # ansonsten versuchen auf dem aktuellen Transponder/Kanal nach Infos zu suchen if not self.program_try_search_running: self.program_do_start(1) else: # Simulation hat geklappt if 1 < self.nextExecution: self.nextExecution = 1 else: # Simulation läuft schon # hole Demux stream = self.simulate_recordService.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux == -1: # ist noch nicht soweit(?), in einer Sekunde erneut versuchen if 1 < self.nextExecution: self.nextExecution = 1 else: self.program_do_start(0) elif self.timer.state == TimerEntry.StatePrepared or self.timer.state == TimerEntry.StateRunning: stream = self.timer.record_service.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux != -1: self.program_do_start(0) # überprüft, ob etwas zu tun ist und gibt die Sekunden zurück, bis die Funktion # spätestens wieder aufgerufen werden sollte # oder -1, um vps_timer löschen zu lassen def check(self): # Simulation ggf. stoppen if self.timer.state > TimerEntry.StateWaiting and self.simulate_recordService: self.stop_simulation() # VPS wurde wieder deaktiviert oder Timer wurde beendet if self.timer is None or self.timer.state == TimerEntry.StateEnded or self.timer.cancelled: self.program_abort() self.stop_simulation() return -1 if self.timer.vpsplugin_enabled == False or config.plugins.vps.enabled.value == False: if self.activated_auto_increase: self.timer.autoincrease = False self.program_abort() self.stop_simulation() return -1 self.nextExecution = 180 if config.plugins.vps.initial_time.value < 2 and self.timer.vpsplugin_overwrite: initial_time = 120 else: initial_time = config.plugins.vps.initial_time.value * 60 if self.timer.vpsplugin_overwrite == True: if self.timer.state == TimerEntry.StateWaiting or self.timer.state == TimerEntry.StatePrepared: # Startzeit verschieben if (self.timer.begin - 60) < time(): if self.org_timer_begin == 0: self.org_timer_begin = self.timer.begin elif (self.org_timer_begin + self.max_extending_timer) < time(): # Sendung begann immer noch nicht -> abbrechen self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() self.timer.log(0, "[VPS] abort timer, waited enough to find Event-ID") return -1 self.timer.begin += 60 if (self.timer.end - self.timer.begin) < 300: self.timer.end += 180 # auf Timer-Konflikt prüfen timersanitycheck = TimerSanityCheck(self.session.nav.RecordTimer.timer_list, self.timer) if not timersanitycheck.check(): self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() self.timer.log(0, "[VPS] abort timer due to TimerSanityCheck") return -1 self.session.nav.RecordTimer.timeChanged(self.timer) if 30 < self.nextExecution: self.nextExecution = 30 # Programm starten if not self.program_running: if self.timer.state == TimerEntry.StateRunning: self.program_start() elif initial_time > 0: if (self.timer.begin - initial_time) <= time(): self.program_start() else: n = self.timer.begin - initial_time - time() if n < self.nextExecution: self.nextExecution = n if self.timer.state == TimerEntry.StateRunning: if self.activated_auto_increase and self.org_timer_end != 0 and (self.org_timer_end + (4*3600)) < time(): # Aufnahme läuft seit 4 Stunden im Autoincrease -> abbrechen self.timer.autoincrease = False self.activated_auto_increase = False self.dont_restart_program = True self.program_abort() self.stop_simulation() self.timer.log(0, "[VPS] stop recording, too much autoincrease") return self.nextExecution
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Workaround to allow search for umlauts if we know the encoding #match = timer.match match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": epgmatches = [] mask = (eServiceReference.isMarker | eServiceReference.isDirectory) casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() # Service filter defined # Search only using the specified services test = [(service, 0, -1, -1) for service in timer.services] for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory and not s.flags & eServiceReference.isInvisible: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 31) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet'%(service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 2500, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() doLog("[AutoTimer] Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc and timer.descShortEqualExt: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldEntry = None oldExists = False allow_modify = True newAT = None # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") newEntry = rtimer oldEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8) or timeSimilarityPercent(rtimer, evtBegin, evtEnd, timer) > 80: oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") newEntry = rtimer oldEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) if allow_modify: if self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit, base_timer=timer): msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 else: msg = "[AutoTimer] AutoTimer modification not allowed for timer %s because conflicts or double timer." % (newEntry.name) doLog(msg) if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] conflict for modification timer %s detected return to old timer" % (newEntry.name)) continue else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) continue else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newAT = True msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if 'AutoTimer' not in tags: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists and newAT is None: if self.isResolvedConflict(newEntry): recordHandler.timeChanged(newEntry) else: if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] rechecking - conflict for timer %s detected return to old timer" % (newEntry.name)) continue elif newAT: newAT = newEntry conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # add new timer in AT timer list atDoubleTimer = False refstr = ':'.join(newEntry.service_ref.ref.toString().split(':')[:11]) for at in addNewTimers: needed_ref = ':'.join(at.service_ref.ref.toString().split(':')[:11]) == refstr if needed_ref and at.eit == newEntry.eit and (newEntry.begin < at.begin <= newEntry.end or at.begin <= newEntry.begin <= at.end): atDoubleTimer = True break if atDoubleTimer: doLog("[AutoTimer] ignore double new auto timer %s." % newEntry.name) continue else: addNewTimers.append(newEntry) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() else: doLog("[AutoTimer] ignore double timer %s." % newEntry.name) # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): recordHandler.timeChanged(newEntry) else: # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) elif newAT != newEntry and newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): if not self.isResolvedConflict(newEntry): newEntry.disabled = True recordHandler.timeChanged(newEntry) doLog("[AutoTimer] Unknown conflict, disable this timer %s." % newEntry.name) return (new, modified)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], timer.match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("possible epgmatch %s" % (name)) doLog("Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() doLog("Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset if ServiceRecordingSettings: begin -= ServiceRecordingSettings.instance.getMarginBefore(eserviceref) end += ServiceRecordingSettings.instance.getMarginAfter(eserviceref) else: begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True doLog("We found a timer based on time guessing") newEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("We found a timer (any service) with same description, skipping event") break if oldExists: doLog("Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("Won't modify existing timer because either no modification allowed or repeated timer") continue if hasattr(newEntry, "isAutoTimer"): msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 if allow_modify: self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if TAG not in tags: tags.append(TAG) if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 skipped = 0 # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value match = timer.match if timer.searchType == "description": epgmatches = [] mask = (eServiceReference.isMarker | eServiceReference.isDirectory) casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() # Service filter defined # Search only using the specified services test = [(service, 0, -1, -1) for service in timer.services] for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) mask = eServiceReference.isDirectory if config.usage.multibouquet.value: bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & mask: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) else: info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) # Get all services mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 3000, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches preveit = False for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: print("[AutoTimer] Could not create Event!") continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it # if begin < time() + 60: # print ("[AutoTimer] Skipping " + name + " because it starts in less than 60 seconds") # skipped += 1 # continue # Set short description to equal extended description if it is empty. if not shortdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or (not similarTimer and (\ timer.checkTimespan(timestamp) \ or timer.checkTimeframe(begin) \ )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek): continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name)) if simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkSimilarity(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc")): print("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if (rtimer.eit == eit or config.plugins.autotimer.try_guessing.getValue()) and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated: print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") break if eit == preveit: break if (evtBegin - (config.recording.margin_before.getValue() * 60) != rtimer.begin) or (evtEnd + (config.recording.margin_after.getValue() * 60) != rtimer.end) or (shortdesc != rtimer.description): if rtimer.isAutoTimer and eit == rtimer.eit: print ("[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) # rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) preveit = eit else: if config.plugins.autotimer.refresh.getValue() != "all": print("[AutoTimer] Won't modify existing timer because it's no timer set by us") break rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, rtimer.name)) newEntry = rtimer modified += 1 self.modifyTimer(rtimer, name, shortdesc, begin, end, serviceref, eit) # rtimer.log(501, "[AutoTimer] AutoTimer modified timer: %s ." % (rtimer.name)) break else: print ("[AutoTimer] Skipping timer because it has not changed.") skipped += 1 break elif timer.avoidDuplicateDescription >= 1 and not rtimer.disabled: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): print("[AutoTimer] We found a timer with similar description, skipping event") oldExists = True break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDoubleTimers(timer, name, rtimer.name, begin, rtimer.begin, end, rtimer.end ): oldExists = True # print("[AutoTimer] We found a timer with same StartTime, skipping event") break if timer.avoidDuplicateDescription >= 2: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True print("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: continue if timer.checkCounter(timestamp): print("[AutoTimer] Not adding new timer because counter is depleted.") continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString)) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) print("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkSimilarity(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: print("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString)) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) self.result=(new, modified, skipped) self.completed.append(timer.name) sleep(0.5)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # enable multiple timer if services or bouquets specified (eg. recording the same event on sd service and hd service) enable_multiple_timer = ((timer.services and 's' in config.plugins.autotimer.enable_multiple_timer.value or False) or (timer.bouquets and 'b' in config.plugins.autotimer.enable_multiple_timer.value or False)) # Workaround to allow search for umlauts if we know the encoding match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "favoritedesc": epgmatches = [] casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() test = [] if timer.services or timer.bouquets: if timer.services: test = [(service, 0, -1, -1) for service in timer.services] if timer.bouquets: for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if services: while True: service = services.getNext() if not service.valid(): break playable = not (service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or (service.flags & eServiceReference.isNumberedMarker) if playable: test.append((service.toString(), 0, -1, -1)) else: # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory and not s.flags & eServiceReference.isInvisible: info = serviceHandler.info(s) if info: bouquetlist.append(s) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 31) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet' % (service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append(bouquetroot) if bouquetlist: for bouquet in bouquetlist: if not bouquet.valid(): break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if services: while True: service = services.getNext() if not service.valid(): break playable = not (service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or (service.flags & eServiceReference.isNumberedMarker) if playable: test.append((service.toString(), 0, -1, -1)) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) or match in (extdesc if casesensitive else extdesc.lower()) or match in (name if casesensitive else name.lower()): epgmatches.append((serviceref, eit, name, begin, duration, shortdesc, extdesc)) else: # Search EPG, default to empty list epgmatches = epgcache.search(('RITBDSE', int(config.plugins.autotimer.max_search_events_match.value), typeMap[timer.searchType], match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) dayofweek_exclude = timer.exclude[3] if dayofweek_exclude: dayofweek_exclude_values = dayofweek_exclude[:] if "weekend" in dayofweek_exclude_values: dayofweek_exclude_values.extend(("5", "6")) if "weekday" in dayofweek_exclude_values: dayofweek_exclude_values.extend(("0", "1", "2", "3", "4")) dayofweek_include = timer.include[3] if dayofweek_include: dayofweek_include_values = dayofweek_include[:] if "weekend" in dayofweek_include_values: dayofweek_include_values.extend(("5", "6")) if "weekday" in dayofweek_include_values: dayofweek_include_values.extend(("0", "1", "2", "3", "4")) # Loop over all EPG matches for idx, (serviceref, eit, name, begin, duration, shortdesc, extdesc) in enumerate(epgmatches): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % serviceref) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Try to determine real service (we always choose the last one) #n = evt.getNumOfLinkageServices() #if n > 0: # i = evt.getLinkageService(eserviceref, n-1) # serviceref = i.toString() # doLog("[AutoTimer] Serviceref2 %s" % serviceref) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc and timer.descShortEqualExt and extdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue current_dayofweek = tdow = timestamp.tm_wday if (timer.timespan[0] != None) and timer.timespan[2]: begin_offset = 60 * timestamp.tm_hour + timestamp.tm_min timer_offset = 60 * timer.timespan[0][0] + timer.timespan[0][1] if begin_offset < timer_offset: tdow = (tdow - 1) % 7 dayofweek = str(tdow) # Update dayofweek when programmes that cross midnight and have a dayofweek filter if str(current_dayofweek) == dayofweek and (dayofweek_exclude or dayofweek_include): end_timestamp = localtime(end) end_dayofweek = str(end_timestamp.tm_wday) if dayofweek != end_dayofweek: if dayofweek_exclude: if dayofweek in dayofweek_exclude_values: if not end_dayofweek in dayofweek_exclude_values: doLog("[AutoTimer] [AutoTimer] Update dayofweek by reason of exclude dayofweek filter") dayofweek = end_dayofweek if dayofweek_include and dayofweek != end_dayofweek: if not dayofweek in dayofweek_include_values: if end_dayofweek in dayofweek_include_values: doLog("[AutoTimer] [AutoTimer] Update dayofweek by reason of include dayofweek filter") dayofweek = end_dayofweek # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldEntry = None oldExists = False allow_modify = True newAT = None # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest, True) if sp and type(sp) in (tuple, list) and len(sp) > 3: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest, True) if sp and type(sp) in (tuple, list) and len(sp) > 3: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) offsetBegin = timer.offset[0] offsetEnd = timer.offset[1] else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 offsetBegin = config.recording.margin_before.value * 60 offsetEnd = config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin evtEnd = evtBegin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkSimilarity(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc"), isMovie=True): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): try: # protect against vps plugin not being present vps_changed = hasVps and (rtimer.vpsplugin_enabled != timer.vps_enabled or rtimer.vpsplugin_overwrite != timer.vps_overwrite) except: vps_changed = False time_changed = (evtBegin - offsetBegin != rtimer.begin) or (evtEnd + offsetEnd != rtimer.end) desc_changed = (timer.avoidDuplicateDescription >= 1 and shortdesc and rtimer.description and shortdesc != rtimer.description) or (timer.avoidDuplicateDescription >= 2 and extdesc and rtimer.extdesc and extdesc != rtimer.extdesc) if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") if time_changed or desc_changed or vps_changed: newEntry = rtimer oldEntry = [rtimer.name, rtimer.description, rtimer.extdesc, rtimer.begin, rtimer.end, rtimer.service_ref, rtimer.eit, rtimer.disabled] break elif config.plugins.autotimer.try_guessing.value: if timeSimilarityPercent(rtimer, evtBegin, evtEnd, timer) > 80: oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") if time_changed or desc_changed or vps_changed: newEntry = rtimer oldEntry = [rtimer.name, rtimer.description, rtimer.extdesc, rtimer.begin, rtimer.end, rtimer.service_ref, rtimer.eit, rtimer.disabled] break if oldExists is None and timer.avoidDuplicateDescription >= 1 and not rtimer.disabled: # searchForDuplicateDescription is 1 - check short description / searchForDuplicateDescription is 2 - check extended description if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles for rtimer in chain.from_iterable(itervalues(timerdict)): if not rtimer.disabled: if self.checkDoubleTimers(timer, name, rtimer.name, begin, rtimer.begin, end, rtimer.end, serviceref, rtimer.service_ref.ref.toString(), enable_multiple_timer): oldExists = True print("[AutoTimer] We found a timer with same start time, skipping event") break if timer.avoidDuplicateDescription >= 2: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # if set option for check/save timer in filterlist and only if not found an existing timer isnewFilterEntry = False if (config.plugins.autotimer.series_save_filter.value or timer.series_save_filter) and not oldExists: if timer.series_labeling and sp_getSeasonEpisode is not None: if sp and type(sp) in (tuple, list) and len(sp) == 4: ret = self.addToFilterfile(str(sp[0]), begin, simulateOnly) if sp and type(sp) in (tuple, list) and len(sp) > 3: filter_title = str(sp[0]) if len(sp) > 4: filter_title = "{series:s} - S{season:02d}E{rawepisode:s} - {title:s}".format(**sp[4]) ret = self.addToFilterfile(filter_title, begin, simulateOnly, str(sp[0])) if ret: if simulateOnly: doLog("[AutoTimer SeriesPlugin] only simulate - new Timer would be saved in autotimer_filter") else: doLog("[AutoTimer SeriesPlugin] new Timer saved in autotimer_filter") isnewFilterEntry = True else: skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) changed = newEntry.begin != begin or newEntry.end != end or newEntry.name != name if allow_modify: if oldExists and newEntry.service_ref.ref.toString() == serviceref and newEntry.eit == eit and newEntry.name == name and newEntry.begin < begin and newEntry.end < end and (0 < begin - newEntry.end <= 600): begin = newEntry.begin doLog("[AutoTimer] This same eit and different times end - update only end") if self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit, base_timer=timer): msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) if changed: self.addToSearchLogfile(newEntry, "#", simulateOnly) modified += 1 else: msg = "[AutoTimer] AutoTimer modification not allowed for timer %s because conflicts or double timer." % (newEntry.name) doLog(msg) if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] conflict for modification timer %s detected return to old timer" % (newEntry.name)) continue else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) continue else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newAT = True msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) msg = "[AutoTimer] Timer start on: %s" % ctime(begin) doLog(msg) newEntry.log(509, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Mark this entry as timer name newEntry.flags.add(stringToXML(timer.name)) # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if 'AutoTimer' not in tags: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists and newAT is None: if self.isResolvedConflict(newEntry): recordHandler.timeChanged(newEntry) else: if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] rechecking - conflict for timer %s detected return to old timer" % (newEntry.name)) continue elif newAT: newAT = newEntry conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # add new timer in AT timer list atDoubleTimer = False refstr = ':'.join(newEntry.service_ref.ref.toString().split(':')[:11]) for at in addNewTimers: needed_ref = ':'.join(at.service_ref.ref.toString().split(':')[:11]) == refstr if needed_ref and at.eit == newEntry.eit and (newEntry.begin < at.begin <= newEntry.end or at.begin <= newEntry.begin <= at.end): atDoubleTimer = True break if atDoubleTimer: doLog("[AutoTimer] ignore double new auto timer %s." % newEntry.name) continue else: addNewTimers.append(newEntry) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts and not timer.hasOffset() and not config.recording.margin_before.value and not config.recording.margin_after.value and len(conflicts) > 1: change_end = change_begin = False conflict_begin = conflicts[1].begin conflict_end = conflicts[1].end if conflict_begin == newEntry.end: newEntry.end -= 30 change_end = True elif newEntry.begin == conflict_end: newEntry.begin += 30 change_begin = True if change_end or change_begin: conflicts = recordHandler.record(newEntry) if conflicts: if change_end: newEntry.end += 30 elif change_begin: newEntry.begin -= 30 else: doLog("[AutoTimer] The conflict is resolved by offset time begin/end (30 sec) for %s." % newEntry.name) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[(i + idx + 1) % lepgm] if self.checkSimilarity(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): new += 1 if isnewFilterEntry: self.addToSearchLogfile(newEntry, "++", simulateOnly) else: self.addToSearchLogfile(newEntry, "+", simulateOnly) newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() else: doLog("[AutoTimer] ignore double timer %s." % newEntry.name) # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): recordHandler.timeChanged(newEntry) else: # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) elif newAT != newEntry and newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): if not self.isResolvedConflict(newEntry): newEntry.disabled = True recordHandler.timeChanged(newEntry) doLog("[AutoTimer] Unknown conflict, disable this timer %s." % newEntry.name) return (new, modified)
def addTimer(serviceref, begin, end, name, description, eit, disabled, dirname, vpsSettings, tags, logentries=None): recordHandler = NavigationInstance.instance.RecordTimer # config.plugins.serienRec.seriensubdir # if not dirname: # try: # dirname = config.plugins.serienRec.savetopath.value # except Exception: # dirname = preferredTimerPath() try: try: timer = RecordTimerEntry( ServiceReference(serviceref), begin, end, name, description, eit, disabled=disabled, justplay=config.plugins.serienRec.justplay.value, zapbeforerecord=config.plugins.serienRec.zapbeforerecord.value, justremind=config.plugins.serienRec.justremind.value, afterEvent=int(config.plugins.serienRec.afterEvent.value), dirname=dirname) except Exception: sys.exc_clear() timer = RecordTimerEntry( ServiceReference(serviceref), begin, end, name, description, eit, disabled, config.plugins.serienRec.justplay.value | config.plugins.serienRec.justremind.value, afterEvent=int(config.plugins.serienRec.afterEvent.value), dirname=dirname, tags=None) timer.repeated = 0 # Add tags timerTags = timer.tags[:] timerTags.append('SerienRecorder') if len(tags) != 0: timerTags.extend(tags) timer.tags = timerTags # If eit = 0 the VPS plugin won't work properly for this timer, so we have to disable VPS in this case. if SerienRecorder.VPSPluginAvailable and eit is not 0: timer.vpsplugin_enabled = vpsSettings[0] timer.vpsplugin_overwrite = timer.vpsplugin_enabled and (not vpsSettings[1]) if logentries: timer.log_entries = logentries timer.log(0, "[SerienRecorder] Timer angelegt") conflicts = recordHandler.record(timer) if conflicts: errors = [] for conflict in conflicts: errors.append(conflict.name) return { "result": False, "message": "In Konflikt stehende Timer vorhanden! %s" % " / ".join(errors) } except Exception, e: print "[%s] <%s>" % (__name__, e) return { "result": False, "message": "Timer konnte nicht angelegt werden '%s'!" % e }
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], timer.match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, (serviceref, eit, name, begin, duration, shortdesc, extdesc) in enumerate(epgmatches): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("possible epgmatch %s" % (name)) doLog("Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("Could not create Event!") skipped.append( (name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n - 1) serviceref = i.toString() doLog("Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog( "Skipping an event because it starts in less than 60 seconds" ) skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog( "Skipping an event because of maximum days in future is reached" ) skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("Skipping an event because of check services") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("Skipping an event because of duration check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("Skipping an event because of timestamp check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("Skipping an event because of timeframe check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("Skipping an event because of filter check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset if ServiceRecordingSettings: begin -= ServiceRecordingSettings.instance.getMarginBefore( eserviceref) end += ServiceRecordingSettings.instance.getMarginAfter( eserviceref) else: begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc")): doLog( "We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("Skipping an event because movie already exists") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] rend = rtimer.end - timer.offset[1] else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration / 10) * 8): oldExists = True doLog("We found a timer based on time guessing") newEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog( "We found a timer (similar service) with same description, skipping event" ) break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog( "Skipping an event because a timer on same service exists" ) skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable(itervalues(timerdict)): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog( "We found a timer (any service) with same description, skipping event" ) break if oldExists: doLog( "Skipping an event because a timer on any service exists" ) skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("Not adding new timer because counter is depleted.") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # if set option for check/save timer in filterlist and only if not found an existing timer isnewFilterEntry = False if (config.plugins.autotimer.series_save_filter.value or timer.series_save_filter) and oldExists == False: # only if use series_labeling and if sp_getSeasonEpisode was succesful if timer.series_labeling and sp_getSeasonEpisode is not None: if sp and type(sp) in (tuple, list) and len(sp) == 4: ret = self.addToFilterfile(str(sp[0]), begin, simulateOnly) if ret: if simulateOnly: doLog( "only simulate - new Timer would be saved in autotimer_filter" ) else: doLog("new Timer saved in autotimer_filter") isnewFilterEntry = True else: skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog( "Won't modify existing timer because either no modification allowed or repeated timer" ) continue if hasattr(newEntry, "isAutoTimer"): msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % ( timer.name) doLog(msg) newEntry.log(501, msg) elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % ( timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog( "Won't modify existing timer because it's no timer set by us" ) continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % ( timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) if newEntry.begin != begin or newEntry.end != end or newEntry.name != name: modified += 1 #self.addToSearchLogfile(newEntry,"#", simulateOnly) if allow_modify: modified_for_searchlog = True if newEntry.begin != begin or newEntry.end != end or newEntry.name != name else False self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) if modified_for_searchlog: self.addToSearchLogfile(newEntry, "#", simulateOnly) msg = "[AutoTimer] AutoTimer modified timer: %s ." % ( newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % ( newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % ( timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if TAG not in tags: tags.append(TAG) if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace( " ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % ( conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join([ "%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts ]) doLog("conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i + idx + 1) % lepgm] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True): # Check if the similar is already known if eitS not in similardict: doLog("Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append( (servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 if isnewFilterEntry: self.addToSearchLogfile(newEntry, "++", simulateOnly) else: self.addToSearchLogfile(newEntry, "+", simulateOnly) newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append( (name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append( (name, begin, end, serviceref, timer.name)) self.addToSearchLogfile(newEntry, "x", simulateOnly) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % ( conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Workaround to allow search for umlauts if we know the encoding #match = timer.match match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": epgmatches = [] mask = (eServiceReference.isMarker | eServiceReference.isDirectory) casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() # Service filter defined # Search only using the specified services test = [(service, 0, -1, -1) for service in timer.services] for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet'%(service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 2000, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) doLog("[AutoTimer] Serviceref2 %s" % (str(serviceref))) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True #doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True #doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") newEntry = rtimer break elif timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 if allow_modify: self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) #if renameTimer is not None and timer.series_labeling: # renameTimer(newEntry, name, evtBegin, evtEnd) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
def JobStart(self): for timer in self.timers: if timer.enabled and timer.name not in self.completed: # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value # Workaround to allow search for umlauts if we know the encoding match = timer.match match = match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": test = [] epgmatches = [] casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() #if timer.services or timer.bouquets: # Service filter defined # Search only using the specified services for service in timer.services: test.append( (service, 0, -1, -1 ) ) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for bouquet in timer.bouquets: services = self.serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: #else: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) mask = eServiceReference.isDirectory if config.usage.multibouquet.value: bouquets = self.serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & mask: info = self.serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) else: info = self.serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) # Get all services mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = self.serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = self.epgcache.lookupEvent( test ) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = self.epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Reset the the marked similar servicerefs self.similar.clear() # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): eserviceref = eServiceReference(serviceref) evt = self.epgcache.lookupEventId(eserviceref, eit) if not evt: print("[AutoTimer] Could not create Event!") continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: print("[AutoTimer] Skipping an event because it starts in less than 60 seconds") continue # Set short description to equal extended description if it is empty. if not shortdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore self.evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in self.similar: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if self.checkEvtLimit: if begin > self.evtLimit: continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or (not similarTimer and (\ timer.checkTimespan(timestamp) \ or timer.checkTimeframe(begin) \ )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek): continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) self.total += 1 # Append to timerlist and abort if simulating self.auto_timers.append((name, begin, end, serviceref, timer.name)) if self.simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in self.moviedict: self.addDirectoryToMovieDict(self.moviedict, dest, self.serviceHandler) for movieinfo in self.moviedict.get(dest, ()): if self.checkSimilarity(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): print("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in self.recorddict.get(serviceref, ()): if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated: print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") break if hasattr(rtimer, "isAutoTimer"): rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) else: if config.plugins.autotimer.refresh.value != "all": print("[AutoTimer] Won't modify existing timer because it's no timer set by us") break rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name)) newEntry = rtimer self.modified += 1 self.modifyTimer(rtimer, name, shortdesc, begin, end, serviceref) break elif timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True print("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(self.recorddict) ): if not rtimer.disabled: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True print("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: continue if timer.checkCounter(timestamp): print("[AutoTimer] Not adding new timer because counter is depleted.") continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: name = timer.name.strip() if name: name = name[0].upper() + name[1:].replace(" ", "_") tags.append(name) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? self.recordHandler.timeChanged(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) else: conflictString = "" if similarTimer: conflictString = self.similar[eit].conflictString newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString)) # Try to add timer conflicts = self.recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) print("[AutoTimer] conflict with %s detected" % (conflictString)) if conflicts and config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkSimilarity(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in self.similar: print("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString self.similar[eit] = newEntry self.similar[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = self.similar[eitS] break if conflicts is None: timer.decrementCounter() self.new += 1 newEntry.extdesc = extdesc self.recorddict[serviceref].append(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: self.similars.append((name, begin, end, serviceref, timer.name)) self.similar.clear() # Don't care about similar timers elif not similarTimer: self.conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString)) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = self.recordHandler.record(newEntry) sleep(1) # return (self.total, self.new, self.modified, self.auto_timers, self.conflicting, self.similars) self.completed.append(timer.name) break
def parseEPG(self, simulateOnly = False): if NavigationInstance.instance is None: print("[AutoTimer] Navigation is not available, can't parse EPG") return (0, 0, 0, [], [], []) total = 0 new = 0 modified = 0 timers = [] conflicting = [] similar = defaultdict(list) # Contains the the marked similar eits and the conflicting strings similars = [] # Contains the added similar timers # NOTE: the config option specifies "the next X days" which means today (== 1) + X delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1) evtLimit = mktime((date.today() + delta).timetuple()) checkEvtLimit = delta.days > 1 del delta # Read AutoTimer configuration self.readXml() # Get E2 instances epgcache = eEPGCache.getInstance() serviceHandler = eServiceCenter.getInstance() recordHandler = NavigationInstance.instance.RecordTimer # Save Recordings in a dict to speed things up a little # We include processed timers as we might search for duplicate descriptions # The recordict is always filled #Question: It might be better to name it timerdict #Question: Move to a separate function getTimerDict() #Note: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match recorddict = defaultdict(list) for timer in chain(recordHandler.timer_list, recordHandler.processed_timers): if timer and timer.service_ref: if timer.eit is not None: event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit) extdesc = event and event.getExtendedDescription() or '' timer.extdesc = extdesc elif not hasattr(timer, 'extdesc'): timer.extdesc = '' recorddict[str(timer.service_ref)].append(timer) # Create dict of all movies in all folders used by an autotimer to compare with recordings # The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings #Question: It might be better to name it recorddict moviedict = defaultdict(list) # Iterate Timer for timer in self.getEnabledTimerList(): # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value # Workaround to allow search for umlauts if we know the encoding match = timer.match if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": test = [] epgmatches = [] casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() #if timer.services or timer.bouquets: # Service filter defined # Search only using the specified services for service in timer.services: test.append( (service, 0, -1, -1 ) ) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: #else: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) mask = eServiceReference.isDirectory if config.usage.multibouquet.value: bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & mask: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) else: info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) # Get all services mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent( test ) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search(('RITBDSE', 1000, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Reset the the marked similar servicerefs similar.clear() # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): #Question: Do we need this? #Question: Move to separate function getRealService() eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: print("[AutoTimer] Could not create Event!") continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: print("[AutoTimer] Skipping an event because it starts in less than 60 seconds") continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similar: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches to not care about the day/time they are on, so ignore them if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or (not similarTimer and (\ timer.checkTimespan(timestamp) \ or timer.checkTimeframe(begin) \ )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek): continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) total += 1 # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name)) if simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: #Question: Move to a separate function getRecordDict() print("[AutoTimer] listing of movies in " + dest + " failed") event = info.getEvent(movieref) if event is None: continue "shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription), "extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string? for movieinfo in moviedict.get(dest, ()): if movieinfo.get("name") == name \ and movieinfo.get("shortdesc") == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match extdescM = movieinfo.get("extdesc") if ( len(extdesc) == len(extdescM) and extdesc == extdescM ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescM).ratio() ): print("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in recorddict.get(serviceref, ()): if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated: print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") break if hasattr(rtimer, "isAutoTimer"): rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) else: if config.plugins.autotimer.refresh.value != "all": print("[AutoTimer] Won't modify existing timer because it's no timer set by us") break rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name)) newEntry = rtimer modified += 1 # Modify values saved in timer newEntry.name = name newEntry.description = shortdesc newEntry.begin = int(begin) newEntry.end = int(end) newEntry.service_ref = ServiceReference(serviceref) break elif timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled \ and rtimer.name == name \ and rtimer.description == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ): oldExists = True print("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(recorddict) ): if not rtimer.disabled \ and rtimer.name == name \ and rtimer.description == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ): oldExists = True print("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: continue if timer.checkCounter(timestamp): print("[AutoTimer] Not adding new timer because counter is depleted.") continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.tags = timer.tags newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similar[eit].conflictString newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString)) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) print("[AutoTimer] conflict with %s detected" % (conflictString)) if conflicts and config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if shortdesc == shortdescS: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(extdescS) and extdesc == extdescS ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescS).ratio() ): # Check if the similar is already known if eitS not in similar: print("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similar[eit] = newEntry similar[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time break else: similarTimer = False newEntry = similar[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc recorddict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similar.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString)) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (total, new, modified, timers, conflicting, similars) # Supporting functions def addDirectoryToMovieDict(self, moviedict, dest, serviceHandler): movielist = serviceHandler.list(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest)) if movielist is None: print("[AutoTimer] listing of movies in " + dest + " failed") else: append = moviedict[dest].append while 1: movieref = movielist.getNext() if not movieref.valid(): break if movieref.flags & eServiceReference.mustDescent: continue info = serviceHandler.info(movieref) if info is None: continue event = info.getEvent(movieref) if event is None: continue append({ "name": info.getName(movieref), "shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription), "extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string? }) def checkSimilarity(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2): foundTitle = (name1 == name2) foundShort = (shortdesc1 == shortdesc2) if timer.searchForDuplicateDescription > 0 else True foundExt = True # NOTE: only check extended if short description already is a match because otherwise # it won't evaluate to True anyway if timer.searchForDuplicateDescription == 2 and foundShort: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match foundExt = ( len(extdesc1) == len(extdesc2) and extdesc1 == extdesc2 ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc1, extdesc2).ratio() ) return (total, new, modified, timers, conflicting, similars)
def parseEPG(self, simulateOnly = False): if NavigationInstance.instance is None: print "[AutoTimer] Navigation is not available, can't parse EPG" return (0, 0, 0, [], []) total = 0 new = 0 modified = 0 timers = [] conflicting = [] # NOTE: the config option specifies "the next X days" which means today (== 1) + X delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1) evtLimit = mktime((date.today() + delta).timetuple()) checkEvtLimit = delta.days > 1 del delta self.readXml() # Save Recordings in a dict to speed things up a little # We include processed timers as we might search for duplicate descriptions recorddict = {} for rtimer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers: if not rtimer.disabled: recorddict.setdefault(str(rtimer.service_ref), []).append(rtimer) # Iterate Timer for timer in self.getEnabledTimerList(): # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value # Workaround to allow search for umlauts if we know the encoding match = timer.match if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass # Search EPG, default to empty list ret = self.epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or () for serviceref, eit in ret: eserviceref = eServiceReference(serviceref) evt = self.epgcache.lookupEventId(eserviceref, eit) if not evt: print "[AutoTimer] Could not create Event!" continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() # Gather Information evtInfo = self.normalizeEvent(evt) evtBegin = begin = evt.getBeginTime() duration = evt.getDuration() evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: print "[AutoTimer] Skipping an event because it starts in less than 60 seconds" continue # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check Duration, Timespan, Timeframe and Excludes if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or timer.checkTimespan(timestamp) \ or timer.checkTimeframe(begin) \ or timer.checkFilter( evtInfo.name, evtInfo.shortDescription, evtInfo.extendedDescription, str(timestamp.tm_wday) ): continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) total += 1 # Append to timerlist and abort if simulating timers.append((evtInfo.name, begin, end, serviceref, timer.name)) if simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3 and self.checkMovies(evtInfo, dest): continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in recorddict.get(serviceref, ()): if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none": print "[AutoTimer] Won't modify existing timer because no modification allowed" break if rtimer.repeated: print "[AutoTimer] Won't modify existing timer because repeated timer" break if hasattr(rtimer, "isAutoTimer"): rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name,)) else: if config.plugins.autotimer.refresh.value != "all": print "[AutoTimer] Won't modify existing timer because it's no timer set by us" break rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name,)) newEntry = rtimer modified += 1 # Modify values saved in timer newEntry.name = evtInfo.name newEntry.description = evtInfo.shortDescription newEntry.begin = int(begin) newEntry.end = int(end) newEntry.service_ref = ServiceReference(serviceref) break elif timer.avoidDuplicateDescription >= 1 and self.normalizeRecordTimer(rtimer) == evtInfo: oldExists = True print "[AutoTimer] We found a timer with same service and description, skipping event" break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2 and self.isEventInList(evtInfo, recorddict, self.normalizeRecordTimer): print "[AutoTimer] We found a timer with same description, skipping event" continue if timer.checkCounter(timestamp): print "[AutoTimer] Not adding new timer because counter is depleted." continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, evtInfo.name, evtInfo.shortDescription, eit) newEntry.log(500, "[AutoTimer] Adding new timer based on AutoTimer %s." % (timer.name,)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.tags = timer.tags newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? NavigationInstance.instance.RecordTimer.timeChanged(newEntry) else: conflicts = NavigationInstance.instance.RecordTimer.record(newEntry) if conflicts and config.plugins.autotimer.disabled_on_conflict.value: conflictString = ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString,)) del conflictString newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = NavigationInstance.instance.RecordTimer.record(newEntry) conflicting.append((evtInfo.name, begin, end, serviceref, timer.name)) if conflicts is None: timer.decrementCounter() new += 1 recorddict.setdefault(serviceref, []).append(newEntry) else: conflicting.append((evtInfo.name, begin, end, serviceref, timer.name)) return (total, new, modified, timers, conflicting)
class vps_timer: def __init__(self, timer, session): self.timer = timer self.session = session self.program = eConsoleAppContainer() self.program.dataAvail.append(self.program_dataAvail) self.program.appClosed.append(self.program_closed) self.program_running = False self.program_try_search_running = False self.activated_auto_increase = False self.simulate_recordService = None self.demux = -1 self.rec_ref = None self.found_pdc = False self.dont_restart_program = False self.org_timer_end = 0 self.org_timer_begin = 0 self.max_extending_timer = 4 * 3600 self.next_events = [] self.new_timer_copy = None def program_closed(self, retval): self.timer.log(0, "[VPS] stop monitoring (process terminated)") if self.program_running or self.program_try_search_running: self.program_running = False self.program_try_search_running = False self.stop_simulation() def program_dataAvail(self, str): if self.timer is None or self.timer.state == TimerEntry.StateEnded or self.timer.cancelled: self.program_abort() self.stop_simulation() return if self.timer.vpsplugin_enabled == False or config.plugins.vps.enabled.value == False: if self.activated_auto_increase: self.timer.autoincrease = False self.program_abort() self.stop_simulation() return lines = str.split("\n") for line in lines: data = line.split() if len(data) == 0: continue self.timer.log(0, "[VPS] " + line) if data[0] == "RUNNING_STATUS": if data[1] == "0": # undefined if data[2] == "FOLLOWING": data[1] = "1" else: data[1] = "4" if data[1] == "1": # not running # Wenn der Eintrag im Following (Section_Number = 1) ist, # dann nicht beenden (Sendung begann noch gar nicht) if data[2] == "FOLLOWING": self.activate_autoincrease() else: if self.timer.state == TimerEntry.StateRunning and not self.set_next_event( ): self.activated_auto_increase = False self.timer.autoincrease = False if self.timer.vpsplugin_overwrite: # sofortiger Stopp self.timer.abort() self.session.nav.RecordTimer.doActivate( self.timer) self.stop_simulation() self.dont_restart_program = True self.program_abort() elif data[1] == "2": # starts in a few seconds self.activate_autoincrease() if self.timer.state == TimerEntry.StateWaiting: self.session.nav.RecordTimer.doActivate(self.timer) elif data[1] == "3": # pausing if self.timer.state == TimerEntry.StateRunning: self.activate_autoincrease() elif data[1] == "4": # running if self.timer.state == TimerEntry.StateRunning: self.activate_autoincrease() elif self.timer.state == TimerEntry.StateWaiting or self.timer.state == TimerEntry.StatePrepared: # setze Startzeit auf jetzt self.timer.begin = int(time()) self.session.nav.RecordTimer.timeChanged(self.timer) self.activate_autoincrease() self.program_abort() self.stop_simulation() vps_timers.checksoon(2000) # Programm neu starten elif data[1] == "5": # service off-air self.timer.vpsplugin_overwrite = False if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False elif data[0] == "EVENT_ENDED": if not self.set_next_event(): if self.timer.state == TimerEntry.StateRunning: self.activated_auto_increase = False self.timer.autoincrease = False if self.timer.vpsplugin_overwrite: # sofortiger Stopp self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) self.stop_simulation() self.program_abort() self.stop_simulation() elif data[0] == "OTHER_TS_RUNNING_STATUS": if self.timer.state == TimerEntry.StateWaiting: self.timer.start_prepare = int(time()) self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() vps_timers.checksoon(2000) # PDC elif data[0] == "PDC_FOUND_EVENT_ID": self.found_pdc = True self.timer.eit = int(data[1]) epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, self.timer.eit) if evt: self.timer.name = evt.getEventName() self.timer.description = evt.getShortDescription() self.program_abort() vps_timers.checksoon(500) elif data[0] == "FOUND_EVENT_ON_SCHEDULE": starttime = int(data[1]) duration = int(data[2]) # Soll die Sendung laut EPG erst nach dem Ende dieses Timers beginnen? if (not self.timer.vpsplugin_overwrite and (self.timer.end + 300) < starttime) or ( self.timer.vpsplugin_overwrite and (self.timer.end + self.max_extending_timer - 1800) < starttime): if self.new_timer_copy is None: if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False self.copyTimer(starttime, duration) self.timer.log( 0, "[VPS] copied this timer, since the event may start later than this timer ends" ) elif not self.activated_auto_increase: self.activate_autoincrease() elif data[0] == "EVENT_OVER" or data[0] == "CANNOT_FIND_EVENT": self.max_extending_timer = 2 * 3600 if self.activated_auto_increase: self.timer.autoincrease = False self.activated_auto_increase = False elif data[0] == "PDC_MULTIPLE_FOUND_EVENT": self.check_and_add_event(int(data[1])) # Programm meldet, dass die EIT (present/following) des Senders offenbar # momentan fehlerhaft ist elif data[0] == "EIT_APPARENTLY_UNRELIABLE": if self.timer.vpsplugin_overwrite: self.timer.vpsplugin_overwrite = False self.timer.log( 0, "[VPS] can't trust EPG currently, go to safe mode") def activate_autoincrease(self): if not self.activated_auto_increase: self.activated_auto_increase = True self.timer.autoincrease = True self.timer.autoincreasetime = 60 if self.org_timer_end == 0: self.org_timer_end = self.timer.end self.timer.log(0, "[VPS] enable autoincrease") if self.new_timer_copy is not None and ( self.new_timer_copy in self.session.nav.RecordTimer.timer_list): self.new_timer_copy.afterEvent = AFTEREVENT.NONE self.new_timer_copy.dontSave = True NavigationInstance.instance.RecordTimer.removeEntry( self.new_timer_copy) self.new_timer_copy = None self.timer.log(0, "[VPS] delete timer copy") # Noch ein Event aufnehmen? def set_next_event(self): if not self.timer.vpsplugin_overwrite and len(self.next_events) > 0: if not self.activated_auto_increase: self.activate_autoincrease() (starttime, neweventid) = self.next_events.pop(0) self.timer.eit = neweventid self.dont_restart_program = False self.program_abort() self.timer.log(0, "[VPS] record now event_id " + str(neweventid)) vps_timers.checksoon(3000) return True else: return False def program_abort(self): if self.program_running or self.program_try_search_running: #self.program.sendCtrlC() self.program.kill() self.program_running = False self.program_try_search_running = False self.timer.log(0, "[VPS] stop monitoring") def stop_simulation(self): if self.simulate_recordService: NavigationInstance.instance.stopRecordService( self.simulate_recordService) self.simulate_recordService = None self.timer.log(0, "[VPS] stop RecordService (simulation)") def check_and_add_event(self, neweventid): if not config.plugins.vps.allow_seeking_multiple_pdc.value: return epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, neweventid) if evt: evt_begin = evt.getBeginTime() + 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 if evt_begin < self.timer.begin: return for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - evt_begin) > 3600 * 2: break compareString = checktimer.service_ref.ref.toCompareString() if compareString == self.timer.service_ref.ref.toCompareString( ) or compareString == self.rec_ref.toCompareString(): if checktimer.eit == neweventid: return if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if checktimer.vpsplugin_enabled is None or not checktimer.vpsplugin_enabled: return # manuell angelegter Timer mit VPS if checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = neweventid checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription() checktimer.vpsplugin_time = None checktimer.log( 0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)" ) return # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: check_already_existing = [ x for (x, y) in self.next_events if y == neweventid ] if len(check_already_existing) > 0: start = check_already_existing.pop() if start == evt_begin: return else: self.next_events.remove((start, neweventid)) self.timer.log( 0, "[VPS] delete event_id " + str(neweventid) + " because of delay " + str(evt_begin - start)) self.next_events.append((evt_begin, neweventid)) self.next_events = sorted(self.next_events) self.timer.log(0, "[VPS] add event_id " + str(neweventid)) else: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.dirname = self.timer.dirname newEntry.log( 0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)" ) # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. res = NavigationInstance.instance.RecordTimer.record(newEntry) self.timer.log(0, "[VPS] added another timer, res " + str(res)) def copyTimer(self, start, duration): starttime = start - config.recording.margin_before.getValue() * 60 endtime = start + duration + config.recording.margin_after.getValue( ) * 60 self.new_timer_copy = RecordTimerEntry( ServiceReference(self.rec_ref), starttime, endtime, self.timer.name, self.timer.description, self.timer.eit, False, False, AFTEREVENT.AUTO, False, self.timer.dirname, self.timer.tags) self.new_timer_copy.vpsplugin_enabled = True self.new_timer_copy.vpsplugin_overwrite = self.timer.vpsplugin_overwrite self.new_timer_copy.log(0, "[VPS] added this timer") NavigationInstance.instance.RecordTimer.record(self.new_timer_copy) # startet den Hintergrundprozess def program_do_start(self, mode): if self.program_running or self.program_try_search_running: self.program_abort() if mode == 1: self.demux = -1 current_service = NavigationInstance.instance.getCurrentService() if current_service: stream = current_service.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux == -1: return self.program_try_search_running = True self.program_running = False mode_program = 1 else: self.program_try_search_running = False self.program_running = True mode_program = 0 sid = self.rec_ref.getData(1) tsid = self.rec_ref.getData(2) onid = self.rec_ref.getData(3) demux = "/dev/dvb/adapter0/demux" + str(self.demux) # PDC-Zeit? if (self.timer.name == "" or self.timer.eit is None ) and self.timer.vpsplugin_time is not None and not self.found_pdc: mode_program += 2 day = strftime("%d", localtime(self.timer.vpsplugin_time)) month = strftime("%m", localtime(self.timer.vpsplugin_time)) hour = strftime("%H", localtime(self.timer.vpsplugin_time)) minute = strftime("%M", localtime(self.timer.vpsplugin_time)) cmd = vps_exe + " " + demux + " " + str(mode_program) + " " + str( onid) + " " + str(tsid) + " " + str( sid ) + " 0 " + day + " " + month + " " + hour + " " + minute self.timer.log(0, "[VPS] seek PDC-Time") self.program.execute(cmd) return cmd = vps_exe + " " + demux + " " + str(mode_program) + " " + str( onid) + " " + str(tsid) + " " + str(sid) + " " + str( self.timer.eit) self.timer.log(0, "[VPS] start monitoring running-status") self.program.execute(cmd) def program_start(self): self.demux = -1 if self.dont_restart_program: return self.rec_ref = self.timer.service_ref and self.timer.service_ref.ref if self.rec_ref and self.rec_ref.flags & eServiceReference.isGroup: self.rec_ref = getBestPlayableServiceReference( self.rec_ref, eServiceReference()) # recordService (Simulation) ggf. starten if self.timer.state == TimerEntry.StateWaiting: if self.simulate_recordService is None: if self.rec_ref: self.simulate_recordService = NavigationInstance.instance.recordService( self.rec_ref, True) if self.simulate_recordService: res = self.simulate_recordService.start() self.timer.log( 0, "[VPS] start recordService (simulation) " + str(res)) if res != 0 and res != -1: # Fehler aufgetreten (kein Tuner frei?) NavigationInstance.instance.stopRecordService( self.simulate_recordService) self.simulate_recordService = None # in einer Minute ggf. nochmal versuchen if 60 < self.nextExecution: self.nextExecution = 60 # Bei Overwrite versuchen ohne Fragen auf Sender zu schalten if self.timer.vpsplugin_overwrite == True: cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference( ) if cur_ref and not cur_ref.getPath( ) and self.rec_ref.toCompareString( ) != cur_ref.toCompareString(): self.timer.log( 9, "[VPS-Plugin] zap without asking (simulation)" ) Notifications.AddNotification( MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n" ), type=MessageBox.TYPE_INFO, timeout=20) NavigationInstance.instance.playService( self.rec_ref) if 3 < self.nextExecution: self.nextExecution = 3 else: # ansonsten versuchen auf dem aktuellen Transponder/Kanal nach Infos zu suchen if not self.program_try_search_running: self.program_do_start(1) else: # Simulation hat geklappt if 1 < self.nextExecution: self.nextExecution = 1 else: # Simulation läuft schon # hole Demux stream = self.simulate_recordService.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux == -1: # ist noch nicht soweit(?), in einer Sekunde erneut versuchen if 1 < self.nextExecution: self.nextExecution = 1 else: self.program_do_start(0) elif self.timer.state == TimerEntry.StatePrepared or self.timer.state == TimerEntry.StateRunning: stream = self.timer.record_service.stream() if stream: streamdata = stream.getStreamingData() if (streamdata and ('demux' in streamdata)): self.demux = streamdata['demux'] if self.demux != -1: self.program_do_start(0) # überprüft, ob etwas zu tun ist und gibt die Sekunden zurück, bis die Funktion # spätestens wieder aufgerufen werden sollte # oder -1, um vps_timer löschen zu lassen def check(self): # Simulation ggf. stoppen if self.timer.state > TimerEntry.StateWaiting and self.simulate_recordService: self.stop_simulation() # VPS wurde wieder deaktiviert oder Timer wurde beendet if self.timer is None or self.timer.state == TimerEntry.StateEnded or self.timer.cancelled: self.program_abort() self.stop_simulation() return -1 if self.timer.vpsplugin_enabled == False or config.plugins.vps.enabled.value == False: if self.activated_auto_increase: self.timer.autoincrease = False self.program_abort() self.stop_simulation() return -1 self.nextExecution = 180 if config.plugins.vps.initial_time.value < 2 and self.timer.vpsplugin_overwrite: initial_time = 120 else: initial_time = config.plugins.vps.initial_time.value * 60 if self.timer.vpsplugin_overwrite == True: if self.timer.state == TimerEntry.StateWaiting or self.timer.state == TimerEntry.StatePrepared: # Startzeit verschieben if (self.timer.begin - 60) < time(): if self.org_timer_begin == 0: self.org_timer_begin = self.timer.begin elif (self.org_timer_begin + self.max_extending_timer) < time(): # Sendung begann immer noch nicht -> abbrechen self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() self.timer.log( 0, "[VPS] abort timer, waited enough to find Event-ID" ) return -1 self.timer.begin += 60 if (self.timer.end - self.timer.begin) < 300: self.timer.end += 180 # auf Timer-Konflikt prüfen timersanitycheck = TimerSanityCheck( self.session.nav.RecordTimer.timer_list, self.timer) if not timersanitycheck.check(): self.timer.abort() self.session.nav.RecordTimer.doActivate(self.timer) self.program_abort() self.stop_simulation() self.timer.log( 0, "[VPS] abort timer due to TimerSanityCheck") return -1 self.session.nav.RecordTimer.timeChanged(self.timer) if 30 < self.nextExecution: self.nextExecution = 30 # Programm starten if not self.program_running: if self.timer.state == TimerEntry.StateRunning: self.program_start() elif initial_time > 0: if (self.timer.begin - initial_time) <= time(): self.program_start() else: n = self.timer.begin - initial_time - time() if n < self.nextExecution: self.nextExecution = n if self.timer.state == TimerEntry.StateRunning: if self.activated_auto_increase and self.org_timer_end != 0 and ( self.org_timer_end + (4 * 3600)) < time(): # Aufnahme läuft seit 4 Stunden im Autoincrease -> abbrechen self.timer.autoincrease = False self.activated_auto_increase = False self.dont_restart_program = True self.program_abort() self.stop_simulation() self.timer.log(0, "[VPS] stop recording, too much autoincrease") try: if self.timer.vpsplugin_wasTimerWakeup: self.timer.vpsplugin_wasTimerWakeup = False if not Screens.Standby.inTryQuitMainloop: RecordTimerEntry.TryQuitMainloop(False) except: pass return self.nextExecution
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, existing, timerdict, moviedict, taskname, simulateOnly=False): new = 0 modified = 0 # enable multiple timer if services or bouquets specified (eg. recording the same event on sd service and hd service) enable_multiple_timer = ( (timer.services and 's' in config.plugins.autotimer.enable_multiple_timer.value or False) or (timer.bouquets and 'b' in config.plugins.autotimer.enable_multiple_timer.value or False)) # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value # Workaround to allow search for umlauts if we know the encoding match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": epgmatches = [] casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() test = [] if timer.services: test = [(service, 0, -1, -1) for service in timer.services] elif timer.bouquets: for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if services: while True: service = services.getNext() if not service.valid(): break playable = not ( service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or ( service.flags & eServiceReference.isNumberedMarker) if playable: test.append((service.toString(), 0, -1, -1)) else: # Get all bouquets bouquetlist = [] refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) mask = eServiceReference.isDirectory if config.usage.multibouquet.value: bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & mask: info = serviceHandler.info(s) if info: bouquetlist.append(s) else: info = serviceHandler.info(bouquetroot) if info: bouquetlist.append(bouquetroot) if bouquetlist: for bouquet in bouquetlist: if not bouquet.valid(): break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if services: while True: service = services.getNext() if not service.valid(): break playable = not ( service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or ( service.flags & eServiceReference.isNumberedMarker) if playable: test.append( (service.toString(), 0, -1, -1)) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) or match in ( extdesc if casesensitive else extdesc.lower()): epgmatches.append((serviceref, eit, name, begin, duration, shortdesc, extdesc)) else: # Search EPG, default to empty list if timer.searchType in typeMap: EPG_searchType = typeMap[timer.searchType] else: EPG_searchType = typeMap["partial"] epgmatches = epgcache.search( ('RITBDSE', 3000, EPG_searchType, match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches preveit = False for idx, (serviceref, eit, name, begin, duration, shortdesc, extdesc) in enumerate(epgmatches): eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) evtBegin = begin evtEnd = end = begin + duration if not evt: msg = "[AutoTimer] Could not create Event!" print(msg) skipped.append( (name, begin, end, str(serviceref), timer.name, msg)) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n - 1) serviceref = i.toString() # If event starts in less than 60 seconds skip it # if begin < time() + 60: # print ("[AutoTimer] Skipping " + name + " because it starts in less than 60 seconds") # skipped += 1 # continue # Set short description to equal extended description if it is empty. if not shortdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: msg = "[AutoTimer] Skipping an event because of maximum days in future is reached" # print(msg) skipped.append( (name, begin, end, serviceref, timer.name, msg)) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or (not similarTimer and ( timer.checkTimespan(timestamp) or timer.checkTimeframe(begin) )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek): msg = "[AutoTimer] Skipping an event because of filter check" # print(msg) skipped.append((name, begin, end, serviceref, timer.name, msg)) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) offsetBegin = timer.offset[0] offsetEnd = timer.offset[1] else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 offsetBegin = config.recording.margin_before.value * 60 offsetEnd = config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name)) if simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkSimilarity(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc")): # print("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: msg = "[AutoTimer] Skipping an event because movie already exists" # print(msg) skipped.append( (name, begin, end, serviceref, timer.name, msg)) continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit or ( config.plugins.autotimer.try_guessing.getValue() and timeSimilarityPercent(rtimer, evtBegin, evtEnd, timer) > 80): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated: # print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") break if eit == preveit: break try: # protect against vps plugin not being present vps_changed = rtimer.vpsplugin_enabled != timer.vps_enabled or rtimer.vpsplugin_overwrite != timer.vps_overwrite except AttributeError: vps_changed = False if (evtBegin - offsetBegin != rtimer.begin ) or (evtEnd + offsetEnd != rtimer.end) or ( shortdesc != rtimer.description) or vps_changed: if rtimer.isAutoTimer and eit == rtimer.eit: # print ("[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) # rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) preveit = eit else: if config.plugins.autotimer.refresh.getValue( ) != "all": # print("[AutoTimer] Won't modify existing timer because it's no timer set by us") break rtimer.log( 501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, rtimer.name)) newEntry = rtimer modified += 1 self.modifyTimer(rtimer, name, shortdesc, begin, end, serviceref, eit) # rtimer.log(501, "[AutoTimer] AutoTimer modified timer: %s ." % (rtimer.name)) break else: # print ("[AutoTimer] Skipping timer because it has not changed.") existing.append( (name, begin, end, serviceref, timer.name)) break elif timer.avoidDuplicateDescription >= 1 and not rtimer.disabled: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): # print("[AutoTimer] We found a timer with similar description, skipping event") oldExists = True break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles for rtimer in chain.from_iterable(itervalues(timerdict)): if not rtimer.disabled: if self.checkDoubleTimers(timer, name, rtimer.name, begin, rtimer.begin, end, rtimer.end, serviceref, str(rtimer.service_ref), enable_multiple_timer): oldExists = True print( "[AutoTimer] We found a timer with same StartTime, skipping event" ) break if timer.avoidDuplicateDescription >= 2: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True # print("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: continue if timer.checkCounter(timestamp): # print("[AutoTimer] Not adding new timer because counter is depleted.") continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newEntry.log( 500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)) newEntry.log(509, "[AutoTimer] Timer start on: %s" % ctime(begin)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) newEntry.isAutoTimer = True newEntry.autoTimerId = timer.id # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite if hasattr(timer, 'always_zap') and hasattr( newEntry, 'always_zap'): newEntry.always_zap = timer.always_zap tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if TAG not in tags: tags.append(TAG) if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace( " ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % ( conflictString) print(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts and not timer.hasOffset( ) and not config.recording.margin_before.value and not config.recording.margin_after.value and len( conflicts) > 1: change_end = change_begin = False conflict_begin = conflicts[1].begin conflict_end = conflicts[1].end if conflict_begin == newEntry.end: newEntry.end -= 30 change_end = True elif newEntry.begin == conflict_end: newEntry.begin += 30 change_begin = True if change_end or change_begin: conflicts = recordHandler.record(newEntry) if conflicts: if change_end: newEntry.end += 30 elif change_begin: newEntry.begin -= 30 else: print( "[AutoTimer] The conflict is resolved by offset time begin/end (30 sec) for %s." % newEntry.name) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join([ "%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts ]) print("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i + idx + 1) % lepgm] if self.checkSimilarity(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True): # Check if the similar is already known if eitS not in similardict: # print("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append( (servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) if renameTimer is not None and timer.series_labeling: renameTimer(newEntry, name, evtBegin, evtEnd) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append( (name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append( (name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % ( conflictString) print(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) self.result = (new, modified) self.completed.append(taskname) sleep(0.5)
def check_and_add_event(self, neweventid): if not config.plugins.vps.allow_seeking_multiple_pdc.value: return epgcache = eEPGCache.getInstance() evt = epgcache.lookupEventId(self.rec_ref, neweventid) if evt: evt_begin = evt.getBeginTime() + 60 evt_end = evt.getBeginTime() + evt.getDuration() - 60 if evt_begin < self.timer.begin: return for checktimer in self.session.nav.RecordTimer.timer_list: if checktimer == self.timer: continue if (checktimer.begin - evt_begin) > 3600 * 2: break compareString = checktimer.service_ref.ref.toCompareString() if compareString == self.timer.service_ref.ref.toCompareString( ) or compareString == self.rec_ref.toCompareString(): if checktimer.eit == neweventid: return if checktimer.begin <= evt_begin and checktimer.end >= evt_end: if checktimer.vpsplugin_enabled is None or not checktimer.vpsplugin_enabled: return # manuell angelegter Timer mit VPS if checktimer.name == "" and checktimer.vpsplugin_time is not None: checktimer.eit = neweventid checktimer.name = evt.getEventName() checktimer.description = evt.getShortDescription() checktimer.vpsplugin_time = None checktimer.log( 0, "[VPS] changed timer (found same PDC-Time as in other VPS-recording)" ) return # eigenen Timer überprüfen, wenn Zeiten nicht überschrieben werden dürfen if not self.timer.vpsplugin_overwrite and evt_begin <= self.timer.end: check_already_existing = [ x for (x, y) in self.next_events if y == neweventid ] if len(check_already_existing) > 0: start = check_already_existing.pop() if start == evt_begin: return else: self.next_events.remove((start, neweventid)) self.timer.log( 0, "[VPS] delete event_id " + str(neweventid) + " because of delay " + str(evt_begin - start)) self.next_events.append((evt_begin, neweventid)) self.next_events = sorted(self.next_events) self.timer.log(0, "[VPS] add event_id " + str(neweventid)) else: newevent_data = parseEvent(evt) newEntry = RecordTimerEntry(ServiceReference(self.rec_ref), *newevent_data) newEntry.vpsplugin_enabled = True newEntry.vpsplugin_overwrite = True newEntry.dirname = self.timer.dirname newEntry.log( 0, "[VPS] added this timer (found same PDC-Time as in other VPS-recording)" ) # Wenn kein Timer-Konflikt auftritt, wird der Timer angelegt. res = NavigationInstance.instance.RecordTimer.record(newEntry) self.timer.log(0, "[VPS] added another timer, res " + str(res))
def parseEPG(self, simulateOnly = False): if NavigationInstance.instance is None: print("[AutoTimer] Navigation is not available, can't parse EPG") return (0, 0, 0, [], [], []) total = 0 new = 0 modified = 0 timers = [] conflicting = [] similar = defaultdict(list) # Contains the the marked similar eits and the conflicting strings similars = [] # Contains the added similar timers # NOTE: the config option specifies "the next X days" which means today (== 1) + X delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1) evtLimit = mktime((date.today() + delta).timetuple()) checkEvtLimit = delta.days > 1 del delta # Read AutoTimer configuration self.readXml() # Get E2 instances epgcache = eEPGCache.getInstance() serviceHandler = eServiceCenter.getInstance() recordHandler = NavigationInstance.instance.RecordTimer # Save Recordings in a dict to speed things up a little # We include processed timers as we might search for duplicate descriptions # The recordict is always filled #Question: It might be better to name it timerdict #Question: Move to a separate function getTimerDict() #Note: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match recorddict = defaultdict(list) for timer in chain(recordHandler.timer_list, recordHandler.processed_timers): if timer and timer.service_ref: if timer.eit is not None: event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit) extdesc = event and event.getExtendedDescription() or '' timer.extdesc = extdesc elif not hasattr(timer, 'extdesc'): timer.extdesc = '' recorddict[str(timer.service_ref)].append(timer) # Create dict of all movies in all folders used by an autotimer to compare with recordings # The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings #Question: It might be better to name it recorddict moviedict = defaultdict(list) # Iterate Timer for timer in self.getEnabledTimerList(): # Precompute timer destination dir dest = timer.destination or config.usage.default_path.value # Workaround to allow search for umlauts if we know the encoding match = timer.match if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass # Search EPG, default to empty list epgmatches = epgcache.search(('RITBDSE', 1000, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Reset the the marked similar servicerefs similar.clear() # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): #Question: Do we need this? #Question: Move to separate function getRealService() eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: print("[AutoTimer] Could not create Event!") continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: print("[AutoTimer] Skipping an event because it starts in less than 60 seconds") continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similar: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches to not care about the day/time they are on, so ignore them if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or (not similarTimer and (\ timer.checkTimespan(timestamp) \ or timer.checkTimeframe(begin) \ )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek): continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) total += 1 # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name)) if simulateOnly: continue # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False # Eventually create cache if dest and dest not in moviedict: #Question: Move to a separate function getRecordDict() movielist = serviceHandler.list(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest)) if movielist is None: print("[AutoTimer] listing of movies in " + dest + " failed") else: append = moviedict[dest].append while 1: movieref = movielist.getNext() if not movieref.valid(): break if movieref.flags & eServiceReference.mustDescent: continue info = serviceHandler.info(movieref) if info is None: continue event = info.getEvent(movieref) if event is None: continue append({ "name": info.getName(movieref), "shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription), "extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string? }) del append for movieinfo in moviedict.get(dest, ()): if movieinfo.get("name") == name \ and movieinfo.get("shortdesc") == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match extdescM = movieinfo.get("extdesc") if ( len(extdesc) == len(extdescM) and extdesc == extdescM ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescM).ratio() ): print("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: continue # Initialize newEntry = None oldExists = False # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in recorddict.get(serviceref, ()): if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated: print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") break if hasattr(rtimer, "isAutoTimer"): rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)) else: if config.plugins.autotimer.refresh.value != "all": print("[AutoTimer] Won't modify existing timer because it's no timer set by us") break rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name)) newEntry = rtimer modified += 1 # Modify values saved in timer newEntry.name = name newEntry.description = shortdesc newEntry.begin = int(begin) newEntry.end = int(end) newEntry.service_ref = ServiceReference(serviceref) break elif timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled \ and rtimer.name == name \ and rtimer.description == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ): oldExists = True print("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(recorddict) ): if not rtimer.disabled \ and rtimer.name == name \ and rtimer.description == shortdesc: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ): oldExists = True print("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: continue if timer.checkCounter(timestamp): print("[AutoTimer] Not adding new timer because counter is depleted.") continue newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = timer.destination newEntry.justplay = timer.justplay newEntry.tags = timer.tags newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similar[eit].conflictString newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString)) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) print("[AutoTimer] conflict with %s detected" % (conflictString)) if conflicts and config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if shortdesc == shortdescS: # Some channels indicate replays in the extended descriptions # If the similarity percent is higher then 0.8 it is a very close match if ( len(extdesc) == len(extdescS) and extdesc == extdescS ) \ or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescS).ratio() ): # Check if the similar is already known if eitS not in similar: print("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similar[eit] = newEntry similar[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time break else: similarTimer = False newEntry = similar[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc recorddict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similar.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString)) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (total, new, modified, timers, conflicting, similars)