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 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 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