def UPDT(self, args): # <id> <settings> args = args.split(None, 1) if len(args) != 2: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) try: timerId = int(args[0]) except ValueError: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) list = self.getTimerList() if timerId < 1: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) if len(list) >= timerId: oldTimer = list[timerId - 1] else: oldTimer = None try: flags, channelid, datestring, beginstring, endstring, priority, lifetime, name, description = args[1].split(':') flags = int(flags) service_ref = ServiceReference(self.channelList[int(channelid)-1]) datestruct = strptime(datestring, '%Y-%m-%d') timestruct = strptime(beginstring, '%H%M') begin = mktime((datestruct.tm_year, datestruct.tm_mon, datestruct.tm_mday, timestruct.tm_hour, timestruct.tm_min, 0, datestruct.tm_wday, datestruct.tm_yday, -1)) timestruct = strptime(endstring, '%H%M') end = mktime((datestruct.tm_year, datestruct.tm_mon, datestruct.tm_mday, timestruct.tm_hour, timestruct.tm_min, 0, datestruct.tm_wday, datestruct.tm_yday, -1)) del datestruct, timestruct except ValueError as e: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) except KeyError as e: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) if end < begin: end += 86400 # Add 1 day, beware - this is evil and might not work correctly due to dst timer = RecordTimerEntry(service_ref, begin, end, name, description, 0, disabled=flags & 1 == 0) if oldTimer: recordTimer.removeEntry(oldTimer) timer.justplay = oldTimer.justplay timer.afterEvent = oldTimer.afterEvent timer.dirname = oldTimer.dirname timer.tags = oldTimer.tags timer.log_entries = oldTimer.log_entries conflict = recordTimer.record(timer) if conflict is None: return self.sendTimerLine(timer, timerId, last=True) else: payload = "%d timer conflict detected, original timer lost." % (CODE_ERR_LOCAL,) return self.sendLine(payload)
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 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 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 = [] 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 timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers: recorddict.setdefault(str(timer.service_ref), []).append(timer) # Iterate Timer for timer in self.getEnabledTimerList(): # 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 epgcache = eEPGCache.getInstance() ret = epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or () for serviceref, eit in ret: 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() # Gather Information name = evt.getEventName() description = evt.getShortDescription() begin = evt.getBeginTime() duration = evt.getDuration() end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check Duration, Timespan and Excludes if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or timer.checkTimespan(timestamp) \ or timer.checkFilter(name, description, evt.getExtendedDescription(), 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((name, begin, end, serviceref, timer.name)) if simulateOnly: 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" or rtimer.repeated: print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer" break if hasattr(rtimer, "isAutoTimer"): print "[AutoTimer] Modifying existing AutoTimer!" else: if config.plugins.autotimer.refresh.value != "all": print "[AutoTimer] Won't modify existing timer because it's no timer set by us" break print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us" newEntry = rtimer modified += 1 # Modify values saved in timer newEntry.name = name newEntry.description = description 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 == description: oldExists = True print "[AutoTimer] We found a timer 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: # I thinks thats the fastest way to do this, though it's a little ugly try: for list in recorddict.values(): for rtimer in list: if not rtimer.disabled and rtimer.name == name and rtimer.description == description: raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event") except AutoTimerIgnoreTimerException, etite: print etite continue if timer.checkCounter(timestamp): continue print "[AutoTimer] Adding an event." newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit) # 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 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: 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((name, begin, end, serviceref, timer.name)) if conflicts is None: timer.decrementCounter() new += 1 recorddict.setdefault(serviceref, []).append(newEntry) else: conflicting.append((name, begin, end, serviceref, timer.name))
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)
payload = "%d argument error" % (CODE_SYNTAX, ) return self.sendLine(payload) if end < begin: end += 86400 # Add 1 day, beware - this is evil and might not work correctly due to dst timer = RecordTimerEntry(service_ref, begin, end, name, description, 0, disabled=flags & 1 == 0) if oldTimer: recordTimer.removeEntry(oldTimer) timer.justplay = oldTimer.justplay timer.afterEvent = oldTimer.afterEvent timer.dirname = oldTimer.dirname timer.tags = oldTimer.tags timer.log_entries = oldTimer.log_entries conflict = recordTimer.record(timer) if conflict is None: return self.sendTimerLine(timer, timerId, last=True) else: payload = "%d timer conflict detected, original timer lost." % ( CODE_ERR_LOCAL, ) return self.sendLine(payload) def NEWT(self, args): self.UPDT("999999999 " + args)
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 = [] 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 timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers: recorddict.setdefault(str(timer.service_ref), []).append(timer) # Iterate Timer for timer in self.getEnabledTimerList(): # 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 epgcache = eEPGCache.getInstance() ret = epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or () for serviceref, eit in ret: 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() # Gather Information name = evt.getEventName() description = evt.getShortDescription() begin = evt.getBeginTime() duration = evt.getDuration() end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check Duration, Timespan and Excludes if timer.checkServices(serviceref) \ or timer.checkDuration(duration) \ or timer.checkTimespan(timestamp) \ or timer.checkFilter(name, description, evt.getExtendedDescription(), 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((name, begin, end, serviceref, timer.name)) if simulateOnly: 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" or rtimer.repeated: print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer" break if hasattr(rtimer, "isAutoTimer"): print "[AutoTimer] Modifying existing AutoTimer!" else: if config.plugins.autotimer.refresh.value != "all": print "[AutoTimer] Won't modify existing timer because it's no timer set by us" break print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us" newEntry = rtimer modified += 1 # Modify values saved in timer newEntry.name = name newEntry.description = description 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 == description: oldExists = True print "[AutoTimer] We found a timer 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: # I thinks thats the fastest way to do this, though it's a little ugly try: for list in recorddict.values(): for rtimer in list: if not rtimer.disabled and rtimer.name == name and rtimer.description == description: raise AutoTimerIgnoreTimerException( "We found a timer with same description, skipping event" ) except AutoTimerIgnoreTimerException, etite: print etite continue if timer.checkCounter(timestamp): continue print "[AutoTimer] Adding an event." newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit) # 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 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: 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( (name, begin, end, serviceref, timer.name)) if conflicts is None: timer.decrementCounter() new += 1 recorddict.setdefault(serviceref, []).append(newEntry) else: conflicting.append( (name, begin, end, serviceref, timer.name))
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)
timestruct = strptime(endstring, '%H%M') end = mktime((datestruct.tm_year, datestruct.tm_mon, datestruct.tm_mday, timestruct.tm_hour, timestruct.tm_min, 0, datestruct.tm_wday, datestruct.tm_yday, -1)) del datestruct, timestruct except ValueError, e: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) except KeyError, e: payload = "%d argument error" % (CODE_SYNTAX,) return self.sendLine(payload) if end < begin: end += 86400 # Add 1 day, beware - this is evil and might not work correctly due to dst timer = RecordTimerEntry(service_ref, begin, end, name, description, 0, disabled=flags & 1 == 0) if oldTimer: recordTimer.removeEntry(oldTimer) timer.justplay = oldTimer.justplay timer.afterEvent = oldTimer.afterEvent timer.dirname = oldTimer.dirname timer.tags = oldTimer.tags timer.log_entries = oldTimer.log_entries conflict = recordTimer.record(timer) if conflict is None: return self.sendTimerLine(timer, timerId, last=True) else: payload = "%d timer conflict detected, original timer lost." % (CODE_ERR_LOCAL,) return self.sendLine(payload) def NEWT(self, args): self.UPDT("999999999 " + args) def MODT(self, args):
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)